2.2 使用encode()进行编码

在自学时本段主要参考对象为Lua利用cjson读写json使用Lua CJSON库进行encode与decode操作完成对Json数据转化,特此表达感谢。
与使用decode()时相同,首先需要调用json.lua

json = require("json")

然后利用自己建好的表的名称,例如我在上一篇中使用的是usagi
并将其存储在一个变量中:

json_text = json.encode(usagi)

返回的内容是:{"A":"abc","B":"def","C":["123","456"]}
等等,好像有哪里不对!!
噔 噔 咚
很明显,我忘记多嵌套一层了……散落在外的键像极了我破碎的心
(这段不是节目效果)

是的,最外层的表的名字不会被写入,而是直接读取表的所有索引与对应元素,并写成JSON数据,并作为字符串输出。
因此如果需要让它在写进JSON时也多套一层娃,正确的写法是这样的:

j= {}
j["usagi"] = {}
j["usagi"]["A"] = "abc"
j["usagi"]["B"] = "def"
j["usagi"]["C"] = {}
j["usagi"]["C"][1] = "123"
j["usagi"]["C"][2] = "456"
json = require("json")
json_text = json.encode(j)
return json_text

这样返回的东西就都储存于键usagi的值里了:{"usagi":{"C":["123","456"],"B":"def","A":"abc"}}
JSON编码时会随机打乱顺序,但不影响文件读取与路径

2.3 编写表时需要注意的逻辑

在这一章节中我将以一个正在进行中的脚本为例,来解释一些容易碰到的问题。
这是一个我想要做的脚本,首先我需要将想要达成的要求列成一个表(略去了部分不需要存储的内容):

  • 想要可以收到留言。留言内容就跟在指令的后面
  • 收到留言的同时,还想获取用户的QQ号、所在群号等信息
  • 想要让这些留言可以在脚本执行时随机抽取其中一条并发送
  • 由于这个指令每天限1次,肯定抽取的比发出的多,所以想让留言被抽取到一定次数后再销毁,而非阅后即焚
  • 但是有些留言我也想永久保存,大不了通过后台改

再对需求表进行细化,确认至少需要以下这些键用来存储值:

  • 收到的留言
  • QQ号、群号、用户名、群名
  • 一整个容器,用来容纳这些内容并随机抽取
  • 一个用来存储留言被抽取次数的变量
  • 一个用来记录是否需要让留言永久保存的变量

2.3.1 我应该怎么选择元素的类型?

首先还是列一下元素:

同时考虑到JSON与Lua时,元素中可用的数据类型:<我 引 用 我 自 己>
  • 字符串(string)——用""[[]]括起的内容
  • 数字(number)——就是数字,不需要被括起。如果添加了括号,它将变为字符串并失去数字的特性(数学计算等)
  • 布尔值(boolean)——TRUE和FALSE
  • 另一个表(table)或数组(array)——然后每个表里面又可以套一个表,每个表里面又可以…(下略)
  • nil——不完全对应JSON中的null。表示一个无效值,较少用到,通常用来移除表中的值。

那么,很明显,留言、昵称、群名称,肯定都是字符串形式的。
其次,QQ号与群号,由于不需要进行数学运算,所以也是字符串形式的。(实际上这些内容,即msg.fromQQmsg.fromGroup,本来就是字符串)
一个需要随机调用的容器?用ranint(min,max)数组解决吧!使用数组+随机数字作为索引值,刚好符合要求。
存储留言被抽取多少次的变量,因为需要进行简单的数学运算,所以用数字
对于识别留言永久保存与否,虽然可以用字符串或者数字强行匹配,不过肯定是用布尔值最方便。

——这样就对目标变量有了一个简单的心理认知,可以进行下一步了。

*特别备注:ranint(min,max)是Dice!预置的Lua函数之一,并不普遍地存在于其他不使用Dice!框架运行的Lua脚本中。

2.3.2 我应该如何将想要输入的元素归类?

有很多种归类的方法,不过我这里的话,由于已经确定了数组作为主要内容,因此大体框架肯定是这样的:

{"random_name":[{被省略的元素1},{被省略的元素2},{被省略的元素3}]}

这样如果我使用如下的方式:

num = ranint(1,#random_name)
xxx = random_name[num].xxx

就可以很方便的访问数组并调用想要的值了。
其他的内容由于并没有特别的包含关系,所以一股脑地塞进元素里也是可以的。

分类通常遵循范围上从大到小的原则,无论是地理或是其他方面。
如果不嫌瞎眼的,可以参考新冠疫情信息,其中有明显的省级-地级的排序。
(这个api分享自タブー術的【指令脚本】以一个用http函数调用api示例脚本的做的新脚本,感谢主动分享!)
(另外,此api返回信息量极大,即使是放进格式化工具也会卡顿好一会。但这种情况下更加推荐此工具,你可以在右边的树形结构图中更直观地看到以地区为主的分类规则,位置是list[n].city[n])

同时你也可以遵循重要度从高到低的排法。
例如,我想写一个新的脚本,一个用户会有很多不同的发言,我也可以将这些发言都包括在以用户QQ号为键的值内。
*注意这种情况下最好给索引取类似于qq12345678的字符串,而非使用纯数字,以免出现意想不到的错误。

三、额外内容-关于文件I/O的写法

很多用户在使用带有require的脚本时出现了这样的错误:
aaa是我自己做的函数库名称。它真的叫aaa……)

骰娘运行lua文件G:\Dice - 副本\Dice3306860448\plugin\测试.lua失败:G:\Dice - 副本\Dice3306860448\plugin\测试.lua:3: module 'aaa' not found:
	no field package.preload['aaa']
	no file 'G:\Dice - 鍓湰\Dice3306860448\plugin\aaa.lua'
	no file 'G:\Dice - 鍓湰\Dice3306860448\plugin\aaa\init.lua'
	no file 'G:\Dice - 鍓湰\Diceki\lua\aaa.lua'
	no file 'G:\Dice - 鍓湰\Diceki\lua\aaa\init.lua'
	no file 'C:\Program Files\Java\jdk-17.0.1\bin\lua\aaa.lua'
	no file 'C:\Program Files\Java\jdk-17.0.1\bin\lua\aaa\init.lua'
	no file 'C:\Program Files\Java\jdk-17.0.1\bin\aaa.lua'
	no file 'C:\Program Files\Java\jdk-17.0.1\bin\aaa\init.lua'
	no file 'C:\Program Files\Java\jdk-17.0.1\bin\..\share\lua\5.4\aaa.lua'
	no file 'C:\Program Files\Java\jdk-17.0.1\bin\..\share\lua\5.4\aaa\init.lua'
	no file '.\aaa.lua'
	no file '.\aaa\init.lua'
	no file 'G:\Dice - 鍓湰\Diceki\lua\aaa.dll'
	no file 'G:\Dice - 鍓湰\Diceki\lib\aaa.dll'
	no file 'C:\Program Files\Java\jdk-17.0.1\bin\aaa.dll'
	no file 'C:\Program Files\Java\jdk-17.0.1\bin\..\lib\lua\5.4\aaa.dll'
	no file 'C:\Program Files\Java\jdk-17.0.1\bin\loadall.dll'
	no file '.\aaa.dll'

或者是在有文件I/O的脚本中,出现这样的问题:

骰娘调用G:\Dice - 副本\Dice3306860448\plugin\DriftingBottles.lua函数throw_bottle失败!
G:\Dice - 副本\Dice3306860448\plugin\DriftingBottles.lua:103: attempt to index a nil value (global 'file')

查看用户手册可以发现这样一条内容。

lua文件的字符编码问题 <来源>

Windows系统一般使用GBK字符集。Dice!支持utf-8及GBK两种字符集的lua文件,在读写字符串时将自动检测utf-8编码并转换。而出现以下情况时,编码并非二者皆可:
 

  • lua文件相互调用或读写其他文本文件,且字符串含有非ASCII字符时,关联文件字符集应保持一致
  • lua文件使用require或os等以文件名为参数的函数,且路径含有非ASCII字符时,必须使用GBK

出现上述错误的原因就是使用了UTF-8编码的脚本而自己的文件路径含有非ASCII字符,例如中文。
文件I/O也会出现同样的状况,是因为它调用的路径通常是以骰娘的getDiceDir()开始的。
所以路径和编码总得妥协一个。尤其是在json.lua本身也是UTF-8编码的、并且安装新脚本也需要和它编码一致的情况下,与其将所有脚本都转码为GBK,还不如干脆将骰娘文件夹移动到纯英文路径。

文件的I/O是涉及脚本本体外的内容的一种形式。它会在指定的路径创建并读写一个文档,通常来说使用txt就足够了。文末会放出读写文件时常用的三个函数,有需要的可以直接复制进相关脚本中。

需要注意的是,在利用含JSON的脚本时,第一次读取的文件无法直接交给json.lua解码,因为文档里空空荡荡什么都没有,甚至可能连文档都没有。
因此推荐读文件时添加一个类似于初始化的条件判定。可以利用read_file()读取空文件返回零长字符串的特点加以判定,若满足条件则不进行json.decode()的工作。

于是,在文件为空的情况下,如果我想实现前面提到的记录用户留言及其他信息,我必须这么做:

function ttbd_write(message,user_qq)
	local letter = read_file(ttbd_path) -- 读取
	json = require("json")
	if #letter==0 then -- 如果是空的
		j = {} -- 初始化表“j”
		j.usagi = {} -- 初始化数组“j.usagi”
		num = 1 -- 数组索引为1
	else
		j = json.decode(letter) -- 否则进行解码
		num = #j.usagi + 1 -- 数组索引为比原有数组的数字多1
	end
	j.usagi[num] = {} -- 初始化所在数组索引的表
	j.usagi[num].message = message -- 进行正常的存表内容工作
	j.usagi[num].name = getUserConf(user_qq,"nick","")
	j.usagi[num].qq = user_qq -- 最后放弃了记录群号的想法,因为意义不大
	j.usagi[num].times = 0
	j.usagi[num].isPermanent = false
	letter_full = json.encode(j) -- 编码
	overwrite_file(ttbd_path,letter_full) -- 覆写文件,由于JSON有特定格式因此无法使用追加写入
end

这样在没有文件的情况下,程序就会跳过解码,直接创建指定文件夹路径和文本文档。
我向骰娘添加的留言是喵喵喵,因此会写入如下内容:

{"usagi":[{"qq":"1142145792","name":"兔兔零号机","times":0,"message":"喵喵喵","isPermanent":false}]}

而在第二次及以上留言时,则会正常的进行解码-添加新表-编码的过程,再覆写文件。第二次我的留言是汪汪汪,文档内容会被修改为如下:

{"usagi":[{"times":1,"isPermanent":false,"qq":"1142145792","message":"喵喵喵","name":"兔兔零号机"},{"times":0,"isPermanent":false,"qq":"1142145792","message":"汪汪汪","name":"兔兔零号机"}]}

其中喵喵喵这一条已经被我写好的读取代码调用了一次,因此可以看见它的times值有增加。

以下为文件读、写、覆写函数,可直接复制使用:

-- 用于读文件,参数为文件路径
function read_file(path)
    local text = ""
    local file = io.open(path, "r") -- 打开了文件读写路径,以读取的方式
    if (file ~= nil) then -- 如果文件不是空的
        text = file.read(file, "*a") -- 读取内容
        io.close(file) -- 关闭文件
    end 
    return text -- 返回读取的内容
end

-- 用于写文件,参数为路径和需要写入的文本
function write_file(path, text)
	file = io.open(path, "a") -- 以追加的方式
    file.write(file, text) -- 写入内容
    io.close(file) -- 关闭文件
end

-- 用于覆写文件,参数与写文件相同
function overwrite_file(path, text)
	file = io.open(path, "w") -- 以只写的方式,会将原内容清空后写
	file.write(file, text)
	io.close(file)
end

四、常见报错信息及解决办法

同时推荐查看Dice!文档的附录:lua报错信息说明。这里只列举在该教程中会出现的报错信息并加以解释。

骰娘运行lua文件G:\Dice - 副本\Dice3306860448\plugin\测试.lua失败:G:\Dice - 副本\Dice3306860448\plugin\测试.lua:3: module 'aaa' not found:
	no field package.preload['aaa']
	no file 'G:\Dice - 鍓湰\Dice3306860448\plugin\aaa.lua'
	no file 'G:\Dice - 鍓湰\Dice3306860448\plugin\aaa\init.lua'
	...
	(下略)

产生原因:①UTF-8编码且存在requireloadLua的脚本,在含非ASCII字符集的路径中运行,见上一条
     ②放在了读取不到的地方,例如plugin\test\aaa.lua。这种情况不一定会产生如上的乱码。
解决办法:①将脚本全部转码或将骰娘转移至ASCII字符集的路径(可以简单理解为英文+数字+键盘上打得出的大部分英文符号)
     ②修改package.path,具体见->RainChain的回帖(非常感谢补充)
 

骰娘调用G:\Dice - 副本\Dice3306860448\plugin\DriftingBottles.lua函数throw_bottle失败!
G:\Dice - 副本\Dice3306860448\plugin\DriftingBottles.lua:103: attempt to index a nil value (global 'file')

产生原因:明明已经给file变量赋值了以getDiceDir()开头的路径,但是UTF-8编码的脚本在含非ASCII字符集的路径中运行,导致file变量未能正常写入,变成了nil
解决办法:如果只存在文件读写可以考虑转码,但更推荐修改骰娘的路径
 

骰娘调用G:\Dice\Dice3306860448\plugin\签到.lua函数ttbd失败!
G:\Dice\Dice3306860448\plugin\json.lua:184: unexpected character '' at line 1 col 1

注:上述文本中,''之间应当有一个符号,为黑色菱形中间带一个问号。因为实际上代表空字符所以可能在复制后消失了
产生原因:可能是让json.lua解码了nil内容
解决办法:重新看一下是不是脚本中的初始化部分没做对,或者在其前面加一个条件判断使其不进行解码
 

骰娘调用G:\Dice\Dice3306860448\plugin\签到.lua函数ttbd失败!
G:\Dice\Dice3306860448\plugin\签到.lua:29: attempt to index a nil value (global 'j')

产生原因:这一行的内容是j.usagi = {}。试图对空变量j使用索引
解决办法:声明j。在出现问题的行前面添加j = {}

五、结语

我可不可以在这里碎碎念点什么,大概行吧反正是我自己的东西我爱怎么写怎么写(?
总之,首先,感谢你一路看到这里。第一次写教程,也不知道写了多少字,感觉比写期末论文还长……能忍受我这么啰嗦的语言看到最后,真的辛苦你了(
至此应该是将能教的都教了,如果有没涉及到的地方,希望能够获得反馈。

另外,感谢タブー術对该教程结构方面的指点,还有Text_Koaku当了一期小白鼠(话说您完全不用论坛是吗)(笑死,地址忘了)

参考资料:
Lua 变量 | 菜鸟教程
Lua 字符串 | 菜鸟教程
Lua 数组 | 菜鸟教程
Lua table(表) | 菜鸟教程
Lua 文件I/O | 菜鸟教程
JSON 教程 | 菜鸟教程
JSON 语法 | 菜鸟教程
JSON 对象 | 菜鸟教程
JSON 数组 | 菜鸟教程
Lua利用cjson读写json - KAME - 博客园
使用Lua CJSON库进行encode与decode操作完成对Json数据转化 - echo111333 - 博客园
在线工具:
JSON在线解析 | 菜鸟工具
Lua在线工具 | 菜鸟工具

——碎碎念呢?
——想了想还是不写了,就祝大家虎年大吉吧。
然后,某人似乎打算把漂流瓶利用JSON重制一遍,期待ta的表现~
但为什么最后做漂流瓶重制版的人换了一个!某人呢某人呢?!

hhhh新冠疫情查询,我还没做完有空这周就继续写

认真读完了,写得很好,甚至比当时学JavaScript时看的教程还详细,泪目了w。
但还是厚着脸皮提两个问题吧(

一、setUserConf函数已经可以支持读写字符串√以及lua表(似乎在更新中看到了),但setUserToday还是只能读写number。

二、关于使用require后出现报错的补充

路径中使用了UTF-8编码的确会导致报错,但还可能是package.path的问题。
现在骰娘plugin文件下有一个test文件夹,内含tutuhaobang.lua(兔 兔 好 棒!!)
现在在插件中使用require "tutuhaobang"将同样会报错。

解决方法为修改package.path
package.path = getDiceDir() .. "/plugin/test/?.lua"
require "tutuhaobang"

注:require "tutuhaobang"改成require "tutuhaobang.lua"将会报错,因为package.path中使用了?占位符。
以及,2.6.1后Dice!优化了require路径,默认读取Diceki/lua/文件夹下的模块了。
这意味着,要读取Dice!自配的json.lua后读取其他文件夹下的模块,先进行require "json"后修改package.path即可。

    RainChain
    收到——这就去添加
    非常感谢呜呜
    仔细一想都能用getUserConf获取字符串了凭什么setUserConf就不行呢我脑子怎么长的(

    然后我也不是计算机专业出身的,只是很喜欢编程而已,但很多时候编程的门槛比想象中的要高了那么亿点点,所以就想着,写个教程总结经验同时也方便你我吧
    顺便光明正大的嫖更多人的脚本

      感恩好东西!!!正好用来检查一下脚本
      想悄咪咪地问下http.post函数要怎么用呢,想调用一些请求方式是post的api【跪地

        aphonic 那个我因为没有需求所以还没开始研究(
        要不你去骰主群之类的地方问问其他大佬

        2 个月 后

        更新了五个脚本收录和一个在技术交流区导致漏了的脚本收录(点头)
        总之感谢提醒,之前真的忘了(

        18 天 后
        4 个月 后

        更新了5个脚本收录
        距离上次更新脚本收录已经过了两百个左右的帖子编号(11xx-13xx),由此可见我有多懒狗

        另外还更新了部分帖子内的细节:

        • 删除了几个跑路的api网址
        • 更新了lolicon api的网址到新版本
        • 更新了备注和一些美化润色
        • 把一楼的格式参考帖的作者从溯洄改为了Hikari Sakurai,首次编写时可能是看错了

        论坛上斜体看得不是很清楚,不过也没什么比加粗的程度稍微轻一些还不影响观感的方式,讲真,如果有下划线的话我一定会选择下划线的

        api这种东西确实还是不稳定啊,但是作为轻量级语言来说利用各大api是最便利的增加自己多样性的办法……但是寻找api这种活就只能靠自己了

        7 天 后

        aphonic

        local json=require('json')
        local para={}
        para[key]=val
        _, data=http.post(url,json.encode(para))
        return data
        说点什么吧...