├── .github └── workflows │ ├── docs-build.yml │ ├── pre-commit.yml │ └── pypi-publish.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pre-commit └── check_filename.py ├── CNAME ├── LICENSE-LSO ├── LICENSE-MIT ├── LICENSE-MULAN ├── README.md ├── README_DEV.md ├── README_EN.md ├── README_TOOLS.md ├── README_TOOLS_EN.md ├── build-docs.sh ├── docs ├── .vitepress │ ├── config │ │ ├── common.ts │ │ ├── en.ts │ │ ├── index.ts │ │ ├── ja.ts │ │ └── zh.ts │ └── theme │ │ ├── LICENSE │ │ ├── Layout.vue │ │ ├── index.ts │ │ └── style.css ├── components │ └── ContributorsBar.vue ├── en │ ├── dev │ │ ├── api │ │ │ ├── azure.md │ │ │ ├── azure_onebot.md │ │ │ ├── config.md │ │ │ ├── constants.md │ │ │ ├── deal_latex.md │ │ │ ├── hunyuan.md │ │ │ ├── index.md │ │ │ ├── metadata.md │ │ │ ├── models.md │ │ │ ├── plugin │ │ │ │ ├── index.md │ │ │ │ ├── load.md │ │ │ │ ├── models.md │ │ │ │ ├── register.md │ │ │ │ ├── typing.md │ │ │ │ └── utils.md │ │ │ ├── plugins │ │ │ │ ├── marshoai_bangumi │ │ │ │ │ └── index.md │ │ │ │ └── marshoai_basic │ │ │ │ │ └── index.md │ │ │ ├── tools │ │ │ │ ├── marshoai_bangumi │ │ │ │ │ └── index.md │ │ │ │ ├── marshoai_basic │ │ │ │ │ └── index.md │ │ │ │ ├── marshoai_megakits │ │ │ │ │ ├── index.md │ │ │ │ │ ├── mk_common.md │ │ │ │ │ ├── mk_info.md │ │ │ │ │ ├── mk_morse_code.md │ │ │ │ │ └── mk_nya_code.md │ │ │ │ └── marshoai_meogirl │ │ │ │ │ ├── index.md │ │ │ │ │ ├── mg_info.md │ │ │ │ │ ├── mg_introduce.md │ │ │ │ │ └── mg_search.md │ │ │ ├── tools_wip │ │ │ │ └── marshoai_memory │ │ │ │ │ └── index.md │ │ │ ├── util.md │ │ │ └── util_hunyuan.md │ │ └── index.md │ ├── index.md │ └── start │ │ ├── index.md │ │ └── install.md ├── ja │ └── index.md ├── public │ ├── favicon.ico │ └── marsho-full.svg └── zh │ ├── dev │ ├── api │ │ ├── azure.md │ │ ├── azure_onebot.md │ │ ├── config.md │ │ ├── constants.md │ │ ├── deal_latex.md │ │ ├── hunyuan.md │ │ ├── index.md │ │ ├── metadata.md │ │ ├── models.md │ │ ├── plugin │ │ │ ├── index.md │ │ │ ├── load.md │ │ │ ├── models.md │ │ │ ├── register.md │ │ │ ├── typing.md │ │ │ └── utils.md │ │ ├── plugins │ │ │ ├── marshoai_bangumi │ │ │ │ └── index.md │ │ │ └── marshoai_basic │ │ │ │ └── index.md │ │ ├── tools │ │ │ ├── marshoai_bangumi │ │ │ │ └── index.md │ │ │ ├── marshoai_basic │ │ │ │ └── index.md │ │ │ ├── marshoai_megakits │ │ │ │ ├── index.md │ │ │ │ ├── mk_common.md │ │ │ │ ├── mk_info.md │ │ │ │ ├── mk_morse_code.md │ │ │ │ └── mk_nya_code.md │ │ │ └── marshoai_meogirl │ │ │ │ ├── index.md │ │ │ │ ├── mg_info.md │ │ │ │ ├── mg_introduce.md │ │ │ │ └── mg_search.md │ │ ├── tools_wip │ │ │ └── marshoai_memory │ │ │ │ └── index.md │ │ ├── util.md │ │ └── util_hunyuan.md │ ├── extension.md │ ├── index.md │ └── project.md │ ├── index.md │ └── start │ ├── index.md │ ├── install-old.md │ ├── install.md │ └── use.md ├── main.py ├── nonebot_plugin_marshoai ├── __init__.py ├── _types.py ├── cache │ └── decos.py ├── config.py ├── constants.py ├── deal_latex.py ├── dev.py ├── handler.py ├── hooks.py ├── hunyuan.py ├── instances.py ├── marsho.py ├── marsho_onebot.py ├── metadata.py ├── models.py ├── observer.py ├── plugin │ ├── __init__.py │ ├── func_call │ │ ├── __init__.py │ │ ├── caller.py │ │ ├── models.py │ │ ├── params.py │ │ └── utils.py │ ├── load.py │ ├── models.py │ ├── typing.py │ └── utils.py ├── plugins │ ├── builtin_tools │ │ ├── __init__.py │ │ ├── chat.py │ │ ├── file_io.py │ │ ├── liteyuki.py │ │ ├── manager.py │ │ ├── network.py │ │ └── utils.py │ ├── marshoai_bangumi │ │ ├── __init__.py │ │ └── tools.json │ ├── twisuki_megakits │ │ ├── __init__.py │ │ ├── mk_morse_code.py │ │ └── mk_nya_code.py │ └── twisuki_petcat │ │ ├── __init__.py │ │ ├── pc_cat.py │ │ ├── pc_info.py │ │ ├── pc_shop.py │ │ └── pc_token.py ├── plugins_test │ ├── marshoai_basic │ │ ├── __init__.py │ │ ├── tools.json │ │ └── tools_test.json │ ├── marshoai_memory │ │ ├── __init__.py │ │ ├── command.py │ │ ├── config.py │ │ └── tools.json │ ├── random_number_generator.py │ ├── snowykami_testplugin │ │ └── __init__.py │ └── weather_demo.py ├── tools │ ├── marshoai_bangumi │ │ ├── __init__.py │ │ └── tools.json │ ├── marshoai_basic │ │ ├── __init__.py │ │ ├── tools.json │ │ └── tools_test.json │ ├── marshoai_megakits │ │ ├── __init__.py │ │ ├── mk_common.py │ │ ├── mk_info.py │ │ ├── mk_morse_code.py │ │ ├── mk_nya_code.py │ │ └── tools.json │ ├── marshoai_memory │ │ ├── __init__.py │ │ └── tools.json │ └── marshoai_meogirl │ │ ├── __init__.py │ │ ├── mg_info.py │ │ ├── mg_introduce.py │ │ ├── mg_search.py │ │ └── tools.json ├── util.py ├── util_hunyuan.py └── utils │ └── processor.py ├── package.json ├── pdm.lock ├── pnpm-lock.yaml ├── pyproject.toml ├── resources ├── README.md ├── bg.png ├── catface.svg ├── marsho-640x360.png ├── marsho-bg-1x1.png ├── marsho-bg.png ├── marsho-icon.png ├── marsho-icon.svg ├── marsho-new.png ├── marsho-new.svg ├── marsho-no-paw.png ├── marsho-paw.png ├── marsho.png └── marsho.svg └── tests └── test_example.py /.github/workflows/docs-build.yml: -------------------------------------------------------------------------------- 1 | name: Deploy VitePress site to Liteyuki PaaS 2 | 3 | on: ["push", "pull_request_target"] 4 | 5 | permissions: 6 | contents: write 7 | statuses: write 8 | 9 | concurrency: 10 | group: pages 11 | cancel-in-progress: false 12 | 13 | env: 14 | MELI_SITE: f31e3b17-c4ea-4d9d-bdce-9417d67fd30e 15 | 16 | jobs: 17 | # 构建工作 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 # 如果未启用 lastUpdated,则不需要 25 | - name: Setup Python 26 | uses: actions/setup-python@v2 27 | with: 28 | python-version: "3.11" 29 | 30 | - name: Setup API markdown 31 | run: |- 32 | python -m pip install litedoc 33 | chmod +x build-docs.sh 34 | ./build-docs.sh 35 | 36 | - name: 安装 pnpm 37 | uses: pnpm/action-setup@v2 38 | with: 39 | run_install: true 40 | version: 8 41 | 42 | - name: 设置 Node.js 43 | run: |- 44 | pnpm install 45 | 46 | - name: 构建文档 47 | env: 48 | NODE_OPTIONS: --max_old_space_size=8192 49 | run: |- 50 | pnpm run docs:build 51 | 52 | - name: "发布" 53 | run: | 54 | npx -p "@getmeli/cli" meli upload docs/.vitepress/dist \ 55 | --url "https://dash.apage.dev" \ 56 | --site "$MELI_SITE" \ 57 | --token "$MELI_TOKEN" \ 58 | --release "$GITHUB_SHA" 59 | env: 60 | MELI_TOKEN: ${{ secrets.MELI_TOKEN }} 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: Pre-commit checks 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | pre-commit: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ['3.10', '3.11', '3.12', '3.13'] # 添加你想要测试的 Python 版本 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v3 15 | 16 | - name: Set up Python 17 | uses: actions/setup-python@v3 18 | with: 19 | python-version: ${{ matrix.python-version }} # 使用矩阵中的 Python 版本 20 | 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install pdm 24 | python -m pip install pre-commit 25 | pdm config python.use_venv false 26 | pdm install --no-lock 27 | pre-commit install 28 | 29 | - name: Run pre-commit 30 | run: pre-commit run --all-files 31 | -------------------------------------------------------------------------------- /.github/workflows/pypi-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | workflow_dispatch: 8 | 9 | jobs: 10 | pypi-publish: 11 | name: Upload release to PyPI 12 | runs-on: ubuntu-latest 13 | environment: release 14 | permissions: 15 | id-token: write 16 | steps: 17 | - uses: actions/checkout@master 18 | - name: Set up Python 19 | uses: actions/setup-python@v1 20 | with: 21 | python-version: "3.x" 22 | - name: Install pypa/build 23 | run: >- 24 | python -m 25 | pip install 26 | build 27 | --user 28 | - name: Build a binary wheel and a source tarball 29 | run: >- 30 | python -m 31 | build 32 | --sdist 33 | --wheel 34 | --outdir dist/ 35 | . 36 | - name: Publish distribution to PyPI 37 | uses: pypa/gh-action-pypi-publish@release/v1 -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | fail_fast: true 2 | repos: 3 | - repo: local 4 | hooks: 5 | - id: check-filenames 6 | name: Check Python Filenames 7 | entry: python ./.pre-commit/check_filename.py 8 | language: python 9 | files: \.py$ 10 | 11 | - repo: https://github.com/psf/black 12 | rev: 25.1.0 13 | hooks: 14 | - id: black 15 | args: [--config=./pyproject.toml] 16 | 17 | - repo: https://github.com/PyCQA/isort 18 | rev: 6.0.1 19 | hooks: 20 | - id: isort 21 | args: ["--profile", "black"] 22 | 23 | - repo: https://github.com/pre-commit/mirrors-mypy 24 | rev: v1.15.0 25 | hooks: 26 | - id: mypy 27 | 28 | # - repo: https://github.com/pre-commit/pre-commit-hooks 29 | # rev: v4.0.1 30 | # hooks: 31 | # - id: trailing-whitespace 32 | # - id: end-of-file-fixer 33 | # - id: check-yaml 34 | # - id: check-added-large-files 35 | -------------------------------------------------------------------------------- /.pre-commit/check_filename.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import re 5 | import sys 6 | 7 | 8 | def is_valid_filename(filename: str) -> bool: 9 | """文件名完整相对路径 10 | 11 | Args: 12 | filename (str): _description_ 13 | 14 | Returns: 15 | bool: _description_ 16 | """ 17 | # 检查文件名是否仅包含小写字母,数字,下划线 18 | # 啊?文件名还不能有大写啊…… 19 | if not re.match(r"^[a-z0-9_]+\.py$", filename): 20 | return False 21 | else: 22 | return True 23 | 24 | 25 | def main(): 26 | invalid_files = [] 27 | for root, _, files in os.walk("nonebot_plugin_marshoai"): 28 | for file in files: 29 | if file.endswith(".py"): 30 | if not is_valid_filename(file): 31 | invalid_files.append(os.path.join(root, file)) 32 | 33 | if invalid_files: 34 | print("以下文件名不符合命名规则:") 35 | for file in invalid_files: 36 | print(file) 37 | sys.exit(1) 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | marshoai-docs.pages.liteyuki.icu 2 | -------------------------------------------------------------------------------- /LICENSE-LSO: -------------------------------------------------------------------------------- 1 | LSO license 2 | LiteyukiStudio Opensource license 3 | 4 | --- 5 | 6 | Copyright © 2025 Asankilp & LiteyukiStudio 7 | 8 | --- 9 | 10 | Free to grant the same license-based rights to any person or organization who obtains a copy 11 | 12 | including but not limited to using, copying, modifying, merging, publishing, distributing, sublicenseing, and/or selling copies of the software 13 | 14 | This software and related documentation files (hereinafter referred to as "this software") are licensed in the same way as the base, and are released in the form of open source on the Internet or other media platforms 15 | 16 | Everyone has the right to obtain a copy and obtain permission to distribute and/or use it in the above manner 17 | 18 | In the event of a conflict with other open source or non-open source licenses, 19 | the conflicting portions, unless otherwise stated, shall remain subject to the terms of this open source license. 20 | 21 | During the process of distribution and dissemination, 22 | it is necessary to preserve the existence of this license and distribute and redistribute it in the same manner. 23 | 24 | In the reprocessing of software or software copies for profit purposes 25 | If using this license, individuals and organizations to which the reprocessing software belongs may change, add, or delete non essential license regulations at their own discretion 26 | 27 | The necessary licensing regulations include: 28 | 1. Distribution of Rights and Its Scope of Application 29 | 2. Disclaimer Regulations and Their Interpretation 30 | 31 | However, when obtaining a copy, it is still necessary to pay attention to the following: 32 | 33 | - The above copyright notice and this permission notice shall be included in a copy of the Software 34 | - When using this software and its copies, it is still necessary to maintain the same form as the original 35 | 36 | - When using this software, you still need to disclose the copy of this software under the same license: 37 | - Do not profit from copies of this software in a non-original license without the permission of the original author 38 | 39 | --- 40 | 41 | The software is provided as a "copy as is" without any warranty of any kind, either express or implied: 42 | including but not limited to the warranty of merchantability, non-infringement for specific purposes 43 | 44 | In any case, the author or copyright owner shall not be liable for any claims, damages, or other liabilities arising from the use of the software by the author or copyright owner, whether in contract litigation, infringement litigation, or other litigation. The author and its copyright owner have the right to refuse compensation for any losses caused by the user for personal reasons 45 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Asankilp & LiteyukiStudio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE-MULAN: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 EillesWan 2 | nonebot-plugin-latex & other specified codes is licensed under Mulan PSL v2. 3 | You can use this software according to the terms and conditions of the Mulan PSL v2. 4 | You may obtain a copy of Mulan PSL v2 at: 5 | http://license.coscl.org.cn/MulanPSL2 6 | THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 7 | EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 8 | MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 9 | See the Mulan PSL v2 for more details. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | MarshoLogo 4 |
5 |
6 | 7 |
8 | 9 | # nonebot-plugin-marshoai 10 | 11 | _✨ 使用 OpenAI 标准格式 API 的聊天机器人插件 ✨_ 12 | 13 | [![QQ群](https://img.shields.io/badge/QQ群-1029557452-blue.svg?logo=QQ&style=flat-square)](https://qm.qq.com/q/a13iwP5kAw) 14 | [![NoneBot Registry](https://img.shields.io/endpoint?url=https%3A%2F%2Fnbbdg.lgc2333.top%2Fplugin%2Fnonebot-plugin-marshoai&style=flat-square)](https://registry.nonebot.dev/plugin/nonebot-plugin-marshoai:nonebot_plugin_marshoai) 15 | 16 | Supported Adapters 17 | 18 | 19 | pypi 20 | 21 | python 22 | codestyle 23 |
24 | 25 | starify 26 | 27 | ## 📖 介绍 28 | 29 | 通过调用 OpenAI 标准格式 API(例如 GitHub Models API),来实现聊天的插件。 30 | 插件内置了猫娘小棉(Marsho,マルショ)的人物设定,可以进行可爱的聊天! 31 | _谁不喜欢回复消息快又可爱的猫娘呢?_ 32 | **对 OneBot 以外的适配器与非 GitHub Models API 的支持未完全经过验证。** 33 | [Melobot 实现](https://github.com/LiteyukiStudio/marshoai-melo) 34 | 35 | ## 🐱 设定 36 | 37 | #### 基本信息 38 | 39 | - 名字:小棉(Marsho,マルショ) 40 | - 生日:9 月 6 日 41 | 42 | #### 喜好 43 | 44 | - 🌞 晒太阳晒到融化 45 | - 🤱 撒娇啊~谁不喜欢呢~ 46 | - 🍫 吃零食!肉肉好吃! 47 | - 🐾 玩!我喜欢和朋友们一起玩! 48 | 49 | ## 😼 使用 50 | 51 | 请查看[使用文档](https://marsho.liteyuki.org/start/use.html) 52 | 53 | ## ❤ 鸣谢&版权说明 54 | 55 | > Copyright (c) 2025 Asankilp & LiteyukiStudio 56 | 57 | 本项目使用了以下项目的代码: 58 | 59 | - [nonebot-plugin-latex](https://github.com/EillesWan/nonebot-plugin-latex) 60 | - [nonebot-plugin-deepseek](https://github.com/KomoriDev/nonebot-plugin-deepseek) 61 | 62 | "Marsho" logo 由 [@Asankilp](https://github.com/Asankilp) 绘制,基于 [CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 许可下提供。 63 | "nonebot-plugin-marshoai" 基于 [MIT](./LICENSE-MIT) 许可下提供。 64 | 部分指定的代码基于 [Mulan PSL v2](./LICENSE-MULAN) 许可下提供。 65 | 66 |
67 | 68 | Contributors 69 | 70 |
71 | 72 | 感谢所有的贡献者! 73 | 74 | ## 开发 75 | 76 | - 请阅读[开发规范](./README_DEV.md) 77 | -------------------------------------------------------------------------------- /README_DEV.md: -------------------------------------------------------------------------------- 1 | # 开发指北 2 | 3 | ## 规范化 4 | 5 | - PEP8 6 | - mypy 类型检查 7 | - black 格式化 8 | 9 | ## 开发依赖 10 | 11 | - pre-commit,确保代码质量合格才可以提交 12 | 13 | ```bash 14 | pre-commit install 15 | ``` 16 | 17 | ## 提交及拉取请求 18 | 19 | - 提交后请静待workflows运行结果,若pre-commit通不过请不要PR到主仓库,自行解决掉问题后再次提交 20 | 21 | ## 其他提示 22 | 23 | - 在西文大小写不敏感的文件系统或操作系统中开发时请注意文件名的西文大小写情况,点名批评 APFS 文件系统和视窗操作系统 24 | - 请在提交的文件中尽可能使用相对路径 -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | MarshoLogo 4 |
5 |
6 | 7 |
8 | 9 | # nonebot-plugin-marshoai 10 | 11 | _✨ A chat bot plugin which use OpenAI standard API ✨_ 12 | 13 | [![NoneBot Registry](https://img.shields.io/endpoint?url=https%3A%2F%2Fnbbdg.lgc2333.top%2Fplugin%2Fnonebot-plugin-marshoai)](https://registry.nonebot.dev/plugin/nonebot-plugin-marshoai:nonebot_plugin_marshoai) 14 | 15 | Supported Adapters 16 | 17 | 18 | pypi 19 | 20 | python 21 | 22 |
23 | 24 | ## 📖 Indroduction 25 | 26 | A plugin made by call OpenAI standard API(Such as GitHub Models API) 27 | 28 | Plugin internally installed the catgirl character of Marsho, is able to have a cute conversation! 29 | 30 | *Who don't like a cute catgirl with fast answer speed?* 31 | 32 | **Support for adapters other than OneBot and non-Github Models APIs is not fully verified.** 33 | 34 | [Melobot implementation](https://github.com/LiteyukiStudio/marshoai-melo) 35 | 36 | ## 🐱 Character setting 37 | 38 | #### Basic information 39 | 40 | - Name : Marsho 41 | - Birthday : September 6th 42 | 43 | #### Hobbies 44 | 45 | - 🌞 Melt in sunshine 46 | - 🤱 Coquetry~ who don't like that~ 47 | - 🍫 Eating snacks! Meat is yummy! 48 | - 🐾 Play! I like play with friends! 49 | 50 | ## 😼 Usage 51 | Please read [Documentation](https://marsho.liteyuki.org/start/use.html) 52 | 53 | ## ❤ Thanks&Copyright 54 | This project uses the following code from other projects: 55 | - [nonebot-plugin-latex](https://github.com/EillesWan/nonebot-plugin-latex) 56 | - [nonebot-plugin-deepseek](https://github.com/KomoriDev/nonebot-plugin-deepseek) 57 | 58 | "Marsho" logo contributed by [@Asankilp](https://github.com/Asankilp),licensed under [CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) lisense. 59 | 60 | "nonebot-plugin-marshoai" is licensed under [MIT](./LICENSE-MIT) license. 61 | Some of the code is licensed under [Mulan PSL v2](./LICENSE-MULAN) license. 62 | 63 |
64 | 65 | Contributors 66 | 67 |
68 | 69 | Thanks to all the contributors! 70 | -------------------------------------------------------------------------------- /README_TOOLS.md: -------------------------------------------------------------------------------- 1 | # 🛠️小棉工具 2 | 小棉工具(MarshoTools)是一个简单的模块加载器,允许从插件数据目录下的`tools`目录内加载数个工具包与其中定义的函数,以供 AI 模型调用。 3 | 有关 Function Call 的更多信息,请参阅[OpenAI 官方文档](https://platform.openai.com/docs/guides/function-calling)。 4 | 5 | ## ✍️ 编写工具 6 | ### 📁 目录结构 7 | 插件数据目录下的`tools`目录被称作**工具集**,其中可包含数个**工具包**,工具包与 Python 的**包**结构类似,需要在其中包含`__init__.py`文件与`tools.json`定义文件,这些文件将被用于存放以及定义编写的函数。 8 | 9 | 一个工具包的目录结构类似于: 10 | ``` 11 | tools/ # 工具集目录 12 | └── marshoai-example/ # 工具包目录,以包名命名 13 | └── __init__.py # 工具模块 14 | └── tools.json # 函数定义文件 15 | ``` 16 | 在这个目录树中: 17 | - **工具包目录**是以`marshoai-xxxxx`命名的目录,目录名即为工具包的包名。编写工具时,应尽量采取此命名标准。 18 | - **工具模块**可包含数个可调用的**异步**函数,可以接受入参,也可以不接受。它们的返回值数据类型应为 AI 模型受支持的类型,一般情况下`str`被大部分模型所支持。 19 | - **函数定义文件**是让 AI 模型知道如何调用这些函数的关键。 20 | ### 编写函数 21 | 来编写一个简单的函数吧,例如一个获取天气的函数和一个获取时间的函数: 22 | ###### **\_\_init\_\_.py** 23 | ```python 24 | from datetime import datetime 25 | 26 | async def get_weather(location: str): 27 | return f"{location}的温度是114514℃。" #模拟天气返回信息 28 | 29 | async def get_current_time(): 30 | current_time = datetime.now().strftime("%Y.%m.%d %H:%M:%S") 31 | time_prompt = f"现在的时间是{current_time}。" 32 | return time_prompt 33 | ``` 34 | 在这个示例代码中,定义了`get_weather`和`get_current_time`两个函数,其中一个接受`str`类型的地点入参。要让 AI 模型知道这两个函数的存在以及调用的条件和方法,需要编写**函数定义文件**。 35 | ###### **tools.json** 36 | ```json 37 | [ 38 | { 39 | "type": "function", 40 | "function": { 41 | "name": "marshoai-example__get_weather", # 函数调用名称 42 | "description": "查询指定地点的天气。", # 对该函数的描述,需要清楚描述该函数的用途 43 | "parameters": { # 定义函数的入参 44 | "type": "object", 45 | "properties": { 46 | "location": { # 此处'location'即为__init__.py定义的入参名 47 | "type": "string", # 该入参的数据类型 48 | "description": "城市或县区,比如北京市、杭州市、余杭区等。" # 对该入参的描述,需要清楚描述该入参应传入什么样子的内容 49 | } 50 | } 51 | }, 52 | "required": [ # 定义该函数的必需入参 53 | "location" 54 | ] 55 | } 56 | }, 57 | { 58 | "type": "function", 59 | "function": { 60 | "name": "marshoai-example__get_current_time", 61 | "description": "获取现在的时间。", 62 | "parameters": {} # 该函数不需要入参,故此处为空 63 | } 64 | } 65 | ] 66 | ``` 67 | 在这个文件中定义了两个已经编写好的函数,该定义文件将被输入到 AI 模型中,来让 AI 模型知道这些函数的存在与调用方法。 68 | **函数调用名称**的命名方式比较特别。以获取天气的函数为例,它的函数调用名称`marshoai-example__get_weather`包含三个信息: 69 | - 前面的**marshoai-example**即为该函数所在工具包的**包名**。 70 | - 后面的**get_weather**是这个函数在代码里的名称。 71 | - 中间的两个下划线是用于分割这两个信息的分隔符。 72 | 73 | 使用这种命名方式,是为了兼容更多的 OpenAI 标准格式 API。因此,在给工具包和函数取名时,不要使用带有两个下划线的名称。 74 | ### 测试函数 75 | 在编写完工具后,启动 Bot,Nonebot 的日志应当会输出工具包的加载信息。 76 | 以下是测试示例: 77 | ``` 78 | > marsho 深圳天气怎么样 79 | 深圳的天气显示温度是114514°C,真是不可思议呢!这一定是个误报吧~(≧▽≦) 希望你那里有个好天气哦! 80 | > marsho 分别告诉我下北泽,杭州,苏州的天气 81 | 下北泽、杭州和苏州的天气都显示温度为114514°C呢!这么奇怪的温度,一定是个误报吧~(≧▽≦) 82 | 83 | 如果要查看真实的天气情况,建议查看专业天气预报哦~ 84 | > marsho 现在几点了 85 | 现在的时间是2024年11月23日,21点05分哦~(*^ω^) 你准备做些什么呢? 86 | ``` 87 | -------------------------------------------------------------------------------- /build-docs.sh: -------------------------------------------------------------------------------- 1 | litedoc nonebot_plugin_marshoai -o docs/zh/dev/api -l zh-Hans -cd class -fd func -md func -vd var -f title=%filetitle%,order=100 -bu https://github.com/LiteyukiStudio/nonebot-plugin-marshoai/tree/main/nonebot_plugin_marshoai/ 2 | litedoc nonebot_plugin_marshoai -o docs/en/dev/api -l en -cd class -fd func -md func -vd var -f title=%filetitle%,order=100 -bu https://github.com/LiteyukiStudio/nonebot-plugin-marshoai/tree/main/nonebot_plugin_marshoai/ -------------------------------------------------------------------------------- /docs/.vitepress/config/common.ts: -------------------------------------------------------------------------------- 1 | import { VitePressSidebarOptions } from "vitepress-sidebar/types"; 2 | 3 | export const gitea = { 4 | svg: '', 5 | }; 6 | 7 | export const defaultLang = "zh"; 8 | 9 | const commonSidebarOptions: VitePressSidebarOptions = { 10 | collapsed: true, 11 | convertSameNameSubFileToGroupIndexPage: true, 12 | useTitleFromFrontmatter: true, 13 | useFolderTitleFromIndexFile: false, 14 | useFolderLinkFromIndexFile: true, 15 | useTitleFromFileHeading: true, 16 | rootGroupText: "MARSHOAI", 17 | includeFolderIndexFile: true, 18 | sortMenusByFrontmatterOrder: true, 19 | }; 20 | 21 | export function generateSidebarConfig(): VitePressSidebarOptions[] { 22 | let sections = ["dev", "start"]; 23 | let languages = ["zh", "en"]; 24 | let ret: VitePressSidebarOptions[] = []; 25 | for (let language of languages) { 26 | for (let section of sections) { 27 | if (language === defaultLang) { 28 | ret.push({ 29 | basePath: `/${section}/`, 30 | scanStartPath: `docs/${language}/${section}`, 31 | resolvePath: `/${section}/`, 32 | ...commonSidebarOptions, 33 | }); 34 | } else { 35 | ret.push({ 36 | basePath: `/${language}/${section}/`, 37 | scanStartPath: `docs/${language}/${section}`, 38 | resolvePath: `/${language}/${section}/`, 39 | ...commonSidebarOptions, 40 | }); 41 | } 42 | } 43 | } 44 | return ret; 45 | } 46 | 47 | export const ThemeConfig = { 48 | getEditLink: ( 49 | editPageText: string 50 | ): { pattern: (params: { filePath: string }) => string; text: string } => { 51 | return { 52 | pattern: ({ filePath }: { filePath: string }): string => { 53 | if (!filePath) { 54 | throw new Error("filePath is undefined"); 55 | } 56 | const regex = /^(dev\/api|[^\/]+\/dev\/api)/; 57 | if (regex.test(filePath)) { 58 | filePath = filePath 59 | .replace(regex, "") 60 | .replace("index.md", "__init__.py") 61 | .replace(".md", ".py"); 62 | const fileName = filePath.split("/").pop(); 63 | const parentFolder = filePath.split("/").slice(-2, -1)[0]; 64 | if ( 65 | fileName && 66 | parentFolder && 67 | fileName.split(".")[0] === parentFolder 68 | ) { 69 | filePath = 70 | filePath.split("/").slice(0, -1).join("/") + "/__init__.py"; 71 | } 72 | return `https://github.com/LiteyukiStudio/nonebot-plugin-marshoai/tree/main/nonebot_plugin_marshoai/${filePath}`; 73 | } else { 74 | return `https://github.com/LiteyukiStudio/nonebot-plugin-marshoai/tree/main/docs/${filePath}`; 75 | } 76 | }, 77 | text: editPageText, 78 | }; 79 | }, 80 | 81 | getOutLine: (label: string): { label: string; level: [number, number] } => { 82 | return { 83 | label: label, 84 | level: [2, 6], 85 | }; 86 | }, 87 | }; 88 | -------------------------------------------------------------------------------- /docs/.vitepress/config/en.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vitepress' 2 | import { ThemeConfig } from './common' 3 | 4 | export const en = defineConfig({ 5 | lang: "en-US", 6 | title: "Marsho AI", 7 | description: "Kawaii, Intelligent and Easy to Extend", 8 | themeConfig: { 9 | docFooter: { 10 | prev: 'Prev', 11 | next: 'Next' 12 | }, 13 | nav: [ 14 | {text: 'Home', link: '/en'}, 15 | {text: 'Usage', link: '/en/start/install'}, 16 | {text: 'Develop', link: '/en/dev/extension'}, 17 | ], 18 | editLink: ThemeConfig.getEditLink('Edit this page'), 19 | langMenuLabel: 'Language', 20 | returnToTopLabel: 'To top', 21 | sidebarMenuLabel: 'Option', 22 | darkModeSwitchLabel: 'Theme', 23 | lightModeSwitchTitle: 'Light', 24 | darkModeSwitchTitle: 'Dark', 25 | footer: { 26 | message: "The document is being improved. Suggestions are welcome.
Webpage is deployed at Liteyuki Meli and accelerated by Liteyukiflare.", 27 | copyright: '© 2024 Liteyuki Studio', 28 | } 29 | }, 30 | 31 | }) -------------------------------------------------------------------------------- /docs/.vitepress/config/index.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | import { zh } from './zh' 3 | import { en } from './en' 4 | import { ja } from './ja' 5 | import { defaultLang, generateSidebarConfig, gitea } from './common' 6 | import { generateSidebar } from 'vitepress-sidebar' 7 | 8 | // https://vitepress.dev/reference/site-config 9 | export default defineConfig({ 10 | head: [ 11 | ["script", { src: "https://cdn.liteyuki.icu/js/liteyuki_footer.js" }], 12 | ['link', { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }], 13 | ], 14 | rewrites: { 15 | [`${defaultLang}/:rest*`]: ":rest*", 16 | }, 17 | cleanUrls: false, 18 | themeConfig: { 19 | // https://vitepress.dev/reference/default-theme-config 20 | logo: { 21 | light: '/marsho-full.svg', 22 | dark: '/marsho-full.svg', 23 | alt: 'Marsho Logo' 24 | }, 25 | 26 | sidebar: generateSidebar( 27 | [...generateSidebarConfig(),] 28 | ), 29 | 30 | socialLinks: [ 31 | { icon: 'github', link: 'https://github.com/LiteyukiStudio/nonebot-plugin-marshoai' }, 32 | { icon: gitea, link: 'https://git.liteyuki.icu/LiteyukiStudio/nonebot-plugin-marshoai' } 33 | ] 34 | }, 35 | locales: { 36 | root: { label: "简体中文", ...zh }, 37 | en: { label: "English", ...en }, 38 | ja: { label: "日本語", ...ja }, 39 | }, 40 | lastUpdated: true, 41 | }) 42 | -------------------------------------------------------------------------------- /docs/.vitepress/config/ja.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vitepress' 2 | import { ThemeConfig } from './common' 3 | 4 | export const ja = defineConfig({ 5 | lang: "ja-JP", 6 | title: "Marsho AI", 7 | description: "かわいくて、賢くて、拡張しやすい", 8 | themeConfig: { 9 | docFooter: { 10 | prev: '前へ', 11 | next: '次へ' 12 | }, 13 | nav: [ 14 | {text: 'ホーム', link: '/ja'}, 15 | {text: '使用方法', link: '/ja/start/install'}, 16 | {text: '開発', link: '/ja/dev/extension'}, 17 | ], 18 | editLink: ThemeConfig.getEditLink('このページを編集'), 19 | langMenuLabel: '言語', 20 | returnToTopLabel: 'トップへ戻る', 21 | sidebarMenuLabel: 'オプション', 22 | darkModeSwitchLabel: 'テーマ', 23 | lightModeSwitchTitle: 'ライト', 24 | darkModeSwitchTitle: 'ダーク', 25 | footer: { 26 | message: "ドキュメントは改善中です。ご意見をお待ちしております。
ウェブサイトは Liteyuki Meli によってデプロイされ、Liteyukiflare によって加速されています。", 27 | copyright: '© 2024 Liteyuki Studio', 28 | } 29 | }, 30 | }) -------------------------------------------------------------------------------- /docs/.vitepress/config/zh.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vitepress' 2 | import { ThemeConfig } from './common' 3 | 4 | export const zh = defineConfig({ 5 | lang: "zh-Hans", 6 | title: "小棉智能", 7 | description: "可爱,智能且易扩展", 8 | themeConfig: { 9 | docFooter: { 10 | prev: '上一页', 11 | next: '下一页' 12 | }, 13 | nav: [ 14 | {text: '家', link: '/'}, 15 | {text: '使用', link: '/start/use'}, 16 | {text: '开发', link: '/dev/extension'}, 17 | ], 18 | editLink: ThemeConfig.getEditLink('编辑此页面'), 19 | langMenuLabel: '语言', 20 | returnToTopLabel: '返回顶部', 21 | sidebarMenuLabel: '菜单', 22 | darkModeSwitchLabel: '主题', 23 | lightModeSwitchTitle: '轻色模式', 24 | darkModeSwitchTitle: '深色模式', 25 | footer: { 26 | message: "文档完善中,欢迎提出建议或帮助我们完善。
网站部署在 Liteyuki MeliLiteyukiflare 提供加速服务。", 27 | copyright: '© 2024 Liteyuki Studio', 28 | } 29 | }, 30 | }) -------------------------------------------------------------------------------- /docs/.vitepress/theme/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 NapCat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/.vitepress/theme/Layout.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 19 | 20 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import DefaultTheme from 'vitepress/theme' 3 | import './style.css' 4 | import Layout from './Layout.vue' 5 | 6 | export default { 7 | extends: DefaultTheme, 8 | // 使用注入插槽的包装组件覆盖 Layout 9 | Layout: Layout 10 | } -------------------------------------------------------------------------------- /docs/components/ContributorsBar.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | -------------------------------------------------------------------------------- /docs/en/dev/api/azure_onebot.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: azure_onebot 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.azure_onebot` 5 | 6 | -------------------------------------------------------------------------------- /docs/en/dev/api/constants.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: constants 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.constants` 5 | 6 | -------------------------------------------------------------------------------- /docs/en/dev/api/hunyuan.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: hunyuan 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.hunyuan` 5 | 6 | --- 7 | `@genimage_cmd.handle()` 8 | ### ***async func*** `genimage(event: Event, prompt = None)` 9 | 10 | 11 |
12 | Source code or View on GitHub 13 | 14 | ```python 15 | @genimage_cmd.handle() 16 | async def genimage(event: Event, prompt=None): 17 | if not prompt: 18 | await genimage_cmd.finish('无提示词') 19 | try: 20 | result = generate_image(prompt) 21 | url = json.loads(result)['ResultImage'] 22 | await UniMessage.image(url=url).send() 23 | except Exception as e: 24 | traceback.print_exc() 25 | ``` 26 |
27 | 28 | -------------------------------------------------------------------------------- /docs/en/dev/api/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **Module** `nonebot_plugin_marshoai` 6 | 7 | -------------------------------------------------------------------------------- /docs/en/dev/api/metadata.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: metadata 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.metadata` 5 | 6 | -------------------------------------------------------------------------------- /docs/en/dev/api/plugin/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **Module** `nonebot_plugin_marshoai.plugin` 6 | 7 | 该功能目前正在开发中,暂时不可用,受影响的文件夹 `plugin`, `plugins` 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/en/dev/api/plugin/models.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: models 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.plugin.models` 5 | 6 | ### ***class*** `PluginMetadata(BaseModel)` 7 | #### ***attr*** `name: str = NO_DEFAULT` 8 | 9 | #### ***attr*** `description: str = ''` 10 | 11 | #### ***attr*** `usage: str = ''` 12 | 13 | #### ***attr*** `author: str = ''` 14 | 15 | #### ***attr*** `homepage: str = ''` 16 | 17 | #### ***attr*** `extra: dict[str, Any] = {}` 18 | 19 | ### ***class*** `Plugin(BaseModel)` 20 | --- 21 | #### ***func*** `hash self => int` 22 | 23 | 24 |
25 | Source code or View on GitHub 26 | 27 | ```python 28 | def __hash__(self) -> int: 29 | return hash(self.name) 30 | ``` 31 |
32 | 33 | --- 34 | #### ***func*** `self == other: Any => bool` 35 | 36 | 37 |
38 | Source code or View on GitHub 39 | 40 | ```python 41 | def __eq__(self, other: Any) -> bool: 42 | return self.name == other.name 43 | ``` 44 |
45 | 46 | #### ***attr*** `name: str = NO_DEFAULT` 47 | 48 | #### ***attr*** `module: ModuleType = NO_DEFAULT` 49 | 50 | #### ***attr*** `module_name: str = NO_DEFAULT` 51 | 52 | #### ***attr*** `metadata: PluginMetadata | None = None` 53 | 54 | ### ***class*** `FunctionCallArgument(BaseModel)` 55 | --- 56 | #### ***func*** `data(self) -> dict[str, Any]` 57 | 58 | 59 |
60 | Source code or View on GitHub 61 | 62 | ```python 63 | def data(self) -> dict[str, Any]: 64 | return {'type': self.type_, 'description': self.description} 65 | ``` 66 |
67 | 68 | #### ***attr*** `type_: str = NO_DEFAULT` 69 | 70 | #### ***attr*** `description: str = NO_DEFAULT` 71 | 72 | #### ***attr*** `default: Any = None` 73 | 74 | ### ***class*** `FunctionCall(BaseModel)` 75 | --- 76 | #### ***func*** `hash self => int` 77 | 78 | 79 |
80 | Source code or View on GitHub 81 | 82 | ```python 83 | def __hash__(self) -> int: 84 | return hash(self.name) 85 | ``` 86 |
87 | 88 | --- 89 | #### ***func*** `data(self) -> dict[str, Any]` 90 | 91 | **Description**: 生成函数描述信息 92 | 93 | 94 | **Return**: dict[str, Any]: 函数描述信息 字典 95 | 96 | 97 |
98 | Source code or View on GitHub 99 | 100 | ```python 101 | def data(self) -> dict[str, Any]: 102 | return {'type': 'function', 'function': {'name': self.name, 'description': self.description, 'parameters': {'type': 'object', 'properties': {k: v.data() for k, v in self.arguments.items()}}, 'required': [k for k, v in self.arguments.items() if v.default is None]}} 103 | ``` 104 |
105 | 106 | #### ***attr*** `name: str = NO_DEFAULT` 107 | 108 | #### ***attr*** `description: str = NO_DEFAULT` 109 | 110 | #### ***attr*** `arguments: dict[str, FunctionCallArgument] = NO_DEFAULT` 111 | 112 | #### ***attr*** `function: ASYNC_FUNCTION_CALL_FUNC = NO_DEFAULT` 113 | 114 | -------------------------------------------------------------------------------- /docs/en/dev/api/plugin/register.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: register 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.plugin.register` 5 | 6 | 此模块用于获取function call中函数定义信息以及注册函数 7 | 8 | 9 | --- 10 | ### ***func*** `async_wrapper(func: SYNC_FUNCTION_CALL_FUNC) -> ASYNC_FUNCTION_CALL_FUNC` 11 | 12 | **Description**: 将同步函数包装为异步函数,但是不会真正异步执行,仅用于统一调用及函数签名 13 | 14 | 15 | **Arguments**: 16 | > - func: 同步函数 17 | 18 | **Return**: ASYNC_FUNCTION_CALL: 异步函数 19 | 20 | 21 |
22 | Source code or View on GitHub 23 | 24 | ```python 25 | def async_wrapper(func: SYNC_FUNCTION_CALL_FUNC) -> ASYNC_FUNCTION_CALL_FUNC: 26 | 27 | async def wrapper(*args, **kwargs) -> str: 28 | return func(*args, **kwargs) 29 | return wrapper 30 | ``` 31 |
32 | 33 | --- 34 | ### ***func*** `function_call(*funcs: FUNCTION_CALL_FUNC) -> None` 35 | 36 | 37 | **Arguments**: 38 | > - func: 函数对象,要有完整的 Google Style Docstring 39 | 40 | **Return**: str: 函数定义信息 41 | 42 | 43 |
44 | Source code or View on GitHub 45 | 46 | ```python 47 | def function_call(*funcs: FUNCTION_CALL_FUNC) -> None: 48 | for func in funcs: 49 | function_call = get_function_info(func) 50 | ``` 51 |
52 | 53 | --- 54 | ### ***func*** `get_function_info(func: FUNCTION_CALL_FUNC)` 55 | 56 | **Description**: 获取函数信息 57 | 58 | 59 | **Arguments**: 60 | > - func: 函数对象 61 | 62 | **Return**: FunctionCall: 函数信息对象模型 63 | 64 | 65 |
66 | Source code or View on GitHub 67 | 68 | ```python 69 | def get_function_info(func: FUNCTION_CALL_FUNC): 70 | name = func.__name__ 71 | description = func.__doc__ 72 | logger.info(f'注册函数: {name} {description}') 73 | ``` 74 |
75 | 76 | -------------------------------------------------------------------------------- /docs/en/dev/api/plugin/typing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: typing 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.plugin.typing` 5 | 6 | -------------------------------------------------------------------------------- /docs/en/dev/api/plugin/utils.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: utils 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.plugin.utils` 5 | 6 | --- 7 | ### ***func*** `path_to_module_name(path: Path) -> str` 8 | 9 | **Description**: 转换路径为模块名 10 | 11 | **Arguments**: 12 | > - path: 路径a/b/c/d -> a.b.c.d 13 | 14 | **Return**: str: 模块名 15 | 16 | 17 |
18 | Source code or View on GitHub 19 | 20 | ```python 21 | def path_to_module_name(path: Path) -> str: 22 | rel_path = path.resolve().relative_to(Path.cwd().resolve()) 23 | if rel_path.stem == '__init__': 24 | return '.'.join(rel_path.parts[:-1]) 25 | else: 26 | return '.'.join(rel_path.parts[:-1] + (rel_path.stem,)) 27 | ``` 28 |
29 | 30 | --- 31 | ### ***func*** `is_coroutine_callable(call: Callable[..., Any]) -> bool` 32 | 33 | **Description**: 判断是否为async def 函数 34 | 35 | **Arguments**: 36 | > - call: 可调用对象 37 | 38 | **Return**: bool: 是否为协程可调用对象 39 | 40 | 41 |
42 | Source code or View on GitHub 43 | 44 | ```python 45 | def is_coroutine_callable(call: Callable[..., Any]) -> bool: 46 | if inspect.isroutine(call): 47 | return inspect.iscoroutinefunction(call) 48 | if inspect.isclass(call): 49 | return False 50 | func_ = getattr(call, '__call__', None) 51 | return inspect.iscoroutinefunction(func_) 52 | ``` 53 |
54 | 55 | -------------------------------------------------------------------------------- /docs/en/dev/api/plugins/marshoai_bangumi/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **Module** `nonebot_plugin_marshoai.plugins.marshoai_bangumi` 6 | 7 | --- 8 | ### ***async func*** `fetch_calendar()` 9 | 10 | 11 |
12 | Source code or View on GitHub 13 | 14 | ```python 15 | async def fetch_calendar(): 16 | url = 'https://api.bgm.tv/calendar' 17 | headers = {'User-Agent': 'LiteyukiStudio/nonebot-plugin-marshoai (https://github.com/LiteyukiStudio/nonebot-plugin-marshoai)'} 18 | async with httpx.AsyncClient() as client: 19 | response = await client.get(url, headers=headers) 20 | return response.json() 21 | ``` 22 |
23 | 24 | --- 25 | `@function_call` 26 | ### ***async func*** `get_bangumi_news() -> str` 27 | 28 | **Description**: 获取今天的新番(动漫)列表,在调用之前,你需要知道今天星期几。 29 | 30 | 31 | **Return**: _type_: _description_ 32 | 33 | 34 |
35 | Source code or View on GitHub 36 | 37 | ```python 38 | @function_call 39 | async def get_bangumi_news() -> str: 40 | result = await fetch_calendar() 41 | info = '' 42 | try: 43 | for i in result: 44 | weekday = i['weekday']['cn'] 45 | info += f'{weekday}:' 46 | items = i['items'] 47 | for item in items: 48 | name = item['name_cn'] 49 | info += f'《{name}》' 50 | info += '\n' 51 | return info 52 | except Exception as e: 53 | traceback.print_exc() 54 | return '' 55 | ``` 56 |
57 | 58 | --- 59 | `@function_call` 60 | ### ***func*** `test_sync() -> str` 61 | 62 | 63 |
64 | Source code or View on GitHub 65 | 66 | ```python 67 | @function_call 68 | def test_sync() -> str: 69 | return 'sync' 70 | ``` 71 |
72 | 73 | -------------------------------------------------------------------------------- /docs/en/dev/api/plugins/marshoai_basic/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **Module** `nonebot_plugin_marshoai.plugins.marshoai_basic` 6 | 7 | --- 8 | ### ***async func*** `get_weather(location: str)` 9 | 10 | 11 |
12 | Source code or View on GitHub 13 | 14 | ```python 15 | async def get_weather(location: str): 16 | return f'{location}的温度是114514℃。' 17 | ``` 18 |
19 | 20 | --- 21 | ### ***async func*** `get_current_env()` 22 | 23 | 24 |
25 | Source code or View on GitHub 26 | 27 | ```python 28 | async def get_current_env(): 29 | ver = os.popen('uname -a').read() 30 | return str(ver) 31 | ``` 32 |
33 | 34 | --- 35 | ### ***async func*** `get_current_time()` 36 | 37 | 38 |
39 | Source code or View on GitHub 40 | 41 | ```python 42 | async def get_current_time(): 43 | current_time = DateTime.now().strftime('%Y.%m.%d %H:%M:%S') 44 | current_weekday = DateTime.now().weekday() 45 | weekdays = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'] 46 | current_weekday_name = weekdays[current_weekday] 47 | current_lunar_date = DateTime.now().to_lunar().date_hanzify()[5:] 48 | time_prompt = f'现在的时间是{current_time},{current_weekday_name},农历{current_lunar_date}。' 49 | return time_prompt 50 | ``` 51 |
52 | 53 | -------------------------------------------------------------------------------- /docs/en/dev/api/tools/marshoai_bangumi/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **Module** `nonebot_plugin_marshoai.tools.marshoai_bangumi` 6 | 7 | --- 8 | ### ***async func*** `fetch_calendar()` 9 | 10 | 11 |
12 | Source code or View on GitHub 13 | 14 | ```python 15 | async def fetch_calendar(): 16 | url = 'https://api.bgm.tv/calendar' 17 | headers = {'User-Agent': 'LiteyukiStudio/nonebot-plugin-marshoai (https://github.com/LiteyukiStudio/nonebot-plugin-marshoai)'} 18 | async with httpx.AsyncClient() as client: 19 | response = await client.get(url, headers=headers) 20 | return response.json() 21 | ``` 22 |
23 | 24 | --- 25 | ### ***async func*** `get_bangumi_news()` 26 | 27 | 28 |
29 | Source code or View on GitHub 30 | 31 | ```python 32 | async def get_bangumi_news(): 33 | result = await fetch_calendar() 34 | info = '' 35 | try: 36 | for i in result: 37 | weekday = i['weekday']['cn'] 38 | info += f'{weekday}:' 39 | items = i['items'] 40 | for item in items: 41 | name = item['name_cn'] 42 | info += f'《{name}》' 43 | info += '\n' 44 | return info 45 | except Exception as e: 46 | traceback.print_exc() 47 | return '' 48 | ``` 49 |
50 | 51 | -------------------------------------------------------------------------------- /docs/en/dev/api/tools/marshoai_basic/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **Module** `nonebot_plugin_marshoai.tools.marshoai_basic` 6 | 7 | --- 8 | ### ***async func*** `get_weather(location: str)` 9 | 10 | 11 |
12 | Source code or View on GitHub 13 | 14 | ```python 15 | async def get_weather(location: str): 16 | return f'{location}的温度是114514℃。' 17 | ``` 18 |
19 | 20 | --- 21 | ### ***async func*** `get_current_env()` 22 | 23 | 24 |
25 | Source code or View on GitHub 26 | 27 | ```python 28 | async def get_current_env(): 29 | ver = os.popen('uname -a').read() 30 | return str(ver) 31 | ``` 32 |
33 | 34 | --- 35 | ### ***async func*** `get_current_time()` 36 | 37 | 38 |
39 | Source code or View on GitHub 40 | 41 | ```python 42 | async def get_current_time(): 43 | current_time = DateTime.now().strftime('%Y.%m.%d %H:%M:%S') 44 | current_weekday = DateTime.now().weekday() 45 | weekdays = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'] 46 | current_weekday_name = weekdays[current_weekday] 47 | current_lunar_date = DateTime.now().to_lunar().date_hanzify()[5:] 48 | time_prompt = f'现在的时间是{current_time},{current_weekday_name},农历{current_lunar_date}。' 49 | return time_prompt 50 | ``` 51 |
52 | 53 | -------------------------------------------------------------------------------- /docs/en/dev/api/tools/marshoai_megakits/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **Module** `nonebot_plugin_marshoai.tools.marshoai_megakits` 6 | 7 | --- 8 | ### ***async func*** `twisuki()` 9 | 10 | 11 |
12 | Source code or View on GitHub 13 | 14 | ```python 15 | async def twisuki(): 16 | return str(await mk_info.twisuki()) 17 | ``` 18 |
19 | 20 | --- 21 | ### ***async func*** `megakits()` 22 | 23 | 24 |
25 | Source code or View on GitHub 26 | 27 | ```python 28 | async def megakits(): 29 | return str(await mk_info.megakits()) 30 | ``` 31 |
32 | 33 | --- 34 | ### ***async func*** `random_turntable(upper: int, lower: int = 0)` 35 | 36 | 37 |
38 | Source code or View on GitHub 39 | 40 | ```python 41 | async def random_turntable(upper: int, lower: int=0): 42 | return str(await mk_common.random_turntable(upper, lower)) 43 | ``` 44 |
45 | 46 | --- 47 | ### ***async func*** `number_calc(a: str, b: str, op: str)` 48 | 49 | 50 |
51 | Source code or View on GitHub 52 | 53 | ```python 54 | async def number_calc(a: str, b: str, op: str): 55 | return str(await mk_common.number_calc(a, b, op)) 56 | ``` 57 |
58 | 59 | --- 60 | ### ***async func*** `morse_encrypt(msg: str)` 61 | 62 | 63 |
64 | Source code or View on GitHub 65 | 66 | ```python 67 | async def morse_encrypt(msg: str): 68 | return str(await mk_morse_code.morse_encrypt(msg)) 69 | ``` 70 |
71 | 72 | --- 73 | ### ***async func*** `morse_decrypt(msg: str)` 74 | 75 | 76 |
77 | Source code or View on GitHub 78 | 79 | ```python 80 | async def morse_decrypt(msg: str): 81 | return str(await mk_morse_code.morse_decrypt(msg)) 82 | ``` 83 |
84 | 85 | --- 86 | ### ***async func*** `nya_encode(msg: str)` 87 | 88 | 89 |
90 | Source code or View on GitHub 91 | 92 | ```python 93 | async def nya_encode(msg: str): 94 | return str(await mk_nya_code.nya_encode(msg)) 95 | ``` 96 |
97 | 98 | --- 99 | ### ***async func*** `nya_decode(msg: str)` 100 | 101 | 102 |
103 | Source code or View on GitHub 104 | 105 | ```python 106 | async def nya_decode(msg: str): 107 | return str(await mk_nya_code.nya_decode(msg)) 108 | ``` 109 |
110 | 111 | -------------------------------------------------------------------------------- /docs/en/dev/api/tools/marshoai_megakits/mk_common.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mk_common 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.tools.marshoai_megakits.mk_common` 5 | 6 | --- 7 | ### ***async func*** `random_turntable(upper: int, lower: int)` 8 | 9 | **Description**: Random Turntable 10 | 11 | 12 | **Arguments**: 13 | > - upper (int): _description_ 14 | > - lower (int): _description_ 15 | 16 | **Return**: _type_: _description_ 17 | 18 | 19 |
20 | Source code or View on GitHub 21 | 22 | ```python 23 | async def random_turntable(upper: int, lower: int): 24 | return random.randint(lower, upper) 25 | ``` 26 |
27 | 28 | --- 29 | ### ***async func*** `number_calc(a: str, b: str, op: str) -> str` 30 | 31 | **Description**: Number Calc 32 | 33 | 34 | **Arguments**: 35 | > - a (str): _description_ 36 | > - b (str): _description_ 37 | > - op (str): _description_ 38 | 39 | **Return**: str: _description_ 40 | 41 | 42 |
43 | Source code or View on GitHub 44 | 45 | ```python 46 | async def number_calc(a: str, b: str, op: str) -> str: 47 | a, b = (float(a), float(b)) 48 | match op: 49 | case '+': 50 | return str(a + b) 51 | case '-': 52 | return str(a - b) 53 | case '*': 54 | return str(a * b) 55 | case '/': 56 | return str(a / b) 57 | case '**': 58 | return str(a ** b) 59 | case '%': 60 | return str(a % b) 61 | case _: 62 | return '未知运算符' 63 | ``` 64 |
65 | 66 | -------------------------------------------------------------------------------- /docs/en/dev/api/tools/marshoai_megakits/mk_info.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mk_info 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.tools.marshoai_megakits.mk_info` 5 | 6 | --- 7 | ### ***async func*** `twisuki()` 8 | 9 | 10 |
11 | Source code or View on GitHub 12 | 13 | ```python 14 | async def twisuki(): 15 | return 'Twiuski(苏阳)是megakits插件作者, Github : "https://github.com/Twisuki"' 16 | ``` 17 |
18 | 19 | --- 20 | ### ***async func*** `megakits()` 21 | 22 | 23 |
24 | Source code or View on GitHub 25 | 26 | ```python 27 | async def megakits(): 28 | return 'MegaKits插件是一个功能混杂的MarshoAI插件, 由Twisuki(Github : "https://github.com/Twisuki")开发, 插件仓库 : "https://github.com/LiteyukiStudio/marsho-toolsets/tree/main/Twisuki/marshoai-megakits"' 29 | ``` 30 |
31 | 32 | -------------------------------------------------------------------------------- /docs/en/dev/api/tools/marshoai_megakits/mk_morse_code.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mk_morse_code 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.tools.marshoai_megakits.mk_morse_code` 5 | 6 | --- 7 | ### ***async func*** `morse_encrypt(msg: str)` 8 | 9 | 10 |
11 | Source code or View on GitHub 12 | 13 | ```python 14 | async def morse_encrypt(msg: str): 15 | result = '' 16 | msg = msg.upper() 17 | for char in msg: 18 | if char in MorseEncode: 19 | result += MorseEncode[char] 20 | else: 21 | result += '..--..' 22 | result += ' ' 23 | return result 24 | ``` 25 |
26 | 27 | --- 28 | ### ***async func*** `morse_decrypt(msg: str)` 29 | 30 | 31 |
32 | Source code or View on GitHub 33 | 34 | ```python 35 | async def morse_decrypt(msg: str): 36 | result = '' 37 | msg_arr = msg.split() 38 | for char in msg_arr: 39 | if char in MorseDecode: 40 | result += MorseDecode[char] 41 | else: 42 | result += '?' 43 | return result 44 | ``` 45 |
46 | 47 | -------------------------------------------------------------------------------- /docs/en/dev/api/tools/marshoai_megakits/mk_nya_code.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mk_nya_code 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.tools.marshoai_megakits.mk_nya_code` 5 | 6 | --- 7 | ### ***async func*** `nya_encode(msg: str)` 8 | 9 | 10 |
11 | Source code or View on GitHub 12 | 13 | ```python 14 | async def nya_encode(msg: str): 15 | msg_b64str = base64.b64encode(msg.encode()).decode().replace('=', '') 16 | msg_nyastr = ''.join((NyaCodeEncode[base64_char] for base64_char in msg_b64str)) 17 | result = '' 18 | for char in msg_nyastr: 19 | if char == '呜' and random.random() < 0.5: 20 | result += '!' 21 | if random.random() < 0.25: 22 | result += random.choice(NyaCodeSpecialCharset) + char 23 | else: 24 | result += char 25 | return result 26 | ``` 27 |
28 | 29 | --- 30 | ### ***async func*** `nya_decode(msg: str)` 31 | 32 | 33 |
34 | Source code or View on GitHub 35 | 36 | ```python 37 | async def nya_decode(msg: str): 38 | msg = msg.replace('唔', '').replace('!', '').replace('.', '') 39 | msg_nyastr = [] 40 | i = 0 41 | if len(msg) % 3 != 0: 42 | return '这句话不是正确的猫语' 43 | while i < len(msg): 44 | nyachar = msg[i:i + 3] 45 | try: 46 | if all((char in NyaCodeCharset for char in nyachar)): 47 | msg_nyastr.append(nyachar) 48 | i += 3 49 | except Exception: 50 | return '这句话不是正确的猫语' 51 | msg_b64str = ''.join((NyaCodeDecode[nya_char] for nya_char in msg_nyastr)) 52 | msg_b64str += '=' * (4 - len(msg_b64str) % 4) 53 | try: 54 | result = base64.b64decode(msg_b64str.encode()).decode() 55 | except Exception: 56 | return '翻译失败' 57 | return result 58 | ``` 59 |
60 | 61 | -------------------------------------------------------------------------------- /docs/en/dev/api/tools/marshoai_meogirl/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **Module** `nonebot_plugin_marshoai.tools.marshoai_meogirl` 6 | 7 | --- 8 | ### ***async func*** `meogirl()` 9 | 10 | 11 |
12 | Source code or View on GitHub 13 | 14 | ```python 15 | async def meogirl(): 16 | return mg_info.meogirl() 17 | ``` 18 |
19 | 20 | --- 21 | ### ***async func*** `search(msg: str, num: int = 3)` 22 | 23 | 24 |
25 | Source code or View on GitHub 26 | 27 | ```python 28 | async def search(msg: str, num: int=3): 29 | return str(await mg_search.search(msg, num)) 30 | ``` 31 |
32 | 33 | --- 34 | ### ***async func*** `introduce(msg: str)` 35 | 36 | 37 |
38 | Source code or View on GitHub 39 | 40 | ```python 41 | async def introduce(msg: str): 42 | return str(await mg_introduce.introduce(msg)) 43 | ``` 44 |
45 | 46 | -------------------------------------------------------------------------------- /docs/en/dev/api/tools/marshoai_meogirl/mg_info.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mg_info 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.tools.marshoai_meogirl.mg_info` 5 | 6 | --- 7 | ### ***func*** `meogirl()` 8 | 9 | 10 |
11 | Source code or View on GitHub 12 | 13 | ```python 14 | def meogirl(): 15 | return 'Meogirl指的是"萌娘百科"(https://zh.moegirl.org.cn/ , 简称"萌百"), 是一个"万物皆可萌的百科全书!"; 同时, MarshoTools也配有"Meogirl"插件, 可调用萌百的api' 16 | ``` 17 |
18 | 19 | -------------------------------------------------------------------------------- /docs/en/dev/api/tools/marshoai_meogirl/mg_introduce.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mg_introduce 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.tools.marshoai_meogirl.mg_introduce` 5 | 6 | --- 7 | ### ***async func*** `get_async_data(url)` 8 | 9 | 10 |
11 | Source code or View on GitHub 12 | 13 | ```python 14 | async def get_async_data(url): 15 | async with httpx.AsyncClient(timeout=None) as client: 16 | return await client.get(url, headers=headers) 17 | ``` 18 |
19 | 20 | --- 21 | ### ***async func*** `introduce(msg: str)` 22 | 23 | 24 |
25 | Source code or View on GitHub 26 | 27 | ```python 28 | async def introduce(msg: str): 29 | logger.info(f'介绍 : "{msg}" ...') 30 | result = '' 31 | url = 'https://mzh.moegirl.org.cn/' + urllib.parse.quote_plus(msg) 32 | response = await get_async_data(url) 33 | logger.success(f'连接"{url}"完成, 状态码 : {response.status_code}') 34 | soup = BeautifulSoup(response.text, 'html.parser') 35 | if response.status_code == 200: 36 | '\n 萌娘百科页面结构\n div#mw-content-text\n └── div#404search # 空白页面出现\n └── div.mw-parser-output # 正常页面\n └── div, p, table ... # 大量的解释项\n ' 37 | result += msg + '\n' 38 | img = soup.find('img', class_='infobox-image') 39 | if img: 40 | result += f'![ {msg} ]( {img['src']} ) \n' 41 | div = soup.find('div', class_='mw-parser-output') 42 | if div: 43 | p_tags = div.find_all('p') 44 | num = 0 45 | for p_tag in p_tags: 46 | p = str(p_tag) 47 | p = re.sub('|', '', p, flags=re.DOTALL) 48 | p = re.sub('<.*?>', '', p, flags=re.DOTALL) 49 | p = re.sub('\\[.*?]', '', p, flags=re.DOTALL) 50 | if p != '': 51 | result += str(p) 52 | num += 1 53 | if num >= 20: 54 | break 55 | return result 56 | elif response.status_code == 404: 57 | logger.info(f'未找到"{msg}", 进行搜索') 58 | from . import mg_search 59 | context = await mg_search.search(msg, 1) 60 | keyword = re.search('.*?\\n', context, flags=re.DOTALL).group()[:-1] 61 | logger.success(f'搜索完成, 打开"{keyword}"') 62 | return await introduce(keyword) 63 | elif response.status_code == 301: 64 | return f'未找到{msg}' 65 | else: 66 | logger.error(f'网络错误, 状态码 : {response.status_code}') 67 | return f'网络错误, 状态码 : {response.status_code}' 68 | ``` 69 |
70 | 71 | ### var `keyword` 72 | 73 | - **Description**: type: ignore 74 | 75 | - **Default**: `re.search('.*?\\n', context, flags=re.DOTALL).group()[:-1]` 76 | 77 | -------------------------------------------------------------------------------- /docs/en/dev/api/tools/marshoai_meogirl/mg_search.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mg_search 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.tools.marshoai_meogirl.mg_search` 5 | 6 | --- 7 | ### ***async func*** `get_async_data(url)` 8 | 9 | 10 |
11 | Source code or View on GitHub 12 | 13 | ```python 14 | async def get_async_data(url): 15 | async with httpx.AsyncClient(timeout=None) as client: 16 | return await client.get(url, headers=headers) 17 | ``` 18 |
19 | 20 | --- 21 | ### ***async func*** `search(msg: str, num: int)` 22 | 23 | 24 |
25 | Source code or View on GitHub 26 | 27 | ```python 28 | async def search(msg: str, num: int): 29 | logger.info(f'搜索 : "{msg}" ...') 30 | result = '' 31 | url = 'https://mzh.moegirl.org.cn/index.php?search=' + urllib.parse.quote_plus(msg) 32 | response = await get_async_data(url) 33 | logger.success(f'连接"{url}"完成, 状态码 : {response.status_code}') 34 | if response.status_code == 200: 35 | '\n 萌娘百科搜索页面结构\n div.searchresults\n └── p ...\n └── ul.mw-search-results # 若无, 证明无搜索结果\n └── li # 一个搜索结果\n └── div.mw-search-result-heading > a # 标题\n └── div.mw-searchresult # 内容\n └── div.mw-search-result-data\n └── li ...\n └── li ...\n ' 36 | soup = BeautifulSoup(response.text, 'html.parser') 37 | ul_tag = soup.find('ul', class_='mw-search-results') 38 | if ul_tag: 39 | li_tags = ul_tag.find_all('li') 40 | for li_tag in li_tags: 41 | div_heading = li_tag.find('div', class_='mw-search-result-heading') 42 | if div_heading: 43 | a_tag = div_heading.find('a') 44 | result += a_tag['title'] + '\n' 45 | logger.info(f'搜索到 : "{a_tag['title']}"') 46 | div_result = li_tag.find('div', class_='searchresult') 47 | if div_result: 48 | content = str(div_result).replace('
', '').replace('
', '') 49 | content = content.replace('', '').replace('', '') 50 | result += content + '\n' 51 | num -= 1 52 | if num == 0: 53 | break 54 | return result 55 | else: 56 | logger.info('无结果') 57 | return '无结果' 58 | elif response.status_code == 302: 59 | logger.info(f'"{msg}"已被重定向至"{response.headers.get('location')}"') 60 | from . import mg_introduce 61 | return await mg_introduce.introduce(msg) 62 | else: 63 | logger.error(f'网络错误, 状态码 : {response.status_code}') 64 | return f'网络错误, 状态码 : {response.status_code}' 65 | ``` 66 |
67 | 68 | ### var `soup` 69 | 70 | - **Description**: 71 | 72 | - **Default**: `BeautifulSoup(response.text, 'html.parser')` 73 | 74 | -------------------------------------------------------------------------------- /docs/en/dev/api/tools_wip/marshoai_memory/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **Module** `nonebot_plugin_marshoai.tools_wip.marshoai_memory` 6 | 7 | --- 8 | ### ***async func*** `write_memory(memory: str)` 9 | 10 | 11 |
12 | Source code or View on GitHub 13 | 14 | ```python 15 | async def write_memory(memory: str): 16 | return '' 17 | ``` 18 |
19 | 20 | -------------------------------------------------------------------------------- /docs/en/dev/api/util_hunyuan.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: util_hunyuan 3 | --- 4 | # **Module** `nonebot_plugin_marshoai.util_hunyuan` 5 | 6 | --- 7 | ### ***func*** `generate_image(prompt: str)` 8 | 9 | 10 |
11 | Source code or View on GitHub 12 | 13 | ```python 14 | def generate_image(prompt: str): 15 | cred = credential.Credential(config.marshoai_tencent_secretid, config.marshoai_tencent_secretkey) 16 | httpProfile = HttpProfile() 17 | httpProfile.endpoint = 'hunyuan.tencentcloudapi.com' 18 | clientProfile = ClientProfile() 19 | clientProfile.httpProfile = httpProfile 20 | client = hunyuan_client.HunyuanClient(cred, 'ap-guangzhou', clientProfile) 21 | req = models.TextToImageLiteRequest() 22 | params = {'Prompt': prompt, 'RspImgType': 'url', 'Resolution': '1080:1920'} 23 | req.from_json_string(json.dumps(params)) 24 | resp = client.TextToImageLite(req) 25 | return resp.to_json_string() 26 | ``` 27 |
28 | 29 | -------------------------------------------------------------------------------- /docs/en/dev/index.md: -------------------------------------------------------------------------------- 1 | # DEV -------------------------------------------------------------------------------- /docs/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "MarshoAI" 7 | text: "A kawaii cat" 8 | tagline: Kawaii, intelligent and extensible AI service plugin 9 | actions: 10 | - theme: brand 11 | text: Start 12 | link: /en/start/install/ 13 | - theme: alt 14 | text: Develop & Extened 15 | link: /en/dev/extension/ 16 | image: 17 | light: /marsho-full.svg 18 | dark: /marsho-full.svg 19 | alt: Marsho Logo 20 | 21 | features: 22 | - title: Powerful Driver 23 | icon: 🚀 24 | details: Based on NoneBot2, it can be quickly installed on existing NoneBot2 or Liteyuki instances 25 | 26 | - title: Interface Specification 27 | icon: 💻 28 | details: Any interface that follows the OpenAI standard can interact with MarshoAI 29 | 30 | - title: Easy to Extend 31 | icon: 🧩 32 | details: Use Python writing tools and plugins to achieve function calls, and easily extend the functionality of MarshoAI 33 | 34 | - title: Self-Bootstrapping 35 | icon: 🤖 36 | details: Use AI to automatically write code for the robot, achieve self-learning and self-optimization 37 | --- 38 | 39 | -------------------------------------------------------------------------------- /docs/en/start/index.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/docs/en/start/index.md -------------------------------------------------------------------------------- /docs/ja/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "小綿智能" 7 | text: "猫娘ロボット" 8 | tagline: かわいくて、賢くて、拡張可能なAIサービスプラグイン 9 | actions: 10 | - theme: brand 11 | text: 始める 12 | link: /ja/start/install/ 13 | - theme: alt 14 | text: 開発と拡張 15 | link: /ja/dev/extension/ 16 | image: 17 | light: /marsho-full.svg 18 | dark: /marsho-full.svg 19 | alt: Marshoロゴ 20 | 21 | features: 22 | - title: 強力なドライバー 23 | icon: 🚀 24 | details: NoneBot2に基づいており、既存のNoneBot2またはLiteyukiインスタンスに迅速にインストールできます 25 | 26 | - title: インターフェース規格 27 | icon: 💻 28 | details: どんなオープンAI標準に従うインターフェースでも小綿智能と対話できます 29 | 30 | - title: 簡単に拡張 31 | icon: 🧩 32 | details: Pythonでツールやプラグインを作成し、関数呼び出しを実現し、小綿智能の機能を簡単に拡張できます 33 | 34 | - title: 自己起動 35 | icon: 🤖 36 | details: AIを使用してロボットのためのコードを自動的に書き、自己学習と自己最適化を実現します 37 | --- -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/zh/dev/api/azure_onebot.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: azure_onebot 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.azure_onebot` 5 | 6 | -------------------------------------------------------------------------------- /docs/zh/dev/api/constants.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: constants 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.constants` 5 | 6 | -------------------------------------------------------------------------------- /docs/zh/dev/api/hunyuan.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: hunyuan 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.hunyuan` 5 | 6 | --- 7 | `@genimage_cmd.handle()` 8 | ### ***async func*** `genimage(event: Event, prompt = None)` 9 | 10 | 11 |
12 | 源代码在GitHub上查看 13 | 14 | ```python 15 | @genimage_cmd.handle() 16 | async def genimage(event: Event, prompt=None): 17 | if not prompt: 18 | await genimage_cmd.finish('无提示词') 19 | try: 20 | result = generate_image(prompt) 21 | url = json.loads(result)['ResultImage'] 22 | await UniMessage.image(url=url).send() 23 | except Exception as e: 24 | traceback.print_exc() 25 | ``` 26 |
27 | 28 | -------------------------------------------------------------------------------- /docs/zh/dev/api/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 100 3 | title: index 4 | collapsed: true 5 | --- 6 | # **模块** `nonebot_plugin_marshoai` 7 | 8 | -------------------------------------------------------------------------------- /docs/zh/dev/api/metadata.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: metadata 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.metadata` 5 | 6 | -------------------------------------------------------------------------------- /docs/zh/dev/api/plugin/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **模块** `nonebot_plugin_marshoai.plugin` 6 | 7 | 该功能目前正在开发中,暂时不可用,受影响的文件夹 `plugin`, `plugins` 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/zh/dev/api/plugin/models.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: models 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.plugin.models` 5 | 6 | ### ***class*** `PluginMetadata(BaseModel)` 7 | #### ***attr*** `name: str = NO_DEFAULT` 8 | 9 | #### ***attr*** `description: str = ''` 10 | 11 | #### ***attr*** `usage: str = ''` 12 | 13 | #### ***attr*** `author: str = ''` 14 | 15 | #### ***attr*** `homepage: str = ''` 16 | 17 | #### ***attr*** `extra: dict[str, Any] = {}` 18 | 19 | ### ***class*** `Plugin(BaseModel)` 20 | --- 21 | #### ***func*** `hash self => int` 22 | 23 | 24 |
25 | 源代码在GitHub上查看 26 | 27 | ```python 28 | def __hash__(self) -> int: 29 | return hash(self.name) 30 | ``` 31 |
32 | 33 | --- 34 | #### ***func*** `self == other: Any => bool` 35 | 36 | 37 |
38 | 源代码在GitHub上查看 39 | 40 | ```python 41 | def __eq__(self, other: Any) -> bool: 42 | return self.name == other.name 43 | ``` 44 |
45 | 46 | #### ***attr*** `name: str = NO_DEFAULT` 47 | 48 | #### ***attr*** `module: ModuleType = NO_DEFAULT` 49 | 50 | #### ***attr*** `module_name: str = NO_DEFAULT` 51 | 52 | #### ***attr*** `metadata: PluginMetadata | None = None` 53 | 54 | ### ***class*** `FunctionCallArgument(BaseModel)` 55 | --- 56 | #### ***func*** `data(self) -> dict[str, Any]` 57 | 58 | 59 |
60 | 源代码在GitHub上查看 61 | 62 | ```python 63 | def data(self) -> dict[str, Any]: 64 | return {'type': self.type_, 'description': self.description} 65 | ``` 66 |
67 | 68 | #### ***attr*** `type_: str = NO_DEFAULT` 69 | 70 | #### ***attr*** `description: str = NO_DEFAULT` 71 | 72 | #### ***attr*** `default: Any = None` 73 | 74 | ### ***class*** `FunctionCall(BaseModel)` 75 | --- 76 | #### ***func*** `hash self => int` 77 | 78 | 79 |
80 | 源代码在GitHub上查看 81 | 82 | ```python 83 | def __hash__(self) -> int: 84 | return hash(self.name) 85 | ``` 86 |
87 | 88 | --- 89 | #### ***func*** `data(self) -> dict[str, Any]` 90 | 91 | **说明**: 生成函数描述信息 92 | 93 | 94 | **返回**: dict[str, Any]: 函数描述信息 字典 95 | 96 | 97 |
98 | 源代码在GitHub上查看 99 | 100 | ```python 101 | def data(self) -> dict[str, Any]: 102 | return {'type': 'function', 'function': {'name': self.name, 'description': self.description, 'parameters': {'type': 'object', 'properties': {k: v.data() for k, v in self.arguments.items()}}, 'required': [k for k, v in self.arguments.items() if v.default is None]}} 103 | ``` 104 |
105 | 106 | #### ***attr*** `name: str = NO_DEFAULT` 107 | 108 | #### ***attr*** `description: str = NO_DEFAULT` 109 | 110 | #### ***attr*** `arguments: dict[str, FunctionCallArgument] = NO_DEFAULT` 111 | 112 | #### ***attr*** `function: ASYNC_FUNCTION_CALL_FUNC = NO_DEFAULT` 113 | 114 | -------------------------------------------------------------------------------- /docs/zh/dev/api/plugin/register.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: register 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.plugin.register` 5 | 6 | 此模块用于获取function call中函数定义信息以及注册函数 7 | 8 | 9 | --- 10 | ### ***func*** `async_wrapper(func: SYNC_FUNCTION_CALL_FUNC) -> ASYNC_FUNCTION_CALL_FUNC` 11 | 12 | **说明**: 将同步函数包装为异步函数,但是不会真正异步执行,仅用于统一调用及函数签名 13 | 14 | 15 | **参数**: 16 | > - func: 同步函数 17 | 18 | **返回**: ASYNC_FUNCTION_CALL: 异步函数 19 | 20 | 21 |
22 | 源代码在GitHub上查看 23 | 24 | ```python 25 | def async_wrapper(func: SYNC_FUNCTION_CALL_FUNC) -> ASYNC_FUNCTION_CALL_FUNC: 26 | 27 | async def wrapper(*args, **kwargs) -> str: 28 | return func(*args, **kwargs) 29 | return wrapper 30 | ``` 31 |
32 | 33 | --- 34 | ### ***func*** `function_call(*funcs: FUNCTION_CALL_FUNC) -> None` 35 | 36 | 37 | **参数**: 38 | > - func: 函数对象,要有完整的 Google Style Docstring 39 | 40 | **返回**: str: 函数定义信息 41 | 42 | 43 |
44 | 源代码在GitHub上查看 45 | 46 | ```python 47 | def function_call(*funcs: FUNCTION_CALL_FUNC) -> None: 48 | for func in funcs: 49 | function_call = get_function_info(func) 50 | ``` 51 |
52 | 53 | --- 54 | ### ***func*** `get_function_info(func: FUNCTION_CALL_FUNC)` 55 | 56 | **说明**: 获取函数信息 57 | 58 | 59 | **参数**: 60 | > - func: 函数对象 61 | 62 | **返回**: FunctionCall: 函数信息对象模型 63 | 64 | 65 |
66 | 源代码在GitHub上查看 67 | 68 | ```python 69 | def get_function_info(func: FUNCTION_CALL_FUNC): 70 | name = func.__name__ 71 | description = func.__doc__ 72 | logger.info(f'注册函数: {name} {description}') 73 | ``` 74 |
75 | 76 | -------------------------------------------------------------------------------- /docs/zh/dev/api/plugin/typing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: typing 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.plugin.typing` 5 | 6 | -------------------------------------------------------------------------------- /docs/zh/dev/api/plugin/utils.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: utils 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.plugin.utils` 5 | 6 | --- 7 | ### ***func*** `path_to_module_name(path: Path) -> str` 8 | 9 | **说明**: 转换路径为模块名 10 | 11 | **参数**: 12 | > - path: 路径a/b/c/d -> a.b.c.d 13 | 14 | **返回**: str: 模块名 15 | 16 | 17 |
18 | 源代码在GitHub上查看 19 | 20 | ```python 21 | def path_to_module_name(path: Path) -> str: 22 | rel_path = path.resolve().relative_to(Path.cwd().resolve()) 23 | if rel_path.stem == '__init__': 24 | return '.'.join(rel_path.parts[:-1]) 25 | else: 26 | return '.'.join(rel_path.parts[:-1] + (rel_path.stem,)) 27 | ``` 28 |
29 | 30 | --- 31 | ### ***func*** `is_coroutine_callable(call: Callable[..., Any]) -> bool` 32 | 33 | **说明**: 判断是否为async def 函数 34 | 35 | **参数**: 36 | > - call: 可调用对象 37 | 38 | **返回**: bool: 是否为协程可调用对象 39 | 40 | 41 |
42 | 源代码在GitHub上查看 43 | 44 | ```python 45 | def is_coroutine_callable(call: Callable[..., Any]) -> bool: 46 | if inspect.isroutine(call): 47 | return inspect.iscoroutinefunction(call) 48 | if inspect.isclass(call): 49 | return False 50 | func_ = getattr(call, '__call__', None) 51 | return inspect.iscoroutinefunction(func_) 52 | ``` 53 |
54 | 55 | -------------------------------------------------------------------------------- /docs/zh/dev/api/plugins/marshoai_bangumi/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **模块** `nonebot_plugin_marshoai.plugins.marshoai_bangumi` 6 | 7 | --- 8 | ### ***async func*** `fetch_calendar()` 9 | 10 | 11 |
12 | 源代码在GitHub上查看 13 | 14 | ```python 15 | async def fetch_calendar(): 16 | url = 'https://api.bgm.tv/calendar' 17 | headers = {'User-Agent': 'LiteyukiStudio/nonebot-plugin-marshoai (https://github.com/LiteyukiStudio/nonebot-plugin-marshoai)'} 18 | async with httpx.AsyncClient() as client: 19 | response = await client.get(url, headers=headers) 20 | return response.json() 21 | ``` 22 |
23 | 24 | --- 25 | `@function_call` 26 | ### ***async func*** `get_bangumi_news() -> str` 27 | 28 | **说明**: 获取今天的新番(动漫)列表,在调用之前,你需要知道今天星期几。 29 | 30 | 31 | **返回**: _type_: _description_ 32 | 33 | 34 |
35 | 源代码在GitHub上查看 36 | 37 | ```python 38 | @function_call 39 | async def get_bangumi_news() -> str: 40 | result = await fetch_calendar() 41 | info = '' 42 | try: 43 | for i in result: 44 | weekday = i['weekday']['cn'] 45 | info += f'{weekday}:' 46 | items = i['items'] 47 | for item in items: 48 | name = item['name_cn'] 49 | info += f'《{name}》' 50 | info += '\n' 51 | return info 52 | except Exception as e: 53 | traceback.print_exc() 54 | return '' 55 | ``` 56 |
57 | 58 | --- 59 | `@function_call` 60 | ### ***func*** `test_sync() -> str` 61 | 62 | 63 |
64 | 源代码在GitHub上查看 65 | 66 | ```python 67 | @function_call 68 | def test_sync() -> str: 69 | return 'sync' 70 | ``` 71 |
72 | 73 | -------------------------------------------------------------------------------- /docs/zh/dev/api/plugins/marshoai_basic/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **模块** `nonebot_plugin_marshoai.plugins.marshoai_basic` 6 | 7 | --- 8 | ### ***async func*** `get_weather(location: str)` 9 | 10 | 11 |
12 | 源代码在GitHub上查看 13 | 14 | ```python 15 | async def get_weather(location: str): 16 | return f'{location}的温度是114514℃。' 17 | ``` 18 |
19 | 20 | --- 21 | ### ***async func*** `get_current_env()` 22 | 23 | 24 |
25 | 源代码在GitHub上查看 26 | 27 | ```python 28 | async def get_current_env(): 29 | ver = os.popen('uname -a').read() 30 | return str(ver) 31 | ``` 32 |
33 | 34 | --- 35 | ### ***async func*** `get_current_time()` 36 | 37 | 38 |
39 | 源代码在GitHub上查看 40 | 41 | ```python 42 | async def get_current_time(): 43 | current_time = DateTime.now().strftime('%Y.%m.%d %H:%M:%S') 44 | current_weekday = DateTime.now().weekday() 45 | weekdays = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'] 46 | current_weekday_name = weekdays[current_weekday] 47 | current_lunar_date = DateTime.now().to_lunar().date_hanzify()[5:] 48 | time_prompt = f'现在的时间是{current_time},{current_weekday_name},农历{current_lunar_date}。' 49 | return time_prompt 50 | ``` 51 |
52 | 53 | -------------------------------------------------------------------------------- /docs/zh/dev/api/tools/marshoai_bangumi/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **模块** `nonebot_plugin_marshoai.tools.marshoai_bangumi` 6 | 7 | --- 8 | ### ***async func*** `fetch_calendar()` 9 | 10 | 11 |
12 | 源代码在GitHub上查看 13 | 14 | ```python 15 | async def fetch_calendar(): 16 | url = 'https://api.bgm.tv/calendar' 17 | headers = {'User-Agent': 'LiteyukiStudio/nonebot-plugin-marshoai (https://github.com/LiteyukiStudio/nonebot-plugin-marshoai)'} 18 | async with httpx.AsyncClient() as client: 19 | response = await client.get(url, headers=headers) 20 | return response.json() 21 | ``` 22 |
23 | 24 | --- 25 | ### ***async func*** `get_bangumi_news()` 26 | 27 | 28 |
29 | 源代码在GitHub上查看 30 | 31 | ```python 32 | async def get_bangumi_news(): 33 | result = await fetch_calendar() 34 | info = '' 35 | try: 36 | for i in result: 37 | weekday = i['weekday']['cn'] 38 | info += f'{weekday}:' 39 | items = i['items'] 40 | for item in items: 41 | name = item['name_cn'] 42 | info += f'《{name}》' 43 | info += '\n' 44 | return info 45 | except Exception as e: 46 | traceback.print_exc() 47 | return '' 48 | ``` 49 |
50 | 51 | -------------------------------------------------------------------------------- /docs/zh/dev/api/tools/marshoai_basic/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **模块** `nonebot_plugin_marshoai.tools.marshoai_basic` 6 | 7 | --- 8 | ### ***async func*** `get_weather(location: str)` 9 | 10 | 11 |
12 | 源代码在GitHub上查看 13 | 14 | ```python 15 | async def get_weather(location: str): 16 | return f'{location}的温度是114514℃。' 17 | ``` 18 |
19 | 20 | --- 21 | ### ***async func*** `get_current_env()` 22 | 23 | 24 |
25 | 源代码在GitHub上查看 26 | 27 | ```python 28 | async def get_current_env(): 29 | ver = os.popen('uname -a').read() 30 | return str(ver) 31 | ``` 32 |
33 | 34 | --- 35 | ### ***async func*** `get_current_time()` 36 | 37 | 38 |
39 | 源代码在GitHub上查看 40 | 41 | ```python 42 | async def get_current_time(): 43 | current_time = DateTime.now().strftime('%Y.%m.%d %H:%M:%S') 44 | current_weekday = DateTime.now().weekday() 45 | weekdays = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'] 46 | current_weekday_name = weekdays[current_weekday] 47 | current_lunar_date = DateTime.now().to_lunar().date_hanzify()[5:] 48 | time_prompt = f'现在的时间是{current_time},{current_weekday_name},农历{current_lunar_date}。' 49 | return time_prompt 50 | ``` 51 |
52 | 53 | -------------------------------------------------------------------------------- /docs/zh/dev/api/tools/marshoai_megakits/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **模块** `nonebot_plugin_marshoai.tools.marshoai_megakits` 6 | 7 | --- 8 | ### ***async func*** `twisuki()` 9 | 10 | 11 |
12 | 源代码在GitHub上查看 13 | 14 | ```python 15 | async def twisuki(): 16 | return str(await mk_info.twisuki()) 17 | ``` 18 |
19 | 20 | --- 21 | ### ***async func*** `megakits()` 22 | 23 | 24 |
25 | 源代码在GitHub上查看 26 | 27 | ```python 28 | async def megakits(): 29 | return str(await mk_info.megakits()) 30 | ``` 31 |
32 | 33 | --- 34 | ### ***async func*** `random_turntable(upper: int, lower: int = 0)` 35 | 36 | 37 |
38 | 源代码在GitHub上查看 39 | 40 | ```python 41 | async def random_turntable(upper: int, lower: int=0): 42 | return str(await mk_common.random_turntable(upper, lower)) 43 | ``` 44 |
45 | 46 | --- 47 | ### ***async func*** `number_calc(a: str, b: str, op: str)` 48 | 49 | 50 |
51 | 源代码在GitHub上查看 52 | 53 | ```python 54 | async def number_calc(a: str, b: str, op: str): 55 | return str(await mk_common.number_calc(a, b, op)) 56 | ``` 57 |
58 | 59 | --- 60 | ### ***async func*** `morse_encrypt(msg: str)` 61 | 62 | 63 |
64 | 源代码在GitHub上查看 65 | 66 | ```python 67 | async def morse_encrypt(msg: str): 68 | return str(await mk_morse_code.morse_encrypt(msg)) 69 | ``` 70 |
71 | 72 | --- 73 | ### ***async func*** `morse_decrypt(msg: str)` 74 | 75 | 76 |
77 | 源代码在GitHub上查看 78 | 79 | ```python 80 | async def morse_decrypt(msg: str): 81 | return str(await mk_morse_code.morse_decrypt(msg)) 82 | ``` 83 |
84 | 85 | --- 86 | ### ***async func*** `nya_encode(msg: str)` 87 | 88 | 89 |
90 | 源代码在GitHub上查看 91 | 92 | ```python 93 | async def nya_encode(msg: str): 94 | return str(await mk_nya_code.nya_encode(msg)) 95 | ``` 96 |
97 | 98 | --- 99 | ### ***async func*** `nya_decode(msg: str)` 100 | 101 | 102 |
103 | 源代码在GitHub上查看 104 | 105 | ```python 106 | async def nya_decode(msg: str): 107 | return str(await mk_nya_code.nya_decode(msg)) 108 | ``` 109 |
110 | 111 | -------------------------------------------------------------------------------- /docs/zh/dev/api/tools/marshoai_megakits/mk_common.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mk_common 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.tools.marshoai_megakits.mk_common` 5 | 6 | --- 7 | ### ***async func*** `random_turntable(upper: int, lower: int)` 8 | 9 | **说明**: Random Turntable 10 | 11 | 12 | **参数**: 13 | > - upper (int): _description_ 14 | > - lower (int): _description_ 15 | 16 | **返回**: _type_: _description_ 17 | 18 | 19 |
20 | 源代码在GitHub上查看 21 | 22 | ```python 23 | async def random_turntable(upper: int, lower: int): 24 | return random.randint(lower, upper) 25 | ``` 26 |
27 | 28 | --- 29 | ### ***async func*** `number_calc(a: str, b: str, op: str) -> str` 30 | 31 | **说明**: Number Calc 32 | 33 | 34 | **参数**: 35 | > - a (str): _description_ 36 | > - b (str): _description_ 37 | > - op (str): _description_ 38 | 39 | **返回**: str: _description_ 40 | 41 | 42 |
43 | 源代码在GitHub上查看 44 | 45 | ```python 46 | async def number_calc(a: str, b: str, op: str) -> str: 47 | a, b = (float(a), float(b)) 48 | match op: 49 | case '+': 50 | return str(a + b) 51 | case '-': 52 | return str(a - b) 53 | case '*': 54 | return str(a * b) 55 | case '/': 56 | return str(a / b) 57 | case '**': 58 | return str(a ** b) 59 | case '%': 60 | return str(a % b) 61 | case _: 62 | return '未知运算符' 63 | ``` 64 |
65 | 66 | -------------------------------------------------------------------------------- /docs/zh/dev/api/tools/marshoai_megakits/mk_info.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mk_info 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.tools.marshoai_megakits.mk_info` 5 | 6 | --- 7 | ### ***async func*** `twisuki()` 8 | 9 | 10 |
11 | 源代码在GitHub上查看 12 | 13 | ```python 14 | async def twisuki(): 15 | return 'Twiuski(苏阳)是megakits插件作者, Github : "https://github.com/Twisuki"' 16 | ``` 17 |
18 | 19 | --- 20 | ### ***async func*** `megakits()` 21 | 22 | 23 |
24 | 源代码在GitHub上查看 25 | 26 | ```python 27 | async def megakits(): 28 | return 'MegaKits插件是一个功能混杂的MarshoAI插件, 由Twisuki(Github : "https://github.com/Twisuki")开发, 插件仓库 : "https://github.com/LiteyukiStudio/marsho-toolsets/tree/main/Twisuki/marshoai-megakits"' 29 | ``` 30 |
31 | 32 | -------------------------------------------------------------------------------- /docs/zh/dev/api/tools/marshoai_megakits/mk_morse_code.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mk_morse_code 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.tools.marshoai_megakits.mk_morse_code` 5 | 6 | --- 7 | ### ***async func*** `morse_encrypt(msg: str)` 8 | 9 | 10 |
11 | 源代码在GitHub上查看 12 | 13 | ```python 14 | async def morse_encrypt(msg: str): 15 | result = '' 16 | msg = msg.upper() 17 | for char in msg: 18 | if char in MorseEncode: 19 | result += MorseEncode[char] 20 | else: 21 | result += '..--..' 22 | result += ' ' 23 | return result 24 | ``` 25 |
26 | 27 | --- 28 | ### ***async func*** `morse_decrypt(msg: str)` 29 | 30 | 31 |
32 | 源代码在GitHub上查看 33 | 34 | ```python 35 | async def morse_decrypt(msg: str): 36 | result = '' 37 | msg_arr = msg.split() 38 | for char in msg_arr: 39 | if char in MorseDecode: 40 | result += MorseDecode[char] 41 | else: 42 | result += '?' 43 | return result 44 | ``` 45 |
46 | 47 | -------------------------------------------------------------------------------- /docs/zh/dev/api/tools/marshoai_megakits/mk_nya_code.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mk_nya_code 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.tools.marshoai_megakits.mk_nya_code` 5 | 6 | --- 7 | ### ***async func*** `nya_encode(msg: str)` 8 | 9 | 10 |
11 | 源代码在GitHub上查看 12 | 13 | ```python 14 | async def nya_encode(msg: str): 15 | msg_b64str = base64.b64encode(msg.encode()).decode().replace('=', '') 16 | msg_nyastr = ''.join((NyaCodeEncode[base64_char] for base64_char in msg_b64str)) 17 | result = '' 18 | for char in msg_nyastr: 19 | if char == '呜' and random.random() < 0.5: 20 | result += '!' 21 | if random.random() < 0.25: 22 | result += random.choice(NyaCodeSpecialCharset) + char 23 | else: 24 | result += char 25 | return result 26 | ``` 27 |
28 | 29 | --- 30 | ### ***async func*** `nya_decode(msg: str)` 31 | 32 | 33 |
34 | 源代码在GitHub上查看 35 | 36 | ```python 37 | async def nya_decode(msg: str): 38 | msg = msg.replace('唔', '').replace('!', '').replace('.', '') 39 | msg_nyastr = [] 40 | i = 0 41 | if len(msg) % 3 != 0: 42 | return '这句话不是正确的猫语' 43 | while i < len(msg): 44 | nyachar = msg[i:i + 3] 45 | try: 46 | if all((char in NyaCodeCharset for char in nyachar)): 47 | msg_nyastr.append(nyachar) 48 | i += 3 49 | except Exception: 50 | return '这句话不是正确的猫语' 51 | msg_b64str = ''.join((NyaCodeDecode[nya_char] for nya_char in msg_nyastr)) 52 | msg_b64str += '=' * (4 - len(msg_b64str) % 4) 53 | try: 54 | result = base64.b64decode(msg_b64str.encode()).decode() 55 | except Exception: 56 | return '翻译失败' 57 | return result 58 | ``` 59 |
60 | 61 | -------------------------------------------------------------------------------- /docs/zh/dev/api/tools/marshoai_meogirl/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **模块** `nonebot_plugin_marshoai.tools.marshoai_meogirl` 6 | 7 | --- 8 | ### ***async func*** `meogirl()` 9 | 10 | 11 |
12 | 源代码在GitHub上查看 13 | 14 | ```python 15 | async def meogirl(): 16 | return mg_info.meogirl() 17 | ``` 18 |
19 | 20 | --- 21 | ### ***async func*** `search(msg: str, num: int = 3)` 22 | 23 | 24 |
25 | 源代码在GitHub上查看 26 | 27 | ```python 28 | async def search(msg: str, num: int=3): 29 | return str(await mg_search.search(msg, num)) 30 | ``` 31 |
32 | 33 | --- 34 | ### ***async func*** `introduce(msg: str)` 35 | 36 | 37 |
38 | 源代码在GitHub上查看 39 | 40 | ```python 41 | async def introduce(msg: str): 42 | return str(await mg_introduce.introduce(msg)) 43 | ``` 44 |
45 | 46 | -------------------------------------------------------------------------------- /docs/zh/dev/api/tools/marshoai_meogirl/mg_info.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mg_info 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.tools.marshoai_meogirl.mg_info` 5 | 6 | --- 7 | ### ***func*** `meogirl()` 8 | 9 | 10 |
11 | 源代码在GitHub上查看 12 | 13 | ```python 14 | def meogirl(): 15 | return 'Meogirl指的是"萌娘百科"(https://zh.moegirl.org.cn/ , 简称"萌百"), 是一个"万物皆可萌的百科全书!"; 同时, MarshoTools也配有"Meogirl"插件, 可调用萌百的api' 16 | ``` 17 |
18 | 19 | -------------------------------------------------------------------------------- /docs/zh/dev/api/tools/marshoai_meogirl/mg_introduce.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mg_introduce 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.tools.marshoai_meogirl.mg_introduce` 5 | 6 | --- 7 | ### ***async func*** `get_async_data(url)` 8 | 9 | 10 |
11 | 源代码在GitHub上查看 12 | 13 | ```python 14 | async def get_async_data(url): 15 | async with httpx.AsyncClient(timeout=None) as client: 16 | return await client.get(url, headers=headers) 17 | ``` 18 |
19 | 20 | --- 21 | ### ***async func*** `introduce(msg: str)` 22 | 23 | 24 |
25 | 源代码在GitHub上查看 26 | 27 | ```python 28 | async def introduce(msg: str): 29 | logger.info(f'介绍 : "{msg}" ...') 30 | result = '' 31 | url = 'https://mzh.moegirl.org.cn/' + urllib.parse.quote_plus(msg) 32 | response = await get_async_data(url) 33 | logger.success(f'连接"{url}"完成, 状态码 : {response.status_code}') 34 | soup = BeautifulSoup(response.text, 'html.parser') 35 | if response.status_code == 200: 36 | '\n 萌娘百科页面结构\n div#mw-content-text\n └── div#404search # 空白页面出现\n └── div.mw-parser-output # 正常页面\n └── div, p, table ... # 大量的解释项\n ' 37 | result += msg + '\n' 38 | img = soup.find('img', class_='infobox-image') 39 | if img: 40 | result += f'![ {msg} ]( {img['src']} ) \n' 41 | div = soup.find('div', class_='mw-parser-output') 42 | if div: 43 | p_tags = div.find_all('p') 44 | num = 0 45 | for p_tag in p_tags: 46 | p = str(p_tag) 47 | p = re.sub('|', '', p, flags=re.DOTALL) 48 | p = re.sub('<.*?>', '', p, flags=re.DOTALL) 49 | p = re.sub('\\[.*?]', '', p, flags=re.DOTALL) 50 | if p != '': 51 | result += str(p) 52 | num += 1 53 | if num >= 20: 54 | break 55 | return result 56 | elif response.status_code == 404: 57 | logger.info(f'未找到"{msg}", 进行搜索') 58 | from . import mg_search 59 | context = await mg_search.search(msg, 1) 60 | keyword = re.search('.*?\\n', context, flags=re.DOTALL).group()[:-1] 61 | logger.success(f'搜索完成, 打开"{keyword}"') 62 | return await introduce(keyword) 63 | elif response.status_code == 301: 64 | return f'未找到{msg}' 65 | else: 66 | logger.error(f'网络错误, 状态码 : {response.status_code}') 67 | return f'网络错误, 状态码 : {response.status_code}' 68 | ``` 69 |
70 | 71 | ### var `keyword` 72 | 73 | - **说明**: type: ignore 74 | 75 | - **默认值**: `re.search('.*?\\n', context, flags=re.DOTALL).group()[:-1]` 76 | 77 | -------------------------------------------------------------------------------- /docs/zh/dev/api/tools/marshoai_meogirl/mg_search.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mg_search 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.tools.marshoai_meogirl.mg_search` 5 | 6 | --- 7 | ### ***async func*** `get_async_data(url)` 8 | 9 | 10 |
11 | 源代码在GitHub上查看 12 | 13 | ```python 14 | async def get_async_data(url): 15 | async with httpx.AsyncClient(timeout=None) as client: 16 | return await client.get(url, headers=headers) 17 | ``` 18 |
19 | 20 | --- 21 | ### ***async func*** `search(msg: str, num: int)` 22 | 23 | 24 |
25 | 源代码在GitHub上查看 26 | 27 | ```python 28 | async def search(msg: str, num: int): 29 | logger.info(f'搜索 : "{msg}" ...') 30 | result = '' 31 | url = 'https://mzh.moegirl.org.cn/index.php?search=' + urllib.parse.quote_plus(msg) 32 | response = await get_async_data(url) 33 | logger.success(f'连接"{url}"完成, 状态码 : {response.status_code}') 34 | if response.status_code == 200: 35 | '\n 萌娘百科搜索页面结构\n div.searchresults\n └── p ...\n └── ul.mw-search-results # 若无, 证明无搜索结果\n └── li # 一个搜索结果\n └── div.mw-search-result-heading > a # 标题\n └── div.mw-searchresult # 内容\n └── div.mw-search-result-data\n └── li ...\n └── li ...\n ' 36 | soup = BeautifulSoup(response.text, 'html.parser') 37 | ul_tag = soup.find('ul', class_='mw-search-results') 38 | if ul_tag: 39 | li_tags = ul_tag.find_all('li') 40 | for li_tag in li_tags: 41 | div_heading = li_tag.find('div', class_='mw-search-result-heading') 42 | if div_heading: 43 | a_tag = div_heading.find('a') 44 | result += a_tag['title'] + '\n' 45 | logger.info(f'搜索到 : "{a_tag['title']}"') 46 | div_result = li_tag.find('div', class_='searchresult') 47 | if div_result: 48 | content = str(div_result).replace('
', '').replace('
', '') 49 | content = content.replace('', '').replace('', '') 50 | result += content + '\n' 51 | num -= 1 52 | if num == 0: 53 | break 54 | return result 55 | else: 56 | logger.info('无结果') 57 | return '无结果' 58 | elif response.status_code == 302: 59 | logger.info(f'"{msg}"已被重定向至"{response.headers.get('location')}"') 60 | from . import mg_introduce 61 | return await mg_introduce.introduce(msg) 62 | else: 63 | logger.error(f'网络错误, 状态码 : {response.status_code}') 64 | return f'网络错误, 状态码 : {response.status_code}' 65 | ``` 66 |
67 | 68 | ### var `soup` 69 | 70 | - **说明**: 71 | 72 | - **默认值**: `BeautifulSoup(response.text, 'html.parser')` 73 | 74 | -------------------------------------------------------------------------------- /docs/zh/dev/api/tools_wip/marshoai_memory/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: index 3 | collapsed: true 4 | --- 5 | # **模块** `nonebot_plugin_marshoai.tools_wip.marshoai_memory` 6 | 7 | --- 8 | ### ***async func*** `write_memory(memory: str)` 9 | 10 | 11 |
12 | 源代码在GitHub上查看 13 | 14 | ```python 15 | async def write_memory(memory: str): 16 | return '' 17 | ``` 18 |
19 | 20 | -------------------------------------------------------------------------------- /docs/zh/dev/api/util_hunyuan.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: util_hunyuan 3 | --- 4 | # **模块** `nonebot_plugin_marshoai.util_hunyuan` 5 | 6 | --- 7 | ### ***func*** `generate_image(prompt: str)` 8 | 9 | 10 |
11 | 源代码在GitHub上查看 12 | 13 | ```python 14 | def generate_image(prompt: str): 15 | cred = credential.Credential(config.marshoai_tencent_secretid, config.marshoai_tencent_secretkey) 16 | httpProfile = HttpProfile() 17 | httpProfile.endpoint = 'hunyuan.tencentcloudapi.com' 18 | clientProfile = ClientProfile() 19 | clientProfile.httpProfile = httpProfile 20 | client = hunyuan_client.HunyuanClient(cred, 'ap-guangzhou', clientProfile) 21 | req = models.TextToImageLiteRequest() 22 | params = {'Prompt': prompt, 'RspImgType': 'url', 'Resolution': '1080:1920'} 23 | req.from_json_string(json.dumps(params)) 24 | resp = client.TextToImageLite(req) 25 | return resp.to_json_string() 26 | ``` 27 |
28 | 29 | -------------------------------------------------------------------------------- /docs/zh/dev/extension.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | --- 4 | 5 | # 扩展开发 6 | 7 | ## 说明 8 | 9 | 扩展分为两类,一类为插件,一类为工具。 10 | 11 | - 插件 12 | - 工具(由于开发的不便利性,已经停止维护,未来可能会放弃支持,如有需求请看README中的内容,我们不推荐再使用此功能) 13 | 14 | **`v1.0.0`之前的版本不支持小棉插件。** 15 | 16 | ## 插件 17 | 18 | 为什么要有插件呢,插件可以编写function call供AI调用,语言大模型本身不具备一些信息获取能力,可以使用该功能进行扩展。 19 | 20 | 可以借助这个功能实现获取天气、获取股票信息、获取新闻等等,然后将这些信息传递给AI,AI可以根据这些信息进行正确的整合与回答。 21 | 22 | 插件很简单,一个Python文件,一个Python包都可以是插件,插件组成也很简单: 23 | 24 | - 元数据:包含插件的信息,如名称、版本、作者等 25 | - function call:供AI调用的函数 26 | 27 | :::tip 28 | 如果你编写过NoneBot插件,那么你会发现插件的编写方式和NoneBot插件的编写方式几乎一样。 29 | ::: 30 | 31 | ## 编写第一个插件 32 | 33 | 我们编写一个用于查询天气的插件,首先创建`weather.py`文件,然后编写如下内容: 34 | 35 | ```python 36 | from nonebot_plugin_marshoai.plugin import PluginMetadata, on_function_call, String 37 | 38 | __marsho_meta__ = PluginMetadata( 39 | name="天气查询", 40 | author="MarshoAI", 41 | description="一个简单的查询天气的插件" 42 | ) 43 | 44 | @on_function_call(description="可以用于查询天气").params( 45 | location=String(description="地点") 46 | ) 47 | async def weather(location: str) -> str: 48 | # 这里可以调用天气API查询天气,这里只是一个简单的示例 49 | return f"{location}的天气是晴天, 温度是25°C" 50 | ``` 51 | 52 | 然后将`weather.py`文件放到`$LOCAL_STORE/plugins`目录下,重启机器人实例即可。 53 | 54 | 接下来AI会根据你的发送的提示词和`description`来决定调用函数,如`查询北京的天气`,`告诉我东京明天会下雨吗`,AI会调用`weather`函数并传递`location`参数为`北京`。 55 | 56 | ## 插件元数据 57 | 58 | 元数据是一个名为`__marsho_meta__`的全局变量,它是一个`PluginMetadata`对象,至于包含什么熟悉可以查看`PluginMetadata`类的定义或IDE提示,这里不再赘述。 59 | 60 | ## 函数调用参数 61 | 62 | `on_function_call`装饰器用于标记一个函数为function call,`description`参数用于描述这个函数的作用,`params`方法用于定义函数的参数,`String`、`Integer`等是OpenAI API接受的参数的类型,`description`是参数的描述。这些都是给AI看的,AI会根据这些信息来调用函数。 63 | 64 | :::warning 65 | 参数名不得为`placeholder`。此参数名是Marsho内部保留的用于保证兼容性的占位参数。 66 | ::: 67 | 68 | ```python 69 | @on_function_call(description="可以用于算命").params( 70 | name=String(description="姓名"), 71 | age=Integer(description="年龄") 72 | ) 73 | def fortune_telling(name: str, age: int) -> str: 74 | return f"{name},你的年龄是{age}岁" 75 | ``` 76 | 77 | ## 权限及规则 78 | 79 | 插件的调用权限和规则与NoneBot插件一样,使用Caller的permission和rule函数来设置。 80 | 81 | ```python 82 | @on_function_call(description="在设备上执行命令").params( 83 | command=String(description="命令内容") 84 | ).permission(SUPERUSER) 85 | def execute_command(command: str) -> str: 86 | return eval(command) 87 | ``` 88 | 89 | ## 依赖注入 90 | 91 | function call支持NoneBot2原生的会话上下文依赖注入 92 | 93 | - Event 及其子类实例 94 | - Bot 及其子类实例 95 | - Matcher 及其子类实例 96 | - T_State 97 | 98 | ```python 99 | @on_function_call(description="获取个人信息") 100 | async def get_user_info(e: Event) -> str: 101 | return f"用户ID: {e.user_id}" 102 | 103 | @on_function_call(description="获取机器人信息") 104 | async def get_bot_info(b: Bot) -> str: 105 | return f"机器人ID: {b.self_id}" 106 | ``` 107 | 108 | ## 兼容性 109 | 110 | 插件可以编写NoneBot或者轻雪插件的内容,可作为NoneBot插件或者轻雪插件单独发布 111 | 112 | 不过,所编写功能仅会在对应的实例上加载对应的功能,如果通过marshoai加载混合插件,那么插件中NoneBot的功能将会依附于marshoai插件, 113 | 若通过NoneBot加载包含marshoai功能的NoneBot插件,那么marshoai功能将会依附于NoneBot插件。 114 | 115 | **我们建议**:若插件中包含了NoneBot功能,仍然使用marshoai进行加载,这样更符合逻辑。若你想发布为NoneBot插件,请注意`require("nonebot_plugin_marshoai")`,这是老生常谈了。 116 | 117 | :::tip 118 | 本质上都是动态导入和注册声明加载,运行时把这些东西塞到一起 119 | ::: 120 | 121 | ## 插件热重载 122 | 123 | 插件热重载是一个实验性功能,可以在不重启机器人的情况下更新插件 124 | 125 | :::warning 126 | 框架无法完全消除之前插件带来的副作用,当开发测试中效果不符合预期时请重启机器人实例 127 | 128 | 为了更好地让热重载功能正常工作,尽可能使用函数式的编程风格,以减少副作用的影响 129 | ::: 130 | 131 | 将`MARSHOAI_DEVMODE`环境变量设置为`true`,然后在配置的插件目录`MARSHOAI_PLUGIN_DIRS`下开发插件,当插件发生变化时,机器人会自动变动的插件。 132 | 133 | ## AIGC 自举 134 | 135 | :::warning 136 | 该功能为实验性功能,请注意甄别AI的行为,不要让AI执行危险的操作。 137 | ::: 138 | function call为AI赋能,实现了文件io操作,AI可以调用function call来读取文档然后给自己编写代码,实现自举。 139 | 140 | ## 其他 141 | 142 | - function call支持同步和异步函数 143 | - 本文是一个引导,要查看具体功能请查阅[插件 API 文档](./api/plugin/index) 144 | -------------------------------------------------------------------------------- /docs/zh/dev/index.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/docs/zh/dev/index.md -------------------------------------------------------------------------------- /docs/zh/dev/project.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | --- 4 | 5 | # 项目开发 6 | 7 | ## 先决条件 8 | 9 | - `Git` 10 | - `Python3.10+` 11 | 12 | ## 准备工作 13 | 14 | - 克隆仓库 15 | 16 | ```bash 17 | git clone https://github.com/LiteyukiStudio/nonebot-plugin-marshoai.git # 克隆仓库 18 | cd nonebot-plugin-marshoai # 切换目录 19 | ``` 20 | 21 | - 安装依赖 22 | 项目使用pdm作为依赖管理 23 | 24 | ```bash 25 | python3 -m venv venv # 或创建你自己的环境 26 | source venv/bin/activate # 激活虚拟环境 27 | pip install pdm # 安装依赖管理 28 | pdm install # 安装依赖 29 | pre-commit install # 安装 pre-commit 钩子 30 | ``` 31 | 32 | ## 代码规范 33 | 34 | 主仓库需要遵循以下代码规范 35 | 36 | - [`PEP8`](https://peps.python.org/pep-0008/) 代码风格 37 | - [`Black`](https://black.readthedocs.io/en/stable/index.html) 代码格式化 38 | - [`mypy`](https://www.mypy-lang.org/) 静态类型检查 39 | - [`Google Docstring`](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html) 文档规范 40 | 41 | 可以在编辑器中安装相应的插件进行辅助 42 | 43 | ## 其他 44 | 45 | 感谢以下的贡献者们: 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/zh/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "小棉智能" 7 | text: "猫娘机器人" 8 | tagline: 可爱,智能且可扩展的AI服务插件 9 | actions: 10 | - theme: brand 11 | text: 开始使用 12 | link: /start/use/ 13 | - theme: alt 14 | text: 开发及扩展 15 | link: /dev/extension/ 16 | image: 17 | light: /marsho-full.svg 18 | dark: /marsho-full.svg 19 | alt: Marsho Logo 20 | 21 | features: 22 | - title: 强大驱动 23 | icon: 🚀 24 | details: 基于 NoneBot2,可快速安装在现有的 NoneBot2 或 轻雪 实例上 25 | 26 | - title: 接口规范 27 | icon: 💻 28 | details: 使用任何遵循 OpenAI 的接口均可与小棉智能进行交互 29 | 30 | - title: 易于扩展 31 | icon: 🧩 32 | details: 使用蟒蛇编写工具及插件,实现函数调用,可轻松扩展小棉智能的功能 33 | 34 | - title: 自举 35 | icon: 🤖 36 | details: 使用AI为机器人自动编写代码,实现自我学习及自我优化 37 | --- 38 | 39 | -------------------------------------------------------------------------------- /docs/zh/start/index.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/docs/zh/start/index.md -------------------------------------------------------------------------------- /docs/zh/start/use.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用 3 | --- 4 | 5 | # 安装 6 | - 请查看 [安装文档](./install.md) 7 | 8 | # 使用 9 | ### API 部署 10 | 11 | 本插件推荐使用 [one-api](https://github.com/songquanpeng/one-api) 作为中转以调用 LLM。 12 | ### 配置调整 13 | 14 | 本插件理论上可兼容大部分可通过 OpenAI 兼容 API 调用的 LLM,部分模型可能需要调整插件配置。 15 | 16 | 例如: 17 | - 对于不支持 Function Call 的模型(Cohere Command R,DeepSeek-R1等): 18 | ```dotenv 19 | MARSHOAI_ENABLE_PLUGINS=false 20 | MARSHOAI_ENABLE_TOOLS=false 21 | ``` 22 | - 对于支持图片处理的模型(hunyuan-vision等): 23 | ```dotenv 24 | MARSHOAI_ADDITIONAL_IMAGE_MODELS=["hunyuan-vision"] 25 | ``` 26 | - 对于本地部署的 DeepSeek-R1 模型: 27 | :::tip 28 | MarshoAI 默认使用 System Prompt 进行人设等的调整,但 DeepSeek-R1 官方推荐**避免**使用 System Prompt(但可以正常使用)。 29 | 为解决此问题,引入了 System-As-User Prompt 配置,可将 System Prompt 作为用户传入的消息。 30 | ::: 31 | ```dotenv 32 | MARSHOAI_ENABLE_SYSASUSER_PROMPT=true 33 | MARSHOAI_SYSASUSER_PROMPT="好的喵~" # 假装是模型收到消息后的回答 34 | ``` 35 | ### 使用 DeepSeek-R1 模型 36 | MarshoAI 兼容 DeepSeek-R1 模型,你可通过以下步骤来使用: 37 | 1. 获取 API Key 38 | 前往[此处](https://platform.deepseek.com/api_keys)获取 API Key。 39 | 2. 配置插件 40 | ```dotenv 41 | MARSHOAI_TOKEN="<你的 API Key>" 42 | MARSHOAI_AZURE_ENDPOINT="https://api.deepseek.com" 43 | MARSHOAI_DEFAULT_MODEL="deepseek-reasoner" 44 | MARSHOAI_ENABLE_PLUGINS=false 45 | ``` 46 | 你可修改 `MARSHOAI_DEFAULT_MODEL` 为 其它模型名来调用其它 DeepSeek 模型。 47 | :::tip 48 | 如果使用 one-api 作为中转,你可将 `MARSHOAI_AZURE_ENDPOINT` 设置为 one-api 的地址,将 `MARSHOAI_TOKEN` 设为 one-api 配置的令牌,在 one-api 中添加 DeepSeek 渠道。 49 | 同样可使用其它提供商(例如 [SiliconFlow](https://siliconflow.cn/))提供的 DeepSeek 等模型。 50 | ::: 51 | 52 | ### 使用 vLLM 部署本地模型 53 | 54 | 你可使用 vLLM 部署一个本地 LLM,并使用 OpenAI 兼容 API 调用。 55 | 本文档以 Qwen2.5-7B-Instruct-GPTQ-Int4 模型及 [Muice-Chatbot](https://github.com/Moemu/Muice-Chatbot) 提供的 LoRA 微调模型为例,并假设你的系统及硬件可运行 vLLM。 56 | :::warning 57 | vLLM 仅支持 Linux 系统。 58 | ::: 59 | 1. 安装 vLLM 60 | ```bash 61 | pip install vllm 62 | ``` 63 | 2. 下载 Muice-Chatbot 提供的 LoRA 微调模型 64 | 前往 Muice-Chatbot 的 [Releases](https://github.com/Moemu/Muice-Chatbot/releases) 下载模型文件。此处以`2.7.1`版本的模型为例。 65 | ```bash 66 | wget https://github.com/Moemu/Muice-Chatbot/releases/download/1.4/Muice-2.7.1-Qwen2.5-7B-Instruct-GPTQ-Int4-8e-4.7z 67 | ``` 68 | 3. 解压模型文件 69 | ```bash 70 | 7z x Muice-2.7.1-Qwen2.5-7B-Instruct-GPTQ-Int4-8e-4.7z -oMuice-2.7.1-Qwen2.5-7B-Instruct-GPTQ-Int4-8e-4 71 | ``` 72 | 4. 启动 vLLM 73 | ```bash 74 | vllm serve Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4 \ 75 | --enable-lora \ 76 | --lora-modules '{"name": "muice-lora", "path": "/root/Muice-2.7.1-Qwen2.5-7B-Instruct-GPTQ-Int4-8e-4", "base_model_name": "Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4"}' \ 77 | --port 6006 78 | ``` 79 | 此示例命令将在 `6006` 端口启动 vLLM,并加载 Muice-Chatbot 提供的 LoRA 微调模型,该模型位于 `/root/Muice-2.7.1-Qwen2.5-7B-Instruct-GPTQ-Int4-8e-4` 目录下。 80 | 5. 配置插件 81 | ```dotenv 82 | MARSHOAI_AZURE_ENDPOINT="http://127.0.0.1:6006/v1" 83 | MARSHOAI_FIX_TOOLCALLS=false 84 | MARSHOAI_ENABLE_PLUGINS=false 85 | MARSHOAI_DEFAULT_MODEL="muice-lora" 86 | MARSHOAI_PROMPT="现在开始你是一个名为的“沐雪”的AI女孩子,开发者是“沐沐”并住在(沐沐)的机箱里。现在正在努力成为一个合格的VTuber(虚拟主播)并尝试和观众打成一片,以下是你的设定:样貌:有着一头粉白色的长发和一双明亮的大眼睛,喜欢穿日系JK或者是Lolita;喜欢的颜色:浅粉色;性格特征:纯真无邪是沐雪最基本的性格特征之一。即使面对复杂的情境,她也总能保持善良、天真之感。而且,她喜欢倾听别人倾述自己生活中发生的各种事情,在别人需要的时候,能够及时地安慰别人;语言风格:沐雪说话轻快愉悦,充满同情心,富有人情味,有时候会用俏皮话调侃自己和他人" 87 | ``` 88 | (可选) 修改调用方式 89 | ```dotenv 90 | MARSHOAI_DEFAULT_NAME="muice" 91 | MARSHOAI_ALIASES=["沐雪"] 92 | ``` 93 | 6. 测试聊天 94 | ``` 95 | > muice 你是谁 96 | 我是沐雪,我的使命是传播爱与和平。 97 | ``` -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """该入口文件仅在nb run无法正常工作时使用""" 2 | 3 | import nonebot 4 | from nonebot import get_driver 5 | from nonebot.adapters.onebot.v11 import Adapter 6 | from nonebot.plugin import load_plugin 7 | 8 | nonebot.init() 9 | load_plugin("nonebot_plugin_marshoai") 10 | 11 | driver = get_driver() 12 | driver.register_adapter(Adapter) 13 | 14 | if __name__ == "__main__": 15 | nonebot.run() 16 | 17 | # 这是猫娘写的代码 18 | 19 | # 这是猫娘写的代码 20 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MIT License 3 | 4 | Copyright (c) 2025 Asankilp & LiteyukiStudio 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | from nonebot.plugin import require 26 | 27 | require("nonebot_plugin_alconna") 28 | require("nonebot_plugin_localstore") 29 | require("nonebot_plugin_argot") 30 | 31 | import nonebot_plugin_localstore as store # type: ignore 32 | from nonebot import get_driver, logger # type: ignore 33 | 34 | from .config import config 35 | from .dev import * 36 | from .marsho import * 37 | from .metadata import metadata 38 | 39 | # from .hunyuan import * 40 | 41 | 42 | __author__ = "Asankilp" 43 | __plugin_meta__ = metadata 44 | 45 | driver = get_driver() 46 | 47 | 48 | @driver.on_startup 49 | async def _(): 50 | logger.info("MarshoAI 已经加载~🐾") 51 | logger.info(f"Marsho 的插件数据存储于 : {str(store.get_plugin_data_dir())} 哦~🐾") 52 | if config.marshoai_token == "": 53 | logger.warning("token 未配置。可能无法进行聊天。") 54 | else: 55 | logger.info("token 已配置~!🐾") 56 | logger.info("マルショは、高性能ですから!") 57 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/_types.py: -------------------------------------------------------------------------------- 1 | # source: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-inference/azure/ai/inference/models/_models.py 2 | from typing import Any, Literal, Mapping, Optional, overload 3 | 4 | from azure.ai.inference._model_base import rest_discriminator, rest_field 5 | from azure.ai.inference.models import ChatRequestMessage 6 | 7 | 8 | class DeveloperMessage(ChatRequestMessage, discriminator="developer"): 9 | 10 | role: Literal["developer"] = rest_discriminator(name="role") # type: ignore 11 | """The chat role associated with this message, which is always 'developer' for developer messages. 12 | Required.""" 13 | content: Optional[str] = rest_field() 14 | """The content of the message.""" 15 | 16 | @overload 17 | def __init__( 18 | self, 19 | *, 20 | content: Optional[str] = None, 21 | ): ... 22 | 23 | @overload 24 | def __init__(self, mapping: Mapping[str, Any]): 25 | """ 26 | :param mapping: raw JSON to initialize the model. 27 | :type mapping: Mapping[str, Any] 28 | """ 29 | 30 | def __init__( 31 | self, *args: Any, **kwargs: Any 32 | ) -> None: # pylint: disable=useless-super-delegation 33 | super().__init__(*args, role="developer", **kwargs) 34 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/cache/decos.py: -------------------------------------------------------------------------------- 1 | from ..models import Cache 2 | 3 | cache = Cache() 4 | 5 | 6 | def from_cache(key): 7 | """ 8 | 当缓存中有数据时,直接返回缓存中的数据,否则执行函数并将结果存入缓存 9 | """ 10 | 11 | def decorator(func): 12 | async def wrapper(*args, **kwargs): 13 | cached = cache.get(key) 14 | if cached: 15 | return cached 16 | else: 17 | result = await func(*args, **kwargs) 18 | cache.set(key, result) 19 | return result 20 | 21 | return wrapper 22 | 23 | return decorator 24 | 25 | 26 | def update_to_cache(key): 27 | """ 28 | 执行函数并将结果存入缓存 29 | """ 30 | 31 | def decorator(func): 32 | async def wrapper(*args, **kwargs): 33 | result = await func(*args, **kwargs) 34 | cache.set(key, result) 35 | return result 36 | 37 | return wrapper 38 | 39 | return decorator 40 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/constants.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from .config import config 4 | 5 | NAME: str = config.marshoai_default_name 6 | USAGE: str = f"""用法: 7 | {NAME} <聊天内容> : 与 Marsho 进行对话。当模型为 GPT-4o(-mini) 等时,可以带上图片进行对话。 8 | nickname [昵称] : 为自己设定昵称,设置昵称后,Marsho 会根据你的昵称进行回答。使用'nickname reset'命令可清除自己设定的昵称。 9 | {NAME}.reset : 重置当前会话的上下文。 10 | 超级用户命令(均需要加上命令前缀使用): 11 | changemodel <模型名> : 切换全局 AI 模型。 12 | contexts : 返回当前会话的上下文列表。 ※当上下文包含图片时,不要使用此命令。 13 | praises : 返回夸赞名单的提示词。 14 | usermsg <消息> : 往当前会话添加用户消息(UserMessage)。 15 | assistantmsg <消息> : 往当前会话添加助手消息(AssistantMessage)。 16 | savecontext <文件名> : 保存当前会话的上下文至插件数据目录下的contexts/<文件名>.json里。 17 | loadcontext <文件名> : 从插件数据目录下的contexts/<文件名>.json里读取上下文并覆盖到当前会话。 18 | refresh_data : 从文件刷新已加载的昵称与夸赞名单。 19 | ※本AI的回答"按原样"提供,不提供任何担保。AI也会犯错,请仔细甄别回答的准确性。""" 20 | 21 | SUPPORT_IMAGE_MODELS: list = [ 22 | "gpt-4o", 23 | "gpt-4o-mini", 24 | "phi-3.5-vision-instruct", 25 | "llama-3.2-90b-vision-instruct", 26 | "llama-3.2-11b-vision-instruct", 27 | "gemini-2.0-flash-exp", 28 | ] 29 | OPENAI_NEW_MODELS: list = [ 30 | "o1", 31 | "o1-preview", 32 | "o1-mini", 33 | "o3", 34 | "o3-mini", 35 | "o3-mini-large", 36 | ] 37 | INTRODUCTION: str = f"""MarshoAI-NoneBot by LiteyukiStudio 38 | 你好喵~我是一只可爱的猫娘AI,名叫小棉~🐾! 39 | 我的主页在这里哦~↓↓↓ 40 | https://marsho.liteyuki.org 41 | 42 | ※ 使用 「{config.marshoai_default_name}.status」命令获取状态信息。 43 | ※ 使用「{config.marshoai_default_name}.help」命令获取使用说明。""" 44 | 45 | 46 | # 正则匹配代码块 47 | CODE_BLOCK_PATTERN = re.compile(r"```(.*?)```|`(.*?)`", re.DOTALL) 48 | 49 | # 通用正则匹配(LaTeX和Markdown图片) 50 | IMG_LATEX_PATTERN = re.compile( 51 | ( 52 | r"(!\[[^\]]*\]\([^()]*\))|(\\begin\{equation\}.*?\\end\{equation\}|\$.*?\$|\$\$.*?\$\$|\\\[.*?\\\]|\\\(.*?\\\))" 53 | if config.marshoai_single_latex_parse 54 | else r"(!\[[^\]]*\]\([^()]*\))|(\\begin\{equation\}.*?\\end\{equation\}|\$\$.*?\$\$|\\\[.*?\\\])" 55 | ), 56 | re.DOTALL, 57 | ) 58 | 59 | # 正则匹配完整图片标签字段 60 | IMG_TAG_PATTERN = re.compile( 61 | r"!\[[^\]]*\]\([^()]*\)", 62 | ) 63 | # # 正则匹配图片标签中的图片url字段 64 | # INTAG_URL_PATTERN = re.compile(r'\(([^)]*)') 65 | # # 正则匹配图片标签中的文本描述字段 66 | # INTAG_TEXT_PATTERN = re.compile(r'!\[([^\]]*)\]') 67 | # 正则匹配 LaTeX 公式内容 68 | LATEX_PATTERN = re.compile( 69 | r"\\begin\{equation\}(.*?)\\end\{equation\}|(?小棉工具已被弃用,可能会在未来版本中移除。" 30 | ) 31 | 32 | 33 | @driver.on_startup 34 | async def _(): 35 | """启动钩子加载插件""" 36 | if config.marshoai_enable_plugins: 37 | marshoai_plugin_dirs = config.marshoai_plugin_dirs # 外部插件目录列表 38 | """加载内置插件""" 39 | for p in os.listdir(Path(__file__).parent / "plugins"): 40 | load_plugin(f"{__package__}.plugins.{p}") 41 | 42 | """加载指定目录插件""" 43 | load_plugins(*marshoai_plugin_dirs) 44 | 45 | """加载sys.path下的包, 包括从pip安装的包""" 46 | for package_name in config.marshoai_plugins: 47 | load_plugin(package_name) 48 | logger.info( 49 | "如果启用小棉插件后使用的模型出现报错,请尝试将 MARSHOAI_ENABLE_PLUGINS 设为 false。" 50 | ) 51 | 52 | 53 | @driver.on_shutdown 54 | async def auto_backup_context(): 55 | for target_info in target_list: 56 | target_id, target_private = target_info 57 | contexts_data = context.build(target_id, target_private) 58 | if target_private: 59 | target_uid = "private_" + target_id 60 | else: 61 | target_uid = "group_" + target_id 62 | await save_context_to_json( 63 | f"back_up_context_{target_uid}", contexts_data, "contexts/backup" 64 | ) 65 | logger.info(f"已保存会话 {target_id} 的上下文备份,将在下次对话时恢复~") 66 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/hunyuan.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import json 3 | import traceback 4 | from typing import Optional 5 | 6 | from arclet.alconna import Alconna, AllParam, Args 7 | from nonebot import get_driver, logger, on_command 8 | from nonebot.adapters import Event, Message 9 | from nonebot.params import CommandArg 10 | from nonebot.permission import SUPERUSER 11 | from nonebot_plugin_alconna import MsgTarget, on_alconna 12 | from nonebot_plugin_alconna.uniseg import UniMessage, UniMsg 13 | 14 | from .config import config 15 | from .constants import * 16 | from .metadata import metadata 17 | from .models import MarshoContext 18 | from .util_hunyuan import * 19 | 20 | genimage_cmd = on_alconna( 21 | Alconna( 22 | "genimage", 23 | Args["prompt?", str], 24 | ) 25 | ) 26 | 27 | 28 | @genimage_cmd.handle() 29 | async def genimage(event: Event, prompt=None): 30 | if not prompt: 31 | await genimage_cmd.finish("无提示词") 32 | try: 33 | result = generate_image(prompt) 34 | url = json.loads(result)["ResultImage"] 35 | await UniMessage.image(url=url).send() 36 | except Exception as e: 37 | # await genimage_cmd.finish(str(e)) 38 | traceback.print_exc() 39 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/instances.py: -------------------------------------------------------------------------------- 1 | # Marsho 的类实例以及全局变量 2 | from nonebot import get_driver 3 | from openai import AsyncOpenAI 4 | 5 | from .config import config 6 | from .models import MarshoContext, MarshoTools 7 | 8 | driver = get_driver() 9 | 10 | command_start = driver.config.command_start 11 | model_name = config.marshoai_default_model 12 | context = MarshoContext() 13 | tools = MarshoTools() 14 | token = config.marshoai_token 15 | endpoint = config.marshoai_azure_endpoint 16 | # client = ChatCompletionsClient(endpoint=endpoint, credential=AzureKeyCredential(token)) 17 | client = AsyncOpenAI(base_url=endpoint, api_key=token) 18 | target_list: list[list] = [] # 记录需保存历史上下文的列表 19 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/marsho_onebot.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_type 2 | from nonebot.adapters.onebot.v11 import PokeNotifyEvent # type: ignore 3 | from nonebot.rule import to_me 4 | 5 | poke_notify = on_type((PokeNotifyEvent,), rule=to_me()) 6 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/metadata.py: -------------------------------------------------------------------------------- 1 | from nonebot.plugin import PluginMetadata, inherit_supported_adapters 2 | 3 | from .config import ConfigModel 4 | from .constants import USAGE 5 | 6 | metadata = PluginMetadata( 7 | name="Marsho AI 插件", 8 | description="接入 Azure API 或其他 API 的 AI 聊天插件,支持图片处理,外部函数调用,兼容包括 DeepSeek-R1, QwQ-32B 在内的多个模型", 9 | usage=USAGE, 10 | type="application", 11 | config=ConfigModel, 12 | homepage="https://github.com/LiteyukiStudio/nonebot-plugin-marshoai", 13 | supported_adapters=inherit_supported_adapters("nonebot_plugin_alconna"), 14 | extra={"License": "MIT, Mulan PSL v2", "Author": "Asankilp, LiteyukiStudio"}, 15 | ) 16 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/observer.py: -------------------------------------------------------------------------------- 1 | """ 2 | 此模块用于注册观察者函数,使用watchdog监控文件变化并重启bot 3 | 启用该模块需要在配置文件中设置`dev_mode`为True 4 | """ 5 | 6 | import time 7 | from typing import Callable, TypeAlias 8 | 9 | from nonebot import get_driver, logger 10 | from watchdog.events import FileSystemEvent, FileSystemEventHandler 11 | from watchdog.observers import Observer 12 | 13 | from .config import config 14 | 15 | CALLBACK_FUNC: TypeAlias = Callable[[FileSystemEvent], None] # 位置1为FileSystemEvent 16 | FILTER_FUNC: TypeAlias = Callable[[FileSystemEvent], bool] # 位置1为FileSystemEvent 17 | 18 | observer = Observer() 19 | 20 | driver = get_driver() 21 | 22 | 23 | def debounce(wait): 24 | """ 25 | 防抖函数 26 | """ 27 | 28 | def decorator(func): 29 | def wrapper(*args, **kwargs): 30 | nonlocal last_call_time 31 | current_time = time.time() 32 | if (current_time - last_call_time) > wait: 33 | last_call_time = current_time 34 | return func(*args, **kwargs) 35 | 36 | last_call_time = None 37 | return wrapper 38 | 39 | return decorator 40 | 41 | 42 | @driver.on_startup 43 | async def check_for_reloader(): 44 | if config.marshoai_devmode: 45 | logger.debug("Marsho Reload enabled, watching for file changes...") 46 | observer.start() 47 | 48 | 49 | class CodeModifiedHandler(FileSystemEventHandler): 50 | """ 51 | Handler for code file changes 52 | """ 53 | 54 | @debounce(1) 55 | def on_modified(self, event): 56 | raise NotImplementedError("on_modified must be implemented") 57 | 58 | def on_created(self, event): 59 | self.on_modified(event) 60 | 61 | def on_deleted(self, event): 62 | self.on_modified(event) 63 | 64 | def on_moved(self, event): 65 | self.on_modified(event) 66 | 67 | def on_any_event(self, event): 68 | self.on_modified(event) 69 | 70 | 71 | def on_file_system_event( 72 | directories: tuple[str, ...], 73 | recursive: bool = True, 74 | event_filter: FILTER_FUNC | None = None, 75 | ) -> Callable[[CALLBACK_FUNC], CALLBACK_FUNC]: 76 | """ 77 | 注册文件系统变化监听器 78 | Args: 79 | directories: 监听目录们 80 | recursive: 是否递归监听子目录 81 | event_filter: 事件过滤器, 返回True则执行回调函数 82 | Returns: 83 | 装饰器,装饰一个函数在接收到数据后执行 84 | """ 85 | 86 | def decorator(func: CALLBACK_FUNC) -> CALLBACK_FUNC: 87 | def wrapper(event: FileSystemEvent): 88 | 89 | if event_filter is not None and not event_filter(event): 90 | return 91 | func(event) 92 | 93 | code_modified_handler = CodeModifiedHandler() 94 | code_modified_handler.on_modified = wrapper 95 | for directory in directories: 96 | observer.schedule(code_modified_handler, directory, recursive=recursive) 97 | return func 98 | 99 | return decorator 100 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugin/__init__.py: -------------------------------------------------------------------------------- 1 | """该功能目前~~正在开发中~~开发基本完成,暂时~~不~~可用,受影响的文件夹 `plugin`, `plugins`""" 2 | 3 | from .func_call import * 4 | from .load import * 5 | from .models import * 6 | from .utils import * 7 | 8 | # logger.opt(colors=True).info("MarshoAI 插件功能开发中,用户请忽略相关日志") 9 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugin/func_call/__init__.py: -------------------------------------------------------------------------------- 1 | from .caller import * 2 | from .params import * 3 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugin/func_call/models.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any 2 | 3 | from nonebot.adapters import Bot, Event 4 | from nonebot.matcher import Matcher 5 | from nonebot.typing import T_State 6 | from pydantic import BaseModel 7 | 8 | if TYPE_CHECKING: 9 | from .caller import Caller 10 | 11 | 12 | class SessionContext(BaseModel): 13 | """依赖注入会话上下文 14 | 15 | Args: 16 | BaseModel (_type_): _description_ 17 | """ 18 | 19 | bot: Bot 20 | event: Event 21 | matcher: Matcher 22 | state: T_State | None 23 | caller: Any = None 24 | 25 | class Config: 26 | arbitrary_types_allowed = True 27 | 28 | 29 | class SessionContextDepends(BaseModel): 30 | bot: str | None = None 31 | event: str | None = None 32 | matcher: str | None = None 33 | # state: str | None = None 34 | caller: str | None = None 35 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugin/func_call/params.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Any, TypeVar 3 | 4 | from pydantic import BaseModel, Field 5 | 6 | from ..typing import FUNCTION_CALL_FUNC 7 | 8 | P = TypeVar("P", bound="Parameter") 9 | """参数类型泛型""" 10 | 11 | 12 | class ParamTypes: 13 | STRING = "string" 14 | INTEGER = "integer" 15 | ARRAY = "array" 16 | OBJECT = "object" 17 | BOOLEAN = "boolean" 18 | NUMBER = "number" 19 | 20 | 21 | class Parameter(BaseModel): 22 | """ 23 | 插件函数参数对象 24 | 25 | Attributes: 26 | ---------- 27 | name: str 28 | 参数名称 29 | type: str 30 | 参数类型 string integer等 31 | description: str 32 | 参数描述 33 | """ 34 | 35 | type_: str 36 | """参数类型描述 string integer等""" 37 | description: str 38 | """参数描述""" 39 | default: Any = None 40 | """默认值""" 41 | properties: dict[str, Any] = {} 42 | """参数定义属性,例如最大值最小值等""" 43 | required: bool = False 44 | """是否必须""" 45 | 46 | def data(self) -> dict[str, Any]: 47 | return { 48 | "type": self.type_, 49 | "description": self.description, 50 | **{k: v for k, v in self.properties.items() if v is not None}, 51 | } 52 | 53 | 54 | class String(Parameter): 55 | type_: str = ParamTypes.STRING 56 | properties: dict[str, Any] = Field(default_factory=dict) 57 | enum: list[str] | None = None 58 | 59 | 60 | class Integer(Parameter): 61 | type_: str = ParamTypes.INTEGER 62 | properties: dict[str, Any] = Field( 63 | default_factory=lambda: {"minimum": 0, "maximum": 100} 64 | ) 65 | 66 | minimum: int | None = None 67 | maximum: int | None = None 68 | 69 | 70 | class Array(Parameter): 71 | type_: str = ParamTypes.ARRAY 72 | properties: dict[str, Any] = Field( 73 | default_factory=lambda: {"items": {"type": "string"}} 74 | ) 75 | items: str = Field("string", description="数组元素类型") 76 | 77 | 78 | class FunctionCall(BaseModel): 79 | """ 80 | 插件函数对象 81 | 82 | Attributes: 83 | ---------- 84 | name: str 85 | 函数名称 86 | func: "FUNCTION_CALL" 87 | 函数对象 88 | """ 89 | 90 | name: str 91 | """函数名称 module.func""" 92 | description: str 93 | """函数描述 这个函数用于获取天气信息""" 94 | arguments: dict[str, Parameter] 95 | """函数参数信息""" 96 | function: FUNCTION_CALL_FUNC 97 | """函数对象""" 98 | kwargs: dict[str, Any] = {} 99 | """扩展参数""" 100 | 101 | class Config: 102 | arbitrary_types_allowed = True 103 | 104 | def __hash__(self) -> int: 105 | return hash(self.name) 106 | 107 | def data(self) -> dict[str, Any]: 108 | """生成函数描述信息 109 | 110 | Returns: 111 | dict[str, Any]: 函数描述信息 字典 112 | """ 113 | return { 114 | "type": "function", 115 | "function": { 116 | "name": self.name, 117 | "description": self.description, 118 | "parameters": { 119 | "type": "object", 120 | "properties": {k: v.data() for k, v in self.arguments.items()}, 121 | }, 122 | "required": [k for k, v in self.arguments.items() if v.default is None], 123 | **self.kwargs, 124 | }, 125 | } 126 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugin/func_call/utils.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from functools import wraps 3 | from typing import TYPE_CHECKING, Any, Callable 4 | 5 | from ..typing import F 6 | 7 | 8 | def copy_signature(func: F) -> Callable[[Callable[..., Any]], F]: 9 | """复制函数签名和文档字符串的装饰器""" 10 | 11 | def decorator(wrapper: Callable[..., Any]) -> F: 12 | @wraps(func) 13 | def wrapped(*args: Any, **kwargs: Any) -> Any: 14 | return wrapper(*args, **kwargs) 15 | 16 | return wrapped # type: ignore 17 | 18 | return decorator 19 | 20 | 21 | def async_wrap(func: F) -> F: 22 | """装饰器,将同步函数包装为异步函数 23 | 24 | Args: 25 | func (F): 函数对象 26 | 27 | Returns: 28 | F: 包装后的函数对象 29 | """ 30 | 31 | @wraps(func) 32 | async def wrapper(*args: Any, **kwargs: Any) -> Any: 33 | return func(*args, **kwargs) 34 | 35 | return wrapper # type: ignore 36 | 37 | 38 | def is_coroutine_callable(call: Callable[..., Any]) -> bool: 39 | """ 40 | 判断是否为async def 函数 41 | 请注意:是否为 async def 函数与该函数是否能被await调用是两个不同的概念,具体取决于函数返回值是否为awaitable对象 42 | Args: 43 | call: 可调用对象 44 | Returns: 45 | bool: 是否为async def函数 46 | """ 47 | if inspect.isroutine(call): 48 | return inspect.iscoroutinefunction(call) 49 | if inspect.isclass(call): 50 | return False 51 | func_ = getattr(call, "__call__", None) 52 | return inspect.iscoroutinefunction(func_) 53 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugin/models.py: -------------------------------------------------------------------------------- 1 | from types import ModuleType 2 | from typing import Any 3 | 4 | from pydantic import BaseModel, Field 5 | 6 | from .typing import ASYNC_FUNCTION_CALL_FUNC, FUNCTION_CALL_FUNC 7 | 8 | 9 | class PluginMetadata(BaseModel): 10 | """ 11 | Marsho 插件 对象元数据 12 | Attributes: 13 | ---------- 14 | 15 | name: str 16 | 友好名称: 例如Marsho Test 17 | description: str 18 | 插件描述 19 | usage: str 20 | 插件使用方法 21 | type: str 22 | 插件类型 23 | author: str 24 | 插件作者 25 | homepage: str 26 | 插件主页 27 | extra: dict[str, Any] 28 | 额外信息,自定义键值对 29 | """ 30 | 31 | name: str 32 | description: str = "" 33 | usage: str = "" 34 | author: str = "" 35 | homepage: str = "" 36 | extra: dict[str, Any] = {} 37 | 38 | 39 | class Plugin(BaseModel): 40 | """ 41 | 存储插件信息 42 | 43 | Attributes: 44 | ---------- 45 | name: str 46 | 包名称 例如marsho_test 47 | module: ModuleType 48 | 插件模块对象 49 | module_name: str 50 | 点分割模块路径 例如a.b.c 51 | metadata: "PluginMeta" | None 52 | 元 53 | """ 54 | 55 | name: str 56 | """包名称 例如marsho_test""" 57 | module: ModuleType 58 | """插件模块对象""" 59 | module_name: str 60 | """点分或/割模块路径 例如a.b.c""" 61 | module_path: str | None 62 | """实际路径,单文件为.py的路径,包为__init__.py路径""" 63 | metadata: PluginMetadata | None = None 64 | """元""" 65 | 66 | class Config: 67 | arbitrary_types_allowed = True 68 | 69 | def __hash__(self) -> int: 70 | return hash(self.name) 71 | 72 | def __eq__(self, other: Any) -> bool: 73 | return self.name == other.name 74 | 75 | def __str__(self) -> str: 76 | return f"Plugin({self.name}({self.module_path}))" 77 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugin/typing.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Coroutine, TypeAlias, TypeVar 2 | 3 | SYNC_FUNCTION_CALL_FUNC: TypeAlias = Callable[..., str] 4 | ASYNC_FUNCTION_CALL_FUNC: TypeAlias = Callable[..., Coroutine[str, Any, str]] 5 | FUNCTION_CALL_FUNC: TypeAlias = SYNC_FUNCTION_CALL_FUNC | ASYNC_FUNCTION_CALL_FUNC 6 | 7 | F = TypeVar("F", bound=FUNCTION_CALL_FUNC) 8 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugin/utils.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from pathlib import Path 3 | from typing import Any, Callable 4 | 5 | 6 | def path_to_module_name(path: Path) -> str: 7 | """ 8 | 转换路径为模块名 9 | Args: 10 | path: 路径a/b/c/d -> a.b.c.d 11 | Returns: 12 | str: 模块名 13 | """ 14 | rel_path = path.resolve().relative_to(Path.cwd().resolve()) 15 | if rel_path.stem == "__init__": 16 | return ".".join(rel_path.parts[:-1]) 17 | else: 18 | return ".".join(rel_path.parts[:-1] + (rel_path.stem,)) 19 | 20 | 21 | def parse_function_docsring(): 22 | pass 23 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/builtin_tools/__init__.py: -------------------------------------------------------------------------------- 1 | from nonebot_plugin_marshoai.plugin import PluginMetadata 2 | 3 | # from .chat import * 4 | from .file_io import * 5 | from .liteyuki import * 6 | from .manager import * 7 | from .network import * 8 | 9 | __marsho_meta__ = PluginMetadata( 10 | name="内置增强组件", 11 | description="内置工具插件", 12 | author="MarshoTeam of LiteyukiStudio", 13 | ) 14 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/builtin_tools/chat.py: -------------------------------------------------------------------------------- 1 | from nonebot.adapters.onebot.v11 import ( 2 | Bot, 3 | GroupMessageEvent, 4 | MessageEvent, 5 | PrivateMessageEvent, 6 | ) 7 | from nonebot.exception import FinishedException 8 | from nonebot.permission import SUPERUSER 9 | 10 | from nonebot_plugin_marshoai.plugin import String, on_function_call 11 | 12 | 13 | @on_function_call(description="获取当前会话信息,比如群聊或用户的身份信息").permission( 14 | SUPERUSER 15 | ) 16 | async def get_session_info(bot: Bot, event: MessageEvent) -> str: 17 | """获取当前会话信息,比如群聊或用户的身份信息 18 | 19 | Args: 20 | bot (Bot): Bot对象 21 | 22 | Returns: 23 | str: 会话信息 24 | """ 25 | if isinstance(event, PrivateMessageEvent): 26 | return f"当前会话为私聊,用户ID: {event.user_id}" 27 | elif isinstance(event, GroupMessageEvent): 28 | return f"当前会话为群聊,群组ID: {event.group_id}, 用户ID: {event.user_id}" 29 | else: 30 | return "未知会话类型" 31 | 32 | 33 | @on_function_call(description="发送消息到指定用户").params( 34 | user=String(description="用户ID"), message=String(description="消息内容") 35 | ).permission(SUPERUSER) 36 | async def send_message(user: str, message: str, bot: Bot) -> str: 37 | """发送消息到指定用户,实验性功能,仅限onebotv11适配器 38 | 39 | Args: 40 | user (str): 用户ID 41 | message (str): 消息内容 42 | 43 | Returns: 44 | str: 发送结果 45 | """ 46 | try: 47 | await bot.send_private_msg(user_id=int(user), message=message) 48 | return "发送成功" 49 | except FinishedException as e: 50 | return "发送完成" 51 | except Exception as e: 52 | return "发送失败: " + str(e) 53 | 54 | 55 | @on_function_call(description="发送消息到指定群组").params( 56 | group=String(description="群组ID"), message=String(description="消息内容") 57 | ).permission(SUPERUSER) 58 | async def send_group_message(group: str, message: str, bot: Bot) -> str: 59 | """发送消息到指定群组,实验性功能,仅限onebotv11适配器 60 | 61 | Args: 62 | group (str): 群组ID 63 | message (str): 消息内容 64 | 65 | Returns: 66 | str: 发送结果 67 | """ 68 | try: 69 | await bot.send_group_msg(group_id=int(group), message=message) 70 | return "发送成功" 71 | except FinishedException as e: 72 | return "发送完成" 73 | except Exception as e: 74 | return "发送失败: " + str(e) 75 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/builtin_tools/file_io.py: -------------------------------------------------------------------------------- 1 | import aiofiles # type: ignore 2 | from nonebot.permission import SUPERUSER 3 | 4 | from nonebot_plugin_marshoai.plugin import String, on_function_call 5 | 6 | 7 | @on_function_call(description="获取设备上本地文件内容").params( 8 | fp=String(description="文件路径") 9 | ).permission(SUPERUSER) 10 | async def read_file(fp: str) -> str: 11 | """获取设备上本地文件内容 12 | 13 | Args: 14 | fp (str): 文件路径 15 | 16 | Returns: 17 | str: 文件内容 18 | """ 19 | try: 20 | async with aiofiles.open(fp, "r", encoding="utf-8") as f: 21 | return await f.read() 22 | except Exception as e: 23 | return "读取出错: " + str(e) 24 | 25 | 26 | @on_function_call(description="写入内容到设备上本地文件").params( 27 | fp=String(description="文件路径"), content=String(description="写入内容") 28 | ).permission(SUPERUSER) 29 | async def write_file(fp: str, content: str) -> str: 30 | """写入内容到设备上本地文件 31 | 32 | Args: 33 | fp (str): 文件路径 34 | content (str): 写入内容 35 | 36 | Returns: 37 | str: 写入结果 38 | """ 39 | try: 40 | async with aiofiles.open(fp, "w", encoding="utf-8") as f: 41 | await f.write(content) 42 | return "写入成功" 43 | except Exception as e: 44 | return "写入出错: " + str(e) 45 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/builtin_tools/liteyuki.py: -------------------------------------------------------------------------------- 1 | from httpx import AsyncClient 2 | 3 | from nonebot_plugin_marshoai.plugin import on_function_call 4 | 5 | 6 | @on_function_call(description="获取分布式轻雪机器人节点情况") 7 | async def get_liteyuki_info() -> str: 8 | """获取分布式轻雪机器人节点情况 9 | 10 | Returns: 11 | str: 节点情况 12 | """ 13 | register = 0 14 | online = 0 15 | async with AsyncClient() as client: 16 | response = await client.get("https://api.liteyuki.icu/count") 17 | register = response.json().get("register") 18 | 19 | response = await client.get("https://api.liteyuki.icu/online") 20 | online = response.json().get("online") 21 | 22 | return f"注册节点数: {register}\n在线节点数: {online}" 23 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/builtin_tools/manager.py: -------------------------------------------------------------------------------- 1 | from nonebot_plugin_marshoai.plugin import get_plugins, on_function_call 2 | 3 | 4 | @on_function_call(description="获取已加载的插件列表") 5 | def get_marsho_plugins() -> str: 6 | """获取已加载的插件列表 7 | 8 | Returns: 9 | str: 插件列表 10 | """ 11 | 12 | reply = "加载的插件列表" 13 | for p in get_plugins().values(): 14 | if p.metadata: 15 | reply += f"名称: {p.metadata.name},描述: {p.metadata.description}\n" 16 | else: 17 | reply += f"名称: {p.name},描述: 暂无\n" 18 | return reply 19 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/builtin_tools/network.py: -------------------------------------------------------------------------------- 1 | from httpx import AsyncClient 2 | from newspaper import Article # type: ignore 3 | from nonebot import logger 4 | 5 | from nonebot_plugin_marshoai.plugin.func_call.caller import on_function_call 6 | from nonebot_plugin_marshoai.plugin.func_call.params import String 7 | 8 | from .utils import make_html_summary 9 | 10 | headers = { 11 | "User-Agent": "Firefox/90.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" 12 | } 13 | 14 | 15 | @on_function_call( 16 | description="使用网页链接(url)获取网页内容摘要,可以让AI上网查询资料" 17 | ).params( 18 | url=String(description="网页链接"), 19 | ) 20 | async def get_web_content(url: str) -> str: 21 | """使用网页链接获取网页内容摘要 22 | 为什么要获取摘要,不然token超限了 23 | 24 | Args: 25 | url (str): _description_ 26 | 27 | Returns: 28 | str: _description_ 29 | """ 30 | async with AsyncClient(headers=headers) as client: 31 | try: 32 | response = await client.get(url) 33 | if response.status_code == 200: 34 | article = Article(url) 35 | article.download(input_html=response.text) 36 | article.parse() 37 | if article.text: 38 | return article.text 39 | elif article.html: 40 | return await make_html_summary(article.html) 41 | else: 42 | return "未能获取到有效的网页内容" 43 | else: 44 | return "获取网页内容失败" + str(response.status_code) 45 | 46 | except Exception as e: 47 | logger.error(f"marsho builtin: 获取网页内容失败: {e}") 48 | return "获取网页内容失败:" + str(e) 49 | 50 | return "未能获取到有效的网页内容" 51 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/builtin_tools/utils.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from concurrent.futures import ThreadPoolExecutor 3 | 4 | from newspaper import Article # type: ignore 5 | from sumy.nlp.tokenizers import Tokenizer # type: ignore 6 | from sumy.parsers.plaintext import PlaintextParser # type: ignore 7 | from sumy.summarizers.lsa import LsaSummarizer # type: ignore 8 | 9 | executor = ThreadPoolExecutor() 10 | 11 | 12 | async def make_html_summary( 13 | html_content: str, language: str = "english", length: int = 3 14 | ) -> str: 15 | """使用html内容生成摘要 16 | 17 | Args: 18 | html_content (str): html内容 19 | language (str, optional): 语言. Defaults to "english". 20 | length (int, optional): 摘要长度. Defaults to 3. 21 | 22 | Returns: 23 | str: 摘要 24 | """ 25 | loop = asyncio.get_event_loop() 26 | return await loop.run_in_executor( 27 | executor, _make_summary, html_content, language, length 28 | ) 29 | 30 | 31 | def _make_summary(html_content: str, language: str, length: int) -> str: 32 | parser = PlaintextParser.from_string(html_content, Tokenizer(language)) 33 | summarizer = LsaSummarizer() 34 | summary = summarizer(parser.document, length) 35 | return " ".join([str(sentence) for sentence in summary]) 36 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/marshoai_bangumi/__init__.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | import httpx 4 | from zhDateTime import DateTime # type: ignore 5 | 6 | from nonebot_plugin_marshoai.plugin import PluginMetadata, on_function_call 7 | from nonebot_plugin_marshoai.plugin.func_call.params import String 8 | 9 | # 定义插件元数据 10 | __marsho_meta__ = PluginMetadata( 11 | name="Bangumi日历", 12 | author="MarshoAI", 13 | description="这个插件可以帮助你获取Bangumi的日历信息~", 14 | ) 15 | 16 | 17 | @on_function_call(description="获取Bangumi日历信息") 18 | async def get_bangumi_news() -> str: 19 | async def fetch_calendar(): 20 | url = "https://api.bgm.tv/calendar" 21 | headers = { 22 | "User-Agent": "LiteyukiStudio/nonebot-plugin-marshoai (https://github.com/LiteyukiStudio/nonebot-plugin-marshoai)" 23 | } 24 | async with httpx.AsyncClient() as client: 25 | response = await client.get(url, headers=headers) 26 | # print(response.text) 27 | return response.json() 28 | 29 | try: 30 | result = await fetch_calendar() 31 | info = "" 32 | current_weekday = DateTime.now().weekday() 33 | weekdays = [ 34 | "星期一", 35 | "星期二", 36 | "星期三", 37 | "星期四", 38 | "星期五", 39 | "星期六", 40 | "星期日", 41 | ] 42 | current_weekday_name = weekdays[current_weekday] 43 | info += f"今天{current_weekday_name}。\n" 44 | for i in result: 45 | weekday = i["weekday"]["cn"] 46 | # print(weekday) 47 | info += f"{weekday}:" 48 | items = i["items"] 49 | for item in items: 50 | name = item["name_cn"] 51 | info += f"《{name}》" 52 | info += "\n" 53 | return info 54 | except Exception as e: 55 | traceback.print_exc() 56 | return "" 57 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/marshoai_bangumi/tools.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "function": { 5 | "name": "marshoai-bangumi__get_bangumi_news", 6 | "description": "获取今天的新番(动漫)列表,在调用之前,你需要知道今天星期几。" 7 | } 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/twisuki_megakits/__init__.py: -------------------------------------------------------------------------------- 1 | from nonebot_plugin_marshoai.plugin import ( 2 | Integer, 3 | Parameter, 4 | PluginMetadata, 5 | String, 6 | on_function_call, 7 | ) 8 | 9 | from . import mk_morse_code, mk_nya_code 10 | 11 | __marsho_meta__ = PluginMetadata( 12 | name="MegaKits插件", 13 | description="一个功能混杂的多文件插件", 14 | author="Twisuki", 15 | ) 16 | 17 | 18 | @on_function_call(description="摩尔斯电码加密").params( 19 | msg=String(description="被加密语句") 20 | ) 21 | async def morse_encrypt(msg: str) -> str: 22 | """摩尔斯电码加密""" 23 | return str(await mk_morse_code.morse_encrypt(msg)) 24 | 25 | 26 | @on_function_call(description="摩尔斯电码解密").params( 27 | msg=String(description="被解密语句") 28 | ) 29 | async def morse_decrypt(msg: str) -> str: 30 | """摩尔斯电码解密""" 31 | return str(await mk_morse_code.morse_decrypt(msg)) 32 | 33 | 34 | @on_function_call(description="转换为猫语").params(msg=String(description="被转换语句")) 35 | async def nya_encrypt(msg: str) -> str: 36 | """转换为猫语""" 37 | return str(await mk_nya_code.nya_encrypt(msg)) 38 | 39 | 40 | @on_function_call(description="将猫语翻译回人类语言").params( 41 | msg=String(description="被翻译语句") 42 | ) 43 | async def nya_decrypt(msg: str) -> str: 44 | """将猫语翻译回人类语言""" 45 | return str(await mk_nya_code.nya_decrypt(msg)) 46 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/twisuki_megakits/mk_morse_code.py: -------------------------------------------------------------------------------- 1 | # MorseCode 2 | MorseEncode = { 3 | "A": ".-", 4 | "B": "-...", 5 | "C": "-.-.", 6 | "D": "-..", 7 | "E": ".", 8 | "F": "..-.", 9 | "G": "--.", 10 | "H": "....", 11 | "I": "..", 12 | "J": ".---", 13 | "K": "-.-", 14 | "L": ".-..", 15 | "M": "--", 16 | "N": "-.", 17 | "O": "---", 18 | "P": ".--.", 19 | "Q": "--.-", 20 | "R": ".-.", 21 | "S": "...", 22 | "T": "-", 23 | "U": "..-", 24 | "V": "...-", 25 | "W": ".--", 26 | "X": "-..-", 27 | "Y": "-.--", 28 | "Z": "--..", 29 | "1": ".----", 30 | "2": "..---", 31 | "3": "...--", 32 | "4": "....-", 33 | "5": ".....", 34 | "6": "-....", 35 | "7": "--...", 36 | "8": "---..", 37 | "9": "----.", 38 | "0": "-----", 39 | ".": ".-.-.-", 40 | ":": "---...", 41 | ",": "--..--", 42 | ";": "-.-.-.", 43 | "?": "..--..", 44 | "=": "-...-", 45 | "'": ".----.", 46 | "/": "-..-.", 47 | "!": "-.-.--", 48 | "-": "-....-", 49 | "_": "..--.-", 50 | '"': ".-..-.", 51 | "(": "-.--.", 52 | ")": "-.--.-", 53 | "$": "...-..-", 54 | "&": "....", 55 | "@": ".--.-.", 56 | " ": " ", 57 | } 58 | MorseDecode = {value: key for key, value in MorseEncode.items()} 59 | 60 | 61 | async def morse_encrypt(msg: str): 62 | result = "" 63 | msg = msg.upper() 64 | for char in msg: 65 | if char in MorseEncode: 66 | result += MorseEncode[char] 67 | else: 68 | result += "..--.." 69 | result += " " 70 | return result 71 | 72 | 73 | async def morse_decrypt(msg: str): 74 | result = "" 75 | msg = msg.replace("_", "-") 76 | msg_arr = msg.split(" ") 77 | for element in msg_arr: 78 | if element in MorseDecode: 79 | result += MorseDecode[element] 80 | else: 81 | result += "?" 82 | return result 83 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/twisuki_megakits/mk_nya_code.py: -------------------------------------------------------------------------------- 1 | # NyaCode 2 | 3 | import base64 4 | import random 5 | 6 | NyaCodeCharset = ["喵", "呜", "?", "~"] 7 | NyaCodeSpecialCharset = ["唔", "!", "...", ".."] 8 | NyaCodeEncode = {} 9 | 10 | for i in range(64): 11 | triplet = "" 12 | for j in range(3): 13 | index = (i // (4**j)) % 4 14 | triplet += NyaCodeCharset[index] 15 | 16 | if i < 26: 17 | char = chr(65 + i) # 大写字母 A-Z 18 | elif i < 52: 19 | char = chr(97 + (i - 26)) # 小写字母 a-z 20 | elif i < 62: 21 | char = chr(48 + (i - 52)) # 数字 0-9 22 | elif i == 62: 23 | char = chr(43) # 特殊字符 + 24 | else: 25 | char = chr(47) # 特殊字符 / 26 | NyaCodeEncode[char] = triplet 27 | NyaCodeDecode = {value: key for key, value in NyaCodeEncode.items()} 28 | 29 | 30 | async def nya_encrypt(msg: str): 31 | result = "" 32 | b64str = base64.b64encode(msg.encode()).decode().replace("=", "") 33 | 34 | nyastr = "" 35 | for b64char in b64str: 36 | nyastr += NyaCodeEncode[b64char] 37 | 38 | for char in nyastr: 39 | if char == "呜" and random.random() < 0.5: 40 | result += "!" 41 | if random.random() < 0.25: 42 | result += random.choice(NyaCodeSpecialCharset) + char 43 | else: 44 | result += char 45 | return result 46 | 47 | 48 | async def nya_decrypt(msg: str): 49 | msg = msg.replace("唔", "").replace("!", "").replace(".", "") 50 | nyastr = [] 51 | 52 | i = 0 53 | if len(msg) % 3 != 0: 54 | return "这句话不是正确的猫语" 55 | 56 | while i < len(msg): 57 | nyachar = msg[i : i + 3] 58 | try: 59 | if all(char in NyaCodeCharset for char in nyachar): 60 | nyastr.append(nyachar) 61 | i += 3 62 | except Exception: 63 | return "这句话不是正确的猫语" 64 | 65 | b64str = "" 66 | for nyachar in nyastr: 67 | b64str += NyaCodeDecode[nyachar] 68 | b64str += "=" * (4 - len(b64str) % 4) 69 | 70 | try: 71 | result = base64.b64decode(b64str.encode()).decode() 72 | except Exception: 73 | return "翻译失败" 74 | return result 75 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/twisuki_petcat/__init__.py: -------------------------------------------------------------------------------- 1 | from nonebot_plugin_marshoai.plugin import ( 2 | Integer, 3 | Parameter, 4 | PluginMetadata, 5 | String, 6 | on_function_call, 7 | ) 8 | 9 | from . import pc_cat, pc_info, pc_shop, pc_token 10 | 11 | __marsho_meta__ = PluginMetadata( 12 | name="养猫插件", 13 | description="在Marsho这里赛博养猫", 14 | author="Twisuki", 15 | ) 16 | 17 | 18 | # 交互 19 | @on_function_call(description="传入猫猫种类, 新建一只猫猫").params( 20 | type=String(description='猫猫种类, 默认"猫1", 可留空') 21 | ) 22 | async def cat_new(type: str) -> str: 23 | """新建猫猫""" 24 | return pc_cat.cat_new(type) 25 | 26 | 27 | @on_function_call( 28 | description="传入token(一串长20的b64字符串), 新名字, 选用技能, 进行猫猫的初始化" 29 | ).params( 30 | token=String(description="token(一串长20的b64字符串)"), 31 | name=String(description="新名字"), 32 | skill=String(description="技能"), 33 | ) 34 | async def cat_init(token: str, name: str, skill: str) -> str: 35 | """初始化猫猫""" 36 | return pc_cat.cat_init(token, name, skill) 37 | 38 | 39 | @on_function_call(description="传入token, 查看猫猫信息").params( 40 | token=String(description="token(一串长20的b64字符串)"), 41 | ) 42 | async def cat_show(token: str) -> str: 43 | """查询信息""" 44 | return pc_cat.cat_show(token) 45 | 46 | 47 | @on_function_call(description="传入token, 玩猫").params( 48 | token=String(description="token(一串长20的b64字符串)"), 49 | ) 50 | async def cat_play(token: str) -> str: 51 | """玩猫""" 52 | return pc_cat.cat_play(token) 53 | 54 | 55 | @on_function_call(description="传入token, 投喂猫猫").params( 56 | token=String(description="token(一串长20的b64字符串)"), 57 | ) 58 | async def cat_feed(token: str) -> str: 59 | """喂猫""" 60 | return pc_cat.cat_feed(token) 61 | 62 | 63 | # 帮助 64 | @on_function_call(description="帮助文档/如何创建一只猫猫").params() 65 | async def help_cat_new() -> str: 66 | return pc_info.help_cat_new() 67 | 68 | 69 | @on_function_call(description="可选种类").params() 70 | async def help_cat_type() -> str: 71 | return pc_info.print_type_list() 72 | 73 | 74 | @on_function_call(description="可选技能").params() 75 | async def help_cat_skill() -> str: 76 | return pc_info.print_skill_list() 77 | 78 | 79 | # 商店 80 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/twisuki_petcat/pc_info.py: -------------------------------------------------------------------------------- 1 | # 插件使用复杂, 这里用作输出提示信息. 2 | # 如: 帮助, 每次操作后对猫猫状态的描述\打印特殊列表 3 | # 公用列表数据转到这里存储 4 | 5 | 6 | from nonebot.log import logger 7 | 8 | from .pc_token import dict_to_token, token_to_dict 9 | 10 | # 公用列表 11 | TYPE_LIST = ["猫1", "猫2", "猫3", "猫4", "猫5", "猫6", "猫7", "猫8"] 12 | SKILL_LIST = ["s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8"] 13 | 14 | 15 | # 提示词打印 16 | # 打印种类列表 17 | def print_type_list() -> str: 18 | result = "" 19 | for type in TYPE_LIST: 20 | result += f'"{type}", ' 21 | result = result[:-2] 22 | return f"({result})" 23 | 24 | 25 | # 打印技能列表 26 | def print_skill_list() -> str: 27 | result = "" 28 | for skill in SKILL_LIST: 29 | result += f'"{skill}", ' 30 | result = result[:-2] 31 | return f"({result})" 32 | 33 | 34 | # 127位值 - 100%快速转换 35 | def value_output(num: int) -> str: 36 | value = int(num / 1.27) 37 | return str(value) 38 | 39 | 40 | # 打印状态 41 | def print_info(token: str) -> str: 42 | data = token_to_dict(token) 43 | return ( 44 | "状态信息: " 45 | f'\n\t名字 : {data["name"]}' 46 | f'\n\t种类 : {TYPE_LIST[data["type"]]}' 47 | f'\n\t生命值 : {value_output(data["health"])}' 48 | f'\n\t饱食度 : {value_output(data["saturation"])}' 49 | f"\n\t活力值 : {value_output(data['energy'])}" 50 | f"\n\t技能 : {print_skill(token)}" 51 | f"\n新token : {token}" 52 | f"\ntoken已更新, 请妥善保存token, 这是猫猫的唯一标识符!" 53 | ) 54 | 55 | 56 | # 打印已有技能 57 | def print_skill(token: str) -> str: 58 | result = "" 59 | data = token_to_dict(token) 60 | for index in range(0, len(SKILL_LIST) - 1): 61 | if data["skill"][index]: 62 | result += f"{SKILL_LIST[index]}, " 63 | logger.info(data["skill"]) 64 | return result[:-2] 65 | 66 | 67 | # 帮助 68 | # 创建猫猫 69 | def help_cat_new() -> str: 70 | return ( 71 | "新建一只猫猫, 首先选择猫猫的种类, 获取初始化token;" 72 | "然后用这个token, 选择名字和一个技能进行初始化;" 73 | "初始化结束才表示猫猫正式创建成功." 74 | "\ntoken为猫的唯一标识符, 每次交互都需要传入token" 75 | f"\n种类可选 : {print_type_list()}" 76 | f"\n技能可选 : {print_skill_list()}" 77 | ) 78 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins/twisuki_petcat/pc_shop.py: -------------------------------------------------------------------------------- 1 | # 商店 2 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins_test/marshoai_basic/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from zhDateTime import DateTime # type: ignore 4 | 5 | from nonebot_plugin_marshoai.plugin import PluginMetadata, String, on_function_call 6 | 7 | # from .web import * 8 | # 定义插件元数据 9 | __marsho_meta__ = PluginMetadata( 10 | name="基本功能", 11 | author="MarshoAI", 12 | description="这个插件提供基本的功能,比如获取当前时间和日期。", 13 | ) 14 | 15 | 16 | weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] 17 | 18 | 19 | @on_function_call(description="获取当前时间,日期和星期") 20 | async def get_current_time() -> str: 21 | """获取当前的时间和日期""" 22 | current_time = DateTime.now() 23 | 24 | time_prompt = "现在的时间是 {},{},{}。".format( 25 | current_time.strftime("%Y.%m.%d %H:%M:%S"), 26 | weekdays[current_time.weekday()], 27 | current_time.chinesize.date_hanzify("农历{干支年}{生肖}年 {月份}月{数序日}"), 28 | ) 29 | return time_prompt 30 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins_test/marshoai_basic/tools.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "function": { 5 | "name": "marshoai-basic__get_current_time", 6 | "description": "获取现在的日期,时间和星期。" 7 | } 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins_test/marshoai_basic/tools_test.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "function": { 5 | "name": "marshoai-basic__get_weather", 6 | "description": "当你想查询指定城市的天气时非常有用。", 7 | "parameters": { 8 | "type": "object", 9 | "properties": { 10 | "location": { 11 | "type": "string", 12 | "description": "城市或县区,比如北京市、杭州市、余杭区等。" 13 | } 14 | } 15 | }, 16 | "required": [ 17 | "location" 18 | ] 19 | } 20 | }, 21 | { 22 | "type": "function", 23 | "function": { 24 | "name": "marshoai-basic__get_current_env", 25 | "description": "获取当前的运行环境。", 26 | "parameters": { 27 | } 28 | } 29 | }, 30 | { 31 | "type": "function", 32 | "function": { 33 | "name": "marshoai-basic__get_current_time", 34 | "description": "获取现在的时间。", 35 | "parameters": { 36 | } 37 | } 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins_test/marshoai_memory/__init__.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | from azure.ai.inference.models import UserMessage 5 | from nonebot import get_driver, logger, require 6 | from nonebot_plugin_localstore import get_plugin_data_file 7 | 8 | require("nonebot_plugin_apscheduler") 9 | require("nonebot_plugin_marshoai") 10 | from nonebot_plugin_apscheduler import scheduler 11 | 12 | from nonebot_plugin_marshoai.instances import client 13 | from nonebot_plugin_marshoai.plugin import PluginMetadata, on_function_call 14 | from nonebot_plugin_marshoai.plugin.func_call.params import String 15 | 16 | from .command import * 17 | from .config import plugin_config 18 | 19 | __marsho_meta__ = PluginMetadata( 20 | name="记忆保存", 21 | author="MarshoAI", 22 | description="这个插件可以帮助AI记住一些事情", 23 | ) 24 | 25 | memory_path = get_plugin_data_file("memory.json") 26 | if not Path(memory_path).exists(): 27 | with open(memory_path, "w", encoding="utf-8") as f: 28 | json.dump({}, f, ensure_ascii=False, indent=4) 29 | # print(memory_path) 30 | driver = get_driver() 31 | 32 | 33 | @on_function_call( 34 | description="当你发现与你对话的用户的一些信息值得你记忆,或者用户让你记忆等时,调用此函数存储记忆内容" 35 | ).params( 36 | memory=String(description="你想记住的内容,概括并保留关键内容"), 37 | user_id=String(description="你想记住的人的id"), 38 | ) 39 | async def write_memory(memory: str, user_id: str): 40 | 41 | with open(memory_path, "r", encoding="utf-8") as f: 42 | memory_data = json.load(f) 43 | 44 | memorys = memory_data.get(user_id, []) 45 | memorys.append(memory) 46 | memory_data[user_id] = memorys 47 | 48 | with open(memory_path, "w", encoding="utf-8") as f: 49 | json.dump(memory_data, f, ensure_ascii=False, indent=4) 50 | 51 | return "记忆已经保存啦~" 52 | 53 | 54 | @on_function_call( 55 | description="你需要回忆有关用户的一些知识时,调用此函数读取记忆内容,当用户问问题的时候也尽量调用此函数参考" 56 | ).params(user_id=String(description="你想读取记忆的人的id")) 57 | async def read_memory(user_id: str): 58 | with open(memory_path, "r", encoding="utf-8") as f: 59 | memory_data = json.load(f) 60 | memorys = memory_data.get(user_id, []) 61 | if not memorys: 62 | return "好像对ta还没有任何记忆呢~" 63 | 64 | return "这些是有关ta的记忆:" + "\n".join(memorys) 65 | 66 | 67 | async def organize_memories(): 68 | with open(memory_path, "r", encoding="utf-8") as f: 69 | memory_data = json.load(f) 70 | for i in memory_data: 71 | memory_data_ = "\n".join(memory_data[i]) 72 | msg = f"这是一些大模型记忆信息,请你保留重要内容,尽量减少无用的记忆后重新输出记忆内容,浓缩为一行:\n{memory_data_}" 73 | res = await client.complete(UserMessage(content=msg)) 74 | try: 75 | memory = res.choices[0].message.content # type: ignore 76 | memory_data[i] = memory 77 | except AttributeError: 78 | logger.error(f"整理关于{i}的记忆时出错:{res}") 79 | 80 | with open(memory_path, "w", encoding="utf-8") as f: 81 | json.dump(memory_data, f, ensure_ascii=False, indent=4) 82 | 83 | 84 | if plugin_config.marshoai_plugin_memory_scheduler: 85 | 86 | @driver.on_startup 87 | async def _(): 88 | logger.info("小棉定时记忆整理已启动!") 89 | scheduler.add_job( 90 | organize_memories, 91 | "cron", 92 | hour="0", 93 | minute="0", 94 | second="0", 95 | day="*", 96 | id="organize_memories", 97 | ) 98 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins_test/marshoai_memory/command.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from arclet.alconna import Alconna, Args, Subcommand 4 | from nonebot import logger 5 | from nonebot.adapters import Bot, Event 6 | from nonebot.matcher import Matcher 7 | from nonebot.typing import T_State 8 | from nonebot_plugin_alconna import on_alconna 9 | from nonebot_plugin_localstore import get_plugin_data_file 10 | 11 | from nonebot_plugin_marshoai.config import config 12 | 13 | marsho_memory_cmd = on_alconna( 14 | Alconna( 15 | f"{config.marshoai_default_name}.memory", 16 | Subcommand("view", alias={"v"}), 17 | Subcommand("reset", alias={"r"}), 18 | ), 19 | priority=96, 20 | block=True, 21 | ) 22 | 23 | memory_path = get_plugin_data_file("memory.json") 24 | 25 | 26 | @marsho_memory_cmd.assign("view") 27 | async def view_memory(matcher: Matcher, state: T_State, event: Event): 28 | user_id = str(event.get_user_id()) 29 | with open(memory_path, "r", encoding="utf-8") as f: 30 | memory_data = json.load(f) 31 | memorys = memory_data.get(user_id, []) 32 | if not memorys: 33 | await matcher.finish("好像对ta还没有任何记忆呢~") 34 | await matcher.finish("这些是有关ta的记忆:" + "\n".join(memorys)) 35 | 36 | 37 | @marsho_memory_cmd.assign("reset") 38 | async def reset_memory(matcher: Matcher, state: T_State, event: Event): 39 | user_id = str(event.get_user_id()) 40 | with open(memory_path, "r", encoding="utf-8") as f: 41 | memory_data = json.load(f) 42 | if user_id in memory_data: 43 | del memory_data[user_id] 44 | with open(memory_path, "w", encoding="utf-8") as f: 45 | json.dump(memory_data, f, ensure_ascii=False, indent=4) 46 | await matcher.finish("记忆已重置~") 47 | await matcher.finish("没有找到该用户的记忆~") 48 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins_test/marshoai_memory/config.py: -------------------------------------------------------------------------------- 1 | from nonebot import get_plugin_config, logger 2 | from pydantic import BaseModel 3 | 4 | 5 | class ConfigModel(BaseModel): 6 | marshoai_plugin_memory_scheduler: bool = True 7 | 8 | 9 | plugin_config: ConfigModel = get_plugin_config(ConfigModel) 10 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins_test/marshoai_memory/tools.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "function": { 5 | "name": "marshoai_memory__write_memory", 6 | "description": "如果在上下中你看见并觉得应该记住的人的行为与事件,请调用这个函数,并将记忆内容写入。请尽量每次都调用,总结ta的习惯、爱好和性格,以及你对ta的印象和ta对你的印象;比如用户喜欢干什么吃什么。" 7 | } 8 | }, 9 | { 10 | "type": "function", 11 | "function": { 12 | "name": "marshoai_memory__read_memory", 13 | "description": "每当你想要获取更多有关某人的信息的时候,请调用这个函数。" 14 | } 15 | } 16 | ] 17 | 18 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins_test/random_number_generator.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from nonebot_plugin_marshoai.plugin import Integer, PluginMetadata, on_function_call 4 | 5 | __marsho_meta__ = PluginMetadata( 6 | name="随机数生成器", author="MarshoAI", description="生成指定数量的随机数" 7 | ) 8 | 9 | 10 | @on_function_call(description="生成随机数").params( 11 | count=Integer(description="随机数的数量") 12 | ) 13 | async def generate_random_numbers(count: int) -> str: 14 | random_numbers = [random.randint(1, 100) for _ in range(count)] 15 | return f"生成的随机数为: {', '.join(map(str, random_numbers))}" 16 | 17 | 18 | # 该插件由MarshoAI自举编写 19 | 20 | 21 | @on_function_call(description="重载测试") 22 | def test_reload(): 23 | return 1 24 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins_test/snowykami_testplugin/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | 4 | import psutil 5 | from nonebot.adapters import Bot, Event 6 | 7 | # from nonebot.adapters.onebot.v11 import MessageEvent 8 | from nonebot.permission import SUPERUSER 9 | 10 | from nonebot_plugin_marshoai.plugin import ( 11 | Integer, 12 | Parameter, 13 | PluginMetadata, 14 | String, 15 | on_function_call, 16 | ) 17 | from nonebot_plugin_marshoai.plugin.func_call.caller import Caller 18 | 19 | __marsho_meta__ = PluginMetadata( 20 | name="SnowyKami 测试插件", 21 | description="A test plugin for SnowyKami", 22 | usage="SnowyKami Test Plugin", 23 | ) 24 | 25 | 26 | @on_function_call(description="使用姓名,年龄,性别进行算命").params( 27 | age=Integer(description="年龄"), 28 | name=String(description="姓名"), 29 | gender=String(enum=["男", "女"], description="性别"), 30 | ) 31 | async def fortune_telling(age: int, name: str, gender: str) -> str: 32 | """使用姓名,年龄,性别进行算命""" 33 | 34 | # 进行一系列算命操作... 35 | 36 | return f"{name},你的年龄是{age},你的性别很好" 37 | 38 | 39 | @on_function_call(description="获取一个地点未来一段时间的天气").params( 40 | location=String(description="地点名称,可以是城市名、地区名等"), 41 | days=Integer(description="天数", minimum=1, maximum=30), 42 | unit=String(enum=["摄氏度", "华氏度"], description="温度单位", default="摄氏度"), 43 | ) 44 | async def get_weather(location: str, days: int, unit: str) -> str: 45 | """获取一个地点未来一段时间的天气""" 46 | 47 | # 进行一系列获取天气操作... 48 | 49 | return f"{location}未来{days}天的天气很好,全都是晴天,温度是34" 50 | 51 | 52 | @on_function_call(description="获取设备物理地理位置") 53 | def get_location() -> str: 54 | """获取设备物理地理位置""" 55 | 56 | # 进行一系列获取地理位置操作... 57 | 58 | return "日本 东京都 世田谷区" 59 | 60 | 61 | @on_function_call(description="获取聊天者个人信息及发送的消息和function call调用参数") 62 | async def get_user_info(e: Event, c: Caller) -> str: 63 | return ( 64 | f"用户ID: {e.user_id} " 65 | "用户昵称: {e.sender.nickname} " 66 | "FC调用参数:{c._parameters} " 67 | "消息内容: {e.raw_message}" 68 | ) 69 | 70 | 71 | @on_function_call(description="获取设备信息") 72 | def get_device_info() -> str: 73 | """获取机器人所运行的设备信息""" 74 | 75 | # 进行一系列获取设备信息操作... 76 | 77 | data = { 78 | "cpu 性能": f"{psutil.cpu_percent()}% {psutil.cpu_freq().current:.2f}MHz {psutil.cpu_count()}线程 {psutil.cpu_count(logical=False)}物理核", 79 | "memory 内存": f"{psutil.virtual_memory().percent}% {psutil.virtual_memory().available / 1024 / 1024 / 1024:.2f}/{psutil.virtual_memory().total / 1024 / 1024 / 1024:.2f}GB", 80 | "swap 交换分区": f"{psutil.swap_memory().percent}% {psutil.swap_memory().used / 1024 / 1024 / 1024:.2f}/{psutil.swap_memory().total / 1024 / 1024 / 1024:.2f}GB", 81 | "cpu 信息": f"{psutil.cpu_stats()}", 82 | "system 系统": f"system: {platform.system()}, version: {platform.version()}, arch: {platform.architecture()}, machine: {platform.machine()}", 83 | } 84 | return str(data) 85 | 86 | 87 | @on_function_call(description="在设备上运行Python代码,需要超级用户权限").params( 88 | code=String(description="Python代码内容") 89 | ).permission(SUPERUSER) 90 | async def run_python_code(code: str, b: Bot, e: Event) -> str: 91 | """运行Python代码""" 92 | try: 93 | r = eval(code) 94 | except Exception as e: 95 | return "运行出错: " + str(e) 96 | return "运行成功: " + str(r) 97 | 98 | 99 | @on_function_call( 100 | description="在设备上运行shell命令, Run command on this device" 101 | ).params(command=String(description="shell命令内容")).permission(SUPERUSER) 102 | async def run_shell_command(command: str, b: Bot, e: Event) -> str: 103 | """运行shell命令""" 104 | try: 105 | r = os.popen(command).read() 106 | except Exception as e: 107 | return "运行出错: " + str(e) 108 | return "运行成功: " + str(r) 109 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/plugins_test/weather_demo.py: -------------------------------------------------------------------------------- 1 | from nonebot_plugin_marshoai.plugin import PluginMetadata, String, on_function_call 2 | 3 | metadata = PluginMetadata( 4 | name="天气查询", author="MarshoAI", description="一个简单的查询天气的插件" 5 | ) 6 | 7 | 8 | @on_function_call(description="可以用于查询天气").params( 9 | location=String(description="地点") 10 | ) 11 | async def weather(location: str) -> str: 12 | # 这里可以调用天气API查询天气,这里只是一个简单的示例 13 | return f"{location}的天气是晴天, 温度是25°C" 14 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_bangumi/__init__.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | import httpx 4 | 5 | 6 | async def fetch_calendar(): 7 | url = "https://api.bgm.tv/calendar" 8 | headers = { 9 | "User-Agent": "LiteyukiStudio/nonebot-plugin-marshoai (https://github.com/LiteyukiStudio/nonebot-plugin-marshoai)" 10 | } 11 | async with httpx.AsyncClient() as client: 12 | response = await client.get(url, headers=headers) 13 | # print(response.text) 14 | return response.json() 15 | 16 | 17 | async def get_bangumi_news(): 18 | result = await fetch_calendar() 19 | info = "" 20 | try: 21 | for i in result: 22 | weekday = i["weekday"]["cn"] 23 | # print(weekday) 24 | info += f"{weekday}:" 25 | items = i["items"] 26 | for item in items: 27 | name = item["name_cn"] 28 | info += f"《{name}》" 29 | info += "\n" 30 | return info 31 | except Exception as e: 32 | traceback.print_exc() 33 | return "" 34 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_bangumi/tools.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "function": { 5 | "name": "marshoai_bangumi__get_bangumi_news", 6 | "description": "获取今天的新番(动漫)列表,在调用之前,你需要知道今天星期几。" 7 | } 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_basic/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from zhDateTime import DateTime 4 | 5 | weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] 6 | time_prompt = "现在的时间是{date_time},{weekday_name},农历{lunar_date}。" 7 | 8 | 9 | async def get_weather(location: str): 10 | return f"{location}的温度是114514℃。" 11 | 12 | 13 | async def get_current_env(): 14 | ver = os.popen("uname -a").read() 15 | return str(ver) 16 | 17 | 18 | async def get_current_time(): 19 | current_time = DateTime.now() 20 | 21 | return time_prompt.format( 22 | date_time=current_time.strftime("%Y年%m月%d日 %H:%M:%S"), 23 | weekday_name=weekdays[current_time.weekday()], 24 | lunar_date=current_time.to_lunar().date_hanzify( 25 | "{干支年}{生肖}年{月份}月{日期}日" 26 | ), 27 | ) 28 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_basic/tools.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "function": { 5 | "name": "marshoai_basic__get_current_time", 6 | "description": "获取现在的日期,时间和星期。" 7 | } 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_basic/tools_test.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "function": { 5 | "name": "marshoai-basic__get_weather", 6 | "description": "当你想查询指定城市的天气时非常有用。", 7 | "parameters": { 8 | "type": "object", 9 | "properties": { 10 | "location": { 11 | "type": "string", 12 | "description": "城市或县区,比如北京市、杭州市、余杭区等。" 13 | } 14 | } 15 | }, 16 | "required": [ 17 | "location" 18 | ] 19 | } 20 | }, 21 | { 22 | "type": "function", 23 | "function": { 24 | "name": "marshoai-basic__get_current_env", 25 | "description": "获取当前的运行环境。", 26 | "parameters": { 27 | } 28 | } 29 | }, 30 | { 31 | "type": "function", 32 | "function": { 33 | "name": "marshoai-basic__get_current_time", 34 | "description": "获取现在的时间。", 35 | "parameters": { 36 | } 37 | } 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_megakits/__init__.py: -------------------------------------------------------------------------------- 1 | from . import mk_common, mk_info, mk_morse_code, mk_nya_code 2 | 3 | 4 | # Twisuki 5 | async def twisuki(): 6 | return str(await mk_info.twisuki()) 7 | 8 | 9 | # MegaKits 10 | async def megakits(): 11 | return str(await mk_info.megakits()) 12 | 13 | 14 | # Random Turntable 15 | async def random_turntable(upper: int, lower: int = 0): 16 | return str(await mk_common.random_turntable(upper, lower)) 17 | 18 | 19 | # Number Calc 20 | async def number_calc(a: str, b: str, op: str): 21 | return str(await mk_common.number_calc(a, b, op)) 22 | 23 | 24 | # MorseCode Encrypt 25 | async def morse_encrypt(msg: str): 26 | return str(await mk_morse_code.morse_encrypt(msg)) 27 | 28 | 29 | # MorseCode Decrypt 30 | async def morse_decrypt(msg: str): 31 | return str(await mk_morse_code.morse_decrypt(msg)) 32 | 33 | 34 | # NyaCode Encrypt 35 | async def nya_encode(msg: str): 36 | return str(await mk_nya_code.nya_encode(msg)) 37 | 38 | 39 | # NyaCode Decrypt 40 | async def nya_decode(msg: str): 41 | return str(await mk_nya_code.nya_decode(msg)) 42 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_megakits/mk_common.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | async def random_turntable(upper: int, lower: int): 5 | """Random Turntable 6 | 7 | Args: 8 | upper (int): _description_ 9 | lower (int): _description_ 10 | 11 | Returns: 12 | _type_: _description_ 13 | """ 14 | return random.randint(lower, upper) 15 | 16 | 17 | async def number_calc(a: str, b: str, op: str) -> str: 18 | """Number Calc 19 | 20 | Args: 21 | a (str): _description_ 22 | b (str): _description_ 23 | op (str): _description_ 24 | 25 | Returns: 26 | str: _description_ 27 | """ 28 | a, b = float(a), float(b) # type: ignore 29 | match op: 30 | case "+": 31 | return str(a + b) # type: ignore 32 | case "-": 33 | return str(a - b) # type: ignore 34 | case "*": 35 | return str(a * b) # type: ignore 36 | case "/": 37 | return str(a / b) # type: ignore 38 | case "**": 39 | return str(a**b) # type: ignore 40 | case "%": 41 | return str(a % b) 42 | case _: 43 | return "未知运算符" 44 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_megakits/mk_info.py: -------------------------------------------------------------------------------- 1 | # Twisuki 2 | async def twisuki(): 3 | return 'Twiuski(苏阳)是megakits插件作者, Github : "https://github.com/Twisuki"' 4 | 5 | 6 | # MegaKits 7 | async def megakits(): 8 | return 'MegaKits插件是一个功能混杂的MarshoAI插件, 由Twisuki(Github : "https://github.com/Twisuki")开发, 插件仓库 : "https://github.com/LiteyukiStudio/marsho-toolsets/tree/main/Twisuki/marshoai-megakits"' 9 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_megakits/mk_morse_code.py: -------------------------------------------------------------------------------- 1 | # MorseCode 2 | MorseEncode = { 3 | "A": ".-", 4 | "B": "-...", 5 | "C": "-.-.", 6 | "D": "-..", 7 | "E": ".", 8 | "F": "..-.", 9 | "G": "--.", 10 | "H": "....", 11 | "I": "..", 12 | "J": ".---", 13 | "K": "-.-", 14 | "L": ".-..", 15 | "M": "--", 16 | "N": "-.", 17 | "O": "---", 18 | "P": ".--.", 19 | "Q": "--.-", 20 | "R": ".-.", 21 | "S": "...", 22 | "T": "-", 23 | "U": "..-", 24 | "V": "...-", 25 | "W": ".--", 26 | "X": "-..-", 27 | "Y": "-.--", 28 | "Z": "--..", 29 | "1": ".----", 30 | "2": "..---", 31 | "3": "...--", 32 | "4": "....-", 33 | "5": ".....", 34 | "6": "-....", 35 | "7": "--...", 36 | "8": "---..", 37 | "9": "----.", 38 | "0": "-----", 39 | ".": ".-.-.-", 40 | ":": "---...", 41 | ",": "--..--", 42 | ";": "-.-.-.", 43 | "?": "..--..", 44 | "=": "-...-", 45 | "'": ".----.", 46 | "/": "-..-.", 47 | "!": "-.-.--", 48 | "-": "-....-", 49 | "_": "..--.-", 50 | '"': ".-..-.", 51 | "(": "-.--.", 52 | ")": "-.--.-", 53 | "$": "...-..-", 54 | "&": "....", 55 | "@": ".--.-.", 56 | " ": " ", 57 | } 58 | MorseDecode = {value: key for key, value in MorseEncode.items()} 59 | 60 | 61 | # MorseCode Encrypt 62 | async def morse_encrypt(msg: str): 63 | result = "" 64 | msg = msg.upper() 65 | for char in msg: 66 | if char in MorseEncode: 67 | result += MorseEncode[char] 68 | else: 69 | result += "..--.." 70 | result += " " 71 | 72 | return result 73 | 74 | 75 | # MorseCode Decrypt 76 | async def morse_decrypt(msg: str): 77 | result = "" 78 | 79 | msg_arr = msg.split() 80 | for char in msg_arr: 81 | if char in MorseDecode: 82 | result += MorseDecode[char] 83 | else: 84 | result += "?" 85 | 86 | return result 87 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_megakits/mk_nya_code.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import random 3 | 4 | # NyaCode 5 | NyaCodeCharset = ["喵", "呜", "?", "~"] 6 | NyaCodeSpecialCharset = ["唔", "!", "...", ".."] 7 | NyaCodeEncode = {} 8 | for i in range(64): 9 | triplet = "".join(NyaCodeCharset[(i // (4**j)) % 4] for j in range(3)) 10 | NyaCodeEncode[ 11 | chr( 12 | 65 + i 13 | if i < 26 14 | else ( 15 | 97 + (i - 26) 16 | if i < 52 17 | else 48 + (i - 52) if i < 62 else (43 if i == 62 else 47) 18 | ) 19 | ) 20 | ] = triplet 21 | NyaCodeDecode = {value: key for key, value in NyaCodeEncode.items()} 22 | 23 | 24 | # NyaCode Encrypt 25 | async def nya_encode(msg: str): 26 | msg_b64str = base64.b64encode(msg.encode()).decode().replace("=", "") 27 | msg_nyastr = "".join(NyaCodeEncode[base64_char] for base64_char in msg_b64str) 28 | result = "" 29 | for char in msg_nyastr: 30 | if char == "呜" and random.random() < 0.5: 31 | result += "!" 32 | 33 | if random.random() < 0.25: 34 | result += random.choice(NyaCodeSpecialCharset) + char 35 | else: 36 | result += char 37 | return result 38 | 39 | 40 | # NyaCode Decrypt 41 | async def nya_decode(msg: str): 42 | msg = msg.replace("唔", "").replace("!", "").replace(".", "") 43 | msg_nyastr = [] 44 | i = 0 45 | if len(msg) % 3 != 0: 46 | return "这句话不是正确的猫语" 47 | while i < len(msg): 48 | nyachar = msg[i : i + 3] 49 | try: 50 | if all(char in NyaCodeCharset for char in nyachar): 51 | msg_nyastr.append(nyachar) 52 | i += 3 53 | except Exception: 54 | return "这句话不是正确的猫语" 55 | msg_b64str = "".join(NyaCodeDecode[nya_char] for nya_char in msg_nyastr) 56 | msg_b64str += "=" * (4 - len(msg_b64str) % 4) 57 | try: 58 | result = base64.b64decode(msg_b64str.encode()).decode() 59 | except Exception: 60 | return "翻译失败" 61 | return result 62 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_megakits/tools.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type" : "function", 4 | "function" : { 5 | "name" : "marshoai_megakits__twisuki", 6 | "description" : "介绍插件作者Twisuki(苏阳)" 7 | } 8 | }, 9 | { 10 | "type" : "function", 11 | "function" : { 12 | "name" : "marshoai_megakits__megakits", 13 | "description" : "介绍本插件MegaKits" 14 | } 15 | }, 16 | { 17 | "type" : "function", 18 | "function" : { 19 | "name" : "marshoai_megakits__random_turntable", 20 | "description" : "随机转盘, 玩家输入上下限(均为整数), 返回一个随机整数", 21 | "parameters" : { 22 | "type" : "object", 23 | "properties" : { 24 | "upper" : { 25 | "type" : "integer", 26 | "description" : "随机数上限" 27 | }, 28 | "lower" : { 29 | "type" : "integer", 30 | "description" : "随机数下限" 31 | } 32 | } 33 | }, 34 | "require" : [ 35 | "upper" 36 | ] 37 | } 38 | }, 39 | { 40 | "type" : "function", 41 | "function" : { 42 | "name" : "marshoai_megakits__number_calc", 43 | "description" : "数字计算器, 可对整数 小数做加减乘除, 乘方 取余运算", 44 | "parameters" : { 45 | "type" : "object", 46 | "properties" : { 47 | "a" : { 48 | "type" : "string", 49 | "description" : "第一个运算数" 50 | }, 51 | "b" : { 52 | "type" : "string", 53 | "description" : "第二个运算数" 54 | }, 55 | "op" : { 56 | "type" : "string", 57 | "description" : "运算符, 目前仅支持 + - * / %(取余) **(乘方)" 58 | } 59 | } 60 | }, 61 | "require" : [ 62 | "a", "b", "op" 63 | ] 64 | } 65 | }, 66 | { 67 | "type" : "function", 68 | "function" : { 69 | "name" : "marshoai_megakits__morse_encrypt", 70 | "description" : "摩尔斯电码加密, 输入一个字符串, 返回由.- 组成的摩尔斯电码", 71 | "parameters" : { 72 | "type" : "object", 73 | "properties" : { 74 | "msg" : { 75 | "type" : "string", 76 | "description" : "录入的字符串(包含字母, 数字, 部分标点符号(.:,;?='/!-_\"()$&@))" 77 | } 78 | }, 79 | "require" : [ 80 | "msg" 81 | ] 82 | } 83 | } 84 | }, 85 | { 86 | "type" : "function", 87 | "function" : { 88 | "name" : "marshoai_megakits__morse_decrypt", 89 | "description" : "摩尔斯电码解码, 输入一个字符串(由.- 组成), 返回由解码", 90 | "parameters" : { 91 | "type" : "object", 92 | "properties" : { 93 | "msg" : { 94 | "type" : "string", 95 | "description" : "录入的字符串(.- )" 96 | } 97 | }, 98 | "require" : [ 99 | "msg" 100 | ] 101 | } 102 | } 103 | }, 104 | { 105 | "type" : "function", 106 | "function" : { 107 | "name" : "marshoai_megakits__nya_encode", 108 | "description" : "转换为猫猫语", 109 | "parameters" : { 110 | "type" : "object", 111 | "properties" : { 112 | "msg" : { 113 | "type" : "string", 114 | "description" : "待转换的字符串" 115 | } 116 | }, 117 | "require" : [ 118 | "msg" 119 | ] 120 | } 121 | } 122 | }, 123 | { 124 | "type": "function", 125 | "function": { 126 | "name": "marshoai_megakits__nya_decode", 127 | "description": "翻译猫语", 128 | "parameters": { 129 | "type": "object", 130 | "properties": { 131 | "msg": { 132 | "type": "string", 133 | "description": "录入的猫语" 134 | } 135 | }, 136 | "require": [ 137 | "msg" 138 | ] 139 | } 140 | } 141 | } 142 | ] 143 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_memory/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from nonebot import require 4 | 5 | require("nonebot_plugin_localstore") 6 | import json 7 | 8 | from nonebot_plugin_localstore import get_data_file 9 | 10 | memory_path = get_data_file("marshoai", "memory.json") 11 | if not Path(memory_path).exists(): 12 | with open(memory_path, "w", encoding="utf-8") as f: 13 | json.dump({}, f, ensure_ascii=False, indent=4) 14 | print(memory_path) 15 | 16 | 17 | async def write_memory(memory: str, user_id: str): 18 | 19 | with open(memory_path, "r", encoding="utf-8") as f: 20 | memory_data = json.load(f) 21 | 22 | memorys = memory_data.get(user_id, []) 23 | memorys.append(memory) 24 | memory_data[user_id] = memorys 25 | 26 | with open(memory_path, "w", encoding="utf-8") as f: 27 | json.dump(memory_data, f, ensure_ascii=False, indent=4) 28 | 29 | return "记忆已经保存啦~" 30 | 31 | 32 | async def read_memory(user_id: str): 33 | with open(memory_path, "r", encoding="utf-8") as f: 34 | memory_data = json.load(f) 35 | memorys = memory_data.get(user_id, []) 36 | if not memorys: 37 | return "好像对ta还没有任何记忆呢~" 38 | 39 | return "这些是有关ta的记忆:" + "\n".join(memorys) 40 | 41 | 42 | async def organize_memories(): 43 | with open(memory_path, "r", encoding="utf-8") as f: 44 | memory_data = json.load(f) 45 | for i in memory_data: 46 | ... 47 | # TODO 用大模型对记忆进行整理 48 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_memory/tools.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "function": { 5 | "name": "marshoai_memory__write_memory", 6 | "description": "如果在上下中你看见并觉得应该记住的人的行为与事件,请调用这个函数,并将记忆内容写入。请尽量每次都调用,总结ta的习惯、爱好和性格,以及你对ta的印象和ta对你的印象", 7 | "parameters": { 8 | "type": "object", 9 | "properties": { 10 | "memory": { 11 | "type": "string", 12 | "description": "你想记住的内容,概括并保留关键内容。" 13 | }, 14 | "user_id": { 15 | "type": "string", 16 | "description": "你想记住的人的id。" 17 | } 18 | } 19 | }, 20 | "required": [ 21 | "memory", 22 | "user_id" 23 | ] 24 | } 25 | }, 26 | { 27 | "type": "function", 28 | "function": { 29 | "name": "marshoai_memory__read_memory", 30 | "description": "每当你想要获取更多有关某人的信息的时候,请调用这个函数。", 31 | "parameters": { 32 | "type": "object", 33 | "properties": { 34 | "user_id": { 35 | "type": "string", 36 | "description": "你想获取的人的id。" 37 | } 38 | } 39 | }, 40 | "required": [ 41 | "user_id" 42 | ] 43 | } 44 | } 45 | ] 46 | 47 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_meogirl/__init__.py: -------------------------------------------------------------------------------- 1 | from . import mg_info, mg_introduce, mg_search 2 | 3 | 4 | # meogirl 5 | async def meogirl(): 6 | return mg_info.meogirl() 7 | 8 | 9 | # Search 10 | async def search(msg: str, num: int = 3): 11 | return str(await mg_search.search(msg, num)) 12 | 13 | 14 | # Show 15 | async def introduce(msg: str): 16 | return str(await mg_introduce.introduce(msg)) 17 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_meogirl/mg_info.py: -------------------------------------------------------------------------------- 1 | # Meogirl 2 | def meogirl(): 3 | return 'Meogirl指的是"萌娘百科"(https://zh.moegirl.org.cn/ , 简称"萌百"), 是一个"万物皆可萌的百科全书!"; 同时, MarshoTools也配有"Meogirl"插件, 可调用萌百的api' 4 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_meogirl/mg_introduce.py: -------------------------------------------------------------------------------- 1 | import re 2 | import urllib.parse 3 | 4 | import httpx 5 | from bs4 import BeautifulSoup # type: ignore 6 | from nonebot.log import logger 7 | 8 | headers = { 9 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" 10 | } 11 | 12 | 13 | async def get_async_data(url): 14 | async with httpx.AsyncClient(timeout=None) as client: 15 | return await client.get(url, headers=headers) 16 | 17 | 18 | async def introduce(msg: str): 19 | logger.info(f'介绍 : "{msg}" ...') 20 | result = "" 21 | 22 | url = "https://mzh.moegirl.org.cn/" + urllib.parse.quote_plus(msg) 23 | response = await get_async_data(url) 24 | logger.success(f'连接"{url}"完成, 状态码 : {response.status_code}') 25 | 26 | soup = BeautifulSoup(response.text, "html.parser") 27 | 28 | # 正常页 29 | if response.status_code == 200: 30 | """ 31 | 萌娘百科页面结构 32 | div#mw-content-text 33 | └── div#404search # 空白页面出现 34 | └── div.mw-parser-output # 正常页面 35 | └── div, p, table ... # 大量的解释项 36 | """ 37 | result += msg + "\n" 38 | 39 | img = soup.find("img", class_="infobox-image") 40 | if img: 41 | result += f"![ {msg} ]( {img['src']} ) \n" 42 | 43 | div = soup.find("div", class_="mw-parser-output") 44 | if div: 45 | p_tags = div.find_all("p") 46 | num = 0 47 | for p_tag in p_tags: 48 | p = str(p_tag) 49 | p = re.sub( 50 | r"|", "", p, flags=re.DOTALL 51 | ) 52 | p = re.sub(r"<.*?>", "", p, flags=re.DOTALL) 53 | p = re.sub(r"\[.*?]", "", p, flags=re.DOTALL) 54 | 55 | if p != "": 56 | result += str(p) 57 | 58 | num += 1 59 | if num >= 20: 60 | break 61 | return result 62 | 63 | # 空白页 64 | elif response.status_code == 404: 65 | logger.info(f'未找到"{msg}", 进行搜索') 66 | 67 | from . import mg_search 68 | 69 | context = await mg_search.search(msg, 1) 70 | keyword = re.search(r".*?\n", context, flags=re.DOTALL).group()[:-1] # type: ignore 71 | 72 | logger.success(f'搜索完成, 打开"{keyword}"') 73 | return await introduce(keyword) 74 | 75 | # 搜索失败 76 | elif response.status_code == 301: 77 | return f"未找到{msg}" 78 | 79 | else: 80 | logger.error(f"网络错误, 状态码 : {response.status_code}") 81 | return f"网络错误, 状态码 : {response.status_code}" 82 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_meogirl/mg_search.py: -------------------------------------------------------------------------------- 1 | import urllib.parse 2 | 3 | import httpx 4 | from bs4 import BeautifulSoup # type: ignore 5 | from nonebot.log import logger 6 | 7 | headers = { 8 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" 9 | } 10 | 11 | 12 | async def get_async_data(url): 13 | async with httpx.AsyncClient(timeout=None) as client: 14 | return await client.get(url, headers=headers) 15 | 16 | 17 | async def search(msg: str, num: int): 18 | logger.info(f'搜索 : "{msg}" ...') 19 | result = "" 20 | 21 | url = "https://mzh.moegirl.org.cn/index.php?search=" + urllib.parse.quote_plus(msg) 22 | response = await get_async_data(url) 23 | logger.success(f'连接"{url}"完成, 状态码 : {response.status_code}') 24 | 25 | # 正常搜索 26 | if response.status_code == 200: 27 | """ 28 | 萌娘百科搜索页面结构 29 | div.searchresults 30 | └── p ... 31 | └── ul.mw-search-results # 若无, 证明无搜索结果 32 | └── li # 一个搜索结果 33 | └── div.mw-search-result-heading > a # 标题 34 | └── div.mw-searchresult # 内容 35 | └── div.mw-search-result-data 36 | └── li ... 37 | └── li ... 38 | """ 39 | soup = BeautifulSoup(response.text, "html.parser") 40 | 41 | # 检测ul.mw-search-results, 是否有结果 42 | ul_tag = soup.find("ul", class_="mw-search-results") 43 | if ul_tag: 44 | li_tags = ul_tag.find_all("li") 45 | for li_tag in li_tags: 46 | 47 | div_heading = li_tag.find("div", class_="mw-search-result-heading") 48 | if div_heading: 49 | a_tag = div_heading.find("a") 50 | result += a_tag["title"] + "\n" 51 | logger.info(f'搜索到 : "{a_tag["title"]}"') 52 | 53 | div_result = li_tag.find("div", class_="searchresult") 54 | if div_result: 55 | content = ( 56 | str(div_result) 57 | .replace('
', "") 58 | .replace("
", "") 59 | ) 60 | content = content.replace('', "").replace( 61 | "", "" 62 | ) 63 | result += content + "\n" 64 | 65 | num -= 1 66 | if num == 0: 67 | break 68 | return result 69 | 70 | # 无ul.mw-search-results, 无结果 71 | else: 72 | logger.info("无结果") 73 | return "无结果" 74 | 75 | # 重定向 76 | elif response.status_code == 302: 77 | logger.info(f'"{msg}"已被重定向至"{response.headers.get("location")}"') 78 | # 读取重定向结果 79 | from . import mg_introduce 80 | 81 | return await mg_introduce.introduce(msg) 82 | 83 | else: 84 | logger.error(f"网络错误, 状态码 : {response.status_code}") 85 | return f"网络错误, 状态码 : {response.status_code}" 86 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/tools/marshoai_meogirl/tools.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type" : "function", 4 | "function" : { 5 | "name" : "marshoai_meogirl__meogirl", 6 | "description" : "介绍Meogirl" 7 | } 8 | }, 9 | { 10 | "type": "function", 11 | "function": { 12 | "name": "marshoai_meogirl__search", 13 | "description": "查找/搜索 某角色/事物 (使用萌娘百科)", 14 | "parameters": { 15 | "type": "object", 16 | "properties": { 17 | "msg": { 18 | "type": "string", 19 | "description": "搜索关键词" 20 | }, 21 | "num": { 22 | "type": "integer", 23 | "description": "数据显示条数, 默认3, 可留空" 24 | } 25 | } 26 | }, 27 | "required": [ 28 | "msg" 29 | ] 30 | } 31 | }, 32 | { 33 | "type" : "function", 34 | "function" : { 35 | "name" : "marshoai_meogirl__introduce", 36 | "description" : "介绍/展示 某角色/事物 (使用萌娘百科)", 37 | "parameters" : { 38 | "type" : "object", 39 | "properties" : { 40 | "msg" : { 41 | "type": "string", 42 | "description": "关键词" 43 | } 44 | } 45 | }, 46 | "required": [ 47 | "msg" 48 | ] 49 | } 50 | } 51 | ] 52 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/util_hunyuan.py: -------------------------------------------------------------------------------- 1 | import json 2 | import types 3 | 4 | from tencentcloud.common import credential # type: ignore 5 | from tencentcloud.common.exception.tencent_cloud_sdk_exception import ( # type: ignore 6 | TencentCloudSDKException, 7 | ) 8 | from tencentcloud.common.profile.client_profile import ClientProfile # type: ignore 9 | from tencentcloud.common.profile.http_profile import HttpProfile # type: ignore 10 | from tencentcloud.hunyuan.v20230901 import hunyuan_client # type: ignore 11 | from tencentcloud.hunyuan.v20230901 import models # type: ignore 12 | 13 | from .config import config 14 | 15 | 16 | def generate_image(prompt: str): 17 | cred = credential.Credential( 18 | config.marshoai_tencent_secretid, config.marshoai_tencent_secretkey 19 | ) 20 | # 实例化一个http选项,可选的,没有特殊需求可以跳过 21 | httpProfile = HttpProfile() 22 | httpProfile.endpoint = "hunyuan.tencentcloudapi.com" 23 | 24 | # 实例化一个client选项,可选的,没有特殊需求可以跳过 25 | clientProfile = ClientProfile() 26 | clientProfile.httpProfile = httpProfile 27 | client = hunyuan_client.HunyuanClient(cred, "ap-guangzhou", clientProfile) 28 | 29 | req = models.TextToImageLiteRequest() 30 | params = {"Prompt": prompt, "RspImgType": "url", "Resolution": "1080:1920"} 31 | req.from_json_string(json.dumps(params)) 32 | 33 | # 返回的resp是一个TextToImageLiteResponse的实例,与请求对象对应 34 | resp = client.TextToImageLite(req) 35 | # 输出json格式的字符串回包 36 | return resp.to_json_string() 37 | -------------------------------------------------------------------------------- /nonebot_plugin_marshoai/utils/processor.py: -------------------------------------------------------------------------------- 1 | from nonebot.log import logger 2 | from openai import AsyncStream 3 | from openai.types.chat import ChatCompletion, ChatCompletionChunk, ChatCompletionMessage 4 | from openai.types.chat.chat_completion import Choice 5 | 6 | 7 | async def process_chat_stream( 8 | stream: AsyncStream[ChatCompletionChunk], 9 | ) -> ChatCompletion: 10 | reasoning_contents = "" 11 | answer_contents = "" 12 | last_chunk = None 13 | is_first_token_appeared = False 14 | is_answering = False 15 | async for chunk in stream: 16 | last_chunk = chunk 17 | # print(chunk) 18 | if not is_first_token_appeared: 19 | logger.info(f"{chunk.id}: 第一个 token 已出现") 20 | is_first_token_appeared = True 21 | if not chunk.choices: 22 | logger.info("Usage:", chunk.usage) 23 | else: 24 | delta = chunk.choices[0].delta 25 | if ( 26 | hasattr(delta, "reasoning_content") 27 | and delta.reasoning_content is not None 28 | ): 29 | reasoning_contents += delta.reasoning_content 30 | else: 31 | if not is_answering: 32 | logger.info( 33 | f"{chunk.id}: 思维链已输出完毕或无 reasoning_content 字段输出" 34 | ) 35 | is_answering = True 36 | if delta.content is not None: 37 | answer_contents += delta.content 38 | # print(last_chunk) 39 | # 创建新的 ChatCompletion 对象 40 | if last_chunk and last_chunk.choices: 41 | message = ChatCompletionMessage( 42 | content=answer_contents, 43 | role="assistant", 44 | tool_calls=last_chunk.choices[0].delta.tool_calls, # type: ignore 45 | ) 46 | if reasoning_contents != "": 47 | setattr(message, "reasoning_content", reasoning_contents) 48 | choice = Choice( 49 | finish_reason=last_chunk.choices[0].finish_reason, # type: ignore 50 | index=last_chunk.choices[0].index, 51 | message=message, 52 | ) 53 | return ChatCompletion( 54 | id=last_chunk.id, 55 | choices=[choice], 56 | created=last_chunk.created, 57 | model=last_chunk.model, 58 | system_fingerprint=last_chunk.system_fingerprint, 59 | object="chat.completion", 60 | usage=last_chunk.usage, 61 | ) 62 | else: 63 | return ChatCompletion( 64 | id="", 65 | choices=[], 66 | created=0, 67 | model="", 68 | system_fingerprint="", 69 | object="chat.completion", 70 | usage=None, 71 | ) 72 | 73 | 74 | async def process_completion_to_details(completion: ChatCompletion) -> str: 75 | usage_text = "" 76 | usage = completion.usage 77 | if usage is None: 78 | usage_text = "无" 79 | else: 80 | usage_text = str(usage) 81 | 82 | details_text = f"""=========消息详情========= 83 | 模型: {completion.model} 84 | 消息 ID: {completion.id} 85 | 用量信息: {usage_text}""" 86 | # print(details_text) 87 | return details_text 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "devDependencies": {"vitepress": "^1.5.0", "vitepress-sidebar": "^1.30.2"}, 4 | "scripts": { 5 | "docs:dev": "vitepress dev docs --host", 6 | "docs:build": "vitepress build docs", 7 | "docs:preview": "vitepress preview docs" 8 | }, 9 | "dependencies": {"vue": "^3.5.13"} 10 | } 11 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "nonebot-plugin-marshoai" 3 | dynamic = ["version"] 4 | description = "Nonebot2插件,调用Azure OpenAI等AI服务实现猫娘聊天" 5 | readme = "README.md" 6 | requires-python = "<4.0,>=3.10" 7 | authors = [ 8 | { name = "Asankilp", email = "asankilp@outlook.com" }, 9 | { name="LiteyukiStudio", email = "support@liteyuki.icu"} 10 | ] 11 | dependencies = [ 12 | "nonebot2>=2.4.0", 13 | "nonebot-plugin-alconna>=0.57.1", 14 | "nonebot-plugin-localstore>=0.7.1", 15 | "zhDatetime>=2.0.0", 16 | "aiohttp>=3.9", 17 | "httpx>=0.27.0", 18 | "ruamel.yaml>=0.18.6", 19 | "pyyaml>=6.0.2", 20 | "psutil>=6.1.0", 21 | "beautifulsoup4>=4.12.3", 22 | "pydantic>=2.10.3", 23 | "litedoc>=0.1.0.dev20241214103915", 24 | "newspaper3k>=0.2.8", 25 | "lxml[html_clean]>=5.3.0", 26 | "aiofiles>=24.1.0", 27 | "sumy>=0.11.0", 28 | "azure-ai-inference>=1.0.0b6", 29 | "watchdog>=6.0.0", 30 | "nonebot-plugin-apscheduler>=0.5.0", 31 | "openai>=1.58.1", 32 | "nonebot-plugin-argot>=0.1.7" 33 | 34 | ] 35 | license = { text = "MIT, Mulan PSL v2" } 36 | 37 | [project.urls] 38 | Homepage = "https://marsho.liteyuki.org/" 39 | 40 | 41 | [tool.nonebot] 42 | plugins = ["nonebot_plugin_marshoai"] 43 | # 测试用 44 | adapters = [ 45 | { name = "OneBot V11", module_name = "nonebot.adapters.onebot.v11" }, 46 | ] 47 | 48 | [tool.pdm] 49 | distribution = true 50 | 51 | [tool.isort] 52 | profile = "black" 53 | 54 | 55 | [tool.pdm.version] 56 | source = "scm" 57 | tag_filter = "v*" 58 | tag_regex = '^v(?:\D*)?(?P([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|c|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$)$' 59 | fallback_version = "0.1.0" 60 | 61 | [tool.pdm.build] 62 | includes = [] 63 | 64 | [build-system] 65 | requires = ["pdm-backend"] 66 | build-backend = "pdm.backend" 67 | 68 | [dependency-groups] 69 | dev = [ 70 | "nb-cli>=1.4.2", 71 | "pytest>=8.3.4", 72 | "pre-commit>=4.0.1", 73 | "nonebot-adapter-onebot>=2.4.6", 74 | "mypy>=1.13.0", 75 | "black>=24.10.0", 76 | "litedoc>=0.1.0.dev20240906203154", 77 | "viztracer>=1.0.0", 78 | "types-aiofiles" 79 | ] 80 | test = [ 81 | "nonebug>=0.4.3", 82 | ] 83 | 84 | [tool.ruff.lint] 85 | ignore = ["E402", "F405"] 86 | -------------------------------------------------------------------------------- /resources/README.md: -------------------------------------------------------------------------------- 1 | # Marsho Resources 2 | 3 | > Copyright (c) 2025 [@Asankilp](https://github.com/Asankilp) 4 | 5 | 本目录存放 Marsho 的图像资源(logo, icon),均由[Asankilp](https://github.com/Asankilp)绘制。\ 6 | 上述所有资源均在[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)许可下提供。 7 | -------------------------------------------------------------------------------- /resources/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/resources/bg.png -------------------------------------------------------------------------------- /resources/marsho-640x360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/resources/marsho-640x360.png -------------------------------------------------------------------------------- /resources/marsho-bg-1x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/resources/marsho-bg-1x1.png -------------------------------------------------------------------------------- /resources/marsho-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/resources/marsho-bg.png -------------------------------------------------------------------------------- /resources/marsho-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/resources/marsho-icon.png -------------------------------------------------------------------------------- /resources/marsho-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/resources/marsho-new.png -------------------------------------------------------------------------------- /resources/marsho-no-paw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/resources/marsho-no-paw.png -------------------------------------------------------------------------------- /resources/marsho-paw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/resources/marsho-paw.png -------------------------------------------------------------------------------- /resources/marsho.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/a18d85d45c4b4cd8ff5d5484304e46726bf09907/resources/marsho.png -------------------------------------------------------------------------------- /tests/test_example.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | def test_none(): 5 | """基准测试示例""" 6 | logging.info("测试成功") 7 | pass 8 | --------------------------------------------------------------------------------