Skip to content

Instantly share code, notes, and snippets.

@joshgit
Created May 24, 2016 22:15
Show Gist options
  • Save joshgit/fa7fe443f09c336da96a17062f48634c to your computer and use it in GitHub Desktop.
Save joshgit/fa7fe443f09c336da96a17062f48634c to your computer and use it in GitHub Desktop.
import scala.annotation.tailrec
import spray.json._
import DefaultJsonProtocol._
import java.io._
import java.net._
object IrcClientMain
{
def main(args: Array[String]): Unit = {
// Supply the host, port, and nick via the command line, if desired.
val host = if (args.length >= 1) args(0) else "irc.freenode.net";
val port = if (args.length >= 2) args(1).toInt else 6665;
val nick = if (args.length >= 3) args(2) else "bubble_bot";
val channel = if (args.length >= 4) args(3) else "##breathe";
val sock = new Socket(host, port)
val out = new OutputStreamWriter(sock.getOutputStream)
new StreamLineReaderThread(System.in, new LineSink {
def doLine(line: String): Unit = {
if (!line.isEmpty) {
out.write(line + "\r\n")
out.flush()
}
}
})
new StreamLineReaderThread(
sock.getInputStream,
new MsgParser(
new TeeMsgHandler(new NormalHandler(out), new NickTryer(nick, channel, out), new ModeHandler(channel, out), new SellerHandler(out))))
}
}
trait MsgHandler {
def doMsg(msg: Msg): Unit
}
class TeeMsgHandler(handlers: MsgHandler*) extends MsgHandler {
def doMsg(msg: Msg): Unit = handlers.foreach(_.doMsg(msg))
}
class NormalHandler(out: OutputStreamWriter) extends MsgHandler {
def doMsg(msg: Msg): Unit = {
println(msg)
msg match {
case PingMsg(server) =>
out.write("PONG " + server + "\r\n")
out.flush()
case _ =>
}
}
}
class SellerHandler(out: OutputStreamWriter) extends MsgHandler {
var bought = Map[String, Int]()
def requestRates(): String = {
val url = new URL("http://apilayer.net/api/live?access_key=cda42933e9bb6c86635dba28c897d2d4")
val connection = url.openConnection().asInstanceOf[HttpURLConnection]
val is = connection.getInputStream()
val rd = new BufferedReader(new InputStreamReader(is))
var buf = rd.readLine()
var response = ""
while (buf != null) {
response += buf
buf = rd.readLine()
}
rd.close()
response
}
def parseRates(s: String): Option[Map[String, JsValue]] = {
println(s)
// https://github.com/spray/spray-json/blob/master/src/main/scala/spray/json/JsValue.scala
s.parseJson match {
case JsObject(fields) => fields("quotes") match {
case JsObject(quotes) => Some(quotes)
case _ =>
println("quotes field was not an object: " + s)
None
}
case _ =>
println("Couldn't parse String into JSON object: " + s)
None
}
}
def toDouble(rates: Map[String, JsValue], key: String): Double = {
if (rates.isDefinedAt(key)) {
println("defined: " + rates(key))
rates(key) match {
case JsNumber(bigDec) => bigDec.toDouble
case _ => 0.0
}
}
else {
println("not defined")
0.0
}
}
def doMsg(msg: Msg): Unit = {
msg match {
// make this translate money!
case PrivMsg(source, target, msg) =>
if (msg.trim().startsWith(":@exchange")) {
parseRates(requestRates()) foreach { rates =>
if (msg.trim() == ":@exchange") {
out.write("PRIVMSG " + target + " :" + rates + "\r\n")
}
else {
msg.substring(":@exchange".length).trim().toUpperCase().split("\\s+") match {
case Array("USD", targetCur) =>
val key = "USD" + targetCur
val rate = toDouble(rates, key)
println("from usd: " + (key, rate))
out.write("PRIVMSG " + target + " :USD->" + targetCur + ": " + rate + "\r\n")
case Array(targetCur) =>
val key = "USD" + targetCur
val rate = toDouble(rates, key)
println("from usd: " + (key, rate))
out.write("PRIVMSG " + target + " :USD->" + targetCur + ": " + rate + "\r\n")
case Array(sourceCur, "USD") =>
val key = "USD" + sourceCur
val rate = 1.0 / toDouble(rates, key)
println("to usd: " + (key, rate))
out.write("PRIVMSG " + target + " :" + sourceCur + "->USD: " + rate + "\r\n")
case Array(sourceCur, targetCur) =>
val sourceToUsa = 1.0 / toDouble(rates, "USD" + sourceCur)
val usaToTarget = toDouble(rates, "USD" + targetCur)
println("sourceCur: " + sourceCur)
println("sourceToUsa: " + sourceToUsa)
println("targetCur: " + targetCur)
println("usaToTarget: " + usaToTarget)
val sourceToTarget = sourceToUsa * usaToTarget
val msg = "PRIVMSG " + target + " :" + sourceCur + "->" + targetCur + " : " + sourceToTarget + "\r\n"
println("Sending msg:" + msg)
out.write(msg)
case _ =>
out.write("PRIVMSG " + target + " :@exchange source target\r\n")
}
}
}
out.flush()
}
else if (msg.toLowerCase().contains("potty") || msg.toLowerCase().contains("toilet") || msg.toLowerCase().contains("bathroom")) {
if (new scala.util.Random().nextInt(3) == 0) {
out.write("PRIVMSG " + target + " :I could lend you some toilet paper.\r\n")
out.flush()
}
}
else if (msg.toLowerCase().contains("hugs") || msg.toLowerCase().contains("drugs")) {
if (new scala.util.Random().nextInt(3) == 0) {
out.write("PRIVMSG " + target + " :Hugs, not drugs.\r\n")
out.flush()
}
}
else if (msg.startsWith(":@order ")) {
val item = msg.substring(msg.indexOf(" ") + 1).trim
if (!item.isEmpty) {
if (!bought.contains(item)) {
out.write("PRIVMSG " + target + " :My first " + item + " sold! 1USD, please.\r\n")
out.flush()
bought += (item -> 1)
} else {
val priceStr = "%.2f".format(1 + Math.log(bought(item) + 1) / Math.log(2.0))
out.write("PRIVMSG " + target + " :1 " + item + " sold. Demand is increasing. " + priceStr + "USD, please.\r\n")
out.flush()
bought += (item -> (bought(item) + 1))
}
}
}
case _ =>
}
}
}
class NickTryer(nick: String, channel: String, out: OutputStreamWriter) extends MsgHandler {
var tries = 1
out.write("NICK " + nick + "\r\n")
out.write("USER " + nick + " 0 * :abc" + "\r\n")
out.flush()
def doMsg(msg: Msg): Unit = {
msg match {
case NumMsg(_, code, _, _) if List(431, 432, 433, 436) contains code =>
tries += 1
out.write("NICK " + nick + tries + "\r\n")
out.flush()
case _ =>
}
}
}
class ModeHandler(channel: String, out: OutputStreamWriter) extends MsgHandler {
def doMsg(msg: Msg): Unit = {
msg match {
case ModeMsg(_, _, ":+i") =>
out.write("JOIN " + channel + "\r\n")
out.write("PRIVMSG " + channel + " :is open for business. @order anything you want.\r\n")
out.flush()
case _ =>
}
}
}
trait LineSink {
def doLine(line: String): Unit
}
class StreamLineReaderThread(in: InputStream, sink: LineSink) {
val br = new BufferedReader(new InputStreamReader(in))
@tailrec final def inputLoop(): Unit = {
if (br.ready()) {
val input = br.readLine
sink.doLine(input.trim)
} else {
Thread.sleep(500)
}
inputLoop()
}
new Thread(new Runnable() {
override def run() = {
inputLoop()
println("StreamLineReaderThread finished.")
}
}).start()
}
sealed trait Msg
case class PingMsg(server: String) extends Msg
case class NumMsg(name: String, code: Int, target: String, rest: String) extends Msg
case class PrivMsg(source: String, target: String, msg: String) extends Msg
case class QuitMsg(name: String, reason: String) extends Msg
case class NoticeMsg(name: String, target: String, rest: String) extends Msg
case class ModeMsg(name: String, target: String, rest: String) extends Msg
case class JoinMsg(name: String, channel: String) extends Msg
case class Unknown(line: String) extends Msg
class MsgParser(handler: MsgHandler) extends LineSink {
def doLine(line: String): Unit = {
val msg =
line.split("\\s+", 4) match {
case Array("PING", serverName) => PingMsg(serverName.substring(1))
case Array(serverName, "NOTICE", targetName, rest) => NoticeMsg(serverName, targetName, rest)
case Array(serverName, "MODE", targetName, rest) => ModeMsg(serverName, targetName, rest)
case Array(source, "PRIVMSG", target, msg) => PrivMsg(source, target, msg)
case Array(name, "QUIT", _, reason) => QuitMsg(name, reason)
case Array(name, "JOIN", channel) => JoinMsg(name, channel)
case Array(serverName, num, targetName, rest) =>
try {
NumMsg(serverName, num.toInt, targetName, rest)
} catch {
case e: Exception => Unknown(line)
}
case _ => Unknown(line)
}
handler.doMsg(msg)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment