diff --git a/404.html b/404.html new file mode 100644 index 000000000..252f10743 --- /dev/null +++ b/404.html @@ -0,0 +1,22 @@ + + +
+ + +官网:海豹官网,新版本发布的正式地址,还提供试用,log 染色 等多项功能。
海豹开源组织 GitHub:海豹核心源码的托管地址。
请参见 官网—投喂,目前尚未更新蓝绿修改器(暂时顾不上)。
你的投喂将用于支付服务器费用,以及帮助社畜制作者们稍微抵抗来自现实的引力。
非常感谢。
海豹的源代码以 MIT 协议开源,托管在 GitHub 下。海豹核心的诞生离不开开源社区和其它开源软件的支持。
',15),h=[n];function l(i,s,c,p,b,d){return r(),e("div",null,h)}const u=a(o,[["render",l]]);export{f as __pageData,u as default}; diff --git a/assets/about_about.md.hrcKISE7.lean.js b/assets/about_about.md.hrcKISE7.lean.js new file mode 100644 index 000000000..e21c613d2 --- /dev/null +++ b/assets/about_about.md.hrcKISE7.lean.js @@ -0,0 +1 @@ +import{_ as a,c as e,o as r,a8 as t}from"./chunks/framework.C7ysi3tu.js";const f=JSON.parse('{"title":"关于","description":"","frontmatter":{"lang":"zh-cn","title":"关于"},"headers":[],"relativePath":"about/about.md","filePath":"about/about.md","lastUpdated":1718529177000}'),o={name:"about/about.md"},n=t("",15),h=[n];function l(i,s,c,p,b,d){return r(),e("div",null,h)}const u=a(o,[["render",l]]);export{f as __pageData,u as default}; diff --git a/assets/about_archieve.md.BYjlOLqB.js b/assets/about_archieve.md.BYjlOLqB.js new file mode 100644 index 000000000..3e0268e5a --- /dev/null +++ b/assets/about_archieve.md.BYjlOLqB.js @@ -0,0 +1 @@ +import{_ as n,D as c,c as p,j as a,a as t,I as o,w as e,a8 as s,o as d}from"./chunks/framework.C7ysi3tu.js";const h="/sealdice-manual-next/assets/platform-qq-qsign-1.DewZ-7A7.png",u="/sealdice-manual-next/assets/platform-qq-qsign-2.Cv0YuS6C.png",g="/sealdice-manual-next/assets/select-account.BQuutaYp.png",m="data:image/png;base64,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",k="data:image/png;base64,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",b="data:image/png;base64,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",A="/sealdice-manual-next/assets/qsign-config.C7X6Y-jY.png",q="/sealdice-manual-next/assets/gocq-folder.BqYrXyGR.png",f="/sealdice-manual-next/assets/gocq-warn1.DnQT2Y8j.png",S="/sealdice-manual-next/assets/gocq-warn2.Be6mD1Aa.png",v="/sealdice-manual-next/assets/gocq-step1.pboT6Yxb.png",C="/sealdice-manual-next/assets/gocq-step2.-UlYTUsA.png",Q="/sealdice-manual-next/assets/gocq-success.DvcHSdvM.png",B="/sealdice-manual-next/assets/onebot11.hPU3-IW2.png",P="/sealdice-manual-next/assets/onebot11-success.B8KhG_3g.png",_="/sealdice-manual-next/assets/platform-qq-shamrock-1.DJ7tSnjn.png",x="/sealdice-manual-next/assets/platform-qq-shamrock-2.BojvCHzi.png",w="/sealdice-manual-next/assets/platform-qq-shamrock-3.BlEqBGNz.png",L="/sealdice-manual-next/assets/platform-qq-shamrock-4.FmFC-9a8.png",I="/sealdice-manual-next/assets/platform-qq-shamrock-5.BMm24yfS.png",E="/sealdice-manual-next/assets/platform-qq-shamrock-6.Bf1yZXSr.png",R="/sealdice-manual-next/assets/platform-qq-shamrock-7.DfAmZJ9X.png",T="/sealdice-manual-next/assets/platform-qq-shamrock-8.BJqlFaWq.png",y="/sealdice-manual-next/assets/platform-qq-shamrock-9.CRgE7lL1.png",z="/sealdice-manual-next/assets/image-016.McE9_BqK.png",W="/sealdice-manual-next/assets/image-017.h2f0ACoz.png",Y="/sealdice-manual-next/assets/image-018.DTqSvbQG.png",Sa=JSON.parse('{"title":"归档","description":"","frontmatter":{"lang":"zh-cn","title":"归档"},"headers":[],"relativePath":"about/archieve.md","filePath":"about/archieve.md","lastUpdated":1721204034000}'),K={name:"about/archieve.md"},F=s('本节内容
本节将记录海豹曾经使用但新版本已弃用的功能。
危险:此方案已经接近不可用
由于 QQ 官方的检测,使用 Go-cqhttp 方案成功连接的成功率已经越来越低。即使成功连接,也可能面临高达每月 2 次的频繁冻结等情况。
我们不推荐任何用户再使用此方案连接 QQ 平台。
危险:Go-cqhttp 已停止维护
Go-cqhttp 的开发者已无力维护项目(见 go-cqhttp/issue#2471)。在未来 qsign 签名服务彻底被官方封死之后,Go-cqhttp 将无法继续使用。
危险:qsign 已停止维护
原 qsign 作者已因「不可抗力」无法再维护此项目,对应原代码仓库也已删除,该方法会在未来逐渐失效,请做好预期准备。
部署签名服务,即使用开源签名服务 qsign,是目前用来绕过检测的最有效手段。
你可以自己在本地搭一个 qsign 服务,也可以使用别人搭好的。
注意:自行搭建签名服务
如果你的动手能力足够强或者有足够的电脑知识,强烈推荐 自己搭建本地签名服务器。
使用他人的签名服务可能会泄漏以下信息 (截取自 qsign 的说明):
- 登录账号
- 登录时间
- 登录后发送的消息内容
- 登录后发送消息的群号/好友 ID
不会泄露的信息:
- 账号密码
- 账号
session
- 群列表/好友列表
- 接收的消息
- 除发送消息外的任何历史记录
使用共享签名服务可能会提高账号被封禁的概率。
在登录账号的时候会看到这样一个界面:
点击下面的「签名服务」一栏的「简易配置」,可以看到如下配置项:
提示:默认的 qsign 配置
没有特殊设置的话,qsign 的 url 通常默认为 http://localhost:13579
,key 通常默认为 114514
。
提示:有能力的用户可以自行搭建服务。
注意
由于项目的特殊性,下面的方法随时可能失效,我们不对信息的及时性做任何保证。可以加入海豹官方群寻求帮助。
目前,Gocq 的过验证码网站已经停止服务,你需要自行抓取 ticket 进行登录。
步骤如下:
启动海豹,打开海豹的管理 ui,账号设置,添加账号;
账号类型选 QQ 账号,设备选「Android Pad-可共存」(此协议登录手机可同时在线,qsign 仅 Android 协议和 Android Pad 协议可用):
版本选择 8.9.70(如果你的 qsign 是其它版本,请选择对应版本):
填写 QQ 账号密码:
选择简易配置:
服务 url 填你的 qsign 服务地址。
服务 key 填你的 qsign 服务密码,没有可以不填。
服务鉴权不填写。
接着点击登录,然后退出海豹(结束进程)。
将 go-cqhttp\\go-cqhttp.exe
文件复制到 海豹目录/data/default/extra/gocqQQ号(你登录骰娘的qq号)
这个文件夹下。
双击运行 go-cqhttp.exe
,两次确认后出现 go-cqhttp.bat
文件。
双击运行 go-cqhttp.bat
,出现以下消息后输入 2
,回车,复制链接到浏览器(终端选中后右键即可复制粘贴,没有选项)。
提示:出现 open image cache db failed
出现该报错的原因很可能是因为 gocq 的缓存数据库损坏,可以尝试删除 gocq 的 data 目录后重新运行 gocq。(注意是 gocq 的 data 而不是海豹的!)
按照 手动抓取 ticket 教程 - 哔哩哔哩 视频教程操作,成功滑条后(需要抓 ticket,不只是滑条)复制 ticket 到终端后回车。
如果登录成功,你应当能看到一条类似于 2022-05-06 20:00:00 [INFO] 登录成功,欢迎使用:XXX
的日志。
同时你应当在下方看到一条类似于 2022-05-06 20:00:00 [INFO] CQ Websocket 服务器已启动:[::]:8080
的日志。
结尾的 8080
即为 gocq 的 ws 端口。你的端口号可能不同,总之请记住这个端口号。
打开海豹,删除之前添加的账号,然后重新添加账号,选择 QQ(OneBot11 分离部署)
在连接地址中填写 ws://localhost:8080
(请把8080
替换为你的 gocq 端口号)。填写完成后点击下一步。
你的账号应当已经成功连接。
""
或少复制了内容),重新登录;解压一个新的 qsign 文件,重新配置,端口需要输入不同于前面的端口。
登录 QQ 的程序,现各大骰系都用此程序,此外还有 mirai 等其他程序。
通常 SignServer 都不会出现用户可直接读出的报错信息。如果出现了崩溃或闪退的情况,可以同时按下键盘上 Ctrl
Alt
Delete
这三个按键,找到任务管理器,在右上角的内存
一栏,那里会出现数字,有些调过设置了的会出现占用的运行内存占可用运行内存的百分比。
很显然,这里的内存就是指电脑 CPU 的可用运行内存不足,这时候通过在任务管理器关闭部分没有在使用的进程就可以解决。
注意:进程不能轻易关闭
通常情况下,Windows 系统会运行一些进程以保证系统的正常运行。这些进程一旦关闭,系统可能会出现无法运行的问题。
在一般情况下,不推荐关闭这些进程。如果需要通过关闭进程来腾出运行内存,可以选择关闭其他应用程序运行的进程。
当然,关闭应用进程跟直接退出应用程序差别不大。对于在看这个界面的你来说,关闭应用程序是更好的选择。
危险:Shamrock 已停止更新
Shamrock 已于 2024 年 4 月 20 日归档,将不再进行更新。
危险:1.1.0
及以上版本的 Shamrock 不适用以下教程
2024 年 4 月 2 日,OpenShamrock 开发组于 Discussion#272 宣布,Shamrock 将会从 1.1.0
版本起弃用 OneBot V11 支持,迁移至新的 Kritor 协议。
这意味着 1.1.0
及之后版本的 Shamrock 将不再支持 OneBot V11,以下教程也不再适用。请仔细分辨,以免造成麻烦。
海豹开发组也正在着手对新的 Kritor 协议进行适配,请耐心等待。
注意:有难度的操作
此方式存在一定难度,你可能需要对 Root,使用命令行程序等有所了解。
Shamrock
Shamrock 是一个基于 LSPosed/Xposed 框架,实现劫持 QQ 以对外暴露 Onebot Api 的软件。你可以在 Android 手机/模拟器中使用 Shamrock 代替已经停止开发的 gocq。
遗憾的是,Shamrock 的使用依赖于 Android 的 root 权限,而手机厂商对 root 管控愈加严格,实体手机获取 root 权限的门槛很高,而模拟器中使用 Shamrock 的效果也不是很可观。因此,这种解决方案适合个人与朋友使用骰子的场景。
此外,如果你想使用 Shamrock 代替 gocq,请确保你有良好的计算机使用能力。
本节主要讲解如何使用模拟器实现 Shamrock,如果你有一台已经 root 的手机,也可以参考本节内容,本教程不涉及说明如何 root 手机,海豹官方也不对 root 手机造成的后果负责,请自行斟酌。
提示:如何 Root 手机
Root 手机可以参阅 小米手机安装面具教程。也可以前往 酷安 寻找更详细的教程。
注意:低配置设备可能无法使用 Shamrock!
在尝试通过模拟器使用 Shamrock 的场景下,由于模拟器对性能要求较高,包括 轻量级服务器、旧电脑、小主机等配置较低的设备可能无法支持使用。
下面将使用 夜神模拟器 作为示例。
使用时,确保安卓版本在安卓 8 以上,而在安卓 11 以下,最好使用安卓 9。
Magisk
Magisk(面具)是一套开源的 Android 自定义工具,通常用于获取 root 权限。
Root 即 Android 的超级用户权限,如对 QQ 应用进行注入等的危险操作需要 root 权限。
在使用之前,请在模拟器设置中打开 root 选项,软件中获取的一切权限都给予 通过,包括 root 权限。
使用 面具安装工具,把它安装到模拟器。
然后启动软件,输入 m
回车,再输入 y
回车,会索取超级用户权限,给予,然后输入 1
回车,再输入 a
回车,输入 1
回车,此时面具就安装到你的模拟器上了。
打开面具模块,此时面具会索取超级用户权限,给予,此时你会发现你的超级用户权限那里是灰的,关闭你的超级用户权限,重新启动你的模拟器。
此时你会发现你的超级用户模块已经激活。在面具的设置里启动 zygisk
模块,随后你需要再次重启模拟器,使得 zygisk
模块生效。
提示:使用 xposed/edxposed
理论上,使用更为老旧的 xposed/edxposed 或在手机上运行虚拟机的 virtualXposed 的方案也是可行的,但我们不推荐也未尝试过使用它们。
任何不按教程的行动请自行处理疑难问题,海豹官方不对此提供帮助。
请于 LSPosed Releases 页下载模块。
注意:zygisk
务必选择以 zygisk
结尾的包。
下载完成后,把文件上传到模拟器中。一般情况下,直接把文件拖动到模拟器就可以传文件了,且文件一般在 picture
文件夹中,如果没有请参照你使用的模拟器说明。
在你传完文件之后,在最右侧切换到「模块」页后,你可以看到从本地安装的选项。单击你刚刚传到模拟器里的文件,等待安装完成即可,随后你可以在右下角看到重启按钮,点击等待重启。
安装完成后应该如下所示:
请于 Shamrock Releases 下载 Shamrock 的 apk 安装包,直接将 apk 文件拖动到模拟器即可进行安装。
如果模拟器中没有安装 QQ,此时你还需要将 QQ 安装到模拟器中。
安装完成后,首先启动 Shamrock。在通知上,你可以打开 LSPosed 的页面,在模块一栏中启用 Shamrock。
选中 Shamrock,进入勾选 QQ,并长按 QQ 选择 强行停止。
随后再打开 QQ,你可以看到「加载 Shamrock 库成功」的提示,这代表 Shamrock 已经成功注入了 QQ。
成功注入后,打开 Shamrock 启用 ws 服务,通常情况下无需修改 Shamrock 的任何内容,如有其它需求请阅读 Shamrock 文档。
首先下载 adb 工具,解压到电脑中任何可用的位置。
随后找到模拟器供 adb 连接的端口,夜神模拟器路径示例如下:
其中:
Nox
是模拟器根路径。Nox_4
是模拟器的编号,你可以在多开助手中看到你的编号。guestport
值为 5555
的 hostport
即为所需端口,如示例中就是 62028,记住这个端口号。
在你解压的 platform-tools
里打开终端,或者把 platform-tools
加入环境变量后再启用终端。也可以在 platform-tools
里新建一个 .bat
文件,把下面的命令写到文件里面。
在打开的终端中输入命令:
.\\adb connect 127.0.0.1:端口
如替换为上面示例中的 62828:
.\\adb connect 127.0.0.1:62028
随后再执行:
.\\adb forward tcp:5800 tcp:5800
此时你已经成功开启端口了。
在账号添加中,选择「QQ(onebot11正向WS)」,按照下面的格式进行填写:
成功连接后即可使用。
',56),ia={id:"shamrock-lspatch",tabindex:"-1"},ca=a("a",{class:"header-anchor",href:"#shamrock-lspatch","aria-label":'Permalink to "Shamrock LSPatch注意:已发现 QQ 客户端针对 Shamrock 增加了检测机制。在新版 QQ 客户端上,Shamrock 已无法使用。以下内容归档于 2024 年 7 月 17 日。
注意:有难度的操作
此方式存在一定难度,你可能需要对使用命令行程序有所了解。
注意:Andriod 版本要求
由于 LSPatch 要求安卓版本 9.0 以上,因此你的安卓手机版本必须超过安卓 9。
LSPatch
LSPatch 是在无 root 环境下使用 Shamrock 的一种途径,原理是利用 Shizuku 安装经 LSPatch 修补后的 QQ,再使用 Shamrock 劫持 QQ 并对外开放 API。
Shizuku
Shizuku 是一个帮助其他应用直接使用系统 API 的应用,而 Shizuku 本身则需要通过电脑使用 adb 工具赋予权限。
Adb 即 Android 调试桥,是安卓官方提供的帮助在电脑端调试 Android 设备的命令行工具。
首先需要在你的手机安装 Shizuku,安装后需要在电脑中使用 adb 命令为其赋予权限。
',7),na=a("p",null,[a("strong",null,"在 Windows 中使用 cmd 执行 adb 命令:")],-1),pa=a("ol",null,[a("li",null,[t("安装 adb,工具下载见 "),a("a",{href:"#开放模拟器端口供海豹对接"},"上文"),t(";")]),a("li",null,[t("打开 cmd 窗口; "),a("ul",null,[a("li",null,[t("如果你的电脑是 Window 11 操作系统,你可以直接右键 "),a("code",null,"platform-tools"),t(" 文件夹单击 "),a("strong",null,"在此处打开命令行"),t(";")]),a("li",null,"其它版本的打开方式请自行搜索。")])]),a("li",null,"你有多种方式使用 adb:")],-1),da=a("ul",null,[a("li",null,[t("将 adb 添加至系统环境变量,在系统开始一栏中可以直接搜索到该功能,随后将 "),a("strong",null,"解压好的"),t(),a("code",null,"platform-tools"),t(" 路径填入至系统变量中的 "),a("code",null,"path"),t(",例如,adb 在 "),a("code",null,"E:/shamrock achieve/platform-tools"),t(" 文件夹中,那么你只需要将该路径填入 "),a("code",null,"path"),t(" 即可。 "),a("img",{src:z,alt:"adb path"}),a("ul",null,[a("li",null,[t("如果你是旧版本 Window(如 Win 7),系统未提供对应的 GUI,你需要使用 "),a("strong",null,[a("code",null,";")]),t(" 隔开不同的路径。")])])]),a("li",null,[t("也可以选择使用 "),a("code",null,"cd"),t(" 命令切换至 adb 目录,使用此方法请将 adb 放在 C 盘;(由于 Windows 权限问题,使用运行开启的 cmd 实例无法访问 C 盘之外的路径。) "),a("ul",null,[a("li",null,[a("code",null,"win + R"),t(" 键启动「运行」;")]),a("li",null,[t("在运行中输入 "),a("code",null,"cmd"),t(" 并回车;")]),a("li",null,[t("在打开的黑框框中输入命令 "),a("code",null,"cd <替换为对应路径>"),t("。 "),a("img",{src:W,alt:"切换到 adb 文件夹"})])])]),a("li",null,[t("还可以选择在 "),a("code",null,"platform-tools"),t(" 文件夹中新建"),a("code",null,".bat"),t(" 文件。")])],-1),ha=s('在手机中,你需要开启 USB 调试 ,在手机设置中,选择「更多设置—关于手机」,多次点击软件版本号,即可进入开发者模式。
随后在「更多设置—开发者选项」中打开 USB 调试,使用数据线连接电脑和手机,随后在你的手机中出现指纹调试弹框,给予通过。
在电脑中使用命令:
adb shell sh /storage/emulated/0/Android/data/moe.shizuku.privileged.api/start.sh
安装方式 上文 有提及,此处不予重复。
在 LSPatch 中,长按修补后的 QQ 出现模块作用域,允许 Shamrock 然后重启 LSPatch。
激活 Shamrock 模块有三个前提,即 QQ 进程,Shamrock 进程和 LSPatch 进程存活,请自行配置保活策略,例如允许自启动,允许后台存活和关闭后台高耗电等。
首先安装海豹安卓端。
注意:确认海豹版本
请使用版本为 1.4.2 以上的安卓端海豹。
建议使用 反向 ws 设置。在海豹中,账号添加中选择「QQ(onebot11反向WS)」,填入骰子 QQ 号和要开放的 ws 端口(例如 :6544
)。
随后在 Shamrock 中的被动 ws 连接地址中写 ws://localhost:6544/ws
。
无论是普通用户还是开发者,我们都非常欢迎你参与海豹的开发,一起努力让海豹变得更好。
海豹的所有源代码都托管在 海豹开源组织 GitHub 下,每个模块都存放在对应的仓库中,可阅读对应仓库的 Readme 获取更多信息。
几个比较重要的仓库:
所有的 Bug 和功能建议都可反馈在 核心 仓库的 issue 中,按照模板填写可方便开发组快速定位问题。
提示:无法访问 GitHub?
受限于各种原因,不是所有人都能顺利访问 GitHub,如有 Bug 可以加入官方群进行反馈。但如果有条件,我们还是建议在 GitHub 向开发组反馈问题。
欢迎各种类型的 pr 提交,可以帮助改进代码,增加功能,也可以是完善文档等等。向对应仓库发起 pr 即可。
贡献指南
下面将以提交对手册的改进为示例,为还不清楚怎么在 GitHub 上提 Pull Request 的准贡献者做个示范。
main
或 master
)切出一个新分支作为工作分支;首先 fork 对应想要修改的仓库,此处以手册仓库 sealdice-manual-next 为例:
点击 Create a new fork
按钮,如果此处已经有 fork 过仓库,可以直接进行后续步骤无需再次 fork。
点击按钮后进入如下页面:
可不做任何修改,直接点击 Create fork
按钮,等待 fork 进度条完成,此时你应当自动跳转到了 fork 出的新仓库。
提示:假如你熟悉 Git 操作
如果你熟悉 Git 操作,可以自行 clone 仓库到本地后进行修改。
切换到新仓库的分支页面,创建分支:
填写新分支名,建议为形如 feature/xxx
的有意义的英文。确认前请务必保证是从主分支切出的。
创建完毕后点击分支名切换到对应分支页面:
点击你想要修改的文件,进行在线编辑:
修改完成后点击右上角提交变更,填写变更内容信息:
你的修改完成后,在你 fork 的新仓库向主仓库发起 Pull Request:
提交时间很近的时候,GitHub 会提示快捷发起 PR 的操作按钮:
进入 PR 编辑页填写信息,请确认是从你的仓库的新分支,提向主仓库的主分支的:
主仓库 主分支 <- fork仓库 功能分支
填写完成后,等待开发组进行 review,有时会给你提出一些修改建议。在你的 PR review 通过并合并后,功能分支就可以被删除了。
如果之后还要提交新的 PR,建议先同步上游的主分支,再从同步后的主分支再新切分支进行操作。
如果你曾经修改了主分支,此处很可能会出现和上游分支有冲突的问题,需要你先手动解决冲突。
点击 Sync fork
进行同步。
进行使用即代表同意此声明:
尊敬的用户,欢迎您选择由木落等研发的海豹骰点核心(SealDice),在您使用海豹骰点核心前,请务必仔细阅读使用须知,使用即代表您已同意使用须知的内容,使用造成的任何后果与海豹开发组无关。
您需了解,海豹核心官方版只支持 TRPG 功能,娱乐功能定制化请自便,和海豹无关。
您清楚并明白您对通过骰子提供的全部内容负责,包括自定义回复、非自带的插件、牌堆。海豹骰不对非自身提供以外的内容合法性负责。您不得在使用海豹骰服务时,导入包括但不限于以下情形的内容:
(1) 反对中华人民共和国宪法所确定的基本原则的;
(2) 危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;
(3) 损害国家荣誉和利益的;
(4) 煽动民族仇恨、民族歧视、破坏民族团结的;
(5) 破坏国家宗教政策,宣扬邪教和封建迷信的;
(6) 散布谣言,扰乱社会秩序,破坏社会稳定的;
(7) 散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;
(8) 侮辱或者诽谤他人,侵害他人合法权益的;
(9) 宣扬、教唆使用外挂、私服、病毒、恶意代码、木马及其相关内容的;
(10) 侵犯他人知识产权或涉及第三方商业秘密及其他专有权利的;
(11) 散布任何贬损、诋毁、恶意攻击海豹骰及开发人员、海洋馆工作人员、mod 编写人员、关联合作者的;
(12) 含有中华人民共和国法律、行政法规、政策、上级主管部门下发通知中所禁止的其他内容的。
一旦您有以上禁止行为,请立即停用海豹骰。
',18),n=[s];function c(_,r,i,l,d,h){return t(),p("div",null,n)}const f=e(o,[["render",c]]);export{u as __pageData,f as default}; diff --git a/assets/about_license.md.DYRuXiXr.lean.js b/assets/about_license.md.DYRuXiXr.lean.js new file mode 100644 index 000000000..d4a8966fe --- /dev/null +++ b/assets/about_license.md.DYRuXiXr.lean.js @@ -0,0 +1 @@ +import{_ as e,c as p,o as t,a8 as a}from"./chunks/framework.C7ysi3tu.js";const u=JSON.parse('{"title":"许可协议","description":"","frontmatter":{"lang":"zh-cn","title":"许可协议"},"headers":[],"relativePath":"about/license.md","filePath":"about/license.md","lastUpdated":1714442589000}'),o={name:"about/license.md"},s=a("",18),n=[s];function c(_,r,i,l,d,h){return t(),p("div",null,n)}const f=e(o,[["render",c]]);export{u as __pageData,f as default}; diff --git a/assets/about_start-from-zero.md.DjHXncO3.js b/assets/about_start-from-zero.md.DjHXncO3.js new file mode 100644 index 000000000..8936791f2 --- /dev/null +++ b/assets/about_start-from-zero.md.DjHXncO3.js @@ -0,0 +1 @@ +import{_ as a,c as e,o as t,a8 as r}from"./chunks/framework.C7ysi3tu.js";const _=JSON.parse('{"title":"从零开始","description":"","frontmatter":{"lang":"zh-cn","title":"从零开始"},"headers":[],"relativePath":"about/start-from-zero.md","filePath":"about/start-from-zero.md","lastUpdated":1720509767000}'),l={name:"about/start-from-zero.md"},o=r('首先感谢你选用海豹核心。
我们将本手册的所有内容按照从零开始到深入定制的操作顺序重新排列成了本节的内容。我们推荐新用户按此顺序阅读手册。
在阅读本节时,我们建议你读完整段再点击链接跳转到对应内容。读完对应内容后,你可以立即付诸实践。然后,建议你回到本节,继续按此顺序阅读剩余内容。
如果你只需要详细了解某个特定话题,请你直接从导航栏查找对应分类。
如果你对于服务器或电脑尚不了解,我们为你准备了简单的科普。请阅读 名词/术语速查表 和 如何高效学习。
海豹核心是由一群热心的开发者无偿开发并开源发布的。开发者们和一众热心群友在各个用户群中为用户提供答疑。为了你的疑问能够获得最快最好的解答,请阅读 如何正确提问。
首先,你需要让海豹核心在你的服务器或电脑上运行起来。
如果你是从零开始部署,请阅读 快速开始。
如果你之前部署过海豹核心,现在需要将旧部署转移到新的机器,请在阅读过新平台的部署方法后阅读 迁移。
海豹核心成功运行后,你需要将它接入你希望的即时通讯平台。这样它才能接收和发送消息。
海豹目前支持了许多平台,开列如下。如果你还希望海豹接入更多平台,请你提出 Issue,我们会尽可能满足你的需求。
海豹核心提供了许多指令,它们是所有功能的中坚。
请阅读 基础概念,了解指令的基本格式。
然后,你可以熟悉一下海豹核心提供的不同自定义功能。你可能暂时不会用到所有的功能。但是,首先熟悉海豹核心能做到、不能做到哪些事,会对你的使用大有帮助。
请阅读这一章下面的各节,你可以跳过一些细节问题: 配置。
我们推荐你简单浏览一遍各种指令。你不需要记住每个指令的所有细节,只要大概知道有什么指令,能够完成什么功能,就很足够了。
首先,请阅读 核心指令。这一节介绍了海豹核心最重要的一些指令。
然后,你可以阅读此章的剩余各节。它们按照扩展列出了每个指令的详细用法。
如果你希望自己动手,充分利用海豹核心提供的自定义能力,你需要对于海豹核心对不同功能的约定有所了解。
请你首先阅读 进阶介绍,然后阅读此章中你感兴趣的节。
',28),h=[o];function p(i,d,n,s,m,c){return t(),e("div",null,h)}const u=a(l,[["render",p]]);export{_ as __pageData,u as default}; diff --git a/assets/about_start-from-zero.md.DjHXncO3.lean.js b/assets/about_start-from-zero.md.DjHXncO3.lean.js new file mode 100644 index 000000000..ea838900e --- /dev/null +++ b/assets/about_start-from-zero.md.DjHXncO3.lean.js @@ -0,0 +1 @@ +import{_ as a,c as e,o as t,a8 as r}from"./chunks/framework.C7ysi3tu.js";const _=JSON.parse('{"title":"从零开始","description":"","frontmatter":{"lang":"zh-cn","title":"从零开始"},"headers":[],"relativePath":"about/start-from-zero.md","filePath":"about/start-from-zero.md","lastUpdated":1720509767000}'),l={name:"about/start-from-zero.md"},o=r("",28),h=[o];function p(i,d,n,s,m,c){return t(),e("div",null,h)}const u=a(l,[["render",p]]);export{_ as __pageData,u as default}; diff --git a/assets/advanced_README.md.PhrOw_Qu.js b/assets/advanced_README.md.PhrOw_Qu.js new file mode 100644 index 000000000..af585a717 --- /dev/null +++ b/assets/advanced_README.md.PhrOw_Qu.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as a,a8 as l}from"./chunks/framework.C7ysi3tu.js";const p=JSON.parse('{"title":"进阶","description":"","frontmatter":{"title":"进阶","index":false},"headers":[],"relativePath":"advanced/README.md","filePath":"advanced/README.md","lastUpdated":1721275296000}'),i={name:"advanced/README.md"},_=l('',1),r=[_];function s(d,h,c,o,m,n){return a(),t("div",null,r)}const u=e(i,[["render",s]]);export{p as __pageData,u as default}; diff --git a/assets/advanced_README.md.PhrOw_Qu.lean.js b/assets/advanced_README.md.PhrOw_Qu.lean.js new file mode 100644 index 000000000..85831b9d0 --- /dev/null +++ b/assets/advanced_README.md.PhrOw_Qu.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as a,a8 as l}from"./chunks/framework.C7ysi3tu.js";const p=JSON.parse('{"title":"进阶","description":"","frontmatter":{"title":"进阶","index":false},"headers":[],"relativePath":"advanced/README.md","filePath":"advanced/README.md","lastUpdated":1721275296000}'),i={name:"advanced/README.md"},_=l("",1),r=[_];function s(d,h,c,o,m,n){return a(),t("div",null,r)}const u=e(i,[["render",s]]);export{p as __pageData,u as default}; diff --git a/assets/advanced_edit_complex_custom_text.md.DSU9kc9u.js b/assets/advanced_edit_complex_custom_text.md.DSU9kc9u.js new file mode 100644 index 000000000..a2cc42bdf --- /dev/null +++ b/assets/advanced_edit_complex_custom_text.md.DSU9kc9u.js @@ -0,0 +1,18 @@ +import{_ as n,D as o,c as p,j as t,I as s,a8 as e,o as c}from"./chunks/framework.C7ysi3tu.js";const f=JSON.parse('{"title":"编写复杂文案","description":"","frontmatter":{"lang":"zh-cn","title":"编写复杂文案"},"headers":[],"relativePath":"advanced/edit_complex_custom_text.md","filePath":"advanced/edit_complex_custom_text.md","lastUpdated":1714442589000}'),i={name:"advanced/edit_complex_custom_text.md"},l=e(`本节内容
本节将展示复杂文案的编写技巧,请善用侧边栏和搜索,按需阅读文档。
在文案中通常会有结果变量,通过对结果变量的判断来实现展示不同文案。如修改 .r 骰点_单项结果文本
文案为例
{$t表达式文本}{$t计算过程}={$t计算结果}{%
+$t计算结果 == 100 ? " 乐!",
+$t计算结果 == 1 ? " 啧!"
+%}
正则匹配:mode switch(.*)
回复:
{%
+if $t1==""||$t1==" 默认" {$g文案模式 = 0; $t输出="默认模式启用"};
+if $t1==" 模式1" {$g文案模式 = 1; $t输出="模式1启用"};
+if $t1==" 模式2" {$g文案模式 = 2; $t输出="模式2启用"};
+if $t1!=""&&$t1!=" 默认"&&$t1!=" 模式1"&&$t1!=" 模式2" {$t输出=\`不存在指定模式{$t1}\`};
+$t输出
+%}
精确匹配:mode show
回复:
{%
+$g文案模式==0 ? "当前在默认模式",
+$g文案模式==1 ? "当前在模式1" ,
+$g文案模式==2 ? "当前在模式2"
+%}
按如下模式调整你的自定义文案项:
{
+if $g文案模式 == 0 {$t目标文案 = \`(这里填你个性化的默认模式文案)\`};
+if $g文案模式 == 1 {$t目标文案 = \`(这里填你个性化的模式1文案)\`};
+if $g文案模式 == 2 {$t目标文案 = \`(这里填你个性化的模式2文案)\`}
+}{$t目标文案}
以修改了 .jrrp
的文案为例:
本节内容
本节将介绍牌堆的编写,请善用侧边栏和搜索,按需阅读文档。
海豹核心目前支持 toml
、json
和 yaml
格式的牌堆。
如果对其中某种格式的语法有了解,建议选择熟悉的格式。如果都不了解,建议选择使用适用性最广的 json
格式。
当然,如果你的海豹版本较新,我们也非常推荐你尝试 toml
格式的牌堆编写。toml
格式诞生较晚,语法支持更多现代特性,海豹为这种格式的牌堆支持了更多功能。
注意:牌堆文件的编码
永远 使用「UTF-8 编码」来编写牌堆。
我们将简单介绍 toml
json
yaml
的语法,仅说明牌堆编写中会用到的部分,帮助你快速了解它们。
注意:务必注意使用半角符号!
下面的语法中涉及到的符号都是 半角符号,如果你出现了奇怪的问题,记得检查是否在输入法的中文输入状态,导致输入了错误的符号。
比如应该为 ,
却使用了 ,
,应该为 ""
却使用了 “”
。
使用专业的编辑器能帮助你检查这些问题。
海豹的抽取指令为 .draw <key>
,而牌堆就是可以提供一些 key 作为牌组的文件。
我们从编写一个最简单的牌堆开始,我们希望:
快端上来罢
和 数字论证
两个牌组; .draw 快端上来罢
可以抽取出 哼哼哼啊啊啊啊啊
你是一个一个一个牌堆结果
这两个结果;.draw 数字论证
可以抽取出 114514
1919810
这两个结果。这个牌堆编写如下,你可以选择以下任意一种格式来学习:
',5),Is=s("div",{class:"language-toml vp-adaptive-theme"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"toml"),s("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"}},"# 元信息表")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"["),s("span",{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"}},"meta"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"]")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"title = "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"野兽牌堆"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"author = "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"田所浩二"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"}},"# 有多个作者时可以使用,两者仅需保留一行")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"authors = ["),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"田所浩二"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"]")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"version = "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"1.0"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"license = "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"CC-BY-NC-SA 4.0"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"date = "),s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},"1919-08-10")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"update_date = "),s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},"1919-08-10")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"desc = "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"这个示例牌堆怎么这么臭(恼)"')]),i(` +`),s("span",{class:"line"}),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"}},"# 牌组表")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"["),s("span",{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"}},"decks"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"]")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},'"快端上来罢" = [')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "哼哼哼啊啊啊啊啊"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},",")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "你是一个一个一个牌堆结果"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"]")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},'"数字论证" = [')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "114514"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},",")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "1919810"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"]")])])])],-1),Ys=s("p",null,[i("一个 TOML 牌堆的最基本格式如上。其中 "),s("code",null,"meta"),i(" 表中的信息不是必须的,但我们非常建议你保留并填写这些项,它们能在分享时说明更多信息:")],-1),Js=s("ul",null,[s("li",null,[s("code",null,"title"),i(":牌堆的标题;")]),s("li",null,[s("code",null,"author"),i("/"),s("code",null,"authors"),i(":牌堆作者;")]),s("li",null,[s("code",null,"date"),i(":牌堆创建日期;")]),s("li",null,[s("code",null,"updateDate"),i(":牌堆更新日期;")]),s("li",null,[s("code",null,"desc"),i(":牌堆简介;")]),s("li",null,[s("code",null,"version"),i(":牌堆版本;")]),s("li",null,[s("code",null,"license"),i(":牌堆协议。")])],-1),Vs=s("p",null,[i("可以将上述内容保存名为 "),s("code",null,"野兽牌堆.toml"),i("(名称任意,但必须是以 "),s("code",null,".toml"),i(" 为后缀扩展名)的文件进行测试。")],-1),$s=s("div",{class:"language-json vp-adaptive-theme"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"json"),s("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"{")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "_title"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [ "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"野兽牌堆"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ],")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "_author"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [ "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"田所浩二"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ],")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "_date"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [ "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"1919-08-10"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ],")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "_updateDate"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [ "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"1919-08-10"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ],")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "_brief"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [ "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"这个示例牌堆怎么这么臭(恼)"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ],")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "_version"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [ "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"1.0"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ],")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "_license"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [ "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"CC-BY-NC-SA 4.0"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ],")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "快端上来罢"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "哼哼哼啊啊啊啊啊"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},",")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "你是一个一个一个牌堆结果"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ],")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "数字论证"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "114514"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},",")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "1919810"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ]")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"}")])])])],-1),Us=s("p",null,[i("一个 JSON 牌堆的最基本格式如上。其中以 "),s("code",null,"_"),i(" 开头的项不是必须的,但是我们非常建议你保留并填写这些项,它们能在分享时说明更多信息:")],-1),Rs=s("ul",null,[s("li",null,[s("code",null,"_title"),i(":牌堆的标题;")]),s("li",null,[s("code",null,"_author"),i(":牌堆作者;")]),s("li",null,[s("code",null,"_date"),i(":牌堆创建日期;")]),s("li",null,[s("code",null,"_updateDate"),i(":牌堆更新日期;")]),s("li",null,[s("code",null,"_brief"),i(":牌堆简介;")]),s("li",null,[s("code",null,"_version"),i(":牌堆版本;")]),s("li",null,[s("code",null,"_license"),i(":牌堆协议。")])],-1),Ks=s("p",null,[i("可以将上述内容保存名为 "),s("code",null,"野兽牌堆.json"),i("(名称任意,但必须是以 "),s("code",null,".json"),i(" 为后缀扩展名)的文件进行测试。")],-1),zs=s("div",{class:"language-yaml vp-adaptive-theme"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"yaml"),s("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"name"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"野兽牌堆")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"author"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"田所浩二")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"version"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": "),s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},"1")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"license"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"CC-BY-NC-SA 4.0")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"desc"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"这个示例牌堆怎么这么臭(恼)")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"command"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},":")]),i(` +`),s("span",{class:"line"}),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"快端上来罢"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},":")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," - "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"哼哼哼啊啊啊啊啊")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," - "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"你是一个一个一个牌堆结果")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"数字论证"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},":")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," - "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"'114514'")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," - "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"'1919810'")])])])],-1),Ws=s("p",null,"一个 YAML 牌堆的最基本格式如上。其中上半部分的一些项不是必须的,但是我们非常建议你保留并填写这些项,它们能在分享时说明更多信息:",-1),Gs=s("ul",null,[s("li",null,[s("code",null,"name"),i(":牌堆的标题;")]),s("li",null,[s("code",null,"author"),i(":牌堆作者;")]),s("li",null,[s("code",null,"version"),i(":牌堆版本,应是一个整数;")]),s("li",null,[s("code",null,"license"),i(":牌堆协议;")]),s("li",null,[s("code",null,"desc"),i(":牌堆简介。")])],-1),Zs=s("p",null,[i("可以将上述内容保存名为 "),s("code",null,"野兽牌堆.yaml"),i("(名称任意,但必须是以 "),s("code",null,".yaml"),i(" 或 "),s("code",null,".yml"),i(" 为后缀扩展名)的文件进行测试。")],-1),Hs=e('注意:牌堆文件扩展名正确吗?
保存文件时务必确认开启了操作系统的扩展名显示,避免出现看上去保存了 xxx.json
文件,但实际上的文件名是 xxx.json.txt
。
牌堆抽取结果字符串中,可以实现抽取其他项,将内容拼接进该结果。
{key}
表示不放回抽取;{%key}
表示放回抽取。放回抽取,指此次抽取后抽取的牌组保持原样,每次都相当于从全新牌组中抽取,你仍可能抽到相同结果。
不放回抽取,尺度是一次抽牌指令,在这一次指令的执行过程中不会再从该牌组中抽到相同结果。多次抽取指令 draw 3# 牌组
只被视为一次指令,不放回抽取生效。指令执行结束后,将重置所有牌组到初始状态。
需要提到,带有权重调整的项目不会被视为在牌组中有多个副本。在下文中有详细讨论。
示例:不放回抽取
[decks]
+"拷打木落" = [
+ "泡面偷走叉子调料包",
+ "捏碎所有薯片",
+ "用勺子把西瓜最中间的一块全挖走",
+]
+"拷打木落三次" = [ "{%拷打木落} {%拷打木落} {%拷打木落}" ]
+"不同方式拷打木落三次" = [ "{拷打木落} {拷打木落} {拷打木落}" ]
在此示例中,牌组「拷打木落三次」的项目采用了放回抽取,「不同方式拷打木落三次」采用了不放回抽取。
如果抽取 draw 拷打木落三次
,得到的三个结果可能有重复。
如果抽取 draw 不同方式拷打木落三次
,得到的三个结果将互不相同,但顺序是随机的。通过 draw 3# 拷打木落
也可达成类似的效果。
抽取结果字符串中的 [exp]
,会先执行其中的掷骰表达式 exp
,再组合到原字符串里。
如:抽取 企鹅早该爆金币辣!v我[1d10]个金币
的结果可能是 企鹅早该爆金币辣!v我1个金币
。
研究旧牌堆,可能会发现有牌堆通过下面这样的写法来实现这个功能,现在请不要这么做了。
{
+ "数字": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+ "爆金币": "企鹅早该爆金币辣!v我{%数字}个金币"
+}
抽取结果字符串中最前面的 ::value::
表示该项的权重。不含有该标记的项目被视为拥有默认权重 1。
示例:有 1/10 的几率出现闪光海豹
`,16),li=s("div",{class:"language-toml vp-adaptive-theme"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"toml"),s("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"["),s("span",{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"}},"decks"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"]")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},'"捕捉海豹" = [')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "::9::海豹"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},",")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "✨闪光海豹✨"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"]")])])])],-1),ni=s("div",{class:"language-json vp-adaptive-theme"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"json"),s("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"{")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "捕捉海豹"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "::9::海豹"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},",")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "✨闪光海豹✨"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ]")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"}")])])])],-1),ti=s("div",{class:"language-yaml vp-adaptive-theme"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"yaml"),s("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"捕捉海豹"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},":")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," - "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"::9::海豹")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," - "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"✨闪光海豹✨")])])])],-1),hi=e(`在使用不放回抽取的情况下,标记权重的项目不会被视为单个项目的多个副本。以上文的例子而言,牌组中仍被视为只有 2 个项目。
如果使用 draw 捕捉海豹 2#
进行抽取,一定会获得 1 个「海豹」和 1 个「✨闪光海豹✨」。
在这种情况下,权重影响的是抽出的顺序。你将以 9/10 的概率先抽出「海豹」,以 1/10 的概率先抽出「✨闪光海豹✨」。
研究旧牌堆,可能会发现有牌堆通过下面这样的写法来实现这个功能,现在请不要这么做了。
{
+ "捕捉海豹": [
+ "海豹", "海豹", "海豹", "海豹", "海豹", "海豹", "海豹", "海豹", "海豹",
+ "✨闪光海豹✨"
+ ]
+}
抽取结果字符串中可以插入 CQ 码和海豹码,比如 [图:data/images/sealdice.png]
。
发布带图牌堆最常见的选择是牌堆文件连同图片打包成一个压缩包,但是这样做有诸多不便之处。
如果你不想发布时在压缩包里添加一个教程,告诉你的用户如何解压、图片要放到那个文件夹下、什么是相对路径……那就把牌堆打包成可以在海豹 UI 直接上传的 *.deck
文件吧。
信息
deck
文件在本质上依然是 zip
文件,修改后缀只是便于海豹识别。
假如牌堆文件内容如下(使用相对路径 ./asstes/...
):
{
+ "test":["[图:./asstes/1.jpg]"]
+}
则牌堆文件所在文件夹结构应是:
.
+├─asstes
+│ └─1.jpg
+└─ test.json
选中牌堆文件和 assets 文件夹压缩为 ZIP 文件,修改文件后缀为 deck 。
图例中所使用的软件为 Bandizip ,使用 Windows 右键菜单中的 压缩为 ZIP 文件
与之等价的。
注意:小心嵌套文件夹
以示例图中路径为例,不要回退到上一级目录然后选中 top 文件夹压缩。
.draw keys
指令会列出所有允许抽取的牌组,但在牌堆编写过程中,经常会需要用到辅助的牌组,这些辅助项是不希望暴露给用户的。我们可以通过一定方式来隐藏这些项。
提示:UI 中识别隐藏的牌组
你可以通过查看牌堆管理界面中的「牌堆列表」来识别牌组是否隐藏。
灰色的牌组是隐藏的,即不展示在列表中,但能够被 .draw <key>
抽取。
未导出(不展示也不能抽取)的牌组不展示在该列表中。
很多时候,牌堆内容不是一成不变的。而使用牌堆的用户需要手动去获取最新的牌堆,才能获得作者们提供的最新内容。
我们为牌堆提供了配置更新链接的机制,方便骰主快速获取牌堆更新。有能力的牌堆作者可以配置更新链接,便于分享最新的牌堆内容。
',4),Ai=s("p",null,[i("配置牌堆文件的 "),s("code",null,"updateUrls"),i(" 以指定对应的更新链接:")],-1),xi=s("div",{class:"language-toml vp-adaptive-theme"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"toml"),s("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"["),s("span",{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"}},"meta"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"]")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"title = "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"野兽牌堆"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"updateUrls = [")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "https://updateurl.com"'),s("span",{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"}}," # 此处填写你的更新链接")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"]")]),i(` +`),s("span",{class:"line"}),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#6A737D","--shiki-dark":"#6A737D"}},"# 牌组表")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"["),s("span",{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"}},"decks"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"]")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},'"快端上来罢" = [')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "哼哼哼啊啊啊啊啊"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},",")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "你是一个一个一个牌堆结果"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"]")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},'"数字论证" = [')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "114514"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},",")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "1919810"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"]")])])])],-1),fi=s("p",null,[i("配置牌堆文件的 "),s("code",null,"_updateUrls"),i(" 以指定对应的更新链接:")],-1),Di=s("div",{class:"language-json vp-adaptive-theme"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"json"),s("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"{")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "_title"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [ "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"野兽牌堆"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ],")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "_updateUrls"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [ "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},'"https://updateurl.com"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ],")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "快端上来罢"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "哼哼哼啊啊啊啊啊"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},",")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "你是一个一个一个牌堆结果"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ],")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#005CC5","--shiki-dark":"#79B8FF"}},' "数字论证"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": [")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "114514"'),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},",")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},' "1919810"')]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," ]")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},"}")])])])],-1),Ti=s("p",null,[i("配置牌堆文件的 "),s("code",null,"update_urls"),i(" 以指定对应的更新链接:")],-1),Pi=s("div",{class:"language-yaml vp-adaptive-theme"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"yaml"),s("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"name"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},": "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"野兽牌堆")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"update_urls"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},":")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," - "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"'https://updateurl.com'")]),i(` +`),s("span",{class:"line"}),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"快端上来罢"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},":")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," - "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"哼哼哼啊啊啊啊啊")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," - "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"你是一个一个一个牌堆结果")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#22863A","--shiki-dark":"#85E89D"}},"数字论证"),s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}},":")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," - "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"'114514'")]),i(` +`),s("span",{class:"line"},[s("span",{style:{"--shiki-light":"#24292E","--shiki-dark":"#E1E4E8"}}," - "),s("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}},"'1919810'")])])])],-1),ji=e('有多个更新链接时,海豹将依次从上往下检查更新。
抽取结果字符串中可以插入 海豹语 。
提示:特殊的括号
与在文案和自定义回复中插入海豹语使用 {}
不同,在牌堆中 {}
有其他含义,需要用 []
代替 {}
。
TOML 牌堆中,你可以以表的形式来创建更复杂的牌组,这允许你更精细的控制每个牌组的选项。
复杂牌组和普通牌组在使用上没有什么区别,仅仅是提供了配置选项的能力。
[meta]
+title = "野兽牌堆"
+updateUrls = [
+ "https://updateurl.com" # 此处填写你的更新链接
+]
+
+# 简单牌组表
+[decks]
+"快端上来罢" = [
+ "哼哼哼啊啊啊啊啊",
+ "你是一个一个一个牌堆结果"
+]
+
+# 复杂牌组
+["数字论证"]
+export = true
+visible = true
+aliases = [ "恶臭论证" ]
+options = [
+ "114514",
+ "1919810",
+]
表名将作为这个牌组的名称,复杂牌组提供以下选项:
export
:是否导出该牌组;visible
:该牌组是否可见,不可见的将不展示在 .draw keys
列表中;aliases
:牌组的别名,可以使用别名抽取改牌组,如上述示例中可以使用 .draw 数字论证
或 .draw 恶臭论证
来抽取;options
:该牌组的项。注意:设置的选项未生效?
注意,对应的选项名需要完全一致,否则海豹将无法正确解析。
如 aliases
不要误写成 alias
,options
不要误写成 option
。
某些情况下,我们希望牌堆内容能够自动更新,骰主无需反复更新骰子中装载的牌堆,用户也能抽取到最新内容。例如,一个实时更新的公骰列表牌堆。
海豹支持为牌堆提供云端内容,这需要牌堆作者有能力提供一个 API 接口,每次 配置了接口的牌组被抽取的时候,都会调用该接口获取该牌组的最新数据。
注意:不受控的云端内容
海豹核心无法核查从接口处获取的数据,骰主需要自行确认数据源不会返回你不希望骰子发出的信息。
对于提供了云端内容的牌堆,在海豹 Web UI 的牌端管理界面,会强制增加相应标识,以显示该牌堆提供了云端内容。
海豹将以 HTTP GET 的方式请求对应接口,接口应当返回一个 JSON 字符串数组数据,其每个元素均是牌组的一个条目。
返回结果示例如下:
[
+ "3 = 11*-4+51-4",
+ "114 = 11*4+5*14",
+ "514 = (1-1)/4+514"
+]
目前仅有 TOML 牌堆支持云端内容,示例如下:
[meta]
+title = "野兽牌堆-云端内容示例"
+
+["114514种论证"]
+export = true
+visible = true
+options = [
+ "1 = 11/(45-1)*4",
+ "2 = -11+4-5+14",
+ "3 = 11*-4+51-4"
+]
+# 开启云内容
+cloud_extra = true
+# 云内容与本地内容去重
+dictinct = true
+# 云内容链接
+options_urls = [
+ "此处替换为对应的 API 链接,类似 http://example.com/xxx",
+]
假定上面填入了 API,它会返回上一节中的示例 JSON 字符串数组,即 ["3 = 11*-4+51-4", "114 = 11*4+5*14", "514 = (1-1)/4+514"]
。
正如上面示例中看到的那样,在 TOML 格式的复杂牌组中,有一些云端内容的相关配置项,每个牌组都可以单独控制云端相关设置。抽取牌组时,接口返回的数据将和牌组本身配置的 options
数组合并,最后的抽取是从合并的数组中进行。
cloud_extra
:为 true
时,抽取本牌组时将请求接口获取云端数据;options_urls
:配置云端内容的 API 链接,链接能够配置多个。每次抽取这个牌组时,会依次调用配置的 API,在任意一个接口成功返回数据后即停止;distinct
:为 true
时,云端数据与本地数据合并后会进行去重,确保最终抽取的牌组中不含重复条目。以上述牌组为例,当执行 .draw 114514种论证
的时候,将首先调用 API 获取云端内容,成功获取后进行合并并去重,最终抽取的牌组相当于直接配置了这样的牌组:
["114514种论证"]
+export = true
+visible = true
+options = [
+ "1 = 11/(45-1)*4",
+ "2 = -11+4-5+14",
+ "3 = 11*-4+51-4",
+ "114 = 11*4+5*14",
+ "514 = (1-1)/4+514"
+]
你可以直接按照以下格式书写 <helpdoc>.json
:
{
+ "mod": "名字",
+ "author": "作者",
+ "brief": "概述",
+ "comment": "备注",
+ "helpdoc": {
+ "词条A": "词条 A 的具体内容。",
+ "词条B": "词条 B 的具体内容。",
+ "词条C": "[图:data/images/sealdice.png] 词条 C 的图片与内容。"
+ }
+}
若你的文本需要换行,你可以使用 \\n
作为换行符,而不是在编写时直接换行,这会导致格式错误。你还可以使用 \\f
或 {FormFeed}
作为分页符。
注意:标准 JSON 而不是 JSON5
海豹支持的 JSON 帮助文档是「标准 JSON」格式,诸如注释,尾逗号等语法不属于 JSON 语法,而是 JSON5 这个扩展标准的语法,出错时请严格检查。
目前暂不考虑支持 JSON5 标准。
注意:文件编码
请永远使用 UTF-8 编码编写 helpdoc。
你可以在 JSON 格式的帮助文档中嵌套引用其它条目:
{
+ "helpdoc": {
+ "词条A": "词条 A 的具体内容。",
+ "词条B": "{词条A}词条 B 的具体内容。",
+ "词条C": "你还可以 {词条A}\\n{词条B}"
+ }
+}
如此一来当你发送 .help 词条 B
时,骰子将回复:「词条 A 的具体内容。词条 B 的具体内容。」。
{
+ "mod": "KIYTarot",
+ "author": "浣熊旅記",
+ "brief": "KIY 塔罗牌",
+ "comment": "释义来源网络。",
+ "helpdoc": {
+ "愚者逆位": "漂泊,冒险,鲁莽,冒失,疯狂,无视物质损失,灵魂堕落,内心空虚,感情轻浮。",
+ "魔术师逆位": "方向错误,被骗或失败,局面失控,二流角色,缺乏热忱和创造力,爱情难有进展。",
+ "女祭司逆位": "挑剔,贪心,目光短浅,洁癖,不适宜的激情,自尊心太强,锋芒外露,单相思。",
+ "女皇逆位": "冷淡,缺乏上进心,困难,享乐,环境险恶,贴近自然,自负,纠纷,感情挫折。",
+ "皇帝逆位": "幼稚,挫折,武断,滥用权利,冷酷,占有欲和控制力强烈,感情勉强。"
+ }
+}
你还可以按照以下格式书写 <helpdoc>.xlsx
:
Key | Synonym | Content | Description | Catalogue | Tag |
---|---|---|---|---|---|
词条 A | 词条同义词 | 词条内容 | 对词条的简述 | 所属目录 | 内容 TAG |
海豹骰 | 豹骰/海豹/sealdice | 一个简单易用的跑团骰子系统。\\n形象是海豹,可以被叫做海豹骰、豹骰,豹子骰之类。 |
Excel 格式的 helpdoc 分为 Key
、Synonym
、Content
、Description
、Catalogue
、Tag
六块,其中第二列、后三项为选填,一般情况下你都可以留空。
Synonym
列可以填写多个同义词,使用 /
分隔即可。\\n
来换行,但不支持其它换行符与分页符),因此它常用来快速编写有大段文字的帮助文档。测试
,则对应的词条会显示为 测试:词条 A
。由于海豹的 .find
指令会对词条进行模糊检索,所以你不必担心前缀对查询带来的影响。注意:必须保留首行!
不要删去第一行的 Key
、Synonym
等词。请从第二行开始编写词条。
提示:EXCEL 格式的帮助文档
<helpdoc>.xlsx
原本是梨骰用于 D&D 词条查询的格式,但海豹同样可以读取它。
本节内容
本节将展示自定义回复编写的进阶部分,请善用侧边栏和搜索,按需阅读文档。
我们假定你已经阅读了自定义回复编写的 基础部分(「配置 - 自定义回复」节)。如果你还没有阅读该章节,请先阅读这一节。
在部分内容中,我们假定你了解海豹变量机制和正则表达式。如果你对正则表达式还很陌生,可以参考以下材料,或在互联网自行搜索学习。
海豹核心跟随 Go 语言,采用 RE2 规范。该规范与其他规范在个别扩展语法上存在细微差别,请注意辨析。
在使用 Regex 101 网站时,请注意在左侧的「FLAVOR」栏选中「Golang」,以确保正则表达式的表现一致。
首先我们打开「自定义回复」页,新建一项自定义回复。
提示:使用 .text 帮助测试
.text <文本>
将是你的一大助力,它会输出后面文本的执行结果,你可以将它看成不需要触发词的自定义回复。
当然,在采用 .text
指令进行 debug 时,可能出现因为测试的内容对变量造成影响,而不得不频繁复制黏贴清空指令的情况。此时建议专门开一个一两个字即可触发的自定义回复,用来测试。
提示:嵌入 CQ 码
返回内容可以嵌入 CQ 码。CQ 码文档见 CQ 码列表。
自定义回复中分「条件」和「结果」两个部分。在 配置 - 自定义回复 中,我们已经了解了一些简单的回复编写,下面将补充介绍进阶内容。
模糊匹配将比较收到的消息与指定文本,内容相似时触发。
比较定义在 dice/ext_reply_logic.go
中,内容 jaro 和 hamming 平均值高于阈值(0.7)被认为是相似:
func strCompare(a string, b string) float64 {
+ va := strsim.Compare(a, b, strsim.Jaro())
+ vb := strsim.Compare(a, b, strsim.Hamming())
+ return (va + vb) / 2
+}
提示:模糊匹配未命中
模糊匹配可能出现感觉能匹配上,但实际上没办法匹配上的情况。我们更建议你使用「包含文本」或「正则匹配」。
使用正则匹配时,直接在要匹配的文本一栏中写入正则表达式即可。当正则表达式获得匹配时,该条件得到满足。
对于正则表达式中的捕获组,海豹遵循命令行脚本的通行做法,将其按顺序存储于多个变量 $t1
$t2
等。
正则匹配:^购买(.+)
+输出文本:{$t玩家}购买了{$t1}
可以发现第 1 捕获组命中的字符串 猫粮
,被存储于变量 $t1
中。
此外,对于正则表达式匹配的完整内容将被存储于变量 $t0
。如果捕获组存在命名,如命名为 A
的捕获组 (?P<A>cc)
,命中的字符串也将额外存入变量 $tA
。
注意:不要使用某些前缀!
由于 .
。
/
!
等符号会作为海豹中指令的前缀,因此作为前缀时可能导致将其识别为指令而非自定义回复的情况,建议换成别的前缀。
提示:正则中匹配 CQ 码
海豹支持用正则匹配 CQ 码。但是,由于 [
]
符号在正则表达式中含有特殊含义,如果你这么做了,请对它们进行转义。
示例:^\\[CQ:xxx,xx=xxx\\]
在「表达式为真」匹配中,只需要直接在匹配文本中写出形如 变量名 == 需要的值
的形式即可,不需要使用任何 {}
。
在回复文本中,可以调用一些 变量,也可以嵌入 内置脚本语言。
提示:未赋值的变量
所有变量在未被赋值时被调用的值都为 0。
{% %}
被称为执行块。由 {% %}
括起来的部分会被作为代码执行,输出最后一个语句的结果。执行块中的两个语句之间要使用 ;
隔开。
如果想要输出字符串,则应该用 \`
/ "
/ '
括上。
{% \\$t1="114514" %}
的式子会输出 114514
。提示:结果中调用变量
若想在结果中调用变量,请用反引号。目前海豹只支持在反引号中调用变量。
下面是一个简单的示例,这里的第一句也是最后一句,所以它的结果会被输出。
.text {% $t测试=1 %}
if
{%
+$t测试=1;
+if $t测试==1 {$t输出="赞美木落"};
+if $t测试==2 {$t输出="快点更新"};
+if $t测试!=2&&$t测试!=1 {$t输出="群主女装"}
+%}
+{$t输出}
首先给 $t测试
赋值为 1,然后进入判断:
$t测试
等于 1,则变量 $t输出
等于 "赞美木落"
;$t测试
等于 2,则变量 $t输出
等于 "快点更新"
;$t测试
既不等于 2 也不等于 1,则变量 $t输出
等于 "群主女装"
;最后,输出变量 $t输出
。
提示:容易误解的 if 使用
目前海豹语并不支持 else if
/ elif
,if
和 else
是一对一匹配的。所以当形如:
if <条件 1> {<执行 1>};
+if <条件 2> {<执行 2>}
+else {<执行 3>}
的语句出现时,第二个 if
与 else
为一个整体,且不与第一个 if
构成 if elseif else
的关系!即使 <条件 1>
满足,其仍会对 <条件 2>
进行判断,并在 <执行 2>
与 <执行 3>
中择一执行!
{%
+$t0 = 1;
+$tRand = d6;
+if $t0 == 1 { $t0 = $t0 + $tRand }
+%}
+{$t0}
在这个例子里,我们先给 $t0
赋值为 1,然后判断 $t0
是否等于 1,若通过则 $t0
的值增加 1d6,最后输出结果。
以养猫功能中猫好感度为例:
{%
+$mCatFavor<=100 ? \`#{DRAW-第一档猫好感}\`,
+$mCatFavor<=250 ? \`#{DRAW-第二档猫好感}\`,
+$mCatFavor<=600 ? \`#{DRAW-第三档猫好感}\`,
+$mCatFavor<=1500 ? \`#{DRAW-第四档猫好感}\`,
+$mCatFavor<=2500 ? \`#{DRAW-第五档猫好感}\`,
+1 ? \`#{DRAW-第六档猫好感}\`
+%}
在这一脚本中,根据猫好感度 $mCatFavor
不同,需要输出不同回复的机制。
除了形如
{%
+判断1 ? \`#{DRAW-牌组1}\`,
+判断2 ? \`#{DRAW-牌组2}\`,
+判断3 ? \`#{DRAW-牌组3}\`,
+1 ? \`#{DRAW-牌组4}\`,
+%}
的写法外,还可以使用:
{%
+if 判断1 {
+ $tRand=d6;
+ $t输出=$tRand==1 ? \`内容1\`,
+ $t输出=$tRand==2 ? \`内容2\`,
+ $t输出=$tRand==3 ? \`内容3\`,
+ $t输出=$tRand==4 ? \`内容4\`,
+ $t输出=$tRand==5 ? \`内容5\`,
+ $t输出=$tRand==6 ? \`内容6\`
+};
+if 判断2 { $tRand=d10; $t输出 = $tRand==1 ? \`内容1\`, …… };
+if 判断3 { $tRand=d10; $t输出 = $tRand==1 ? \`内容1\`, …… };
+%}
实质上没有太大区别,可根据喜好选择。
自定义回复中的多行输出通常并不是:
{$t输出0}
+{$t输出1}
+{$t输出2}
+{$t输出3}
而是在为四个 $t输出
变量赋值时,于内部写入 \\n
,自定义回复中则采用 {$t输出0}{$t输出1}{$t输出2}{$t输出3}
的形式。
这是因为如果某一变量可能为空时,如果采用第一种分行的写法,会出现这样的效果:
`,28),f={class:"info custom-block"},q=a("p",{class:"custom-block-title"},"示例",-1),E=e(`可以看见,在中间会出现突兀的空行。这是因为虽然 $t输出2
变量为空,海豹仍会按照自定义回复中设置的格式输出。因此在对应 $t输出2
的一行中,海豹会首先输出空变量 {$t输出2}
,随后按自定义回复的格式进行换行,导致发生空行。
所以,如果想要在某个变量为空时,不让用户注意到突兀的空缺,就最好多做几次实验,好好规划一下换行符 \\n
的位置。
海豹提供了一系列时间变量来调用,以打卡指令为例,可以采用如下两种写法中的一种(示例为每人每天一次,如要每群自行将 $m
换成 $g
):
写法 1
文本匹配:你需要的文本
表达式为真:$m变量 != $tDate
回复:{if 1 {$m变量=$tDate}}你需要的回复文本
$m变量
作为标记变量,用 if
是防止它出现在回复文本中。文本匹配:你需要的文本
表达式为真:$m变量==$tDate
回复:在一天触发多次时的回复
写法 2
文本匹配:你需要的文本
回复:
{%
+if $m变量 != $tDate {
+ $t输出 = \`你需要的回复文本\`;
+ $m变量 = $tDate //对其赋值,作为标记
+} else {
+ $t输出 = \`在一天触发多次时的回复\`
+}
+%}
+{$t输出}
以上两种写法实现效果没有差别,具体使用哪种请自行决断。
以石头剪刀布为例,为了实现骰子随机出招的效果,令 $tRand=d3
,然后根据 $tRand
的情况赋值 $tDicePlay
。通过骰子出招和玩家出招两个变量判断,输出游戏结果,并记录场次。
为了防止直接使用 {%%}
进行赋值导致的变量显示,需要在最外面写 if 1
,则在生成 $tRand
之后再次判断的时候,可以使用 $tDicePlay=条件算符
,或是再新开一行用条件算符或者条件语句实现。
以下给出在同一个 if
内直接赋值的写法和新开一行使用条件语句的写法:
{% //在同一个if内直接赋值。可以在赋值时使用条件算符。
+if 1 {
+ $tRand=d3;
+ $tDicePlay = $tRand==1 ? "石头",
+ $tRand==2 ? "剪刀",
+ 1 ? "布"
+}
+%}
+
+{% //新开一行赋值
+if 1 {
+ $tRand=d3;
+};
+if $tRand==1 {$tDicePlay="石头"};
+if $tRand==2 {$tDicePlay="剪刀"};
+if $tRand==3 {$tDicePlay="布"}
+%}
两种写法实现效果相同,石头剪刀布内在这里采用了第一种,实际上没有差别。
生成骰子出招并获取玩家出招之后,就开始判断。这里除了平局可以使用 $tDicePlay==$t0
省事之外,其他的都需要在条件中用多个进行嵌套。
注意:条件判断
豹语语法中,判断时条件中的 ||
和 &&
是从左往右计算的,如果后面有需要优先计算与或的东西,请加好括号。
if $t0 == $tDicePlay {
+ $t输出 = \`那我出{$tDicePlay}!{$t玩家}出的是{$t0}啊,我们平局了。\`;
+ $mPlayerTime = $mPlayerTime + 1
+};
+if ($t0=="剪刀" && $tDicePlay=="石头") || ($t0=="石头" && $tDicePlay=="布") || ($t0=="布" && $tDicePlay=="剪刀") {
+ $t输出 = \`那我出{$tDicePlay}!{$t玩家}出的是{$t0}啊,我赢了。\`;
+ $mPlayerTime = $mPlayerTime + 1;
+ $mPlayerLose = $mPlayerLose + 1
+};
+if ($t0=="石头" && $tDicePlay=="剪刀") || ($t0=="布" && $tDicePlay=="石头") || ($t0=="剪刀" && $tDicePlay=="布") {
+ $t输出 = \`那我出{$tDicePlay}!{$t玩家}出的是{$t0}啊,你赢了。\`;
+ $mPlayerTime = $mPlayerTime + 1;
+ $mPlayerWin = $mPlayerWin + 1
+}
AxxxxxxxB
型文本 前缀匹配:A
后缀匹配:B
回复:怎么辉石呢
具体实现,第一段
第二段
这两段较为常规,第三段开始变形了:
{%
+ if $mStory == 4 { $mStory = 5 };
+ if $mStory == 3 { $mStory = 4 };
+ if $mStory == 2 { $mStory = 3 };
+
+ $mStory == 3 ? '这个村子有一户人家,门前有两棵树',
+ $mStory == 4 ? '一棵是函树,一棵是反函树',
+ $mStory == 5 ? '我讲完了。',
+ 1 ? '?'
+%}
+
+{% if $mStory == 5 { $mStory=0 } %}
本节内容
本节将介绍敏感词库的编写,请善用侧边栏和搜索,按需阅读文档。
你可以直接按照以下格式书写 <words>.txt
:
#notice
+提醒级词汇 1
+提醒级词汇 2
+
+#caution
+注意级词汇 1
+注意级词汇 2
+
+#warning
+警告级词汇
+
+#danger
+危险级词汇
你可以直接按照以下格式书写 <words>.toml
:
# 元信息,用于填写一些额外的展示内容
+[meta]
+# 词库名称
+name = '测试词库'
+# 作者,和 authors 存在一个即可
+author = ''
+# 作者(多个),和 author 存在一个即可
+authors = [ '<匿名>' ]
+# 版本,建议使用语义化版本号
+version = '1.0'
+# 简介
+desc = '一个测试词库'
+# 协议
+license = 'CC-BY-NC-SA 4.0'
+# 创建日期,使用 RFC 3339 格式
+date = 2023-10-30
+# 更新日期,使用 RFC 3339 格式
+updateDate = 2023-10-30
+
+# 词表,出现相同词汇时按最高级别判断
+[words]
+# 忽略级词表,没有实际作用
+ignore = []
+# 提醒级词表
+notice = [
+ '提醒级词汇 1',
+ '提醒级词汇 2'
+]
+# 注意级词表
+caution = [
+ '注意级词汇 1',
+ '注意级词汇 2'
+]
+# 警告级词表
+warning = [
+ '警告级词汇 1',
+ '警告级词汇 2'
+]
+# 危险级词表
+danger = [
+ '危险级词汇 1',
+ '危险级词汇 2'
+]
本节内容
本节将介绍海豹扩展功能的进阶内容,请善用侧边栏和搜索,按需阅读文档。
海豹核心提供了多种扩展功能,这些功能能让你实现更多的个性化功能,但通常也需要你具备一些知识。
我们建议,在尝试使用对应功能进行个性化前,先阅读本章的对应小节,帮助你快速掌握相关内容。
阅读完小节教程后,如果在使用中依然存在疑问,可以在官方群内寻求帮助。
在很多地方为了实现一些逻辑,都需要用到海豹所提供的一种简单的脚本语言,可以称为「海豹语」「豹语」等。
我们非常建议你在扩展功能的编写中,先了解海豹语。见 扩展-内置脚本语言。
在牌堆、JS 插件、敏感词库等的编写中,我们通常需要在对应格式的文件进行编写。
务必使用更专业的编辑器,如果你不知道选择什么,可以尝试使用 vscode、sublime text或 MT 管理器(Android)等,这些都是被广泛使用的优秀编辑器。
更专业的编辑器能更好地帮助你编写相关格式的文件。这些编辑器提供的语法高亮、自动补全、格式化、错误检查、编码转换等诸多功能,能为你带来更好的编写体验。
不建议使用 Windows 记事本、Notepad++ 编写。
提示:如何获取安装包?
官网应当是你下载软件的首选途径,请不要被搜索引擎等误导进各种下载站,它们很可能会提供二次封装后、捆绑大量流氓软件的安装包。
但由于各种原因,官网下载可能会十分缓慢,你还可以选择从海豹官方群的群文件中获取安装包。通常群文件的「工具&内置文件」中,请善用搜索。
本节内容
本节为海豹提供的 JS API 列表。目前的内容是从上古文档中直接迁移过来的,难免存在错误和缺失,参考本节时请注意识别。
更好的方式是参考海豹提供的 seal.d.ts 文件。(但同样存在缺失)
如果你需要最准确的内容,当前只能查阅海豹源码。主要查看 dice_jsvm.go,还有一些 API 以结构体方法的形式存在。
其中 ctx 为信息的 MsgContext,msg 为信息的 Message,一般会在定义指令函数时就声明,如:
cmd.solve = (ctx, msg, cmdArgs) => {
+ someFunction;
+}
下面是 api 的说明(完全了吧......应该?):
//被注释掉的 api 是可以提供的,但是在源码中被注释。
+//seal.setVarInt(ctx, \`$XXX\`, valueToSet) //\`$XXX\`即 rollvm(初阶豹语)中的变量,其会将$XXX 的值设定为 int 类型的 valueToSet。
+//seal.setVarStr(ctx, \`$XXX\`, valueToSet) //同上,区别是设定的为 str 类型的 valueToSet。
+seal.replyGroup(ctx, msg, something) //向收到指令的群中发送 something。
+seal.replyPerson(ctx, msg, something) //顾名思义,类似暗骰,向指令触发者(若为好友)私信 something。
+seal.replyToSender(ctx, msg, something) //同上,区别是群内收到就群内发送,私聊收到就私聊发送。
+seal.memberBan(ctx, groupID, userID, dur) //将指定群的指定用户封禁指定时间 (似乎只实现了 walleq 协议?)
+seal.memberKick(ctx, groupID, userID) //将指定群的指定用户踢出 (似乎也只实现了 walleq 协议?)
+seal.format(ctx, something) //将 something 经过一层 rollvm 转译并返回,注意需要配合 replyToSender 才能发送给触发者!
+seal.formatTmpl(ctx, something) //调用自定义文案 something
+seal.getCtxProxyFirst(ctx, cmdArgs) //获取被 at 的第一个人,等价于 getCtxProxyAtPos(ctx, 0)
+seal.vars.intGet(ctx, \`$XXX\`) //返回一个数组,其为 \`[int 类型的触发者的该变量的值,bool]\` 当 strGet 一个 int 或 intGet 一个 str 时 bool 为 false,若一切正常则为 true。(之所以会有这么奇怪的说法是因为 rollvm 的「个人变量」机制)。
+seal.vars.intSet(ctx, \`$XXX\`, valueToSet) //\`$XXX\` 即 rollvm(初阶豹语)中的变量,其会将 $XXX 的值设定为 int 类型的 valueToSet。
+seal.vars.strGet(ctx, \`$XXX\`) //返回一个数组,其为 \`[str 类型的触发者的该变量的值,bool]\`(之所以会有这么奇怪的说法是因为 rollvm 的「个人变量」机制),当 strGet 一个 int 或 intGet 一个 str 时 bool 为 false,如果一切正常则为 true。
+seal.vars.strSet(ctx, \`$XXX\`, valueToSet) //\`$XXX\` 即 rollvm(初阶豹语)中的变量,其会将 $XXX 的值设定为 str 类型的 valueToSet。
+//seal.vars.varSet(ctx, \`$XXX\`, valueToSet) //可能是根据数据类型自动推断 int 或 str?
+//seal.vars.varGet(ctx, \`$XXX\`) //同上
+seal.ext.newCmdItemInfo() //用来定义新的指令;没有参数,个人觉得可以视其为类(class)。
+seal.ext.newCmdExecuteResult(bool) //用于判断指令执行结果,true 为成功,false 为失败。
+seal.ext.new(extName, extAuthor, Version) //用于建立一个名为 extName,作者为 extAuthor,版本为 Version 的扩展。注意,extName,extAuthor 和 Version 均为字符串。
+seal.ext.find(extName) //用于查找名为 extname 的扩展,若存在则返回 true,否则返回 false。
+seal.ext.register(newExt) //将扩展 newExt 注册到系统中。注意 newExt 是 seal.ext.new 的返回值,将 register 视为 seal.ext.new() 是错误的。
+seal.coc.newRule() //用来创建自定义 coc 规则,github.com/sealdice/javascript/examples 中已有详细例子,不多赘述。
+seal.coc.newRuleCheckResult() //同上,不多赘述。
+seal.coc.registerRule(rule) //同上,不多赘述。
+seal.deck.draw(ctx, deckname, isShuffle) //他会返回一个抽取牌堆的结果。这里有些复杂:deckname 为需要抽取的牌堆名,而 isShuffle 则是一个布尔值,它决定是否放回抽取;false 为放回,true 为不放回。
+seal.deck.reload() //重新加载牌堆。
+//下面是 1.2 新增 api
+seal.newMessage() //返回一个空白的 Message 对象,结构与收到消息的 msg 相同
+seal.createTempCtx(endpoint, msg) // 制作一个 ctx, 需要 msg.MessageType 和 msg.Sender.UserId
+seal.applyPlayerGroupCardByTemplate(ctx, tmpl) // 设定当前 ctx 玩家的自动名片格式
+seal.gameSystem.newTemplate(string) //从 json 解析新的游戏规则。
+seal.gameSystem.newTemplateByYaml(string) //从 yaml 解析新的游戏规则。
+seal.getCtxProxyAtPos(ctx, pos) //获取第 pos 个被 at 的人,pos 从 0 开始计数
+atob(base64String) //返回被解码的 base64 编码
+btoa(string) //将 string 编码为 base64 并返回
+
+//下面是 1.4.1 新增 api
+seal.ext.newConfigItem() //用于创建一个新的配置项,返回一个 ConfigItem 对象
+seal.ext.registerConfig(configItem) //用于注册一个配置项,参数为 ConfigItem 对象
+seal.ext.getConfig(ext, "key") //用于获取一个配置项的值,参数为扩展对象和配置项的 key
+seal.ext.registerStringConfig(ext, "key", "defaultValue") //用于注册一个 string 类型的配置项,参数为扩展对象、配置项的 key 和默认值
+seal.ext.registerIntConfig(ext, "key", 123) //用于注册一个 int 类型的配置项,参数为扩展对象、配置项的 key 和默认值
+seal.ext.registerFloatConfig(ext, "key", 123.456) //用于注册一个 float 类型的配置项,参数为扩展对象、配置项的 key 和默认值
+seal.ext.registerBoolConfig(ext, "key", true) //用于注册一个 bool 类型的配置项,参数为扩展对象、配置项的 key 和默认值
+seal.ext.registerTemplateConfig(ext, "key", ["1", "2", "3", "4"]) //用于注册一个 template 类型的配置项,参数为扩展对象、配置项的 key 和默认值
+seal.ext.registerOptionConfig(ext, "key", "1", ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]) //用于注册一个 option 类型的配置项,参数为扩展对象、配置项的 key、默认值和可选项
+seal.ext.getStringConfig(ext, "key") //用于获取一个 string 类型配置项的值,参数为扩展对象和配置项的 key
+seal.ext.getIntConfig(ext, "key") //用于获取一个 int 类型配置项的值,参数为扩展对象和配置项的 key
+seal.ext.getFloatConfig(ext, "key") //用于获取一个 float 类型配置项的值,参数为扩展对象和配置项的 key
+seal.ext.getBoolConfig(ext, "key") //用于获取一个 bool 类型配置项的值,参数为扩展对象和配置项的 key
+seal.ext.getTemplateConfig(ext, "key") //用于获取一个 template 类型配置项的值,参数为扩展对象和配置项的 key
+seal.ext.getOptionConfig(ext, "key") //用于获取一个 option 类型配置项的值,参数为扩展对象和配置项的 key
+
+//下面是 1.4.4 新增 api
+seal.setPlayerGroupCard(ctx, tmpl) //设置当前 ctx 玩家的名片
+seal.ban.addBan(ctx, id, place, reason)
+seal.ban.addTrust(ctx, id, place, reason)
+seal.ban.remove(ctx, id)
+seal.ban.getList()
+seal.ban.getUser(id)
以下为部分 api 使用示例。
提示
声明和注册扩展的代码部分已省略。
replyGroup
replyPerson
replyToSender
//在私聊触发 replyGroup 不会回复
+seal.replyGroup(ctx, msg, 'something'); //触发者会收到"something"的回复
+seal.replyPerson(ctx, msg, 'something'); //触发者会收到"something"的私聊回复
+seal.replyToSender(ctx, msg, 'something'); //触发者会收到"something"的回复
memberBan
memberKick
是否保留待议
//注意这些似乎只能在 WQ 协议上实现;
+seal.memberBan(ctx, groupID, userID, dur) //将群为 groupID,userid 为 userID 的人封禁 dur(单位未知)
+seal.memberKick(ctx, groupID, userID) //将群为 groupID,userid 为 userID 的人踢出那个群
format
formatTmpl
//注意 format 不会自动 reply,而是 return,所以请套一层 reply
+seal.replyToSender(ctx, msg, seal.format(\`{$t玩家}的人品为:{$t人品}\`))
+//{$t人品} 是一个 rollvm 变量,其值等于 .jrrp 出的数值
+//回复:
+//群主的人品为:87
+seal.replyToSender(ctx, msg, seal.formatTmpl(unknown))
+//这里等大佬来了再研究
getCtxProxyFirst
getCtxProxyAtPos
cmd.solve = (ctx, msg, cmdArgs) => {
+ let ctxFirst = seal.getCtxProxyFirst(ctx, cmdArgs)
+ seal.replyToSender(ctx, msg, ctxFirst.player.name)
+}
+ext.cmdMap['test'] = cmd
+//输入:.test @A @B
+//返回:A 的名称。这里其实获取的是 A 玩家的 ctx,具体见 ctx 数据结构。
+cmd.solve = (ctx, msg, cmdArgs) => {
+ let ctx3 = seal.getCtxProxyAtPos(ctx, 3)
+ seal.replyToSender(ctx, msg, ctx3.player.name)
+}
+ext.cmdMap['test'] = cmd
+//输入:.test @A @B @C
+//返回:C(第三个被@的人)的名称。这里其实获取的是 C 玩家的 ctx,具体见 ctx 数据结构。
vars
// 要看懂这里你可能需要学习一下初阶豹语
+seal.vars.intSet(ctx, \`$m今日打卡次数\`, 8) //将触发者的该个人变量设置为 8
+seal.vars.intGet(ctx, \`$m今日打卡次数\`) //返回 [8,true]
+seal.vars.strSet(ctx, \`$g群友经典语录\`, \`我要 Git Blame 一下看看是谁写的\`) //将群内的该群组变量设置为“我要 Git Blame 一下看看是谁写的”
+seal.vars.strGet(ctx, \`$g群友经典语录\`) //返回 ["我要 Git Blame 一下看看是谁写的",true]
ext
//用于注册扩展和定义指令的 api,已有详细示例,不多赘述
coc
//用于创建 coc 村规的 api,已有详细示例,不多赘述
deck
seal.deck.draw(ctx, \`煤气灯\`, false) //返回 放回抽取牌堆“煤气灯”的结果
+seal.deck.draw(ctx, \`煤气灯\`, true) //返回 不放回抽取牌堆“煤气灯”的结果
+seal.deck.reload() //重新加载牌堆
//这里实在不知道如何举例了
+seal.gameSystem.newTemplate(string) //从 json 解析新的游戏规则。
+seal.gameSystem.newTemplateByYaml(string) //从 yaml 解析新的游戏规则。
+seal.applyPlayerGroupCardByTemplate(ctx, tmpl) // 设定当前 ctx 玩家的自动名片格式
+seal.setPlayerGroupCard(ctx, tmpl) // 立刻设定当前 ctx 玩家的名片格式
seal.newMessage() //返回一个空白的 Message 对象,结构与收到消息的 msg 相同
+seal.createTempCtx(endpoint, msg) // 制作一个 ctx, 需要 msg.MessageType 和 msg.Sender.UserId
+seal.getEndPoints() //返回骰子(应该?)的 EndPoints
+seal.getVersion() //返回一个 map,键值为 version 和 versionCode
ctx
的内容 //在 github.com/sealdice/javascript/examples_ts/seal.d.ts 中有完整内容
+// 成员
+ctx.group // 当前群信息 (对象)
+ctx.player // 当前玩家数据 (对象)
+ctx.endPoint // 接入点数据 (对象)
+// 以上三个对象内容暂略
+ctx.isCurGroupBotOn // bool
+ctx.isPrivate // bool 是否私聊
+ctx.privilegeLevel // int 权限等级:40 邀请者、50 管理、60 群主、70 信任、100 master
+ctx.delegateText // string 代骰附加文本
+// 方法 (太长,懒.)
+chBindCur
+chBindCurGet
+chBindGet
+chBindGetList
+chExists
+chGet
+chLoad
+chNew
+chUnbind
+chUnbindCur
+chVarsClear
+chVarsGet
+chVarsNumGet
+chVarsUpdateTime
+loadGroupVars
+loadPlayerGlobalVars
+loadPlayerGroupVars,notice
ctx.group
的内容 // 成员
+active
+groupId
+guildId
+groupName
+cocRuleIndex
+logCurName
+logOn
+recentDiceSendTime
+showGroupWelcome
+groupWelcomeMessage
+enteredTime
+inviteUserId
+// 方法
+extActive
+extClear
+extGetActive
+extInactive
+extInactiveByName
+getCharTemplate
+isActive
+playerGet
ctx.player
的内容 // 成员
+name
+userId
+lastCommandTime
+autoSetNameTemplate
+// 方法
+getValueNameByAlias
ctx.endPoint
的内容 // 成员
+baseInfo
+id
+nickname
+state
+userId
+groupNum
+cmdExecutedNum
+cmdExecutedLastTime
+onlineTotalTime
+platform
+enable
+// 方法
+adapterSetup
+refreshGroupNum
+setEnable
+unmarshalYAML
msg
的内容 // 成员
+msg.time // int64 发送时间
+msg.messageType // string group 群聊 private 私聊
+msg.groupId // string 如果是群聊,群号
+msg.guildId // string 服务器群组号,会在 discord,kook,dodo 等平台见到
+msg.sender // 发送者信息 (对象)
+ sender.nickname
+ sender.userId
+msg.message
+msg.rawId // 原始信息 ID, 用于撤回等
+msg.platform // 平台
+// 方法
+// (似乎目前没有?)
cmdArgs
的内容 // 成员
+.command // string
+.args // []string
+.kwargs // []Kwarg
+.at // []AtInfo
+.rawArgs // string
+.amIBeMentioned // bool (为何要加一个 Be?)
+.cleanArgs // string 一种格式化后的参数,也就是中间所有分隔符都用一个空格替代
+.specialExecuteTimes // 特殊的执行次数,对应 3# 这种
+// 方法
+.isArgEqual(n, ss...) // 返回 bool, 检查第 n 个参数是否在 ss 中
+.eatPrefixWith(ss...) // 似乎是从 cleanArgs 中去除 ss 中第一个匹配的前缀
+.chopPrefixToArgsWith(ss...) // 不懂
+.getArgN(n) // -> string
+.getKwarg(str) // -> Kwarg 如果有名为 str 的 flag,返回对象,否则返回 null/undefined(不确定)
+.getRestArgsFrom(n) // -> string 获取从第 n 个参数之后的所有参数,用空格拼接成一个字符串
在海豹中,所有指令必须归属于某个扩展,而一个扩展可以包含多条指令。
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;
+
+ // 无实际意义,用于消除编译报错
+ export {}
+
+ 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
获取当前用户的权限等级。 每个身份所对应的数字如下表所示:
身份 | 权限值 |
---|---|
骰主 | 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);
+}
添加:seal.ban.addBan(ctx, uid, place, reason)
移除:seal.ban.remove(ctx, uid)
ctx
:MsgContext
类型,指令上下文,solve()
函数传进来的第一个参数uid
:用户 ID,字符串类型,例如 QQ:123456789
, TG:123456789
place
:拉黑的地方,字符串类型,随便写,一般来说在群内拉黑就写群 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==
+
+// 先将扩展模块创建出来,如果已创建就直接使用
+let ext = seal.ext.find('test');
+if (!ext) {
+ 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==
+ext = seal.ext.find('hide');
+if (!ext){
+ 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 哒!
+ seal.replyPerson(ctx, msg, \`你在群\${msg.groupId}的掷骰结果为:\${rd(x)}\`);
+ return seal.ext.newCmdExecuteResult(true);
+ }
+ }
+ ext.cmdMap['hide'] = cmdHide;
+
+ seal.ext.register(ext);
+}
可以看到使用seal.replyPerson
做到暗骰的效果。
// ==UserScript==
+// @name 示例:编写代骰指令
+// @author 木落
+// @version 1.0.0
+// @description 捕捉某人,格式.catch <@名字>
+// @timestamp 1671540835
+// 2022-12-20
+// @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');
+
+ // 创建指令 .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);
+ }
+ });
+}
// ==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')){
+ 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;
+ }
+ }
+}
插件若要在 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 中修改配置项的值
',11);function f(S,w,_,R,j,T){const a=l("Badge");return t(),p("div",null,[r,s("div",d,[g,y,s("p",null,[i("从 "),n(a,{type:"tip",text:"v1.4.5"}),i(" 起,在私聊中,除了"),F,i(","),c,i("和"),o,i(",其他用户被视为拥有与群管理员等同的权限,即权限值 50。")]),s("p",null,[i("在 "),n(a,{type:"danger",text:"v1.4.4"}),i(" 或更早版本,私聊中除了"),C,i(","),A,i("和"),D,i(",其他用户的权限等级为普通用户(0)。")])]),B,s("h2",u,[i("黑名单 / 信任名单操作 "),n(a,{type:"tip",text:"v1.4.4"}),i(),m]),x,s("h2",v,[i("注册插件配置项 "),n(a,{type:"tip",text:"v1.4.1"}),i(),b]),q])}const P=k(E,[["render",f]]);export{N as __pageData,P as default}; diff --git a/assets/advanced_js_example.md.8Rv-Oom9.lean.js b/assets/advanced_js_example.md.8Rv-Oom9.lean.js new file mode 100644 index 000000000..04f1b0b18 --- /dev/null +++ b/assets/advanced_js_example.md.8Rv-Oom9.lean.js @@ -0,0 +1 @@ +import{_ as k,D as l,c as p,j as s,a as i,I as n,a8 as h,o as t}from"./chunks/framework.C7ysi3tu.js";const e="/sealdice-manual-next/assets/js-config-example.Ctkb00mX.png",N=JSON.parse('{"title":"常见用法示例","description":"","frontmatter":{"lang":"zh-cn","title":"常见用法示例"},"headers":[],"relativePath":"advanced/js_example.md","filePath":"advanced/js_example.md","lastUpdated":1719397382000}'),E={name:"advanced/js_example.md"},r=h("",50),d={class:"info custom-block"},g=s("p",{class:"custom-block-title"},"信息",-1),y=s("p",null,[s("strong",null,"注意:"),i(" 部分权限等级仅在群聊中有效。")],-1),F=s("strong",null,"骰主",-1),c=s("strong",null,"白名单用户",-1),o=s("strong",null,"黑名单用户",-1),C=s("strong",null,"骰主",-1),A=s("strong",null,"白名单用户",-1),D=s("strong",null,"黑名单用户",-1),B=h("",5),u={id:"黑名单-信任名单操作",tabindex:"-1"},m=s("a",{class:"header-anchor",href:"#黑名单-信任名单操作","aria-label":'Permalink to "黑名单 / 信任名单操作本节内容
本节将介绍如何为你的海豹核心编写和添加一个新的 TRPG 规则。包括编写规则模板和编写指令。
本文中将涉及使用 JavaScript 与 TypeScript 编写插件的内容。如果你尚未阅读 常见用法示例,请先阅读它。
在本节中,假设我们创建了一个叫做《摸鱼大赛》的 TRPG 规则,简称为 fish 规则。
每个角色有 2 个关键属性,即脸皮厚度和摸鱼技能等级。取值分别为 1 - 3,通过投掷 D3 来生成。
此外有生命值,生命值上限 = 脸皮厚度 * 2。
简单说:这是一个类 21 点游戏。
玩家的目标是尽可能使得自己的积分接近 21,但不能大于等于 22(因为摸到 22 就不存在了!)。
开始游戏时,每个玩家创建一个角色,可以互换交换一次属性点位置。
每一轮游戏中,每个玩家都要决定“摸鱼”和“不摸鱼”。如果选择摸鱼,获得 d6 + d(摸鱼等级) 的积分;如果选择不摸鱼,则跳过本回合。
如果选择摸鱼后累计积分超过 22,玩家自动消耗 1 点生命值、随机弃牌 1 张。如果累计积分仍大于 22,那么重复以上过程直到积分小于 22 或生命值归零。
如果生命值归零,此玩家就无法再摸鱼了。
当连续两轮所有玩家都选择不摸鱼时,游戏结束。积分最大的人取胜,如果积分相同,生命值更大的人取胜。
规则模板是海豹核心中几种功能的整合发展,包括角色卡、属性同义词、自动修改群名片等。
具体地说,规则模板能为 fish 规则提供以下功能:
在 set
指令 中注册这个游戏系统,可以直接使用 set fish
打开扩展并设定默认骰子为 D6;
在 st
指令中添加 fish 规则的角色卡,这包括:(对于以下内容,可以参考海豹核心内置的 CoC 7th 系统与 D&D 5e 系统角色卡的功能)
在 sn
指令 中添加 fish 规则的自动群名片格式,可以使用 sn fish
来开启。
你需要根据规则编写规则模板,并把规则中的动作编写成指令。
对于 fish 规则来说,指令应该实现以下动作:
另外,对于一个合适的指令,它也应提供帮助文本。
以上的规则模板和指令都可以在同一个 TypeScript 或 JavaScript 插件文件中完成。我们已经写了一个比较完善且有详细注释的示例,以供参考:
如果你希望直接尝试以上插件的效果,可以从以下链接获取经过编译的 JavaScript 文件:
',17),o=[l];function p(r,c,h,d,n,m){return t(),e("div",null,o)}const u=a(i,[["render",p]]);export{f as __pageData,u as default}; diff --git a/assets/advanced_js_gamesystem.md.rQ5YJxwZ.lean.js b/assets/advanced_js_gamesystem.md.rQ5YJxwZ.lean.js new file mode 100644 index 000000000..be8aef6b2 --- /dev/null +++ b/assets/advanced_js_gamesystem.md.rQ5YJxwZ.lean.js @@ -0,0 +1 @@ +import{_ as a,c as e,o as t,a8 as s}from"./chunks/framework.C7ysi3tu.js";const f=JSON.parse('{"title":"编写新的 TRPG 规则","description":"","frontmatter":{"lang":"zh-cn","title":"编写新的 TRPG 规则"},"headers":[],"relativePath":"advanced/js_gamesystem.md","filePath":"advanced/js_gamesystem.md","lastUpdated":1715601883000}'),i={name:"advanced/js_gamesystem.md"},l=s("",17),o=[l];function p(r,c,h,d,n,m){return t(),e("div",null,o)}const u=a(i,[["render",p]]);export{f as __pageData,u as default}; diff --git a/assets/advanced_js_project.md.rcx_9l9O.js b/assets/advanced_js_project.md.rcx_9l9O.js new file mode 100644 index 000000000..ca91cd0da --- /dev/null +++ b/assets/advanced_js_project.md.rcx_9l9O.js @@ -0,0 +1,8 @@ +import{_ as o,D as t,c as d,I as s,w as a,a8 as n,j as e,o as c}from"./chunks/framework.C7ysi3tu.js";const x=JSON.parse('{"title":"插件的工程化编写","description":"","frontmatter":{"lang":"zh-cn","title":"插件的工程化编写"},"headers":[],"relativePath":"advanced/js_project.md","filePath":"advanced/js_project.md","lastUpdated":1721283943000}'),h={name:"advanced/js_project.md"},p=n('本节内容
本节将介绍如何使用 Node.js 项目编译出海豹可使用的插件,面向有前端经验的开发者。
我们假定你了解如何使用前端工具链,你应当具备诸如命令行、Node.js、npm/pnpm 等工具的使用知识。如果你对这些内容感到陌生,请自行了解或转至 使用单 JS 文件编写,手册不会介绍这些相关背景知识。
如果你打算使用 TypeScript,或者需要编写大型插件,希望更加工程化以方便维护,可以创建项目使用前端工具链来编译出插件。
海豹提供了相应的 模板项目。注册扩展和指令的代码已经写好,可以直接编译出一个可直接装载的 JS 扩展文件。
推荐的流程:
git clone
到本地,进行开发。如果不打算使用 GitHub 托管仓库,希望先在本地编写:
当插件开发完成后(或者开始开发时),你还需要修改几处地方:
header.txt
:这个文件是你插件的描述信息;tools/build-config.js
:最开头一行 var filename = 'sealdce-js-ext.js';
改成你中意的名字,注意不要与现有的重名。这决定了编译时输出的插件文件名。package.json
:修改其中 name
version
description
等项目描述信息,不过不修改也不会影响编译。在确认你所使用的包管理器后,在命令行使用如下命令安装依赖:
',14),r=e("div",{class:"language-bash vp-adaptive-theme"},[e("button",{title:"Copy Code",class:"copy"}),e("span",{class:"lang"},"bash"),e("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"}},"npm"),e("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}}," install")])])])],-1),k=e("div",{class:"language-bash vp-adaptive-theme"},[e("button",{title:"Copy Code",class:"copy"}),e("span",{class:"lang"},"bash"),e("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"}},"pnpm"),e("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}}," install")])])])],-1),u=e("p",null,"当你写好了代码,需要工程编译为插件的单 js 文件以便上传到海豹骰时,在命令行使用如下命令:",-1),g=e("div",{class:"language-bash vp-adaptive-theme"},[e("button",{title:"Copy Code",class:"copy"}),e("span",{class:"lang"},"bash"),e("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"}},"npm"),e("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}}," run"),e("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}}," build")])])])],-1),b=e("div",{class:"language-bash vp-adaptive-theme"},[e("button",{title:"Copy Code",class:"copy"}),e("span",{class:"lang"},"bash"),e("pre",{class:"shiki shiki-themes github-light github-dark vp-code",tabindex:"0"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{"--shiki-light":"#6F42C1","--shiki-dark":"#B392F0"}},"pnpm"),e("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}}," run"),e("span",{style:{"--shiki-light":"#032F62","--shiki-dark":"#9ECBFF"}}," build")])])])],-1),m=n(`编译成功的 js 文件在 dist
目录下,默认的名字是 sealdce-js-ext.js
。
只列出其中主要的一些文件
src
index.ts
:你的扩展的代码就写在这个文件里。tools
build-config.js
:一些编译的配置,影响 index.ts
编译成 js 文件的方式;build.js
:在命令 npm run build
执行时所运行的脚本,用于读取 build-config
并按照配置进行编译。types
seal.d.ts
:类型文件,海豹核心提供的扩展 API。header.txt
:扩展头信息,会在编译时自动加到目标文件头部;package.json
:命令 npm install
时就在安装这个文件里面所指示的依赖包;tsconfig.json
:TypeScript 的配置文件。当然可以,像正常的前端项目一样,你可以在其中引用其他 npm 包,比如模板项目中就为你引入了常用的 lodash-es
。
一般来说纯 JS 编写的包都是可以引用的,一些强 native 相关的包可能存在兼容性问题,你需要自行尝试。
推荐你尽量使用 esm 格式的包,不过 commonjs 格式的包也是可以使用的,如 dayjs
。其他格式的支持和更多问题排查,请查阅模板项目所使用的构建工具 esbuild 的文档,tools/build-config.js
中即是 esbuild 的配置项。
types/seal.d.ts
文件中维护了海豹提供的 API,但目前来说维护的并不完全。如果你发现有一些存在的 API 未被提示,可以手动在 types/seal.d.ts
补上来解决报错。
有时 seal.d.ts
会有更新,可以去模板项目仓库看看有没有最新的,有的话可以替换到你的项目中。也非常欢迎你向模板仓库提 PR 来帮忙完善。
调整 tools/build-config.js
中的选项,关闭 minify:
module.exports = {
+ ...
+ build: {
+ ...
+ minify: false,
+ ...
+ }
+}
本节内容
本节将介绍 JavaScript 脚本的编写的前置知识,更多内容请看 API 列表 和 常见用法示例,请善用侧边栏和搜索,按需阅读文档。
我们假定你熟悉 JavaScript / TypeScript,编程语言的教学超出了本文档的目的,如果你还不熟悉它们,可以从互联网上寻找到很多优秀的教程。如:
注意:你只需要学习 JavaScript 语言本身,不包括 WebAPI。
JavaScript(JS)虽然作为 Web 页面中的脚本语言被人所熟知,但是它也被用到了很多 非浏览器环境 中,例如 Node.js、Apache CouchDB、Adobe Acrobat 等。
—— 来自 MDN 文档
海豹的 JS 插件就是运行在一个非浏览器环境中—— goja 作为 JS 脚本引擎所提供的环境,该环境目前支持了 ES6 基本上全部的特性,包括 async/await
、promise
和 generator
等异步编程友好的特性。
除了 JS 语言规范所提供的 内置对象,海豹额外在环境中提供了如下全局对象:
seal
用于自定义扩展、增加指令、管理黑白名单……几乎所有与海豹本体有关的 API 都挂载在这个内置对象上。console
专门与海豹的日志模块进行交互。setTimeout/setInterval
等与事件循环相关的函数。fetch
用于网络请求。atob/btoa
用于 base64 编解码。警告
需要注意引擎的整型为 32 位,请小心溢出问题。
现在,让我们从最简单的扩展开始,这个扩展只会在日志中打印一条 Hello World!
。
新建一个 JS 文件,写入如下内容,然后通过海豹的 WebUI 上传并重载 JS 环境(或是直接复制到海豹 WebUI 的调试控制台中运行)。
// ==UserScript==
+// @name 示例:如何开始
+// @author 木落
+// @version 1.0.0
+// @description 这是一个演示脚本,并没有任何实际作用。
+// @timestamp 1671368035
+// 2022-12-18
+// @license Apache-2
+// @homepageURL https://github.com/sealdice/javascript
+// ==/UserScript==
+
+console.log('Hello World!');
你应当能在控制台中观察到一条 Hello World 的日志。
提示:打印日志
console 打印出来的东西不仅会在控制台中出现,在日志中也会显示。
涉及网络请求或延迟执行的内容,有时候不会在控制台调试面板上显示出来,而在日志中能看到。
每个 JS 扩展需要在开头以固定格式注释的形式留下信息,以便使用和分享,这些信息通常被称为「插件元数据」:
// ==UserScript==
+// @name 脚本的名字
+// @author 木落
+// @version 1.0.0
+// @description 这是一个演示脚本,并没有任何实际作用。
+// @timestamp 1672066028
+// @license Apache-2
+// @homepageURL https://github.com/sealdice/javascript
+// @depends SzzRain:demo:1.0.0
+// @sealVersion 1.4.5
+// ==/UserScript==
我们更推荐使用 TypeScript 来编写插件,编译到 ES6 后使用即可。不过先从 JavaScript 开始也是没有任何问题的。
编写插件时,可以下载海豹提供的 seal.d.ts 文件,将其保存在和你要编写的 JS 文件同级的目录下。
seal.d.ts
支持了在使用 vscode 等工具编写时,对海豹提供的 API 的代码补全。
提示
seal.d.ts
文件随时可能会有更新,如果你需要的 API 没有提示,可以检查一下是否是最新版本。
如果你打算使用 TypeScript,或者需要编写大型插件,希望更加工程化以方便维护,见 插件的工程化编写。
插件的工程化编写
插件的工程化编写是 为有一定经验的开发者准备的。相关文档默认你了解如何使用前端工具链,你应当具备诸如命令行、Node.js、npm/pnpm 等工具的使用知识。
如果你对上面的内容感到陌生,请自行了解或放弃使用,手册不会介绍相关内容。
使用单 JS 文件编写插件与使用模板工程编写本质上是没有区别的,并不会出现某些功能无法使用的差异。
指定依赖的格式为 作者:插件名[:SemVer版本约束,可选]
,其中 :
是分隔符,注意必须是半角符号。
例如,使用 @depends SzzRain:定时任务
,这意味着该插件需要同时安装一个名为 定时任务
,作者名为 SzzRain
的插件才可正常工作。
在上面的示例中,可以看见「SzzRain:每日新闻」通过 @depends
指明了其依赖 SzzRain:定时任务:>=2.0.0
。
版本限制是可选的,比如上面示例中的 >=2.0.0
,这表示 SzzRain:每日新闻
依赖于 SzzRain:定时任务
,且后者的版本必须大于等于 2.0.0
。这在你需要依赖高版本插件的情况下很有用。
插件名、作者、版本号
指定依赖插件涉及到对应的插件名、作者名和版本号,其为插件元数据中的 @name
、@author
和 @version
。
有更复杂的指定依赖版本需求?
除了上面提到的 >=2.0.0
之外,你还可以参阅海豹所使用的 go-semver 库的文档,来进行更复杂的限制。
比如 1.1.4-5.1.4
意味着指定的依赖版本需要在 1.1.4
和 5.1.4
之间。
但是,目标依赖的版本号需要符合 SemVer 才能更好的支持你进行版本限制。
依赖可以是多个,每一行指定一个 @depends
,这意味着这个插件需要同时满足所有的依赖才能工作。
例如我们给上面的「SzzRain:每日新闻」增加一个新的依赖:
// ==UserScript==
+// @name 每日新闻
+// @author SzzRain
+// @version 2.0.0
+// ...
+// @depends SzzRain:定时任务:>=2.0.0
+// @depends sealdice:强制依赖
此时,这个插件需要同时安装 SzzRain:定时任务
(且版本大于等于 2.0.0
)和 sealdice:强制依赖
这两个插件时,才能正常加载。
例如,当插件使用了在 1.4.6
新增的 API,可以指定目标海豹版本 @sealVersion 1.4.6
。而当加载该插件的海豹版本为 1.4.5
时,会向骰主提示海豹版本不兼容而拒绝加载。
兼容的海豹版本
插件作者只需要指定目标海豹版本即可,如 1.4.5
新增的 API 则指定 @sealVersion 1.4.5
。
海豹会尝试在兼容的情况下尽可能地加载插件,这是由海豹核心自动处理的。
VS Code 可以安装 SealDice Snippets 插件,提供了一些常见代码片段,帮助快速生成模板代码。
这个问题分为两个部分,如果你所说的其他编程语言为 Python、Lua 等与 JavaScript 无关的编程语言,那么目前是无法使用这些语言编写海豹插件的。如果你有一些其它骰系的插件(比如一些 Lua 插件),你只能通过按原逻辑重写为海豹 JavaScript 插件的方式来在海豹中使用。
但是有些编程语言是可以编译为 JavaScript 的,典型的像 TypeScript、CoffeeScript。这些语言在编写时使用它们自己,最后只要编译成 JavaScript 就可以在海豹中使用了。
',8);function G(H,M,$,O,K,Q){const t=n("Badge"),p=n("PluginTabsTab"),o=n("PluginTabs");return d(),c("div",null,[_,a("table",u,[g,a("tbody",null,[b,m,A,v,S,D,y,a("tr",null,[a("td",null,[s("@depends "),e(t,{type:"tip",text:"v1.4.4"})]),a("td",null,[s("可选,从 "),e(t,{type:"tip",text:"v1.4.4"}),s(" 加入,指定你的扩展依赖的其他扩展,"),f,s("。详见 "),z])]),a("tr",null,[a("td",null,[s("@sealVersion "),e(t,{type:"tip",text:"v1.4.5"})]),a("td",null,[s("可选,从 "),e(t,{type:"tip",text:"v1.4.5"}),s(" 加入,指定你的扩展的目标海豹版本。详见 "),T])])])]),x,a("h3",P,[s("依赖其他扩展 "),e(t,{type:"tip",text:"v1.4.4"}),s(),j]),C,J,a("p",null,[s("从 "),e(t,{type:"tip",text:"v1.4.4"}),s(" 开始,你可以在 "),R,s(" 中通过 "),q,s(" 来指定扩展依赖的其他扩展。")]),I,e(o,null,{default:l(()=>[e(p,{label:"SzzRain:每日新闻"},{default:l(()=>[E]),_:1}),e(p,{label:"SzzRain:定时任务"},{default:l(()=>[V]),_:1})]),_:1}),N,a("h2",w,[s("目标海豹版本 "),e(t,{type:"tip",text:"v1.4.5"}),s(),B]),U,a("p",null,[s("为了让插件作者无需反复说明,也更好地提示使用插件的骰主,从 "),e(t,{type:"tip",text:"v1.4.5"}),s(" 开始,你可以在 "),W,s(" 中通过 "),L,s(" 来指定插件的目标海豹版本。")]),F])}const Z=r(k,[["render",G]]);export{Y as __pageData,Z as default}; diff --git a/assets/advanced_js_start.md.spoJKT3w.lean.js b/assets/advanced_js_start.md.spoJKT3w.lean.js new file mode 100644 index 000000000..efb0721a0 --- /dev/null +++ b/assets/advanced_js_start.md.spoJKT3w.lean.js @@ -0,0 +1,9 @@ +import{_ as r,D as n,c,j as a,a as s,I as e,w as l,a8 as i,o as d}from"./chunks/framework.C7ysi3tu.js";const h="/sealdice-manual-next/assets/edit-jsscript-dts.j849kmdb.png",Y=JSON.parse('{"title":"前言","description":"","frontmatter":{"lang":"zh-cn","title":"前言"},"headers":[],"relativePath":"advanced/js_start.md","filePath":"advanced/js_start.md","lastUpdated":1721287883000}'),k={name:"advanced/js_start.md"},_=i("",16),u={tabindex:"0"},g=a("thead",null,[a("tr",null,[a("th",null,"属性"),a("th",null,"含义")])],-1),b=a("tr",null,[a("td",null,"@name"),a("td",null,"必填,JS 扩展的名称,会展示在插件列表页面")],-1),m=a("tr",null,[a("td",null,"@author"),a("td",null,"必填,作者名")],-1),A=a("tr",null,[a("td",null,"@version"),a("td",null,[s("必填,版本号,可以自己定义,但建议遵循 "),a("a",{href:"https://semver.org/lang/zh-CN/",target:"_blank",rel:"noreferrer"},"语义版本控制(Semantic Versioning)")])],-1),v=a("tr",null,[a("td",null,"@description"),a("td",null,"可选,对扩展的功能的描述")],-1),S=a("tr",null,[a("td",null,"@timestamp"),a("td",null,[s("可选,最后更新时间,以秒为单位的 unix 时间戳,新版本支持了直接使用时间字符串,如 "),a("code",null,"2023-10-30"),s("。")])],-1),D=a("tr",null,[a("td",null,"@license"),a("td",null,"可选,开源协议,示例中的 Apache-2 是一个比较自由的协议,允许任意使用和分发(包括商用),当然你也可以使用其它协议(MIT GPL 等)")],-1),y=a("tr",null,[a("td",null,"@homepageURL"),a("td",null,"可选,你的扩展的主页链接,有 GitHub 仓库可以填仓库链接,没有则可以填海豹官方插件仓库")],-1),f=a("strong",null,"可以不含此行或含有多行",-1),z=a("a",{href:"#依赖其他扩展"},"依赖其他扩展",-1),T=a("a",{href:"#目标海豹版本"},"目标海豹版本",-1),x=i("",11),P={id:"依赖其他扩展",tabindex:"-1"},j=a("a",{class:"header-anchor",href:"#依赖其他扩展","aria-label":'Permalink to "依赖其他扩展本节内容
本节将介绍海豹内置的脚本语言,请善用侧边栏和搜索,按需阅读文档。
为了在很多地方支持实现一些逻辑,但又不至于直接使用学习门槛较高的正式编程语言,海豹提供了一种简单易学的脚本语言,可以称为「海豹语」「豹语」等。
你可能注意到,在自定义文案等地方,出现了一些以 $t
开头的东西,这些是海豹中的变量。
变量的名称可以是汉字、字母和数字,$t
是一个特殊的变量前缀,还有其它的前缀。不同前缀代表不同的作用域:
变量名字 | 用途 | 举例 |
---|---|---|
普通名字 | 玩家的角色属性 | 理智、力量、智力 |
$t开头 | 个人临时变量,不存数据库 | $t随机点数 |
$m开头 | 个人变量,跨群存在 | $m今日人品 |
$g开头 | 群变量,群内所有人共享 | $g群主体重 |
所有变量均可以在 .rx
/ .rxh
/ .ra
/ .text
等指令以及「自定义文案」中使用。
注意:$t
是临时变量
$t
开头的临时变量的存活周期仅为单次指令执行,执行完毕后不应当认为该变量值还保留。
在下一次指令中使用上一次指令设置的 $t
变量属于未定义行为,可能会出现包括但不限于变量值不变,变量值丢失,变量值被覆盖,变量值变为随机数,变量值变成 114514
等任何情况。如果你需要这样的持久变量,请使用 $m
或 $g
代替。
内置的 $t
变量的值固定并不代表它们是持久变量,其值是在每次指令执行的初始阶段设置的。
提示:变量不生效?
如果一部分变量无效,请检查海豹是否为最新版本。
一些内置变量
变量名 | 内容 | 示例结果 |
---|---|---|
$t玩家 | 当前人物卡的名字,如果不存在则为群昵称或 QQ 昵称。 | <木落> |
$t玩家_RAW | 同上,但没有<> | 木落 |
$tQQ昵称 | QQ 昵称 | <木落> |
$t账号ID | 海豹格式的 ID | QQ:123456789 |
$t账号ID_RAW | 原始格式的 ID | 123456789 |
$tQQ | 海豹格式的 ID | QQ:123456789 |
$t群名 | 群名 | 海豹核心·SealDice 用户群 |
$t群号 | 海豹格式的 ID | QQ-Group:987654321 |
$t群号_RAW | 原始格式的 ID | 987654321 |
$t个人骰子面数 | 个人骰子面数 | 100 |
$tDate | 数字格式的现日期 | 20230109 |
$tYear | 数字格式的年份 | 2023 |
$tMonth | 数字格式的现月份 | 1 |
$tDay | 数字格式的现日期 | 9 |
$tWeekday | 数字格式的星期(1-7) | 1 |
$tHour | 数字格式的现时间(小时) | 15 |
$tMinute | 数字格式的现时间(分钟) | 41 |
$tSecond | 数字格式的现时间(秒) | 55 |
$tTimestamp | 数字格式的 10 位时间戳 | 1673250115 |
$t文本长度 | 触发消息的文本,汉字长度为 3,英文字母和数字长度为 1。 | 6 |
$t平台 | 触发的平台 | |
$t游戏模式 | 随 .set coc/dnd 改变 | coc7 |
$t消息类型 | 触发位置为群还是私聊(group/private) | group |
娱乐:今日人品 | 自定义文案 | <木落> 的今日人品为 0 |
常量:APPNAME | 软件名 | SealDice |
常量:VERSION | 版本号 | 1.4.0 |
$tMsgID | 消息 ID,仅自定义回复中可用。 | -123 |
所有自定义文案也均为可用变量。
// 注意,目前并不支持写注释,此为教程中便于展示
+// 文本类型
+$t0 = '文本'
+$t0 = "也是文本"
+$t0 = \`特殊文本类型,可以插入表达式,例如,玩家的力量数值: {力量}\`
+$t0 = \`另一种插入表达式的写法 {% 力量 %} \`
+
+// 数字类型
+$t0 = 1
+
+// 布尔类型:没有专门的布尔类型,0 或空字符串被视为 False,非零和非空字符串为 True
+$t0 > 1
+$t0 >= 1
+$t0 == 1
+$t0 != 1
+$t0 < 1
+$t0 <= 1
注意:务必区分 =
与 ==
混淆 =
与 ==
是小白常犯的错误之一。前者用于赋值而后者用于比较。当你试图在下文所述的条件算符或条件语句中,比较两个值时,需要使用 ==
。
多个语句可以用 ;
分隔,取分隔后的最后一项的值,为整个表达式的值,例如:
$t0 = 1;2;3
此时 $t0
的值为 3。
注意:分号的使用
不要在最后一条语句的后面再使用分号,会变得不幸。
// 正确写法
+if 1 {
+ $t0 = 1;
+ $t1 = 2
+}
+
+// 错误写法
+if 1 {
+ $t0 = 1;
+ $t1 = 2;
+}
加减乘除余 + - * / %
+乘方 ^ ** // 2 ** 3 或 2 ^ 3,即 2 的 3 次方
&& 逻辑与
+|| 逻辑或
+! 逻辑非
d
常规骰子算符,用法举例 d20
2d20k1
d20 优势
。
f
命运骰,随机骰 4 次,每骰结果可能是 -1 0 1,记为 - 0 +。
b
奖励骰 (CoC)。
p
惩罚骰 (CoC)。
c
双十字。
?
灵视 >= 40 ? '如果灵视达到40以上,你就能看到这句话'
可以用这个指令测试,下同:
.st 灵视 41
+.text {灵视 >= 40 ? '如果灵视达到 40 以上,你就能看到这句话'}
? ,
灵视 >= 80 ? '看得很清楚吗?',
+灵视 >= 50 ? '不错,再靠近一点……',
+灵视 >= 30 ? '仔细听……',
+灵视 >= 0 ? '呵,无知之人。'
应用举例,默认的 jrrp
{$t玩家} 今日人品为{$t人品},{%
+ $t人品 > 95 ? '人品爆表!',
+ $t人品 > 80 ? '运气还不错!',
+ $t人品 > 50 ? '人品还行吧',
+ $t人品 > 10 ? '今天不太行',
+ 1 ? '流年不利啊!'
+%}
? :
灵视 >= 40 ? '如果灵视达到 40 以上,你就能看到这句话' : '无知亦是幸运'
if $t0 > 10 {
+ $t1 = "aaa"
+} else {
+ $t1 = 'bbb'
+}
提示:出现格式化错误
如果上面的代码输出「格式化错误」,那是因为你的 $t0
不是数值。字符串当然不能和数值比较大小,所以会报错。
解决方法:.text {$t0=0}
实际测试:
.text {% if $t0 > 10 { $t1="aaa"} else { $t1 = 'bbb' }; $t1 %}
${e.children.map(t).join("")}
`:`Unsupported markdown: ${e.type}`}return r.map(t).join("")}function Mr(n){return Intl.Segmenter?[...new Intl.Segmenter().segment(n)].map(r=>r.segment):[...n]}function jr(n,r){const t=Mr(r.content);return gt(n,[],t,r.type)}function gt(n,r,t,e){if(t.length===0)return[{content:r.join(""),type:e},{content:"",type:e}];const[u,...i]=t,l=[...r,u];return n([{content:l.join(""),type:e}])?gt(n,l,i,e):(r.length===0&&u&&(r.push(u),t.shift()),[{content:r.join(""),type:e},{content:t.join(""),type:e}])}function Rr(n,r){if(n.some(({content:t})=>t.includes(` +`)))throw new Error("splitLineToFitWidth does not support newlines in the line");return Bn(n,r)}function Bn(n,r,t=[],e=[]){if(n.length===0)return e.length>0&&t.push(e),t.length>0?t:[];let u="";n[0].content===" "&&(u=" ",n.shift());const i=n.shift()??{content:" ",type:"normal"},l=[...e];if(u!==""&&l.push({content:u,type:"normal"}),l.push(i),r(l))return Bn(n,r,t,l);if(e.length>0)t.push(e),n.unshift(i);else if(i.content){const[a,m]=jr(r,i);t.push([a]),m.content&&n.unshift(m)}return Bn(n,r,t)}function qr(n,r){r&&n.attr("style",r)}function Hr(n,r,t,e,u=!1){const i=n.append("foreignObject"),l=i.append("xhtml:div"),a=r.label,m=r.isNode?"nodeLabel":"edgeLabel";l.html(` + "+a+""),qr(l,r.labelStyle),l.style("display","table-cell"),l.style("white-space","nowrap"),l.style("max-width",t+"px"),l.attr("xmlns","http://www.w3.org/1999/xhtml"),u&&l.attr("class","labelBkg");let c=l.node().getBoundingClientRect();return c.width===t&&(l.style("display","table"),l.style("white-space","break-spaces"),l.style("width",t+"px"),c=l.node().getBoundingClientRect()),i.style("width",c.width),i.style("height",c.height),i.node()}function Pn(n,r,t){return n.append("tspan").attr("class","text-outer-tspan").attr("x",0).attr("y",r*t-.1+"em").attr("dy",t+"em")}function Nr(n,r,t){const e=n.append("text"),u=Pn(e,1,r);_n(u,t);const i=u.node().getComputedTextLength();return e.remove(),i}function Qr(n,r,t){var e;const u=n.append("text"),i=Pn(u,1,r);_n(i,[{content:t,type:"normal"}]);const l=(e=i.node())==null?void 0:e.getBoundingClientRect();return l&&u.remove(),l}function Vr(n,r,t,e=!1){const i=r.append("g"),l=i.insert("rect").attr("class","background"),a=i.append("text").attr("y","-10.1");let m=0;for(const c of t){const p=x=>Nr(i,1.1,x)<=n,f=p(c)?[c]:Rr(c,p);for(const x of f){const h=Pn(a,m,1.1);_n(h,x),m++}}if(e){const c=a.node().getBBox(),p=2;return l.attr("x",-p).attr("y",-p).attr("width",c.width+2*p).attr("height",c.height+2*p),i.node()}else return a.node()}function _n(n,r){n.text(""),r.forEach((t,e)=>{const u=n.append("tspan").attr("font-style",t.type==="emphasis"?"italic":"normal").attr("class","text-inner-tspan").attr("font-weight",t.type==="strong"?"bold":"normal");e===0?u.text(t.content):u.text(" "+t.content)})}const Ur=(n,r="",{style:t="",isTitle:e=!1,classes:u="",useHtmlLabels:i=!0,isNode:l=!0,width:a=200,addSvgBackground:m=!1}={})=>{if(At.info("createText",r,t,e,u,i,l,m),i){const c=_r(r),p={isNode:l,label:zt(c).replace(/fa[blrs]?:fa-[\w-]+/g,x=>``),labelStyle:t.replace("fill:","color:")};return Hr(n,p,a,u,m)}else{const c=Pr(r);return Vr(a,n,c,m)}};export{Qr as a,Ur as c}; diff --git a/assets/chunks/edges-066a5561.CPwEKA1n.js b/assets/chunks/edges-066a5561.CPwEKA1n.js new file mode 100644 index 000000000..5a8229b74 --- /dev/null +++ b/assets/chunks/edges-066a5561.CPwEKA1n.js @@ -0,0 +1,4 @@ +import{q as H,c as b,d as V,an as q,h as E,l as g,z as j,ao as lt}from"../app.BKabOgys.js";import{c as st}from"./createText-ca0c5216.lHq48BDc.js";import{l as ct}from"./line._HMy3R8R.js";const ht=(e,t,a,i)=>{t.forEach(l=>{wt[l](e,a,i)})},ot=(e,t,a)=>{g.trace("Making markers for ",a),e.append("defs").append("marker").attr("id",a+"_"+t+"-extensionStart").attr("class","marker extension "+t).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 1,7 L18,13 V 1 Z"),e.append("defs").append("marker").attr("id",a+"_"+t+"-extensionEnd").attr("class","marker extension "+t).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 1,1 V 13 L18,7 Z")},yt=(e,t,a)=>{e.append("defs").append("marker").attr("id",a+"_"+t+"-compositionStart").attr("class","marker composition "+t).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),e.append("defs").append("marker").attr("id",a+"_"+t+"-compositionEnd").attr("class","marker composition "+t).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},pt=(e,t,a)=>{e.append("defs").append("marker").attr("id",a+"_"+t+"-aggregationStart").attr("class","marker aggregation "+t).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),e.append("defs").append("marker").attr("id",a+"_"+t+"-aggregationEnd").attr("class","marker aggregation "+t).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},ft=(e,t,a)=>{e.append("defs").append("marker").attr("id",a+"_"+t+"-dependencyStart").attr("class","marker dependency "+t).attr("refX",6).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 5,7 L9,13 L1,7 L9,1 Z"),e.append("defs").append("marker").attr("id",a+"_"+t+"-dependencyEnd").attr("class","marker dependency "+t).attr("refX",13).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},xt=(e,t,a)=>{e.append("defs").append("marker").attr("id",a+"_"+t+"-lollipopStart").attr("class","marker lollipop "+t).attr("refX",13).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("circle").attr("stroke","black").attr("fill","transparent").attr("cx",7).attr("cy",7).attr("r",6),e.append("defs").append("marker").attr("id",a+"_"+t+"-lollipopEnd").attr("class","marker lollipop "+t).attr("refX",1).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("circle").attr("stroke","black").attr("fill","transparent").attr("cx",7).attr("cy",7).attr("r",6)},dt=(e,t,a)=>{e.append("marker").attr("id",a+"_"+t+"-pointEnd").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",6).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),e.append("marker").attr("id",a+"_"+t+"-pointStart").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",4.5).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 5 L 10 10 L 10 0 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},gt=(e,t,a)=>{e.append("marker").attr("id",a+"_"+t+"-circleEnd").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",11).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),e.append("marker").attr("id",a+"_"+t+"-circleStart").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",-1).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},ut=(e,t,a)=>{e.append("marker").attr("id",a+"_"+t+"-crossEnd").attr("class","marker cross "+t).attr("viewBox","0 0 11 11").attr("refX",12).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0"),e.append("marker").attr("id",a+"_"+t+"-crossStart").attr("class","marker cross "+t).attr("viewBox","0 0 11 11").attr("refX",-1).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0")},bt=(e,t,a)=>{e.append("defs").append("marker").attr("id",a+"_"+t+"-barbEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",14).attr("markerUnits","strokeWidth").attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z")},wt={extension:ot,composition:yt,aggregation:pt,dependency:ft,lollipop:xt,point:dt,circle:gt,cross:ut,barb:bt},hr=ht;function mt(e,t){t&&e.attr("style",t)}function kt(e){const t=E(document.createElementNS("http://www.w3.org/2000/svg","foreignObject")),a=t.append("xhtml:div"),i=e.label,l=e.isNode?"nodeLabel":"edgeLabel";return a.html('"+i+""),mt(a,e.labelStyle),a.style("display","inline-block"),a.style("white-space","nowrap"),a.attr("xmlns","http://www.w3.org/1999/xhtml"),t.node()}const vt=(e,t,a,i)=>{let l=e||"";if(typeof l=="object"&&(l=l[0]),H(b().flowchart.htmlLabels)){l=l.replace(/\\n|\n/g,"