Skip to content

快速开始

编写一个星际旅行插件

假设所有消息都是群消息

基本使用

打开和关闭应用

状态 动作 效果
闲置状态 星际旅行、travel 打开星际旅行应用
运行星际旅行中 退出、exit 关闭星际旅行应用
from ayaka import AyakaApp, AyakaInput

app = AyakaApp("星际旅行")
app.help = "xing ji lv xing"

# 启动应用
app.set_start_cmds("星际旅行", "travel")

# 装饰器的顺序没有强制要求,随便写


# 关闭应用
@app.on_state()
@app.on_deep_all()
@app.on_cmd("退出", "exit")
async def exit_app():
    await app.close()

群组状态

收到星际旅行travel命令后,群组会从闲置状态变为运行星际旅行应用(即从root状态变为root.星际旅行状态)

收到退出exit命令后,群组会从运行星际旅行应用变为闲置状态(即从root.星际旅行状态变为root状态)

旅行

实现旅行的功能

地点 动作 效果
地球 drink 喝水
月球 drink 喝土
太阳 drink 喝太阳风
任意地点 move <地名> 去指定地点
任意地点 hi hi I'm in <地名>
from pydantic import Field
from ayaka import AyakaApp, AyakaInput

app = AyakaApp("星际旅行")
app.help = "xing ji lv xing"

# 启动应用
app.set_start_cmds("星际旅行", "travel")

# 装饰器的顺序没有强制要求,随便写


# 关闭应用
@app.on_state()
@app.on_deep_all()
@app.on_cmd("退出", "exit")
async def exit_app():
    await app.close()


# 注册各种行动
@app.on_state("地球")
@app.on_cmd("drink")
async def drink():
    '''喝水'''
    await app.send("喝水")


@app.on_state("月球")
@app.on_cmd("drink")
async def drink():
    '''喝土'''
    await app.send("喝土")


@app.on_cmd("drink")
@app.on_state("太阳")
async def drink():
    '''喝太阳风'''
    await app.send("喝太阳风")


class UserInput(AyakaInput):
    where: str = Field(description="你要去的地方")


@app.on_state()
@app.on_deep_all()
@app.on_cmd("move")
async def move(userinput: UserInput):
    '''移动'''
    await app.set_state(userinput.where)
    await app.send(f"前往 {userinput.where}")


@app.on_state()
@app.on_deep_all()
@app.on_cmd("hi")
async def say_hi():
    '''打招呼'''
    await app.send(f"hi I'm in {app.state[2:]}")

用状态指代地点

如果我们用root.星际旅行.地球状态指代你正处于地球

那么打开应用后,你处于root.星际旅行状态,这一状态是一切其他状态(root.星际旅行.地球root.星际旅行.月球等)的基础

on_state

代码 对应的state
@app.on_state() 参数为空,对应root.星际旅行
@app.on_state("地球") 对应root.星际旅行.地球
@app.on_state(["地球","中国"]) 对应root.星际旅行.地球.中国
@app.on_state("地球","月球") 对应root.星际旅行.地球root.星际旅行.月球

注册回调

对不同的状态注册不同的回调 = 在不同的地点做不同的事情

# 在地球喝水
@app.on_state("地球")
@app.on_cmd("drink")
async def drink():
    '''喝水'''
    await app.send("喝水")


# 在月球喝水
@app.on_state("月球")
@app.on_cmd("drink")
async def drink():
    '''喝土'''
    await app.send("喝土")

子状态可以触发父状态的回调

# 该回调注册在 root.星际旅行 状态下,由于设置了on_deep_all,它还对所有子状态生效
# 也就说,当我们位于 root.星际旅行.月球 时,也可以触发move回调
@app.on_state()
@app.on_deep_all()
@app.on_cmd("move")
async def move(userinput: UserInput):
    '''移动'''
    await app.set_state(userinput.where)
    await app.send(f"前往 {userinput.where}")

state还可以切片

    print(app.state)
    # root.星际旅行.太阳

    print(app.state[2:])
    # 太阳

进一步了解app.state AyakaState

AyakaInput

class UserInput(AyakaInput):
    where: str = Field(description="你要去的地方")

进一步了解AyakaInput AyakaInput

实现效果

<<< "user" 说:#travel >>> "Bot" 说:已打开应用 [星际旅行] <<< "user" 说:#hi >>> "Bot" 说:hi I'm in <<< "user" 说:#move 地球 >>> "Bot" 说:前往 地球 <<< "user" 说:#hi >>> "Bot" 说:hi I'm in 地球 <<< "user" 说:#drink >>> "Bot" 说:喝水 <<< "user" 说:#move 月球 >>> "Bot" 说:前往 月球 <<< "user" 说:#drink >>> "Bot" 说:喝土 <<< "user" 说:#move 太阳 >>> "Bot" 说:前往 太阳 <<< "user" 说:#drink >>> "Bot" 说:喝太阳风 <<< "user" 说:#hi >>> "Bot" 说:hi I'm in 太阳 <<< "user" 说:#exit >>> "Bot" 说:已关闭应用 [星际旅行]

多层次的回调

在刚才的基础上,我们希望能再加点功能,比如,太阳上新开了一家奶茶店

地点 动作 效果
太阳.奶茶店 drink 喝了一口3000度的奶茶
太阳.其他地点 drink 喝太阳风
from pydantic import Field
from ayaka import AyakaApp, AyakaInput

app = AyakaApp("星际旅行")
app.help = "xing ji lv xing"


# 启动应用
app.set_start_cmds("星际旅行", "travel")

# 装饰器的顺序没有强制要求,随便写


# 关闭应用
@app.on_state()
@app.on_cmd("退出", "exit")
@app.on_deep_all()
async def exit_app():
    await app.close()


# 注册各种行动
@app.on_state("地球")
@app.on_cmd("drink")
async def drink():
    '''喝水'''
    await app.send("喝水")


@app.on_state("月球")
@app.on_cmd("drink")
async def drink():
    '''喝土'''
    await app.send("喝土")


@app.on_state("太阳")
@app.on_cmd("drink")
@app.on_deep_all()
async def drink():
    '''喝太阳风'''
    await app.send("喝太阳风")


@app.on_state(["太阳", "奶茶店"])
@app.on_cmd("drink")
async def drink():
    '''喝奶茶'''
    await app.send("喝了一口3000度的奶茶")


class UserInput(AyakaInput):
    where: str = Field(description="你要去的地方")


@app.on_state()
@app.on_cmd("move")
@app.on_deep_all()
async def move(userinput: UserInput):
    '''移动'''
    await app.set_state(userinput.where)
    await app.send(f"前往 {userinput.where}")


@app.on_state()
@app.on_cmd("hi")
@app.on_deep_all()
async def say_hi():
    '''打招呼'''
    await app.send(f"hi I'm in {app.state[2:]}")

注意到,目前定义了两个drink

地点 动作 效果
太阳 drink 喝太阳风
太阳.奶茶店 drink 喝了一口3000度的奶茶

优先触发最匹配的回调

  • 当状态为太阳.奶茶店时,触发后者
  • 当状态为太阳.森林公园太阳.巴拉巴拉太阳.我就是来凑个数太阳时,触发前者

进一步了解上溯查询 上溯查询

实现效果

<<< "user" 说:#travel >>> "Bot" 说:已打开应用 [星际旅行] <<< "user" 说:#move 太阳 >>> "Bot" 说:前往 太阳 <<< "user" 说:#drink >>> "Bot" 说:喝太阳风 <<< "user" 说:#move 太阳.森林公园 >>> "Bot" 说:前往 太阳.森林公园 <<< "user" 说:#drink >>> "Bot" 说:喝太阳风 <<< "user" 说:#move 太阳.奶茶店 >>> "Bot" 说:前往 太阳.奶茶店 <<< "user" 说:#drink >>> "Bot" 说:喝了一口3000度的奶茶 <<< "user" 说:#hi >>> "Bot" 说:hi I'm in 太阳.奶茶店 <<< "user" 说:#exit >>> "Bot" 说:已关闭应用 [星际旅行]

缓存

宇宙旅游公司又开设了一个新项目:耀斑表演

我们需要先去售票处买耀斑表演的门票,然后才能看表演,一张票不能用多次

地点 动作 效果
太阳.售票处 buy 耀斑表演门票+1
太阳.任意地点 watch 看表演
from pydantic import Field
from ayaka import AyakaApp, AyakaInput, AyakaCache

app = AyakaApp("星际旅行")
app.help = "xing ji lv xing"


# 启动应用
app.set_start_cmds("星际旅行", "travel")

# 装饰器的顺序没有强制要求,随便写


# 关闭应用
@app.on_state()
@app.on_cmd("退出", "exit")
@app.on_deep_all()
async def exit_app():
    await app.close()


# 注册各种行动
@app.on_state("地球")
@app.on_cmd("drink")
async def drink():
    '''喝水'''
    await app.send("喝水")


@app.on_state("月球")
@app.on_cmd("drink")
async def drink():
    '''喝土'''
    await app.send("喝土")


@app.on_state("太阳")
@app.on_cmd("drink")
@app.on_deep_all()
async def drink():
    '''喝太阳风'''
    await app.send("喝太阳风")


@app.on_state(["太阳", "奶茶店"])
@app.on_cmd("drink")
async def drink():
    '''喝奶茶'''
    await app.send("喝了一口3000度的奶茶")


class UserInput(AyakaInput):
    where: str = Field(description="你要去的地方")


@app.on_state()
@app.on_cmd("move")
@app.on_deep_all()
async def move(userinput: UserInput):
    '''移动'''
    await app.set_state(userinput.where)
    await app.send(f"前往 {userinput.where}")


@app.on_state()
@app.on_cmd("hi")
@app.on_deep_all()
async def say_hi():
    '''打招呼'''
    await app.send(f"hi I'm in {app.state[2:]}")


class Cache(AyakaCache):
    ticket: int = 0


@app.on_state(["太阳", "售票处"])
@app.on_cmd("buy", "买票")
async def buy_ticket(cache: Cache):
    '''买门票'''
    cache.ticket += 1
    await app.send("耀斑表演门票+1")


@app.on_deep_all()
@app.on_state("太阳")
@app.on_cmd("watch", "看表演")
async def watch(cache: Cache):
    '''看表演'''
    if cache.ticket <= 0:
        await app.send("先去售票处买票!")
    else:
        cache.ticket -= 1
        await app.send("10分甚至9分的好看")

进一步了解AyakaCache AyakaCache

实现效果

<<< "user" 说:#travel >>> "Bot" 说:已打开应用 [星际旅行] <<< "user" 说:#move 太阳 >>> "Bot" 说:前往 太阳 <<< "user" 说:#watch >>> "Bot" 说:先去售票处买票! <<< "user" 说:#move 太阳.售票处 >>> "Bot" 说:前往 太阳.售票处 <<< "user" 说:#buy >>> "Bot" 说:耀斑表演门票+1 <<< "user" 说:#watch >>> "Bot" 说:10分甚至9分的好看 <<< "user" 说:#watch >>> "Bot" 说:先去售票处买票! <<< "user" 说:#exit >>> "Bot" 说:已关闭应用 [星际旅行]

命令触发 vs 消息触发

刚才的所有回调都是由具体的、指定的命令来触发运行的

然而,你也可以使用消息触发

比如,你在奶茶店随便说了一句话,就能触发该回调运行

from pydantic import Field
from ayaka import AyakaApp, AyakaInput, AyakaCache

app = AyakaApp("星际旅行")
app.help = "xing ji lv xing"


# 启动应用
app.set_start_cmds("星际旅行", "travel")

# 装饰器的顺序没有强制要求,随便写


# 关闭应用
@app.on_state()
@app.on_cmd("退出", "exit")
@app.on_deep_all()
async def exit_app():
    await app.close()


# 注册各种行动
@app.on_state("地球")
@app.on_cmd("drink")
async def drink():
    '''喝水'''
    await app.send("喝水")


@app.on_state("月球")
@app.on_cmd("drink")
async def drink():
    '''喝土'''
    await app.send("喝土")


@app.on_state("太阳")
@app.on_cmd("drink")
@app.on_deep_all()
async def drink():
    '''喝太阳风'''
    await app.send("喝太阳风")


@app.on_state(["太阳", "奶茶店"])
@app.on_cmd("drink")
async def drink():
    '''喝奶茶'''
    await app.send("喝了一口3000度的奶茶")


class UserInput(AyakaInput):
    where: str = Field(description="你要去的地方")


@app.on_state()
@app.on_cmd("move")
@app.on_deep_all()
async def move(userinput: UserInput):
    '''移动'''
    await app.set_state(userinput.where)
    await app.send(f"前往 {userinput.where}")


@app.on_state()
@app.on_cmd("hi")
@app.on_deep_all()
async def say_hi():
    '''打招呼'''
    await app.send(f"hi I'm in {app.state[2:]}")


class Cache(AyakaCache):
    ticket: int = 0


@app.on_state(["太阳", "售票处"])
@app.on_cmd("buy", "买票")
async def buy_ticket(cache: Cache):
    '''买门票'''
    cache.ticket += 1
    await app.send("耀斑表演门票+1")


@app.on_deep_all()
@app.on_state("太阳")
@app.on_cmd("watch", "看表演")
async def watch(cache: Cache):
    '''看表演'''
    if cache.ticket <= 0:
        await app.send("先去售票处买票!")
    else:
        cache.ticket -= 1
        await app.send("10分甚至9分的好看")


@app.on_state(["太阳", "奶茶店"])
async def handle():
    '''令人震惊的事实'''
    await app.send("你发现这里只卖热饮")

消息触发的优先级低于命令触发

进一步了解app.on_xxx() on_xxx

实现效果

<<< "user" 说:#travel >>> "Bot" 说:已打开应用 [星际旅行] <<< "user" 说:#move 太阳 >>> "Bot" 说:前往 太阳 <<< "user" 说:#hi >>> "Bot" 说:hi I'm in 太阳 <<< "user" 说:#move 太阳.奶茶店 >>> "Bot" 说:前往 太阳.奶茶店 <<< "user" 说:嗯? >>> "Bot" 说:你发现这里只卖热饮 <<< "user" 说:#hi >>> "Bot" 说:hi I'm in 太阳.奶茶店 <<< "user" 说:#exit >>> "Bot" 说:已关闭应用 [星际旅行]

插件帮助

现在,我们写完了一个小插件了,那么该为它编写帮助了

不幸的是,你已经在上述代码中完成了它

通过分析 注册回调的注释(__doc__),ayaka内置插件ayaka_master会自动生成对应的帮助

现在,你只需发送命令help

实现效果

<<< "user" 说:#help 星际旅行 >>> "Bot" 说:[星际旅行] xing ji lv xing - 星际旅行/travel | 打开应用 [星际旅行] - 退出/exit - move <where> | 移动 <where> 你要去的地方 - hi | 打招呼 [星际旅行.地球] - drink | 喝水 [星际旅行.月球] - drink | 喝土 [星际旅行.太阳] - drink | 喝太阳风 - watch/看表演 | 看表演 [星际旅行.太阳.奶茶店] - drink | 喝奶茶 - <任意文字> | 令人震惊的事实 [星际旅行.太阳.售票处] - buy/买票 | 买门票 <<< "user" 说:#travel >>> "Bot" 说:已打开应用 [星际旅行] <<< "user" 说:#help >>> "Bot" 说:[星际旅行] - 退出/exit - move <where> | 移动 <where> 你要去的地方 - hi | 打招呼 <<< "user" 说:#move 太阳.奶茶店 >>> "Bot" 说:前往 太阳.奶茶店 <<< "user" 说:#help >>> "Bot" 说:[星际旅行.太阳.奶茶店] - drink | 喝奶茶 - <任意文字> | 令人震惊的事实 [星际旅行.太阳] - watch/看表演 | 看表演 [星际旅行] - 退出/exit - move <where> | 移动 <where> 你要去的地方 - hi | 打招呼 <<< "user" 说:#exit >>> "Bot" 说:已关闭应用 [星际旅行]

下一步

在这里~ ↘