常见用法示例
创建和注册扩展
在海豹中,所有指令必须归属于某个扩展,而一个扩展可以包含多条指令。
JS 脚本中创建扩展的方式如下,在创建扩展后,还需要注册扩展,才能让扩展起效,不要漏掉哦!
// 如何建立一个扩展
// 首先检查是否已经存在
if (!seal.ext.find('test')) {
// 不存在,那么建立扩展,名为 test,作者“木落”,版本 1.0.0
const ext = seal.ext.new('test', '木落', '1.0.0');
// 注册扩展
seal.ext.register(ext);
}注意:JS 脚本和扩展
在实现上,海豹允许你在一个 JS 脚本文件中注册多个扩展,但我们不建议这样做。一般来说,一个 JS 脚本文件只会注册一个扩展。
注意:修改内置功能?
出于对公平性的考虑,JS 脚本不能替换内置指令和内置扩展。
自定义指令
想要创建一条自定义指令,首先需要创建一个扩展(seal.ExtInfo),写好自定义指令的实现逻辑之后,再注册到扩展中。
创建指令
接上面的代码,假定目前已经注册好了一个名为 test 的扩展,现在要写一个名为 seal 的指令:
- 这个命令的功能为,显示「抓到一只海豹」的文案;
- 如果命令写
.seal ABC,那么文案中将海豹命名为「ABC」; - 如果命令中没写名字,那么命名为默认值「氪豹」。
- 如果命令写
第一步,创建新的自定义指令,设置好名字和帮助信息。
const cmdSeal = seal.ext.newCmdItemInfo();
cmdSeal.name = 'seal'; // 指令名字,可用中文
cmdSeal.help = '召唤一只海豹,可用 .seal <名字> 命名';注意:命令的帮助信息
命令的帮助信息是在使用 .help <命令> 时会返回的帮助内容。
帮助信息不要以 . 开头,防止查看帮助时骰子的回复触发其他骰子。
第二步,编写指令的具体处理代码。
你需要编写指令对象的 solve 函数,在使用该指令的时候,海豹核心会调用你写的这个函数。
cmdSeal.solve = (ctx, msg, cmdArgs) => {
// 这里是你需要编写的内容
};| 参数 | 说明 |
|---|---|
ctx | 主要是和当前环境以及用户相关的内容,如当前发指令用户,当前群组信息等 |
msg | 原始指令内容,如指令文本,发送平台,发送时间等 |
cmdArgs | 指令信息,会将用户发的信息进行分段,方便快速取用 |
这里仅说明需要用到的接口,详细可见 插件仓库 examp_ts 下的 seal.d.ts 文件,里面包含了目前开放的接口的定义及其注释说明。
指令参数与返回值
假设用户发送过来的消息是 .seal A B C,那么可以用 cmdArgs.getArgN(1) 获取到 A,cmdArgs.getArgN(2) 获取到 B,cmdArgs.getArgN(3) 获取到 C。
通常会对参数值进行判断,随后作出响应。
以下代码处理的是 .seal help 的情形:
cmdSeal.solve = (ctx, msg, cmdArgs) => {
// 获取第一个参数,例如 .seal A B C
// 这里 cmdArgs.getArgN(1) 的结果即是 A,传参为 2 的话结果是 B
let val = cmdArgs.getArgN(1);
switch (val) {
case 'help': {
// 命令为 .seal help
// 创建一个结果对象,并将 showHelp 标记为 true,这会自动给用户发送帮助
const ret = seal.ext.newCmdExecuteResult(true);
ret.showHelp = true;
return ret;
}
default: {
// 没有传入参数时的代码
return seal.ext.newCmdExecuteResult(true);
}
}
};注意:返回执行结果
在执行完自己的代码之后,需要返回指令结果对象,其参数是是否执行成功。
指令核心执行流程
给消息发送者回应,需要使用 seal.replyToSender() 函数,前两个参数和 solve() 函数接收的参数一致,第三个参数是你要发送的文本。
发送的文本中,可以包含 变量(例如{$t玩家}),也可以包含 CQ 码,用来实现回复发送者、@发送者、发送图片、发送分享卡片等功能。
在这个例子中,我们需要获取作为海豹名字的参数,获取不到就使用默认值,随后向消息发送者发送回应。
在刚刚的位置填入核心代码,就可以完成了。
cmdSeal.solve = (ctx, msg, cmdArgs) => {
// 获取第一个参数,例如 .seal A B C
// 这里 cmdArgs.getArgN(1) 的结果即是 A,传参为 2 的话结果是 B
let val = cmdArgs.getArgN(1);
switch (val) {
case 'help': {
// 命令为 .seal help
// 创建一个结果对象,并将 showHelp 标记为 true,这会自动给用户发送帮助
const ret = seal.ext.newCmdExecuteResult(true);
ret.showHelp = true;
return ret;
}
default: {
// 命令为 .seal XXXX,取第一个参数为名字
if (!val) val = '氪豹';
// 进行回复,如果是群聊发送那么在群里回复,私聊发送则在私聊回复 (听起来是废话文学,但详细区别见暗骰例子)
seal.replyToSender(ctx, msg, `你抓到一只海豹!取名为${val}\n它的逃跑意愿为${Math.ceil(Math.random() * 100)}`);
return seal.ext.newCmdExecuteResult(true);
}
}
};注册指令
第三步,将命令注册到扩展中。
ext.cmdMap['seal'] = cmdSeal;如果你想要给这个命令起一个别名,也就是增加一个触发词,可以这样写:
ext.cmdMap['seal'] = cmdSeal; // 注册 .seal 指令
ext.cmdMap['海豹'] = cmdSeal; // 注册 .海豹 指令,等效于 .seal完整的代码如下:
// ==UserScript==
// @name 示例:编写一条自定义指令
// @author 木落
// @version 1.0.0
// @description 召唤一只海豹,可用.seal <名字> 命名
// @timestamp 1671368035
// 2022-12-18
// @license Apache-2
// @homepageURL https://github.com/sealdice/javascript
// ==/UserScript==
// 编写一条自定义指令
// 先将扩展模块创建出来,如果已创建就直接使用
let ext = seal.ext.find('test');
if (!ext) {
ext = seal.ext.new('test', '木落', '1.0.0');
// 创建指令 .seal
// 这个命令的功能为,显示「抓到一只海豹」的文案;
// 如果命令写 .seal ABC,那么文案中将海豹命名为「ABC」;
// 如果命令中没写名字,那么命名为默认值「氪豹」。
const cmdSeal = seal.ext.newCmdItemInfo();
cmdSeal.name = 'seal'; // 指令名字,可用中文
cmdSeal.help = '召唤一只海豹,可用 .seal <名字> 命名';
// 主函数,指令解析器会将指令信息解析,并储存在几个参数中
// ctx 主要是和当前环境以及用户相关的内容,如当前发指令用户,当前群组信息等
// msg 为原生态的指令内容,如指令文本,发送平台,发送时间等
// cmdArgs 为指令信息,会将用户发的信息进行分段,方便快速取用
cmdSeal.solve = (ctx, msg, cmdArgs) => {
// 获取第一个参数,例如 .seal A B C
// 这里 cmdArgs.getArgN(1) 的结果即是 A,传参为 2 的话结果是 B
let val = cmdArgs.getArgN(1);
switch (val) {
case 'help': {
// 命令为 .seal help
// 创建一个结果对象,并将 showHelp 标记为 true,这会自动给用户发送帮助
const ret = seal.ext.newCmdExecuteResult(true);
ret.showHelp = true;
return ret;
}
default: {
// 命令为 .seal XXXX,取第一个参数为名字
if (!val) val = '氪豹';
// 进行回复,如果是群聊发送那么在群里回复,私聊发送则在私聊回复 (听起来是废话文学,但详细区别见暗骰例子)
seal.replyToSender(ctx, msg, `你抓到一只海豹!取名为${val}\n它的逃跑意愿为${Math.ceil(Math.random() * 100)}`);
return seal.ext.newCmdExecuteResult(true);
}
}
};
// 将命令注册到扩展中
ext.cmdMap['seal'] = cmdSeal;
seal.ext.register(ext);
}这就是最基本的模板了。
抽取牌堆
抽取牌堆的函数是 seal.deck.draw(ctx, deckName, shufflePool)
ctx:MsgContext类型,指令上下文,solve()函数传进来的第一个参数deckName:牌堆名称,字符串类型,例如GRE单词shufflePool:是否放回抽取,布尔类型,true为放回抽取,false为不放回抽取
返回值是一个 map,包含以下字段:
exists:布尔类型,是否抽取成功result:字符串类型,抽取结果err:字符串类型,抽取失败的原因
示例代码:抽取牌堆
// ==UserScript==
// @name 抽取牌堆示例
// @author SzzRain
// @version 1.0.0
// @description 用于演示如何抽取牌堆
// @timestamp 1699077659
// @license MIT
// @homepageURL https://github.com/sealdice/javascript
// ==/UserScript==
// 本脚本用于演示如何抽取牌堆,共有两种实现方式
if (!seal.ext.find('draw-decks-example')) {
const ext = seal.ext.new('draw-decks-example', 'SzzRain', '1.0.0');
// 创建一个命令
const cmdDrawDecks = seal.ext.newCmdItemInfo();
cmdDrawDecks.name = 'dr';
cmdDrawDecks.help = '使用 .dr <牌堆名> 来抽取牌堆';
cmdDrawDecks.solve = (ctx, msg, cmdArgs) => {
// 抽取牌堆
// 参数1:ctx 参数2:牌堆名称 参数3:是否放回抽取
// 返回值:{exists: true, result: '抽取结果', err: '错误原因'}
const decks = seal.deck.draw(ctx, cmdArgs.getArgN(1), true);
// 判断是否抽取成功
if (decks['exists']) {
seal.replyToSender(ctx, msg, decks['result']);
return seal.ext.newCmdExecuteResult(true);
} else {
seal.replyToSender(ctx, msg, '抽取牌堆失败,原因:' + decks['err']);
return seal.ext.newCmdExecuteResult(true);
}
};
// 注册命令
ext.cmdMap['dr'] = cmdDrawDecks;
// 创建一个命令
const cmdDrawDecks2 = seal.ext.newCmdItemInfo();
cmdDrawDecks2.name = 'dr2';
cmdDrawDecks2.help = '使用 .dr2 <牌堆名> 来抽取牌堆';
cmdDrawDecks2.solve = (ctx, msg, cmdArgs) => {
// 抽取牌堆的另一种写法,使用 format 函数,由于经过了 rollvm 的处理,所以代码的执行效率会更慢
// 不过这种写法的返回值固定为字符串,省去了判断是否抽取成功的步骤
// 参数1:ctx 参数2:海豹语表达式,其中 #{DRAW-牌堆名称} 会被替换为抽取结果
const decks = seal.format(ctx, `#{DRAW-${cmdArgs.getArgN(1)}}`);
seal.replyToSender(ctx, msg, decks);
}
// 注册命令
ext.cmdMap['dr2'] = cmdDrawDecks2;
// 注册扩展
seal.ext.register(ext);
}权限识别
海豹中的权限等级,由高到低分别是:骰主,群主,管理员,邀请者,普通用户 和 黑名单用户。 每一个身份都有一个对应的数字,可以通过 ctx.privilegeLevel 获取当前用户的权限等级。 每个身份所对应的数字如下表所示:
信息
注意: 部分权限等级仅在群聊中有效。
从 v1.4.5 起,在私聊中,除了骰主,白名单用户和黑名单用户,其他用户被视为拥有与群管理员等同的权限,即权限值 50。
在 v1.4.4 或更早版本,私聊中除了骰主,白名单用户和黑名单用户,其他用户的权限等级为普通用户(0)。
| 身份 | 权限值 |
|---|---|
| 骰主 | 100 |
| 白名单 | 70 |
| 群主 | 60 |
| 管理员 | 50 |
| 邀请者 | 40 |
| 普通用户 | 0 |
| 黑名单用户 | -30 |
提示:关于白名单用户
白名单用户即通过骰主手动添加的信任名单用户,可以使用所有需要群管理权限的功能,但不具备 Master 权限。
信任名单可以通过 .ban trust <统一ID> 添加,通过 .ban list trust 查看。
提示:关于黑名单用户
通常情况下你不需要考虑黑名单用户的情况,因为黑名单用户的消息会被过滤掉,不会触发任何指令。
示例代码:权限识别
// ==UserScript==
// @name 权限识别样例
// @author SzzRain
// @version 1.0.0
// @description 使用命令 .myperm 查看自己的权限
// @timestamp 1699086084
// @license MIT
// @homepageURL https://github.com/Szzrain
// ==/UserScript==
if (!seal.ext.find('myperm')) {
const ext = seal.ext.new('myperm', 'SzzRain', '1.0.0');
// 创建一个命令
const cmdMyperm = seal.ext.newCmdItemInfo();
cmdMyperm.name = 'myperm';
cmdMyperm.help = '使用 .myperm 展示我的权限';
cmdMyperm.solve = (ctx, msg, cmdArgs) => {
let text = "普通用户";
console.log(ctx.privilegeLevel);
switch (ctx.privilegeLevel) {
case 100:
text = "master";
break;
case 60:
text = "owner";
break;
case 50:
text = "admin";
break;
case 40:
text = "inviter";
break;
case -30:
// 黑名单用户,但是由于黑名单会被过滤掉,所以实际上这里并不会执行,这里只是为了演示
return seal.ext.newCmdExecuteResult(false);
}
seal.replyToSender(ctx, msg, text);
return seal.ext.newCmdExecuteResult(true);
}
// 注册命令
ext.cmdMap['myperm'] = cmdMyperm;
// 注册扩展
seal.ext.register(ext);
}黑名单 / 信任名单操作 v1.4.4
黑名单操作的函数
添加:seal.ban.addBan(ctx, uid, place, reason)
移除:seal.ban.remove(ctx, uid)
ctx:MsgContext类型,指令上下文,solve()函数传进来的第一个参数uid:用户 ID,字符串类型,例如QQ:123456789,TG:123456789place:拉黑的地方,字符串类型,随便写,一般来说在群内拉黑就写群 IDreason:拉黑原因,字符串类型,随便写
信任用户名单
添加:seal.ban.addTrust(ctx, uid, place, reason) 参数说明同上
移除:seal.ban.remove(ctx, uid)
提示:相同的移除函数
黑名单和信任名单存储在同一个数据库中,因此移除时使用的是同一个函数。
你在进行移除操作时需要自己判断是否符合你的预期。
获取黑名单 / 信任名单列表
使用 seal.ban.getList()
返回值为一个数组,数组中的每一项都是一个 BanListInfoItem 对象,包含以下字段:
id:用户 ID,字符串类型name:用户昵称,字符串类型score:怒气值,整数类型rank:拉黑/信任等级 0=没事 -10=警告 -30=禁止 30=信任times:事发时间,数组类型,内部元素为整数时间戳reasons:拉黑/信任原因,数组类型,内部元素为字符串places:拉黑/信任的发生地点,数组类型,内部元素为字符串banTime:拉黑/信任的时间,整数时间戳
获取用户在黑名单 / 信任名单中的信息
使用 seal.ban.getUser(uid)
如果用户没有在黑名单 / 信任名单中,返回值为空值。
如果有则返回一个 BanListInfoItem 对象,字段同上。
示例代码:黑名单 / 信任名单操作
// ==UserScript==
// @name js-ban
// @author SzzRain
// @version 1.0.0
// @description 演示 js 扩展操作黑名单
// @timestamp 1706684850
// @license MIT
// @homepageURL https://github.com/Szzrain
// ==/UserScript==
if (!seal.ext.find('js-ban')) {
const ext = seal.ext.new('js-ban', 'SzzRain', '1.0.0');
// 创建一个命令
const cmdcban = seal.ext.newCmdItemInfo();
cmdcban.name = 'cban';
cmdcban.help = '使用.cban <用户id> 来拉黑目标用户,仅master可用';
cmdcban.solve = (ctx, msg, cmdArgs) => {
let val = cmdArgs.getArgN(1);
switch (val) {
case 'help': {
const ret = seal.ext.newCmdExecuteResult(true);
ret.showHelp = true;
return ret;
}
default: {
if (ctx.privilegeLevel === 100) {
seal.ban.addBan(ctx, val, "JS扩展拉黑", "JS扩展拉黑测试");
seal.replyToSender(ctx, msg, "已拉黑用户" + val);
} else {
seal.replyToSender(ctx, msg, "你没有权限执行此命令");
}
return seal.ext.newCmdExecuteResult(true);
}
}
}
// 注册命令
ext.cmdMap['cban'] = cmdcban;
// 创建一个命令
const cmdcunban = seal.ext.newCmdItemInfo();
cmdcunban.name = 'cunban';
cmdcunban.help = '使用.cunban <用户id> 来解除拉黑/移除信任目标用户,仅master可用';
cmdcunban.solve = (ctx, msg, cmdArgs) => {
let val = cmdArgs.getArgN(1);
switch (val) {
case 'help': {
const ret = seal.ext.newCmdExecuteResult(true);
ret.showHelp = true;
return ret;
}
default: {
if (ctx.privilegeLevel === 100) {
// 信任用户和拉黑用户存在同一个列表中,remove 前请先判断是否符合预期
seal.ban.remove(ctx, val);
seal.replyToSender(ctx, msg, "已解除拉黑/信任用户" + val);
} else {
seal.replyToSender(ctx, msg, "你没有权限执行此命令");
}
return seal.ext.newCmdExecuteResult(true);
}
}
}
// 注册命令
ext.cmdMap['cunban'] = cmdcunban;
// 创建一个命令
const cmdctrust = seal.ext.newCmdItemInfo();
cmdctrust.name = 'ctrust';
cmdctrust.help = '使用.ctrust <用户id> 来信任目标用户,仅master可用';
cmdctrust.solve = (ctx, msg, cmdArgs) => {
let val = cmdArgs.getArgN(1);
switch (val) {
case 'help': {
const ret = seal.ext.newCmdExecuteResult(true);
ret.showHelp = true;
return ret;
}
default: {
if (ctx.privilegeLevel === 100) {
seal.ban.addTrust(ctx, val, "JS扩展信任", "JS扩展信任测试");
seal.replyToSender(ctx, msg, "已信任用户" + val);
} else {
seal.replyToSender(ctx, msg, "你没有权限执行此命令");
}
return seal.ext.newCmdExecuteResult(true);
}
}
}
// 注册命令
ext.cmdMap['ctrust'] = cmdctrust;
// 创建一个命令
const cmdcbanlist = seal.ext.newCmdItemInfo();
cmdcbanlist.name = 'cbanlist';
cmdcbanlist.help = '使用.cbanlist 来查看黑名单和信任列表,仅master可用';
cmdcbanlist.solve = (ctx, msg, cmdArgs) => {
let val = cmdArgs.getArgN(1);
switch (val) {
case 'help': {
const ret = seal.ext.newCmdExecuteResult(true);
ret.showHelp = true;
return ret;
}
default: {
if (ctx.privilegeLevel === 100) {
let text = "黑名单/信任列表:\n";
seal.ban.getList().forEach((v) => {
text += `${v.name}(${v.id}) 当前等级:${v.rank} 怒气值:${v.score}\n`;
});
seal.replyToSender(ctx, msg, text);
} else {
seal.replyToSender(ctx, msg, "你没有权限执行此命令");
}
return seal.ext.newCmdExecuteResult(true);
}
}
}
// 注册命令
ext.cmdMap['cbanlist'] = cmdcbanlist;
// 创建一个命令
const cmdcget = seal.ext.newCmdItemInfo();
cmdcget.name = 'cget';
cmdcget.help = '使用.cget <用户id> 来查看目标用户的黑名单/信任信息,仅master可用';
cmdcget.solve = (ctx, msg, cmdArgs) => {
let val = cmdArgs.getArgN(1);
switch (val) {
case 'help': {
const ret = seal.ext.newCmdExecuteResult(true);
ret.showHelp = true;
return ret;
}
default: {
if (ctx.privilegeLevel === 100) {
let info = seal.ban.getUser(val);
if (!info) {
seal.replyToSender(ctx, msg, "用户不存在或未被拉黑/信任");
return seal.ext.newCmdExecuteResult(true);
}
let level = info.rank;
// 不知道为什么,用 === 是 false
if (info.rank == 30) {
level = "信任"
} else if (info.rank == -30) {
level = "拉黑"
} else if (info.rank == -10) {
level = "警告"
}
let text = `用户${info.name}(${info.id}) 当前等级:${level} 怒气值:${info.score}`;
seal.replyToSender(ctx, msg, text);
} else {
seal.replyToSender(ctx, msg, "你没有权限执行此命令");
}
return seal.ext.newCmdExecuteResult(true);
}
}
}
// 注册命令
ext.cmdMap['cget'] = cmdcget;
// 注册扩展
seal.ext.register(ext);
}存取数据
相关的 API 是两个函数,ExtInfo.storageSet(key, value) 函数和 ExtInfo.storageGet(key),一个存,一个取。
关于 key:
存储时需要指定 key,你可以设定为你的扩展的名字,也可以设定为其他的,注意不要和别的扩展的 key 重名就可以了。
就好比你在商场门口想要把随身物品存进暂存柜中,需要先找到个和别人不重复的柜子,避免放错地方或者取错东西。
关于 value:
存放的数据是字符串类型,且只能存一个,但如果想要存放更多的数据以及非字符串类型的数据怎么办?
答案是使用 JSON.stringify() 函数将存储了数据的 JS 对象转换为 JSON 字符串,存储起来,需要取出的时候,再使用 JSON.parse() 函数将数据再转换为 JS 对象。。
示例代码:投喂插件
// ==UserScript==
// @name 示例:存储数据
// @author 木落
// @version 1.0.0
// @description 投喂,格式 .投喂 <物品>
// @timestamp 1672423909
// 2022-12-31
// @license Apache-2
// @homepageURL https://github.com/sealdice/javascript
// ==/UserScript==
// 先将扩展模块创建出来,如果已创建就直接使用
if (!seal.ext.find('test')) {
const ext = seal.ext.new('test', '木落', '1.0.0');
const cmdFeed = seal.ext.newCmdItemInfo();
cmdFeed.name = '投喂';
cmdFeed.help = '投喂,格式:.投喂 <物品>\n.投喂 记录 // 查看记录';
cmdFeed.solve = (ctx, msg, cmdArgs) => {
let val = cmdArgs.getArgN(1);
switch (val) {
case 'help':
case '': {
// .投喂 help
const ret = seal.ext.newCmdExecuteResult(true);
ret.showHelp = true;
return ret;
}
case '列表':
case '记录':
case 'list': {
const data = JSON.parse(ext.storageGet('feedInfo') || '{}');
const lst = [];
for (let [k, v] of Object.entries(data)) {
lst.push(`- ${k}: 数量 ${v}`);
}
seal.replyToSender(ctx, msg, `投喂记录:\n${lst.join('\n')}`);
return seal.ext.newCmdExecuteResult(true);
}
default: {
const data = JSON.parse(ext.storageGet('feedInfo') || '{}');
const name = val || '空气';
if (data[name] === undefined) {
data[name] = 0;
}
data[name] += 1;
ext.storageSet('feedInfo', JSON.stringify(data));
seal.replyToSender(ctx, msg, `你给海豹投喂了${name},要爱护动物!`);
return seal.ext.newCmdExecuteResult(true);
}
}
};
// 将命令注册到扩展中
ext.cmdMap['投喂'] = cmdFeed;
ext.cmdMap['feed'] = cmdFeed;
seal.ext.register(ext);
}示例代码:群内安价收集
这是关于数据的增加、删除、查询等操作的实现示例(修改的话就是删除之后增加)
// ==UserScript==
// @name 群内安价收集
// @author 憧憬少
// @version 1.0.0
// @description 在群内收集群友给出的安价选项,并掷骰得出结果
// @timestamp 1676386517
// 2023-02-14 22:55:17
// @license MIT
// @homepageURL https://github.com/ChangingSelf/sealdice-js-ext-anchor
// ==/UserScript==
(() => {
// src/index.ts
const HELP = `群内安价收集 (ak 是アンカー罗马字缩写)
注意 ak 后面有空格,“.ak”也可以换成“.安价”
.ak help // 查看帮助
.ak # 标题 // 新建一轮分歧并设标题
.ak + 选项 // 需要添加的选项的内容
.ak - 序号 // 需要移除的选项的序号
.ak ? // 列出目前所有选项
.ak = // 随机抽取 1 个选项并继续
.ak = n // 随机抽取 n 个选项并继续
`;
const STORAGE_KEY = "anchor";
const OPTION_NUM_PER_PAGE = 15; // 列出所有选项时,每页放多少个选项
function akNew(ctx, msg, ext, title) {
const data = {
"title": title,
"options": []
};
ext.storageSet(STORAGE_KEY, JSON.stringify(data));
seal.replyToSender(ctx, msg, `已新建分歧:${title}`);
}
function akAdd(ctx, msg, ext, option) {
const data = JSON.parse(ext.storageGet(STORAGE_KEY) || '{"title":"","options":[]}');
data.options.push(option);
seal.replyToSender(ctx, msg, `当前分歧:${data.title}
已添加第${data.options.length}个选项:${option}`);
ext.storageSet(STORAGE_KEY, JSON.stringify(data));
}
function akDel(ctx, msg, ext, index) {
const data = JSON.parse(ext.storageGet(STORAGE_KEY) || '{"title":"","options":[]}');
const removed = data.options.splice(index - 1, 1)[0];
seal.replyToSender(ctx, msg, `当前分歧:${data.title}
已移除第${index}个选项:${removed}`);
ext.storageSet(STORAGE_KEY, JSON.stringify(data));
}
function akList(ctx, msg, ext) {
const data = JSON.parse(ext.storageGet(STORAGE_KEY) || '{"title":"","options":[]}');
if (data.options.length === 0) {
seal.replyToSender(ctx, msg, `当前分歧:${data.title}
还没有任何选项呢`);
return;
}
let optStr = "";
let curPageRows = 0;
data.options.forEach((value, index) => {
optStr += `${index + 1}.${value}
`;
++curPageRows;
if (curPageRows >= OPTION_NUM_PER_PAGE) {
seal.replyToSender(ctx, msg, `当前分歧:${data.title}
${optStr}`);
optStr = "";
curPageRows = 0;
}
});
if (curPageRows > 0) {
seal.replyToSender(ctx, msg, `当前分歧:${data.title}
${optStr}`);
}
}
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function akGet(ctx, msg, ext, num = 1) {
const data = JSON.parse(ext.storageGet(STORAGE_KEY) || '{"title":"","options":[]}');
if (data.options.length === 0) {
seal.replyToSender(ctx, msg, `当前分歧:${data.title}
还没有任何选项呢`);
return;
}
akList(ctx, msg, ext);
let optStr = "";
for (let i = 0; i < num; ++i) {
const r = randomInt(1, data.options.length);
const result = data.options.splice(r - 1, 1);
optStr += `${i + 1}.${result}
`;
}
seal.replyToSender(ctx, msg, `结果是:
${optStr}`);
ext.storageSet(STORAGE_KEY, JSON.stringify(data));
}
function main() {
let ext = seal.ext.find("anchor");
if (!ext) {
ext = seal.ext.new("anchor", "憧憬少", "1.0.0");
const cmdSeal = seal.ext.newCmdItemInfo();
cmdSeal.name = "安价";
cmdSeal.help = HELP;
cmdSeal.solve = (ctx, msg, cmdArgs) => {
try {
let val = cmdArgs.getArgN(1);
switch (val) {
case "#": {
const title = cmdArgs.getArgN(2);
akNew(ctx, msg, ext, title);
return seal.ext.newCmdExecuteResult(true);
}
case "+": {
const option = cmdArgs.getArgN(2);
akAdd(ctx, msg, ext, option);
return seal.ext.newCmdExecuteResult(true);
}
case "-": {
const index = Number(cmdArgs.getArgN(2));
akDel(ctx, msg, ext, index);
return seal.ext.newCmdExecuteResult(true);
}
case "?":
case "?": {
akList(ctx, msg, ext);
return seal.ext.newCmdExecuteResult(true);
}
case "=": {
let num = 1;
if (cmdArgs.args.length >= 2) {
num = Number(cmdArgs.getArgN(2));
}
akGet(ctx, msg, ext, num);
return seal.ext.newCmdExecuteResult(true);
}
case "help":
default: {
const ret = seal.ext.newCmdExecuteResult(true);
ret.showHelp = true;
return ret;
}
}
} catch (error) {
seal.replyToSender(ctx, msg, error.Message);
return seal.ext.newCmdExecuteResult(true);
}
};
ext.cmdMap["安价"] = cmdSeal;
ext.cmdMap["ak"] = cmdSeal;
seal.ext.register(ext);
}
}
main();
})();数据处理模板
关于取出数据来修改的函数,可以参考如下代码:
const STORAGE_KEY = "anchor"; // 将你的 key 抽出来单独作为一个常量,方便开发阶段修改(使用了之后就不要修改了)
//函数:添加选项
function akAdd(ctx, msg, ext, option) {
//取出数据
const data = JSON.parse(ext.storageGet(STORAGE_KEY) || '{"title":"","options":[]}');
//处理数据
data.options.push(option);
//响应发送者
seal.replyToSender(ctx, msg, `当前分歧:${data.title}\n已添加第${data.options.length}个选项:${option}`);
//将处理完的数据写回去
ext.storageSet(STORAGE_KEY, JSON.stringify(data));
}读取玩家或群组数据
可以查看 API 列表。
编写暗骰指令
如下:
// ==UserScript==
// @name 示例:编写暗骰指令
// @author 流溪
// @version 1.0.0
// @description 暗骰,格式.hide 原因
// @timestamp 1671540835
// 2022-12-20
// @license Apache-2
// @homepageURL https://github.com/sealdice/javascript
// ==/UserScript==
if (!seal.ext.find('hide')){
const ext = seal.ext.new('hide','流溪','0.0.1');
const cmdHide = seal.ext.newCmdItemInfo;
cmdHide.name = 'hide';
cmdHide.help = '暗骰,使用 .hide 面数 暗骰';
cmdHide.solve = (ctx, msg, cmdArgs) => {
if (msg.messageType !== 'group'){
seal.replyToSender(ctx, msg, '暗骰只能在群内触发');
return seal.ext.newCmdExecuteResult(true);
}
function rd(x){
// 这里写的时候有点不清醒了,感觉是对的,如果不对请拷打我
return Math.round(Math.random() * (x - 1) + 1);
}
let x = cmdArgs.getArgN(1);
if (x === 'help'){
return seal.ext.newCmdExecuteResult(true).showhelp = true;
} else if (isNaN(Number(x))){
// 我知道这里有更好的判断是否为数字的方法但是我不会.jpg
seal.replyToSender(ctx, msg, `骰子面数应是数字`);
return seal.ext.newCmdExecuteResult(true);
} else {
// 这就是暗骰 api 哒!
ctx.commandHideFlag = msg.groupId; // 暗骰回复需要记录到 log 中,所以需要设置这个 flag
seal.replyGroup(ctx, msg, `你在群暗骰了一个${x}面骰`);
seal.replyPerson(ctx, msg, `你在群${msg.groupId}的掷骰结果为:${rd(x)}`);
return seal.ext.newCmdExecuteResult(true);
}
}
ext.cmdMap['hide'] = cmdHide;
seal.ext.register(ext);
}可以看到使用seal.replyPerson做到暗骰的效果。
log 记录暗骰指令 v1.5.0
自 v1.5.0 版本起,海豹支持将插件的私聊回复记录到日志中。
若要实现此功能,需将 ctx.commandHideFlag 字段设置为来源群号。
同时,要使用相同的 ctx 对象,先向来源群发送一条消息,紧接着向目标私聊发送暗骰结果。
编写代骰指令
// ==UserScript==
// @name 示例:编写代骰指令
// @author 木落
// @version 1.0.0
// @description 捕捉某人,格式.catch <@名字>
// @timestamp 1671540835
// 2022-12-20
// @license Apache-2
// @homepageURL https://github.com/sealdice/javascript
// ==/UserScript==
// 编写代骰指令
// 先将扩展模块创建出来,如果已创建就直接使用
if (!seal.ext.find('test')) {
const ext = seal.ext.new('test', '木落', '1.0.0');
// 创建指令 .catch
// 这个命令的功能为,显示“试图捕捉某人”,并给出成功率
// 如果命令写“.catch @张三”,那么就会试着捕捉张三
const cmdCatch = seal.ext.newCmdItemInfo();
cmdCatch.name = 'catch';
cmdCatch.help = '捕捉某人,格式.catch <@名字>';
// 对这个指令启用使用代骰功能,即@某人时,可获取对方的数据,以对方身份进行骰点
cmdCatch.allowDelegate = true;
cmdCatch.solve = (ctx, msg, cmdArgs) => {
// 获取对方数据,之后用 mctx 替代 ctx,mctx 下读出的数据即被代骰者的个人数据
const mctx = seal.getCtxProxyFirst(ctx, cmdArgs);
let val = cmdArgs.getArgN(1);
switch (val) {
case 'help': {
// 命令为 .catch help
const ret = seal.ext.newCmdExecuteResult(true);
ret.showHelp = true;
return ret;
}
default: {
const text = `正在试图捕捉${mctx.player.name},成功率为${Math.ceil(Math.random() * 100)}%`;
seal.replyToSender(mctx, msg, text);
return seal.ext.newCmdExecuteResult(true);
}
}
};
// 将命令注册到扩展中
ext.cmdMap['catch'] = cmdCatch;
seal.ext.register(ext);
}网络请求
主要使用 Fetch API 进行网络请求,详细文档见链接。fetch 函数返回一个 Promise,传统的写法是这样:
// 你可以使用 generator 来重写这段代码,欢迎 pr
// 访问网址
fetch('https://api-music.imsyy.top/cloudsearch?keywords=稻香').then((resp) => {
// 在返回对象的基础上,将文本流作为 json 解析
resp.json().then((data) => {
// 打印解析出的数据
console.log(JSON.stringify(data));
});
});你也可以使用异步编程(async/await)来简化代码:
const response = await fetch('https://api-music.imsyy.top/cloudsearch?keywords=稻香');
if (!response.ok) {
// 处理不成功的请求...
}
const data = await response.json();
console.log(JSON.stringify(data));套用这个模板,你可以写出很多调用 API 的简单扩展。
比如核心代码只有一行的「随机猫猫图片」扩展:
seal.replyToSender(ctx, msg, `[CQ:image,file=https://thiscatdoesnotexist.com/,cache=0]`);核心代码同样只有一行的「随机二次元图片」扩展:
seal.replyToSender(ctx, msg, `[CQ:image,file=https://api.oick.cn/random/api.php?type=${val},cache=0]`);当然,也有稍微复杂的,比如「AI 骰娘」扩展,但也没有太复杂,只是处理了一下发送者传过来的消息,再发送给网络 API,收到响应之后再回应发送者。
它的核心代码如下:
const BID = ''; // 填入你的骰娘的大脑的 id
const KEY = ''; // 填入你的 key
/**
* 给 AI 主脑发送消息并接收回复
* @param ctx 主要是和当前环境以及用户相关的内容,如当前发指令用户,当前群组信息等
* @param msg 为原生态的指令内容,如指令文本,发送平台,发送时间等
* @param message 要发送给骰娘的具体消息
*/
function chatWithBot(ctx,msg,message) {
fetch(`http://api.brainshop.ai/get?bid=${BID}&key=${KEY}&uid=${msg.sender.userId}&msg=${message}`).then(response => {
if (!response.ok) {
seal.replyToSender(ctx, msg, `抱歉,我连接不上主脑了。它传递过来的信息是:${response.status}`);
return seal.ext.newCmdExecuteResult(false);
} else {
response.json().then(data => {
seal.replyToSender(ctx, msg, data["cnt"]);
return seal.ext.newCmdExecuteResult(true);
});
return seal.ext.newCmdExecuteResult(true);
}
});
}自定义 COC 规则
// ==UserScript==
// @name 示例:自定义 COC 规则
// @author 木落
// @version 1.0.0
// @description 自设规则,出 1 大成功,出 100 大失败。困难极难等保持原样
// @timestamp 1671886435
// 2022-12-24
// @license Apache-2
// @homepageURL https://github.com/sealdice/javascript
// ==/UserScript==
const rule = seal.coc.newRule();
rule.index = 20; // 自定义序号必须大于等于 20,代表可用.setcoc 20 切换
rule.key = '测试'; // 代表可用 .setcoc 测试 切换
rule.name = '自设规则'; // 已切换至规则文本 name: desc
rule.desc = '出 1 大成功\n出 100 大失败';
// d100 为出目,checkValue 为技能点数
rule.check = (ctx, d100, checkValue) => {
let successRank = 0;
const criticalSuccessValue = 1;
const fumbleValue = 100;
if (d100 <= checkValue) {
successRank = 1;
} else {
successRank = -1;
}
// 成功判定
if (successRank == 1) {
// 区分大成功、困难成功、极难成功等
if (d100 <= checkValue / 2) {
//suffix = "成功(困难)"
successRank = 2;
}
if (d100 <= checkValue / 5) {
//suffix = "成功(极难)"
successRank = 3;
}
if (d100 <= criticalSuccessValue) {
//suffix = "大成功!"
successRank = 4;
}
} else {
if (d100 >= fumbleValue) {
//suffix = "大失败!"
successRank = -2;
}
}
let ret = seal.coc.newRuleCheckResult();
ret.successRank = successRank;
ret.criticalSuccessValue = criticalSuccessValue;
return ret;
};
// 注册规则
seal.coc.registerRule(rule);补充:使用非指令关键词
你是否因为自定义回复能实现的功能有限而烦恼?你是否因为自定义回复的匹配方式不全而愤怒?你是否因为自定义回复只能调用图片 API 而感到焦头烂额?
不要紧张,我的朋友,试试非指令关键词,这会非常有用。
通常情况下,我们使用 ext.onNotCommandReceived 作为非指令关键词的标志;这限定了只有在未收到命令且未达成自定义回复时,海豹才会触发此流程。
一个完整的非指令关键词模板如下:
// 必要流程,注册扩展,注意即使是非指令关键词也是依附于扩展的
if (!seal.ext.find('xxx')){
const ext = seal.ext.new('xxx','xxx','x.x.x');
seal.ext.register(ext);
// 这里其实是编写处理函数
ext.onNotCommandReceived = (ctx, msg) => {
let message = msg.message;
// 这里请自己处理要如何达成 message 的匹配条件,js 那么多的匹配方法,足够你玩出花来。
if(xxx){
// 匹配到关键词了,要干什么?
xxx;
}
}
}注册插件配置项 v1.4.1
插件若要在 UI 中注册可供用户修改的配置项,需要在插件注册后调用 seal.ext.registerXXXConfig() 函数注册配置项。
XXX 为配置项的类型,目前支持 string、int、float、bool、template、option 六种类型。注意按照小驼峰命名法大写
同样的,插件也可以使用 seal.ext.getXXXConfig() 函数获取配置项的值。
你也可以直接使用 seal.ext.getConfig() 函数获取配置项的值,这个函数会返回一个 ConfigItem 对象, 包含了配置项的类型、值、默认值等信息。
ConfigItem 对象的类型定义如下,调用时请使用 jsbind 中的值作为 key
type ConfigItem struct {
Key string `json:"key" jsbind:"key"`
Type string `json:"type" jsbind:"type"`
DefaultValue interface{} `json:"defaultValue" jsbind:"defaultValue"`
Value interface{} `json:"value,omitempty" jsbind:"value"`
Option interface{} `json:"option,omitempty" jsbind:"option"`
Deprecated bool `json:"deprecated,omitempty" jsbind:"deprecated"`
}提示:更原始的 API
seal.ext.registerConfig() 函数也是可以使用的,你需要自己通过 seal.ext.newConfigItem() 来获取一个新的 ConfigItem 对象。
在对你的 ConfigItem 对象进行修改后,再调用 seal.ext.registerConfig() 函数进行注册。
示例代码:注册配置项
// ==UserScript==
// @name js-config-example
// @author Szzrain
// @version 1.0.0
// @description 演示 js 配置项的用法
// @timestamp 1698636875
// @license MIT
// ==/UserScript==
if (!seal.ext.find('js-config-example')) {
const ext = seal.ext.new('js-config-example', 'SzzRain', '1.0.0');
// 创建一个命令
const cmdgetConfig = seal.ext.newCmdItemInfo();
cmdgetConfig.name = 'getconfig';
cmdgetConfig.help = '使用.getconfig <key> 来获取配置项,仅 master 可用';
cmdgetConfig.allowDelegate = true;
cmdgetConfig.solve = (ctx, msg, cmdArgs) => {
let val = cmdArgs.getArgN(1);
switch (val) {
case 'help': {
const ret = seal.ext.newCmdExecuteResult(true);
ret.showHelp = true;
return ret;
}
default: {
if (ctx.privilegeLevel !== 100) {
seal.replyToSender(ctx, msg, "你没有权限执行此命令");
return seal.ext.newCmdExecuteResult(true);
}
switch (val) {
case "1":
strVal = seal.ext.getStringConfig(ext, "testkey1");
seal.replyToSender(ctx, msg, strVal);
break;
case "2":
intVal = seal.ext.getIntConfig(ext, "testkey2");
seal.replyToSender(ctx, msg, intVal);
break;
case "3":
floatVal = seal.ext.getFloatConfig(ext, "testkey3");
seal.replyToSender(ctx, msg, floatVal);
break;
case "4":
boolVal = seal.ext.getBoolConfig(ext, "testkey4");
seal.replyToSender(ctx, msg, boolVal);
break;
case "5":
tmplVal = seal.ext.getTemplateConfig(ext, "testkey5");
seal.replyToSender(ctx, msg, tmplVal);
break;
case "6":
optVal = seal.ext.getOptionConfig(ext, "testkey6");
seal.replyToSender(ctx, msg, optVal);
break;
default:
let config = seal.ext.getConfig(ext, val);
if (config) {
seal.replyToSender(ctx, msg, config.value);
} else {
seal.replyToSender(ctx, msg, "配置项不存在");
}
break;
}
return seal.ext.newCmdExecuteResult(true);
}
}
}
// 注册命令
ext.cmdMap['getconfig'] = cmdgetConfig;
// 注册扩展
seal.ext.register(ext);
// 注册配置项需在 ext 注册后进行
// 通常来说,register 函数的参数为 ext, key, defaultValue
seal.ext.registerStringConfig(ext, "testkey1", "testvalue");
seal.ext.registerIntConfig(ext, "testkey2", 123);
seal.ext.registerFloatConfig(ext, "testkey3", 123.456);
seal.ext.registerBoolConfig(ext, "testkey4", true);
seal.ext.registerTemplateConfig(ext, "testkey5", ["1", "2", "3", "4"]);
// 注册单选项函数的参数为 ext, key, defaultValue, options
seal.ext.registerOptionConfig(ext, "testkey6", "1", ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]);
}注册后的配置项会在 UI 中显示,可以在 UI 中修改配置项的值

注册定时任务 v1.4.6
提示:Cron 表达式
如果你对 cron 或下文中提到的 Cron 表达式并不熟悉,可以参考 Linux crontab 命令 | 菜鸟教程 或 Cron 表达式 - 阿里云文档。
从 v1.4.6 版本开始,海豹核心新增了用于定时任务的 API。
API 参数
seal.ext.registerTask(ext, taskType, value, func, key="", description="")其各个参数的含义如下:
taskType: string:registerTask接受两种类型的定时任务表达式——使用 Cron 表达式或使用hh:mm/h:mm格式的「每日」任务。当使用前者时,taskType应填入"cron",而后者应填入"daily"。value: string:- 当
taskType填入"cron"时,value应填入有效的 Cron 表达式,例如:"*/5 * * * *"。registerTask会根据 Cron 表达式定时执行func。 - 当
taskType填入"daily"时,value应填入hh:mm或h:mm格式的时间,例如:"08:00"、"3:00"、"20:35"。registerTask会根据时间,每天定时执行func。
- 当
func: (taskCtx: JsScriptTaskCtx) => void:定时任务的实际执行函数。其中taskCtx的数据类型为:typescripttype JsScriptTaskCtx { now: number; key: string; }taskCtx.now提供了func实际被唤起时的 Unix 时间戳;如果填写了可选参数key,taskCtx.key则与之相同。使用定时任务 API 的用户应该将实际业务逻辑放置在
func内,定时任务 API 仅承担唤醒功能。key: string:可选参数。为此定时任务提供唯一索引。当填写了key时,此定时任务也会出现在 WebUI 的插件配置项中,可以通过 WebUI 修改定时任务表达式。description: string:可选参数。为此定时任务提供可读性更高的描述。当同时填写了key与description时,WebUI 的插件配置项中将会显示关于此定时任务的描述。
使用示例
// 实现任意定时功能
seal.ext.registerTask(ext, "cron", "* * * * *", (taskCtx) => {
// 检查当前时间点附近,尚未执行的所有任务
const tasks = getTasks(taskCtx.now, ext.StorageGet("tasks"));
for (const task of tasks) {
// 检查当前群聊中,插件功能是否开启
if (!checkGroupStatus(task.groupId, ext.storageGet("status"))) {
continue;
}
doTask(task);
}
});// 类似每日新闻
seal.ext.registerTask(ext, "daily", "08:30", (taskCtx) => {
// 所有需要发送每日新闻的群聊
const groups = getGroups(ext.StorageGet("groups"));
for (const group of groups) {
sendDailyNews(group);
}
}, "daily_news", "每天触发「每日新闻」的时间");