Created
February 10, 2012 00:08
-
-
Save lopex/1784611 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
package pl.irc | |
import org.jibble.pircbot.PircBot | |
import dispatch._ | |
import Http._ | |
import net.liftweb.json._ | |
import net.liftweb.json.JsonDSL._ | |
import net.liftweb.json.Printer.{compact} | |
import java.io.{PrintStream, ByteArrayOutputStream} | |
object Multibottest extends PircBot { | |
val PRODUCTION = Option(System getProperty "multibot.production") map (_ toBoolean) getOrElse false | |
val BOTNAME = if (PRODUCTION) "multibot_" else "multibot__" | |
val BOTMSG = BOTNAME + ":" | |
val NUMLINES = 5 | |
val INNUMLINES = 8 | |
val LAMBDABOT = "lambdabot" | |
val LAMBDABOTIGNORE = Set("#scala", "#scalaz") | |
val ADMINS = List("***") | |
def main(args: Array[String]) { | |
setName(BOTNAME) | |
setVerbose(true) | |
setEncoding("UTF-8") | |
connect() | |
} | |
def connect() { | |
connect("irc.freenode.net") | |
val channels = if (PRODUCTION) List("#clojure.pl", "#scala.pl", "#jruby", "#ruby.pl", "#rubyonrails.pl", "#scala", "#scalaz") else List("#multibottest") | |
channels foreach joinChannel | |
} | |
override def onDisconnect: Unit = while (true) | |
try { | |
connect() | |
return | |
} catch { case e: Exception => | |
e.printStackTrace | |
Thread sleep 10000 | |
} | |
var lastChannel: Option[String] = None | |
override def onPrivateMessage(sender: String, login: String, hostname: String, message: String) = sender match { | |
case LAMBDABOT => lastChannel foreach (sendMessage(_, message)) | |
case _ => onMessage(sender, sender, login, hostname, message) | |
} | |
override def onNotice(sender: String, login: String, hostname: String, target: String, notice: String) = sender match { | |
case LAMBDABOT => lastChannel foreach (sendNotice(_, notice)) | |
case _ => | |
} | |
override def onAction(sender: String, login: String, hostname: String, target: String, action: String) = sender match { | |
case LAMBDABOT => lastChannel foreach (sendAction(_, action)) | |
case _ => | |
} | |
override def onMessage(channel: String, sender: String, login: String, hostname: String, message: String) = | |
serve(Msg(channel, sender, login, hostname, message)) | |
object Cmd {def unapply(s: String) = if (s.contains(' ')) Some(s.split(" ", 2).toList) else None} | |
object PasteCmd { def unapply(s: String) = if (s.indexOf("paste") == 1 && s.length > "?paste ".length) Some((s.substring(0, 1), s.substring("?paste ".length))) else None } | |
case class Msg(channel: String, sender: String, login: String, hostname: String, message: String) | |
val stdOut = System.out | |
val stdErr = System.err | |
val conOut = new ByteArrayOutputStream | |
val conOutStream = new PrintStream(conOut) | |
def captureOutput(block: => Unit) = try { | |
System setOut conOutStream | |
System setErr conOutStream | |
block | |
} finally { | |
System setOut stdOut | |
System setErr stdErr | |
conOut.flush | |
conOut.reset | |
} | |
import scala.tools.nsc.interpreter.{IMain} | |
val scalaInt = scala.collection.mutable.Map[String, IMain]() | |
def scalaInterpreter(channel: String)(f: (IMain, ByteArrayOutputStream) => Unit) = this.synchronized { | |
val si = scalaInt.getOrElseUpdate(channel, { | |
val settings = new scala.tools.nsc.Settings(null) | |
settings.usejavacp.value = true | |
settings.deprecation.value = true | |
val si = new IMain(settings) { override def parentClassLoader = Thread.currentThread.getContextClassLoader } | |
si.quietImport("scalaz._") | |
si.quietImport("Scalaz._") | |
si.quietImport("org.scalacheck.Prop._") | |
si | |
}) | |
captureOutput{f(si, conOut)} | |
} | |
import org.jruby.{RubyInstanceConfig, Ruby} | |
import org.jruby.runtime.scope.{ManyVarsDynamicScope} | |
val jrubyInt = scala.collection.mutable.Map[String, (Ruby, ManyVarsDynamicScope)]() | |
def jrubyInterpreter(channel: String)(f: (Ruby, ManyVarsDynamicScope, ByteArrayOutputStream) => Unit) = this.synchronized { | |
val (jr, sc) = jrubyInt.getOrElseUpdate(channel, { | |
val config = new RubyInstanceConfig | |
config setOutput conOutStream | |
config setError conOutStream | |
val jruby = Ruby.newInstance(config) | |
val scope = new ManyVarsDynamicScope(jruby.getStaticScopeFactory.newEvalScope(jruby.getCurrentContext.getCurrentScope.getStaticScope), jruby.getCurrentContext.getCurrentScope) | |
(jruby, scope) | |
}) | |
captureOutput{f(jr, sc, conOut)} | |
} | |
var pythonSession = "" | |
def sendLines(channel: String, message: String) = message split ("\n") filter (! _.isEmpty) take NUMLINES foreach (m => sendMessage(channel, " " + (if (!m.isEmpty && m.charAt(0) == 13) m.substring(1) else m))) | |
def serve(implicit msg: Msg): Unit = msg.message match { | |
case Cmd(BOTMSG :: m :: Nil) if ADMINS contains msg.sender => m match { | |
case Cmd("join" :: ch :: Nil) => joinChannel(ch) | |
case Cmd("leave" :: ch :: Nil) => partChannel(ch) | |
case Cmd("reply" :: ch :: Nil) => sendMessage(msg.channel, ch) | |
case Cmd("cookies":: "" :: Nil) => sendMessage(msg.channel, cookies.map{case (k, v) => k + " -> " + v}.mkString(" - ")) //cookies foreach {case(k, v) => sendMessage(msg.channel, k + " -> " + v)} | |
case _ => sendMessage(msg.channel, "unknown command") | |
} | |
case "@listchans" => sendMessage(msg.channel, getChannels mkString " ") | |
case "@bot" | "@bots" => sendMessage(msg.channel, ":)") | |
case "@help" => sendMessage(msg.channel, "(!) scala (!reset|type|scalex), (%) ruby (%reset), (,) clojure, (>>) haskell, (^) python, (&) javascript, (##) groovy, (<prefix>paste url), lambdabot relay (" + !LAMBDABOTIGNORE.contains(msg.channel) + ")") | |
case Cmd("!" :: m :: Nil) => scalaInterpreter(msg.channel){(si, cout) => | |
import scala.tools.nsc.interpreter.Results._ | |
sendLines(msg.channel, (si interpret m match { | |
case Success => cout.toString.replaceAll("(?m:^res[0-9]+: )", "") // + "\n" + iout.toString.replaceAll("(?m:^res[0-9]+: )", "") | |
case Error => cout.toString.replaceAll("^<console>:[0-9]+: ", "") | |
case Incomplete => "error: unexpected EOF found, incomplete expression" | |
})) | |
//.split("\n") take NUMLINES foreach (m => sendMessage(msg.channel, " " + (if (m.charAt(0) == 13) m.substring(1) else m))) | |
} | |
case PasteCmd(cmd, m) => // Http(url(m) >- {source => serve(msg.copy(message = "! " + source))}) | |
val conOut = new ByteArrayOutputStream | |
(new Http with NoLogging)(url(m) >>> new PrintStream(conOut)) | |
serve(msg.copy(message = cmd + " " + conOut)) | |
case Cmd("!type" :: m :: Nil) => scalaInterpreter(msg.channel)((si, cout) => sendMessage(msg.channel, si.typeOfExpression(m) map (_.toString) getOrElse "Failed to determine type.")) | |
case "!reset" => scalaInt -= msg.channel | |
case "!reset-all" => scalaInt.clear | |
case Cmd("!scalex" :: m :: Nil) => respondJSON(:/("api.scalex.org") <<? Map("q" -> m)) { | |
case JObject(_ :: _ :: JField("results", JArray(results)) :: _) => Some(results.collect { | |
case JObject(JField("resultType", JString(rtype)) :: | |
JField("parent", JObject(JField("name", JString(pname)) :: _ :: JField("typeParams", JString(ptparams)) :: Nil)) :: | |
_ :: | |
JField("name", JString(name)) :: | |
_ :: | |
JField("typeParams", JString(tparams)) :: | |
_ :: | |
JField("comment", | |
JObject( | |
_ :: | |
_ :: | |
JField("short", | |
JObject(_ :: | |
JField("txt", JString(txt)) :: | |
Nil | |
) | |
) :: | |
_ :: | |
_) | |
) :: | |
JField("valueParams", JString(vparams)) :: | |
Nil | |
) => pname + ptparams + " " + name + tparams + ": " + vparams + ": " + rtype + " '" + txt + "'" | |
}.mkString("\n")) | |
case e => Some("unexpected: " + e) | |
} | |
case Cmd("!!" :: m :: Nil) => respond(:/("www.simplyscala.com") / "interp" <<? Map("bot" -> "irc", "code" -> m)) { | |
case "warning: there were deprecation warnings; re-run with -deprecation for details" | | |
"warning: there were unchecked warnings; re-run with -unchecked for details" | | |
"New interpreter instance being created for you, this may take a few seconds." |"Please be patient." => None | |
case line => Some(line.replaceAll("^res[0-9]+: ", "")) | |
} | |
case Cmd("," :: m :: Nil) => respondJSON(:/("try-clojure.org") / "eval.json" <<? Map("expr" -> m)) { | |
case JObject(JField("expr", JString(_)) :: JField("result", JString(result)) :: Nil) => Some(result) | |
case JObject(JField("error", JBool(true)) :: JField("message", JString(message)) :: Nil) => Some(message) | |
case e => Some("unexpected: " + e) | |
} | |
case Cmd(">>" :: m :: Nil) => respondJSON(:/("tryhaskell.org") / "haskell.json" <<? Map("method" -> "eval", "expr" -> m)) { | |
case JObject(JField("result", JString(result)) :: JField("type", JString(xtype)) :: JField("expr", JString(_)) :: Nil) => Some(result + " :: " + xtype) | |
case JObject(JField("error", JString(error)) :: Nil) => Some(error) | |
case e => Some("unexpected: " + e) | |
} | |
case Cmd("%%" :: m :: Nil) => respondJSON(:/("tryruby.org") / "/levels/1/challenges/0" <:< | |
Map("Accept" -> "application/json, text/javascript, */*; q=0.01", | |
"Content-Type" -> "application/x-www-form-urlencoded; charset=UTF-8", | |
"X-Requested-With" -> "XMLHttpRequest", | |
"Connection" -> "keep-alive") <<< "cmd=" + java.net.URLEncoder.encode(m, "UTF-8")) { | |
case JObject(JField("success", JBool(true)) :: JField("output", JString(output)) :: _) => Some(output) | |
case JObject(JField("success", JBool(false)) :: _ :: JField("result", JString(output)) :: _) => Some(output) | |
case e => Some("unexpected: " + e) | |
} | |
case "%reset" => jrubyInt -= msg.channel | |
case "%reset-all" => jrubyInt.clear | |
case Cmd("%" :: m :: Nil) => jrubyInterpreter(msg.channel){(jr, sc, cout) => | |
try { | |
val result = jr.evalScriptlet(m, sc).toString | |
sendLines(msg.channel, cout.toString) | |
sendLines(msg.channel, result.toString) | |
} catch { | |
case e: Exception => sendMessage(msg.channel, e.getMessage) | |
} | |
} | |
case Cmd("&" :: m :: Nil) => | |
val src = """ | |
var http = require('http'); | |
http.createServer(function (req, res) { | |
res.writeHead(200, {'Content-Type': 'text/plain'}); | |
var a = (""" + m + """) + ""; | |
res.end(a); | |
}).listen(); | |
""" | |
respondJSON((:/("jsapp.us") / "ajax" << compact(render( ("actions", List(("action", "test") ~ ("code", src) ~ ("randToken", "3901") ~ ("fileName", ""))) ~ ("user", "null") ~ ("token", "null"))))) { | |
case JObject(JField("user", JNull) :: JField("data", JArray(JString(data) :: Nil)) :: Nil) => var s: String = ""; (new Http with NoLogging)(url(data) >- {source => s = source}); Some(s) | |
case e => Some("unexpected: " + e) | |
} | |
case Cmd("^" :: m :: Nil) => respondJSON2(:/("try-python.appspot.com") / "json" << compact(render( ("method", "exec") ~ ("params", List(pythonSession, m)) ~ ("id" -> "null") )), | |
:/("try-python.appspot.com") / "json" << compact(render( ("method", "start_session") ~ ("params", List[String]()) ~ ("id" -> "null") ))) { | |
case JObject(JField("error", JNull) :: JField("id" , JString("null")) :: JField("result", JObject(JField("text", JString(result)) :: _)) :: Nil) => Some(result) | |
case e => Some("unexpected: " + e) | |
} { | |
case JObject(_ :: _ :: JField("result" , JString(session)) :: Nil) => pythonSession = session; None | |
case e => None | |
} | |
case Cmd("##" :: m :: Nil) => respondJSON(:/("groovyconsole.appspot.com") / "executor.groovy" <<? Map("script" -> m), true) { | |
case JObject(JField("executionResult", JString(result)) :: JField("outputText", JString(output)) :: JField("stacktraceText", JString("")) :: Nil) => Some(result.trim + "\n" + output.trim) | |
case JObject(JField("executionResult", JString("")) :: JField("outputText", JString("")) :: JField("stacktraceText", JString(err)) :: Nil) => Some(err) | |
case e => Some("unexpected" + e) | |
} | |
case m if (m.startsWith("@") || m.startsWith(">") || m.startsWith("?")) && m.trim.length > 1 && !LAMBDABOTIGNORE.contains(msg.channel) => | |
lastChannel = Some(msg.channel) | |
sendMessage(LAMBDABOT, m) | |
case _ => | |
} | |
val cookies = scala.collection.mutable.Map[String, String]() | |
def respondJSON(req: Request, join: Boolean = false)(response: JValue => Option[String])(implicit msg: Msg) = respond(req, join){line => response(JsonParser.parse(line))} | |
def respondJSON2(req: Request, init: Request)(response: JValue => Option[String])(initResponse: JValue => Option[String])(implicit msg: Msg) = try { | |
respond(req){line => response(JsonParser.parse(line))} | |
} catch { | |
case _ => | |
respond(init){line => initResponse(JsonParser.parse(line))} | |
respond(req){line => response(JsonParser.parse(line))} | |
} | |
def respond(req: Request, join: Boolean = false)(response: String => Option[String])(implicit msg: Msg) = { | |
val Msg(channel, sender, login, hostname, message) = msg | |
val host = req.host | |
val request = cookies.get(channel + host) map (c => req <:< Map("Cookie" -> c)) getOrElse req | |
// val request = cookies.get(channel + host) map (c => req <:< Map("Cookie" -> c)) getOrElse { | |
// val req2 = req >:> { headers => println(headers.get("Set-Cookie"))} | |
// (new Http)(req2) | |
// req | |
// } | |
val handler = request >+> { r => | |
r >:> { headers => headers.get("Set-Cookie").foreach(h => h.foreach(c => cookies(channel + host) = c.split(";").head)) | |
r >~ { source => | |
val lines = source.getLines.take(NUMLINES) | |
(if (join) List(lines.mkString) else lines).foreach(line => response(line).foreach(l => l.split("\n").take(INNUMLINES).foreach(ml => sendMessage(channel, ml)))) | |
}}} // non empty lines | |
(new Http with NoLogging)(handler) // with thread.Safety | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for the info! (and for scalex primarily). Now the bot queries json using for{} so the order doesn't matter, Just created a repo: https://github.com/lopex/multibot.