Ignored bin directory
This commit is contained in:
parent
f8de100dde
commit
208f3a82d9
100 changed files with 5 additions and 10574 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -54,8 +54,9 @@ atlassian-ide-plugin.xml
|
||||||
# Editor-based Rest Client
|
# Editor-based Rest Client
|
||||||
.idea/httpRequests
|
.idea/httpRequests
|
||||||
|
|
||||||
local.properties
|
bin
|
||||||
|
|
||||||
deploy
|
deploy
|
||||||
|
local.properties
|
||||||
logs
|
logs
|
||||||
mobibot.properties
|
mobibot.properties
|
||||||
|
out
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
~ log4j2.xml
|
|
||||||
~
|
|
||||||
~ 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<Configuration status="warn">
|
|
||||||
<Appenders>
|
|
||||||
<Console name="stderr" target="SYSTEM_ERR">
|
|
||||||
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
|
|
||||||
</Console>
|
|
||||||
<Console name="input" target="SYSTEM_OUT">
|
|
||||||
<PatternLayout pattern="%d{UNIX_MILLIS} %msg%n"/>
|
|
||||||
<Filters>
|
|
||||||
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
|
|
||||||
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
|
||||||
</Filters>
|
|
||||||
</Console>
|
|
||||||
<Console name="output" target="SYSTEM_OUT">
|
|
||||||
<PatternLayout pattern="%d{UNIX_MILLIS} >>>%msg%n"/>
|
|
||||||
<Filters>
|
|
||||||
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
|
|
||||||
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
|
||||||
</Filters>
|
|
||||||
</Console>
|
|
||||||
</Appenders>
|
|
||||||
<Loggers>
|
|
||||||
<Root level="warn" additivity="false">
|
|
||||||
<AppenderRef ref="stderr"/>
|
|
||||||
</Root>
|
|
||||||
<logger level="debug" name="org.pircbotx.InputParser" additivity="false">
|
|
||||||
<appender-ref ref="input"/>
|
|
||||||
<appender-ref ref="stderr" level="warn"/>
|
|
||||||
</logger>
|
|
||||||
<logger level="debug" name="org.pircbotx.output.OutputRaw" additivity="false">
|
|
||||||
<appender-ref ref="output"/>
|
|
||||||
<appender-ref ref="stderr" level="warn"/>
|
|
||||||
</logger>
|
|
||||||
<logger level="trace" name="net.thauvin.erik.mobibot" additivity="false">
|
|
||||||
<appender-ref ref="stderr"/>
|
|
||||||
</logger>
|
|
||||||
</Loggers>
|
|
||||||
</Configuration>
|
|
|
@ -1,190 +0,0 @@
|
||||||
/*
|
|
||||||
* 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
/*
|
|
||||||
* 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"
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,421 +0,0 @@
|
||||||
/*
|
|
||||||
* 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,113 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 )"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,439 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* 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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,147 +0,0 @@
|
||||||
/*
|
|
||||||
* 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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,124 +0,0 @@
|
||||||
/*
|
|
||||||
* 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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,151 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,207 +0,0 @@
|
||||||
/*
|
|
||||||
* 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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,164 +0,0 @@
|
||||||
/*
|
|
||||||
* 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]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
/*
|
|
||||||
* 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())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
/*
|
|
||||||
* 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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,306 +0,0 @@
|
||||||
/*
|
|
||||||
* 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
* 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,213 +0,0 @@
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,187 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
/*
|
|
||||||
* 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>"))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,176 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,159 +0,0 @@
|
||||||
/*
|
|
||||||
* 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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,222 +0,0 @@
|
||||||
/*
|
|
||||||
* 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])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
/*
|
|
||||||
* 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]"))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
* 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"))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,171 +0,0 @@
|
||||||
/*
|
|
||||||
* 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>"))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
* 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"))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
/*
|
|
||||||
* 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()}!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,236 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,250 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,390 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
|
@ -1,65 +0,0 @@
|
||||||
/*
|
|
||||||
* 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')"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
|
@ -1,116 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
~ current.xml
|
|
||||||
~
|
|
||||||
~ 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
|
|
||||||
<channel>
|
|
||||||
<title>#mobibot IRC Links</title>
|
|
||||||
<link>https://www.mobitopia.org/mobibot/logs</link>
|
|
||||||
<description>Links from irc.example.com on #mobibot</description>
|
|
||||||
<language>en</language>
|
|
||||||
<pubDate>Sun, 31 Oct 2021 21:45:11 GMT</pubDate>
|
|
||||||
<dc:date>2021-10-31T21:45:11Z</dc:date>
|
|
||||||
<dc:language>en</dc:language>
|
|
||||||
<item>
|
|
||||||
<title>Example 2</title>
|
|
||||||
<link>https://www.example.com/2</link>
|
|
||||||
<description>Posted by <b>Skynx</b> on <a href="irc://irc.libera.chat/#mobibot"><b>#mobibot</b></a></description>
|
|
||||||
<category>tag2-1</category>
|
|
||||||
<category>tag2-2</category>
|
|
||||||
<pubDate>Sun, 31 Oct 2021 21:45:11 GMT</pubDate>
|
|
||||||
<guid>https://www.foo.com</guid>
|
|
||||||
<dc:creator>mobibot@irc.libera.chat (Skynx)</dc:creator>
|
|
||||||
<dc:date>2021-10-31T00:01:00Z</dc:date>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>Example 1</title>
|
|
||||||
<link>https://www.example.com/1</link>
|
|
||||||
<description>Posted by <b>ErikT</b> on <a href="irc://irc.libera.chat/#mobibot"><b>#mobibot</b></a>
|
|
||||||
<br/><br/>ErikT: This is comment 1. <br/>Skynx: This is comment 2.
|
|
||||||
</description>
|
|
||||||
<category>tag1-1</category>
|
|
||||||
<category>tag1-2</category>
|
|
||||||
<pubDate>Sun, 31 Oct 2021 21:43:15 GMT</pubDate>
|
|
||||||
<guid>https://www.example.com/</guid>
|
|
||||||
<dc:creator>mobibot@irc.libera.chat (ErikT)</dc:creator>
|
|
||||||
<dc:date>2021-10-31T00:00:00Z</dc:date>
|
|
||||||
</item>
|
|
||||||
</channel>
|
|
||||||
</rss>
|
|
|
@ -1,86 +0,0 @@
|
||||||
/*
|
|
||||||
* AddonsTest.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 assertk.assertThat
|
|
||||||
import assertk.assertions.containsExactly
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import assertk.assertions.size
|
|
||||||
import net.thauvin.erik.mobibot.commands.ChannelFeed
|
|
||||||
import net.thauvin.erik.mobibot.commands.Cycle
|
|
||||||
import net.thauvin.erik.mobibot.commands.Die
|
|
||||||
import net.thauvin.erik.mobibot.commands.Ignore
|
|
||||||
import net.thauvin.erik.mobibot.commands.links.Comment
|
|
||||||
import net.thauvin.erik.mobibot.commands.links.View
|
|
||||||
import net.thauvin.erik.mobibot.modules.*
|
|
||||||
import kotlin.test.Test
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class AddonsTest {
|
|
||||||
private val p = Properties().apply {
|
|
||||||
put("disabled-modules", "war,dice Lookup")
|
|
||||||
put("disabled-commands", "View | comment")
|
|
||||||
}
|
|
||||||
private val addons = Addons(p)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun addTest() {
|
|
||||||
// Modules
|
|
||||||
addons.add(Joke())
|
|
||||||
addons.add(RockPaperScissors())
|
|
||||||
addons.add(War())
|
|
||||||
addons.add(Dice())
|
|
||||||
addons.add(Lookup())
|
|
||||||
assertThat(addons::modules).size().isEqualTo(2)
|
|
||||||
assertThat(addons.names.modules, "names.modules").containsExactly("Joke", "RockPaperScissors")
|
|
||||||
|
|
||||||
// Commands
|
|
||||||
addons.add(View())
|
|
||||||
addons.add(Comment())
|
|
||||||
addons.add(Cycle())
|
|
||||||
addons.add(Die()) // invisible
|
|
||||||
addons.add(ChannelFeed("channel")) // no properties, disabled
|
|
||||||
p[Ignore.IGNORE_PROP] = "nick"
|
|
||||||
addons.add(Ignore())
|
|
||||||
assertThat(addons::commands).size().isEqualTo(3)
|
|
||||||
|
|
||||||
assertThat(addons.names.ops, "names.ops").containsExactly("cycle")
|
|
||||||
|
|
||||||
assertThat(addons.names.commands, "names.command").containsExactly(
|
|
||||||
"joke",
|
|
||||||
"rock",
|
|
||||||
"paper",
|
|
||||||
"scissors",
|
|
||||||
"ignore"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
* ExceptionSanitizer.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.obfuscate
|
|
||||||
import net.thauvin.erik.mobibot.Utils.replaceEach
|
|
||||||
import net.thauvin.erik.mobibot.modules.ModuleException
|
|
||||||
|
|
||||||
object ExceptionSanitizer {
|
|
||||||
/**
|
|
||||||
* Returns a sanitized exception to avoid displaying api keys, etc. in CI logs.
|
|
||||||
*/
|
|
||||||
fun ModuleException.sanitize(vararg sanitize: String): ModuleException {
|
|
||||||
val search = sanitize.filter { it.isNotBlank() }.toTypedArray()
|
|
||||||
if (search.isNotEmpty()) {
|
|
||||||
val obfuscate = search.map { it.obfuscate() }.toTypedArray()
|
|
||||||
with(this) {
|
|
||||||
if (!cause?.message.isNullOrBlank()) {
|
|
||||||
return ModuleException(
|
|
||||||
debugMessage,
|
|
||||||
cause?.javaClass?.name + ": " + cause?.message?.replaceEach(search, obfuscate),
|
|
||||||
this
|
|
||||||
)
|
|
||||||
} else if (!message.isNullOrBlank()) {
|
|
||||||
return ModuleException(debugMessage, message?.replaceEach(search, obfuscate), this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
* FeedReaderTest.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 assertk.all
|
|
||||||
import assertk.assertFailure
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.*
|
|
||||||
import com.rometools.rome.io.FeedException
|
|
||||||
import net.thauvin.erik.mobibot.FeedReader.Companion.readFeed
|
|
||||||
import net.thauvin.erik.mobibot.msg.Message
|
|
||||||
import kotlin.test.Test
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.MalformedURLException
|
|
||||||
import java.net.UnknownHostException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `FeedReader Test` class.
|
|
||||||
*/
|
|
||||||
class FeedReaderTest {
|
|
||||||
@Test
|
|
||||||
fun readFeedTest() {
|
|
||||||
var messages = readFeed("https://feeds.thauvin.net/ethauvin")
|
|
||||||
assertThat(messages, "messages").all {
|
|
||||||
size().isEqualTo(10)
|
|
||||||
index(1).prop(Message::msg).contains("erik.thauvin.net")
|
|
||||||
}
|
|
||||||
|
|
||||||
messages = readFeed("https://lorem-rss.herokuapp.com/feed?length=0")
|
|
||||||
assertThat(messages, "messages").index(0).prop(Message::msg).contains("nothing")
|
|
||||||
|
|
||||||
messages = readFeed("https://lorem-rss.herokuapp.com/feed?length=84", 42)
|
|
||||||
assertThat(messages, "messages").size().isEqualTo(84)
|
|
||||||
messages.forEachIndexed { i, m ->
|
|
||||||
if (i % 2 == 0) {
|
|
||||||
assertThat(m, "messages($i)").prop(Message::msg).startsWith("Lorem ipsum")
|
|
||||||
} else {
|
|
||||||
assertThat(m, "messages($i)").prop(Message::msg).contains("http://example.com/test/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertFailure { readFeed("blah") }.isInstanceOf(MalformedURLException::class.java)
|
|
||||||
|
|
||||||
assertFailure { readFeed("https://www.example.com") }.isInstanceOf(FeedException::class.java)
|
|
||||||
|
|
||||||
assertFailure { readFeed("https://www.thauvin.net/foo") }.isInstanceOf(IOException::class.java)
|
|
||||||
|
|
||||||
assertFailure { readFeed("https://www.examplesfoo.com/") }.isInstanceOf(UnknownHostException::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
/*
|
|
||||||
* LocalProperties.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 org.testng.annotations.BeforeSuite
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.InetAddress
|
|
||||||
import java.net.UnknownHostException
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Access to `local.properties`.
|
|
||||||
*/
|
|
||||||
open class LocalProperties {
|
|
||||||
@BeforeSuite(alwaysRun = true)
|
|
||||||
fun loadProperties() {
|
|
||||||
val localPath = Paths.get("local.properties")
|
|
||||||
if (Files.exists(localPath)) {
|
|
||||||
try {
|
|
||||||
Files.newInputStream(localPath).use { stream -> localProps.load(stream) }
|
|
||||||
} catch (ignore: IOException) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val localProps = Properties()
|
|
||||||
|
|
||||||
fun getHostName(): String {
|
|
||||||
val ciName = System.getenv("CI_NAME")
|
|
||||||
return ciName ?: try {
|
|
||||||
InetAddress.getLocalHost().hostName
|
|
||||||
} catch (ignore: UnknownHostException) {
|
|
||||||
"Unknown Host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getProperty(key: String): String {
|
|
||||||
return if (localProps.containsKey(key)) {
|
|
||||||
localProps.getProperty(key)
|
|
||||||
} else {
|
|
||||||
val env = System.getenv(keyToEnv(key))
|
|
||||||
env?.let {
|
|
||||||
localProps.setProperty(key, env)
|
|
||||||
}
|
|
||||||
env
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun keyToEnv(key: String): String {
|
|
||||||
return key.replace('-', '_').uppercase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
/*
|
|
||||||
* PinboardTest.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.encodeUrl
|
|
||||||
import net.thauvin.erik.mobibot.Utils.reader
|
|
||||||
import net.thauvin.erik.mobibot.entries.EntryLink
|
|
||||||
import org.testng.Assert.assertFalse
|
|
||||||
import org.testng.Assert.assertTrue
|
|
||||||
import kotlin.test.Test
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
class PinboardTest : LocalProperties() {
|
|
||||||
private val pinboard = Pinboard()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testPinboard() {
|
|
||||||
val apiToken = getProperty("pinboard-api-token")
|
|
||||||
val url = "https://www.example.com/${(1000..5000).random()}"
|
|
||||||
val ircServer = "irc.test.com"
|
|
||||||
val entry = EntryLink(url, "Test Example", "ErikT", "", "#mobitopia", listOf("test"))
|
|
||||||
|
|
||||||
pinboard.setApiToken(apiToken)
|
|
||||||
|
|
||||||
pinboard.addPin(ircServer, entry)
|
|
||||||
assertTrue(validatePin(apiToken, url = entry.link, entry.title, entry.nick, entry.channel), "addPin")
|
|
||||||
|
|
||||||
entry.link = "https://www.example.com/${(5001..9999).random()}"
|
|
||||||
pinboard.updatePin(ircServer, url, entry)
|
|
||||||
assertTrue(validatePin(apiToken, url = entry.link, ircServer), "updatePin")
|
|
||||||
|
|
||||||
entry.title = "Foo Title"
|
|
||||||
pinboard.updatePin(ircServer, entry.link, entry)
|
|
||||||
assertTrue(validatePin(apiToken, url = entry.link, entry.title), "updatePin(${entry.title}")
|
|
||||||
|
|
||||||
pinboard.deletePin(entry)
|
|
||||||
assertFalse(validatePin(apiToken, url = entry.link), "deletePin")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validatePin(apiToken: String, url: String, vararg matches: String): Boolean {
|
|
||||||
val response =
|
|
||||||
URL("https://api.pinboard.in/v1/posts/get?auth_token=${apiToken}&tag=test&" + url.encodeUrl()).reader().body
|
|
||||||
|
|
||||||
matches.forEach {
|
|
||||||
if (!response.contains(it)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.contains(url)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,278 +0,0 @@
|
||||||
/*
|
|
||||||
* UtilsTest.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 assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import assertk.assertions.length
|
|
||||||
import net.thauvin.erik.mobibot.Utils.appendIfMissing
|
|
||||||
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.colorize
|
|
||||||
import net.thauvin.erik.mobibot.Utils.cyan
|
|
||||||
import net.thauvin.erik.mobibot.Utils.encodeUrl
|
|
||||||
import net.thauvin.erik.mobibot.Utils.getIntProperty
|
|
||||||
import net.thauvin.erik.mobibot.Utils.green
|
|
||||||
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.obfuscate
|
|
||||||
import net.thauvin.erik.mobibot.Utils.plural
|
|
||||||
import net.thauvin.erik.mobibot.Utils.reader
|
|
||||||
import net.thauvin.erik.mobibot.Utils.red
|
|
||||||
import net.thauvin.erik.mobibot.Utils.replaceEach
|
|
||||||
import net.thauvin.erik.mobibot.Utils.reverseColor
|
|
||||||
import net.thauvin.erik.mobibot.Utils.toIntOrDefault
|
|
||||||
import net.thauvin.erik.mobibot.Utils.toIsoLocalDate
|
|
||||||
import net.thauvin.erik.mobibot.Utils.toUtcDateTime
|
|
||||||
import net.thauvin.erik.mobibot.Utils.today
|
|
||||||
import net.thauvin.erik.mobibot.Utils.underline
|
|
||||||
import net.thauvin.erik.mobibot.Utils.unescapeXml
|
|
||||||
import net.thauvin.erik.mobibot.msg.Message.Companion.DEFAULT_COLOR
|
|
||||||
import org.pircbotx.Colors
|
|
||||||
import org.testng.annotations.BeforeClass
|
|
||||||
import kotlin.test.Test
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.URL
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `Utils Test` class.
|
|
||||||
*/
|
|
||||||
class UtilsTest {
|
|
||||||
private val ascii =
|
|
||||||
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
|
|
||||||
private val cal = Calendar.getInstance()
|
|
||||||
private val localDateTime = LocalDateTime.of(1952, 2, 17, 12, 30, 0)
|
|
||||||
private val test = "This is a test."
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
fun setUp() {
|
|
||||||
cal[1952, Calendar.FEBRUARY, 17, 12, 30] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testAppendIfMissing() {
|
|
||||||
val dir = "dir"
|
|
||||||
val sep = '/'
|
|
||||||
val url = "https://erik.thauvin.net"
|
|
||||||
assertThat(dir.appendIfMissing(File.separatorChar), "appendIfMissing(dir)")
|
|
||||||
.isEqualTo(dir + File.separatorChar)
|
|
||||||
assertThat(url.appendIfMissing(sep), "appendIfMissing(url)").isEqualTo("$url$sep")
|
|
||||||
assertThat("$url$sep".appendIfMissing(sep), "appendIfMissing($url$sep)").isEqualTo("$url$sep")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testBold() {
|
|
||||||
assertThat(1.bold(), "bold(1)").isEqualTo(Colors.BOLD + "1" + Colors.BOLD)
|
|
||||||
assertThat(2L.bold(), "bold(2L)").isEqualTo(Colors.BOLD + "2" + Colors.BOLD)
|
|
||||||
assertThat(ascii.bold(), "ascii.bold()").isEqualTo(Colors.BOLD + ascii + Colors.BOLD)
|
|
||||||
assertThat("test".bold(), "test.bold()").isEqualTo(Colors.BOLD + "test" + Colors.BOLD)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCapitalise() {
|
|
||||||
assertThat("test".capitalise(), "capitalize(test)").isEqualTo("Test")
|
|
||||||
assertThat("Test".capitalise(), "capitalize(Test)").isEqualTo("Test")
|
|
||||||
assertThat(test.capitalise(), "capitalize($test)").isEqualTo(test)
|
|
||||||
assertThat("".capitalise(), "capitalize()").isEqualTo("")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun textCapitaliseWords() {
|
|
||||||
assertThat(test.capitalizeWords(), "captiatlizeWords(test)").isEqualTo("This Is A Test.")
|
|
||||||
assertThat("Already Capitalized".capitalizeWords(), "already capitalized")
|
|
||||||
.isEqualTo("Already Capitalized")
|
|
||||||
assertThat(" a test ".capitalizeWords(), "with spaces").isEqualTo(" A Test ")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testColorize() {
|
|
||||||
assertThat(ascii.colorize(Colors.REVERSE), "reverse.colorize()").isEqualTo(
|
|
||||||
Colors.REVERSE + ascii + Colors.REVERSE
|
|
||||||
)
|
|
||||||
assertThat(ascii.colorize(Colors.RED), "red.colorize()")
|
|
||||||
.isEqualTo(Colors.RED + ascii + Colors.NORMAL)
|
|
||||||
assertThat(ascii.colorize(Colors.BOLD), "colorized(bold)")
|
|
||||||
.isEqualTo(Colors.BOLD + ascii + Colors.BOLD)
|
|
||||||
assertThat(null.colorize(Colors.RED), "null.colorize()").isEqualTo("")
|
|
||||||
assertThat("".colorize(Colors.RED), "colorize()").isEqualTo("")
|
|
||||||
assertThat(ascii.colorize(DEFAULT_COLOR), "ascii.colorize()").isEqualTo(ascii)
|
|
||||||
assertThat(" ".colorize(Colors.NORMAL), "blank.colorize()")
|
|
||||||
.isEqualTo(Colors.NORMAL + " " + Colors.NORMAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testCyan() {
|
|
||||||
assertThat(ascii.cyan()).isEqualTo(Colors.CYAN + ascii + Colors.NORMAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testEncodeUrl() {
|
|
||||||
assertThat("Hello Günter".encodeUrl()).isEqualTo("Hello%20G%C3%BCnter")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGetIntProperty() {
|
|
||||||
val p = Properties()
|
|
||||||
p["one"] = "1"
|
|
||||||
p["two"] = "two"
|
|
||||||
assertThat(p.getIntProperty("one", 9), "getIntProperty(one)").isEqualTo(1)
|
|
||||||
assertThat(p.getIntProperty("two", 2), "getIntProperty(two)").isEqualTo(2)
|
|
||||||
assertThat(p.getIntProperty("foo", 3), "getIntProperty(foo)").isEqualTo(3)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGreen() {
|
|
||||||
assertThat(ascii.green()).isEqualTo(Colors.DARK_GREEN + ascii + Colors.NORMAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testHelpCmdSyntax() {
|
|
||||||
val bot = "mobibot"
|
|
||||||
assertThat(helpCmdSyntax("%c $test %n $test", bot, false), "helpCmdSyntax(private)")
|
|
||||||
.isEqualTo("$bot: $test $bot $test")
|
|
||||||
assertThat(helpCmdSyntax("%c %n $test %c $test %n", bot, true), "helpCmdSyntax(public)")
|
|
||||||
.isEqualTo("/msg $bot $bot $test /msg $bot $test $bot")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testHelpFormat() {
|
|
||||||
assertThat(helpFormat(test, isBold = true, isIndent = false), "helpFormat(bold)")
|
|
||||||
.isEqualTo("${Colors.BOLD}$test${Colors.BOLD}")
|
|
||||||
assertThat(helpFormat(test, isBold = false, isIndent = true), "helpFormat(indent)")
|
|
||||||
.isEqualTo(test.prependIndent())
|
|
||||||
assertThat(helpFormat(test, isBold = true, isIndent = true), "helpFormat(bold,indent)")
|
|
||||||
.isEqualTo(test.colorize(Colors.BOLD).prependIndent())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIsoLocalDate() {
|
|
||||||
assertThat(cal.time.toIsoLocalDate(), "isoLocalDate(date)").isEqualTo("1952-02-17")
|
|
||||||
assertThat(localDateTime.toIsoLocalDate(), "isoLocalDate(localDate)").isEqualTo("1952-02-17")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLastOrEmpty() {
|
|
||||||
val two = listOf("1", "2")
|
|
||||||
assertThat(two.lastOrEmpty(), "lastOrEmpty(1,2)").isEqualTo("2")
|
|
||||||
val one = listOf("1")
|
|
||||||
assertThat(one.lastOrEmpty(), "lastOrEmpty(1)").isEqualTo("")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testObfuscate() {
|
|
||||||
assertThat(ascii.obfuscate(), "obfuscate()").all {
|
|
||||||
length().isEqualTo(ascii.length)
|
|
||||||
isEqualTo(("x".repeat(ascii.length)))
|
|
||||||
}
|
|
||||||
assertThat(" ".obfuscate(), "obfuscate(blank)").isEqualTo(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testPlural() {
|
|
||||||
val week = "week"
|
|
||||||
val weeks = "weeks"
|
|
||||||
|
|
||||||
for (i in -1..3) {
|
|
||||||
assertThat(week.plural(i.toLong()), "plural($i)").isEqualTo(if (i > 1) weeks else week)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testReplaceEach() {
|
|
||||||
val search = arrayOf("one", "two", "three")
|
|
||||||
val replace = arrayOf("1", "2", "3")
|
|
||||||
assertThat(search.joinToString(",").replaceEach(search, replace), "replaceEach(1,2,3")
|
|
||||||
.isEqualTo(replace.joinToString(","))
|
|
||||||
|
|
||||||
assertThat(test.replaceEach(search, replace), "replaceEach(nothing)").isEqualTo(test)
|
|
||||||
|
|
||||||
assertThat(test.replaceEach(arrayOf("t", "e"), arrayOf("", "E")), "replaceEach($test)")
|
|
||||||
.isEqualTo(test.replace("t", "").replace("e", "E"))
|
|
||||||
|
|
||||||
assertThat(test.replaceEach(search, emptyArray()), "replaceEach(search, empty)")
|
|
||||||
.isEqualTo(test)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testRed() {
|
|
||||||
assertThat(ascii.red()).isEqualTo(ascii.colorize(Colors.RED))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testReverseColor() {
|
|
||||||
assertThat(ascii.reverseColor()).isEqualTo(Colors.REVERSE + ascii + Colors.REVERSE)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testToday() {
|
|
||||||
assertThat(today()).isEqualTo(LocalDateTime.now().toIsoLocalDate())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testToIntOrDefault() {
|
|
||||||
assertThat("10".toIntOrDefault(1), "toIntOrDefault(10, 1)").isEqualTo(10)
|
|
||||||
assertThat("a".toIntOrDefault(2), "toIntOrDefault(a, 2)").isEqualTo(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testUnderline() {
|
|
||||||
assertThat(ascii.underline()).isEqualTo(ascii.colorize(Colors.UNDERLINE))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testUnescapeXml() {
|
|
||||||
assertThat("<a name="test & ''">".unescapeXml()).isEqualTo(
|
|
||||||
"<a name=\"test & ''\">"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun testUrlReader() {
|
|
||||||
val reader = URL("https://postman-echo.com/status/200").reader()
|
|
||||||
assertThat(reader.body).isEqualTo("{\n \"status\": 200\n}")
|
|
||||||
assertThat(reader.responseCode).isEqualTo(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testUtcDateTime() {
|
|
||||||
assertThat(cal.time.toUtcDateTime(), "utcDateTime(date)").isEqualTo("1952-02-17 12:30")
|
|
||||||
assertThat(localDateTime.toUtcDateTime(), "utcDateTime(localDate)").isEqualTo("1952-02-17 12:30")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
/*
|
|
||||||
* InfoTest.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 assertk.assertThat
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import net.thauvin.erik.mobibot.commands.Info.Companion.toUptime
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class InfoTest {
|
|
||||||
@Test
|
|
||||||
fun testToUptime() {
|
|
||||||
assertThat(
|
|
||||||
547800300076L.toUptime(),
|
|
||||||
"upTime(full)"
|
|
||||||
).isEqualTo("17 years 4 months 2 weeks 1 day 6 hours 45 minutes")
|
|
||||||
assertThat(24300000L.toUptime(), "upTime(hours minutes)").isEqualTo("6 hours 45 minutes")
|
|
||||||
assertThat(110700000L.toUptime(), "upTime(days hours minutes)").isEqualTo("1 day 6 hours 45 minutes")
|
|
||||||
assertThat(
|
|
||||||
1320300000L.toUptime(),
|
|
||||||
"upTime(weeks days hours minutes)"
|
|
||||||
).isEqualTo("2 weeks 1 day 6 hours 45 minutes")
|
|
||||||
assertThat(2700000L.toUptime(), "upTime(45 minutes)").isEqualTo("45 minutes")
|
|
||||||
assertThat(60000L.toUptime(), "upTime(1 minute)").isEqualTo("1 minute")
|
|
||||||
assertThat(59000L.toUptime(), "upTime(59 seconds)").isEqualTo("59 seconds")
|
|
||||||
assertThat(0L.toUptime(), "upTime(0 second)").isEqualTo("0 second")
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
* RecapTest.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 assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import assertk.assertions.matches
|
|
||||||
import assertk.assertions.prop
|
|
||||||
import assertk.assertions.size
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class RecapTest {
|
|
||||||
@Test
|
|
||||||
fun storeRecapTest() {
|
|
||||||
for (i in 1..20) {
|
|
||||||
Recap.storeRecap("sender$i", "test $i", false)
|
|
||||||
}
|
|
||||||
assertThat(Recap.recaps, "Recap.recaps").all {
|
|
||||||
size().isEqualTo(Recap.MAX_RECAPS)
|
|
||||||
prop(MutableList<String>::first)
|
|
||||||
.matches("[1-2]\\d{3}-[01]\\d-[0-3]\\d [0-2]\\d:[0-6]\\d - sender11: test 11".toRegex())
|
|
||||||
prop(MutableList<String>::last)
|
|
||||||
.matches("[1-2]\\d{3}-[01]\\d-[0-3]\\d [0-2]\\d:[0-6]\\d - sender20: test 20".toRegex())
|
|
||||||
}
|
|
||||||
|
|
||||||
Recap.storeRecap("sender", "test action", true)
|
|
||||||
assertThat(Recap.recaps.last())
|
|
||||||
.matches("[1-2]\\d{3}-[01]\\d-[0-3]\\d [0-2]\\d:[0-6]\\d - sender test action".toRegex())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
* LinksManagerTest.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 assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.contains
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import assertk.assertions.isTrue
|
|
||||||
import assertk.assertions.size
|
|
||||||
import net.thauvin.erik.mobibot.Constants
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class LinksManagerTest {
|
|
||||||
private val linksManager = LinksManager()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun fetchTitle() {
|
|
||||||
assertThat(linksManager.fetchTitle("https://erik.thauvin.net/"), "fetchTitle(Erik)").contains("Erik's Weblog")
|
|
||||||
assertThat(
|
|
||||||
linksManager.fetchTitle("https://www.google.com/foo"),
|
|
||||||
"fetchTitle(Foo)"
|
|
||||||
).isEqualTo(Constants.NO_TITLE)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testMatches() {
|
|
||||||
assertThat(linksManager.matches("https://www.example.com/"), "matches(url)").isTrue()
|
|
||||||
assertThat(linksManager.matches("HTTP://erik.thauvin.net/blog/ Erik's Weblog"), "matches(HTTP)").isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun matchTagKeywordsTest() {
|
|
||||||
linksManager.setProperty(LinksManager.KEYWORDS_PROP, "key1 key2,key3")
|
|
||||||
val tags = mutableListOf<String>()
|
|
||||||
|
|
||||||
linksManager.matchTagKeywords("Test title with key2", tags)
|
|
||||||
assertThat(tags, "tags").contains("key2")
|
|
||||||
tags.clear()
|
|
||||||
|
|
||||||
linksManager.matchTagKeywords("Test key3 title with key1", tags)
|
|
||||||
assertThat(tags, "tags(key1, key3)").all {
|
|
||||||
contains("key1")
|
|
||||||
contains("key3")
|
|
||||||
size().isEqualTo(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
/*
|
|
||||||
* ViewTest.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 assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import assertk.assertions.prop
|
|
||||||
import net.thauvin.erik.mobibot.entries.EntryLink
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class ViewTest {
|
|
||||||
@Test
|
|
||||||
fun testParseArgs() {
|
|
||||||
val view = View()
|
|
||||||
|
|
||||||
for (i in 1..10) {
|
|
||||||
LinksManager.entries.links.add(
|
|
||||||
EntryLink(
|
|
||||||
"https://www.example.com/$i",
|
|
||||||
"Example $i",
|
|
||||||
"nick$i",
|
|
||||||
"login$i",
|
|
||||||
"#channel",
|
|
||||||
emptyList()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(view.parseArgs("1"), "parseArgs(1)").all {
|
|
||||||
prop(Pair<Int, String>::first).isEqualTo(0)
|
|
||||||
prop(Pair<Int, String>::second).isEqualTo("")
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(view.parseArgs("2 foo"), "parseArgs(2, foo)").all {
|
|
||||||
prop(Pair<Int, String>::first).isEqualTo(1)
|
|
||||||
prop(Pair<Int, String>::second).isEqualTo("foo")
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(view.parseArgs("3 FOO"), "parseArgs(3, FOO)").all {
|
|
||||||
prop(Pair<Int, String>::first).isEqualTo(2)
|
|
||||||
prop(Pair<Int, String>::second).isEqualTo("foo")
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(view.parseArgs(" 4 foo bar "), "parseArgs( 4 foo bar )").all {
|
|
||||||
prop(Pair<Int, String>::first).isEqualTo(3)
|
|
||||||
prop(Pair<Int, String>::second).isEqualTo("foo bar")
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(view.parseArgs("foo bar"), "parseArgs(foo bar)").all {
|
|
||||||
prop(Pair<Int, String>::first).isEqualTo(0)
|
|
||||||
prop(Pair<Int, String>::second).isEqualTo("foo bar")
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(view.parseArgs("${Int.MAX_VALUE}1"), "parseArgs(overflow)").all {
|
|
||||||
prop(Pair<Int, String>::first).isEqualTo(0)
|
|
||||||
prop(Pair<Int, String>::second).isEqualTo("${Int.MAX_VALUE}1")
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(view.parseArgs("1a"), "parseArgs(1a)").all {
|
|
||||||
prop(Pair<Int, String>::first).isEqualTo(0)
|
|
||||||
prop(Pair<Int, String>::second).isEqualTo("1a")
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(view.parseArgs("20"), "parseArgs(20)").all {
|
|
||||||
prop(Pair<Int, String>::first).isEqualTo(0)
|
|
||||||
prop(Pair<Int, String>::second).isEqualTo("")
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(view.parseArgs(""), "parseArgs()").all {
|
|
||||||
prop(Pair<Int, String>::first).isEqualTo(LinksManager.entries.links.size - View.MAX_ENTRIES)
|
|
||||||
prop(Pair<Int, String>::second).isEqualTo("")
|
|
||||||
}
|
|
||||||
|
|
||||||
LinksManager.entries.links.clear()
|
|
||||||
|
|
||||||
assertThat(view.parseArgs("4"), "parseArgs(4)").all {
|
|
||||||
prop(Pair<Int, String>::first).isEqualTo(0)
|
|
||||||
prop(Pair<Int, String>::second).isEqualTo("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
/*
|
|
||||||
* SeenTest.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 assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.*
|
|
||||||
import org.testng.annotations.AfterClass
|
|
||||||
import org.testng.annotations.BeforeClass
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.io.path.deleteIfExists
|
|
||||||
import kotlin.io.path.fileSize
|
|
||||||
|
|
||||||
class SeenTest {
|
|
||||||
private val tmpFile = kotlin.io.path.createTempFile(suffix = ".ser")
|
|
||||||
private val seen = Seen(tmpFile.toAbsolutePath().toString())
|
|
||||||
private val nick = "ErikT"
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
fun saveTest() {
|
|
||||||
seen.add("ErikT")
|
|
||||||
assertThat(tmpFile.fileSize(), "tmpFile.size").isGreaterThan(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass(alwaysRun = true)
|
|
||||||
fun afterClass() {
|
|
||||||
tmpFile.deleteIfExists()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(priority = 1, groups = ["commands"])
|
|
||||||
fun loadTest() {
|
|
||||||
seen.clear()
|
|
||||||
assertThat(seen::seenNicks).isEmpty()
|
|
||||||
seen.load()
|
|
||||||
assertThat(seen::seenNicks).key(nick).isNotNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun addTest() {
|
|
||||||
val last = seen.seenNicks[nick]?.lastSeen
|
|
||||||
seen.add(nick.lowercase())
|
|
||||||
assertThat(seen).all {
|
|
||||||
prop(Seen::seenNicks).size().isEqualTo(1)
|
|
||||||
prop(Seen::seenNicks).key(nick).isNotNull().prop(SeenNick::lastSeen).isNotEqualTo(last)
|
|
||||||
prop(Seen::seenNicks).key(nick).isNotNull().prop(SeenNick::nick).isNotNull().isEqualTo(nick.lowercase())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(priority = 10, groups = ["commands"])
|
|
||||||
fun clearTest() {
|
|
||||||
seen.clear()
|
|
||||||
seen.save()
|
|
||||||
seen.load()
|
|
||||||
assertThat(seen::seenNicks).size().isEqualTo(0)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
/*
|
|
||||||
* TellMessageTest.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 assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import assertk.assertions.isFalse
|
|
||||||
import assertk.assertions.isTrue
|
|
||||||
import assertk.assertions.prop
|
|
||||||
import kotlin.test.Test
|
|
||||||
import java.time.Duration
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.temporal.Temporal
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `TellMessageTest` class.
|
|
||||||
*/
|
|
||||||
class TellMessageTest {
|
|
||||||
private fun isValidDate(date: Temporal): Boolean {
|
|
||||||
return Duration.between(date, LocalDateTime.now()).toMinutes() < 1
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testTellMessage() {
|
|
||||||
val message = "Test message."
|
|
||||||
val recipient = "recipient"
|
|
||||||
val sender = "sender"
|
|
||||||
val tellMessage = TellMessage(sender, recipient, message)
|
|
||||||
assertThat(tellMessage).all {
|
|
||||||
prop(TellMessage::sender).isEqualTo(sender)
|
|
||||||
prop(TellMessage::recipient).isEqualTo(recipient)
|
|
||||||
prop(TellMessage::message).isEqualTo(message)
|
|
||||||
}
|
|
||||||
assertThat(isValidDate(tellMessage.queued), "isValidDate()").isTrue()
|
|
||||||
assertThat(tellMessage.isMatch(sender), "isMatch(sender)").isTrue()
|
|
||||||
assertThat(tellMessage.isMatch(recipient), "isMatch(recipient)").isTrue()
|
|
||||||
assertThat(tellMessage.isMatch("foo"), "isMatch(foo)").isFalse()
|
|
||||||
tellMessage.isReceived = false
|
|
||||||
assertThat(tellMessage.receptionDate, "receptionDate").isEqualTo(LocalDateTime.MIN)
|
|
||||||
tellMessage.isReceived = true
|
|
||||||
assertThat(isValidDate(tellMessage.receptionDate), "isValidDate(creationDate)").isTrue()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
/*
|
|
||||||
* TellMessagesMgrTest.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 assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.*
|
|
||||||
import org.testng.annotations.AfterClass
|
|
||||||
import org.testng.annotations.BeforeClass
|
|
||||||
import kotlin.test.Test
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import kotlin.io.path.createTempFile
|
|
||||||
import kotlin.io.path.deleteIfExists
|
|
||||||
import kotlin.io.path.fileSize
|
|
||||||
|
|
||||||
class TellMessagesMgrTest {
|
|
||||||
private val testFile = createTempFile(suffix = ".ser")
|
|
||||||
private val maxDays = 10L
|
|
||||||
private val testMessages = mutableListOf<TellMessage>().apply {
|
|
||||||
for (i in 0..5) {
|
|
||||||
this.add(i, TellMessage("sender$i", "recipient$i", "message $i"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
fun saveTest() {
|
|
||||||
TellManager.save(testFile.toAbsolutePath().toString(), testMessages)
|
|
||||||
assertThat(testFile.fileSize()).isGreaterThan(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
fun afterClass() {
|
|
||||||
testFile.deleteIfExists()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun cleanTest() {
|
|
||||||
testMessages.add(TellMessage("sender", "recipient", "message").apply {
|
|
||||||
queued = LocalDateTime.now().minusDays(maxDays)
|
|
||||||
})
|
|
||||||
val size = testMessages.size
|
|
||||||
assertThat(TellManager.clean(testMessages, maxDays + 2), "clean(maxDays=${maxDays + 2})").isFalse()
|
|
||||||
assertThat(TellManager.clean(testMessages, maxDays), "clean(maxDays=$maxDays)").isTrue()
|
|
||||||
assertThat(testMessages, "testMessages").size().isEqualTo(size - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun loadTest() {
|
|
||||||
val messages = TellManager.load(testFile.toAbsolutePath().toString())
|
|
||||||
for (i in messages.indices) {
|
|
||||||
assertThat(messages).index(i).all {
|
|
||||||
prop(TellMessage::sender).isEqualTo(testMessages[i].sender)
|
|
||||||
prop(TellMessage::recipient).isEqualTo(testMessages[i].recipient)
|
|
||||||
prop(TellMessage::message).isEqualTo(testMessages[i].message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
/*
|
|
||||||
* EntriesUtilsTest.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 assertk.assertThat
|
|
||||||
import assertk.assertions.contains
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import net.thauvin.erik.mobibot.Constants
|
|
||||||
import net.thauvin.erik.mobibot.entries.EntriesUtils.printComment
|
|
||||||
import net.thauvin.erik.mobibot.entries.EntriesUtils.printLink
|
|
||||||
import net.thauvin.erik.mobibot.entries.EntriesUtils.printTags
|
|
||||||
import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class EntriesUtilsTest {
|
|
||||||
private val comment = EntryComment("comment", "nick")
|
|
||||||
private val links = buildList {
|
|
||||||
for (i in 0..5) {
|
|
||||||
add(
|
|
||||||
EntryLink(
|
|
||||||
"https://www.mobitopia.org/$i",
|
|
||||||
"Mobitopia$i",
|
|
||||||
"Skynx$i",
|
|
||||||
"JimH$i",
|
|
||||||
"#mobitopia$i",
|
|
||||||
listOf("tag1", "tag2", "tag3", "TAG4", "Tag5")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun printCommentTest() {
|
|
||||||
assertThat(printComment(0, 0, comment)).isEqualTo("${Constants.LINK_CMD}1.1: [nick] comment")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun printLinkTest() {
|
|
||||||
for (i in links.indices) {
|
|
||||||
assertThat(
|
|
||||||
printLink(i - 1, links[i]), "link $i"
|
|
||||||
).isEqualTo("L$i: [Skynx$i] \u0002Mobitopia$i\u0002 ( \u000303https://www.mobitopia.org/$i\u000F )")
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(links.first().addComment(comment), "addComment()").isEqualTo(0)
|
|
||||||
assertThat(printLink(0, links.first(), isView = true), "printLink(isView=true)").contains("[+1]")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun printTagsTest() {
|
|
||||||
for (i in links.indices) {
|
|
||||||
assertThat(
|
|
||||||
printTags(i - 1, links[i]), "tag $i"
|
|
||||||
).isEqualTo("L${i}T: tag1, tag2, tag3, tag4, tag5")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun toLinkLabelTest() {
|
|
||||||
assertThat(1.toLinkLabel()).isEqualTo("${Constants.LINK_CMD}2")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
/*
|
|
||||||
* EntryLinkTest.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 assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.*
|
|
||||||
import com.rometools.rome.feed.synd.SyndCategory
|
|
||||||
import com.rometools.rome.feed.synd.SyndCategoryImpl
|
|
||||||
import kotlin.test.Test
|
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `EntryUtilsTest` class.
|
|
||||||
*
|
|
||||||
* @author [Erik C. Thauvin](https://erik.thauvin.net/)
|
|
||||||
* @created 2019-04-19
|
|
||||||
* @since 1.0
|
|
||||||
*/
|
|
||||||
class EntryLinkTest {
|
|
||||||
private val entryLink = EntryLink(
|
|
||||||
"https://www.mobitopia.org/", "Mobitopia", "Skynx", "JimH", "#mobitopia",
|
|
||||||
listOf("tag1", "tag2", "tag3", "TAG4", "Tag5")
|
|
||||||
)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testAddDeleteComment() {
|
|
||||||
var i = 0
|
|
||||||
while (i < 5) {
|
|
||||||
entryLink.addComment("c$i", "u$i")
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
assertThat(entryLink.comments, "comments").size().isEqualTo(i)
|
|
||||||
i = 0
|
|
||||||
for (comment in entryLink.comments) {
|
|
||||||
assertThat(comment).all {
|
|
||||||
prop(EntryComment::comment).isEqualTo("c$i")
|
|
||||||
prop(EntryComment::nick).isEqualTo("u$i")
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
val r = SecureRandom()
|
|
||||||
while (entryLink.comments.size > 0) {
|
|
||||||
entryLink.deleteComment(r.nextInt(entryLink.comments.size))
|
|
||||||
}
|
|
||||||
assertThat(entryLink.comments, "hasComments()").isEmpty()
|
|
||||||
entryLink.addComment("nothing", "nobody")
|
|
||||||
entryLink.setComment(0, "something", "somebody")
|
|
||||||
val comment = entryLink.getComment(0)
|
|
||||||
assertThat(comment, "comment[first]").all {
|
|
||||||
prop(EntryComment::nick).isEqualTo("somebody")
|
|
||||||
prop(EntryComment::comment).isEqualTo("something")
|
|
||||||
}
|
|
||||||
assertThat(entryLink.deleteComment(comment), "deleteComment").isTrue()
|
|
||||||
assertThat(entryLink.deleteComment(comment), "comment is already deleted").isFalse()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testConstructor() {
|
|
||||||
val tags = listOf(SyndCategoryImpl().apply { name = "tag1" }, SyndCategoryImpl().apply { name = "tag2" })
|
|
||||||
val link = EntryLink("link", "title", "nick", "channel", Date(), tags)
|
|
||||||
assertThat(link, "link").all {
|
|
||||||
prop(EntryLink::tags).size().isEqualTo(tags.size)
|
|
||||||
prop(EntryLink::tags).index(0).prop(SyndCategory::getName).isEqualTo("tag1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testMatches() {
|
|
||||||
assertThat(entryLink.matches("mobitopia"), "matches(mobitopia)").isTrue()
|
|
||||||
assertThat(entryLink.matches("skynx"), "match(nick)").isTrue()
|
|
||||||
assertThat(entryLink.matches("www.mobitopia.org"), "matches(url)").isTrue()
|
|
||||||
assertThat(entryLink.matches("foo"), "matches(foo)").isFalse()
|
|
||||||
assertThat(entryLink.matches("<empty>"), "matches(empty)").isFalse()
|
|
||||||
assertThat(entryLink.matches(null), "matches(null)").isFalse()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testTags() {
|
|
||||||
val tags: List<SyndCategory> = entryLink.tags
|
|
||||||
for ((i, tag) in tags.withIndex()) {
|
|
||||||
assertThat(tag.name, "tag.name($i)").isEqualTo("tag${i + 1}")
|
|
||||||
}
|
|
||||||
assertThat(entryLink::tags).size().isEqualTo(5)
|
|
||||||
entryLink.setTags("-tag5, tag4")
|
|
||||||
entryLink.setTags("+mobitopia")
|
|
||||||
entryLink.setTags("-mobitopia")
|
|
||||||
assertThat(
|
|
||||||
entryLink.formatTags(","),
|
|
||||||
"formatTags(',')"
|
|
||||||
).isEqualTo("tag1,tag2,tag3,tag4,mobitopia")
|
|
||||||
entryLink.setTags("-tag4 tag5")
|
|
||||||
assertThat(
|
|
||||||
entryLink.formatTags(" ", ","), "formatTag(' ',',')"
|
|
||||||
).isEqualTo(",tag1 tag2 tag3 mobitopia tag5")
|
|
||||||
val size = entryLink.tags.size
|
|
||||||
entryLink.setTags("")
|
|
||||||
assertThat(entryLink.tags, "setTags('')").size().isEqualTo(size)
|
|
||||||
entryLink.setTags(" ")
|
|
||||||
assertThat(entryLink.tags, "setTags(' ')").size().isEqualTo(size)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
/*
|
|
||||||
* FeedMgrTest.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 assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.*
|
|
||||||
import net.thauvin.erik.mobibot.Utils.today
|
|
||||||
import org.testng.annotations.BeforeSuite
|
|
||||||
import kotlin.test.Test
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.io.path.deleteIfExists
|
|
||||||
import kotlin.io.path.fileSize
|
|
||||||
import kotlin.io.path.name
|
|
||||||
|
|
||||||
class FeedMgrTest {
|
|
||||||
private val entries = Entries()
|
|
||||||
private val channel = "mobibot"
|
|
||||||
|
|
||||||
@BeforeSuite(alwaysRun = true)
|
|
||||||
fun beforeSuite() {
|
|
||||||
entries.logsDir = "src/test/resources/"
|
|
||||||
entries.ircServer = "irc.example.com"
|
|
||||||
entries.channel = channel
|
|
||||||
entries.backlogs = "https://www.mobitopia.org/mobibot/logs"
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testFeedMgr() {
|
|
||||||
// Load the feed
|
|
||||||
assertThat(FeedsManager.loadFeed(entries), "loadFeed()").isEqualTo("2021-10-31")
|
|
||||||
|
|
||||||
assertThat(entries.links, "entries.links").size().isEqualTo(2)
|
|
||||||
entries.links.forEachIndexed { i, entryLink ->
|
|
||||||
assertThat(entryLink, "entryLink[${i + 1}]").all {
|
|
||||||
prop(EntryLink::title).isEqualTo("Example ${i + 1}")
|
|
||||||
prop(EntryLink::link).isEqualTo("https://www.example.com/${i + 1}")
|
|
||||||
prop(EntryLink::channel).isEqualTo(channel)
|
|
||||||
}
|
|
||||||
entryLink.tags.forEachIndexed { y, tag ->
|
|
||||||
assertThat(tag.name, "tag${i + 1}-${y + 1}").isEqualTo("tag${i + 1}-${y + 1}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
with(entries.links.first()) {
|
|
||||||
assertThat(nick, "nick[first]").isEqualTo("ErikT")
|
|
||||||
assertThat(date, "date[first]").isEqualTo(Date(1635638400000L))
|
|
||||||
assertThat(comments.first(), "comments[first]").all {
|
|
||||||
prop(EntryComment::comment).endsWith("comment 1.")
|
|
||||||
prop(EntryComment::nick).isEqualTo("ErikT")
|
|
||||||
}
|
|
||||||
assertThat(comments.last(), "comments[last]").all {
|
|
||||||
prop(EntryComment::comment).endsWith("comment 2.")
|
|
||||||
prop(EntryComment::nick).isEqualTo("Skynx")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(entries.links, "links").index(1).all {
|
|
||||||
prop(EntryLink::nick).isEqualTo("Skynx")
|
|
||||||
prop(EntryLink::date).isEqualTo(Date(1635638460000L))
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentFile = Paths.get("${entries.logsDir}test.xml")
|
|
||||||
val backlogFile = Paths.get("${entries.logsDir}${today()}.xml")
|
|
||||||
|
|
||||||
// Save the feed
|
|
||||||
FeedsManager.saveFeed(entries, currentFile.name)
|
|
||||||
|
|
||||||
assertThat(currentFile, "currentFile").exists()
|
|
||||||
assertThat(backlogFile, "backlogFile").exists()
|
|
||||||
|
|
||||||
assertThat(currentFile.fileSize(), "currentFile == backlogFile").isEqualTo(backlogFile.fileSize())
|
|
||||||
|
|
||||||
// Load the test feed
|
|
||||||
entries.links.clear()
|
|
||||||
FeedsManager.loadFeed(entries, currentFile.name)
|
|
||||||
|
|
||||||
entries.links.forEachIndexed { i, entryLink ->
|
|
||||||
assertThat(entryLink.title, "entryLink.title[${i + 1}]").isEqualTo("Example ${i + 1}")
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(currentFile.deleteIfExists(), "currentFile.deleteIfExists()").isTrue()
|
|
||||||
assertThat(backlogFile.deleteIfExists(), "backlogFile.deleteIfExists()").isTrue()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
/*
|
|
||||||
* CalcTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.assertFailure
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import assertk.assertions.isInstanceOf
|
|
||||||
import net.objecthunter.exp4j.tokenizer.UnknownFunctionOrVariableException
|
|
||||||
import net.thauvin.erik.mobibot.Utils.bold
|
|
||||||
import net.thauvin.erik.mobibot.modules.Calc.Companion.calculate
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `CalcTest` class.
|
|
||||||
*/
|
|
||||||
class CalcTest {
|
|
||||||
@Test
|
|
||||||
fun testCalculate() {
|
|
||||||
assertThat(calculate("1 + 1"), "calculate(1+1)").isEqualTo("1+1 = ${2.bold()}")
|
|
||||||
assertThat(calculate("1 -3"), "calculate(1-3)").isEqualTo("1-3 = ${(-2).bold()}")
|
|
||||||
assertThat(calculate("pi+π+e+φ"), "calculate(pi+π+e+φ)").isEqualTo("pi+π+e+φ = ${"10.62".bold()}")
|
|
||||||
assertFailure { calculate("one + one") }.isInstanceOf(UnknownFunctionOrVariableException::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* ChatGptTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.assertFailure
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.contains
|
|
||||||
import assertk.assertions.hasNoCause
|
|
||||||
import assertk.assertions.isInstanceOf
|
|
||||||
import net.thauvin.erik.mobibot.LocalProperties
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class ChatGptTest : LocalProperties() {
|
|
||||||
@Test
|
|
||||||
fun testApiKey() {
|
|
||||||
assertFailure { ChatGpt.chat("1 gallon to liter", "", 0) }
|
|
||||||
.isInstanceOf(ModuleException::class.java)
|
|
||||||
.hasNoCause()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(groups = ["modules", "no-ci"])
|
|
||||||
fun testChat() {
|
|
||||||
val apiKey = getProperty(ChatGpt.API_KEY_PROP)
|
|
||||||
assertThat(
|
|
||||||
ChatGpt.chat("how do I make an HTTP request in Javascript?", apiKey, 100)
|
|
||||||
).contains("XMLHttpRequest")
|
|
||||||
assertThat(
|
|
||||||
ChatGpt.chat("how do I encode a URL in java?", apiKey, 60)
|
|
||||||
).contains("URLEncoder")
|
|
||||||
|
|
||||||
assertFailure { ChatGpt.chat("1 liter to gallon", apiKey, 0) }
|
|
||||||
.isInstanceOf(ModuleException::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
* CryptoPricesTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import assertk.assertions.isGreaterThan
|
|
||||||
import assertk.assertions.prop
|
|
||||||
import net.thauvin.erik.crypto.CryptoPrice
|
|
||||||
import net.thauvin.erik.mobibot.modules.CryptoPrices.Companion.currentPrice
|
|
||||||
import net.thauvin.erik.mobibot.modules.CryptoPrices.Companion.getCurrencyName
|
|
||||||
import net.thauvin.erik.mobibot.modules.CryptoPrices.Companion.loadCurrencies
|
|
||||||
import org.testng.annotations.BeforeClass
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `CryptoPricesTest` class.
|
|
||||||
*/
|
|
||||||
class CryptoPricesTest {
|
|
||||||
@BeforeClass
|
|
||||||
@Throws(ModuleException::class)
|
|
||||||
fun before() {
|
|
||||||
loadCurrencies()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(ModuleException::class)
|
|
||||||
fun testMarketPrice() {
|
|
||||||
var price = currentPrice(listOf("BTC"))
|
|
||||||
assertThat(price, "currentPrice(BTC)").all {
|
|
||||||
prop(CryptoPrice::base).isEqualTo("BTC")
|
|
||||||
prop(CryptoPrice::currency).isEqualTo("USD")
|
|
||||||
prop(CryptoPrice::amount).transform { it.signum() }.isGreaterThan(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
price = currentPrice(listOf("ETH", "EUR"))
|
|
||||||
assertThat(price, "currentPrice(ETH, EUR)").all {
|
|
||||||
prop(CryptoPrice::base).isEqualTo("ETH")
|
|
||||||
prop(CryptoPrice::currency).isEqualTo("EUR")
|
|
||||||
prop(CryptoPrice::amount).transform { it.signum() }.isGreaterThan(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGetCurrencyName() {
|
|
||||||
assertThat(getCurrencyName("USD"), "USD").isEqualTo("United States Dollar")
|
|
||||||
assertThat(getCurrencyName("EUR"), "EUR").isEqualTo("Euro")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
/*
|
|
||||||
* CurrencyConverterTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.contains
|
|
||||||
import assertk.assertions.isInstanceOf
|
|
||||||
import assertk.assertions.matches
|
|
||||||
import assertk.assertions.prop
|
|
||||||
import net.thauvin.erik.mobibot.LocalProperties
|
|
||||||
import net.thauvin.erik.mobibot.modules.CurrencyConverter.Companion.convertCurrency
|
|
||||||
import net.thauvin.erik.mobibot.modules.CurrencyConverter.Companion.loadSymbols
|
|
||||||
import net.thauvin.erik.mobibot.msg.ErrorMessage
|
|
||||||
import net.thauvin.erik.mobibot.msg.Message
|
|
||||||
import net.thauvin.erik.mobibot.msg.PublicMessage
|
|
||||||
import org.testng.annotations.BeforeClass
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `CurrencyConvertTest` class.
|
|
||||||
*/
|
|
||||||
class CurrencyConverterTest : LocalProperties() {
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
@Throws(ModuleException::class)
|
|
||||||
fun before() {
|
|
||||||
val apiKey = getProperty(CurrencyConverter.API_KEY_PROP)
|
|
||||||
loadSymbols(apiKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testConvertCurrency() {
|
|
||||||
val apiKey = getProperty(CurrencyConverter.API_KEY_PROP)
|
|
||||||
assertThat(
|
|
||||||
convertCurrency(apiKey, "100 USD to EUR").msg,
|
|
||||||
"convertCurrency(100 USD to EUR)"
|
|
||||||
).matches("100 United States Dollar = \\d{2,3}\\.\\d{2,3} Euro".toRegex())
|
|
||||||
assertThat(
|
|
||||||
convertCurrency(apiKey, "1 USD to GBP").msg,
|
|
||||||
"convertCurrency(1 USD to BGP)"
|
|
||||||
).matches("1 United States Dollar = 0\\.\\d{2,3} Pound Sterling".toRegex())
|
|
||||||
assertThat(
|
|
||||||
convertCurrency(apiKey, "100,000.00 CAD to USD").msg,
|
|
||||||
"convertCurrency(100,000.00 GBP to USD)"
|
|
||||||
).matches("100,000.00 Canadian Dollar = \\d+\\.\\d{2,3} United States Dollar".toRegex())
|
|
||||||
assertThat(convertCurrency(apiKey, "100 USD to USD"), "convertCurrency(100 USD to USD)").all {
|
|
||||||
prop(Message::msg).contains("You're kidding, right?")
|
|
||||||
isInstanceOf(PublicMessage::class.java)
|
|
||||||
}
|
|
||||||
assertThat(convertCurrency(apiKey, "100 USD"), "convertCurrency(100 USD)").all {
|
|
||||||
prop(Message::msg).contains("Invalid query.")
|
|
||||||
isInstanceOf(ErrorMessage::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
/*
|
|
||||||
* DiceTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import assertk.assertions.matches
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class DiceTest {
|
|
||||||
@Test
|
|
||||||
fun testRoll() {
|
|
||||||
assertThat(Dice.roll(1, 1), "roll(1d1)").isEqualTo("\u00021\u0002")
|
|
||||||
assertThat(Dice.roll(2, 1), "roll(2d1)")
|
|
||||||
.isEqualTo("\u00021\u0002 + \u00021\u0002 = \u00022\u0002")
|
|
||||||
assertThat(Dice.roll(5, 1), "roll(5d1)")
|
|
||||||
.isEqualTo("\u00021\u0002 + \u00021\u0002 + \u00021\u0002 + \u00021\u0002 + \u00021\u0002 = \u00025\u0002")
|
|
||||||
assertThat(Dice.roll(2, 6), "roll(2d6)")
|
|
||||||
.matches("\u0002[1-6]\u0002 \\+ \u0002[1-6]\u0002 = \u0002[1-9][0-2]?\u0002".toRegex())
|
|
||||||
assertThat(Dice.roll(3, 7), "roll(3d7)")
|
|
||||||
.matches("\u0002[1-7]\u0002 \\+ \u0002[1-7]\u0002 \\+ \u0002[1-7]\u0002 = \u0002\\d{1,2}\u0002".toRegex())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
/*
|
|
||||||
* GoogleSearchTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.all
|
|
||||||
import assertk.assertFailure
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.*
|
|
||||||
import net.thauvin.erik.mobibot.ExceptionSanitizer.sanitize
|
|
||||||
import net.thauvin.erik.mobibot.LocalProperties
|
|
||||||
import net.thauvin.erik.mobibot.modules.GoogleSearch.Companion.searchGoogle
|
|
||||||
import net.thauvin.erik.mobibot.msg.ErrorMessage
|
|
||||||
import net.thauvin.erik.mobibot.msg.Message
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `GoogleSearchTest` class.
|
|
||||||
*/
|
|
||||||
class GoogleSearchTest : LocalProperties() {
|
|
||||||
@Test
|
|
||||||
fun testAPIKeys() {
|
|
||||||
assertThat(
|
|
||||||
searchGoogle("", "apikey", "cssKey").first(),
|
|
||||||
"searchGoogle(empty)"
|
|
||||||
).isInstanceOf(ErrorMessage::class.java)
|
|
||||||
|
|
||||||
assertFailure { searchGoogle("test", "", "apiKey") }
|
|
||||||
.isInstanceOf(ModuleException::class.java).hasNoCause()
|
|
||||||
|
|
||||||
assertFailure { searchGoogle("test", "apiKey", "") }
|
|
||||||
.isInstanceOf(ModuleException::class.java).hasNoCause()
|
|
||||||
|
|
||||||
assertFailure { searchGoogle("test", "apiKey", "cssKey") }
|
|
||||||
.isInstanceOf(ModuleException::class.java)
|
|
||||||
.hasMessage("API key not valid. Please pass a valid API key.")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test\n@DisableOnCi
|
|
||||||
@Throws(ModuleException::class)
|
|
||||||
fun testSearchGoogle() {
|
|
||||||
val apiKey = getProperty(GoogleSearch.API_KEY_PROP)
|
|
||||||
val cseKey = getProperty(GoogleSearch.CSE_KEY_PROP)
|
|
||||||
|
|
||||||
try {
|
|
||||||
var query = "mobibot"
|
|
||||||
var messages = searchGoogle(query, apiKey, cseKey)
|
|
||||||
assertThat(messages, "searchGoogle($query)").all {
|
|
||||||
isNotEmpty()
|
|
||||||
index(0).prop(Message::msg).contains(query, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
query = "adadflkjl"
|
|
||||||
messages = searchGoogle(query, apiKey, cseKey)
|
|
||||||
assertThat(messages, "searchGoogle($query)").index(0).all {
|
|
||||||
isInstanceOf(ErrorMessage::class.java)
|
|
||||||
prop(Message::msg).isEqualTo("No results found.")
|
|
||||||
}
|
|
||||||
} catch (e: ModuleException) {
|
|
||||||
// Avoid displaying api keys in CI logs
|
|
||||||
if ("true" == System.getenv("CI")) {
|
|
||||||
throw e.sanitize(apiKey, cseKey)
|
|
||||||
} else {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
/*
|
|
||||||
* JokeTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.*
|
|
||||||
import net.thauvin.erik.mobibot.modules.Joke.Companion.randomJoke
|
|
||||||
import net.thauvin.erik.mobibot.msg.Message
|
|
||||||
import net.thauvin.erik.mobibot.msg.PublicMessage
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `JokeTest` class.
|
|
||||||
*/
|
|
||||||
class JokeTest {
|
|
||||||
@Test
|
|
||||||
@Throws(ModuleException::class)
|
|
||||||
fun testRandomJoke() {
|
|
||||||
val joke = randomJoke()
|
|
||||||
assertThat(joke, "randomJoke()").all {
|
|
||||||
size().isGreaterThan(0)
|
|
||||||
each {
|
|
||||||
it.isInstanceOf(PublicMessage::class.java)
|
|
||||||
it.prop(Message::msg).doesNotContain("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
* LookupTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.any
|
|
||||||
import assertk.assertions.contains
|
|
||||||
import net.thauvin.erik.mobibot.modules.Lookup.Companion.nslookup
|
|
||||||
import net.thauvin.erik.mobibot.modules.Lookup.Companion.whois
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `Lookup Test` class.
|
|
||||||
*/
|
|
||||||
class LookupTest {
|
|
||||||
@Test
|
|
||||||
@Throws(Exception::class)
|
|
||||||
fun testLookup() {
|
|
||||||
var result = nslookup("apple.com")
|
|
||||||
assertThat(result, "lookup(apple.com)").contains("17.253.144.10")
|
|
||||||
|
|
||||||
result = nslookup("204.122.16.136")
|
|
||||||
assertThat(result, "lookup(204.122.16.136)").contains("nix3.thauvin.us")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(Exception::class)
|
|
||||||
fun testWhois() {
|
|
||||||
val result = whois("17.178.96.59", Lookup.WHOIS_HOST)
|
|
||||||
assertThat(result, "whois(17.178.96.59").any { it.contains("Apple Inc.") }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* MastodonTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.contains
|
|
||||||
import net.thauvin.erik.mobibot.LocalProperties
|
|
||||||
import net.thauvin.erik.mobibot.modules.Mastodon.Companion.toot
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class MastodonTest : LocalProperties() {
|
|
||||||
@Test
|
|
||||||
@Throws(ModuleException::class)
|
|
||||||
fun testToot() {
|
|
||||||
val msg = "Testing Mastodon API from ${getHostName()}"
|
|
||||||
assertThat(
|
|
||||||
toot(
|
|
||||||
getProperty(Mastodon.ACCESS_TOKEN_PROP),
|
|
||||||
getProperty(Mastodon.INSTANCE_PROP),
|
|
||||||
getProperty(Mastodon.HANDLE_PROP),
|
|
||||||
msg,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
).contains(msg)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
* ModuleExceptionTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.*
|
|
||||||
import net.thauvin.erik.mobibot.ExceptionSanitizer.sanitize
|
|
||||||
import org.testng.annotations.DataProvider
|
|
||||||
import kotlin.test.Test
|
|
||||||
import java.io.IOException
|
|
||||||
import java.lang.reflect.Method
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `ModuleExceptionTest` class.
|
|
||||||
*/
|
|
||||||
class ModuleExceptionTest {
|
|
||||||
companion object {
|
|
||||||
const val DEBUG_MESSAGE = "debugMessage"
|
|
||||||
const val MESSAGE = "message"
|
|
||||||
}
|
|
||||||
|
|
||||||
@DataProvider(name = "dp")
|
|
||||||
fun createData(@Suppress("UNUSED_PARAMETER") m: Method?): Array<Array<Any>> {
|
|
||||||
return arrayOf(
|
|
||||||
arrayOf(ModuleException(DEBUG_MESSAGE, MESSAGE, IOException("URL http://foobar.com"))),
|
|
||||||
arrayOf(ModuleException(DEBUG_MESSAGE, MESSAGE, IOException("URL http://foobar.com?"))),
|
|
||||||
arrayOf(ModuleException(DEBUG_MESSAGE, MESSAGE))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(dataProvider = "dp")
|
|
||||||
fun testGetDebugMessage(e: ModuleException) {
|
|
||||||
assertThat(e::debugMessage).isEqualTo(DEBUG_MESSAGE)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(dataProvider = "dp")
|
|
||||||
fun testGetMessage(e: ModuleException) {
|
|
||||||
assertThat(e).hasMessage(MESSAGE)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSanitizeMessage() {
|
|
||||||
val apiKey = "1234567890"
|
|
||||||
var e = ModuleException(DEBUG_MESSAGE, MESSAGE, IOException("URL http://foo.com?apiKey=$apiKey&userID=me"))
|
|
||||||
assertThat(
|
|
||||||
e.sanitize(apiKey, "", "me").message, "ModuleException(debugMessage, message, IOException(url))"
|
|
||||||
).isNotNull().all {
|
|
||||||
contains("xxxxxxxxxx", "userID=xx", "java.io.IOException")
|
|
||||||
doesNotContain(apiKey, "me")
|
|
||||||
}
|
|
||||||
|
|
||||||
e = ModuleException(DEBUG_MESSAGE, MESSAGE, null)
|
|
||||||
assertThat(e.sanitize(apiKey), "ModuleException(debugMessage, message, null)").hasMessage(MESSAGE)
|
|
||||||
|
|
||||||
e = ModuleException(DEBUG_MESSAGE, MESSAGE, IOException())
|
|
||||||
assertThat(e.sanitize(apiKey), "ModuleException(debugMessage, message, IOException())").hasMessage(MESSAGE)
|
|
||||||
|
|
||||||
e = ModuleException(DEBUG_MESSAGE, apiKey)
|
|
||||||
assertThat(e.sanitize(apiKey).message, "ModuleException(debugMessage, apiKey)").isNotNull()
|
|
||||||
.doesNotContain(apiKey)
|
|
||||||
|
|
||||||
val msg: String? = null
|
|
||||||
e = ModuleException(DEBUG_MESSAGE, msg, IOException(msg))
|
|
||||||
assertThat(e.sanitize(apiKey).message, "ModuleException(debugMessage, msg, IOException(msg))").isNull()
|
|
||||||
|
|
||||||
e = ModuleException(DEBUG_MESSAGE, msg, IOException("foo is $apiKey"))
|
|
||||||
assertThat(
|
|
||||||
e.sanitize(" ", apiKey, "foo").message,
|
|
||||||
"ModuleException(debugMessage, msg, IOException(foo is $apiKey))"
|
|
||||||
).isNotNull().all {
|
|
||||||
doesNotContain(apiKey)
|
|
||||||
endsWith("xxx is xxxxxxxxxx")
|
|
||||||
}
|
|
||||||
assertThat(e.sanitize(), "exception should be unchanged").isEqualTo(e)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* PingTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.contains
|
|
||||||
import assertk.assertions.isNotEmpty
|
|
||||||
import net.thauvin.erik.mobibot.modules.Ping.Companion.randomPing
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `PingTest` class.
|
|
||||||
*/
|
|
||||||
class PingTest {
|
|
||||||
@Test
|
|
||||||
fun testPingsArray() {
|
|
||||||
assertThat(Ping.PINGS, "Ping.PINGS").isNotEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testRandomPing() {
|
|
||||||
for (i in 0..9) {
|
|
||||||
assertThat(Ping.PINGS, "Ping.PINGS[$i]").contains(randomPing())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
* RockPaperScissorsTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.isEqualTo
|
|
||||||
import net.thauvin.erik.mobibot.modules.RockPaperScissors.Companion.winLoseOrDraw
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class RockPaperScissorsTest {
|
|
||||||
@Test
|
|
||||||
fun testWinLoseOrDraw() {
|
|
||||||
assertThat(winLoseOrDraw("scissors", "paper"), "scissors vs. paper").isEqualTo("win")
|
|
||||||
assertThat(winLoseOrDraw("paper", "rock"), "paper vs. rock").isEqualTo("win")
|
|
||||||
assertThat(winLoseOrDraw("rock", "scissors"), "rock vs. scissors").isEqualTo("win")
|
|
||||||
assertThat(winLoseOrDraw("paper", "scissors"), "paper vs. scissors").isEqualTo("lose")
|
|
||||||
assertThat(winLoseOrDraw("rock", "paper"), "rock vs. paper").isEqualTo("lose")
|
|
||||||
assertThat(winLoseOrDraw("scissors", "rock"), "scissors vs. rock").isEqualTo("lose")
|
|
||||||
assertThat(winLoseOrDraw("scissors", "scissors"), "scissors vs. scissors").isEqualTo("draw")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
/*
|
|
||||||
* StockQuoteTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.all
|
|
||||||
import assertk.assertFailure
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.*
|
|
||||||
import net.thauvin.erik.mobibot.ExceptionSanitizer.sanitize
|
|
||||||
import net.thauvin.erik.mobibot.LocalProperties
|
|
||||||
import net.thauvin.erik.mobibot.modules.StockQuote.Companion.getQuote
|
|
||||||
import net.thauvin.erik.mobibot.msg.ErrorMessage
|
|
||||||
import net.thauvin.erik.mobibot.msg.Message
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `StockQuoteTest` class.
|
|
||||||
*/
|
|
||||||
class StockQuoteTest : LocalProperties() {
|
|
||||||
private fun buildMatch(label: String): String {
|
|
||||||
return "${label}:[ ]+[0-9.]+".prependIndent()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(ModuleException::class)
|
|
||||||
fun testGetQuote() {
|
|
||||||
val apiKey = getProperty(StockQuote.API_KEY_PROP)
|
|
||||||
try {
|
|
||||||
var symbol = "apple inc"
|
|
||||||
val messages = getQuote(symbol, apiKey)
|
|
||||||
assertThat(messages, "response not empty").isNotEmpty()
|
|
||||||
assertThat(messages, "getQuote($symbol)").index(0).prop(Message::msg).matches("Symbol: AAPL .*".toRegex())
|
|
||||||
assertThat(messages, "getQuote($symbol)").index(1).prop(Message::msg).matches(buildMatch("Price").toRegex())
|
|
||||||
assertThat(messages, "getQuote($symbol)").index(2).prop(Message::msg)
|
|
||||||
.matches(buildMatch("Previous").toRegex())
|
|
||||||
assertThat(messages, "getQuote($symbol)").index(3).prop(Message::msg).matches(buildMatch("Open").toRegex())
|
|
||||||
|
|
||||||
symbol = "blahfoo"
|
|
||||||
assertThat(getQuote(symbol, apiKey).first(), "getQuote($symbol)").all {
|
|
||||||
isInstanceOf(ErrorMessage::class.java)
|
|
||||||
prop(Message::msg).isEqualTo(StockQuote.INVALID_SYMBOL)
|
|
||||||
}
|
|
||||||
assertThat(getQuote("", "apikey").first(), "getQuote(empty)").all {
|
|
||||||
isInstanceOf(ErrorMessage::class.java)
|
|
||||||
prop(Message::msg).isEqualTo(StockQuote.INVALID_SYMBOL)
|
|
||||||
}
|
|
||||||
assertFailure { getQuote("test", "") }.isInstanceOf(ModuleException::class.java).hasNoCause()
|
|
||||||
} catch (e: ModuleException) {
|
|
||||||
// Avoid displaying api keys in CI logs
|
|
||||||
if ("true" == System.getenv("CI")) {
|
|
||||||
throw e.sanitize(apiKey)
|
|
||||||
} else {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
/*
|
|
||||||
* Weather2Test.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.all
|
|
||||||
import assertk.assertFailure
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.*
|
|
||||||
import net.aksingh.owmjapis.api.APIException
|
|
||||||
import net.aksingh.owmjapis.core.OWM
|
|
||||||
import net.thauvin.erik.mobibot.LocalProperties
|
|
||||||
import net.thauvin.erik.mobibot.modules.Weather2.Companion.API_KEY_PROP
|
|
||||||
import net.thauvin.erik.mobibot.modules.Weather2.Companion.ftoC
|
|
||||||
import net.thauvin.erik.mobibot.modules.Weather2.Companion.getCountry
|
|
||||||
import net.thauvin.erik.mobibot.modules.Weather2.Companion.getWeather
|
|
||||||
import net.thauvin.erik.mobibot.modules.Weather2.Companion.mphToKmh
|
|
||||||
import net.thauvin.erik.mobibot.msg.Message
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `Weather2Test` class.
|
|
||||||
*/
|
|
||||||
class Weather2Test : LocalProperties() {
|
|
||||||
@Test
|
|
||||||
fun testFtoC() {
|
|
||||||
val t = ftoC(32.0)
|
|
||||||
assertThat(t.second, "32 °F is 0 °C").isEqualTo(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGetCountry() {
|
|
||||||
assertThat(getCountry("foo"), "foo is not a valid country").isEqualTo(OWM.Country.UNITED_STATES)
|
|
||||||
assertThat(getCountry("fr"), "country should France").isEqualTo(OWM.Country.FRANCE)
|
|
||||||
|
|
||||||
val country = OWM.Country.entries.toTypedArray()
|
|
||||||
repeat(3) {
|
|
||||||
val rand = country[(country.indices).random()]
|
|
||||||
assertThat(getCountry(rand.value), rand.name).isEqualTo(rand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testMphToKmh() {
|
|
||||||
val w = mphToKmh(0.62)
|
|
||||||
assertThat(w.second, "0.62 mph is 1 km/h").isEqualTo(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(ModuleException::class)
|
|
||||||
fun testWeather() {
|
|
||||||
var query = "98204"
|
|
||||||
var messages = getWeather(query, getProperty(API_KEY_PROP))
|
|
||||||
assertThat(messages, "getWeather($query)").index(0).prop(Message::msg).all {
|
|
||||||
contains("Everett, United States")
|
|
||||||
contains("US")
|
|
||||||
}
|
|
||||||
assertThat(messages, "getWeather($query)").index(messages.size - 1).prop(Message::msg).endsWith("98204%2CUS")
|
|
||||||
|
|
||||||
query = "San Francisco"
|
|
||||||
messages = getWeather(query, getProperty(API_KEY_PROP))
|
|
||||||
assertThat(messages, "getWeather($query)").index(0).prop(Message::msg).all {
|
|
||||||
contains("San Francisco")
|
|
||||||
contains("US")
|
|
||||||
}
|
|
||||||
assertThat(messages, "getWeather($query)").index(messages.size - 1).prop(Message::msg).endsWith("5391959")
|
|
||||||
|
|
||||||
query = "London, GB"
|
|
||||||
messages = getWeather(query, getProperty(API_KEY_PROP))
|
|
||||||
assertThat(messages, "getWeather($query)").index(0).prop(Message::msg).all {
|
|
||||||
contains("London, United Kingdom")
|
|
||||||
contains("GB")
|
|
||||||
}
|
|
||||||
assertThat(messages, "getWeather($query)").index(messages.size - 1).prop(Message::msg).endsWith("2643743")
|
|
||||||
|
|
||||||
try {
|
|
||||||
query = "Foo, US"
|
|
||||||
getWeather(query, getProperty(API_KEY_PROP))
|
|
||||||
} catch (e: ModuleException) {
|
|
||||||
assertThat(e.cause, "getWeather($query)").isNotNull().isInstanceOf(APIException::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
query = "test"
|
|
||||||
assertFailure { getWeather(query, "") }.isInstanceOf(ModuleException::class.java).hasNoCause()
|
|
||||||
assertFailure { getWeather(query, null) }.isInstanceOf(ModuleException::class.java).hasNoCause()
|
|
||||||
|
|
||||||
messages = getWeather("", "apikey")
|
|
||||||
assertThat(messages, "getWeather(empty)").index(0).prop(Message::isError).isTrue()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
* WolframAlphaTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.assertFailure
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.contains
|
|
||||||
import assertk.assertions.hasMessage
|
|
||||||
import assertk.assertions.isInstanceOf
|
|
||||||
import net.thauvin.erik.mobibot.ExceptionSanitizer.sanitize
|
|
||||||
import net.thauvin.erik.mobibot.LocalProperties
|
|
||||||
import net.thauvin.erik.mobibot.modules.WolframAlpha.Companion.queryWolfram
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class WolframAlphaTest : LocalProperties() {
|
|
||||||
@Test
|
|
||||||
fun testAppId() {
|
|
||||||
assertFailure { queryWolfram("1 gallon to liter", appId = "DEMO") }
|
|
||||||
.isInstanceOf(ModuleException::class.java)
|
|
||||||
.hasMessage("Error 1: Invalid appid")
|
|
||||||
|
|
||||||
assertFailure { queryWolfram("1 gallon to liter", appId = "") }
|
|
||||||
.isInstanceOf(ModuleException::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(groups = ["modules", "no-ci"])
|
|
||||||
@Throws(ModuleException::class)
|
|
||||||
fun queryWolframTest() {
|
|
||||||
val apiKey = getProperty(WolframAlpha.APPID_KEY_PROP)
|
|
||||||
try {
|
|
||||||
var query = "SFO to SEA"
|
|
||||||
assertThat(queryWolfram(query, appId = apiKey), "queryWolfram($query)").contains("miles")
|
|
||||||
|
|
||||||
query = "SFO to LAX"
|
|
||||||
assertThat(
|
|
||||||
queryWolfram(query, WolframAlpha.METRIC, apiKey),
|
|
||||||
"queryWolfram($query)"
|
|
||||||
).contains("kilometers")
|
|
||||||
} catch (e: ModuleException) {
|
|
||||||
// Avoid displaying api key in CI logs
|
|
||||||
if ("true" == System.getenv("CI")) {
|
|
||||||
throw e.sanitize(apiKey)
|
|
||||||
} else {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
/*
|
|
||||||
* WordTimeTest.kt
|
|
||||||
*
|
|
||||||
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* Neither the name of this project nor the names of its contributors may be
|
|
||||||
* used to endorse or promote products derived from this software without
|
|
||||||
* specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.thauvin.erik.mobibot.modules
|
|
||||||
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.endsWith
|
|
||||||
import assertk.assertions.matches
|
|
||||||
import assertk.assertions.startsWith
|
|
||||||
import net.thauvin.erik.mobibot.Utils.bold
|
|
||||||
import net.thauvin.erik.mobibot.modules.WorldTime.Companion.BEATS_KEYWORD
|
|
||||||
import net.thauvin.erik.mobibot.modules.WorldTime.Companion.COUNTRIES_MAP
|
|
||||||
import net.thauvin.erik.mobibot.modules.WorldTime.Companion.time
|
|
||||||
import org.pircbotx.Colors
|
|
||||||
import kotlin.test.Test
|
|
||||||
import java.time.ZoneId
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `WordTimeTest` class.
|
|
||||||
*/
|
|
||||||
class WordTimeTest {
|
|
||||||
@Test
|
|
||||||
fun testTime() {
|
|
||||||
assertThat(time(), "time()").matches(
|
|
||||||
("The time is ${Colors.BOLD}\\d{1,2}:\\d{2}${Colors.BOLD} " +
|
|
||||||
"on ${Colors.BOLD}\\w+, \\d{1,2} \\w+ \\d{4}${Colors.BOLD} " +
|
|
||||||
"in ${Colors.BOLD}Los Angeles${Colors.BOLD}").toRegex()
|
|
||||||
)
|
|
||||||
assertThat(time(""), "time()").endsWith("Los Angeles".bold())
|
|
||||||
assertThat(time("PST"), "time(PST)").endsWith("Los Angeles".bold())
|
|
||||||
assertThat(time("GB"), "time(GB)").endsWith("London".bold())
|
|
||||||
assertThat(time("FR"), "time(FR)").endsWith("Paris".bold())
|
|
||||||
assertThat(time("BLAH"), "time(BLAH)").startsWith("Unsupported")
|
|
||||||
assertThat(time("BEAT"), "time($BEATS_KEYWORD)").matches("[\\w ]+ .?@\\d{3}+.? .beats".toRegex())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testZones() {
|
|
||||||
COUNTRIES_MAP.filter { it.value != BEATS_KEYWORD }.forEach {
|
|
||||||
assertThat(ZoneId.of(it.value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
/*
|
|
||||||
* MessageTest.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 assertk.all
|
|
||||||
import assertk.assertThat
|
|
||||||
import assertk.assertions.isFalse
|
|
||||||
import assertk.assertions.isTrue
|
|
||||||
import assertk.assertions.prop
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class MessageTest {
|
|
||||||
@Test
|
|
||||||
fun testConstructor() {
|
|
||||||
var msg = Message("foo")
|
|
||||||
|
|
||||||
msg.isError = true
|
|
||||||
assertThat(msg.isNotice, "message is notice").isTrue()
|
|
||||||
|
|
||||||
msg = Message("foo", isError = true)
|
|
||||||
assertThat(msg.isNotice, "message is notice too").isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testErrorMessage() {
|
|
||||||
val msg = ErrorMessage("foo")
|
|
||||||
assertThat(msg).all {
|
|
||||||
prop(Message::isError).isTrue()
|
|
||||||
prop(Message::isNotice).isTrue()
|
|
||||||
prop(Message::isPrivate).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIsError() {
|
|
||||||
val msg = Message("foo")
|
|
||||||
msg.isError = true
|
|
||||||
assertThat(msg).all {
|
|
||||||
prop(Message::isError).isTrue()
|
|
||||||
prop(Message::isNotice).isTrue()
|
|
||||||
prop(Message::isPrivate).isFalse()
|
|
||||||
}
|
|
||||||
msg.isError = false
|
|
||||||
assertThat(msg).all {
|
|
||||||
prop(Message::isError).isFalse()
|
|
||||||
prop(Message::isNotice).isTrue()
|
|
||||||
prop(Message::isPrivate).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testNoticeMessage() {
|
|
||||||
val msg = NoticeMessage("food")
|
|
||||||
assertThat(msg).all {
|
|
||||||
prop(Message::isError).isFalse()
|
|
||||||
prop(Message::isNotice).isTrue()
|
|
||||||
prop(Message::isPrivate).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testPrivateMessage() {
|
|
||||||
val msg = PrivateMessage("foo")
|
|
||||||
assertThat(msg).all {
|
|
||||||
prop(Message::isPrivate).isTrue()
|
|
||||||
prop(Message::isError).isFalse()
|
|
||||||
prop(Message::isNotice).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testPublicMessage() {
|
|
||||||
val msg = PublicMessage("foo")
|
|
||||||
assertThat(msg).all {
|
|
||||||
prop(Message::isError).isFalse()
|
|
||||||
prop(Message::isNotice).isFalse()
|
|
||||||
prop(Message::isPrivate).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,11 +14,11 @@ import java.time.ZoneId
|
||||||
*/
|
*/
|
||||||
object ReleaseInfo {
|
object ReleaseInfo {
|
||||||
const val PROJECT = "mobibot"
|
const val PROJECT = "mobibot"
|
||||||
const val VERSION = "0.8.0-rc+20231110234054"
|
const val VERSION = "0.8.0-rc+20231111131146"
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val BUILD_DATE: LocalDateTime = LocalDateTime.ofInstant(
|
val BUILD_DATE: LocalDateTime = LocalDateTime.ofInstant(
|
||||||
Instant.ofEpochMilli(1699688454937L), ZoneId.systemDefault()
|
Instant.ofEpochMilli(1699737106723L), ZoneId.systemDefault()
|
||||||
)
|
)
|
||||||
|
|
||||||
const val WEBSITE = "https://www.mobitopia.org/mobibot/"
|
const val WEBSITE = "https://www.mobitopia.org/mobibot/"
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Configuration status="warn">
|
|
||||||
<Appenders>
|
|
||||||
<Console name="stderr" target="SYSTEM_ERR">
|
|
||||||
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
|
|
||||||
</Console>
|
|
||||||
<Console name="input" target="SYSTEM_OUT">
|
|
||||||
<PatternLayout pattern="%d{UNIX_MILLIS} %msg%n"/>
|
|
||||||
<Filters>
|
|
||||||
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
|
|
||||||
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
|
||||||
</Filters>
|
|
||||||
</Console>
|
|
||||||
<Console name="output" target="SYSTEM_OUT">
|
|
||||||
<PatternLayout pattern="%d{UNIX_MILLIS} >>>%msg%n"/>
|
|
||||||
<Filters>
|
|
||||||
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
|
|
||||||
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
|
|
||||||
</Filters>
|
|
||||||
</Console>
|
|
||||||
</Appenders>
|
|
||||||
<Loggers>
|
|
||||||
<Root level="warn" additivity="false">
|
|
||||||
<AppenderRef ref="stderr"/>
|
|
||||||
</Root>
|
|
||||||
<logger level="debug" name="org.pircbotx.InputParser" additivity="false">
|
|
||||||
<appender-ref ref="input"/>
|
|
||||||
<appender-ref ref="stderr" level="warn"/>
|
|
||||||
</logger>
|
|
||||||
<logger level="debug" name="org.pircbotx.output.OutputRaw" additivity="false">
|
|
||||||
<appender-ref ref="output"/>
|
|
||||||
<appender-ref ref="stderr" level="warn"/>
|
|
||||||
</logger>
|
|
||||||
<logger level="trace" name="net.thauvin.erik.mobibot" additivity="false">
|
|
||||||
<appender-ref ref="stderr"/>
|
|
||||||
</logger>
|
|
||||||
</Loggers>
|
|
||||||
</Configuration>
|
|
Loading…
Add table
Add a link
Reference in a new issue