a useful plugin providing convinient tools for the development of textual game on QQ
Project description
Ayaka 0.3.6
针对Nonebot2框架 Onebot_v11协议的文字游戏开发辅助插件
注意:由于更新pypi的readme.md需要占用版本号,因此其readme.md可能不是最新的,强烈建议读者前往github仓库以获取最新版本的帮助
更新记录
更新记录
0.3.0
借助contextvar内置模块,全部重写了之间的代码,现在它们被合并为一个单文件,并能实现ayaka插件先前提供的所有功能,但不幸的是,其无法兼容0.2.x的ayaka插件,需要代码迁移
0.3.1
规定了应用启动后的默认初始状态为 init
0.3.2
增加了较为完善的注释
0.3.3
在本文档中更新了部分帮助
0.3.5
将ayaka_master集成进本插件中
0.3.6
不兼容
修改了AyakaStorage和AyakaApp的两处方法:
- plugin_storage
- group_storage
修改内容:
取消了suffix参数,现在,你需要在最后一个name中指定suffix,否则,AyakaStorage中的path属性将指向一个文件夹
迁移方式:
app.plugin_storage("name")
-> app.plugin_storage("name.json")
app.plugin_storage("test", "name", suffix=".txt")
-> app.plugin_storage("test", "name.txt")
安装
poetry add nonebot-plugin-ayaka
poetry run playwright install chromium
bot.py
无需修改,只要在ayaka衍生插件里正常导入就行:from ayaka import AyakaApp
ayaka衍生插件需要nonebot来加载
如果没有用到无头浏览器截图的功能,可忽略第二步
配置
推荐配置(非强制要求)
COMMAND_START=["#"]
COMMAND_SEP=[" "]
快速了解
通过ayaka插件,二次封装nonebot2提供的api,提供专用api,便于其他文字游戏插件的编写
基于ayaka的衍生插件库 https://github.com/bridgeL/ayaka_plugins
基于ayaka的小游戏合集 https://github.com/bridgeL/nonebot-plugin-ayaka-games
基于ayaka的谁是卧底小游戏 https://github.com/bridgeL/nonebot-plugin-ayaka-who-is-suspect
基本特性
- 状态机
- 命令隔离
- 数据隔离
ayaka 内置插件
ayaka内部已安装一份特殊的综合管理插件,它基于ayaka插件而实现
命令一览:
- 启用/permit
- 禁用/forbid
- 插件/plugin
- 状态/state
- 帮助/help
所有ayaka衍生插件只需要编写app.help,就可以在用户输入 #help <插件名>
后获取该插件的帮助
代码速看
'''
hello world
ayaka可以帮助你实现命令隔离
'''
from ayaka import AyakaApp
app = AyakaApp("hello-world")
# 你可以不写帮助
# app.help
# 桌面状态下
@app.on_command("hw")
async def app_entrance():
await app.start()
# app运行后,进入指定状态(state = "world")
app.set_state("world")
# 只有world状态可以退出,其他状态运行该指令均为返回world状态
@app.on_state_command(["exit", "退出"], "*")
async def app_exit():
if app.state == "world":
await app.close()
else:
app.set_state("world")
await app.send("跳转到 world")
# 对世界、月亮和太阳打个招呼
@app.on_state_command("hi", ["world", "moon", "sun"])
async def hello():
await app.send(f"hello,{app.state}!")
# 对世界、月亮和太阳来个大比兜
@app.on_state_command("hit", "world")
async def hit():
await app.send("earthquake")
@app.on_state_command("hit", "moon")
async def hit():
await app.send("moon fall")
@app.on_state_command("hit", "sun")
async def hit():
await app.send("big bang!")
# 跳转状态
@app.on_state_command("jump", "*")
async def jump_to_somewhere():
if not app.arg:
await app.send("没有参数!")
else:
next_state = str(app.arg)
app.set_state(next_state)
await app.send(f"跳转到 [{next_state}]")
术语解释
无状态应用
例如,简单复读我说的话的应用
那么对于该应用而言,我之前说了啥都是无所谓的,它只需要复读我当前正在说的这句就行了。也就是说,无论我曾经下达过什么指令,都不会影响到该应用的状态,它是永恒不变的,即无状态应用
无状态应用在注册回调时,使用app.on_command
和app.on_text
即可
有状态应用
例如,代码速看中的hello-world应用
我发送hi指令时,应用需要根据自身的状态(earth/moon/sun)给出不同的响应,因此它是有状态应用
有状态应用在注册回调时,需要使用app.on_state_command
和app.on_state_text
应用启用/禁用
该应用(不管是无状态应用
还是有状态应用
)的指令是否可以响应
比如禁用了hello-world应用后,当你发送hw
时,bot将不会触发回调
应用运行/关闭
是指有状态应用
是否正在运行
比如当前群聊并没有运行应用时,你发送hw
,bot将运行hello-world应用
单一应用原则
一个群聊同一时间只能运行一个有状态应用
,避免过于混乱的情形,如果想要运行另一个应用,请先关闭当前的应用
无状态应用
没有运行和关闭的概念,你发完指令,它立刻就会回应你
API
app属性一览表
名称 | 类型 | 功能 |
---|---|---|
intro | str |
应用介绍(帮助dict中key为init所对应的value) |
help | str |
当前应用在当前状态下的帮助 |
all_help | str |
当前应用的所有帮助 |
state | bool |
当前应用的状态 |
valid | bool |
应用在当前设备 是否被启用 |
bot | Bot |
当前机器人 |
group | AyakaGroup |
当前群组 |
event | MessageEvent |
当前消息事件 |
message | Message |
当前消息 |
arg | Message |
删除了命令后剩下的消息部分 |
args | List[MessageSegment] |
删除命令后,依照分隔符分割,并移除空数据 |
cmd | str |
本次响应是针对哪个命令 |
bot_id | int |
当前机器人的qq号 |
group_id | int |
当前群聊的群号 |
user_id | int |
当前消息的发送者的qq号 |
user_name | str |
当前消息的发送者的群名片或昵称(优先为群名片) |
cache | AyakaCache |
为当前群组当前应用提供的独立缓存数据空间 |
app方法一览表
名称 | 功能 | 是否异步 |
---|---|---|
start | 运行应用 | 是 |
close | 关闭应用 | 是 |
send | 发送消息 | 是 |
send_many | 发送合并转发消息 | 是 |
t_send | 定时器触发回调时所使用的专用发送消息方法 | 是 |
set_state | 设置应用状态(在应用运行时可以设置) | \ |
on_command | 注册桌面模式下的命令回调 | \ |
on_state_command | 注册应用运行时在不同状态下的命令回调 | \ |
on_text | 注册桌面模式下的消息回调 | \ |
on_state_text | 注册应用运行时在不同状态下的消息回调 | \ |
on_everyday | 每日定时触发回调(东8区) | \ |
on_interval | 在指定的时间点后开始循环触发(东8区) | \ |
add_listener | 为该群组添加对 指定私聊 的监听 | \ |
remove_listener | 移除该群组对 指定私聊/所有其他私聊 的监听 | \ |
如何编写ayaka衍生插件
给出几份例程代码以帮助读者理解使用
如何使用例程代码?
以下两种方式皆可
手动复制本文档中的代码
你可以在nonebot工作目录的src/plugins中新建一个代码文件,手动复制代码进去,nonebot会在读到bot.py
文件中的nonebot.load_from_toml(...)
语句后导入该插件
从仓库下载
你也可以前往ayaka衍生插件库,下载其中的example文件夹,放到nonebot工作目录下,然后在bot.py
中添加nonebot.load_plugin("example")
插件编写范例 echo
'''
具有状态机的复读模块
'''
from ayaka import AyakaApp
app = AyakaApp("echo")
# 得益于ayaka内置插件 ayaka_master
# 用户可通过#help命令展示插件帮助,只需编写app.help即可
app.help = '''复读只因
特殊命令一览:
- reverse 开始说反话
- back 停止说反话
- exit 退出
'''
# 另一种写法
app.help = {
"init": "复读只因\n特殊命令一览:\n- reverse 开始说反话\n- exit 退出",
"reverse": "说反话模式\n- back 停止说反话"
}
# 桌面状态下
@app.on_command("echo")
async def app_entrance():
# 输入参数则复读参数(无状态响应
# > #echo hihi
# < hihi
if app.arg:
await app.send(app.arg)
return
# 没有输入参数则运行该应用
await app.start()
# app运行后,进入初始状态(state = "init")
# 正常复读
@app.on_state_text()
async def repeat():
await app.send(app.message)
# 任意状态均可直接退出
@app.on_state_command(["exit", "退出"], "*")
async def app_exit():
await app.close()
# 通过命令,跳转到reverse状态
@app.on_state_command(["rev", "reverse", "话反说", "反", "说反话"])
async def start_rev():
app.set_state("reverse")
await app.send("开始说反话")
# 反向复读
@app.on_state_text("reverse")
async def reverse_echo():
msg = str(app.message)
msg = "".join(s for s in reversed(msg))
await app.send(msg)
# 通过命令,跳转回初始状态
@app.on_state_command("back", "reverse")
async def back():
app.set_state()
await app.send("话反说止停")
插件编写范例 a plus b
'''
a + b
各群聊间、各插件间,数据独立,互不影响;不需要自己再专门建个字典了
'''
from ayaka import AyakaApp
app = AyakaApp("a-plus-b")
@app.on_command("set_a")
async def set_a():
app.cache.a = int(str(app.arg)) if app.arg else 0
await app.send(app.cache.a)
@app.on_command("set_b")
async def set_b():
app.cache.b = int(str(app.arg)) if app.arg else 0
await app.send(app.cache.b)
@app.on_command("calc")
async def calc():
a = app.cache.a or 0
b = app.cache.b or 0
await app.send(str(a+b))
定时器 Timer
注意,定时器触发回调时,由于缺乏消息激励源,app的大部分属性(bot、group、event、valid、cache、user_name等)将无法正确访问到,并且无法使用app.send方法,需要使用专用的t_send方法
'''
整点报时
'''
from ayaka import AyakaApp
app = AyakaApp("整点报时")
@app.on_interval(60, s=0)
async def every_minute():
await app.t_send(bot_id=2317709898, group_id=666214666, message="小乐")
@app.on_interval(3600, m=0, s=0)
async def every_hour():
await app.t_send(bot_id=2317709898, group_id=666214666, message="大乐")
@app.on_everyday(h=23, m=59, s=59)
async def every_day():
await app.t_send(bot_id=2317709898, group_id=666214666, message="呃呃呃一天要结束了")
截图 playwright
注意,win平台使用playwright时需要关闭fastapi的reload功能
'''
can can baidu
'''
from pathlib import Path
from ayaka import get_new_page, AyakaApp, MessageSegment
app = AyakaApp("看看baidu")
@app.on_command("ccb")
async def _():
async with get_new_page() as p:
await p.goto("http://www.baidu.com", wait_until="networkidle")
path = Path("test.png").absolute()
await p.screenshot(path=path)
image = MessageSegment.image(path)
await app.send(image)
自动分割消息
ayaka插件将会自动根据配置项中的分割符来分割消息,例如
#test a b c
会在ayaka插件处理后变为
@app.on_command("test")
async def _():
# 此时app身上的如下属性的值应该是:...
assert app.cmd == "test"
assert str(app.arg) == "a b c"
assert str(app.args[0]) == "a"
assert str(app.args[1]) == "b"
assert str(app.args[2]) == "c"
未来计划
提供aiosqlite数据库支持
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for nonebot_plugin_ayaka-0.3.6.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | d4a2b677b3c95180ff8205ae8d33cd3993d3e3eea06d3276945cd76769d32247 |
|
MD5 | be54bb9f6b715fe0b700917f284b925a |
|
BLAKE2b-256 | 6ab27aa29343fe1e357ff44ca77801c4e4a198dcf8a63019ccdf10704e5a7cf5 |
Hashes for nonebot_plugin_ayaka-0.3.6-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 022643dba856bb5f0474438610a4835f4635692de060ab4f4742a52d28ee8f5d |
|
MD5 | 8668d7d379e353eaa8cc409a6353eddc |
|
BLAKE2b-256 | 05d6a06e4d0772f885f16cd62fdd51e299b0d977393ff6c616dacbbb8e698290 |