Seen is now keeping track of nicks using a TreeMap on various events (nick change, join, etc.)

This commit is contained in:
Erik C. Thauvin 2022-09-16 10:37:15 -07:00
parent 145749408b
commit f35313bf62
8 changed files with 108 additions and 33 deletions

View file

@ -173,6 +173,7 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
event?.let { event?.let {
with(event.getBot<PircBotX>()) { with(event.getBot<PircBotX>()) {
LinksMgr.twitter.notification("$nick disconnected from irc://$serverHostname") LinksMgr.twitter.notification("$nick disconnected from irc://$serverHostname")
seen.add(userChannelDao.getChannel(channel).users)
} }
} }
LinksMgr.twitter.shutdown() LinksMgr.twitter.shutdown()
@ -197,8 +198,10 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
with(event.getBot<PircBotX>()) { with(event.getBot<PircBotX>()) {
if (user.nick == nick) { if (user.nick == nick) {
LinksMgr.twitter.notification("$nick has joined ${event.channel.name} on irc://$serverHostname") LinksMgr.twitter.notification("$nick has joined ${event.channel.name} on irc://$serverHostname")
seen.add(userChannelDao.getChannel(channel).users)
} else { } else {
tell.send(event) tell.send(event)
seen.add(user.nick)
} }
} }
} }
@ -228,7 +231,13 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
} }
override fun onNickChange(event: NickChangeEvent?) { override fun onNickChange(event: NickChangeEvent?) {
event?.let { tell.send(event) } 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?) { override fun onPart(event: PartEvent?) {
@ -236,6 +245,7 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
with(event.getBot<PircBotX>()) { with(event.getBot<PircBotX>()) {
if (user.nick == nick) { if (user.nick == nick) {
LinksMgr.twitter.notification("$nick has left ${event.channel.name} on irc://$serverHostname") LinksMgr.twitter.notification("$nick has left ${event.channel.name} on irc://$serverHostname")
seen.add(userChannelDao.getChannel(channel).users)
} else { } else {
seen.add(user.nick) seen.add(user.nick)
} }
@ -424,10 +434,10 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
addons.add(Ping()) addons.add(Ping())
addons.add(RockPaperScissors()) addons.add(RockPaperScissors())
addons.add(StockQuote()) addons.add(StockQuote())
addons.add(Weather2())
addons.add(WorldTime())
addons.add(War()) addons.add(War())
addons.add(Weather2())
addons.add(WolframAlpha()) addons.add(WolframAlpha())
addons.add(WorldTime())
// Sort the addons // Sort the addons
addons.names.sort() addons.names.sort()

View file

@ -214,7 +214,7 @@ object Utils {
} catch (e: IOException) { } catch (e: IOException) {
logger.error("An IO error occurred loading the ${description}.", e) logger.error("An IO error occurred loading the ${description}.", e)
} catch (e: ClassNotFoundException) { } catch (e: ClassNotFoundException) {
logger.error("An error occurred loading the {$description}.", e) logger.error("An error occurred loading the ${description}.", e)
} }
} }
return default return default

View file

@ -0,0 +1,44 @@
/*
* SeenComparator.kt
*
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.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 {
private const val serialVersionUID = 1L
}
}

View file

@ -32,6 +32,7 @@
package net.thauvin.erik.mobibot.commands.seen package net.thauvin.erik.mobibot.commands.seen
import com.google.common.collect.ImmutableSortedSet
import net.thauvin.erik.mobibot.Utils.bot import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.helpFormat import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.loadData import net.thauvin.erik.mobibot.Utils.loadData
@ -39,13 +40,16 @@ import net.thauvin.erik.mobibot.Utils.saveData
import net.thauvin.erik.mobibot.Utils.sendMessage import net.thauvin.erik.mobibot.Utils.sendMessage
import net.thauvin.erik.mobibot.commands.AbstractCommand import net.thauvin.erik.mobibot.commands.AbstractCommand
import net.thauvin.erik.mobibot.commands.Info.Companion.toUptime import net.thauvin.erik.mobibot.commands.Info.Companion.toUptime
import org.pircbotx.User
import org.pircbotx.hooks.types.GenericMessageEvent import org.pircbotx.hooks.types.GenericMessageEvent
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.util.TreeMap
class Seen(private val serialObject: String) : AbstractCommand() { class Seen(private val serialObject: String) : AbstractCommand() {
private val logger: Logger = LoggerFactory.getLogger(Seen::class.java) private val logger: Logger = LoggerFactory.getLogger(Seen::class.java)
val seenNicks: MutableList<SeenNick> = mutableListOf() val seenNicks = TreeMap<String, SeenNick>(NickComparator())
override val name = "seen" override val name = "seen"
override val help = listOf("To view when a nickname was last seen:", helpFormat("%c $name <nick>")) override val help = listOf("To view when a nickname was last seen:", helpFormat("%c $name <nick>"))
@ -53,6 +57,7 @@ class Seen(private val serialObject: String) : AbstractCommand() {
override val isPublic = true override val isPublic = true
override val isVisible = true override val isVisible = true
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) { override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
if (isEnabled()) { if (isEnabled()) {
if (args.isNotBlank() && !args.contains(' ')) { if (args.isNotBlank() && !args.contains(' ')) {
@ -63,13 +68,12 @@ class Seen(private val serialObject: String) : AbstractCommand() {
return return
} }
} }
seenNicks.forEach { if (seenNicks.containsKey(args)) {
if (it.nick.equals(args, true)) { val seenNick = seenNicks.getValue(args)
val lastSeen = System.currentTimeMillis() - it.last val lastSeen = System.currentTimeMillis() - seenNick.lastSeen
event.sendMessage("${it.nick} was last seen on $channel ${lastSeen.toUptime()} ago.") event.sendMessage("${seenNick.nick} was last seen on $channel ${lastSeen.toUptime()} ago.")
return return
} }
}
event.sendMessage("I haven't seen $args on $channel lately.") event.sendMessage("I haven't seen $args on $channel lately.")
} else { } else {
helpResponse(channel, args, event) helpResponse(channel, args, event)
@ -79,15 +83,16 @@ class Seen(private val serialObject: String) : AbstractCommand() {
fun add(nick: String) { fun add(nick: String) {
if (isEnabled()) { if (isEnabled()) {
seenNicks.forEach { seenNicks[nick] = SeenNick(nick, System.currentTimeMillis())
if (it.nick.equals(nick, true)) {
if (it.nick != nick) it.nick = nick
it.last = System.currentTimeMillis()
save() save()
return
} }
} }
seenNicks.add(SeenNick(nick))
fun add(users: ImmutableSortedSet<User>) {
if (isEnabled()) {
users.forEach {
seenNicks[it.nick] = SeenNick(it.nick, System.currentTimeMillis())
}
save() save()
} }
} }
@ -99,12 +104,14 @@ class Seen(private val serialObject: String) : AbstractCommand() {
fun load() { fun load() {
if (isEnabled()) { if (isEnabled()) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
seenNicks += loadData( seenNicks.putAll(
loadData(
serialObject, serialObject,
mutableListOf<SeenNick>(), TreeMap<String, SeenNick>(),
logger, logger,
"seen nicknames" "seen nicknames"
) as MutableList<SeenNick> ) as TreeMap<String, SeenNick>
)
} }
} }

View file

@ -34,7 +34,7 @@ package net.thauvin.erik.mobibot.commands.seen
import java.io.Serializable import java.io.Serializable
data class SeenNick(var nick: String, var last: Long = System.currentTimeMillis()) : Serializable { data class SeenNick(val nick: String, val lastSeen: Long) : Serializable {
companion object { companion object {
private const val serialVersionUID = 1L private const val serialVersionUID = 1L
} }

View file

@ -35,6 +35,7 @@ package net.thauvin.erik.mobibot.commands.seen
import assertk.assertThat import assertk.assertThat
import assertk.assertions.isEqualTo import assertk.assertions.isEqualTo
import assertk.assertions.isGreaterThan import assertk.assertions.isGreaterThan
import assertk.assertions.isNotEqualTo
import assertk.assertions.isTrue import assertk.assertions.isTrue
import org.testng.annotations.AfterClass import org.testng.annotations.AfterClass
import org.testng.annotations.BeforeClass import org.testng.annotations.BeforeClass
@ -45,6 +46,7 @@ import kotlin.io.path.fileSize
class SeenTest { class SeenTest {
private val tmpFile = kotlin.io.path.createTempFile(suffix = ".ser") private val tmpFile = kotlin.io.path.createTempFile(suffix = ".ser")
private val seen = Seen(tmpFile.toAbsolutePath().toString()) private val seen = Seen(tmpFile.toAbsolutePath().toString())
private val nick = "ErikT"
@BeforeClass @BeforeClass
fun saveTest() { fun saveTest() {
@ -57,12 +59,21 @@ class SeenTest {
tmpFile.deleteIfExists() tmpFile.deleteIfExists()
} }
@Test @Test(priority = 1)
fun loadTest() { fun loadTest() {
val nick = seen.seenNicks[0]
seen.clear() seen.clear()
assertThat(seen.seenNicks.isEmpty(), "nicknames map is not empty").isTrue()
seen.load() seen.load()
assertThat(seen.seenNicks[0] == nick, "nick is different").isTrue() assertThat(seen.seenNicks.containsKey(nick), "nick is missing").isTrue()
}
@Test
fun addTest() {
val last = seen.seenNicks[nick]?.lastSeen
seen.add(nick.lowercase())
assertThat(seen.seenNicks.size, "nick is duplicated").isEqualTo(1)
assertThat(seen.seenNicks[nick]?.lastSeen, "last seen is not different").isNotEqualTo(last)
assertThat(seen.seenNicks[nick]?.nick, "nick is not lowercase").isEqualTo(nick.lowercase())
} }
@Test(priority = 10) @Test(priority = 10)

View file

@ -1,9 +1,9 @@
#Generated by the Semver Plugin for Gradle #Generated by the Semver Plugin for Gradle
#Fri Sep 16 00:01:57 PDT 2022 #Fri Sep 16 10:29:48 PDT 2022
version.buildmeta=525 version.buildmeta=542
version.major=0 version.major=0
version.minor=8 version.minor=8
version.patch=0 version.patch=0
version.prerelease=rc version.prerelease=rc
version.project=mobibot version.project=mobibot
version.semver=0.8.0-rc+525 version.semver=0.8.0-rc+542

View file

@ -100,7 +100,7 @@
<div><code>mobibot: time UK</code></div> <div><code>mobibot: time UK</code></div>
<div><code>mobibot: time GMT</code></div> <div><code>mobibot: time GMT</code></div>
</li> </li>
<li>Sending messages to people on join/activity: <li>Sending messages to people on join/activity
<div><code>mobibot: tell nickname Give me a call when you see this.</code></div> <div><code>mobibot: tell nickname Give me a call when you see this.</code></div>
</li> </li>
<li>Recapping public channel messages <li>Recapping public channel messages
@ -109,6 +109,9 @@
<li>Listing the users on the channel <li>Listing the users on the channel
<div><code>/msg mobibot users</code></div> <div><code>/msg mobibot users</code></div>
</li> </li>
<li>Viewing when a nickname was last seen
<div><code>/msg mobibot seen nickname</code></div>
</li>
<li>Random jokes from <a href="http://www.icndb.com/">The Internet Chuck Norris Database</a> <li>Random jokes from <a href="http://www.icndb.com/">The Internet Chuck Norris Database</a>
<div><code>mobibot: joke</code></div> <div><code>mobibot: joke</code></div>
</li> </li>