Moved from Gradle to bld
This commit is contained in:
parent
1cd7c5a79e
commit
c68c25aa95
240 changed files with 11508 additions and 1650 deletions
190
bin/main/net/thauvin/erik/mobibot/Addons.kt
Normal file
190
bin/main/net/thauvin/erik/mobibot/Addons.kt
Normal file
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* Addons.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.notContains
|
||||
import net.thauvin.erik.mobibot.commands.AbstractCommand
|
||||
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
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Modules and Commands addons.
|
||||
*/
|
||||
class Addons(private val props: Properties) {
|
||||
private val logger: Logger = LoggerFactory.getLogger(Addons::class.java)
|
||||
private val disabledModules = props.getProperty("disabled-modules", "").split(LinksManager.TAG_MATCH)
|
||||
private val disableCommands = props.getProperty("disabled-commands", "").split(LinksManager.TAG_MATCH)
|
||||
|
||||
val commands: MutableList<AbstractCommand> = mutableListOf()
|
||||
val modules: MutableList<AbstractModule> = mutableListOf()
|
||||
val names = Names
|
||||
|
||||
/**
|
||||
* Add a module with properties.
|
||||
*/
|
||||
fun add(module: AbstractModule): Boolean {
|
||||
var enabled = false
|
||||
with(module) {
|
||||
if (disabledModules.notContains(name, true)) {
|
||||
if (hasProperties()) {
|
||||
propertyKeys.forEach {
|
||||
setProperty(it, props.getProperty(it, ""))
|
||||
}
|
||||
}
|
||||
|
||||
if (isEnabled) {
|
||||
modules.add(this)
|
||||
names.modules.add(name)
|
||||
names.commands.addAll(commands)
|
||||
enabled = true
|
||||
} else {
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug("Module $name is disabled.")
|
||||
}
|
||||
names.disabledModules.add(name)
|
||||
}
|
||||
} else {
|
||||
names.disabledModules.add(name)
|
||||
}
|
||||
}
|
||||
return enabled
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a command with properties.
|
||||
*/
|
||||
fun add(command: AbstractCommand): Boolean {
|
||||
var enabled = false
|
||||
with(command) {
|
||||
if (disableCommands.notContains(name, true)) {
|
||||
if (properties.isNotEmpty()) {
|
||||
properties.keys.forEach {
|
||||
setProperty(it, props.getProperty(it, ""))
|
||||
}
|
||||
}
|
||||
if (isEnabled()) {
|
||||
commands.add(this)
|
||||
if (isVisible) {
|
||||
if (isOpOnly) {
|
||||
names.ops.add(name)
|
||||
} else {
|
||||
names.commands.add(name)
|
||||
}
|
||||
}
|
||||
enabled = true
|
||||
} else {
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug("Command $name is disabled.")
|
||||
}
|
||||
names.disabledCommands.add(name)
|
||||
}
|
||||
} else {
|
||||
names.disabledCommands.add(name)
|
||||
}
|
||||
}
|
||||
return enabled
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command or module.
|
||||
*/
|
||||
fun exec(channel: String, cmd: String, args: String, event: GenericMessageEvent): Boolean {
|
||||
val cmds = if (event is PrivateMessageEvent) commands else commands.filter { it.isPublic }
|
||||
for (command in cmds) {
|
||||
if (command.name.startsWith(cmd)) {
|
||||
command.commandResponse(channel, args, event)
|
||||
return true
|
||||
}
|
||||
}
|
||||
val mods = if (event is PrivateMessageEvent) modules.filter { it.isPrivateMsgEnabled } else modules
|
||||
for (module in mods) {
|
||||
if (module.commands.contains(cmd)) {
|
||||
module.commandResponse(channel, cmd, args, event)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a command.
|
||||
*/
|
||||
fun match(channel: String, event: GenericMessageEvent): Boolean {
|
||||
for (command in commands) {
|
||||
if (command.matches(event.message)) {
|
||||
command.commandResponse(channel, event.message, event)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Commands and Modules help.
|
||||
*/
|
||||
fun help(channel: String, topic: String, event: GenericMessageEvent): Boolean {
|
||||
for (command in commands) {
|
||||
if (command.isVisible && command.name.startsWith(topic)) {
|
||||
return command.helpResponse(channel, topic, event)
|
||||
}
|
||||
}
|
||||
for (module in modules) {
|
||||
if (module.commands.contains(topic)) {
|
||||
return module.helpResponse(event)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds commands and modules names.
|
||||
*/
|
||||
object Names {
|
||||
val modules: MutableList<String> = mutableListOf()
|
||||
val disabledModules: MutableList<String> = mutableListOf()
|
||||
val commands: MutableList<String> = mutableListOf()
|
||||
val disabledCommands: MutableList<String> = mutableListOf()
|
||||
val ops: MutableList<String> = mutableListOf()
|
||||
|
||||
fun sort() {
|
||||
modules.sort()
|
||||
disabledModules.sort()
|
||||
commands.sort()
|
||||
disabledCommands.sort()
|
||||
ops.sort()
|
||||
}
|
||||
}
|
||||
}
|
102
bin/main/net/thauvin/erik/mobibot/Constants.kt
Normal file
102
bin/main/net/thauvin/erik/mobibot/Constants.kt
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Constants.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot
|
||||
|
||||
/**
|
||||
* The `Constants`.
|
||||
*/
|
||||
object Constants {
|
||||
/**
|
||||
* The connect/read timeout in ms.
|
||||
*/
|
||||
const val CONNECT_TIMEOUT = 5000
|
||||
|
||||
/**
|
||||
* Debug command line argument.
|
||||
*/
|
||||
const val DEBUG_ARG = "debug"
|
||||
|
||||
/**
|
||||
* Default IRC Port.
|
||||
*/
|
||||
const val DEFAULT_PORT = 6667
|
||||
|
||||
/**
|
||||
* Default IRC Server.
|
||||
*/
|
||||
const val DEFAULT_SERVER = "irc.libera.chat"
|
||||
|
||||
/**
|
||||
* CLI command for usage.
|
||||
*/
|
||||
const val CLI_CMD = "java -jar ${ReleaseInfo.PROJECT}.jar"
|
||||
|
||||
/**
|
||||
* User-Agent
|
||||
*/
|
||||
const val USER_AGENT =
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
|
||||
|
||||
/**
|
||||
* The help command.
|
||||
*/
|
||||
const val HELP_CMD = "help"
|
||||
|
||||
/**
|
||||
* The link command.
|
||||
*/
|
||||
const val LINK_CMD = "L"
|
||||
|
||||
/**
|
||||
* The empty title string.
|
||||
*/
|
||||
const val NO_TITLE = "No Title"
|
||||
|
||||
/**
|
||||
* Properties command line argument.
|
||||
*/
|
||||
const val PROPS_ARG = "properties"
|
||||
|
||||
/**
|
||||
* The tag command
|
||||
*/
|
||||
const val TAG_CMD = "T"
|
||||
|
||||
/**
|
||||
* The timer delay in minutes.
|
||||
*/
|
||||
const val TIMER_DELAY = 10L
|
||||
|
||||
/**
|
||||
* Properties version line argument.
|
||||
*/
|
||||
const val VERSION_ARG = "version"
|
||||
}
|
92
bin/main/net/thauvin/erik/mobibot/FeedReader.kt
Normal file
92
bin/main/net/thauvin/erik/mobibot/FeedReader.kt
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* FeedReader.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot
|
||||
|
||||
import com.rometools.rome.io.FeedException
|
||||
import com.rometools.rome.io.SyndFeedInput
|
||||
import com.rometools.rome.io.XmlReader
|
||||
import net.thauvin.erik.mobibot.Utils.green
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.entries.FeedsManager
|
||||
import net.thauvin.erik.mobibot.msg.Message
|
||||
import net.thauvin.erik.mobibot.msg.NoticeMessage
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* Reads an RSS feed.
|
||||
*/
|
||||
class FeedReader(private val url: String, val event: GenericMessageEvent) : Runnable {
|
||||
private val logger: Logger = LoggerFactory.getLogger(FeedsManager::class.java)
|
||||
|
||||
/**
|
||||
* Fetches the Feed's items.
|
||||
*/
|
||||
override fun run() {
|
||||
try {
|
||||
readFeed(url).forEach {
|
||||
event.sendMessage("", it)
|
||||
}
|
||||
} catch (e: FeedException) {
|
||||
if (logger.isWarnEnabled) logger.warn("Unable to parse the feed at $url", e)
|
||||
event.sendMessage("An error has occurred while parsing the feed: ${e.message}")
|
||||
} catch (e: IOException) {
|
||||
if (logger.isWarnEnabled) logger.warn("Unable to fetch the feed at $url", e)
|
||||
event.sendMessage("An IO error has occurred while fetching the feed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Throws(FeedException::class, IOException::class)
|
||||
fun readFeed(url: String, maxItems: Int = 5): List<Message> {
|
||||
val messages = mutableListOf<Message>()
|
||||
val input = SyndFeedInput()
|
||||
XmlReader(URL(url).openStream()).use { reader ->
|
||||
val feed = input.build(reader)
|
||||
val items = feed.entries
|
||||
if (items.isEmpty()) {
|
||||
messages.add(NoticeMessage("There is currently nothing to view."))
|
||||
} else {
|
||||
items.take(maxItems).forEach {
|
||||
messages.add(NoticeMessage(it.title))
|
||||
messages.add(NoticeMessage(helpFormat(it.link.green(), false)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return messages
|
||||
}
|
||||
}
|
||||
}
|
421
bin/main/net/thauvin/erik/mobibot/Mobibot.kt
Normal file
421
bin/main/net/thauvin/erik/mobibot/Mobibot.kt
Normal file
|
@ -0,0 +1,421 @@
|
|||
/*
|
||||
* Mobibot.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot
|
||||
|
||||
import kotlinx.cli.ArgParser
|
||||
import kotlinx.cli.ArgType
|
||||
import kotlinx.cli.default
|
||||
import net.thauvin.erik.mobibot.Utils.appendIfMissing
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.capitalise
|
||||
import net.thauvin.erik.mobibot.Utils.getIntProperty
|
||||
import net.thauvin.erik.mobibot.Utils.helpCmdSyntax
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import net.thauvin.erik.mobibot.Utils.lastOrEmpty
|
||||
import net.thauvin.erik.mobibot.Utils.sendList
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.Utils.toIsoLocalDate
|
||||
import net.thauvin.erik.mobibot.commands.*
|
||||
import net.thauvin.erik.mobibot.commands.Recap.Companion.storeRecap
|
||||
import net.thauvin.erik.mobibot.commands.links.*
|
||||
import net.thauvin.erik.mobibot.commands.seen.Seen
|
||||
import net.thauvin.erik.mobibot.commands.tell.Tell
|
||||
import net.thauvin.erik.mobibot.modules.*
|
||||
import net.thauvin.erik.semver.Version
|
||||
import org.pircbotx.Configuration
|
||||
import org.pircbotx.PircBotX
|
||||
import org.pircbotx.hooks.ListenerAdapter
|
||||
import org.pircbotx.hooks.events.*
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.*
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@Version(properties = "version.properties", className = "ReleaseInfo", template = "version.mustache", type = "kt")
|
||||
class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Properties) : ListenerAdapter() {
|
||||
// The bot configuration.
|
||||
private val config: Configuration
|
||||
|
||||
// Commands and Modules
|
||||
private val addons: Addons
|
||||
|
||||
// Seen command
|
||||
private val seen: Seen
|
||||
|
||||
// Tell command
|
||||
private val tell: Tell
|
||||
|
||||
/** Logger. */
|
||||
val logger: Logger = LoggerFactory.getLogger(Mobibot::class.java)
|
||||
|
||||
/**
|
||||
* Connects to the server and joins the channel.
|
||||
*/
|
||||
fun connect() {
|
||||
PircBotX(config).startBot()
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds with the default help.
|
||||
*/
|
||||
private fun helpDefault(event: GenericMessageEvent) {
|
||||
event.sendMessage("Type a URL on $channel to post it.")
|
||||
event.sendMessage("For more information on a specific command, type:")
|
||||
event.sendMessage(
|
||||
helpFormat(
|
||||
helpCmdSyntax("%c ${Constants.HELP_CMD} <command>", event.bot().nick, event is PrivateMessageEvent)
|
||||
)
|
||||
)
|
||||
event.sendMessage("The commands are:")
|
||||
event.sendList(addons.names.commands, 8, isBold = true, isIndent = true)
|
||||
if (event.isChannelOp(channel)) {
|
||||
if (addons.names.disabledCommands.isNotEmpty()) {
|
||||
event.sendMessage("The disabled commands are:")
|
||||
event.sendList(addons.names.disabledCommands, 8, isBold = false, isIndent = true)
|
||||
}
|
||||
event.sendMessage("The op commands are:")
|
||||
event.sendList(addons.names.ops, 8, isBold = true, isIndent = true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds with the default, commands or modules help.
|
||||
*/
|
||||
private fun helpResponse(event: GenericMessageEvent, topic: String) {
|
||||
if (topic.isBlank() || !addons.help(channel, topic.lowercase().trim(), event)) {
|
||||
helpDefault(event)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAction(event: ActionEvent?) {
|
||||
event?.channel?.let {
|
||||
if (channel == it.name) {
|
||||
event.user?.let { user ->
|
||||
storeRecap(user.nick, event.action, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisconnect(event: DisconnectEvent?) {
|
||||
event?.let {
|
||||
with(event.getBot<PircBotX>()) {
|
||||
LinksManager.socialManager.notification("$nick disconnected from $serverHostname")
|
||||
seen.add(userChannelDao.getChannel(channel).users)
|
||||
}
|
||||
}
|
||||
LinksManager.socialManager.shutdown()
|
||||
}
|
||||
|
||||
override fun onPrivateMessage(event: PrivateMessageEvent?) {
|
||||
event?.user?.let { user ->
|
||||
if (logger.isTraceEnabled) logger.trace("<<< ${user.nick}: ${event.message}")
|
||||
val cmds = event.message.trim().split(" ".toRegex(), 2)
|
||||
val cmd = cmds[0].lowercase()
|
||||
val args = cmds.lastOrEmpty().trim()
|
||||
if (cmd.startsWith(Constants.HELP_CMD)) { // help
|
||||
helpResponse(event, args)
|
||||
} else if (!addons.exec(channel, cmd, args, event)) { // Execute command or module
|
||||
helpDefault(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onJoin(event: JoinEvent?) {
|
||||
event?.user?.let { user ->
|
||||
with(event.getBot<PircBotX>()) {
|
||||
if (user.nick == nick) {
|
||||
LinksManager.socialManager.notification(
|
||||
"$nick has joined ${event.channel.name} on $serverHostname"
|
||||
)
|
||||
seen.add(userChannelDao.getChannel(channel).users)
|
||||
} else {
|
||||
tell.send(event)
|
||||
seen.add(user.nick)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMessage(event: MessageEvent?) {
|
||||
event?.user?.let { user ->
|
||||
tell.send(event)
|
||||
if (event.message.matches("(?i)${Pattern.quote(event.bot().nick)}:.*".toRegex())) { // mobibot: <command>
|
||||
if (logger.isTraceEnabled) logger.trace(">>> ${user.nick}: ${event.message}")
|
||||
val cmds = event.message.substring(event.bot().nick.length + 1).trim().split(" ".toRegex(), 2)
|
||||
val cmd = cmds[0].lowercase()
|
||||
val args = cmds.lastOrEmpty().trim()
|
||||
if (cmd.startsWith(Constants.HELP_CMD)) { // mobibot: help
|
||||
helpResponse(event, args)
|
||||
} else {
|
||||
// Execute module or command
|
||||
addons.exec(channel, cmd, args, event)
|
||||
}
|
||||
} else if (addons.match(channel, event)) { // Links, e.g.: https://www.example.com/ or L1: , etc.
|
||||
if (logger.isTraceEnabled) logger.trace(">>> ${user.nick}: ${event.message}")
|
||||
}
|
||||
storeRecap(user.nick, event.message, false)
|
||||
seen.add(user.nick)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNickChange(event: NickChangeEvent?) {
|
||||
event?.let {
|
||||
tell.send(event)
|
||||
if (!it.oldNick.equals(it.newNick, true)) {
|
||||
seen.add(it.oldNick)
|
||||
}
|
||||
seen.add(it.newNick)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPart(event: PartEvent?) {
|
||||
event?.user?.let { user ->
|
||||
with(event.getBot<PircBotX>()) {
|
||||
if (user.nick == nick) {
|
||||
LinksManager.socialManager.notification(
|
||||
"$nick has left ${event.channel.name} on $serverHostname"
|
||||
)
|
||||
seen.add(userChannelDao.getChannel(channel).users)
|
||||
} else {
|
||||
seen.add(user.nick)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQuit(event: QuitEvent?) {
|
||||
event?.user?.let { user ->
|
||||
seen.add(user.nick)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Throws(Exception::class)
|
||||
fun main(args: Array<String>) {
|
||||
// Set up the command line options
|
||||
val parser = ArgParser(Constants.CLI_CMD)
|
||||
val debug by parser.option(
|
||||
ArgType.Boolean,
|
||||
Constants.DEBUG_ARG,
|
||||
Constants.DEBUG_ARG.substring(0, 1),
|
||||
"Print debug & logging data directly to the console"
|
||||
).default(false)
|
||||
val property by parser.option(
|
||||
ArgType.String,
|
||||
Constants.PROPS_ARG,
|
||||
Constants.PROPS_ARG.substring(0, 1),
|
||||
"Use alternate properties file"
|
||||
).default("./${ReleaseInfo.PROJECT}.properties")
|
||||
val version by parser.option(
|
||||
ArgType.Boolean,
|
||||
Constants.VERSION_ARG,
|
||||
Constants.VERSION_ARG.substring(0, 1),
|
||||
"Print version info"
|
||||
).default(false)
|
||||
|
||||
// Parse the command line
|
||||
parser.parse(args)
|
||||
|
||||
if (version) {
|
||||
// Output the version
|
||||
println(
|
||||
"${ReleaseInfo.PROJECT.capitalise()} ${ReleaseInfo.VERSION}" +
|
||||
" (${ReleaseInfo.BUILDDATE.toIsoLocalDate()})"
|
||||
)
|
||||
println(ReleaseInfo.WEBSITE)
|
||||
} else {
|
||||
// Load the properties
|
||||
val p = Properties()
|
||||
try {
|
||||
Files.newInputStream(
|
||||
Paths.get(property)
|
||||
).use { fis ->
|
||||
p.load(fis)
|
||||
}
|
||||
} catch (ignore: FileNotFoundException) {
|
||||
System.err.println("Unable to find properties file.")
|
||||
exitProcess(1)
|
||||
} catch (ignore: IOException) {
|
||||
System.err.println("Unable to open properties file.")
|
||||
exitProcess(1)
|
||||
}
|
||||
val nickname = p.getProperty("nick", Mobibot::class.java.name.lowercase())
|
||||
val channel = p.getProperty("channel")
|
||||
val logsDir = p.getProperty("logs", ".").appendIfMissing(File.separatorChar)
|
||||
|
||||
// Redirect stdout and stderr
|
||||
if (!debug) {
|
||||
try {
|
||||
val stdout = PrintStream(
|
||||
BufferedOutputStream(
|
||||
FileOutputStream(
|
||||
logsDir + channel.substring(1) + '.' + Utils.today() + ".log", true
|
||||
)
|
||||
), true
|
||||
)
|
||||
System.setOut(stdout)
|
||||
} catch (ignore: IOException) {
|
||||
System.err.println("Unable to open output (stdout) log file.")
|
||||
exitProcess(1)
|
||||
}
|
||||
try {
|
||||
val stderr = PrintStream(
|
||||
BufferedOutputStream(
|
||||
FileOutputStream("$logsDir$nickname.err", true)
|
||||
), true
|
||||
)
|
||||
System.setErr(stderr)
|
||||
} catch (ignore: IOException) {
|
||||
System.err.println("Unable to open error (stderr) log file.")
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Start the bot
|
||||
Mobibot(nickname, channel, logsDir, p).connect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the bot.
|
||||
*/
|
||||
init {
|
||||
val ircServer = p.getProperty("server", Constants.DEFAULT_SERVER)
|
||||
config = Configuration.Builder().apply {
|
||||
name = nickname
|
||||
login = p.getProperty("login", nickname)
|
||||
realName = p.getProperty("realname", nickname)
|
||||
addServer(
|
||||
ircServer,
|
||||
p.getIntProperty("port", Constants.DEFAULT_PORT)
|
||||
)
|
||||
addAutoJoinChannel(channel)
|
||||
addListener(this@Mobibot)
|
||||
version = "${ReleaseInfo.PROJECT} ${ReleaseInfo.VERSION}"
|
||||
isAutoNickChange = true
|
||||
val identPwd = p.getProperty("ident")
|
||||
if (!identPwd.isNullOrBlank()) {
|
||||
nickservPassword = identPwd
|
||||
}
|
||||
val identNick = p.getProperty("ident-nick")
|
||||
if (!identNick.isNullOrBlank()) {
|
||||
nickservNick = identNick
|
||||
}
|
||||
val identMsg = p.getProperty("ident-msg")
|
||||
if (!identMsg.isNullOrBlank()) {
|
||||
nickservCustomMessage = identMsg
|
||||
}
|
||||
isAutoReconnect = true
|
||||
|
||||
//socketConnectTimeout = Constants.CONNECT_TIMEOUT
|
||||
//socketTimeout = Constants.CONNECT_TIMEOUT
|
||||
//messageDelay = StaticDelay(500)
|
||||
}.buildConfiguration()
|
||||
|
||||
// Load the current entries
|
||||
with(LinksManager) {
|
||||
entries.channel = channel
|
||||
entries.ircServer = ircServer
|
||||
entries.logsDir = logsDirPath
|
||||
entries.backlogs = p.getProperty("backlogs", "")
|
||||
entries.load()
|
||||
|
||||
// Set up pinboard
|
||||
pinboard.setApiToken(p.getProperty("pinboard-api-token", ""))
|
||||
}
|
||||
|
||||
addons = Addons(p)
|
||||
|
||||
// Load the commands
|
||||
addons.add(ChannelFeed(channel.removePrefix("#")))
|
||||
addons.add(Comment())
|
||||
addons.add(Cycle())
|
||||
addons.add(Die())
|
||||
addons.add(Ignore())
|
||||
addons.add(LinksManager())
|
||||
addons.add(Me())
|
||||
addons.add(Modules(addons.names.modules, addons.names.disabledModules))
|
||||
addons.add(Msg())
|
||||
addons.add(Nick())
|
||||
addons.add(Posting())
|
||||
addons.add(Recap())
|
||||
addons.add(Say())
|
||||
|
||||
// Seen command
|
||||
seen = Seen("${logsDirPath}${nickname}-seen.ser")
|
||||
addons.add(seen)
|
||||
|
||||
addons.add(Tags())
|
||||
|
||||
// Tell command
|
||||
tell = Tell("${logsDirPath}${nickname}.ser")
|
||||
addons.add(tell)
|
||||
|
||||
addons.add(Users())
|
||||
addons.add(Versions())
|
||||
addons.add(View())
|
||||
|
||||
// Load social modules
|
||||
LinksManager.socialManager.add(addons, Mastodon())
|
||||
|
||||
// Load the modules
|
||||
addons.add(Calc())
|
||||
addons.add(ChatGpt())
|
||||
addons.add(CryptoPrices())
|
||||
addons.add(CurrencyConverter())
|
||||
addons.add(Dice())
|
||||
addons.add(GoogleSearch())
|
||||
addons.add(Info(tell, seen))
|
||||
addons.add(Joke())
|
||||
addons.add(Lookup())
|
||||
addons.add(Ping())
|
||||
addons.add(RockPaperScissors())
|
||||
addons.add(StockQuote())
|
||||
addons.add(War())
|
||||
addons.add(Weather2())
|
||||
addons.add(WolframAlpha())
|
||||
addons.add(WorldTime())
|
||||
|
||||
// Sort the addons
|
||||
addons.names.sort()
|
||||
}
|
||||
}
|
||||
|
113
bin/main/net/thauvin/erik/mobibot/Pinboard.kt
Normal file
113
bin/main/net/thauvin/erik/mobibot/Pinboard.kt
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Pinboard.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot
|
||||
|
||||
import net.thauvin.erik.mobibot.entries.EntryLink
|
||||
import net.thauvin.erik.pinboard.PinboardPoster
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Handles posts to pinboard.in.
|
||||
*/
|
||||
class Pinboard {
|
||||
private val poster = PinboardPoster()
|
||||
|
||||
/**
|
||||
* Adds a pin.
|
||||
*/
|
||||
fun addPin(ircServer: String, entry: EntryLink) {
|
||||
if (poster.apiToken.isNotBlank()) {
|
||||
with(entry) {
|
||||
poster.addPin(link, title, postedBy(ircServer), formatTags(), date.toTimestamp())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the pinboard API token.
|
||||
*/
|
||||
fun setApiToken(apiToken: String) {
|
||||
poster.apiToken = apiToken
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a pin.
|
||||
*/
|
||||
fun deletePin(entry: EntryLink) {
|
||||
if (poster.apiToken.isNotBlank()) {
|
||||
poster.deletePin(entry.link)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a pin.
|
||||
*/
|
||||
fun updatePin(ircServer: String, oldUrl: String, entry: EntryLink) {
|
||||
if (poster.apiToken.isNotBlank()) {
|
||||
with(entry) {
|
||||
if (oldUrl != link) {
|
||||
poster.deletePin(oldUrl)
|
||||
}
|
||||
poster.addPin(link, title, postedBy(ircServer), formatTags(), date.toTimestamp())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a date to a UTC timestamp.
|
||||
*/
|
||||
private fun Date.toTimestamp(): String {
|
||||
return ZonedDateTime.ofInstant(
|
||||
toInstant().truncatedTo(ChronoUnit.SECONDS), ZoneId.systemDefault()
|
||||
).format(DateTimeFormatter.ISO_INSTANT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the tags for pinboard.
|
||||
*/
|
||||
private fun EntryLink.formatTags(): String {
|
||||
return nick + formatTags(",", ",")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pinboard.in extended attribution line.
|
||||
*/
|
||||
private fun EntryLink.postedBy(ircServer: String): String {
|
||||
return "Posted by $nick on $channel ( $ircServer )"
|
||||
}
|
||||
}
|
||||
|
439
bin/main/net/thauvin/erik/mobibot/Utils.kt
Normal file
439
bin/main/net/thauvin/erik/mobibot/Utils.kt
Normal file
|
@ -0,0 +1,439 @@
|
|||
/*
|
||||
* Utils.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot
|
||||
|
||||
import net.thauvin.erik.mobibot.msg.Message
|
||||
import net.thauvin.erik.mobibot.msg.Message.Companion.DEFAULT_COLOR
|
||||
import net.thauvin.erik.urlencoder.UrlEncoderUtil
|
||||
import org.jsoup.Jsoup
|
||||
import org.pircbotx.Colors
|
||||
import org.pircbotx.PircBotX
|
||||
import org.pircbotx.hooks.events.PrivateMessageEvent
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import java.io.*
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.fileSize
|
||||
|
||||
/**
|
||||
* Miscellaneous utilities.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
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.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String.appendIfMissing(suffix: Char): String {
|
||||
return if (last() != suffix) {
|
||||
"$this${suffix}"
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the given int bold.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun Int.bold(): String = toString().bold()
|
||||
|
||||
/**
|
||||
* Makes the given long bold.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun Long.bold(): String = toString().bold()
|
||||
|
||||
/**
|
||||
* Makes the given string bold.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String?.bold(): String = colorize(Colors.BOLD)
|
||||
|
||||
/**
|
||||
* Returns the [PircBotX] instance.
|
||||
*/
|
||||
fun GenericMessageEvent.bot(): PircBotX {
|
||||
return getBot() as PircBotX
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitalize a string.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String.capitalise(): String = lowercase().replaceFirstChar { it.uppercase() }
|
||||
|
||||
/**
|
||||
* Capitalize words
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String.capitalizeWords(): String = split(" ").joinToString(" ") { it.capitalise() }
|
||||
|
||||
/**
|
||||
* Colorize a string.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String?.colorize(color: String): String {
|
||||
return when {
|
||||
isNullOrEmpty() -> {
|
||||
""
|
||||
}
|
||||
|
||||
color == DEFAULT_COLOR -> {
|
||||
this
|
||||
}
|
||||
|
||||
Colors.BOLD == color || Colors.REVERSE == color -> {
|
||||
color + this + color
|
||||
}
|
||||
|
||||
else -> {
|
||||
color + this + Colors.NORMAL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the given string cyan.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String?.cyan(): String = colorize(Colors.CYAN)
|
||||
|
||||
/**
|
||||
* URL encodes the given string.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String.encodeUrl(): String = UrlEncoderUtil.encode(this)
|
||||
|
||||
/**
|
||||
* Returns a property as an int.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun Properties.getIntProperty(key: String, defaultValue: Int): Int {
|
||||
return getProperty(key)?.toIntOrDefault(defaultValue) ?: defaultValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the given string green.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String?.green(): String = colorize(Colors.DARK_GREEN)
|
||||
|
||||
/**
|
||||
* Build a help command by replacing `%c` with the bot's pub/priv command, and `%n` with the bot's
|
||||
* nick.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun helpCmdSyntax(text: String, botNick: String, isPrivate: Boolean): String {
|
||||
val replace = arrayOf(if (isPrivate) "/msg $botNick" else "$botNick:", botNick)
|
||||
return text.replaceEach(searchFlags, replace)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted help string.
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun helpFormat(help: String, isBold: Boolean = true, isIndent: Boolean = true): String {
|
||||
val s = if (isBold) help.bold() else help
|
||||
return if (isIndent) s.prependIndent() else s
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the specified user is an operator on the [channel].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun GenericMessageEvent.isChannelOp(channel: String): Boolean {
|
||||
return this.bot().userChannelDao.getChannel(channel).isOp(this.user)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if a HTTP status code indicates a successful response.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun Int.isHttpSuccess() = this in 200..399
|
||||
|
||||
/**
|
||||
* Returns the last item of a list of strings or empty if none.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun List<String>.lastOrEmpty(): String {
|
||||
return if (this.size >= 2) {
|
||||
this.last()
|
||||
} else
|
||||
""
|
||||
}
|
||||
|
||||
/**
|
||||
* Load serial data from file.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun loadSerialData(file: String, default: Any, logger: Logger, description: String): Any {
|
||||
val serialFile = Paths.get(file)
|
||||
if (serialFile.exists() && serialFile.fileSize() > 0) {
|
||||
try {
|
||||
ObjectInputStream(
|
||||
BufferedInputStream(Files.newInputStream(serialFile))
|
||||
).use { input ->
|
||||
if (logger.isDebugEnabled) logger.debug("Loading the ${description}.")
|
||||
return input.readObject()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
logger.error("An IO error occurred loading the ${description}.", e)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
logger.error("An error occurred loading the ${description}.", e)
|
||||
}
|
||||
}
|
||||
return default
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the list does not contain the given string.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun List<String>.notContains(text: String, ignoreCase: Boolean = false) = this.none { it.equals(text, ignoreCase) }
|
||||
|
||||
/**
|
||||
* Obfuscates the given string.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String.obfuscate(): String {
|
||||
return if (isNotBlank()) {
|
||||
"x".repeat(length)
|
||||
} else this
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plural form of a word, if count > 1.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String.plural(count: Long): String {
|
||||
return if (count > 1) "${this}s" else this
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the given string red.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String?.red(): String = colorize(Colors.RED)
|
||||
|
||||
/**
|
||||
* Replaces all occurrences of Strings within another String.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String.replaceEach(search: Array<out String>, replace: Array<out String>): String {
|
||||
var result = this
|
||||
if (search.size == replace.size) {
|
||||
search.forEachIndexed { i, s ->
|
||||
result = result.replace(s, replace[i])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the given string reverse color.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String?.reverseColor(): String = colorize(Colors.REVERSE)
|
||||
|
||||
/**
|
||||
* Save data
|
||||
*/
|
||||
@JvmStatic
|
||||
fun saveSerialData(file: String, data: Any, logger: Logger, description: String) {
|
||||
try {
|
||||
BufferedOutputStream(Files.newOutputStream(Paths.get(file))).use { bos ->
|
||||
ObjectOutputStream(bos).use { output ->
|
||||
if (logger.isDebugEnabled) logger.debug("Saving the ${description}.")
|
||||
output.writeObject(data)
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
logger.error("Unable to save the ${description}.", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a formatted commands/modules, etc. list.
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun GenericMessageEvent.sendList(
|
||||
list: List<String>,
|
||||
maxPerLine: Int,
|
||||
separator: String = " ",
|
||||
isBold: Boolean = false,
|
||||
isIndent: Boolean = false
|
||||
) {
|
||||
var i = 0
|
||||
while (i < list.size) {
|
||||
sendMessage(
|
||||
helpFormat(
|
||||
list.subList(i, list.size.coerceAtMost(i + maxPerLine)).joinToString(separator, truncated = ""),
|
||||
isBold,
|
||||
isIndent
|
||||
),
|
||||
)
|
||||
i += maxPerLine
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a [message].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun GenericMessageEvent.sendMessage(channel: String, message: Message) {
|
||||
if (message.isNotice) {
|
||||
bot().sendIRC().notice(user.nick, message.msg.colorize(message.color))
|
||||
} else if (message.isPrivate || this is PrivateMessageEvent || channel.isBlank()) {
|
||||
respondPrivateMessage(message.msg.colorize(message.color))
|
||||
} else {
|
||||
bot().sendIRC().message(channel, message.msg.colorize(message.color))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a response as a private message or notice.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun GenericMessageEvent.sendMessage(message: String) {
|
||||
if (this is PrivateMessageEvent) {
|
||||
respondPrivateMessage(message)
|
||||
} else {
|
||||
bot().sendIRC().notice(user.nick, message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns today's date.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun today(): String = LocalDateTime.now().toIsoLocalDate()
|
||||
|
||||
/**
|
||||
* Converts a string to an int.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String.toIntOrDefault(defaultValue: Int): Int {
|
||||
return try {
|
||||
toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified date as an ISO local date string.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun Date.toIsoLocalDate(): String {
|
||||
return LocalDateTime.ofInstant(toInstant(), ZoneId.systemDefault()).toIsoLocalDate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified date as an ISO local date string.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun LocalDateTime.toIsoLocalDate(): String = format(DateTimeFormatter.ISO_LOCAL_DATE)
|
||||
|
||||
/**
|
||||
* Returns the specified date formatted as `yyyy-MM-dd HH:mm`.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun Date.toUtcDateTime(): String {
|
||||
return LocalDateTime.ofInstant(toInstant(), ZoneId.systemDefault()).toUtcDateTime()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified date formatted as `yyyy-MM-dd HH:mm`.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun LocalDateTime.toUtcDateTime(): String = format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
||||
|
||||
/**
|
||||
* Makes the given string bold.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String?.underline(): String = colorize(Colors.UNDERLINE)
|
||||
|
||||
|
||||
/**
|
||||
* Converts XML/XHTML entities to plain text.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun String.unescapeXml(): String = Jsoup.parse(this).text()
|
||||
|
||||
/**
|
||||
* Reads contents of a URL.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun URL.reader(): UrlReaderResponse {
|
||||
val connection = this.openConnection() as HttpURLConnection
|
||||
connection.setRequestProperty(
|
||||
"User-Agent",
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.0"
|
||||
)
|
||||
return if (connection.responseCode.isHttpSuccess()) {
|
||||
UrlReaderResponse(connection.responseCode, connection.inputStream.bufferedReader().use { it.readText() })
|
||||
} else {
|
||||
UrlReaderResponse(connection.responseCode, connection.errorStream.bufferedReader().use { it.readText() })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the [URL.reader] response code and body text.
|
||||
*/
|
||||
data class UrlReaderResponse(val responseCode: Int, val body: String)
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* AbstractCommand.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpCmdSyntax
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import org.pircbotx.hooks.events.PrivateMessageEvent
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
abstract class AbstractCommand {
|
||||
abstract val name: String
|
||||
abstract val help: List<String>
|
||||
abstract val isOpOnly: Boolean
|
||||
abstract val isPublic: Boolean
|
||||
abstract val isVisible: Boolean
|
||||
|
||||
val properties: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
abstract fun commandResponse(channel: String, args: String, event: GenericMessageEvent)
|
||||
|
||||
open fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean {
|
||||
if (!isOpOnly || isOpOnly == event.isChannelOp(channel)) {
|
||||
for (h in help) {
|
||||
event.sendMessage(helpCmdSyntax(h, event.bot().nick, event is PrivateMessageEvent || !isPublic))
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
open fun initProperties(vararg keys: String) {
|
||||
keys.forEach {
|
||||
properties[it] = ""
|
||||
}
|
||||
}
|
||||
|
||||
open fun isEnabled(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
open fun matches(message: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
open fun setProperty(key: String, value: String) {
|
||||
properties[key] = value
|
||||
}
|
||||
}
|
62
bin/main/net/thauvin/erik/mobibot/commands/ChannelFeed.kt
Normal file
62
bin/main/net/thauvin/erik/mobibot/commands/ChannelFeed.kt
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* ChannelFeed.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.FeedReader
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class ChannelFeed(channel: String) : AbstractCommand() {
|
||||
override val name = channel
|
||||
override val help = listOf("To list the last 5 posts from the channel's weblog feed:", helpFormat("%c $channel"))
|
||||
override val isOpOnly = false
|
||||
override val isPublic = true
|
||||
override val isVisible = true
|
||||
|
||||
companion object {
|
||||
const val FEED_PROP = "feed"
|
||||
}
|
||||
|
||||
init {
|
||||
initProperties(FEED_PROP)
|
||||
}
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
if (isEnabled()) {
|
||||
properties[FEED_PROP]?.let { FeedReader(it, event).run() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun isEnabled(): Boolean {
|
||||
return !properties[FEED_PROP].isNullOrBlank()
|
||||
}
|
||||
}
|
66
bin/main/net/thauvin/erik/mobibot/commands/Cycle.kt
Normal file
66
bin/main/net/thauvin/erik/mobibot/commands/Cycle.kt
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Cycle.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Cycle : AbstractCommand() {
|
||||
private val wait = 10
|
||||
override val name = "cycle"
|
||||
override val help = listOf("To have the bot leave the channel and come back:", helpFormat("%c $name"))
|
||||
override val isOpOnly = true
|
||||
override val isPublic = false
|
||||
override val isVisible = true
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
with(event.bot()) {
|
||||
if (event.isChannelOp(channel)) {
|
||||
runBlocking {
|
||||
launch {
|
||||
sendIRC().message(channel, "${event.user.nick} asked me to leave. I'll be back!")
|
||||
userChannelDao.getChannel(channel).send().part()
|
||||
delay(wait * 1000L)
|
||||
sendIRC().joinChannel(channel)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
helpResponse(channel, args, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
bin/main/net/thauvin/erik/mobibot/commands/Die.kt
Normal file
62
bin/main/net/thauvin/erik/mobibot/commands/Die.kt
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Die.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Die : AbstractCommand() {
|
||||
override val name = "die"
|
||||
override val help = emptyList<String>()
|
||||
override val isOpOnly = true
|
||||
override val isPublic = false
|
||||
override val isVisible = false
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
with(event.bot()) {
|
||||
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!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DIE_PROP = "die"
|
||||
}
|
||||
|
||||
init {
|
||||
initProperties(DIE_PROP)
|
||||
}
|
||||
}
|
147
bin/main/net/thauvin/erik/mobibot/commands/Ignore.kt
Normal file
147
bin/main/net/thauvin/erik/mobibot/commands/Ignore.kt
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Ignore.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bold
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpCmdSyntax
|
||||
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.LinksManager
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Ignore : AbstractCommand() {
|
||||
private val me = "me"
|
||||
|
||||
init {
|
||||
initProperties(IGNORE_PROP)
|
||||
}
|
||||
|
||||
override val name = IGNORE_CMD
|
||||
override val help = listOf(
|
||||
"To ignore a link posted to the channel:",
|
||||
helpFormat("https://www.foo.bar %n"),
|
||||
"To check your ignore status:",
|
||||
helpFormat("%c $name"),
|
||||
"To toggle your ignore status:",
|
||||
helpFormat("%c $name $me")
|
||||
)
|
||||
private val helpOp = help.plus(
|
||||
arrayOf("To add/remove nicks from the ignored list:", helpFormat("%c $name <nick> [<nick> ...]"))
|
||||
)
|
||||
|
||||
override val isOpOnly = false
|
||||
override val isPublic = true
|
||||
override val isVisible = true
|
||||
|
||||
companion object {
|
||||
const val IGNORE_CMD = "ignore"
|
||||
const val IGNORE_PROP = IGNORE_CMD
|
||||
private val ignored = mutableSetOf<String>()
|
||||
|
||||
@JvmStatic
|
||||
fun isNotIgnored(nick: String): Boolean {
|
||||
return !ignored.contains(nick.lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
val isMe = args.trim().equals(me, true)
|
||||
if (isMe || !event.isChannelOp(channel)) {
|
||||
val nick = event.user.nick.lowercase()
|
||||
ignoreNick(nick, isMe, event)
|
||||
} else {
|
||||
ignoreOp(args, event)
|
||||
}
|
||||
}
|
||||
|
||||
override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean {
|
||||
return if (event.isChannelOp(channel)) {
|
||||
for (h in helpOp) {
|
||||
event.sendMessage(helpCmdSyntax(h, event.bot().nick, true))
|
||||
}
|
||||
true
|
||||
} else {
|
||||
super.helpResponse(channel, topic, event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ignoreNick(sender: String, isMe: Boolean, event: GenericMessageEvent) {
|
||||
if (isMe) {
|
||||
if (ignored.remove(sender)) {
|
||||
event.sendMessage("You are no longer ignored.")
|
||||
} else {
|
||||
ignored.add(sender)
|
||||
event.sendMessage("You are now ignored.")
|
||||
}
|
||||
} else {
|
||||
if (ignored.contains(sender)) {
|
||||
event.sendMessage("You are currently ignored.")
|
||||
} else {
|
||||
event.sendMessage("You are not currently ignored.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ignoreOp(args: String, event: GenericMessageEvent) {
|
||||
if (args.isNotEmpty()) {
|
||||
val nicks = args.lowercase().split(" ")
|
||||
for (nick in nicks) {
|
||||
val ignore = if (me == nick) {
|
||||
nick.lowercase()
|
||||
} else {
|
||||
nick
|
||||
}
|
||||
if (!ignored.remove(ignore)) {
|
||||
ignored.add(ignore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ignored.isNotEmpty()) {
|
||||
event.sendMessage("The following nicks are ignored:")
|
||||
event.sendList(ignored.sorted(), 8, isIndent = true)
|
||||
} else {
|
||||
event.sendMessage("No one is currently ${"ignored".bold()}.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun setProperty(key: String, value: String) {
|
||||
super.setProperty(key, value)
|
||||
if (IGNORE_PROP == key) {
|
||||
ignored.addAll(value.split(LinksManager.TAG_MATCH))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
124
bin/main/net/thauvin/erik/mobibot/commands/Info.kt
Normal file
124
bin/main/net/thauvin/erik/mobibot/commands/Info.kt
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Info.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.ReleaseInfo
|
||||
import net.thauvin.erik.mobibot.Utils.capitalise
|
||||
import net.thauvin.erik.mobibot.Utils.green
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import net.thauvin.erik.mobibot.Utils.plural
|
||||
import net.thauvin.erik.mobibot.Utils.sendList
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
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
|
||||
import java.lang.management.ManagementFactory
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
class Info(private val tell: Tell, private val seen: Seen) : AbstractCommand() {
|
||||
private val allVersions = listOf(
|
||||
"${ReleaseInfo.PROJECT.capitalise()} ${ReleaseInfo.VERSION} (${ReleaseInfo.WEBSITE.green()})",
|
||||
"Written by ${ReleaseInfo.AUTHOR} (${ReleaseInfo.AUTHOR_URL.green()})"
|
||||
)
|
||||
override val name = "info"
|
||||
override val help = listOf("To view information about the bot:", helpFormat("%c $name"))
|
||||
override val isOpOnly = false
|
||||
override val isPublic = true
|
||||
override val isVisible = true
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Converts milliseconds to year month week day hour and minutes.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun Long.toUptime(): String {
|
||||
this.toDuration(DurationUnit.MILLISECONDS).toComponents { wholeDays, hours, minutes, seconds, _ ->
|
||||
val years = wholeDays / 365
|
||||
var days = wholeDays % 365
|
||||
val months = days / 30
|
||||
days %= 30
|
||||
val weeks = days / 7
|
||||
days %= 7
|
||||
|
||||
with(StringBuffer()) {
|
||||
if (years > 0) {
|
||||
append(years).append(" year".plural(years)).append(' ')
|
||||
}
|
||||
if (months > 0) {
|
||||
append(months).append(" month".plural(months)).append(' ')
|
||||
}
|
||||
if (weeks > 0) {
|
||||
append(weeks).append(" week".plural(weeks)).append(' ')
|
||||
}
|
||||
if (days > 0) {
|
||||
append(days).append(" day".plural(days)).append(' ')
|
||||
}
|
||||
if (hours > 0) {
|
||||
append(hours).append(" hour".plural(hours.toLong())).append(' ')
|
||||
}
|
||||
|
||||
if (minutes > 0) {
|
||||
append(minutes).append(" minute".plural(minutes.toLong()))
|
||||
} else {
|
||||
append(seconds).append(" second".plural(seconds.toLong()))
|
||||
}
|
||||
|
||||
return toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
event.sendList(allVersions, 1)
|
||||
val info = StringBuilder()
|
||||
info.append("Uptime: ")
|
||||
.append(ManagementFactory.getRuntimeMXBean().uptime.toUptime())
|
||||
.append(" [Entries: ")
|
||||
.append(LinksManager.entries.links.size)
|
||||
if (seen.isEnabled()) {
|
||||
info.append(", Seen: ").append(seen.count())
|
||||
}
|
||||
if (event.isChannelOp(channel)) {
|
||||
if (tell.isEnabled()) {
|
||||
info.append(", Messages: ").append(tell.size())
|
||||
}
|
||||
if (LinksManager.socialManager.entriesCount() > 0) {
|
||||
info.append(", Social: ").append(LinksManager.socialManager.entriesCount())
|
||||
}
|
||||
}
|
||||
info.append(", Recap: ").append(Recap.recaps.size).append(']')
|
||||
event.sendMessage(info.toString())
|
||||
}
|
||||
}
|
51
bin/main/net/thauvin/erik/mobibot/commands/Me.kt
Normal file
51
bin/main/net/thauvin/erik/mobibot/commands/Me.kt
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Me.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Me : AbstractCommand() {
|
||||
override val name = "me"
|
||||
override val help = listOf("To have the bot perform an action:", helpFormat("%c $name <action>"))
|
||||
override val isOpOnly = true
|
||||
override val isPublic = false
|
||||
override val isVisible = true
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
if (event.isChannelOp(channel)) {
|
||||
event.bot().sendIRC().action(channel, args)
|
||||
}
|
||||
}
|
||||
}
|
63
bin/main/net/thauvin/erik/mobibot/commands/Modules.kt
Normal file
63
bin/main/net/thauvin/erik/mobibot/commands/Modules.kt
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Modules.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import net.thauvin.erik.mobibot.Utils.sendList
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Modules(private val modules: List<String>, private val disabledModules: List<String>) : AbstractCommand() {
|
||||
override val name = "modules"
|
||||
override val help = listOf("To view a list of enabled/disabled modules:", helpFormat("%c $name"))
|
||||
override val isOpOnly = true
|
||||
override val isPublic = false
|
||||
override val isVisible = true
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
if (event.isChannelOp(channel)) {
|
||||
if (modules.isEmpty()) {
|
||||
event.respondPrivateMessage("There are no enabled modules.")
|
||||
} else {
|
||||
event.respondPrivateMessage("The enabled modules are: ")
|
||||
event.sendList(modules, 7, isIndent = true)
|
||||
}
|
||||
if (disabledModules.isNotEmpty()) {
|
||||
event.respondPrivateMessage("The disabled modules are: ")
|
||||
event.sendList(disabledModules, 7, isIndent = true)
|
||||
}
|
||||
} else {
|
||||
helpResponse(channel, args, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
60
bin/main/net/thauvin/erik/mobibot/commands/Msg.kt
Normal file
60
bin/main/net/thauvin/erik/mobibot/commands/Msg.kt
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Msg.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Msg : AbstractCommand() {
|
||||
override val name = "msg"
|
||||
override val help = listOf(
|
||||
"To have the bot send a private message to someone:",
|
||||
helpFormat("%c $name <nick> <text>")
|
||||
)
|
||||
override val isOpOnly = true
|
||||
override val isPublic = false
|
||||
override val isVisible = true
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
if (event.isChannelOp(channel)) {
|
||||
val msg = args.split(" ", limit = 2)
|
||||
if (args.length > 2) {
|
||||
event.bot().sendIRC().message(msg[0], msg[1])
|
||||
event.respondPrivateMessage("A message was sent to ${msg[0]}")
|
||||
} else {
|
||||
helpResponse(channel, args, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
bin/main/net/thauvin/erik/mobibot/commands/Nick.kt
Normal file
51
bin/main/net/thauvin/erik/mobibot/commands/Nick.kt
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Nick.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Nick : AbstractCommand() {
|
||||
override val name = "nick"
|
||||
override val help = listOf("To change the bot's nickname:", helpFormat("%c $name <new_nick>"))
|
||||
override val isOpOnly = true
|
||||
override val isPublic = true
|
||||
override val isVisible = true
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
if (event.isChannelOp(channel)) {
|
||||
event.bot().sendIRC().changeNick(args)
|
||||
}
|
||||
}
|
||||
}
|
81
bin/main/net/thauvin/erik/mobibot/commands/Recap.kt
Normal file
81
bin/main/net/thauvin/erik/mobibot/commands/Recap.kt
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Recap.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.Utils.toUtcDateTime
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import java.time.Clock
|
||||
import java.time.LocalDateTime
|
||||
|
||||
class Recap : AbstractCommand() {
|
||||
override val name = "recap"
|
||||
override val help = listOf(
|
||||
"To list the last 10 public channel messages:",
|
||||
helpFormat("%c $name")
|
||||
)
|
||||
override val isOpOnly = false
|
||||
override val isPublic = true
|
||||
override val isVisible = true
|
||||
|
||||
companion object {
|
||||
const val MAX_RECAPS = 10
|
||||
|
||||
@JvmField
|
||||
val recaps = mutableListOf<String>()
|
||||
|
||||
/**
|
||||
* Stores the last 10 public messages and actions.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun storeRecap(sender: String, message: String, isAction: Boolean) {
|
||||
recaps.add(
|
||||
LocalDateTime.now(Clock.systemUTC()).toUtcDateTime()
|
||||
+ " - $sender" + (if (isAction) " " else ": ") + message
|
||||
)
|
||||
if (recaps.size > MAX_RECAPS) {
|
||||
recaps.removeFirst()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
if (recaps.isNotEmpty()) {
|
||||
for (r in recaps) {
|
||||
event.sendMessage(r)
|
||||
}
|
||||
} else {
|
||||
event.sendMessage("Sorry, nothing to recap.")
|
||||
}
|
||||
}
|
||||
}
|
51
bin/main/net/thauvin/erik/mobibot/commands/Say.kt
Normal file
51
bin/main/net/thauvin/erik/mobibot/commands/Say.kt
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Say.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Say : AbstractCommand() {
|
||||
override val name = "say"
|
||||
override val help = listOf("To have the bot say something on the channel:", helpFormat("%c $name <text>"))
|
||||
override val isOpOnly = true
|
||||
override val isPublic = false
|
||||
override val isVisible = true
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
if (event.isChannelOp(channel)) {
|
||||
event.bot().sendIRC().message(channel, args)
|
||||
}
|
||||
}
|
||||
}
|
50
bin/main/net/thauvin/erik/mobibot/commands/Users.kt
Normal file
50
bin/main/net/thauvin/erik/mobibot/commands/Users.kt
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Users.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.sendList
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Users : AbstractCommand() {
|
||||
override val name = "users"
|
||||
override val help = listOf("To list the users present on the channel:", helpFormat("%c $name"))
|
||||
override val isOpOnly = false
|
||||
override val isPublic = true
|
||||
override val isVisible = true
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
val ch = event.bot().userChannelDao.getChannel(channel)
|
||||
event.sendList(ch.users.map { if (it.channelsOpIn.contains(ch)) "@${it.nick}" else it.nick }, 8)
|
||||
}
|
||||
}
|
59
bin/main/net/thauvin/erik/mobibot/commands/Versions.kt
Normal file
59
bin/main/net/thauvin/erik/mobibot/commands/Versions.kt
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Versions.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.commands
|
||||
|
||||
import net.thauvin.erik.mobibot.ReleaseInfo
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import net.thauvin.erik.mobibot.Utils.sendList
|
||||
import net.thauvin.erik.mobibot.Utils.toIsoLocalDate
|
||||
import org.pircbotx.PircBotX
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Versions : AbstractCommand() {
|
||||
private val allVersions = listOf(
|
||||
"Version: ${ReleaseInfo.VERSION} (${ReleaseInfo.BUILDDATE.toIsoLocalDate()})",
|
||||
"${System.getProperty("os.name")} ${System.getProperty("os.version")} (${System.getProperty("os.arch")})" +
|
||||
", JVM ${System.getProperty("java.runtime.version")}",
|
||||
"Kotlin ${KotlinVersion.CURRENT}, PircBotX ${PircBotX.VERSION}"
|
||||
)
|
||||
override val name = "versions"
|
||||
override val help = listOf("To view the versions data (bot, platform, java, etc.):", helpFormat("%c $name"))
|
||||
override val isOpOnly = true
|
||||
override val isPublic = false
|
||||
override val isVisible = true
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
if (event.isChannelOp(channel)) {
|
||||
event.sendList(allVersions, 1)
|
||||
}
|
||||
}
|
||||
}
|
151
bin/main/net/thauvin/erik/mobibot/commands/links/Comment.kt
Normal file
151
bin/main/net/thauvin/erik/mobibot/commands/links/Comment.kt
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Comment.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands.links
|
||||
|
||||
import net.thauvin.erik.mobibot.Constants
|
||||
import net.thauvin.erik.mobibot.Utils.bold
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.commands.AbstractCommand
|
||||
import net.thauvin.erik.mobibot.entries.EntriesUtils.printComment
|
||||
import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel
|
||||
import net.thauvin.erik.mobibot.entries.EntryLink
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Comment : AbstractCommand() {
|
||||
override val name = COMMAND
|
||||
override val help = listOf(
|
||||
"To add a comment:",
|
||||
helpFormat("${Constants.LINK_CMD}1:This is a comment"),
|
||||
"I will reply with a label, for example: ${Constants.LINK_CMD.bold()}1.1",
|
||||
"To edit a comment, use its label: ",
|
||||
helpFormat("${Constants.LINK_CMD}1.1:This is an edited comment"),
|
||||
"To delete a comment, use its label and a minus sign: ",
|
||||
helpFormat("${Constants.LINK_CMD}1.1:-")
|
||||
)
|
||||
override val isOpOnly = false
|
||||
override val isPublic = true
|
||||
override val isVisible = true
|
||||
|
||||
companion object {
|
||||
const val COMMAND = "comment"
|
||||
}
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
val cmds = args.substring(1).split("[.:]".toRegex(), 3)
|
||||
val entryIndex = cmds[0].toInt() - 1
|
||||
|
||||
if (entryIndex < 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()) {
|
||||
"" -> showComment(entry, entryIndex, commentIndex, event) // L1.1:
|
||||
"-" -> deleteComment(channel, entry, entryIndex, commentIndex, event) // L1.1:-
|
||||
else -> {
|
||||
if (cmd.startsWith('?')) { // L1.1:?<author>
|
||||
changeAuthor(channel, cmd, entry, entryIndex, commentIndex, event)
|
||||
} else { // L1.1:<comment>
|
||||
setComment(cmd, entry, entryIndex, commentIndex, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean {
|
||||
if (super.helpResponse(channel, topic, event)) {
|
||||
if (event.isChannelOp(channel)) {
|
||||
event.sendMessage("To change a comment's author:")
|
||||
event.sendMessage(helpFormat("${Constants.LINK_CMD}1.1:?<nick>"))
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun matches(message: String): Boolean {
|
||||
return message.matches("^${Constants.LINK_CMD}\\d+\\.\\d+:.*".toRegex())
|
||||
}
|
||||
|
||||
private fun changeAuthor(
|
||||
channel: String,
|
||||
cmd: String,
|
||||
entry: EntryLink,
|
||||
entryIndex: Int,
|
||||
commentIndex: Int,
|
||||
event: GenericMessageEvent
|
||||
) {
|
||||
if (event.isChannelOp(channel) && cmd.length > 1) {
|
||||
val comment = entry.getComment(commentIndex)
|
||||
comment.nick = cmd.substring(1)
|
||||
event.sendMessage(printComment(entryIndex, commentIndex, comment))
|
||||
LinksManager.entries.save()
|
||||
} else {
|
||||
event.sendMessage("Please ask a channel op to change the author of this comment for you.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteComment(
|
||||
channel: String,
|
||||
entry: EntryLink,
|
||||
entryIndex: Int,
|
||||
commentIndex: Int,
|
||||
event: GenericMessageEvent
|
||||
) {
|
||||
if (event.isChannelOp(channel) || event.user.nick == entry.getComment(commentIndex).nick) {
|
||||
entry.deleteComment(commentIndex)
|
||||
event.sendMessage("Comment ${entryIndex.toLinkLabel()}.${commentIndex + 1} removed.")
|
||||
LinksManager.entries.save()
|
||||
} else {
|
||||
event.sendMessage("Please ask a channel op to delete this comment for you.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun setComment(
|
||||
cmd: String,
|
||||
entry: EntryLink,
|
||||
entryIndex: Int,
|
||||
commentIndex: Int,
|
||||
event: GenericMessageEvent
|
||||
) {
|
||||
entry.setComment(commentIndex, cmd, event.user.nick)
|
||||
event.sendMessage(printComment(entryIndex, commentIndex, entry.getComment(commentIndex)))
|
||||
LinksManager.entries.save()
|
||||
}
|
||||
|
||||
private fun showComment(entry: EntryLink, entryIndex: Int, commentIndex: Int, event: GenericMessageEvent) {
|
||||
event.sendMessage(printComment(entryIndex, commentIndex, entry.getComment(commentIndex)))
|
||||
}
|
||||
}
|
207
bin/main/net/thauvin/erik/mobibot/commands/links/LinksManager.kt
Normal file
207
bin/main/net/thauvin/erik/mobibot/commands/links/LinksManager.kt
Normal file
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* LinksManager.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands.links
|
||||
|
||||
import net.thauvin.erik.mobibot.Constants
|
||||
import net.thauvin.erik.mobibot.Pinboard
|
||||
import net.thauvin.erik.mobibot.Utils.bold
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.Utils.today
|
||||
import net.thauvin.erik.mobibot.commands.AbstractCommand
|
||||
import net.thauvin.erik.mobibot.commands.Ignore.Companion.isNotIgnored
|
||||
import net.thauvin.erik.mobibot.entries.Entries
|
||||
import net.thauvin.erik.mobibot.entries.EntriesUtils.printLink
|
||||
import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel
|
||||
import net.thauvin.erik.mobibot.entries.EntryLink
|
||||
import net.thauvin.erik.mobibot.social.SocialManager
|
||||
import org.jsoup.Jsoup
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import java.io.IOException
|
||||
|
||||
class LinksManager : AbstractCommand() {
|
||||
private val defaultTags: MutableList<String> = mutableListOf()
|
||||
private val keywords: MutableList<String> = mutableListOf()
|
||||
|
||||
override val name = Constants.LINK_CMD
|
||||
override val help = emptyList<String>()
|
||||
override val isOpOnly = false
|
||||
override val isPublic = false
|
||||
override val isVisible = false
|
||||
|
||||
init {
|
||||
initProperties(TAGS_PROP, KEYWORDS_PROP)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LINK_MATCH = "^[hH][tT][tT][pP](|[sS])://.*".toRegex()
|
||||
const val KEYWORDS_PROP = "tags-keywords"
|
||||
const val TAGS_PROP = "tags"
|
||||
val TAG_MATCH = ", *| +".toRegex()
|
||||
|
||||
/**
|
||||
* Entries array
|
||||
*/
|
||||
@JvmField
|
||||
val entries = Entries()
|
||||
|
||||
/**
|
||||
* Pinboard handler.
|
||||
*/
|
||||
@JvmField
|
||||
val pinboard = Pinboard()
|
||||
|
||||
/**
|
||||
* Social Manager handler.
|
||||
*/
|
||||
@JvmField
|
||||
val socialManager = SocialManager()
|
||||
|
||||
/**
|
||||
* Let the user know if the entries are too old to be modified.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun isUpToDate(event: GenericMessageEvent): Boolean {
|
||||
if (entries.lastPubDate != today()) {
|
||||
event.sendMessage("The links are too old to be updated.")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
val cmds = args.split(" ".toRegex(), 2)
|
||||
val sender = event.user.nick
|
||||
val botNick = event.bot().nick
|
||||
val login = event.user.login
|
||||
|
||||
if (isNotIgnored(sender) && (cmds.size == 1 || !cmds[1].contains(botNick))) {
|
||||
val link = cmds[0].trim()
|
||||
if (!isDupEntry(link, event)) {
|
||||
var title = ""
|
||||
val tags = ArrayList<String>(defaultTags)
|
||||
if (cmds.size == 2) {
|
||||
val data = cmds[1].trim().split("${Tags.COMMAND}:", limit = 2)
|
||||
title = data[0].trim()
|
||||
if (data.size > 1) {
|
||||
tags.addAll(data[1].split(TAG_MATCH))
|
||||
}
|
||||
}
|
||||
|
||||
if (title.isBlank()) {
|
||||
title = fetchTitle(link)
|
||||
}
|
||||
|
||||
if (title != Constants.NO_TITLE) {
|
||||
matchTagKeywords(title, tags)
|
||||
}
|
||||
|
||||
// Links are old, clear them
|
||||
if (entries.lastPubDate != today()) {
|
||||
entries.links.clear()
|
||||
}
|
||||
|
||||
val entry = EntryLink(link, title, sender, login, channel, tags)
|
||||
entries.links.add(entry)
|
||||
val index = entries.links.lastIndexOf(entry)
|
||||
event.sendMessage(printLink(index, entry))
|
||||
|
||||
pinboard.addPin(event.bot().serverHostname, entry)
|
||||
|
||||
// Queue link for posting to social media.
|
||||
socialManager.queueEntry(index)
|
||||
|
||||
entries.save()
|
||||
|
||||
if (Constants.NO_TITLE == entry.title) {
|
||||
event.sendMessage("Please specify a title, by typing:")
|
||||
event.sendMessage(helpFormat("${index.toLinkLabel()}:|This is the title"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean = false
|
||||
|
||||
override fun matches(message: String): Boolean {
|
||||
return message.matches(LINK_MATCH)
|
||||
}
|
||||
|
||||
internal fun fetchTitle(link: String): String {
|
||||
try {
|
||||
val html = Jsoup.connect(link)
|
||||
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0")
|
||||
.get()
|
||||
val title = html.title()
|
||||
if (title.isNotBlank()) {
|
||||
return title
|
||||
}
|
||||
} catch (ignore: IOException) {
|
||||
// Do nothing
|
||||
}
|
||||
return Constants.NO_TITLE
|
||||
}
|
||||
|
||||
private fun isDupEntry(link: String, event: GenericMessageEvent): Boolean {
|
||||
synchronized(entries) {
|
||||
return try {
|
||||
val match = entries.links.single { it.link == link }
|
||||
event.sendMessage(
|
||||
"Duplicate".bold() + " >> " + printLink(entries.links.indexOf(match), match)
|
||||
)
|
||||
true
|
||||
} catch (ignore: NoSuchElementException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun matchTagKeywords(title: String, tags: MutableList<String>) {
|
||||
for (match in keywords) {
|
||||
val m = Regex.escape(match)
|
||||
if (title.matches("(?i).*\\b$m\\b.*".toRegex())) {
|
||||
tags.add(match)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setProperty(key: String, value: String) {
|
||||
super.setProperty(key, value)
|
||||
if (KEYWORDS_PROP == key) {
|
||||
keywords.addAll(value.split(TAG_MATCH))
|
||||
} else if (TAGS_PROP == key) {
|
||||
defaultTags.addAll(value.split(TAG_MATCH))
|
||||
}
|
||||
}
|
||||
}
|
164
bin/main/net/thauvin/erik/mobibot/commands/links/Posting.kt
Normal file
164
bin/main/net/thauvin/erik/mobibot/commands/links/Posting.kt
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Posting.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands.links
|
||||
|
||||
import net.thauvin.erik.mobibot.Constants
|
||||
import net.thauvin.erik.mobibot.Utils.bold
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.commands.AbstractCommand
|
||||
import net.thauvin.erik.mobibot.commands.links.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
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Posting : AbstractCommand() {
|
||||
override val name = "posting"
|
||||
override val help = listOf(
|
||||
"Post a URL, by saying it on a line on its own:",
|
||||
helpFormat("<url> [<title>] ${Tags.COMMAND}: <+tag> [...]]"),
|
||||
"I will reply with a label, for example: ${Constants.LINK_CMD.bold()}1",
|
||||
"To add a title, use its label and a pipe:",
|
||||
helpFormat("${Constants.LINK_CMD}1:|This is the title"),
|
||||
"To add a comment:",
|
||||
helpFormat("${Constants.LINK_CMD}1:This is a comment"),
|
||||
"I will reply with a label, for example: ${Constants.LINK_CMD.bold()}1.1",
|
||||
"To edit a comment, see: ",
|
||||
helpFormat("%c ${Constants.HELP_CMD} ${Comment.COMMAND}")
|
||||
)
|
||||
override val isOpOnly = false
|
||||
override val isPublic = true
|
||||
override val isVisible = true
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
val cmds = args.substring(1).split(":", limit = 2)
|
||||
val entryIndex = cmds[0].toInt() - 1
|
||||
|
||||
if (entryIndex < entries.links.size) {
|
||||
val cmd = cmds[1].trim()
|
||||
if (cmd.isBlank()) {
|
||||
showEntry(entryIndex, event) // L1:
|
||||
} else if (LinksManager.isUpToDate(event)) {
|
||||
if (cmd == "-") {
|
||||
removeEntry(channel, entryIndex, event) // L1:-
|
||||
} else {
|
||||
when (cmd[0]) {
|
||||
'|' -> changeTitle(cmd, entryIndex, event) // L1:|<title>
|
||||
'=' -> changeUrl(channel, cmd, entryIndex, event) // L1:=<url>
|
||||
'?' -> changeAuthor(channel, cmd, entryIndex, event) // L1:?<author>
|
||||
else -> addComment(cmd, entryIndex, event) // L1:<comment>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun matches(message: String): Boolean {
|
||||
return message.matches("${Constants.LINK_CMD}\\d+:.*".toRegex())
|
||||
}
|
||||
|
||||
private fun addComment(cmd: String, entryIndex: Int, event: GenericMessageEvent) {
|
||||
val entry: EntryLink = entries.links[entryIndex]
|
||||
val commentIndex = entry.addComment(cmd, event.user.nick)
|
||||
val comment = entry.getComment(commentIndex)
|
||||
event.sendMessage(EntriesUtils.printComment(entryIndex, commentIndex, comment))
|
||||
entries.save()
|
||||
}
|
||||
|
||||
private fun changeTitle(cmd: String, entryIndex: Int, event: GenericMessageEvent) {
|
||||
if (cmd.length > 1) {
|
||||
val entry: EntryLink = entries.links[entryIndex]
|
||||
entry.title = cmd.substring(1).trim()
|
||||
LinksManager.pinboard.updatePin(event.bot().serverHostname, entry.link, entry)
|
||||
event.sendMessage(EntriesUtils.printLink(entryIndex, entry))
|
||||
entries.save()
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeUrl(channel: String, cmd: String, entryIndex: Int, event: GenericMessageEvent) {
|
||||
val entry: EntryLink = entries.links[entryIndex]
|
||||
if (entry.login == event.user.login || event.isChannelOp(channel)) {
|
||||
val link = cmd.substring(1)
|
||||
if (link.matches(LinksManager.LINK_MATCH)) {
|
||||
val oldLink = entry.link
|
||||
entry.link = link
|
||||
LinksManager.pinboard.updatePin(event.bot().serverHostname, oldLink, entry)
|
||||
event.sendMessage(EntriesUtils.printLink(entryIndex, entry))
|
||||
entries.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeAuthor(channel: String, cmd: String, index: Int, event: GenericMessageEvent) {
|
||||
if (event.isChannelOp(channel)) {
|
||||
if (cmd.length > 1) {
|
||||
val entry: EntryLink = entries.links[index]
|
||||
entry.nick = cmd.substring(1)
|
||||
LinksManager.pinboard.updatePin(event.bot().serverHostname, entry.link, entry)
|
||||
event.sendMessage(EntriesUtils.printLink(index, entry))
|
||||
entries.save()
|
||||
}
|
||||
} else {
|
||||
event.sendMessage("Please ask a channel op to change the author of this link for you.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeEntry(channel: String, index: Int, event: GenericMessageEvent) {
|
||||
val entry: EntryLink = entries.links[index]
|
||||
if (entry.login == event.user.login || event.isChannelOp(channel)) {
|
||||
LinksManager.pinboard.deletePin(entry)
|
||||
LinksManager.socialManager.removeEntry(index)
|
||||
entries.links.removeAt(index)
|
||||
event.sendMessage("Entry ${index.toLinkLabel()} removed.")
|
||||
entries.save()
|
||||
} else {
|
||||
event.sendMessage("Please ask a channel op to remove this entry for you.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun showEntry(index: Int, event: GenericMessageEvent) {
|
||||
val entry: EntryLink = entries.links[index]
|
||||
event.sendMessage(EntriesUtils.printLink(index, entry))
|
||||
if (entry.tags.isNotEmpty()) {
|
||||
event.sendMessage(EntriesUtils.printTags(index, entry))
|
||||
}
|
||||
if (entry.comments.isNotEmpty()) {
|
||||
val comments = entry.comments
|
||||
for (i in comments.indices) {
|
||||
event.sendMessage(EntriesUtils.printComment(index, i, comments[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
bin/main/net/thauvin/erik/mobibot/commands/links/Tags.kt
Normal file
87
bin/main/net/thauvin/erik/mobibot/commands/links/Tags.kt
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Tags.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands.links
|
||||
|
||||
import net.thauvin.erik.mobibot.Constants
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.commands.AbstractCommand
|
||||
import net.thauvin.erik.mobibot.entries.EntriesUtils
|
||||
import net.thauvin.erik.mobibot.entries.EntryLink
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class Tags : AbstractCommand() {
|
||||
override val name = COMMAND
|
||||
override val help = listOf(
|
||||
"To categorize or tag a URL, use its label and a ${Constants.TAG_CMD}:",
|
||||
helpFormat("${Constants.LINK_CMD}1${Constants.TAG_CMD}:<+tag|-tag> [...]")
|
||||
)
|
||||
override val isOpOnly = false
|
||||
override val isPublic = true
|
||||
override val isVisible = true
|
||||
|
||||
companion object {
|
||||
const val COMMAND = "tags"
|
||||
}
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
val cmds = args.substring(1).split("${Constants.TAG_CMD}:", limit = 2)
|
||||
val index = cmds[0].toInt() - 1
|
||||
|
||||
if (index < LinksManager.entries.links.size && LinksManager.isUpToDate(event)) {
|
||||
val cmd = cmds[1].trim()
|
||||
val entry: EntryLink = LinksManager.entries.links[index]
|
||||
if (cmd.isNotEmpty()) {
|
||||
if (entry.login == event.user.login || event.isChannelOp(channel)) {
|
||||
entry.setTags(cmd)
|
||||
LinksManager.pinboard.updatePin(event.bot().serverHostname, entry.link, entry)
|
||||
event.sendMessage(EntriesUtils.printTags(index, entry))
|
||||
LinksManager.entries.save()
|
||||
} else {
|
||||
event.sendMessage("Please ask a channel op to change the tags for you.")
|
||||
}
|
||||
} else {
|
||||
if (entry.tags.isNotEmpty()) {
|
||||
event.sendMessage(EntriesUtils.printTags(index, entry))
|
||||
} else {
|
||||
event.sendMessage("The entry has no tags. Why don't add some?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun matches(message: String): Boolean {
|
||||
return message.matches("^${Constants.LINK_CMD}\\d+${Constants.TAG_CMD}:.*".toRegex())
|
||||
}
|
||||
}
|
120
bin/main/net/thauvin/erik/mobibot/commands/links/View.kt
Normal file
120
bin/main/net/thauvin/erik/mobibot/commands/links/View.kt
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* View.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands.links
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpCmdSyntax
|
||||
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.LinksManager.Companion.entries
|
||||
import net.thauvin.erik.mobibot.entries.EntriesUtils
|
||||
import net.thauvin.erik.mobibot.entries.EntryLink
|
||||
import org.pircbotx.hooks.events.PrivateMessageEvent
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
class View : AbstractCommand() {
|
||||
override val name = VIEW_CMD
|
||||
override val help = listOf(
|
||||
"To list or search the current URL posts:",
|
||||
helpFormat("%c $name [<start>] [<query>]")
|
||||
)
|
||||
override val isOpOnly = false
|
||||
override val isPublic = true
|
||||
override val isVisible = true
|
||||
|
||||
companion object {
|
||||
const val MAX_ENTRIES = 6
|
||||
const val VIEW_CMD = "view"
|
||||
}
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
if (entries.links.isNotEmpty()) {
|
||||
val p = parseArgs(args)
|
||||
showPosts(p.first, p.second, event)
|
||||
} else {
|
||||
event.sendMessage("There is currently nothing to view. Why don't you post something?")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun parseArgs(args: String): Pair<Int, String> {
|
||||
var query = args.lowercase().trim()
|
||||
var start = 0
|
||||
if (query.isEmpty() && entries.links.size > MAX_ENTRIES) {
|
||||
start = entries.links.size - MAX_ENTRIES
|
||||
}
|
||||
if (query.matches("^\\d+(| .*)".toRegex())) { // view [<start>] [<query>]
|
||||
val split = query.split(" ", limit = 2)
|
||||
try {
|
||||
start = split[0].toInt() - 1
|
||||
query = split.lastOrEmpty().trim()
|
||||
if (start > entries.links.size) {
|
||||
start = 0
|
||||
}
|
||||
} catch (ignore: NumberFormatException) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
return Pair(start, query)
|
||||
}
|
||||
|
||||
private fun showPosts(start: Int, query: String, event: GenericMessageEvent) {
|
||||
var index = start
|
||||
var entry: EntryLink
|
||||
var sent = 0
|
||||
while (index < entries.links.size && sent < MAX_ENTRIES) {
|
||||
entry = entries.links[index]
|
||||
if (query.isNotBlank()) {
|
||||
if (entry.matches(query)) {
|
||||
event.sendMessage(EntriesUtils.printLink(index, entry, true))
|
||||
sent++
|
||||
}
|
||||
} else {
|
||||
event.sendMessage(EntriesUtils.printLink(index, entry, true))
|
||||
sent++
|
||||
}
|
||||
index++
|
||||
if (sent == MAX_ENTRIES && index < entries.links.size) {
|
||||
event.sendMessage("To view more, try: ")
|
||||
event.sendMessage(
|
||||
helpFormat(
|
||||
helpCmdSyntax("%c $name ${index + 1} $query", event.bot().nick, event is PrivateMessageEvent)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (sent == 0) {
|
||||
event.sendMessage("No matches. Please try again.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* NickComparator.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands.seen
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
class NickComparator : Comparator<String>, Serializable {
|
||||
override fun compare(a: String, b: String): Int {
|
||||
return a.lowercase().compareTo(b.lowercase())
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Suppress("ConstPropertyName")
|
||||
private const val serialVersionUID = 1L
|
||||
}
|
||||
}
|
150
bin/main/net/thauvin/erik/mobibot/commands/seen/Seen.kt
Normal file
150
bin/main/net/thauvin/erik/mobibot/commands/seen/Seen.kt
Normal file
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Seen.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands.seen
|
||||
|
||||
import com.google.common.collect.ImmutableSortedSet
|
||||
import net.thauvin.erik.mobibot.Utils
|
||||
import net.thauvin.erik.mobibot.Utils.bold
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import net.thauvin.erik.mobibot.Utils.loadSerialData
|
||||
import net.thauvin.erik.mobibot.Utils.saveSerialData
|
||||
import net.thauvin.erik.mobibot.Utils.sendList
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.commands.AbstractCommand
|
||||
import net.thauvin.erik.mobibot.commands.Info.Companion.toUptime
|
||||
import org.pircbotx.User
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.*
|
||||
|
||||
|
||||
class Seen(private val serialObject: String) : AbstractCommand() {
|
||||
private val logger: Logger = LoggerFactory.getLogger(Seen::class.java)
|
||||
private val allKeyword = "all"
|
||||
val seenNicks = TreeMap<String, SeenNick>(NickComparator())
|
||||
|
||||
override val name = "seen"
|
||||
override val help = listOf("To view when a nickname was last seen:", helpFormat("%c $name <nick>"))
|
||||
private val helpOp = help.plus(
|
||||
arrayOf("To view all ${"seen".bold()} nicks:", helpFormat("%c $name $allKeyword"))
|
||||
)
|
||||
override val isOpOnly = false
|
||||
override val isPublic = true
|
||||
override val isVisible = true
|
||||
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
if (isEnabled()) {
|
||||
if (args.isNotBlank() && !args.contains(' ')) {
|
||||
val ch = event.bot().userChannelDao.getChannel(channel)
|
||||
if (args == allKeyword && ch.isOp(event.user) && seenNicks.isNotEmpty()) {
|
||||
event.sendMessage("The ${"seen".bold()} nicks are:")
|
||||
event.sendList(seenNicks.keys.toList(), 7, separator = ", ", isIndent = true)
|
||||
return
|
||||
}
|
||||
ch.users.forEach {
|
||||
if (args.equals(it.nick, true)) {
|
||||
event.sendMessage("${it.nick} is on ${channel}.")
|
||||
return
|
||||
}
|
||||
}
|
||||
if (seenNicks.containsKey(args)) {
|
||||
val seenNick = seenNicks.getValue(args)
|
||||
val lastSeen = System.currentTimeMillis() - seenNick.lastSeen
|
||||
event.sendMessage("${seenNick.nick} was last seen on $channel ${lastSeen.toUptime()} ago.")
|
||||
return
|
||||
}
|
||||
event.sendMessage("I haven't seen $args on $channel lately.")
|
||||
} else {
|
||||
helpResponse(channel, args, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun add(nick: String) {
|
||||
if (isEnabled()) {
|
||||
seenNicks[nick] = SeenNick(nick, System.currentTimeMillis())
|
||||
save()
|
||||
}
|
||||
}
|
||||
|
||||
fun add(users: ImmutableSortedSet<User>) {
|
||||
if (isEnabled()) {
|
||||
users.forEach {
|
||||
seenNicks[it.nick] = SeenNick(it.nick, System.currentTimeMillis())
|
||||
}
|
||||
save()
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
seenNicks.clear()
|
||||
}
|
||||
|
||||
fun count(): Int = seenNicks.size
|
||||
|
||||
override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean {
|
||||
return if (event.isChannelOp(channel)) {
|
||||
for (h in helpOp) {
|
||||
event.sendMessage(Utils.helpCmdSyntax(h, event.bot().nick, true))
|
||||
}
|
||||
true
|
||||
} else {
|
||||
super.helpResponse(channel, topic, event)
|
||||
}
|
||||
}
|
||||
|
||||
fun load() {
|
||||
if (isEnabled()) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
seenNicks.putAll(
|
||||
loadSerialData(
|
||||
serialObject,
|
||||
TreeMap<String, SeenNick>(),
|
||||
logger,
|
||||
"seen nicknames"
|
||||
) as TreeMap<String, SeenNick>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun save() {
|
||||
saveSerialData(serialObject, seenNicks, logger, "seen nicknames")
|
||||
}
|
||||
|
||||
init {
|
||||
load()
|
||||
}
|
||||
}
|
41
bin/main/net/thauvin/erik/mobibot/commands/seen/SeenNick.kt
Normal file
41
bin/main/net/thauvin/erik/mobibot/commands/seen/SeenNick.kt
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* SeenNick.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.commands.seen
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class SeenNick(val nick: String, val lastSeen: Long) : Serializable {
|
||||
companion object {
|
||||
@Suppress("ConstPropertyName")
|
||||
private const val serialVersionUID = 1L
|
||||
}
|
||||
}
|
306
bin/main/net/thauvin/erik/mobibot/commands/tell/Tell.kt
Normal file
306
bin/main/net/thauvin/erik/mobibot/commands/tell/Tell.kt
Normal file
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
* Tell.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.commands.tell
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bold
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpCmdSyntax
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||
import net.thauvin.erik.mobibot.Utils.plural
|
||||
import net.thauvin.erik.mobibot.Utils.reverseColor
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.Utils.toIntOrDefault
|
||||
import net.thauvin.erik.mobibot.Utils.toUtcDateTime
|
||||
import net.thauvin.erik.mobibot.commands.AbstractCommand
|
||||
import net.thauvin.erik.mobibot.commands.links.View
|
||||
import org.pircbotx.PircBotX
|
||||
import org.pircbotx.hooks.events.MessageEvent
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.pircbotx.hooks.types.GenericUserEvent
|
||||
|
||||
/**
|
||||
* The `Tell` command.
|
||||
*/
|
||||
class Tell(private val serialObject: String) : AbstractCommand() {
|
||||
// Messages queue
|
||||
private val messages: MutableList<TellMessage> = mutableListOf()
|
||||
|
||||
// Maximum number of days to keep messages
|
||||
private var maxDays = 7
|
||||
|
||||
// Message maximum queue size
|
||||
private var maxSize = 50
|
||||
|
||||
/**
|
||||
* The tell command.
|
||||
*/
|
||||
override val name = "tell"
|
||||
|
||||
override val help = listOf(
|
||||
"To send a message to someone when they join the channel:",
|
||||
helpFormat("%c $name <nick> <message>"),
|
||||
"To view queued and sent messages:",
|
||||
helpFormat("%c $name ${View.VIEW_CMD}"),
|
||||
"Messages are kept for ${maxDays.bold()}" + " day".plural(maxDays.toLong()) + '.'
|
||||
)
|
||||
override val isOpOnly: Boolean = false
|
||||
override val isPublic: Boolean = isEnabled()
|
||||
override val isVisible: Boolean = isEnabled()
|
||||
|
||||
/**
|
||||
* Cleans the messages queue.
|
||||
*/
|
||||
private fun clean(): Boolean {
|
||||
return TellManager.clean(messages, maxDays.toLong())
|
||||
}
|
||||
|
||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||
if (isEnabled()) {
|
||||
when {
|
||||
args.isBlank() -> {
|
||||
helpResponse(channel, args, event)
|
||||
}
|
||||
|
||||
args.startsWith(View.VIEW_CMD) -> {
|
||||
if (event.isChannelOp(channel) && "${View.VIEW_CMD} $TELL_ALL_KEYWORD" == args) {
|
||||
viewAll(event)
|
||||
} else {
|
||||
viewMessages(event)
|
||||
}
|
||||
}
|
||||
|
||||
args.startsWith("$TELL_DEL_KEYWORD ") -> {
|
||||
deleteMessage(channel, args, event)
|
||||
}
|
||||
|
||||
else -> {
|
||||
newMessage(channel, args, event)
|
||||
}
|
||||
}
|
||||
if (clean()) {
|
||||
save()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete message.
|
||||
private fun deleteMessage(channel: String, args: String, event: GenericMessageEvent) {
|
||||
val split = args.split(" ")
|
||||
if (split.size == 2) {
|
||||
val id = split[1]
|
||||
if (TELL_ALL_KEYWORD.equals(id, ignoreCase = true)) {
|
||||
if (messages.removeIf { it.sender.equals(event.user.nick, true) && it.isReceived }) {
|
||||
save()
|
||||
event.sendMessage("Delivered messages have been deleted.")
|
||||
} else {
|
||||
event.sendMessage("No delivered messages were found.")
|
||||
}
|
||||
} else {
|
||||
if (messages.removeIf {
|
||||
it.id == id &&
|
||||
(it.sender.equals(event.user.nick, true) || event.isChannelOp(channel))
|
||||
}) {
|
||||
save()
|
||||
event.sendMessage("The message was deleted from the queue.")
|
||||
} else {
|
||||
event.sendMessage("The specified message [ID $id] could not be found.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
helpResponse(channel, args, event)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isEnabled(): Boolean {
|
||||
return maxSize > 0 && maxDays > 0
|
||||
}
|
||||
|
||||
override fun setProperty(key: String, value: String) {
|
||||
super.setProperty(key, value)
|
||||
if (MAX_DAYS_PROP == key) {
|
||||
maxDays = value.toIntOrDefault(maxDays)
|
||||
} else if (MAX_SIZE_PROP == key) {
|
||||
maxSize = value.toIntOrDefault(maxSize)
|
||||
}
|
||||
}
|
||||
|
||||
// New message.
|
||||
private fun newMessage(channel: String, args: String, event: GenericMessageEvent) {
|
||||
val split = args.split(" ".toRegex(), 2)
|
||||
if (split.size == 2 && split[1].isNotBlank() && split[1].contains(" ")) {
|
||||
if (messages.size < maxSize) {
|
||||
val message = TellMessage(event.user.nick, split[0], split[1].trim())
|
||||
messages.add(message)
|
||||
save()
|
||||
event.sendMessage("Message [ID ${message.id}] was queued for ${message.recipient.bold()}")
|
||||
} else {
|
||||
event.sendMessage("Sorry, the messages queue is currently full.")
|
||||
}
|
||||
} else {
|
||||
helpResponse(channel, args, event)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the messages queue.
|
||||
*/
|
||||
private fun save() {
|
||||
TellManager.save(serialObject, messages)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and sends messages.
|
||||
*/
|
||||
fun send(event: GenericUserEvent) {
|
||||
val nickname = event.user.nick
|
||||
if (isEnabled() && nickname != event.getBot<PircBotX>().nick) {
|
||||
messages.filter { it.isMatch(nickname) }.forEach { message ->
|
||||
if (message.recipient.equals(nickname, ignoreCase = true) && !message.isReceived) {
|
||||
if (message.sender == nickname) {
|
||||
if (event !is MessageEvent) {
|
||||
event.user.send().message(
|
||||
"${"You".bold()} wanted me to remind you: ${message.message.reverseColor()}"
|
||||
)
|
||||
message.isReceived = true
|
||||
message.isNotified = true
|
||||
save()
|
||||
}
|
||||
} else {
|
||||
event.user.send().message(
|
||||
"${message.sender} wanted me to tell you: ${message.message.reverseColor()}"
|
||||
)
|
||||
message.isReceived = true
|
||||
save()
|
||||
}
|
||||
} else if (message.sender.equals(nickname, ignoreCase = true) && message.isReceived
|
||||
&& !message.isNotified
|
||||
) {
|
||||
event.user.send().message(
|
||||
"Your message ${"[ID ${message.id}]".reverseColor()} was sent to "
|
||||
+ "${message.recipient.bold()} on ${message.receptionDate}"
|
||||
)
|
||||
message.isNotified = true
|
||||
save()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the messages queue size.
|
||||
*
|
||||
* @return The size.
|
||||
*/
|
||||
fun size(): Int = messages.size
|
||||
|
||||
// View all messages.
|
||||
private fun viewAll(event: GenericMessageEvent) {
|
||||
if (messages.isNotEmpty()) {
|
||||
for (message in messages) {
|
||||
event.sendMessage(
|
||||
"${message.sender.bold()}$ARROW${message.recipient.bold()} [ID: ${message.id}, " +
|
||||
(if (message.isReceived) "DELIVERED]" else "QUEUED]")
|
||||
)
|
||||
}
|
||||
} else {
|
||||
event.sendMessage("There are no messages in the queue.")
|
||||
}
|
||||
}
|
||||
|
||||
// View messages.
|
||||
private fun viewMessages(event: GenericMessageEvent) {
|
||||
var hasMessage = false
|
||||
for (message in messages.filter { it.isMatch(event.user.nick) }) {
|
||||
if (!hasMessage) {
|
||||
hasMessage = true
|
||||
event.sendMessage("Here are your messages: ")
|
||||
}
|
||||
if (message.isReceived) {
|
||||
event.sendMessage(
|
||||
message.sender.bold() + ARROW + message.recipient.bold() +
|
||||
" [${message.receptionDate.toUtcDateTime()}, ID: ${message.id.bold()}, DELIVERED]"
|
||||
)
|
||||
} else {
|
||||
event.sendMessage(
|
||||
message.sender.bold() + ARROW + message.recipient.bold() +
|
||||
" [${message.queued.toUtcDateTime()}, ID: ${message.id.bold()}, QUEUED]"
|
||||
)
|
||||
}
|
||||
event.sendMessage(helpFormat(message.message))
|
||||
}
|
||||
if (!hasMessage) {
|
||||
event.sendMessage("You have no messages in the queue.")
|
||||
} else {
|
||||
event.sendMessage("To delete one or all delivered messages:")
|
||||
event.sendMessage(
|
||||
helpFormat(
|
||||
helpCmdSyntax("%c $name $TELL_DEL_KEYWORD <id|$TELL_ALL_KEYWORD>", event.bot().nick, true)
|
||||
)
|
||||
)
|
||||
event.sendMessage(help.last())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Max days property.
|
||||
*/
|
||||
const val MAX_DAYS_PROP = "tell-max-days"
|
||||
|
||||
/**
|
||||
* Max size property.
|
||||
*/
|
||||
const val MAX_SIZE_PROP = "tell-max-size"
|
||||
|
||||
// Arrow
|
||||
private const val ARROW = " --> "
|
||||
|
||||
// All keyword
|
||||
private const val TELL_ALL_KEYWORD = "all"
|
||||
|
||||
//T he delete command.
|
||||
private const val TELL_DEL_KEYWORD = "del"
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
init {
|
||||
initProperties(MAX_DAYS_PROP, MAX_SIZE_PROP)
|
||||
|
||||
// Load the message queue
|
||||
messages.addAll(TellManager.load(serialObject))
|
||||
if (clean()) {
|
||||
save()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* TellManager.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.commands.tell
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.loadSerialData
|
||||
import net.thauvin.erik.mobibot.Utils.saveSerialData
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.time.Clock
|
||||
import java.time.LocalDateTime
|
||||
|
||||
/**
|
||||
* The Tell Messages Manager.
|
||||
*/
|
||||
object TellManager {
|
||||
private val logger: Logger = LoggerFactory.getLogger(TellManager::class.java)
|
||||
|
||||
/**
|
||||
* Cleans the messages queue.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun clean(tellMessages: MutableList<TellMessage>, tellMaxDays: Long): Boolean {
|
||||
if (logger.isDebugEnabled) logger.debug("Cleaning the messages.")
|
||||
val today = LocalDateTime.now(Clock.systemUTC())
|
||||
return tellMessages.removeIf { o: TellMessage -> o.queued.plusDays(tellMaxDays).isBefore(today) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the messages.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun load(file: String): List<TellMessage> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return loadSerialData(file, emptyList<TellMessage>(), logger, "message queue") as List<TellMessage>
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the messages.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun save(file: String, messages: List<TellMessage?>?) {
|
||||
if (messages != null) {
|
||||
saveSerialData(file, messages, logger, "messages")
|
||||
}
|
||||
}
|
||||
}
|
104
bin/main/net/thauvin/erik/mobibot/commands/tell/TellMessage.kt
Normal file
104
bin/main/net/thauvin/erik/mobibot/commands/tell/TellMessage.kt
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* TellMessage.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.commands.tell
|
||||
|
||||
import java.io.Serializable
|
||||
import java.time.Clock
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
/**
|
||||
* Tell Message.
|
||||
*/
|
||||
class TellMessage(
|
||||
/**
|
||||
* Returns the message's sender.
|
||||
*/
|
||||
val sender: String,
|
||||
|
||||
/**
|
||||
* Returns the message's recipient.
|
||||
*/
|
||||
val recipient: String,
|
||||
|
||||
/**
|
||||
* Returns the message text.
|
||||
*/
|
||||
val message: String
|
||||
) : Serializable {
|
||||
/**
|
||||
* Returns the queued date/time.
|
||||
*/
|
||||
var queued: LocalDateTime = LocalDateTime.now(Clock.systemUTC())
|
||||
|
||||
/**
|
||||
* Returns the message id.
|
||||
*/
|
||||
var id: String = queued.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
|
||||
|
||||
/**
|
||||
* Returns `true` if a notification was sent.
|
||||
*/
|
||||
var isNotified = false
|
||||
|
||||
/**
|
||||
* Returns `true` if the message was received.
|
||||
*/
|
||||
var isReceived = false
|
||||
set(value) {
|
||||
if (value) {
|
||||
receptionDate = LocalDateTime.now(Clock.systemUTC())
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message creating date.
|
||||
*/
|
||||
var receptionDate: LocalDateTime = LocalDateTime.MIN
|
||||
|
||||
/**
|
||||
* Matches the message sender or recipient.
|
||||
*/
|
||||
fun isMatch(nick: String?): Boolean {
|
||||
return sender.equals(nick, ignoreCase = true) || recipient.equals(nick, ignoreCase = true)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return ("TellMessage{id='$id', isNotified=$isNotified, isReceived=$isReceived, message='$message', " +
|
||||
"queued=$queued, received=$receptionDate, recipient='$recipient', sender='$sender'}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Suppress("ConstPropertyName")
|
||||
private const val serialVersionUID = 2L
|
||||
}
|
||||
}
|
54
bin/main/net/thauvin/erik/mobibot/entries/Entries.kt
Normal file
54
bin/main/net/thauvin/erik/mobibot/entries/Entries.kt
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Entries.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.entries
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.today
|
||||
|
||||
class Entries(
|
||||
var channel: String = "",
|
||||
var ircServer: String = "",
|
||||
var logsDir: String = "",
|
||||
var backlogs: String = ""
|
||||
) {
|
||||
val links = mutableListOf<EntryLink>()
|
||||
|
||||
var lastPubDate = today()
|
||||
|
||||
fun load() {
|
||||
lastPubDate = FeedsManager.loadFeed(this)
|
||||
}
|
||||
|
||||
fun save() {
|
||||
lastPubDate = today()
|
||||
FeedsManager.saveFeed(this)
|
||||
}
|
||||
}
|
83
bin/main/net/thauvin/erik/mobibot/entries/EntriesUtils.kt
Normal file
83
bin/main/net/thauvin/erik/mobibot/entries/EntriesUtils.kt
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* EntriesUtils.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.entries
|
||||
|
||||
import net.thauvin.erik.mobibot.Constants
|
||||
import net.thauvin.erik.mobibot.Utils.bold
|
||||
import net.thauvin.erik.mobibot.Utils.green
|
||||
|
||||
/**
|
||||
* Entries utilities.
|
||||
*/
|
||||
object EntriesUtils {
|
||||
/**
|
||||
* Prints an entry's comment for display on the channel.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun printComment(entryIndex: Int, commentIndex: Int, comment: EntryComment): String =
|
||||
("${entryIndex.toLinkLabel()}.${commentIndex + 1}: [${comment.nick}] ${comment.comment}")
|
||||
|
||||
/**
|
||||
* Prints an entry's link for display on the channel.
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun printLink(entryIndex: Int, entry: EntryLink, isView: Boolean = false): String {
|
||||
val buff = StringBuilder().append(entryIndex.toLinkLabel()).append(": ")
|
||||
.append('[').append(entry.nick).append(']')
|
||||
if (isView && entry.comments.isNotEmpty()) {
|
||||
buff.append("[+").append(entry.comments.size).append(']')
|
||||
}
|
||||
buff.append(' ')
|
||||
with(entry) {
|
||||
if (Constants.NO_TITLE == title) {
|
||||
buff.append(title)
|
||||
} else {
|
||||
buff.append(title.bold())
|
||||
}
|
||||
buff.append(" ( ").append(link.green()).append(" )")
|
||||
}
|
||||
return buff.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints an entry's tags/categories for display on the channel. e.g. L1T: tag1, tag2
|
||||
*/
|
||||
@JvmStatic
|
||||
fun printTags(entryIndex: Int, entry: EntryLink): String =
|
||||
entryIndex.toLinkLabel() + "${Constants.TAG_CMD}: " + entry.formatTags(", ")
|
||||
|
||||
/**
|
||||
* Builds link label based on its index. e.g: L1
|
||||
*/
|
||||
@JvmStatic
|
||||
fun Int.toLinkLabel(): String = Constants.LINK_CMD + (this + 1)
|
||||
}
|
52
bin/main/net/thauvin/erik/mobibot/entries/EntryComment.kt
Normal file
52
bin/main/net/thauvin/erik/mobibot/entries/EntryComment.kt
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* EntryComment.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.entries
|
||||
|
||||
import java.io.Serializable
|
||||
import java.time.LocalDateTime
|
||||
|
||||
/**
|
||||
* Entry comments data class.
|
||||
*/
|
||||
data class EntryComment(var comment: String, var nick: String) : Serializable {
|
||||
/**
|
||||
* Creation date.
|
||||
*/
|
||||
val date: LocalDateTime = LocalDateTime.now()
|
||||
|
||||
override fun toString(): String = "EntryComment{comment='$comment', date=$date, nick='$nick'}"
|
||||
|
||||
companion object {
|
||||
// Serial version UID
|
||||
@Suppress("ConstPropertyName")
|
||||
private const val serialVersionUID: Long = 1L
|
||||
}
|
||||
}
|
213
bin/main/net/thauvin/erik/mobibot/entries/EntryLink.kt
Normal file
213
bin/main/net/thauvin/erik/mobibot/entries/EntryLink.kt
Normal file
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* EntryLink.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.entries
|
||||
|
||||
import com.rometools.rome.feed.synd.SyndCategory
|
||||
import com.rometools.rome.feed.synd.SyndCategoryImpl
|
||||
import net.thauvin.erik.mobibot.commands.links.LinksManager
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* The class used to store link entries.
|
||||
*/
|
||||
class EntryLink(
|
||||
// Link's comments
|
||||
val comments: MutableList<EntryComment> = mutableListOf(),
|
||||
|
||||
// Tags/categories
|
||||
val tags: MutableList<SyndCategory> = mutableListOf(),
|
||||
|
||||
// Channel
|
||||
var channel: String,
|
||||
|
||||
// Creation date
|
||||
var date: Date = Calendar.getInstance().time,
|
||||
|
||||
// Link's URL
|
||||
var link: String,
|
||||
|
||||
// Author's login
|
||||
var login: String = "",
|
||||
|
||||
// Author's nickname
|
||||
var nick: String,
|
||||
|
||||
// Link's title
|
||||
var title: String
|
||||
) : Serializable {
|
||||
/**
|
||||
* Creates a new entry.
|
||||
*/
|
||||
constructor(
|
||||
link: String,
|
||||
title: String,
|
||||
nick: String,
|
||||
login: String,
|
||||
channel: String,
|
||||
tags: List<String?>
|
||||
) : this(link = link, title = title, nick = nick, login = login, channel = channel) {
|
||||
setTags(tags)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new entry.
|
||||
*/
|
||||
constructor(
|
||||
link: String,
|
||||
title: String,
|
||||
nick: String,
|
||||
channel: String,
|
||||
date: Date,
|
||||
tags: List<SyndCategory>
|
||||
) : this(link = link, title = title, nick = nick, channel = channel, date = Date(date.time)) {
|
||||
this.tags.addAll(tags)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new comment
|
||||
*/
|
||||
fun addComment(comment: EntryComment): Int {
|
||||
comments.add(comment)
|
||||
return comments.lastIndex
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new comment.
|
||||
*/
|
||||
fun addComment(comment: String, nick: String): Int {
|
||||
return addComment(EntryComment(comment, nick))
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a specific comment.
|
||||
*/
|
||||
fun deleteComment(index: Int): Boolean {
|
||||
if (index < comments.size) {
|
||||
comments.removeAt(index)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a comment.
|
||||
*/
|
||||
fun deleteComment(entryComment: EntryComment): Boolean {
|
||||
return comments.remove(entryComment)
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the tags.
|
||||
*/
|
||||
fun formatTags(sep: String, prefix: String = ""): String {
|
||||
return tags.joinToString(separator = sep, prefix = prefix) { it.name }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comment.
|
||||
*/
|
||||
fun getComment(index: Int): EntryComment = comments[index]
|
||||
|
||||
/**
|
||||
* Returns true if a string is contained in the link, title, or nick.
|
||||
*/
|
||||
fun matches(match: String?): Boolean {
|
||||
return if (match.isNullOrEmpty()) {
|
||||
false
|
||||
} else {
|
||||
link.contains(match, true) || title.contains(match, true) || nick.contains(match, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a comment.
|
||||
*/
|
||||
fun setComment(index: Int, comment: String?, nick: String?) {
|
||||
if (index < comments.size && !comment.isNullOrBlank() && !nick.isNullOrBlank()) {
|
||||
comments[index] = EntryComment(comment, nick)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tags.
|
||||
*/
|
||||
fun setTags(tags: String) {
|
||||
setTags(tags.split(LinksManager.TAG_MATCH))
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tags.
|
||||
*/
|
||||
private fun setTags(tags: List<String?>) {
|
||||
if (tags.isNotEmpty()) {
|
||||
var category: SyndCategoryImpl
|
||||
for (tag in tags) {
|
||||
if (!tag.isNullOrBlank()) {
|
||||
val t = tag.lowercase()
|
||||
val mod = t[0]
|
||||
if (mod == '-') {
|
||||
// Don't remove the channel tag
|
||||
if (channel.substring(1) != t.substring(1)) {
|
||||
category = SyndCategoryImpl()
|
||||
category.name = t.substring(1)
|
||||
this.tags.remove(category)
|
||||
}
|
||||
} else {
|
||||
category = SyndCategoryImpl()
|
||||
if (mod == '+') {
|
||||
category.name = t.substring(1)
|
||||
} else {
|
||||
category.name = t
|
||||
}
|
||||
if (!this.tags.contains(category)) {
|
||||
this.tags.add(category)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the object.
|
||||
*/
|
||||
override fun toString(): String {
|
||||
return ("EntryLink{channel='$channel', comments=$comments, date=$date, link='$link', login='$login'," +
|
||||
"nick='$nick', tags=$tags, title='$title'}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Serial version UID
|
||||
@Suppress("ConstPropertyName")
|
||||
private const val serialVersionUID: Long = 1L
|
||||
}
|
||||
}
|
187
bin/main/net/thauvin/erik/mobibot/entries/FeedsManager.kt
Normal file
187
bin/main/net/thauvin/erik/mobibot/entries/FeedsManager.kt
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* FeedsManager.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.entries
|
||||
|
||||
import com.rometools.rome.feed.synd.*
|
||||
import com.rometools.rome.io.FeedException
|
||||
import com.rometools.rome.io.SyndFeedInput
|
||||
import com.rometools.rome.io.SyndFeedOutput
|
||||
import net.thauvin.erik.mobibot.Utils.toIsoLocalDate
|
||||
import net.thauvin.erik.mobibot.Utils.today
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.io.OutputStreamWriter
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import kotlin.io.path.exists
|
||||
|
||||
/**
|
||||
* Manages the RSS feeds.
|
||||
*/
|
||||
class FeedsManager private constructor() {
|
||||
companion object {
|
||||
private val logger: Logger = LoggerFactory.getLogger(FeedsManager::class.java)
|
||||
|
||||
// The file containing the current entries.
|
||||
private const val CURRENT_XML = "current.xml"
|
||||
|
||||
// The .xml extension.
|
||||
private const val DOT_XML = ".xml"
|
||||
|
||||
/**
|
||||
* Loads the current feed.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IOException::class, FeedException::class)
|
||||
fun loadFeed(entries: Entries, currentFile: String = CURRENT_XML): String {
|
||||
entries.links.clear()
|
||||
val xml = Paths.get("${entries.logsDir}${currentFile}")
|
||||
var pubDate = today()
|
||||
if (xml.exists()) {
|
||||
val input = SyndFeedInput()
|
||||
InputStreamReader(
|
||||
Files.newInputStream(xml), StandardCharsets.UTF_8
|
||||
).use { reader ->
|
||||
val feed = input.build(reader)
|
||||
pubDate = feed.publishedDate.toIsoLocalDate()
|
||||
val items = feed.entries
|
||||
var entry: EntryLink
|
||||
for (i in items.indices.reversed()) {
|
||||
with(items[i]) {
|
||||
entry = EntryLink(
|
||||
link,
|
||||
title,
|
||||
author.substring(author.lastIndexOf('(') + 1, author.length - 1),
|
||||
entries.channel,
|
||||
publishedDate,
|
||||
categories
|
||||
)
|
||||
var split: List<String>
|
||||
for (comment in description.value.split("<br/>")) {
|
||||
split = comment.split(": ".toRegex(), 2)
|
||||
if (split.size == 2) {
|
||||
entry.addComment(comment = split[1].trim(), nick = split[0].trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
entries.links.add(entry)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create an empty feed.
|
||||
saveFeed(entries)
|
||||
}
|
||||
return pubDate
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the feeds.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun saveFeed(entries: Entries, currentFile: String = CURRENT_XML) {
|
||||
if (logger.isDebugEnabled) logger.debug("Saving the feeds...")
|
||||
if (entries.logsDir.isNotBlank()) {
|
||||
try {
|
||||
val output = SyndFeedOutput()
|
||||
val rss: SyndFeed = SyndFeedImpl()
|
||||
val items: MutableList<SyndEntry> = mutableListOf()
|
||||
var item: SyndEntry
|
||||
OutputStreamWriter(
|
||||
Files.newOutputStream(Paths.get("${entries.logsDir}${currentFile}")), StandardCharsets.UTF_8
|
||||
).use { fw ->
|
||||
with(rss) {
|
||||
feedType = "rss_2.0"
|
||||
title = "${entries.channel} IRC Links"
|
||||
description = "Links from ${entries.ircServer} on ${entries.channel}"
|
||||
if (entries.backlogs.isNotBlank()) link = entries.backlogs
|
||||
publishedDate = Calendar.getInstance().time
|
||||
language = "en"
|
||||
}
|
||||
val buff: StringBuilder = StringBuilder()
|
||||
for (i in entries.links.indices.reversed()) {
|
||||
with(entries.links[i]) {
|
||||
buff.setLength(0)
|
||||
buff.append("Posted by <b>")
|
||||
.append(nick)
|
||||
.append("</b> on <a href=\"irc://")
|
||||
.append(entries.ircServer).append('/')
|
||||
.append(channel)
|
||||
.append("\"><b>")
|
||||
.append(channel)
|
||||
.append("</b></a>")
|
||||
if (comments.isNotEmpty()) {
|
||||
buff.append(" <br/><br/>")
|
||||
for (j in comments.indices) {
|
||||
if (j > 0) {
|
||||
buff.append(" <br/>")
|
||||
}
|
||||
buff.append(comments[j].nick).append(": ").append(comments[j].comment)
|
||||
}
|
||||
}
|
||||
item = SyndEntryImpl()
|
||||
item.link = link
|
||||
item.description = SyndContentImpl().apply { value = buff.toString() }
|
||||
item.title = title
|
||||
item.publishedDate = date
|
||||
item.author = "${channel.removePrefix("#")}@${entries.ircServer} ($nick)"
|
||||
item.categories = tags
|
||||
items.add(item)
|
||||
}
|
||||
}
|
||||
rss.entries = items
|
||||
if (logger.isDebugEnabled) logger.debug("Writing the entries feed.")
|
||||
output.output(rss, fw)
|
||||
}
|
||||
OutputStreamWriter(
|
||||
Files.newOutputStream(
|
||||
Paths.get(
|
||||
entries.logsDir + today() + DOT_XML
|
||||
)
|
||||
), StandardCharsets.UTF_8
|
||||
).use { fw -> output.output(rss, fw) }
|
||||
} catch (e: FeedException) {
|
||||
if (logger.isWarnEnabled) logger.warn("Unable to generate the entries feed.", e)
|
||||
} catch (e: IOException) {
|
||||
if (logger.isWarnEnabled)
|
||||
logger.warn("An IO error occurred while generating the entries feed.", e)
|
||||
}
|
||||
} else {
|
||||
if (logger.isWarnEnabled) {
|
||||
logger.warn("Unable to generate the entries feed. A required property is missing.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
131
bin/main/net/thauvin/erik/mobibot/modules/AbstractModule.kt
Normal file
131
bin/main/net/thauvin/erik/mobibot/modules/AbstractModule.kt
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* AbstractModule.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpCmdSyntax
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import org.pircbotx.hooks.events.PrivateMessageEvent
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
/**
|
||||
* The `Module` abstract class.
|
||||
*/
|
||||
abstract class AbstractModule {
|
||||
/**
|
||||
* The module name.
|
||||
*/
|
||||
abstract val name: String
|
||||
|
||||
/**
|
||||
* The module's commands, if any.
|
||||
*/
|
||||
@JvmField
|
||||
val commands: MutableList<String> = mutableListOf()
|
||||
|
||||
@JvmField
|
||||
val help: MutableList<String> = mutableListOf()
|
||||
val properties: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
/**
|
||||
* Responds to a command.
|
||||
*/
|
||||
abstract fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent)
|
||||
|
||||
/**
|
||||
* Returns the module's property keys.
|
||||
*/
|
||||
val propertyKeys: Set<String>
|
||||
get() = properties.keys
|
||||
|
||||
/**
|
||||
* Returns `true` if the module has properties.
|
||||
*/
|
||||
fun hasProperties(): Boolean {
|
||||
return properties.isNotEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds with the module's help.
|
||||
*/
|
||||
open fun helpResponse(event: GenericMessageEvent): Boolean {
|
||||
for (h in help) {
|
||||
event.sendMessage(helpCmdSyntax(h, event.bot().nick, isPrivateMsgEnabled && event is PrivateMessageEvent))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the properties.
|
||||
*/
|
||||
fun initProperties(vararg keys: String) {
|
||||
for (key in keys) {
|
||||
properties[key] = ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the module is enabled.
|
||||
*/
|
||||
val isEnabled: Boolean
|
||||
get() = if (hasProperties()) {
|
||||
isValidProperties
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the module responds to private messages.
|
||||
*/
|
||||
open val isPrivateMsgEnabled: Boolean = false
|
||||
|
||||
/**
|
||||
* Ensures that all properties have values.
|
||||
*/
|
||||
open val isValidProperties: Boolean
|
||||
get() {
|
||||
for (s in properties.keys) {
|
||||
if (properties[s].isNullOrBlank()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a property key and value.
|
||||
*/
|
||||
fun setProperty(key: String, value: String) {
|
||||
if (key.isNotBlank()) {
|
||||
properties[key] = value
|
||||
}
|
||||
}
|
||||
}
|
87
bin/main/net/thauvin/erik/mobibot/modules/Calc.kt
Normal file
87
bin/main/net/thauvin/erik/mobibot/modules/Calc.kt
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Calc.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.objecthunter.exp4j.ExpressionBuilder
|
||||
import net.objecthunter.exp4j.tokenizer.UnknownFunctionOrVariableException
|
||||
import net.thauvin.erik.mobibot.Utils.bold
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.text.DecimalFormat
|
||||
|
||||
/**
|
||||
* The Calc module.
|
||||
*/
|
||||
class Calc : AbstractModule() {
|
||||
private val logger: Logger = LoggerFactory.getLogger(Calc::class.java)
|
||||
|
||||
override val name = "Calc"
|
||||
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
if (args.isNotBlank()) {
|
||||
try {
|
||||
event.respond(calculate(args))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
if (logger.isWarnEnabled) logger.warn("Failed to calculate: $args", e)
|
||||
event.respond("No idea. This is the kind of math I don't get.")
|
||||
} catch (e: UnknownFunctionOrVariableException) {
|
||||
if (logger.isWarnEnabled) logger.warn("Unable to calculate: $args", e)
|
||||
event.respond("No idea. I must've some form of Dyscalculia.")
|
||||
}
|
||||
} else {
|
||||
helpResponse(event)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Calc command
|
||||
private const val CALC_CMD = "calc"
|
||||
|
||||
/**
|
||||
* Performs a calculation. e.g.: 1 + 1 * 2
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun calculate(query: String): String {
|
||||
val decimalFormat = DecimalFormat("#.##")
|
||||
val calc = ExpressionBuilder(query).build()
|
||||
return query.replace(" ", "") + " = " + decimalFormat.format(calc.evaluate()).bold()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
commands.add(CALC_CMD)
|
||||
help.add("To solve a mathematical calculation:")
|
||||
help.add(helpFormat("%c $CALC_CMD <calculation>"))
|
||||
}
|
||||
}
|
176
bin/main/net/thauvin/erik/mobibot/modules/ChatGpt.kt
Normal file
176
bin/main/net/thauvin/erik/mobibot/modules/ChatGpt.kt
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* ChatGpt.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.mobibot.Constants
|
||||
import net.thauvin.erik.mobibot.Utils
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import org.apache.commons.text.WordUtils
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONWriter
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.net.URI
|
||||
import java.net.http.HttpClient
|
||||
import java.net.http.HttpRequest
|
||||
import java.net.http.HttpResponse
|
||||
|
||||
class ChatGpt : AbstractModule() {
|
||||
private val logger: Logger = LoggerFactory.getLogger(ChatGpt::class.java)
|
||||
|
||||
override val name = CHATGPT_NAME
|
||||
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
if (args.isNotBlank()) {
|
||||
try {
|
||||
val answer = chat(
|
||||
args.trim(), properties[API_KEY_PROP],
|
||||
properties.getOrDefault(MAX_TOKENS_PROP, "1024").toInt()
|
||||
)
|
||||
if (answer.isNotBlank()) {
|
||||
event.sendMessage(WordUtils.wrap(answer, 400))
|
||||
} else {
|
||||
event.respond("$name is stumped.")
|
||||
}
|
||||
} catch (e: ModuleException) {
|
||||
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
|
||||
e.message?.let {
|
||||
event.respond(it)
|
||||
}
|
||||
} catch (e: NumberFormatException) {
|
||||
if (logger.isErrorEnabled) logger.error("Invalid $MAX_TOKENS_PROP property.", e)
|
||||
event.respond("The $name module is misconfigured.")
|
||||
}
|
||||
} else {
|
||||
helpResponse(event)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The service name.
|
||||
*/
|
||||
const val CHATGPT_NAME = "ChatGPT"
|
||||
|
||||
/**
|
||||
* The API Key property.
|
||||
*/
|
||||
const val API_KEY_PROP = "chatgpt-api-key"
|
||||
|
||||
/**
|
||||
* The max tokens property.
|
||||
*/
|
||||
const val MAX_TOKENS_PROP = "chatgpt-max-tokens"
|
||||
|
||||
// ChatGPT API URL
|
||||
private const val API_URL = "https://api.openai.com/v1/completions"
|
||||
|
||||
// ChatGPT command
|
||||
private const val CHATGPT_CMD = "chatgpt"
|
||||
|
||||
|
||||
@JvmStatic
|
||||
@Throws(ModuleException::class)
|
||||
fun chat(query: String, apiKey: String?, maxTokens: Int): String {
|
||||
if (!apiKey.isNullOrEmpty()) {
|
||||
val prompt = JSONWriter.valueToString("Q:$query\nA:")
|
||||
val request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(API_URL))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer $apiKey")
|
||||
.header("User-Agent", Constants.USER_AGENT)
|
||||
.POST(
|
||||
HttpRequest.BodyPublishers.ofString(
|
||||
"""{
|
||||
"model": "text-davinci-003",
|
||||
"prompt": $prompt,
|
||||
"temperature": 0,
|
||||
"max_tokens": $maxTokens,
|
||||
"top_p": 1,
|
||||
"frequency_penalty": 0,
|
||||
"presence_penalty": 0
|
||||
}""".trimIndent()
|
||||
)
|
||||
)
|
||||
.build()
|
||||
try {
|
||||
val response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString())
|
||||
if (response.statusCode() == 200) {
|
||||
try {
|
||||
val jsonResponse = JSONObject(response.body())
|
||||
val choices = jsonResponse.getJSONArray("choices")
|
||||
return choices.getJSONObject(0).getString("text").trim()
|
||||
} catch (e: JSONException) {
|
||||
throw ModuleException(
|
||||
"$CHATGPT_CMD($query): JSON",
|
||||
"A JSON error has occurred while conversing with $CHATGPT_NAME.",
|
||||
e
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (response.statusCode() == 429) {
|
||||
throw ModuleException(
|
||||
"$CHATGPT_CMD($query): Rate limit reached",
|
||||
"Rate limit reached. Please try again later."
|
||||
)
|
||||
} else {
|
||||
throw IOException("HTTP Status Code: " + response.statusCode())
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw ModuleException(
|
||||
"$CHATGPT_CMD($query): IO",
|
||||
"An IO error has occurred while conversing with $CHATGPT_NAME.",
|
||||
e
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw ModuleException("$CHATGPT_CMD($query)", "No $CHATGPT_NAME API key specified.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
commands.add(CHATGPT_CMD)
|
||||
with(help) {
|
||||
add("To get answers from $name:")
|
||||
add(Utils.helpFormat("%c $CHATGPT_CMD <query>"))
|
||||
add("For example:")
|
||||
add(Utils.helpFormat("%c $CHATGPT_CMD explain quantum computing in simple terms"))
|
||||
add(Utils.helpFormat("%c $CHATGPT_CMD how do I make an HTTP request in Javascript?"))
|
||||
}
|
||||
initProperties(API_KEY_PROP, MAX_TOKENS_PROP)
|
||||
}
|
||||
}
|
159
bin/main/net/thauvin/erik/mobibot/modules/CryptoPrices.kt
Normal file
159
bin/main/net/thauvin/erik/mobibot/modules/CryptoPrices.kt
Normal file
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* CryptoPrices.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.crypto.CryptoException
|
||||
import net.thauvin.erik.crypto.CryptoPrice
|
||||
import net.thauvin.erik.crypto.CryptoPrice.Companion.spotPrice
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.sendList
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import org.json.JSONObject
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* The Cryptocurrency Prices module.
|
||||
*/
|
||||
class CryptoPrices : AbstractModule() {
|
||||
private val logger: Logger = LoggerFactory.getLogger(CryptoPrices::class.java)
|
||||
|
||||
override val name = "CryptoPrices"
|
||||
|
||||
/**
|
||||
* Returns the cryptocurrency market price from
|
||||
* [Coinbase](https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-prices#get-spot-price).
|
||||
*/
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
if (CURRENCIES.isEmpty()) {
|
||||
try {
|
||||
loadCurrencies()
|
||||
} catch (e: ModuleException) {
|
||||
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
|
||||
}
|
||||
}
|
||||
|
||||
val debugMessage = "crypto($cmd $args)"
|
||||
if (args == CODES_KEYWORD) {
|
||||
event.sendMessage("The supported currencies are:")
|
||||
event.sendList(ArrayList(CURRENCIES.keys), 10, isIndent = true)
|
||||
} else if (args.matches("\\w+( [a-zA-Z]{3}+)?".toRegex())) {
|
||||
try {
|
||||
val price = currentPrice(args.split(' '))
|
||||
val amount = try {
|
||||
price.toCurrency()
|
||||
} catch (ignore: IllegalArgumentException) {
|
||||
price.amount
|
||||
}
|
||||
event.respond("${price.base} current price is $amount [${CURRENCIES[price.currency]}]")
|
||||
} catch (e: CryptoException) {
|
||||
if (logger.isWarnEnabled) logger.warn("$debugMessage => ${e.statusCode}", e)
|
||||
e.message?.let {
|
||||
event.respond(it)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
if (logger.isErrorEnabled) logger.error(debugMessage, e)
|
||||
event.respond("An IO error has occurred while retrieving the cryptocurrency market price.")
|
||||
}
|
||||
} else {
|
||||
helpResponse(event)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Crypto command
|
||||
private const val CRYPTO_CMD = "crypto"
|
||||
|
||||
// Fiat Currencies
|
||||
private val CURRENCIES: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
// Currency codes keyword
|
||||
private const val CODES_KEYWORD = "codes"
|
||||
|
||||
/**
|
||||
* Get current market price.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun currentPrice(args: List<String>): CryptoPrice {
|
||||
return if (args.size == 2)
|
||||
spotPrice(args[0], args[1])
|
||||
else
|
||||
spotPrice(args[0])
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing purposes.
|
||||
*/
|
||||
fun getCurrencyName(code: String): String? {
|
||||
return CURRENCIES[code]
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the Fiat currencies..
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(ModuleException::class)
|
||||
fun loadCurrencies() {
|
||||
try {
|
||||
val json = JSONObject(CryptoPrice.apiCall(listOf("currencies")))
|
||||
val data = json.getJSONArray("data")
|
||||
for (i in 0 until data.length()) {
|
||||
val d = data.getJSONObject(i)
|
||||
CURRENCIES[d.getString("id")] = d.getString("name")
|
||||
}
|
||||
} catch (e: CryptoException) {
|
||||
throw ModuleException(
|
||||
"loadCurrencies(): CE",
|
||||
"An error has occurred while retrieving the currencies table.",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
commands.add(CRYPTO_CMD)
|
||||
with(help) {
|
||||
add("To retrieve a cryptocurrency's market price:")
|
||||
add(helpFormat("%c $CRYPTO_CMD <symbol> [<currency>]"))
|
||||
add("For example:")
|
||||
add(helpFormat("%c $CRYPTO_CMD BTC"))
|
||||
add(helpFormat("%c $CRYPTO_CMD ETH EUR"))
|
||||
add(helpFormat("%c $CRYPTO_CMD ETH2 GPB"))
|
||||
add("To list the supported currencies:")
|
||||
add(helpFormat("%c $CRYPTO_CMD $CODES_KEYWORD"))
|
||||
}
|
||||
loadCurrencies()
|
||||
}
|
||||
}
|
222
bin/main/net/thauvin/erik/mobibot/modules/CurrencyConverter.kt
Normal file
222
bin/main/net/thauvin/erik/mobibot/modules/CurrencyConverter.kt
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* CurrencyConverter.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpCmdSyntax
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.reader
|
||||
import net.thauvin.erik.mobibot.Utils.sendList
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.msg.ErrorMessage
|
||||
import net.thauvin.erik.mobibot.msg.Message
|
||||
import net.thauvin.erik.mobibot.msg.PublicMessage
|
||||
import org.json.JSONObject
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.text.DecimalFormat
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* The CurrencyConverter module.
|
||||
*/
|
||||
class CurrencyConverter : AbstractModule() {
|
||||
private val logger: Logger = LoggerFactory.getLogger(CurrencyConverter::class.java)
|
||||
|
||||
override val name = "CurrencyConverter"
|
||||
|
||||
// Reload currency codes
|
||||
private fun reload(apiKey: String?) {
|
||||
if (!apiKey.isNullOrEmpty() && SYMBOLS.isEmpty()) {
|
||||
try {
|
||||
loadSymbols(apiKey)
|
||||
} catch (e: ModuleException) {
|
||||
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the specified currencies.
|
||||
*/
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
reload(properties[API_KEY_PROP])
|
||||
|
||||
when {
|
||||
SYMBOLS.isEmpty() -> {
|
||||
event.respond(EMPTY_SYMBOLS_TABLE)
|
||||
}
|
||||
|
||||
args.matches("\\d+([,\\d]+)?(\\.\\d+)? [a-zA-Z]{3}+ (to|in) [a-zA-Z]{3}+".toRegex()) -> {
|
||||
val msg = convertCurrency(properties[API_KEY_PROP], args)
|
||||
event.respond(msg.msg)
|
||||
if (msg.isError) {
|
||||
helpResponse(event)
|
||||
}
|
||||
}
|
||||
|
||||
args.contains(CODES_KEYWORD) -> {
|
||||
event.sendMessage("The supported currency codes are:")
|
||||
event.sendList(SYMBOLS.keys.toList(), 11, isIndent = true)
|
||||
}
|
||||
|
||||
else -> {
|
||||
helpResponse(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun helpResponse(event: GenericMessageEvent): Boolean {
|
||||
reload(properties[API_KEY_PROP])
|
||||
|
||||
if (SYMBOLS.isEmpty()) {
|
||||
event.sendMessage(EMPTY_SYMBOLS_TABLE)
|
||||
} else {
|
||||
val nick = event.bot().nick
|
||||
event.sendMessage("To convert from one currency to another:")
|
||||
event.sendMessage(helpFormat(helpCmdSyntax("%c $CURRENCY_CMD 100 USD to EUR", nick, isPrivateMsgEnabled)))
|
||||
event.sendMessage(
|
||||
helpFormat(
|
||||
helpCmdSyntax("%c $CURRENCY_CMD 50,000 GBP to USD", nick, isPrivateMsgEnabled)
|
||||
)
|
||||
)
|
||||
event.sendMessage("To list the supported currency codes:")
|
||||
event.sendMessage(
|
||||
helpFormat(
|
||||
helpCmdSyntax("%c $CURRENCY_CMD $CODES_KEYWORD", nick, isPrivateMsgEnabled)
|
||||
)
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The API Key property.
|
||||
*/
|
||||
const val API_KEY_PROP = "exchangerate-api-key"
|
||||
|
||||
// Currency command
|
||||
private const val CURRENCY_CMD = "currency"
|
||||
|
||||
// Currency codes keyword
|
||||
private const val CODES_KEYWORD = "codes"
|
||||
|
||||
// Empty symbols table.
|
||||
private const val EMPTY_SYMBOLS_TABLE = "Sorry, but the currency table is empty."
|
||||
|
||||
// Currency symbols
|
||||
private val SYMBOLS: TreeMap<String, String> = TreeMap()
|
||||
|
||||
// Decimal format
|
||||
private val DECIMAL_FORMAT = DecimalFormat("0.00#")
|
||||
|
||||
/**
|
||||
* Converts from a currency to another.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun convertCurrency(apiKey: String?, query: String): Message {
|
||||
if (apiKey.isNullOrEmpty()) {
|
||||
throw ModuleException("${CURRENCY_CMD}($query)", "No Exchange Rate API key specified.")
|
||||
}
|
||||
|
||||
val cmds = query.split(" ")
|
||||
return if (cmds.size == 4) {
|
||||
if (cmds[3] == cmds[1] || "0" == cmds[0]) {
|
||||
PublicMessage("You're kidding, right?")
|
||||
} else {
|
||||
val to = cmds[1].uppercase()
|
||||
val from = cmds[3].uppercase()
|
||||
if (SYMBOLS.contains(to) && SYMBOLS.contains(from)) {
|
||||
try {
|
||||
val amt = cmds[0].replace(",", "")
|
||||
val url = URL("https://v6.exchangerate-api.com/v6/$apiKey/pair/$to/$from/$amt")
|
||||
val body = url.reader().body
|
||||
val json = JSONObject(body)
|
||||
|
||||
if (json.getString("result") == "success") {
|
||||
val result = DECIMAL_FORMAT.format(json.getDouble("conversion_result"))
|
||||
PublicMessage(
|
||||
"${cmds[0]} ${SYMBOLS[to]} = $result ${SYMBOLS[from]}"
|
||||
)
|
||||
} else {
|
||||
ErrorMessage("Sorry, an error occurred while converting the currencies.")
|
||||
}
|
||||
} catch (ignore: IOException) {
|
||||
ErrorMessage("Sorry, an IO error occurred while converting the currencies.")
|
||||
}
|
||||
} else {
|
||||
ErrorMessage("Sounds like monopoly money to me!")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ErrorMessage("Invalid query. Let's try again.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the currency ISO symbols.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(ModuleException::class)
|
||||
fun loadSymbols(apiKey: String?) {
|
||||
if (!apiKey.isNullOrEmpty()) {
|
||||
try {
|
||||
val url = URL("https://v6.exchangerate-api.com/v6/$apiKey/codes")
|
||||
val json = JSONObject(url.reader().body)
|
||||
if (json.getString("result") == "success") {
|
||||
val codes = json.getJSONArray("supported_codes")
|
||||
for (i in 0 until codes.length()) {
|
||||
val code = codes.getJSONArray(i)
|
||||
SYMBOLS[code.getString(0)] = code.getString(1)
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw ModuleException(
|
||||
"loadCodes(): IOE",
|
||||
"An IO error has occurred while retrieving the currencies.",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
commands.add(CURRENCY_CMD)
|
||||
initProperties(API_KEY_PROP)
|
||||
loadSymbols(properties[ChatGpt.API_KEY_PROP])
|
||||
}
|
||||
}
|
87
bin/main/net/thauvin/erik/mobibot/modules/Dice.kt
Normal file
87
bin/main/net/thauvin/erik/mobibot/modules/Dice.kt
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Dice.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bold
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
/**
|
||||
* The Dice module.
|
||||
*/
|
||||
class Dice : AbstractModule() {
|
||||
override val name = "Dice"
|
||||
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
val arg = if (args.isBlank()) "2d6" else args.trim()
|
||||
val match = Regex("^([1-9]|[12]\\d|3[0-2])[dD]([1-9]|[12]\\d|3[0-2])$").find(arg)
|
||||
if (match != null) {
|
||||
val (dice, sides) = match.destructured
|
||||
event.respond("you rolled " + roll(dice.toInt(), sides.toInt()))
|
||||
} else {
|
||||
helpResponse(event)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Dice command
|
||||
private const val DICE_CMD = "dice"
|
||||
|
||||
@JvmStatic
|
||||
fun roll(dice: Int, sides: Int): String {
|
||||
val result = StringBuilder()
|
||||
var total = 0
|
||||
|
||||
repeat(dice) {
|
||||
val roll = (1..sides).random()
|
||||
total += roll
|
||||
|
||||
if (result.isNotEmpty()) {
|
||||
result.append(" + ")
|
||||
}
|
||||
|
||||
result.append(roll.bold())
|
||||
}
|
||||
|
||||
if (dice != 1) {
|
||||
result.append(" = ${total.bold()}")
|
||||
}
|
||||
|
||||
return result.toString()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
commands.add(DICE_CMD)
|
||||
help.add("To roll 2 dice with 6 sides:")
|
||||
help.add(helpFormat("%c $DICE_CMD [2d6]"))
|
||||
}
|
||||
}
|
162
bin/main/net/thauvin/erik/mobibot/modules/GoogleSearch.kt
Normal file
162
bin/main/net/thauvin/erik/mobibot/modules/GoogleSearch.kt
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* GoogleSearch.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.mobibot.ReleaseInfo
|
||||
import net.thauvin.erik.mobibot.Utils.capitalise
|
||||
import net.thauvin.erik.mobibot.Utils.colorize
|
||||
import net.thauvin.erik.mobibot.Utils.encodeUrl
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.reader
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.Utils.unescapeXml
|
||||
import net.thauvin.erik.mobibot.msg.ErrorMessage
|
||||
import net.thauvin.erik.mobibot.msg.Message
|
||||
import net.thauvin.erik.mobibot.msg.NoticeMessage
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.pircbotx.Colors
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* The GoogleSearch module.
|
||||
*/
|
||||
class GoogleSearch : AbstractModule() {
|
||||
private val logger: Logger = LoggerFactory.getLogger(GoogleSearch::class.java)
|
||||
|
||||
override val name = "GoogleSearch"
|
||||
|
||||
/**
|
||||
* Searches Google.
|
||||
*/
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
if (args.isNotBlank()) {
|
||||
try {
|
||||
val results = searchGoogle(
|
||||
args,
|
||||
properties[API_KEY_PROP],
|
||||
properties[CSE_KEY_PROP],
|
||||
event.user.nick
|
||||
)
|
||||
for (msg in results) {
|
||||
if (msg.isError) {
|
||||
event.respond(msg.msg.colorize(msg.color))
|
||||
} else {
|
||||
event.sendMessage(channel, msg)
|
||||
}
|
||||
}
|
||||
} catch (e: ModuleException) {
|
||||
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
|
||||
e.message?.let {
|
||||
event.respond(it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
helpResponse(event)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Google API Key property
|
||||
const val API_KEY_PROP = "google-api-key"
|
||||
|
||||
// Google Custom Search Engine ID property
|
||||
const val CSE_KEY_PROP = "google-cse-cx"
|
||||
|
||||
// Google command
|
||||
private const val GOOGLE_CMD = "google"
|
||||
|
||||
/**
|
||||
* Performs a search on Google.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(ModuleException::class)
|
||||
fun searchGoogle(
|
||||
query: String,
|
||||
apiKey: String?,
|
||||
cseKey: String?,
|
||||
quotaUser: String = ReleaseInfo.PROJECT
|
||||
): List<Message> {
|
||||
if (apiKey.isNullOrBlank() || cseKey.isNullOrBlank()) {
|
||||
throw ModuleException(
|
||||
"${GoogleSearch::class.java.name} is disabled.",
|
||||
"${GOOGLE_CMD.capitalise()} is disabled. The API keys are missing."
|
||||
)
|
||||
}
|
||||
val results = mutableListOf<Message>()
|
||||
if (query.isNotBlank()) {
|
||||
try {
|
||||
val url = URL(
|
||||
"https://www.googleapis.com/customsearch/v1?key=$apiKey&cx=$cseKey" +
|
||||
""aUser=${quotaUser}&q=${query.encodeUrl()}&filter=1&num=5&alt=json"
|
||||
)
|
||||
val json = JSONObject(url.reader().body)
|
||||
if (json.has("items")) {
|
||||
val ja = json.getJSONArray("items")
|
||||
for (i in 0 until ja.length()) {
|
||||
val j = ja.getJSONObject(i)
|
||||
results.add(NoticeMessage(j.getString("title").unescapeXml()))
|
||||
results.add(NoticeMessage(helpFormat(j.getString("link"), false), Colors.DARK_GREEN))
|
||||
}
|
||||
} else if (json.has("error")) {
|
||||
val error = json.getJSONObject("error")
|
||||
val message = error.getString("message")
|
||||
throw ModuleException("searchGoogle($query): ${error.getInt("code")} : $message", message)
|
||||
} else {
|
||||
results.add(ErrorMessage("No results found.", Colors.RED))
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw ModuleException("searchGoogle($query): IOE", "An IO error has occurred searching Google.", e)
|
||||
} catch (e: JSONException) {
|
||||
throw ModuleException(
|
||||
"searchGoogle($query): JSON",
|
||||
"A JSON error has occurred searching Google.",
|
||||
e
|
||||
)
|
||||
}
|
||||
} else {
|
||||
results.add(ErrorMessage("Invalid query. Please try again."))
|
||||
}
|
||||
return results
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
commands.add(GOOGLE_CMD)
|
||||
help.add("To search Google:")
|
||||
help.add(helpFormat("%c $GOOGLE_CMD <query>"))
|
||||
initProperties(API_KEY_PROP, CSE_KEY_PROP)
|
||||
}
|
||||
}
|
105
bin/main/net/thauvin/erik/mobibot/modules/Joke.kt
Normal file
105
bin/main/net/thauvin/erik/mobibot/modules/Joke.kt
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Joke.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.jokeapi.exceptions.HttpErrorException
|
||||
import net.thauvin.erik.jokeapi.exceptions.JokeException
|
||||
import net.thauvin.erik.jokeapi.joke
|
||||
import net.thauvin.erik.jokeapi.models.Type
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.colorize
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.msg.Message
|
||||
import net.thauvin.erik.mobibot.msg.PublicMessage
|
||||
import org.json.JSONException
|
||||
import org.pircbotx.Colors
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* The Joke module.
|
||||
*/
|
||||
class Joke : AbstractModule() {
|
||||
private val logger: Logger = LoggerFactory.getLogger(Joke::class.java)
|
||||
|
||||
override val name = "Joke"
|
||||
|
||||
/**
|
||||
* Returns a random joke from [JokeAPI](https://v2.jokeapi.dev/).
|
||||
*/
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
with(event.bot()) {
|
||||
try {
|
||||
randomJoke().forEach {
|
||||
sendIRC().notice(channel, it.msg.colorize(it.color))
|
||||
}
|
||||
} catch (e: ModuleException) {
|
||||
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
|
||||
e.message?.let {
|
||||
event.respond(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Joke command
|
||||
private const val JOKE_CMD = "joke"
|
||||
|
||||
/**
|
||||
* Retrieves a random joke.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(ModuleException::class)
|
||||
fun randomJoke(): List<Message> {
|
||||
return try {
|
||||
val joke = joke(safe = true, type = Type.SINGLE, splitNewLine = true)
|
||||
joke.joke.map { PublicMessage(it, Colors.CYAN) }
|
||||
} catch (e: JokeException) {
|
||||
throw ModuleException("randomJoke(): ${e.additionalInfo}", e.message, e)
|
||||
} catch (e: HttpErrorException) {
|
||||
throw ModuleException("randomJoke(): HTTP: ${e.statusCode}", e.message, e)
|
||||
} catch (e: IOException) {
|
||||
throw ModuleException("randomJoke(): IOE", "An IO error has occurred retrieving a random joke.", e)
|
||||
} catch (e: JSONException) {
|
||||
throw ModuleException("randomJoke(): JSON", "A parsing error has occurred retrieving a random joke.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
commands.add(JOKE_CMD)
|
||||
help.add("To display a random joke:")
|
||||
help.add(helpFormat("%c $JOKE_CMD"))
|
||||
}
|
||||
}
|
171
bin/main/net/thauvin/erik/mobibot/modules/Lookup.kt
Normal file
171
bin/main/net/thauvin/erik/mobibot/modules/Lookup.kt
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Lookup.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.mobibot.Constants
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import org.apache.commons.net.whois.WhoisClient
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.net.UnknownHostException
|
||||
|
||||
/**
|
||||
* The Lookup module.
|
||||
*/
|
||||
class Lookup : AbstractModule() {
|
||||
private val logger: Logger = LoggerFactory.getLogger(Lookup::class.java)
|
||||
|
||||
override val name = "Lookup"
|
||||
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
if (args.matches("(\\S.)+(\\S)+".toRegex())) {
|
||||
try {
|
||||
event.respondWith(nslookup(args).prependIndent())
|
||||
} catch (ignore: UnknownHostException) {
|
||||
if (args.matches(
|
||||
("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)")
|
||||
.toRegex()
|
||||
)
|
||||
) {
|
||||
try {
|
||||
val lines = whois(args)
|
||||
if (lines.isNotEmpty()) {
|
||||
var line: String
|
||||
var hasData = false
|
||||
for (rawLine in lines) {
|
||||
line = rawLine.trim()
|
||||
if (line.matches("^\\b(?!\\b[Cc]omment\\b)\\w+\\b: .*$".toRegex())) {
|
||||
if (!hasData) {
|
||||
event.respondWith(line)
|
||||
hasData = true
|
||||
} else {
|
||||
event.bot().sendIRC().notice(event.user.nick, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
event.respond("Unknown host.")
|
||||
}
|
||||
} catch (ioe: IOException) {
|
||||
if (logger.isWarnEnabled) {
|
||||
logger.warn("Unable to perform whois IP lookup: $args", ioe)
|
||||
}
|
||||
event.respond("Unable to perform whois IP lookup: ${ioe.message}")
|
||||
}
|
||||
} else {
|
||||
event.respond("Unknown host.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
helpResponse(event)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The whois default host.
|
||||
*/
|
||||
const val WHOIS_HOST = "whois.arin.net"
|
||||
|
||||
// Lookup command
|
||||
private const val LOOKUP_CMD = "lookup"
|
||||
|
||||
/**
|
||||
* Performs a DNS lookup on the specified query.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(UnknownHostException::class)
|
||||
fun nslookup(query: String): String {
|
||||
val buffer = StringBuilder()
|
||||
val results = InetAddress.getAllByName(query)
|
||||
var hostInfo: String
|
||||
for (result in results) {
|
||||
if (result.hostAddress == query) {
|
||||
hostInfo = result.hostName
|
||||
if (hostInfo == query) {
|
||||
throw UnknownHostException()
|
||||
}
|
||||
} else {
|
||||
hostInfo = result.hostAddress
|
||||
}
|
||||
if (buffer.isNotEmpty()) {
|
||||
buffer.append(", ")
|
||||
}
|
||||
buffer.append(hostInfo)
|
||||
}
|
||||
return buffer.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a whois IP query.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun whois(query: String): List<String> {
|
||||
return whois(query, WHOIS_HOST)
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a whois IP query.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IOException::class)
|
||||
fun whois(query: String, host: String): List<String> {
|
||||
val whoisClient = WhoisClient()
|
||||
val lines: List<String>
|
||||
with(whoisClient) {
|
||||
try {
|
||||
defaultTimeout = Constants.CONNECT_TIMEOUT
|
||||
connect(host)
|
||||
soTimeout = Constants.CONNECT_TIMEOUT
|
||||
setSoLinger(false, 0)
|
||||
lines = if (WHOIS_HOST == host) {
|
||||
query("n - $query").split("\n")
|
||||
} else {
|
||||
query(query).split("\n")
|
||||
}
|
||||
} finally {
|
||||
disconnect()
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
commands.add(LOOKUP_CMD)
|
||||
help.add("To perform a DNS lookup query:")
|
||||
help.add(helpFormat("%c $LOOKUP_CMD <ip address or hostname>"))
|
||||
}
|
||||
}
|
149
bin/main/net/thauvin/erik/mobibot/modules/Mastodon.kt
Normal file
149
bin/main/net/thauvin/erik/mobibot/modules/Mastodon.kt
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Mastodon.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils
|
||||
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[AUTO_POST_PROP].toBoolean()
|
||||
|
||||
override val isValidProperties: Boolean
|
||||
get() = !(properties[INSTANCE_PROP].isNullOrBlank() || properties[ACCESS_TOKEN_PROP].isNullOrBlank())
|
||||
|
||||
/**
|
||||
* Formats the entry for posting.
|
||||
*/
|
||||
override fun formatEntry(entry: EntryLink): String {
|
||||
return "${entry.title} (via ${entry.nick} on ${entry.channel})${formatTags(entry)}\n\n${entry.link}"
|
||||
}
|
||||
|
||||
private fun formatTags(entry: EntryLink): String {
|
||||
return entry.tags.filter { !it.name.equals(entry.channel.removePrefix("#"), true) }
|
||||
.joinToString(separator = " ", prefix = "\n\n") { "#${it.name}" }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"
|
||||
private const val TOOT_CMD = "toot"
|
||||
|
||||
/**
|
||||
* Post 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)
|
||||
commands.add(TOOT_CMD)
|
||||
help.add("To toot on Mastodon:")
|
||||
help.add(Utils.helpFormat("%c $TOOT_CMD <message>"))
|
||||
properties[AUTO_POST_PROP] = "false"
|
||||
initProperties(ACCESS_TOKEN_PROP, HANDLE_PROP, INSTANCE_PROP)
|
||||
}
|
||||
}
|
45
bin/main/net/thauvin/erik/mobibot/modules/ModuleException.kt
Normal file
45
bin/main/net/thauvin/erik/mobibot/modules/ModuleException.kt
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* ModuleException.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
/**
|
||||
* The `ModuleException` class.
|
||||
*/
|
||||
class ModuleException @JvmOverloads constructor(
|
||||
val debugMessage: String,
|
||||
message: String? = null,
|
||||
cause: Throwable? = null
|
||||
) : Exception(message, cause) {
|
||||
companion object {
|
||||
@Suppress("ConstPropertyName")
|
||||
private const val serialVersionUID = 1L
|
||||
}
|
||||
}
|
83
bin/main/net/thauvin/erik/mobibot/modules/Ping.kt
Normal file
83
bin/main/net/thauvin/erik/mobibot/modules/Ping.kt
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Ping.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bot
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
/**
|
||||
* The Ping module.
|
||||
*/
|
||||
class Ping : AbstractModule() {
|
||||
override val name = "Ping"
|
||||
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
event.bot().sendIRC().action(channel, randomPing())
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The ping responses.
|
||||
*/
|
||||
@JvmField
|
||||
val PINGS = listOf(
|
||||
"is barely alive.",
|
||||
"is trying to stay awake.",
|
||||
"has gone fishing.",
|
||||
"is somewhere over the rainbow.",
|
||||
"has fallen and can't get up.",
|
||||
"is running. You better go chase it.",
|
||||
"has just spontaneously combusted.",
|
||||
"is talking to itself... don't interrupt. That's rude.",
|
||||
"is bartending at an AA meeting.",
|
||||
"is hibernating.",
|
||||
"is saving energy: apathetic mode activated.",
|
||||
"is busy. Go away!"
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun randomPing(): String {
|
||||
return PINGS[PINGS.indices.random()]
|
||||
}
|
||||
|
||||
/**
|
||||
* The ping command.
|
||||
*/
|
||||
private const val PING_CMD = "ping"
|
||||
}
|
||||
|
||||
init {
|
||||
commands.add(PING_CMD)
|
||||
help.add("To ping the bot:")
|
||||
help.add(helpFormat("%c $PING_CMD"))
|
||||
}
|
||||
}
|
114
bin/main/net/thauvin/erik/mobibot/modules/RockPaperScissors.kt
Normal file
114
bin/main/net/thauvin/erik/mobibot/modules/RockPaperScissors.kt
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* RockPaperScissors.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bold
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
|
||||
|
||||
/**
|
||||
* Simple module example in Kotlin.
|
||||
*/
|
||||
class RockPaperScissors : AbstractModule() {
|
||||
override val name = "RockPaperScissors"
|
||||
|
||||
init {
|
||||
with(commands) {
|
||||
add(Hands.ROCK.name.lowercase())
|
||||
add(Hands.PAPER.name.lowercase())
|
||||
add(Hands.SCISSORS.name.lowercase())
|
||||
}
|
||||
|
||||
with(help) {
|
||||
add("To play Rock Paper Scissors:")
|
||||
add(
|
||||
helpFormat(
|
||||
"%c ${Hands.ROCK.name.lowercase()} | ${Hands.PAPER.name.lowercase()}"
|
||||
+ " | ${Hands.SCISSORS.name.lowercase()}"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class Hands(val action: String) {
|
||||
ROCK("crushes") {
|
||||
override fun beats(hand: Hands): Boolean {
|
||||
return hand == SCISSORS
|
||||
}
|
||||
},
|
||||
PAPER("covers") {
|
||||
override fun beats(hand: Hands): Boolean {
|
||||
return hand == ROCK
|
||||
}
|
||||
},
|
||||
SCISSORS("cuts") {
|
||||
override fun beats(hand: Hands): Boolean {
|
||||
return hand == PAPER
|
||||
}
|
||||
};
|
||||
|
||||
abstract fun beats(hand: Hands): Boolean
|
||||
}
|
||||
|
||||
companion object {
|
||||
// For testing.
|
||||
fun winLoseOrDraw(player: String, bot: String): String {
|
||||
val hand = Hands.valueOf(player.uppercase())
|
||||
val botHand = Hands.valueOf(bot.uppercase())
|
||||
|
||||
return when {
|
||||
hand == botHand -> "draw"
|
||||
hand.beats(botHand) -> "win"
|
||||
else -> "lose"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
val hand = Hands.valueOf(cmd.uppercase())
|
||||
val botHand = Hands.entries[(0..Hands.entries.size).random()]
|
||||
when {
|
||||
hand == botHand -> {
|
||||
event.respond("${hand.name} vs. ${botHand.name} » You ${"tie".bold()}.")
|
||||
}
|
||||
|
||||
hand.beats(botHand) -> {
|
||||
event.respond("${hand.name.bold()} ${hand.action} ${botHand.name} » You ${"win".bold()}!")
|
||||
}
|
||||
|
||||
else -> {
|
||||
event.respond("${botHand.name.bold()} ${botHand.action} ${hand.name} » You ${"lose".bold()}!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
236
bin/main/net/thauvin/erik/mobibot/modules/StockQuote.kt
Normal file
236
bin/main/net/thauvin/erik/mobibot/modules/StockQuote.kt
Normal file
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* StockQuote.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.capitalise
|
||||
import net.thauvin.erik.mobibot.Utils.encodeUrl
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.reader
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.Utils.unescapeXml
|
||||
import net.thauvin.erik.mobibot.msg.ErrorMessage
|
||||
import net.thauvin.erik.mobibot.msg.Message
|
||||
import net.thauvin.erik.mobibot.msg.NoticeMessage
|
||||
import net.thauvin.erik.mobibot.msg.PublicMessage
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* The StockQuote module.
|
||||
*/
|
||||
class StockQuote : AbstractModule() {
|
||||
private val logger: Logger = LoggerFactory.getLogger(StockQuote::class.java)
|
||||
|
||||
override val name = "StockQuote"
|
||||
|
||||
/**
|
||||
* Returns the specified stock quote from Alpha Vantage.
|
||||
*/
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
if (args.isNotBlank()) {
|
||||
try {
|
||||
val messages = getQuote(args, properties[API_KEY_PROP])
|
||||
for (msg in messages) {
|
||||
event.sendMessage(channel, msg)
|
||||
}
|
||||
} catch (e: ModuleException) {
|
||||
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
|
||||
e.message?.let {
|
||||
event.respond(it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
helpResponse(event)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The API property key.
|
||||
*/
|
||||
const val API_KEY_PROP = "alphavantage-api-key"
|
||||
|
||||
/**
|
||||
* The Invalid Symbol error string.
|
||||
*/
|
||||
const val INVALID_SYMBOL = "Invalid symbol."
|
||||
|
||||
// API URL
|
||||
private const val API_URL = "https://www.alphavantage.co/query?function="
|
||||
|
||||
// Quote command
|
||||
private const val STOCK_CMD = "stock"
|
||||
|
||||
@Throws(ModuleException::class)
|
||||
private fun getJsonResponse(response: String, debugMessage: String): JSONObject {
|
||||
return if (response.isNotBlank()) {
|
||||
val json = JSONObject(response)
|
||||
try {
|
||||
val info = json.getString("Information")
|
||||
if (info.isNotEmpty()) {
|
||||
throw ModuleException(debugMessage, info.unescapeXml())
|
||||
}
|
||||
} catch (ignore: JSONException) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
var error = json.getString("Note")
|
||||
if (error.isNotEmpty()) {
|
||||
throw ModuleException(debugMessage, error.unescapeXml())
|
||||
}
|
||||
error = json.getString("Error Message")
|
||||
if (error.isNotEmpty()) {
|
||||
throw ModuleException(debugMessage, error.unescapeXml())
|
||||
}
|
||||
} catch (ignore: JSONException) {
|
||||
// Do nothing
|
||||
}
|
||||
json
|
||||
} else {
|
||||
throw ModuleException(debugMessage, "Empty Response.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a stock quote.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(ModuleException::class)
|
||||
fun getQuote(symbol: String, apiKey: String?): List<Message> {
|
||||
if (apiKey.isNullOrBlank()) {
|
||||
throw ModuleException(
|
||||
"${StockQuote::class.java.name} is disabled.",
|
||||
"${STOCK_CMD.capitalise()} is disabled. The API key is missing."
|
||||
)
|
||||
}
|
||||
val messages = mutableListOf<Message>()
|
||||
if (symbol.isNotBlank()) {
|
||||
val debugMessage = "getQuote($symbol)"
|
||||
var response: String
|
||||
try {
|
||||
with(messages) {
|
||||
// Search for symbol/keywords
|
||||
response = URL(
|
||||
"${API_URL}SYMBOL_SEARCH&keywords=" + symbol.encodeUrl() + "&apikey="
|
||||
+ apiKey.encodeUrl()
|
||||
).reader().body
|
||||
var json = getJsonResponse(response, debugMessage)
|
||||
val symbols = json.getJSONArray("bestMatches")
|
||||
if (symbols.isEmpty) {
|
||||
messages.add(ErrorMessage(INVALID_SYMBOL))
|
||||
} else {
|
||||
val symbolInfo = symbols.getJSONObject(0)
|
||||
|
||||
// Get quote for symbol
|
||||
response = URL(
|
||||
"${API_URL}GLOBAL_QUOTE&symbol="
|
||||
+ symbolInfo.getString("1. symbol").encodeUrl() + "&apikey="
|
||||
+ apiKey.encodeUrl()
|
||||
).reader().body
|
||||
json = getJsonResponse(response, debugMessage)
|
||||
val quote = json.getJSONObject("Global Quote")
|
||||
if (quote.isEmpty) {
|
||||
add(ErrorMessage(INVALID_SYMBOL))
|
||||
} else {
|
||||
|
||||
add(
|
||||
PublicMessage(
|
||||
"Symbol: " + quote.getString("01. symbol").unescapeXml()
|
||||
+ " [" + symbolInfo.getString("2. name").unescapeXml() + ']'
|
||||
)
|
||||
)
|
||||
|
||||
val pad = 10
|
||||
|
||||
add(
|
||||
PublicMessage(
|
||||
"Price:".padEnd(pad).prependIndent()
|
||||
+ quote.getString("05. price").unescapeXml()
|
||||
)
|
||||
)
|
||||
add(
|
||||
PublicMessage(
|
||||
"Previous:".padEnd(pad).prependIndent()
|
||||
+ quote.getString("08. previous close").unescapeXml()
|
||||
)
|
||||
)
|
||||
|
||||
val data = arrayOf(
|
||||
"Open" to "02. open",
|
||||
"High" to "03. high",
|
||||
"Low" to "04. low",
|
||||
"Volume" to "06. volume",
|
||||
"Latest" to "07. latest trading day"
|
||||
)
|
||||
|
||||
data.forEach {
|
||||
add(
|
||||
NoticeMessage(
|
||||
"${it.first}:".padEnd(pad).prependIndent()
|
||||
+ quote.getString(it.second).unescapeXml()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
add(
|
||||
NoticeMessage(
|
||||
"Change:".padEnd(pad).prependIndent()
|
||||
+ quote.getString("09. change").unescapeXml()
|
||||
+ " [" + quote.getString("10. change percent").unescapeXml() + ']'
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw ModuleException("$debugMessage: IOE", "An IO error has occurred retrieving a stock quote.", e)
|
||||
} catch (e: NullPointerException) {
|
||||
throw ModuleException("$debugMessage: NPE", "An error has occurred retrieving a stock quote.", e)
|
||||
}
|
||||
} else {
|
||||
messages.add(ErrorMessage(INVALID_SYMBOL))
|
||||
}
|
||||
return messages
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
commands.add(STOCK_CMD)
|
||||
help.add("To retrieve a stock quote:")
|
||||
help.add(helpFormat("%c $STOCK_CMD <symbol|keywords>"))
|
||||
initProperties(API_KEY_PROP)
|
||||
}
|
||||
}
|
BIN
bin/main/net/thauvin/erik/mobibot/modules/War.class
Normal file
BIN
bin/main/net/thauvin/erik/mobibot/modules/War.class
Normal file
Binary file not shown.
250
bin/main/net/thauvin/erik/mobibot/modules/Weather2.kt
Normal file
250
bin/main/net/thauvin/erik/mobibot/modules/Weather2.kt
Normal file
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* Weather2.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.aksingh.owmjapis.api.APIException
|
||||
import net.aksingh.owmjapis.core.OWM
|
||||
import net.aksingh.owmjapis.core.OWM.Country
|
||||
import net.aksingh.owmjapis.model.CurrentWeather
|
||||
import net.thauvin.erik.mobibot.Utils.bold
|
||||
import net.thauvin.erik.mobibot.Utils.capitalise
|
||||
import net.thauvin.erik.mobibot.Utils.capitalizeWords
|
||||
import net.thauvin.erik.mobibot.Utils.encodeUrl
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import net.thauvin.erik.mobibot.msg.ErrorMessage
|
||||
import net.thauvin.erik.mobibot.msg.Message
|
||||
import net.thauvin.erik.mobibot.msg.NoticeMessage
|
||||
import net.thauvin.erik.mobibot.msg.PublicMessage
|
||||
import org.pircbotx.Colors
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* The `Weather2` module.
|
||||
*/
|
||||
class Weather2 : AbstractModule() {
|
||||
private val logger: Logger = LoggerFactory.getLogger(Weather2::class.java)
|
||||
|
||||
override val name = "Weather"
|
||||
|
||||
/**
|
||||
* Fetches the weather data from a specific city.
|
||||
*/
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
if (args.isNotBlank()) {
|
||||
try {
|
||||
val messages = getWeather(args, properties[API_KEY_PROP])
|
||||
if (messages[0].isError) {
|
||||
helpResponse(event)
|
||||
} else {
|
||||
for (msg in messages) {
|
||||
event.sendMessage(channel, msg)
|
||||
}
|
||||
}
|
||||
} catch (e: ModuleException) {
|
||||
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
|
||||
e.message?.let {
|
||||
event.respond(it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
helpResponse(event)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The OpenWeatherMap API Key property.
|
||||
*/
|
||||
const val API_KEY_PROP = "owm-api-key"
|
||||
|
||||
// Weather command
|
||||
private const val WEATHER_CMD = "weather"
|
||||
|
||||
/**
|
||||
* Converts and rounds temperature from °F to °C.
|
||||
*/
|
||||
fun ftoC(d: Double): Pair<Int, Int> {
|
||||
val c = (d - 32) * 5 / 9
|
||||
return d.roundToInt() to c.roundToInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a country based on its country code. Defaults to [Country.UNITED_STATES] if not found.
|
||||
*/
|
||||
fun getCountry(countryCode: String): Country {
|
||||
for (c in Country.entries) {
|
||||
if (c.value.equals(countryCode, ignoreCase = true)) {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return Country.UNITED_STATES
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the weather data.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(ModuleException::class)
|
||||
fun getWeather(query: String, apiKey: String?): List<Message> {
|
||||
if (apiKey.isNullOrBlank()) {
|
||||
throw ModuleException(
|
||||
"${Weather2::class.java.name} is disabled.",
|
||||
"${WEATHER_CMD.capitalise()} is disabled. The API key is missing."
|
||||
)
|
||||
}
|
||||
val owm = OWM(apiKey)
|
||||
val messages = mutableListOf<Message>()
|
||||
owm.unit = OWM.Unit.IMPERIAL
|
||||
if (query.isNotBlank()) {
|
||||
val argv = query.split(",")
|
||||
if (argv.size in 1..2) {
|
||||
val city = argv[0].trim()
|
||||
val code: String = if (argv.size > 1 && argv[1].isNotBlank()) {
|
||||
argv[1].trim()
|
||||
} else {
|
||||
"US"
|
||||
}
|
||||
try {
|
||||
val country = getCountry(code)
|
||||
val cwd: CurrentWeather = if (city.matches("\\d+".toRegex())) {
|
||||
owm.currentWeatherByZipCode(city.toInt(), country)
|
||||
} else {
|
||||
owm.currentWeatherByCityName(city, country)
|
||||
}
|
||||
if (cwd.hasCityName()) {
|
||||
messages.add(
|
||||
PublicMessage(
|
||||
"City: ${cwd.cityName}, " +
|
||||
country.name.replace('_', ' ').capitalizeWords() + " [${country.value}]"
|
||||
)
|
||||
)
|
||||
cwd.mainData?.let {
|
||||
with(it) {
|
||||
if (hasTemp()) {
|
||||
temp?.let { t ->
|
||||
val (f, c) = ftoC(t)
|
||||
messages.add(PublicMessage("Temperature: ${f}°F, ${c}°C"))
|
||||
}
|
||||
}
|
||||
if (hasHumidity()) {
|
||||
humidity?.let { h ->
|
||||
messages.add(NoticeMessage("Humidity: ${h.roundToInt()}%"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cwd.hasWindData()) {
|
||||
cwd.windData?.let {
|
||||
if (it.hasSpeed()) {
|
||||
it.speed?.let { s ->
|
||||
val w = mphToKmh(s)
|
||||
messages.add(NoticeMessage("Wind: ${w.first} mph, ${w.second} km/h"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cwd.hasWeatherList()) {
|
||||
val condition = StringBuilder("Condition:")
|
||||
cwd.weatherList?.let {
|
||||
for (w in it) {
|
||||
w?.let {
|
||||
condition.append(' ')
|
||||
.append(w.getDescription().capitalise())
|
||||
.append('.')
|
||||
}
|
||||
}
|
||||
messages.add(NoticeMessage(condition.toString()))
|
||||
}
|
||||
}
|
||||
if (cwd.hasCityId()) {
|
||||
cwd.cityId?.let {
|
||||
if (it > 0) {
|
||||
messages.add(
|
||||
NoticeMessage("https://openweathermap.org/city/$it", Colors.GREEN)
|
||||
)
|
||||
} else {
|
||||
messages.add(
|
||||
NoticeMessage(
|
||||
"https://openweathermap.org/find?q="
|
||||
+ "$city,${code.uppercase()}".encodeUrl(),
|
||||
Colors.GREEN
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: APIException) {
|
||||
if (e.code == 404) {
|
||||
throw ModuleException(
|
||||
"getWeather($query): API ${e.code}",
|
||||
"The requested city was not found.",
|
||||
e
|
||||
)
|
||||
} else {
|
||||
throw ModuleException("getWeather($query): API ${e.code}", e.message, e)
|
||||
}
|
||||
} catch (e: NullPointerException) {
|
||||
throw ModuleException("getWeather($query): NPE", "Unable to perform weather lookup.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (messages.isEmpty()) {
|
||||
messages.add(ErrorMessage("Invalid syntax."))
|
||||
}
|
||||
return messages
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts and rounds temperature from mph to km/h.
|
||||
*/
|
||||
fun mphToKmh(w: Double): Pair<Int, Int> {
|
||||
val kmh = w * 1.60934
|
||||
return w.roundToInt() to kmh.roundToInt()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
commands.add(WEATHER_CMD)
|
||||
with(help) {
|
||||
add("To display weather information:")
|
||||
add(helpFormat("%c $WEATHER_CMD <city> [, <country code>]"))
|
||||
add("For example:")
|
||||
add(helpFormat("%c $WEATHER_CMD paris, fr"))
|
||||
add("The default ISO 3166 country code is ${"US".bold()}. Zip codes supported in most countries.")
|
||||
}
|
||||
initProperties(API_KEY_PROP)
|
||||
}
|
||||
}
|
142
bin/main/net/thauvin/erik/mobibot/modules/WolframAlpha.kt
Normal file
142
bin/main/net/thauvin/erik/mobibot/modules/WolframAlpha.kt
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* WolframAlpha.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils
|
||||
import net.thauvin.erik.mobibot.Utils.encodeUrl
|
||||
import net.thauvin.erik.mobibot.Utils.isHttpSuccess
|
||||
import net.thauvin.erik.mobibot.Utils.reader
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
|
||||
class WolframAlpha : AbstractModule() {
|
||||
private val logger: Logger = LoggerFactory.getLogger(WolframAlpha::class.java)
|
||||
|
||||
override val name = "WolframAlpha"
|
||||
|
||||
private fun getUnits(unit: String?): String {
|
||||
return if (unit?.lowercase() == METRIC) {
|
||||
METRIC
|
||||
} else {
|
||||
IMPERIAL
|
||||
}
|
||||
}
|
||||
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
if (args.isNotBlank()) {
|
||||
try {
|
||||
val query = args.trim().split("units=", limit = 2, ignoreCase = true)
|
||||
event.sendMessage(
|
||||
queryWolfram(
|
||||
query[0].trim(),
|
||||
units = if (query.size == 2) {
|
||||
getUnits(query[1].trim())
|
||||
} else {
|
||||
getUnits(properties[UNITS_PROP])
|
||||
},
|
||||
appId = properties[APPID_KEY_PROP]
|
||||
)
|
||||
)
|
||||
} catch (e: ModuleException) {
|
||||
if (logger.isWarnEnabled) logger.warn(e.debugMessage, e)
|
||||
e.message?.let {
|
||||
event.respond(it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
helpResponse(event)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The Wolfram Alpha API Key property.
|
||||
*/
|
||||
const val APPID_KEY_PROP = "wolfram-appid"
|
||||
|
||||
/**
|
||||
* The Wolfram units properties
|
||||
*/
|
||||
const val UNITS_PROP = "wolfram-units"
|
||||
|
||||
const val METRIC = "metric"
|
||||
const val IMPERIAL = "imperial"
|
||||
|
||||
// Wolfram command
|
||||
private const val WOLFRAM_CMD = "wolfram"
|
||||
|
||||
// Wolfram Alpha API URL
|
||||
private const val API_URL = "http://api.wolframalpha.com/v1/spoken?appid="
|
||||
|
||||
@JvmStatic
|
||||
@Throws(ModuleException::class)
|
||||
fun queryWolfram(query: String, units: String = IMPERIAL, appId: String?): String {
|
||||
if (!appId.isNullOrEmpty()) {
|
||||
try {
|
||||
val urlReader = URL("${API_URL}${appId}&units=${units}&i=" + query.encodeUrl()).reader()
|
||||
if (urlReader.responseCode.isHttpSuccess()) {
|
||||
return urlReader.body
|
||||
} else {
|
||||
throw ModuleException(
|
||||
"wolfram($query): ${urlReader.responseCode} : ${urlReader.body} ",
|
||||
urlReader.body.ifEmpty {
|
||||
"Looks like Wolfram Alpha isn't able to answer that. (${urlReader.responseCode})"
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch (ioe: IOException) {
|
||||
throw ModuleException(
|
||||
"wolfram($query): IOE", "An IO Error occurred while querying Wolfram Alpha.", ioe
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw ModuleException("wolfram($query): No API Key", "No Wolfram Alpha API key specified.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
commands.add(WOLFRAM_CMD)
|
||||
with(help) {
|
||||
add("To get answers from Wolfram Alpha:")
|
||||
add(Utils.helpFormat("%c $WOLFRAM_CMD <query> [units=(${METRIC}|${IMPERIAL})]"))
|
||||
add("For example:")
|
||||
add(Utils.helpFormat("%c $WOLFRAM_CMD days until christmas"))
|
||||
add(Utils.helpFormat("%c $WOLFRAM_CMD distance earth moon units=metric"))
|
||||
}
|
||||
initProperties(APPID_KEY_PROP, UNITS_PROP)
|
||||
}
|
||||
}
|
390
bin/main/net/thauvin/erik/mobibot/modules/WorldTime.kt
Normal file
390
bin/main/net/thauvin/erik/mobibot/modules/WorldTime.kt
Normal file
|
@ -0,0 +1,390 @@
|
|||
/*
|
||||
* WorldTime.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.modules
|
||||
|
||||
import net.thauvin.erik.mobibot.Utils.bold
|
||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||
import net.thauvin.erik.mobibot.Utils.sendList
|
||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.temporal.ChronoField
|
||||
|
||||
/**
|
||||
* The WorldTime module.
|
||||
*/
|
||||
class WorldTime : AbstractModule() {
|
||||
override val name = "WorldTime"
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Beats (Internet Time) keyword
|
||||
*/
|
||||
const val BEATS_KEYWORD = ".beats"
|
||||
|
||||
/**
|
||||
* Supported countries
|
||||
*/
|
||||
val COUNTRIES_MAP = buildMap<String, String> {
|
||||
put("AG", "America/Antigua")
|
||||
put("AI", "America/Anguilla")
|
||||
put("AE", "Asia/Dubai")
|
||||
put("AD", "Europe/Andorra")
|
||||
put("AKDT", "America/Anchorage")
|
||||
put("AF", "Asia/Kabul")
|
||||
put("AKST", "America/Anchorage")
|
||||
put("AL", "Europe/Tirane")
|
||||
put("AM", "Asia/Yerevan")
|
||||
put("AO", "Africa/Luanda")
|
||||
put("AQ", "Antarctica/South_Pole")
|
||||
put("AR", "America/Argentina/Buenos_Aires")
|
||||
put("AS", "Pacific/Pago_Pago")
|
||||
put("AT", "Europe/Vienna")
|
||||
put("AU", "Australia/Sydney")
|
||||
put("AW", "America/Aruba")
|
||||
put("AX", "Europe/Mariehamn")
|
||||
put("AZ", "Asia/Baku")
|
||||
put("BA", "Europe/Sarajevo")
|
||||
put("BB", "America/Barbados")
|
||||
put("BD", "Asia/Dhaka")
|
||||
put("BE", "Europe/Brussels")
|
||||
put("BEAT", BEATS_KEYWORD)
|
||||
put("BF", "Africa/Ouagadougou")
|
||||
put("BG", "Europe/Sofia")
|
||||
put("BH", "Asia/Bahrain")
|
||||
put("BI", "Africa/Bujumbura")
|
||||
put("BJ", "Africa/Porto-Novo")
|
||||
put("BL", "America/St_Barthelemy")
|
||||
put("BM", "Atlantic/Bermuda")
|
||||
put("BMT", BEATS_KEYWORD)
|
||||
put("BN", "Asia/Brunei")
|
||||
put("BO", "America/La_Paz")
|
||||
put("BQ", "America/Kralendijk")
|
||||
put("BR", "America/Sao_Paulo")
|
||||
put("BS", "America/Nassau")
|
||||
put("BT", "Asia/Thimphu")
|
||||
put("BW", "Africa/Gaborone")
|
||||
put("BY", "Europe/Minsk")
|
||||
put("BZ", "America/Belize")
|
||||
put("CA", "America/Montreal")
|
||||
put("CC", "Indian/Cocos")
|
||||
put("CD", "Africa/Kinshasa")
|
||||
put("CDT", "America/Chicago")
|
||||
put("CET", "CET")
|
||||
put("CF", "Africa/Bangui")
|
||||
put("CG", "Africa/Brazzaville")
|
||||
put("CH", "Europe/Zurich")
|
||||
put("CI", "Africa/Abidjan")
|
||||
put("CK", "Pacific/Rarotonga")
|
||||
put("CL", "America/Santiago")
|
||||
put("CM", "Africa/Douala")
|
||||
put("CN", "Asia/Shanghai")
|
||||
put("CO", "America/Bogota")
|
||||
put("CR", "America/Costa_Rica")
|
||||
put("CST", "America/Chicago")
|
||||
put("CU", "Cuba")
|
||||
put("CV", "Atlantic/Cape_Verde")
|
||||
put("CW", "America/Curacao")
|
||||
put("CX", "Indian/Christmas")
|
||||
put("CY", "Asia/Nicosia")
|
||||
put("CZ", "Europe/Prague")
|
||||
put("DE", "Europe/Berlin")
|
||||
put("DJ", "Africa/Djibouti")
|
||||
put("DK", "Europe/Copenhagen")
|
||||
put("DM", "America/Dominica")
|
||||
put("DO", "America/Santo_Domingo")
|
||||
put("DZ", "Africa/Algiers")
|
||||
put("EC", "Pacific/Galapagos")
|
||||
put("EDT", "America/New_York")
|
||||
put("EE", "Europe/Tallinn")
|
||||
put("EG", "Africa/Cairo")
|
||||
put("EH", "Africa/El_Aaiun")
|
||||
put("ER", "Africa/Asmara")
|
||||
put("ES", "Europe/Madrid")
|
||||
put("EST", "America/New_York")
|
||||
put("ET", "Africa/Addis_Ababa")
|
||||
put("FI", "Europe/Helsinki")
|
||||
put("FJ", "Pacific/Fiji")
|
||||
put("FK", "Atlantic/Stanley")
|
||||
put("FM", "Pacific/Yap")
|
||||
put("FO", "Atlantic/Faroe")
|
||||
put("FR", "Europe/Paris")
|
||||
put("GA", "Africa/Libreville")
|
||||
put("GB", "Europe/London")
|
||||
put("GD", "America/Grenada")
|
||||
put("GE", "Asia/Tbilisi")
|
||||
put("GF", "America/Cayenne")
|
||||
put("GG", "Europe/Guernsey")
|
||||
put("GH", "Africa/Accra")
|
||||
put("GI", "Europe/Gibraltar")
|
||||
put("GL", "America/Thule")
|
||||
put("GM", "Africa/Banjul")
|
||||
put("GMT", "GMT")
|
||||
put("GN", "Africa/Conakry")
|
||||
put("GP", "America/Guadeloupe")
|
||||
put("GQ", "Africa/Malabo")
|
||||
put("GR", "Europe/Athens")
|
||||
put("GS", "Atlantic/South_Georgia")
|
||||
put("GT", "America/Guatemala")
|
||||
put("GU", "Pacific/Guam")
|
||||
put("GW", "Africa/Bissau")
|
||||
put("GY", "America/Guyana")
|
||||
put("HK", "Asia/Hong_Kong")
|
||||
put("HN", "America/Tegucigalpa")
|
||||
put("HR", "Europe/Zagreb")
|
||||
put("HST", "Pacific/Honolulu")
|
||||
put("HT", "America/Port-au-Prince")
|
||||
put("HU", "Europe/Budapest")
|
||||
put("ID", "Asia/Jakarta")
|
||||
put("IE", "Europe/Dublin")
|
||||
put("IL", "Asia/Tel_Aviv")
|
||||
put("IM", "Europe/Isle_of_Man")
|
||||
put("IN", "Asia/Kolkata")
|
||||
put("IO", "Indian/Chagos")
|
||||
put("IQ", "Asia/Baghdad")
|
||||
put("IR", "Asia/Tehran")
|
||||
put("IS", "Atlantic/Reykjavik")
|
||||
put("IT", "Europe/Rome")
|
||||
put("JE", "Europe/Jersey")
|
||||
put("JM", "Jamaica")
|
||||
put("JO", "Asia/Amman")
|
||||
put("JP", "Asia/Tokyo")
|
||||
put("KE", "Africa/Nairobi")
|
||||
put("KG", "Asia/Bishkek")
|
||||
put("KH", "Asia/Phnom_Penh")
|
||||
put("KI", "Pacific/Tarawa")
|
||||
put("KM", "Indian/Comoro")
|
||||
put("KN", "America/St_Kitts")
|
||||
put("KP", "Asia/Pyongyang")
|
||||
put("KR", "Asia/Seoul")
|
||||
put("KW", "Asia/Riyadh")
|
||||
put("KY", "America/Cayman")
|
||||
put("KZ", "Asia/Oral")
|
||||
put("LA", "Asia/Vientiane")
|
||||
put("LB", "Asia/Beirut")
|
||||
put("LC", "America/St_Lucia")
|
||||
put("LI", "Europe/Vaduz")
|
||||
put("LK", "Asia/Colombo")
|
||||
put("LR", "Africa/Monrovia")
|
||||
put("LS", "Africa/Maseru")
|
||||
put("LT", "Europe/Vilnius")
|
||||
put("LU", "Europe/Luxembourg")
|
||||
put("LV", "Europe/Riga")
|
||||
put("LY", "Africa/Tripoli")
|
||||
put("MA", "Africa/Casablanca")
|
||||
put("MC", "Europe/Monaco")
|
||||
put("MD", "Europe/Chisinau")
|
||||
put("MDT", "America/Denver")
|
||||
put("ME", "Europe/Podgorica")
|
||||
put("MF", "America/Marigot")
|
||||
put("MG", "Indian/Antananarivo")
|
||||
put("MH", "Pacific/Majuro")
|
||||
put("MK", "Europe/Skopje")
|
||||
put("ML", "Africa/Timbuktu")
|
||||
put("MM", "Asia/Yangon")
|
||||
put("MN", "Asia/Ulaanbaatar")
|
||||
put("MO", "Asia/Macau")
|
||||
put("MP", "Pacific/Saipan")
|
||||
put("MQ", "America/Martinique")
|
||||
put("MR", "Africa/Nouakchott")
|
||||
put("MS", "America/Montserrat")
|
||||
put("MST", "America/Denver")
|
||||
put("MT", "Europe/Malta")
|
||||
put("MU", "Indian/Mauritius")
|
||||
put("MV", "Indian/Maldives")
|
||||
put("MW", "Africa/Blantyre")
|
||||
put("MX", "America/Mexico_City")
|
||||
put("MY", "Asia/Kuala_Lumpur")
|
||||
put("MZ", "Africa/Maputo")
|
||||
put("NA", "Africa/Windhoek")
|
||||
put("NC", "Pacific/Noumea")
|
||||
put("NE", "Africa/Niamey")
|
||||
put("NF", "Pacific/Norfolk")
|
||||
put("NG", "Africa/Lagos")
|
||||
put("NI", "America/Managua")
|
||||
put("NL", "Europe/Amsterdam")
|
||||
put("NO", "Europe/Oslo")
|
||||
put("NP", "Asia/Kathmandu")
|
||||
put("NR", "Pacific/Nauru")
|
||||
put("NU", "Pacific/Niue")
|
||||
put("NZ", "Pacific/Auckland")
|
||||
put("OM", "Asia/Muscat")
|
||||
put("PA", "America/Panama")
|
||||
put("PDT", "America/Los_Angeles")
|
||||
put("PE", "America/Lima")
|
||||
put("PF", "Pacific/Tahiti")
|
||||
put("PG", "Pacific/Port_Moresby")
|
||||
put("PH", "Asia/Manila")
|
||||
put("PK", "Asia/Karachi")
|
||||
put("PL", "Europe/Warsaw")
|
||||
put("PM", "America/Miquelon")
|
||||
put("PN", "Pacific/Pitcairn")
|
||||
put("PR", "America/Puerto_Rico")
|
||||
put("PS", "Asia/Gaza")
|
||||
put("PST", "America/Los_Angeles")
|
||||
put("PT", "Europe/Lisbon")
|
||||
put("PW", "Pacific/Palau")
|
||||
put("PY", "America/Asuncion")
|
||||
put("QA", "Asia/Qatar")
|
||||
put("RE", "Indian/Reunion")
|
||||
put("RO", "Europe/Bucharest")
|
||||
put("RS", "Europe/Belgrade")
|
||||
put("RU", "Europe/Moscow")
|
||||
put("RW", "Africa/Kigali")
|
||||
put("SA", "Asia/Riyadh")
|
||||
put("SB", "Pacific/Guadalcanal")
|
||||
put("SC", "Indian/Mahe")
|
||||
put("SD", "Africa/Khartoum")
|
||||
put("SE", "Europe/Stockholm")
|
||||
put("SG", "Asia/Singapore")
|
||||
put("SH", "Atlantic/St_Helena")
|
||||
put("SI", "Europe/Ljubljana")
|
||||
put("SJ", "Atlantic/Jan_Mayen")
|
||||
put("SK", "Europe/Bratislava")
|
||||
put("SL", "Africa/Freetown")
|
||||
put("SM", "Europe/San_Marino")
|
||||
put("SN", "Africa/Dakar")
|
||||
put("SO", "Africa/Mogadishu")
|
||||
put("SR", "America/Paramaribo")
|
||||
put("SS", "Africa/Juba")
|
||||
put("ST", "Africa/Sao_Tome")
|
||||
put("SV", "America/El_Salvador")
|
||||
put("SX", "America/Lower_Princes")
|
||||
put("SY", "Asia/Damascus")
|
||||
put("SZ", "Africa/Mbabane")
|
||||
put("TC", "America/Grand_Turk")
|
||||
put("TD", "Africa/Ndjamena")
|
||||
put("TF", "Indian/Kerguelen")
|
||||
put("TG", "Africa/Lome")
|
||||
put("TH", "Asia/Bangkok")
|
||||
put("TJ", "Asia/Dushanbe")
|
||||
put("TK", "Pacific/Fakaofo")
|
||||
put("TL", "Asia/Dili")
|
||||
put("TM", "Asia/Ashgabat")
|
||||
put("TN", "Africa/Tunis")
|
||||
put("TO", "Pacific/Tongatapu")
|
||||
put("TR", "Europe/Istanbul")
|
||||
put("TT", "America/Port_of_Spain")
|
||||
put("TV", "Pacific/Funafuti")
|
||||
put("TW", "Asia/Taipei")
|
||||
put("TZ", "Africa/Dar_es_Salaam")
|
||||
put("UA", "Europe/Kiev")
|
||||
put("UG", "Africa/Kampala")
|
||||
put("UK", "Europe/London")
|
||||
put("UM", "Pacific/Wake")
|
||||
put("US", "America/New_York")
|
||||
put("UTC", "UTC")
|
||||
put("UY", "America/Montevideo")
|
||||
put("UZ", "Asia/Tashkent")
|
||||
put("VA", "Europe/Vatican")
|
||||
put("VC", "America/St_Vincent")
|
||||
put("VE", "America/Caracas")
|
||||
put("VG", "America/Tortola")
|
||||
put("VI", "America/St_Thomas")
|
||||
put("VN", "Asia/Ho_Chi_Minh")
|
||||
put("VU", "Pacific/Efate")
|
||||
put("WF", "Pacific/Wallis")
|
||||
put("WS", "Pacific/Apia")
|
||||
put("YE", "Asia/Aden")
|
||||
put("YT", "Indian/Mayotte")
|
||||
put("ZA", "Africa/Johannesburg")
|
||||
put("ZM", "Africa/Lusaka")
|
||||
put("ZULU", "Zulu")
|
||||
put("ZW", "Africa/Harare")
|
||||
ZoneId.getAvailableZoneIds().filter { it.length <= 3 && !containsKey(it) }
|
||||
.forEach { tz -> put(tz, tz) }
|
||||
}
|
||||
|
||||
// The Time command
|
||||
private const val TIME_CMD = "time"
|
||||
|
||||
// The zones arguments
|
||||
private const val ZONES_ARGS = "zones"
|
||||
|
||||
// The default zone
|
||||
private const val DEFAULT_ZONE = "PST"
|
||||
|
||||
// Date/Time Format
|
||||
private var dtf =
|
||||
DateTimeFormatter.ofPattern("'The time is ${"'HH:mm'".bold()} on ${"'EEEE, d MMMM yyyy'".bold()} in '")
|
||||
|
||||
/**
|
||||
* Returns the current Internet (beat) Time.
|
||||
*/
|
||||
private fun internetTime(): String {
|
||||
val zdt = ZonedDateTime.now(ZoneId.of("UTC+01:00"))
|
||||
val beats = ((zdt[ChronoField.SECOND_OF_MINUTE] + zdt[ChronoField.MINUTE_OF_HOUR] * 60
|
||||
+ zdt[ChronoField.HOUR_OF_DAY] * 3600) / 86.4).toInt()
|
||||
return "%c%03d".format('@', beats)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time for the given timezone/city.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun time(query: String = DEFAULT_ZONE): String {
|
||||
val tz = COUNTRIES_MAP[(if (query.isNotBlank()) query.trim().uppercase() else DEFAULT_ZONE)]
|
||||
return if (tz != null) {
|
||||
if (BEATS_KEYWORD == tz) {
|
||||
"The current Internet Time is ${internetTime().bold()} $BEATS_KEYWORD"
|
||||
} else {
|
||||
(ZonedDateTime.now().withZoneSameInstant(ZoneId.of(tz)).format(dtf)
|
||||
+ tz.substring(tz.lastIndexOf('/') + 1).replace('_', ' ').bold())
|
||||
}
|
||||
} else {
|
||||
"Unsupported country/zone. Please try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) {
|
||||
if (args.equals(ZONES_ARGS, true)) {
|
||||
event.sendMessage("The supported countries/zones are: ")
|
||||
event.sendList(COUNTRIES_MAP.keys.sorted().map { it.padEnd(4) }, 14, isIndent = true)
|
||||
} else {
|
||||
event.respond(time(args))
|
||||
}
|
||||
}
|
||||
|
||||
override val isPrivateMsgEnabled = true
|
||||
|
||||
init {
|
||||
with(help) {
|
||||
add("To display a country's current date/time:")
|
||||
add(helpFormat("%c $TIME_CMD [<country code or zone>]"))
|
||||
add("For a listing of the supported countries/zones:")
|
||||
add(helpFormat("%c $TIME_CMD $ZONES_ARGS"))
|
||||
}
|
||||
commands.add(TIME_CMD)
|
||||
}
|
||||
}
|
37
bin/main/net/thauvin/erik/mobibot/msg/ErrorMessage.kt
Normal file
37
bin/main/net/thauvin/erik/mobibot/msg/ErrorMessage.kt
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* ErrorMessage.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.msg
|
||||
|
||||
/**
|
||||
* The `ErrorMessage` class.
|
||||
*/
|
||||
class ErrorMessage @JvmOverloads constructor(msg: String, color: String = DEFAULT_COLOR) :
|
||||
Message(msg, color, isError = true)
|
65
bin/main/net/thauvin/erik/mobibot/msg/Message.kt
Normal file
65
bin/main/net/thauvin/erik/mobibot/msg/Message.kt
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Message.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.msg
|
||||
|
||||
import net.thauvin.erik.semver.Constants
|
||||
|
||||
/**
|
||||
* The `Message` class.
|
||||
*/
|
||||
open class Message @JvmOverloads constructor(
|
||||
var msg: String,
|
||||
var color: String = DEFAULT_COLOR,
|
||||
var isNotice: Boolean = false,
|
||||
isError: Boolean = false,
|
||||
var isPrivate: Boolean = false
|
||||
) {
|
||||
companion object {
|
||||
var DEFAULT_COLOR = Constants.EMPTY
|
||||
}
|
||||
|
||||
init {
|
||||
if (isError) {
|
||||
isNotice = true
|
||||
}
|
||||
}
|
||||
|
||||
/** Error flag. */
|
||||
var isError = isError
|
||||
set(value) {
|
||||
if (value) isNotice = true
|
||||
field = value
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Message(color='$color', isError=$isError, isNotice=$isNotice, isPrivate=$isPrivate, msg='$msg')"
|
||||
}
|
||||
}
|
38
bin/main/net/thauvin/erik/mobibot/msg/NoticeMessage.kt
Normal file
38
bin/main/net/thauvin/erik/mobibot/msg/NoticeMessage.kt
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* NoticeMessage.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.msg
|
||||
|
||||
/**
|
||||
* The `NoticeMessage` class.
|
||||
*/
|
||||
class NoticeMessage @JvmOverloads constructor(msg: String, color: String = DEFAULT_COLOR) :
|
||||
Message(msg, color, isNotice = true)
|
||||
|
37
bin/main/net/thauvin/erik/mobibot/msg/PrivateMessage.kt
Normal file
37
bin/main/net/thauvin/erik/mobibot/msg/PrivateMessage.kt
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* PrivateMessage.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.msg
|
||||
|
||||
/**
|
||||
* The `PrivateMessage` class.
|
||||
*/
|
||||
class PrivateMessage @JvmOverloads constructor(msg: String, color: String = DEFAULT_COLOR) :
|
||||
Message(msg, color, isPrivate = true)
|
36
bin/main/net/thauvin/erik/mobibot/msg/PublicMessage.kt
Normal file
36
bin/main/net/thauvin/erik/mobibot/msg/PublicMessage.kt
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* PublicMessage.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.thauvin.erik.mobibot.msg
|
||||
|
||||
/**
|
||||
* The `PublicMessage` class.
|
||||
*/
|
||||
class PublicMessage @JvmOverloads constructor(msg: String, color: String = DEFAULT_COLOR) : Message(msg, color)
|
116
bin/main/net/thauvin/erik/mobibot/social/SocialManager.kt
Normal file
116
bin/main/net/thauvin/erik/mobibot/social/SocialManager.kt
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* SocialManager.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.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.*
|
||||
|
||||
/**
|
||||
* 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()
|
||||
entries.forEach {
|
||||
postEntry(it)
|
||||
}
|
||||
}
|
||||
}
|
96
bin/main/net/thauvin/erik/mobibot/social/SocialModule.kt
Normal file
96
bin/main/net/thauvin/erik/mobibot/social/SocialModule.kt
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* SocialModule.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.social
|
||||
|
||||
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.AbstractModule
|
||||
import net.thauvin.erik.mobibot.modules.ModuleException
|
||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
abstract class SocialModule : AbstractModule() {
|
||||
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()) {
|
||||
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) {
|
||||
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 commandResponse(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
40
bin/main/net/thauvin/erik/mobibot/social/SocialTimer.kt
Normal file
40
bin/main/net/thauvin/erik/mobibot/social/SocialTimer.kt
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* SocialTimer.kt
|
||||
*
|
||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package net.thauvin.erik.mobibot.social
|
||||
|
||||
import java.util.*
|
||||
|
||||
class SocialTimer(private var socialManager: SocialManager, private var index: Int) : TimerTask() {
|
||||
override fun run() {
|
||||
socialManager.postEntry(index)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue