Created
May 24, 2016 22:15
-
-
Save joshgit/fa7fe443f09c336da96a17062f48634c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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