Minecraft(我的世界)中文论坛

 找回密码
 注册(register)

!header_login!

只需一步,立刻登录

查看: 1968|回复: 5

[插件开发讨论] 在插件中同时使用 PlaceholderAPI 和 JavaScript

[复制链接]
土球球 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
13808
钻石
性别
保密
注册时间
2015-8-23
查看详细资料
发表于 2018-9-8 08:00:58 | 显示全部楼层 |阅读模式

您尚未登录,立即登录享受更好的浏览体验!

您需要 登录 才可以下载或查看,没有帐号?注册(register)

x
本帖最后由 ustc_zzzz 于 2018-9-8 08:00 编辑

在插件中同时使用 PlaceholderAPI 和 JavaScript

这篇文章的第一个主角,PlaceholderAPI,算是插件开发平台非常常用的插件了,它可以针对特定玩家,将文字的一部分映射到不同的字符串上。至于第二个主角,JavaScript,也是插件开发者特别喜欢使用的脚本语言,因为它易于上手,几乎不需要插件使用者多少学习成本。如果说具体实现的话,Java 8 也内置了名为 nashorn 的 JavaScript 引擎,大大方便了开发者调用执行。

很多插件喜欢把 PlaceholderAPI 和 JavaScript 结合。JavaScript 本身在 Java 平台支持预编译,也就是将 JavaScript 代码解析编译后再执行,虽然会略微拖慢加载插件的速度,但能提升 JavaScript 代码,尤其是反复执行的 JavaScript 代码的执行效率,因此也值得去做。不过本人发现很少有插件同时使用 PlaceholderAPI 和预编译 JavaScript,因此本人在这里把自己同时使用这两者的一点点经验分享一下。这里本人再针对常见的一些实现方式说一说可能出现的小问题。

本文不针对任何插件平台,因此接下来出现的 Java 代码使用以下约定:

  • papi("xxxxxx", player)
    代表针对“player”返回“%xxxxxx%”对应的值。
  • escape(string)
    代表将“string”转义,通常的操作包括“'”替换成“\'”,“\”替换成“\\”等。
  • pattern
    代表匹配 PlaceholderAPI 的正则表达式,比如说可以是“Pattern.compile("[%]([^ %]+)[%]")”。
  • engine
    代表 JavaScript 引擎,nashorn 可通过“new ScriptEngineManager(null).getEngineByName("nashorn")”得到。

第一步:直接替换

一个最常见的做法是直接替换,比如这样:

  1. StringBuffer sb = new StringBuffer();
  2. Matcher m = pattern.matcher("%player_name% === 'Notch'");

  3. while (m.find()) m.appendReplacement(sb, "'" + papi(m.group(1), player) + "'");
  4. Object result = engine.eval(sb.toString());
复制代码

这种做法就是直接替换了,在这里的话,也就是把“%player_name%”直接替换成玩家名字,比如说“Dinnerbone”,然后整个表达式变成了“'Dinnerbone' === 'Notch'”,直接执行就可以比较了。有的插件替换时甚至不会加上两边的引号,由配置文件的编辑者自己写成诸如“'%player_name%' === 'Notch'”的形式。

但这显然有一个问题:不安全。比如说,如果我把我的名字设置成:“xxxx', player.setOp(true), 'xxxx”,那最后表达式就变成了“'xxxx', player.setOp(true), 'xxxx' === 'Notch'”。这段表达式可以正常执行,但是它执行的时候,就把目标玩家设置成 OP 了。

这种攻击有一个成形的术语,被称作“代码注入”(Code Injection)。当然了,玩家名字不会出现单引号以及括号等奇怪的东西,但是世界上有成百上千种 PlaceholderAPI 呢,开发者有足够的勇气,保证所有的 PlaceholderAPI 都不会被玩家恶意利用吗?

第二步:转义

这种问题的解决方案自然是转义(Escaping):

  1. StringBuffer sb = new StringBuffer();
  2. Matcher m = pattern.matcher("%player_name% === 'Notch'");

  3. while (m.find()) m.appendReplacement(sb, "'" + escape(papi(m.group(1), player)) + "'");
  4. Object result = engine.eval(sb.toString());
复制代码

现在的话,如果我把我的名字设置成了:“xxxx', player.setOp(true), 'xxxx”,那么在处理后,表达式将会是:“'xxxx\', player.setOp(true), \'xxxx' === 'Notch'”。由于中间的两个引号进行了转义,因此即使是这种名字,我也不需要担心玩家会恶意利用某个 PlaceholderAPI 执行我不想执行的代码了。

我们现在回到预编译 JavaScript 的问题。我们注意到,如果我们想要执行这段代码,我们需要将代码用 PlaceholderAPI 处理一遍,而由于玩家不同,因此如果采用这样的写法,我们根本无法预编译,再在使用的时候执行以提高效率。

第三步:暴露接口

办法自然是有的。我们完全可以把根据 PlaceholderAPI 替换的接口抽象出来,然后执行的时候提供不同的实现就可以了:

  1. StringBuffer sb = new StringBuffer();
  2. SimpleScriptContext context = new SimpleScriptContext();
  3. Matcher m = pattern.matcher("%player_name% === 'Notch'");

  4. while (m.find()) m.appendReplacement(sb, "papi('" + escape(m.group(1)) + "')");
  5. CompiledScript compiled = engine.compile(sb.toString());

  6. // end of compilation
  7. // --------------------------------
  8. // beginning of execution

  9. Function<String, Object> papiFunction = string -> papi(string, player);
  10. context.setAttribute("papi", papiFunction, ScriptContext.ENGINE_SCOPE);
  11. Object result = compiled.eval(context);
复制代码

我们在这里,把“%player_name% === 'Notch'”转换成了“papi('player_name') === 'Notch'”,将替换的接口抽象到了一个名为“papi”的方法。接着我们初始化了一个“SimpleScriptContext”,并在执行代码前动态设置它对应的“papi”的值是某个 Lambda 表达式。藉由这种方式,我们得以把预编译 JavaScript 的过程放在加载插件的时候完成,而对于执行代码来说,我们只需要事先设置一下某个全局量的值,而无需每次解析 JavaScript 代码。

总结

这里大概整理了一下本人同时使用 PlaceholderAPI 和预编译 JavaScript 的一点经验,因此文章也没指望写太长,也算不上是一篇教程。希望这篇文章能够对类似需求的人有一定帮助。

转载请联系本人。

Markdown 备份

整个主题帖使用 Markdown 编写,并使用相关工具转换为 BBCode。该部分内容为备份,和主题正文无关。

评分

参与人数 9人气 +18 金粒 +193 绿宝石 +10 收起 理由
Tds... + 2 MCBBS有你更精彩~
乙烯_中国 + 10 MCBBS有你更精彩~
Arasple + 2 + 40 MCBBS有你更精彩~
+ 3 + 20 MCBBS有你更精彩~
海螺螺 + 2 + 50 土球发帖无脑吹就是了
gooding300 + 3 + 33 MCBBS有你更精彩~
森林蝙蝠 + 2 + 40 神乎其技,不服不行!
GiNYAi + 2 MCBBS有你更精彩~
602723113 + 2 + 10 彩色皮球

查看全部评分

晓梦陌爱 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
38
钻石
性别
保密
注册时间
2018-9-8
查看详细资料
发表于 2018-9-10 21:07:48 | 显示全部楼层
MCBBS有你更精彩~
回复

使用道具 举报

缤纷的烟火 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
2131
钻石
性别
保密
注册时间
2017-4-6
查看详细资料
发表于 2018-9-11 18:27:45 | 显示全部楼层
支持楼主大佬
回复

使用道具 举报

一次性手套 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
70
钻石
性别
保密
注册时间
2020-3-22
查看详细资料
发表于 2020-3-23 00:42:51 来自手机 | 显示全部楼层
66666666666
回复

使用道具 举报

寂弈will 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
59
钻石
性别
保密
注册时间
2020-3-17
查看详细资料
发表于 2020-3-23 08:27:36 来自手机 | 显示全部楼层
可以的呀
回复

使用道具 举报

doushididi 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
38
钻石
性别
保密
注册时间
2020-3-23
查看详细资料
发表于 2020-3-23 14:46:43 来自手机 | 显示全部楼层
支持楼主好厉害的样子
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册(register)

本版积分规则

Archiver|小黑屋|Mcbbs.net ( 京ICP备15023768号-1 ) | 京公网安备 11010502037624号 | 手机版

GMT+8, 2020-7-6 11:05 , Processed in 0.054463 second(s), Total 18, Slave 16 queries, Release: Build.2020.06.30.1110, Gzip On, Redis On.

"Minecraft"以及"我的世界"为Mojang Synergies AB的商标 本站与Mojang以及微软公司没有从属关系

© 2010-2020 我的世界中文论坛 版权所有 本站原创图文内容版权属于原创作者,未经许可不得转载

快速回复 返回顶部 返回列表