Skip to content

Commit

Permalink
Merge pull request #2 from CO2-Codes/pronouns
Browse files Browse the repository at this point in the history
Pronouns
  • Loading branch information
CO2-Codes authored Mar 14, 2020
2 parents 9a92c97 + 6cb82dd commit 6488eeb
Show file tree
Hide file tree
Showing 11 changed files with 425 additions and 63 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
target/

# IntelliJ
.idea
.idea

# Do not accidentally check in this file if the default example.conf is used and it's created here
pronouns.txt
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ To run the bot without making a jar, use `sbt "run <path/to/configfile>"`

This bot is tested on openJDK 11.

In the configuration file, the array of `bot-admins` is used to decide who is allowed to run admin-only commands (currently
only !quit in the adminListener). The separate `puppet-masters` array is used to decide who may puppet the bot.
In the configuration file, the array of `bot-admins` is used to decide who is allowed to run admin-only commands.
The separate `puppet-masters` array is used to decide who may puppet the bot.

The `listeners` array is the most important. This decides which functionality your instance of the bot will have.
Every listener has an ignore-channels settings which can be used to ignore all messages from those channels for that
Expand All @@ -32,3 +32,12 @@ given channel or anything, use at your discretion.
### linkListener
This listener reacts to messages and actions containing http/https links. It attempts to retrieve the <title> tag in
html pages and if it can find one, it will send the title to the channel.

### pronounListener
This listener stores users' personal pronouns (he, she, they, it, other) and can be used to look up the pronouns. It
writes these into a file to keep them between restarts.

### User documentation
If you want to run your own instance of the bot please host your own documentation specific to that instance.
Feel free to use [Isaac's documentation](https://co2.codes/xkcd/isaac-docs.php) (the original instance of this bot)
as a basis for your own.
18 changes: 9 additions & 9 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name := "Scala-IRC-bot"

version := "0.2.3"
version := "0.3.0"

scalaVersion := "2.13.0"
scalaVersion := "2.13.1"

// Recommended flags from https://tpolecat.github.io/2017/04/25/scalac-flags.html (Removed scala 2.13 deprecated flags)
scalacOptions ++= Seq(
Expand Down Expand Up @@ -43,24 +43,24 @@ scalacOptions ++= Seq(
"-Ywarn-unused:privates", // Warn if a private member is unused.
"-Ywarn-value-discard", // Warn when non-Unit expression results are unused.
)
lazy val silencerVersion = "1.4.2"
lazy val silencerVersion = "1.6.0"

lazy val ircBot = project.in(file("."))
.settings(
libraryDependencies += compilerPlugin(
"com.github.ghik" %% "silencer-plugin" % silencerVersion
"com.github.ghik" %% "silencer-plugin" % silencerVersion cross CrossVersion.full
),

libraryDependencies ++= Seq(
"org.pircbotx" % "pircbotx" % "2.1",
"com.github.pureconfig" %% "pureconfig" % "0.12.2",
"com.github.pureconfig" %% "pureconfig" % "0.12.3",
"ch.qos.logback" % "logback-classic" % "1.2.3",
"com.typesafe.akka" %% "akka-http" % "10.1.11",
"com.typesafe.akka" %% "akka-actor" % "2.6.3",
"com.typesafe.akka" %% "akka-stream" % "2.6.3",
"com.typesafe.akka" %% "akka-actor" % "2.6.4",
"com.typesafe.akka" %% "akka-stream" % "2.6.4",
"org.apache.commons" % "commons-text" % "1.8",
"com.github.ghik" %% "silencer-lib" % silencerVersion,
"org.scalatest" %% "scalatest" % "3.1.0" % "test",
"com.github.ghik" %% "silencer-lib" % silencerVersion % Provided cross CrossVersion.full,
"org.scalatest" %% "scalatest" % "3.1.1" % "test",
)

)
Expand Down
16 changes: 12 additions & 4 deletions example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,26 @@ bot-configuration {
channels = ["#array", "#of", "#channels"]
finger-msg = "Some message" // Optional
listeners = ["adminListener", "linkListener"] // Most important setting, decides which functionality is enabled.
ignore = ["nickname1", "nickname2"] // Optional, nicknames the bot should ignore

general-config {
ignore-nicks = ["nickname1", "nickname2"] // Optional, nicknames the bot should ignore
ignore-channels = ["#of"] // Optional, channels the bot should ignore. Useful if you want to use it to just keep a channel alive.
bot-admins = ["Array", "of", "nicknames"] // Decides who can use admin-only commands.
}


}

// Specific configurations per listener, only needed if the listener is enabled.
admin-listener {
help-text = "Some help text"
bot-admins = ["Array", "of", "nicknames"] // Decides who can use admin-only commands.
puppet-masters = ["nicknames"] // Decides who can use !say and !act in PM. Optional.
ignore-channels = ["#of"] // Channels ignored by this listener. Optional.
}

link-listener {
bold-titles = true // Optional, sets whether titles sent to channel by this bot should be bold or not.
ignore-channels = ["#of"] // Channels ignored by this listener. Optional.
}

pronoun-listener {
file-path = "pronouns.txt"
}
32 changes: 19 additions & 13 deletions src/main/scala/codes/co2/ircbot/config/BotConfiguration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,25 @@ case class BotConfiguration(
channels: Seq[String],
fingerMsg: Option[String],
listeners: Seq[String],
ignore: Option[Seq[String]],
generalConfig: GeneralConfig,
)

case class Connection(serverName: String, port: Int, ssl: Boolean)

trait ListenerConfig {
val ignoreChannels: Option[Seq[String]]
case class GeneralConfig(
ignoreNicks: Option[Seq[String]],
ignoreChannels: Option[Seq[String]],
botAdmins: Seq[String],
) {
val ignoredChannels: Seq[String] = ignoreChannels.getOrElse(Seq.empty)
val ignoredNicks: Seq[String] = ignoreNicks.getOrElse(Seq.empty)
}

case class LinkListenerConfig(
boldTitles: Option[Boolean],
ignoreChannels: Option[Seq[String]]) extends ListenerConfig
case class LinkListenerConfig(boldTitles: Option[Boolean])

case class AdminListenerConfig(
helpText: String,
botAdmins: Seq[String],
puppetMasters: Option[Seq[String]],
ignoreChannels: Option[Seq[String]]) extends ListenerConfig
case class AdminListenerConfig(helpText: String, puppetMasters: Option[Seq[String]])

case class PronounListenerConfig(filePath: String)

object BotConfiguration {
val log: Logger = LoggerFactory.getLogger(getClass)
Expand All @@ -45,15 +45,21 @@ object BotConfiguration {
.at("link-listener").load[LinkListenerConfig]
.fold(failures => {
log.info(s"Could not load link-listener config, reason ${failures.toList.map(_.description)} Using default config.")
LinkListenerConfig(None, None)
LinkListenerConfig(None)
}, success => success)

def loadAdminListenerConfig(path: Path): AdminListenerConfig = ConfigSource.default(ConfigSource.file(path))
.at("admin-listener").load[AdminListenerConfig]
.fold(failures => {
log.info(s"Could not load admin-listener config, reason ${failures.toList.map(_.description)} Using default config.")
AdminListenerConfig("", Seq.empty, None, None)
AdminListenerConfig("", None)
}, success => success)

def loadPronounListenerConfig(path: Path): PronounListenerConfig = ConfigSource.default(ConfigSource.file(path))
.at("pronoun-listener").load[PronounListenerConfig]
.fold(failures => {
log.info(s"Could not load pronoun-listener config, reason ${failures.toList.map(_.description)} Using default config.")
PronounListenerConfig("pronouns.txt")
}, success => success)

}
18 changes: 15 additions & 3 deletions src/main/scala/codes/co2/ircbot/listeners/GenericListener.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package codes.co2.ircbot.listeners

import codes.co2.ircbot.config.ListenerConfig
import codes.co2.ircbot.config.GeneralConfig
import com.github.ghik.silencer.silent
import org.pircbotx.PircBotX
import org.pircbotx.hooks.ListenerAdapter
import org.pircbotx.hooks.events.{ActionEvent, MessageEvent, PrivateMessageEvent}
import org.pircbotx.hooks.types.GenericChannelUserEvent
import org.pircbotx.hooks.types.{GenericChannelUserEvent, GenericEvent}

abstract class GenericListener(config: ListenerConfig, nicksToIgnore: Seq[String]) extends ListenerAdapter {
abstract class GenericListener(config: GeneralConfig) extends ListenerAdapter {
private val channelsToIgnore = config.ignoredChannels
private val nicksToIgnore = config.ignoredNicks
protected val admins: Seq[String] = config.botAdmins

private def sentInIgnoredChannel(event: GenericChannelUserEvent): Boolean = {
// Wrap in an option because getChannel returns null for PM action events.
Expand All @@ -32,3 +35,12 @@ abstract class GenericListener(config: ListenerConfig, nicksToIgnore: Seq[String
if (!nicksToIgnore.contains(event.getUser.getNick) && !sentInIgnoredChannel(event)) onAcceptedUserMsg(event)
}
}

object GenericListener {

// Scala doesn't seem to like Java's Generics very much...
def getBot(event: GenericEvent): PircBotX = {
event.getBot[PircBotX]
}

}
Original file line number Diff line number Diff line change
@@ -1,49 +1,50 @@
package codes.co2.ircbot.listeners.administration

import akka.actor.ActorSystem
import codes.co2.ircbot.config.AdminListenerConfig
import codes.co2.ircbot.config.{AdminListenerConfig, GeneralConfig}
import codes.co2.ircbot.listeners.GenericListener
import codes.co2.ircbot.listeners.GenericListener._
import org.pircbotx.PircBotX
import org.pircbotx.hooks.Event
import org.pircbotx.hooks.events.{ConnectEvent, MessageEvent, PrivateMessageEvent}

class AdminListener(config: AdminListenerConfig, nicksToIgnore: Seq[String], actorSystem: ActorSystem) extends GenericListener(config, nicksToIgnore) {
class AdminListener(config: AdminListenerConfig, generalConfig: GeneralConfig)(implicit actorSystem: ActorSystem) extends GenericListener(generalConfig) {

private val puppetMasters = config.puppetMasters.getOrElse(Seq.empty)

// Scala doesn't seem to like Java's Generics very much...
private def getBot(event: Event): PircBotX = {
event.getBot[PircBotX]
}

override def onConnect(event: ConnectEvent): Unit = {
// Set IRC server bot user mode
getBot(event).send().mode(getBot(event).getNick, "+B")
}

override def onAcceptedUserPrivateMsg(event: PrivateMessageEvent): Unit = {
event.getMessage match {
case "!quit" if config.botAdmins.contains(event.getUser.getNick) && event.getUser.isVerified =>
shutdown(getBot(event))

case msg if puppetMasters.contains(event.getUser.getNick) && event.getUser.isVerified &&
(msg.startsWith("!say") || msg.startsWith("!act")) =>
val command = msg.split(" ", 3)
command.headOption match {
case Some("!say") if command.sizeIs >= 2 =>
getBot(event).send().message(command(1), command(2))
case Some("!act") if command.sizeIs >= 2 =>
getBot(event).send().action(command(1), command(2))
}
if (event.getMessage.startsWith("!")) {
event.getMessage match {
case "!help" => event.respondWith(config.helpText)
case "!quit" if admins.contains(event.getUser.getNick) && event.getUser.isVerified =>
shutdown(getBot(event))

case msg if puppetMasters.contains(event.getUser.getNick) && event.getUser.isVerified &&
(msg.startsWith("!say") || msg.startsWith("!act")) =>
val command = msg.split(" ", 3)
command.headOption match {
case Some("!say") if command.sizeIs >= 2 =>
getBot(event).send().message(command(1), command(2))
case Some("!act") if command.sizeIs >= 2 =>
getBot(event).send().action(command(1), command(2))
}
}
}
}

override def onAcceptedUserMsg(event: MessageEvent): Unit = {
event.getMessage match {
case string if string.equalsIgnoreCase("botsnack") => event.getChannel.send().message(":D")
case "!help" => event.getChannel.send().message(config.helpText)
case "!quit" if config.botAdmins.contains(event.getUser.getNick) && event.getUser.isVerified =>
shutdown(getBot(event))
if (event.getMessage.startsWith("!")) {
event.getMessage match {
case "!help" => event.respondWith(config.helpText)
case "!quit" if admins.contains(event.getUser.getNick) && event.getUser.isVerified =>
shutdown(getBot(event))
}
} else if (event.getMessage.equalsIgnoreCase("botsnack")) {
event.getChannel.send().message(":D")
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package codes.co2.ircbot.listeners.links

import codes.co2.ircbot.config.LinkListenerConfig
import codes.co2.ircbot.config.{GeneralConfig, LinkListenerConfig}
import codes.co2.ircbot.http.HttpClient
import codes.co2.ircbot.listeners.GenericListener
import org.pircbotx.hooks.events.{ActionEvent, MessageEvent}
Expand All @@ -10,7 +10,7 @@ import org.slf4j.{Logger, LoggerFactory}

import scala.concurrent.ExecutionContext

class LinkListener(httpClient: HttpClient, config: LinkListenerConfig, nicksToIgnore: Seq[String])(implicit ec: ExecutionContext) extends GenericListener(config, nicksToIgnore) {
class LinkListener(httpClient: HttpClient, config: LinkListenerConfig, generalConfig: GeneralConfig)(implicit ec: ExecutionContext) extends GenericListener(generalConfig) {
val log: Logger = LoggerFactory.getLogger(getClass)


Expand Down
Loading

0 comments on commit 6488eeb

Please sign in to comment.