import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptException;
import jdk.nashorn.api.scripting.NashornScriptEngine;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import jdk.nashorn.internal.lookup.Lookup;
import jdk.nashorn.internal.objects.Global;
import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.Property;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
import org.pircbotx.Channel;
import org.pircbotx.PircBotX;
import org.pircbotx.User;
import pl.shockah.Reflection;
import pl.shockah.shocky.Cache;
import pl.shockah.shocky.ScriptModule;
import pl.shockah.shocky.Utils;
import pl.shockah.shocky.cmds.Command;
import pl.shockah.shocky.cmds.Parameters;
import pl.shockah.shocky.js.ShockyScriptFunction;
import pl.shockah.shocky.sql.Factoid;
import pl.shockah.shocky.threads.SandboxThreadFactory;
import pl.shockah.shocky.threads.SandboxThreadGroup;
public class ModuleJavaScript extends ScriptModule {
protected Command cmd;
private final SandboxThreadGroup sandboxGroup = new SandboxThreadGroup("javascript");
private final ThreadFactory sandboxFactory = new SandboxThreadFactory(sandboxGroup);
private NashornScriptEngineFactory engineFactory;
private final Field globalField = Reflection.getPrivateField(ScriptObjectMirror.class, "sobj");
private final StringWrapper MUNGE = new StringWrapper(Lookup.MH.findStatic(MethodHandles.lookup(), Utils.class, "mungeNick", Lookup.MH.type(String.class, CharSequence.class)));
private final StringWrapper ODD = new StringWrapper(Lookup.MH.findStatic(MethodHandles.lookup(), Utils.class, "odd", Lookup.MH.type(String.class, CharSequence.class)));
private final StringWrapper FLIP = new StringWrapper(Lookup.MH.findStatic(MethodHandles.lookup(), Utils.class, "flip", Lookup.MH.type(String.class, CharSequence.class)));
private final StringWrapper PASTE = new StringWrapper(Lookup.MH.findStatic(MethodHandles.lookup(), Utils.class, "paste", Lookup.MH.type(String.class, CharSequence.class)));
public String name() {return "javascript";}
public String identifier() {return "js";}
public void onEnable(File dir) {
Command.addCommands(this, cmd = new CmdJavascript());
Command.addCommand(this, "js",cmd);
engineFactory = new NashornScriptEngineFactory();
}
public void onDisable() {
Command.removeCommands(cmd);
engineFactory = null;
}
public synchronized String parse(Cache cache, final PircBotX bot, Channel channel, User sender, Factoid factoid, String code, String message) {
if (engineFactory == null || code == null) return "";
NashornScriptEngine engine = (NashornScriptEngine)engineFactory.getScriptEngine(new String[] {"-strict", "--no-java", "--no-syntax-extensions"});
Global global = null;
try {
global = (Global) globalField.get(engine.getBindings(100));
} catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
e.printStackTrace();
}
ScriptFunction string = (ScriptFunction)global.string;
ScriptObject obj = (ScriptObject)string.getPrototype();
obj.addOwnProperty(MUNGE.makeProperty("munge", global));
obj.addOwnProperty(ODD.makeProperty("odd", global));
obj.addOwnProperty(FLIP.makeProperty("flip", global));
obj.addOwnProperty(PASTE.makeProperty("paste", global));
Map<String,Object> params = getParams(bot, channel, sender, message, factoid);
for (Map.Entry<String,Object> pair : params.entrySet())
engine.put(pair.getKey(),pair.getValue());
return eval(engine, code);
}
public String eval(NashornScriptEngine engine, String code) {
CompiledScript cs;
try {
cs = engine.compile(code);
} catch (ScriptException e) {
return e.getMessage();
}
JSRunner r = new JSRunner(cs);
final ExecutorService service = Executors.newSingleThreadExecutor(sandboxFactory);
TimeUnit unit = TimeUnit.SECONDS;
String output = null;
try {
Future<String> f = service.submit(r);
output = f.get(30, unit);
}
catch(TimeoutException e) {
output = "Script timed out";
}
catch(Exception e) {
e.printStackTrace();
output = e.getMessage();
}
finally {
service.shutdown();
}
if (output == null)
output = "";
return output;
}
public class CmdJavascript extends ScriptCommand {
public String command() {return "javascript";}
public String help(Parameters params) {
return "javascript/js\njavascript {code} - runs JavaScript code";
}
}
public class JSRunner implements Callable<String> {
private final CompiledScript cs;
public JSRunner(CompiledScript cs) {
this.cs = cs;
}
@Override
public String call() throws Exception {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ScriptContext context = cs.getEngine().getContext();
context.setWriter(pw);
context.setErrorWriter(pw);
try {
Object out = cs.eval();
if (sw.getBuffer().length() != 0)
return sw.toString();
if (out != null)
return out.toString();
}
catch(ScriptException ex) {
return ex.getMessage();
}
return null;
}
}
public static class StringWrapper {
private static final MethodHandle run = Lookup.MH.findVirtual(MethodHandles.lookup(), StringWrapper.class, "run", Lookup.MH.type(Object.class, Object.class, Object.class));
private final MethodHandle handle = run.bindTo(this);
private MethodHandle method;
public StringWrapper(MethodHandle method) {
this.method = method;
}
public Object run(Object self, Object s) throws Throwable {
return method.invoke(JSType.toString(JSType.nullOrUndefined(self) ? s : self));
}
public Property makeProperty(String name, Global global) {
return new ShockyScriptFunction(name, handle, global, null, 0).makeProperty(Property.NOT_WRITABLE | Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE, false).getProperty();
}
}
}