(window.webpackJsonp=window.webpackJsonp||[]).push([[54],{454:function(s,t,a){"use strict";a.r(t);var n=a(55),e=Object(n.a)({},function(){var s=this,t=s.$createElement,a=s._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":s.$parent.slotKey}},[a("h1",{attrs:{id:"编写命令"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#编写命令","aria-hidden":"true"}},[s._v("#")]),s._v(" 编写命令")]),s._v(" "),a("p",[s._v("本章将以一个天气查询插件为例,教你如何编写自己的命令。")]),s._v(" "),a("div",{staticClass:"custom-block tip"},[a("p",{staticClass:"custom-block-title"},[s._v("提示")]),s._v(" "),a("p",[s._v("本章的完整代码可以在 "),a("a",{attrs:{href:"https://github.com/nonebot/nonebot/tree/master/docs/guide/code/awesome-bot-2",target:"_blank",rel:"noopener noreferrer"}},[s._v("awesome-bot-2"),a("OutboundLink")],1),s._v(" 查看。")]),s._v(" "),a("p",[s._v("如果你在寻找对应 1.8.0 版本以下的教程,请参考 "),a("RouterLink",{attrs:{to:"/advanced/legacy_features.html#session-get-和参数解析器"}},[s._v("这里")]),s._v("。")],1)]),s._v(" "),a("h2",{attrs:{id:"创建插件目录"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#创建插件目录","aria-hidden":"true"}},[s._v("#")]),s._v(" 创建插件目录")]),s._v(" "),a("p",[s._v("首先我们需要创建一个目录来存放插件,这个目录需要满足一些条件才能作为插件目录,首先,我们的代码能够比较容易访问到它,其次,它必须是一个能够以 Python 模块形式导入的路径(后面解释为什么),一个比较好的位置是项目目录中的 "),a("code",[s._v("awesome/plugins/")]),s._v(",创建好之后,我们的 "),a("code",[s._v("awesome-bot")]),s._v(" 项目的目录结构如下:")]),s._v(" "),a("div",{staticClass:"language- line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[s._v("awesome-bot\n├── awesome\n│ └── plugins\n├── bot.py\n└── config.py\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br")])]),a("p",[s._v("接着在 "),a("code",[s._v("plugins")]),s._v(" 目录中新建一个名为 "),a("code",[s._v("weather.py")]),s._v(" 的 Python 文件,暂时留空,此时目录结构如下:")]),s._v(" "),a("div",{staticClass:"language- line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[s._v("awesome-bot\n├── awesome\n│ └── plugins\n│ └── weather.py\n├── bot.py\n└── config.py\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br")])]),a("h2",{attrs:{id:"加载插件"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#加载插件","aria-hidden":"true"}},[s._v("#")]),s._v(" 加载插件")]),s._v(" "),a("p",[s._v("现在我们的插件目录已经有了一个空的 "),a("code",[s._v("weather.py")]),s._v(",实际上它已经可以被称为一个插件了,尽管它还什么都没做。下面我们来让 NoneBot 加载这个插件,修改 "),a("code",[s._v("bot.py")]),s._v(" 如下:")]),s._v(" "),a("div",{staticClass:"language-python line-numbers-mode"},[a("div",{staticClass:"highlight-lines"},[a("div",{staticClass:"highlighted"},[s._v(" ")]),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("div",{staticClass:"highlighted"},[s._v(" ")]),a("div",{staticClass:"highlighted"},[s._v(" ")]),a("div",{staticClass:"highlighted"},[s._v(" ")]),a("div",{staticClass:"highlighted"},[s._v(" ")]),a("br"),a("br")]),a("pre",{pre:!0,attrs:{class:"language-python"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("from")]),s._v(" os "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("import")]),s._v(" path\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("import")]),s._v(" nonebot\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("import")]),s._v(" config\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("if")]),s._v(" __name__ "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("==")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'__main__'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v("\n nonebot"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("init"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n nonebot"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("load_builtin_plugins"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n nonebot"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("load_plugins"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("\n path"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("join"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("path"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("dirname"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("__file__"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'awesome'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'plugins'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'awesome.plugins'")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n nonebot"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("run"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n")])]),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br")])]),a("p",[s._v("这里的重点在于 "),a("code",[s._v("nonebot.load_plugins()")]),s._v(" 函数的两个参数。第一个参数是插件目录的路径,这里根据 "),a("code",[s._v("bot.py")]),s._v(" 的所在路径和相对路径拼接得到;第二个参数是导入插件模块时使用的模块名前缀,这个前缀要求必须是一个当前 Python 解释器可以导入的模块前缀,NoneBot 会在它后面加上插件的模块名共同组成完整的模块名来让解释器导入,因此这里我们传入 "),a("code",[s._v("awesome.plugins")]),s._v(",当运行 "),a("code",[s._v("bot.py")]),s._v(" 的时候,Python 解释器就能够正确导入 "),a("code",[s._v("awesome.plugins.weather")]),s._v(" 这个插件模块了。")]),s._v(" "),a("p",[s._v("尝试运行 "),a("code",[s._v("python bot.py")]),s._v(",可以看到日志输出了类似如下内容:")]),s._v(" "),a("div",{staticClass:"language- line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[s._v('[2018-08-18 21:46:55,425 nonebot] INFO: Succeeded to import "awesome.plugins.weather"\n')])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br")])]),a("p",[s._v("这表示 NoneBot 已经成功加载到了 "),a("code",[s._v("weather")]),s._v(" 插件。")]),s._v(" "),a("div",{staticClass:"custom-block warning"},[a("p",{staticClass:"custom-block-title"},[s._v("注意")]),s._v(" "),a("p",[s._v("如果你运行时没有输出成功导入插件的日志,请确保你的当前工作目录是在 "),a("code",[s._v("awesome-bot")]),s._v(" 项目的主目录中。")]),s._v(" "),a("p",[s._v("如果仍然不行,尝试先在 "),a("code",[s._v("awesome-bot")]),s._v(" 主目录中执行下面的命令:")]),s._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[a("span",{pre:!0,attrs:{class:"token function"}},[s._v("export")]),s._v(" PYTHONPATH"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(". "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# Linux / macOS")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("set")]),s._v(" PYTHONPATH"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(". "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# Windows")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br")])])]),s._v(" "),a("h2",{attrs:{id:"编写真正的内容"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#编写真正的内容","aria-hidden":"true"}},[s._v("#")]),s._v(" 编写真正的内容")]),s._v(" "),a("p",[s._v("好了,现在已经确保插件可以正确加载,我们可以开始编写命令的实际代码了。在 "),a("code",[s._v("weather.py")]),s._v(" 中添加如下代码:")]),s._v(" "),a("div",{staticClass:"language-python line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-python"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("from")]),s._v(" nonebot "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("import")]),s._v(" on_command"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" CommandSession\n\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# on_command 装饰器将函数声明为一个命令处理器")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 这里 weather 为命令的名字,同时允许使用别名「天气」「天气预报」「查天气」")]),s._v("\n@on_command"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'weather'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" aliases"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'天气'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'天气预报'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'查天气'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("def")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("weather")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" CommandSession"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 取得消息的内容,并且去掉首尾的空白符")]),s._v("\n city "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("current_arg_text"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("strip"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 如果除了命令的名字之外用户还提供了别的内容,即用户直接将城市名跟在命令名后面,")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 则此时 city 不为空。例如用户可能发送了:\"天气 南京\",则此时 city == '南京'")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v('# 否则这代表用户仅发送了:"天气" 二字,机器人将会向其发送一条消息并且等待其回复')]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("if")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("not")]),s._v(" city"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v("\n city "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("aget"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("prompt"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'你想查询哪个城市的天气呢?'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("strip"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 如果用户只发送空白符,则继续询问")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("while")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("not")]),s._v(" city"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v("\n city "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("aget"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("prompt"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'要查询的城市名称不能为空呢,请重新输入'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("strip"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 获取城市的天气预报")]),s._v("\n weather_report "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" get_weather_of_city"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("city"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 向用户发送天气预报")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("send"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("weather_report"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("def")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("get_weather_of_city")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("city"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token builtin"}},[s._v("str")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("-")]),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token builtin"}},[s._v("str")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 这里简单返回一个字符串")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 实际应用中,这里应该调用返回真实数据的天气 API,并拼接成天气预报内容")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" f"),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'{city}的天气是……'")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br"),a("span",{staticClass:"line-number"},[s._v("15")]),a("br"),a("span",{staticClass:"line-number"},[s._v("16")]),a("br"),a("span",{staticClass:"line-number"},[s._v("17")]),a("br"),a("span",{staticClass:"line-number"},[s._v("18")]),a("br"),a("span",{staticClass:"line-number"},[s._v("19")]),a("br"),a("span",{staticClass:"line-number"},[s._v("20")]),a("br"),a("span",{staticClass:"line-number"},[s._v("21")]),a("br"),a("span",{staticClass:"line-number"},[s._v("22")]),a("br"),a("span",{staticClass:"line-number"},[s._v("23")]),a("br"),a("span",{staticClass:"line-number"},[s._v("24")]),a("br"),a("span",{staticClass:"line-number"},[s._v("25")]),a("br"),a("span",{staticClass:"line-number"},[s._v("26")]),a("br"),a("span",{staticClass:"line-number"},[s._v("27")]),a("br")])]),a("div",{staticClass:"custom-block tip"},[a("p",{staticClass:"custom-block-title"},[s._v("提示")]),s._v(" "),a("p",[s._v("从这里开始,你需要对 Python 的 asyncio 编程有所了解,因为 NoneBot 是完全基于 asyncio 的,具体可以参考 "),a("a",{attrs:{href:"https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143208573480558080fa77514407cb23834c78c6c7309000",target:"_blank",rel:"noopener noreferrer"}},[s._v("廖雪峰的 Python 教程"),a("OutboundLink")],1),s._v("。")])]),s._v(" "),a("p",[s._v("为了简单起见,我们在这里的例子中没有接入真实的天气数据,但要接入也非常简单,你可以使用中国天气网、和风天气网、OpenWeatherMap 等网站提供的 API。")]),s._v(" "),a("p",[s._v("上面的代码中基本上每一行做了什么都在注释里写了。我们来实际启动一下 NoneBot,看看输入命令后会发生什么:")]),s._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" /天气 南京\n南京的天气是……\n\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" /天气\n你想查询哪个城市的天气呢?\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" 南京\n南京的天气是……\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br")])]),a("p",[s._v("恭喜,你已经完成了一个"),a("strong",[s._v("可交互的")]),s._v("天气查询命令的雏形,只需要再接入天气 API 就可以真正投入使用了!")]),s._v(" "),a("p",[s._v("实际上,这里的 "),a("code",[s._v("weather")]),s._v(" 的函数的逻辑就相当于此代码片段:")]),s._v(" "),a("div",{staticClass:"language-python line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-python"}},[a("code",[s._v("city "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" user_arg"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("strip"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("if")]),s._v(" city "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("==")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("''")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v("\n city "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token builtin"}},[s._v("input")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'你想查询哪个城市的天气呢?'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("strip"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("while")]),s._v(" city "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("==")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("''")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v("\n city "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token builtin"}},[s._v("input")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'要查询的城市名称不能为空呢,请重新输入'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("strip"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\nweather_report "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("city"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("print")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("weather_report"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br")])]),a("p",[s._v("可以看到如果你知道如何编写控制台对话的程序,你就知道如何编写 NoneBot 的命令处理器。再复杂的对话也不过而已。")]),s._v(" "),a("h2",{attrs:{id:"原理"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#原理","aria-hidden":"true"}},[s._v("#")]),s._v(" 原理")]),s._v(" "),a("p",[s._v("「命令」是 NoneBot 机器人核心组成部分之一。像 "),a("RouterLink",{attrs:{to:"/guide/whats-happened.html#命令处理器"}},[s._v("之前讲过的一样")]),s._v(",每当用户对机器人发送了一条消息,NoneBot 会尝试将消息匹配到每个命令中。在分别匹配了 "),a("code",[s._v("/")]),s._v(" 和 "),a("code",[s._v("天气")]),s._v(" 后,就会进入到我们定义的 "),a("code",[s._v("weather")]),s._v(" 函数中。")],1),s._v(" "),a("div",{staticClass:"language-python line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-python"}},[a("code",[s._v("@on_command"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'weather'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" aliases"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'天气'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'天气预报'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'查天气'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("def")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("weather")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" CommandSession"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br")])]),a("p",[s._v("如果这两步中没有匹配到相应命令,那么此消息就会被暂时地忽略掉。通过在配置项中的 "),a("code",[s._v("DEBUG")]),s._v(",你可以在运行日志中看到完整的匹配过程。")]),s._v(" "),a("p",[s._v("在进入命令会话后,此时用户发送的消息仅剩了 "),a("code",[s._v("南京")]),s._v(" 这一部分。这部分文本将会通过 "),a("code",[s._v("session.current_arg_text")]),s._v(" 表现出来,从而进行下一步的过程直至此函数执行完毕,即命令处理完毕。")]),s._v(" "),a("p",[s._v("如果我们发送的仅仅是 "),a("code",[s._v("/天气")]),s._v(" 会怎样?此时 "),a("code",[s._v("session.current_arg_text")]),s._v(" 将不包含任何有意义的内容,即为空串。于是我们使用了 "),a("code",[s._v("session.aget")]),s._v(" 功能向用户发起提问:")]),s._v(" "),a("div",{staticClass:"language-python line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-python"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("aget"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("prompt"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'你想查询哪个城市的天气呢?'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br")])]),a("p",[s._v("当我们调用此方法时,正在进行的命令会话会暂停。当用户又一次向机器人说话时,"),a("code",[s._v("aget")]),s._v(" 调用将会获得用户此次发送的消息内容,比如 "),a("code",[s._v("南京")]),s._v(","),a("strong",[s._v("继续执行当前会话。在此期间,机器人将会不被干扰地处理其他消息。")])]),s._v(" "),a("p",[s._v("在这里,我们还对其返回值做了 "),a("code",[s._v(".strip()")]),s._v(",处理。这代表如果用户只是发送了显然没有意义的空白字符,我们将重新询问,例如:")]),s._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" /天气\n你想查询哪个城市的天气呢?\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" \n要查询的城市名称不能为空呢,请重新输入\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" 南京\n南京的天气是……\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br")])]),a("p",[s._v("直至成功获取到 "),a("code",[s._v("city")]),s._v(" 变量并完成命令。此外,如果用户在一定时间内(默认 5 分钟,可通过 "),a("code",[s._v("SESSION_EXPIRE_TIMEOUT")]),s._v(" 配置项来更改)都没有再次跟机器人发消息,则会话将会因超时被关闭。")]),s._v(" "),a("div",{staticClass:"custom-block tip"},[a("p",{staticClass:"custom-block-title"},[s._v("提示")]),s._v(" "),a("p",[s._v("上面用了 "),a("code",[s._v("session.current_arg_text")]),s._v(" 来获取用户当前输入的参数,这表示从用户输入中提取纯文本部分,也就是说不包含图片、表情、语音、卡片分享等。")]),s._v(" "),a("p",[s._v("如果需要用户输入的原始内容,请使用 "),a("code",[s._v("session.current_arg")]),s._v(",里面可能包含 CQ 码。除此之外,还可以通过 "),a("code",[s._v("session.current_arg_images")]),s._v(" 获取消息中的图片 URL 列表。")]),s._v(" "),a("p",[s._v("另外一点值得注意的是,"),a("code",[s._v("@on_command")]),s._v(" 也可以传入正则表达式作为参数 "),a("code",[s._v("patterns")]),s._v(",在这种情况下,整条完整的指令会被作为 "),a("code",[s._v("session.current_arg")]),s._v(" 使用(而不会删除开头匹配到的命令),这点请注意区别。")])])])},[],!1,null,null,null);t.default=e.exports}}]);