├── .github ├── dependabot.yml └── workflows │ ├── pypi-publish.yml │ └── ruff.yml ├── .gitmodules ├── .idea ├── .gitignore ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── nonebot-plugin-osubot.iml └── vcs.xml ├── .pdm-python ├── .pre-commit-config.yaml ├── License ├── README.md ├── nonebot_plugin_osubot ├── __init__.py ├── api.py ├── beatmap_stats_moder.py ├── config.py ├── data │ └── osu │ │ ├── 1849145.osz │ │ └── 1849145.zip ├── database │ ├── __init__.py │ └── models.py ├── draw │ ├── __init__.py │ ├── bmap.py │ ├── bp.py │ ├── catch_preview.py │ ├── catch_preview_templates │ │ ├── css │ │ │ └── style.css │ │ ├── js │ │ │ ├── beatmap │ │ │ │ ├── beatmap.js │ │ │ │ ├── hitobject.js │ │ │ │ ├── point.js │ │ │ │ ├── scroll.js │ │ │ │ └── timingpoint.js │ │ │ ├── catch │ │ │ │ ├── LegacyRandom.js │ │ │ │ ├── PalpableCatchHitObject.js │ │ │ │ ├── bananashower.js │ │ │ │ ├── catch.js │ │ │ │ ├── fruit.js │ │ │ │ └── juicestream.js │ │ │ ├── fakeaudio.js │ │ │ ├── preview.js │ │ │ ├── standard │ │ │ │ ├── curve │ │ │ │ │ ├── bezier2.js │ │ │ │ │ ├── catmullcurve.js │ │ │ │ │ ├── centripetalcatmullrom.js │ │ │ │ │ ├── circumstancedcircle.js │ │ │ │ │ ├── curve.js │ │ │ │ │ ├── curvetype.js │ │ │ │ │ ├── equaldistancemulticurve.js │ │ │ │ │ └── linearbezier.js │ │ │ │ ├── hitcircle.js │ │ │ │ ├── slider.js │ │ │ │ └── spinner.js │ │ │ ├── util.js │ │ │ ├── viewbox.js │ │ │ └── zip.min.js │ │ └── pic.html │ ├── echarts.py │ ├── info.py │ ├── map.py │ ├── match_history.py │ ├── rating.py │ ├── score.py │ ├── static.py │ ├── taiko_preview.py │ ├── templates │ │ ├── bpa_chart.html │ │ ├── echarts.min.js │ │ ├── mod_chart.html │ │ ├── pp_rank_line_chart.html │ │ └── t.html │ └── utils.py ├── exceptions.py ├── file.py ├── info │ ├── __init__.py │ ├── bg.py │ └── bind.py ├── mania │ └── __init__.py ├── matcher │ ├── __init__.py │ ├── bind.py │ ├── bp.py │ ├── bp_analyze.py │ ├── getbg.py │ ├── guess.py │ ├── history.py │ ├── info.py │ ├── map.py │ ├── map_convert.py │ ├── match.py │ ├── medal.py │ ├── mu.py │ ├── osu_help.py │ ├── osudl.py │ ├── pr.py │ ├── preview.py │ ├── rank.py │ ├── rating.py │ ├── recommend.py │ ├── score.py │ ├── update.py │ ├── update_mode.py │ ├── url_match.py │ └── utils.py ├── mods.py ├── network │ ├── __init__.py │ ├── auto_retry.py │ ├── first_response.py │ └── manager.py ├── osufile │ ├── Best Performance.png │ ├── History Score.jpg │ ├── beatmapinfo.png │ ├── convert.jpg │ ├── detail.png │ ├── flags │ │ ├── AD.png │ │ ├── AE.png │ │ ├── AF.png │ │ ├── AG.png │ │ ├── AI.png │ │ ├── AL.png │ │ ├── AM.png │ │ ├── AO.png │ │ ├── AQ.png │ │ ├── AR.png │ │ ├── AS.png │ │ ├── AT.png │ │ ├── AU.png │ │ ├── AW.png │ │ ├── AX.png │ │ ├── AZ.png │ │ ├── BA.png │ │ ├── BB.png │ │ ├── BD.png │ │ ├── BE.png │ │ ├── BF.png │ │ ├── BG.png │ │ ├── BH.png │ │ ├── BI.png │ │ ├── BJ.png │ │ ├── BL.png │ │ ├── BM.png │ │ ├── BN.png │ │ ├── BO.png │ │ ├── BQ (1).png │ │ ├── BQ (2).png │ │ ├── BR.png │ │ ├── BS.png │ │ ├── BT.png │ │ ├── BV.png │ │ ├── BW.png │ │ ├── BY.png │ │ ├── BZ.png │ │ ├── CA.png │ │ ├── CD.png │ │ ├── CF.png │ │ ├── CG.png │ │ ├── CH.png │ │ ├── CI.png │ │ ├── CK.png │ │ ├── CL.png │ │ ├── CM.png │ │ ├── CN.png │ │ ├── CO.png │ │ ├── CR.png │ │ ├── CU.png │ │ ├── CV.png │ │ ├── CX.png │ │ ├── CY.png │ │ ├── CZ.png │ │ ├── DE.png │ │ ├── DJ.png │ │ ├── DK.png │ │ ├── DM.png │ │ ├── DO.png │ │ ├── DZ.png │ │ ├── EC.png │ │ ├── EE.png │ │ ├── EG.png │ │ ├── EH.png │ │ ├── ER.png │ │ ├── ES.png │ │ ├── ET.png │ │ ├── FI.png │ │ ├── FJ.png │ │ ├── FK.png │ │ ├── FO.png │ │ ├── FR.png │ │ ├── GA.png │ │ ├── GB.png │ │ ├── GD.png │ │ ├── GE.png │ │ ├── GF.png │ │ ├── GG.png │ │ ├── GH.png │ │ ├── GI.png │ │ ├── GL.png │ │ ├── GM.png │ │ ├── GN.png │ │ ├── GP.png │ │ ├── GQ.png │ │ ├── GR.png │ │ ├── GS.png │ │ ├── GT.png │ │ ├── GU.png │ │ ├── GW.png │ │ ├── GY.png │ │ ├── HK.png │ │ ├── HM.png │ │ ├── HN.png │ │ ├── HR.png │ │ ├── HT.png │ │ ├── HU.png │ │ ├── ID.png │ │ ├── IE.png │ │ ├── IL.png │ │ ├── IM.png │ │ ├── IN.png │ │ ├── IO.png │ │ ├── IQ.png │ │ ├── IR.png │ │ ├── IS.png │ │ ├── IT.png │ │ ├── JE.png │ │ ├── JM.png │ │ ├── JO.png │ │ ├── JP.png │ │ ├── KE.png │ │ ├── KG.png │ │ ├── KH.png │ │ ├── KI.png │ │ ├── KM.png │ │ ├── KN.png │ │ ├── KP.png │ │ ├── KR.png │ │ ├── KW.png │ │ ├── KY.png │ │ ├── KZ.png │ │ ├── LA.png │ │ ├── LB.png │ │ ├── LC.png │ │ ├── LI.png │ │ ├── LK.png │ │ ├── LR.png │ │ ├── LS.png │ │ ├── LT.png │ │ ├── LU.png │ │ ├── LV.png │ │ ├── LY.png │ │ ├── MA.png │ │ ├── MC.png │ │ ├── MD.png │ │ ├── ME.png │ │ ├── MF.png │ │ ├── MG.png │ │ ├── MH.png │ │ ├── MK.png │ │ ├── ML.png │ │ ├── MM.png │ │ ├── MN.png │ │ ├── MO.png │ │ ├── MP.png │ │ ├── MQ.png │ │ ├── MR.png │ │ ├── MS.png │ │ ├── MT.png │ │ ├── MU.png │ │ ├── MV.png │ │ ├── MW.png │ │ ├── MX.png │ │ ├── MY.png │ │ ├── MZ.png │ │ ├── NA.png │ │ ├── NC.png │ │ ├── NE.png │ │ ├── NF.png │ │ ├── NG.png │ │ ├── NI.png │ │ ├── NL.png │ │ ├── NO.png │ │ ├── NP.png │ │ ├── NR.png │ │ ├── NU.png │ │ ├── NZ.png │ │ ├── OM.png │ │ ├── PA.png │ │ ├── PE.png │ │ ├── PF.png │ │ ├── PG.png │ │ ├── PH.png │ │ ├── PK.png │ │ ├── PL.png │ │ ├── PM.png │ │ ├── PN.png │ │ ├── PR.png │ │ ├── PS.png │ │ ├── PT.png │ │ ├── PW.png │ │ ├── PY.png │ │ ├── QA.png │ │ ├── RE.png │ │ ├── RO.png │ │ ├── RS.png │ │ ├── RU.png │ │ ├── RW.png │ │ ├── SA.png │ │ ├── SB.png │ │ ├── SC.png │ │ ├── SD.png │ │ ├── SE.png │ │ ├── SG.png │ │ ├── SH.png │ │ ├── SI.png │ │ ├── SJ.png │ │ ├── SK.png │ │ ├── SL.png │ │ ├── SM.png │ │ ├── SN.png │ │ ├── SO.png │ │ ├── SR.png │ │ ├── SS.png │ │ ├── ST.png │ │ ├── SV.png │ │ ├── SY.png │ │ ├── SZ.png │ │ ├── TC.png │ │ ├── TD.png │ │ ├── TF.png │ │ ├── TG.png │ │ ├── TH.png │ │ ├── TJ.png │ │ ├── TK.png │ │ ├── TL.png │ │ ├── TM.png │ │ ├── TN.png │ │ ├── TO.png │ │ ├── TR.png │ │ ├── TT.png │ │ ├── TV.png │ │ ├── TW.png │ │ ├── TZ.png │ │ ├── UA.png │ │ ├── UG.png │ │ ├── UM.png │ │ ├── US.png │ │ ├── UY.png │ │ ├── UZ.png │ │ ├── VA.png │ │ ├── VC.png │ │ ├── VE.png │ │ ├── VG.png │ │ ├── VI.png │ │ ├── VN.png │ │ ├── VU.png │ │ ├── WF.png │ │ ├── WS.png │ │ ├── YE.png │ │ ├── YT.png │ │ ├── ZA.png │ │ ├── ZM.png │ │ └── ZW.png │ ├── fonts │ │ ├── Extra.otf │ │ ├── Torus Regular.otf │ │ ├── Torus SemiBold.otf │ │ └── Venera.otf │ ├── help.png │ ├── info.png │ ├── maniabeatmapinfo.png │ ├── match │ │ ├── mplink.png │ │ ├── mplink_map.png │ │ ├── team_blue.png │ │ └── team_red.png │ ├── medals │ │ └── medals.json │ ├── mods │ │ ├── 4K.png │ │ ├── 5K.png │ │ ├── 6K.png │ │ ├── 7K.png │ │ ├── 8K.png │ │ ├── 9K.png │ │ ├── AP.png │ │ ├── CL.png │ │ ├── DT.png │ │ ├── EZ.png │ │ ├── FI.png │ │ ├── FL.png │ │ ├── HD.png │ │ ├── HR.png │ │ ├── HT.png │ │ ├── MR.png │ │ ├── NC.png │ │ ├── NF.png │ │ ├── PF.png │ │ ├── RX.png │ │ ├── SD.png │ │ ├── SO.png │ │ ├── TD.png │ │ └── V2.png │ ├── pfm_ctb.png │ ├── pfm_mania.png │ ├── pfm_std.png │ ├── pfm_taiko.png │ ├── ranking │ │ ├── ranking-A.png │ │ ├── ranking-B.png │ │ ├── ranking-C.png │ │ ├── ranking-D.png │ │ ├── ranking-F.png │ │ ├── ranking-S.png │ │ ├── ranking-SH.png │ │ ├── ranking-X.png │ │ └── ranking-XH.png │ └── work │ │ ├── bmap.png │ │ ├── center.png │ │ ├── left.png │ │ ├── right.png │ │ ├── stardiff.png │ │ ├── stars.png │ │ ├── stars_expertplus.png │ │ └── suppoter.png ├── pp.py ├── schema │ ├── __init__.py │ ├── alphaosu.py │ ├── basemodel.py │ ├── beatmap.py │ ├── match.py │ ├── ppysb │ │ └── __init__.py │ ├── sayo_beatmap.py │ ├── score.py │ └── user.py └── utils │ └── __init__.py ├── pdm.lock └── pyproject.toml /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/pypi-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | pypi-publish: 11 | name: Upload release to PyPI 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@master 15 | - name: Set up Python 16 | uses: actions/setup-python@v1 17 | with: 18 | python-version: "3.x" 19 | - name: Install pypa/build 20 | run: >- 21 | python -m 22 | pip install 23 | build 24 | --user 25 | - name: Build a binary wheel and a source tarball 26 | run: >- 27 | python -m 28 | build 29 | --sdist 30 | --wheel 31 | --outdir dist/ 32 | . 33 | - name: Publish distribution to PyPI 34 | uses: pypa/gh-action-pypi-publish@release/v1 35 | with: 36 | password: ${{ secrets.PYPI_API_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/ruff.yml: -------------------------------------------------------------------------------- 1 | name: Ruff Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | ruff: 11 | name: Ruff Lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Run Ruff Lint 17 | uses: chartboost/ruff-action@v1 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "nonebot_plugin_osubot/draw/catch_preview_templates/js"] 2 | path = nonebot_plugin_osubot/draw/catch_preview_templates/js 3 | url = https://github.com/Exsper/osucatch-preview.git 4 | branch = master 5 | sparse-checkout = docs/jsgit submodule update --init --recursive 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/nonebot-plugin-osubot.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.pdm-python: -------------------------------------------------------------------------------- 1 | C:/Users/57247/PycharmProjects/nonebot-plugin-osubot/.venv/Scripts/python.exe -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_install_hook_types: [pre-commit, prepare-commit-msg] 2 | ci: 3 | autofix_commit_msg: ":rotating_light: auto fix by pre-commit hooks" 4 | autofix_prs: true 5 | autoupdate_branch: master 6 | autoupdate_schedule: monthly 7 | autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks" 8 | repos: 9 | - repo: https://github.com/astral-sh/ruff-pre-commit 10 | rev: v0.11.8 11 | hooks: 12 | - id: ruff 13 | args: [--fix] 14 | stages: [pre-commit] 15 | - id: ruff-format 16 | stages: [pre-commit] 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | NoneBotPluginLogo 3 |
4 |

NoneBotPluginText

5 |
6 | 7 |
8 | 9 | # nonebot-plugin-osubot 10 | 11 | _✨ NoneBot osubot ✨_ 12 | 13 | 14 | 15 | license 16 | 17 | 18 | pypi 19 | 20 | python 21 | 22 |
23 | 24 | 25 | ## 📖 介绍 26 | 27 | 本项目修改自[osuv2](https://github.com/Yuri-YuzuChaN/osuv2),适配了nonebot2,并且在此之上修改了命令的响应逻辑并修改了一些bug使之更易于使用 28 | 29 | 变速功能依赖ffmpeg,需要[自行安装ffmpeg](https://docs.go-cqhttp.org/guide/quick_start.html#%E5%AE%89%E8%A3%85-ffmpeg)才能正常使用 30 | 31 | ## 💿 安装 32 | 33 |
34 | 使用 nb-cli 安装(推荐) 35 | 在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装 36 | 37 | nb plugin install nonebot-plugin-osubot 38 | 39 |
40 | 41 |
42 | 使用包管理器安装(推荐) 43 | 在 nonebot2 项目的插件目录下, 打开命令行, 根据你使用的包管理器, 输入相应的安装命令 44 | 45 |
46 | pip 47 | 48 | pip install nonebot-plugin-osubot 49 |
50 |
51 | pdm 52 | 53 | pdm add nonebot-plugin-osubot 54 |
55 |
56 | poetry 57 | 58 | poetry add nonebot-plugin-osubot 59 |
60 | 61 | 62 | 打开 nonebot2 项目的 `bot.py` 文件, 在其中写入 63 | 64 | nonebot.load_plugin('nonebot_plugin_osubot') 65 | 66 |
67 | 68 |
69 | 从 github 安装 70 | 在 nonebot2 项目的插件目录下, 打开命令行, 输入以下命令克隆此储存库 71 | 72 | git clone https://github.com/yaowan233/nonebot-plugin-osubot.git 73 | 74 | 修改[nonebot_plugin_osubot的__init__.py](https://github.com/yaowan233/nonebot-plugin-osubot/blob/a7c7098f39d92b8fe74dfe85c262397b81db721c/nonebot_plugin_osubot/__init__.py#L37)为 75 | 76 | add_model('src.plugins.database.models') 77 | 78 | 打开 nonebot2 项目的 `bot.py` 文件, 在其中写入 79 | 80 | nonebot.load_plugin('src.plugins.nonebot_plugin_osubot') 81 | 82 |
83 | 84 | ## ⚙️ 配置 85 | 你需要至[OSU个人设置](https://osu.ppy.sh/home/account/edit)申请新的OAuth应用,然后将得到的客户端ID与客户端密钥填入nonebot2 项目的`.env`文件中 86 | 87 | 配置说明 88 | | 配置项 | 必填 | 默认值 | 说明 | 89 | |:-----:|:----:|:----:|:----:| 90 | | OSU_CLIENT | 是 | 无 | 客户端ID | 91 | | OSU_KEY | 是 | 无 | 客户端密钥 | 92 | | tortoise_orm_db_url | 否 | sqlite://db.sqlite3 | 数据库地址 | 93 | | INFO_BG | 否 | ['https://example.com'] | 随机背景api地址,需要打开网页后随机获得一张图片 | 94 | 95 | ## 🎉 使用 96 | ### 指令 97 | 98 | ![image](https://github.com/yaowan233/nonebot-plugin-osubot/assets/30517062/41fd8326-7b97-4de9-be83-c38b31453ea1) 99 | 100 | 101 | ## 💡 贡献 102 | 103 | 如果遇到任何问题,欢迎提各种issue来反馈bug 104 | 你也可以加群(228986744)来进行反馈! 105 | ![1665504476458_temp_qrcode_share_9993](https://user-images.githubusercontent.com/30517062/195143643-5c212f4e-5ee2-49fd-8e71-4f360eef2d46.png) 106 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/__init__.py: -------------------------------------------------------------------------------- 1 | from nonebot import require 2 | from nonebot.log import logger 3 | from nonebot.plugin import PluginMetadata, inherit_supported_adapters 4 | 5 | require("nonebot_plugin_apscheduler") 6 | require("nonebot_plugin_alconna") 7 | require("nonebot_plugin_session") 8 | require("nonebot_plugin_tortoise_orm") 9 | require("nonebot_plugin_htmlrender") 10 | require("nonebot_plugin_waiter") 11 | from nonebot_plugin_apscheduler import scheduler 12 | from nonebot_plugin_tortoise_orm import add_model 13 | 14 | from .config import Config 15 | from .matcher import * # noqa 16 | from .info import update_users_info 17 | from .database.models import UserData 18 | 19 | add_model("nonebot_plugin_osubot.database.models") 20 | usage = "发送/osuhelp 查看帮助" 21 | __plugin_meta__ = PluginMetadata( 22 | name="OSUBot", 23 | description="OSU查分插件", 24 | usage=usage, 25 | type="application", 26 | homepage="https://github.com/yaowan233/nonebot-plugin-osubot", 27 | config=Config, 28 | supported_adapters=inherit_supported_adapters("nonebot_plugin_session", "nonebot_plugin_alconna"), 29 | extra={ 30 | "unique_name": "osubot", 31 | "author": "yaowan233 <572473053@qq.com>", 32 | }, 33 | ) 34 | 35 | 36 | @scheduler.scheduled_job("cron", hour="0", misfire_grace_time=60) 37 | async def update_info(): 38 | result = await UserData.all() 39 | if not result: 40 | return 41 | users = [i.osu_id for i in result] 42 | groups = [users[i : i + 50] for i in range(0, len(users), 50)] 43 | for group in groups: 44 | await update_users_info(group) 45 | logger.info(f"已更新{len(result)}位玩家数据") 46 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/beatmap_stats_moder.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from .utils import GM 4 | from .schema import Beatmap 5 | from .schema.score import Mod, UnifiedScore 6 | 7 | OD0_MS = 80 8 | OD10_MS = 20 9 | AR0_MS = 1800 10 | AR5_MS = 1200 11 | AR10_MS = 450 12 | 13 | OD_MS_STEP = (OD0_MS - OD10_MS) / 10 14 | AR_MS_STEP1 = (AR0_MS - AR5_MS) / 5 15 | AR_MS_STEP2 = (AR5_MS - AR10_MS) / 5 16 | 17 | 18 | def modify_ar(base_ar, speed_mul, multiplier): 19 | ar = base_ar 20 | ar *= multiplier 21 | 22 | arms = AR0_MS - AR_MS_STEP1 * ar if ar < 5 else AR5_MS - AR_MS_STEP2 * (ar - 5) 23 | 24 | arms = min(AR0_MS, max(AR10_MS, arms)) 25 | arms /= speed_mul 26 | 27 | ar = (AR0_MS - arms) / AR_MS_STEP1 if arms > AR5_MS else 5 + (AR5_MS - arms) / AR_MS_STEP2 28 | return ar 29 | 30 | 31 | def modify_od(base_od, speed_mul, multiplier): 32 | od = base_od 33 | od *= multiplier 34 | odms = OD0_MS - OD_MS_STEP * od 35 | odms = min(OD0_MS, max(OD10_MS, odms)) 36 | odms /= speed_mul 37 | od = (OD0_MS - odms) / OD_MS_STEP 38 | return od 39 | 40 | 41 | def with_mods(mapinfo: Beatmap, scoreinfo: Optional[UnifiedScore], mods: list[Mod]): 42 | speed_mul = 1 43 | od_ar_hp_multiplier = 1 44 | mode = GM[scoreinfo.ruleset_id] if scoreinfo else mapinfo.mode 45 | for mod in mods: 46 | if mod.acronym == "DA" and mod.settings: 47 | if mod.settings.get("circle_size"): 48 | mapinfo.cs = mod.settings["circle_size"] 49 | if mod.settings.get("approach_rate"): 50 | mapinfo.ar = mod.settings["approach_rate"] 51 | if mod.settings.get("drain_rate"): 52 | mapinfo.drain = mod.settings["drain_rate"] 53 | if mod.settings.get("overall_difficulty"): 54 | mapinfo.accuracy = mod.settings["overall_difficulty"] 55 | if mod.acronym == "DT" or mod.acronym == "NC": 56 | speed_mul = 1.5 57 | if mod.settings and mod.settings.get("speed_change"): 58 | speed_mul = mod.settings["speed_change"] 59 | mapinfo.bpm *= speed_mul 60 | mapinfo.total_length /= speed_mul 61 | if mod.acronym == "HT": 62 | speed_mul = 0.75 63 | if mod.settings and mod.settings.get("speed_change"): 64 | speed_mul = mod.settings["speed_change"] 65 | mapinfo.bpm *= speed_mul 66 | mapinfo.total_length /= speed_mul 67 | if mod.acronym == "HR": 68 | od_ar_hp_multiplier = 1.4 69 | if mod.acronym == "EZ": 70 | od_ar_hp_multiplier *= 0.5 71 | if mode == "mania": 72 | speed_mul = 1 73 | if mode not in ("mania", "taiko"): 74 | mapinfo.ar = modify_ar(mapinfo.ar, speed_mul, od_ar_hp_multiplier) 75 | if mode == "fruits": 76 | speed_mul = 1 77 | mapinfo.accuracy = modify_od(mapinfo.accuracy, speed_mul, od_ar_hp_multiplier) 78 | if mode not in ("mania", "taiko"): 79 | if Mod(acronym="HR") in mods: 80 | mapinfo.cs *= 1.3 81 | if Mod(acronym="EZ") in mods: 82 | mapinfo.cs *= 0.5 83 | mapinfo.cs = min(10.0, mapinfo.cs) 84 | mapinfo.drain *= od_ar_hp_multiplier 85 | mapinfo.drain = min(10.0, mapinfo.drain) 86 | return mapinfo 87 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/config.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Config(BaseModel): 7 | osu_client: Optional[int] = None 8 | osu_key: Optional[str] = None 9 | info_bg: Optional[list[str]] = ["https://t.alcy.cc/mp", "https://t.alcy.cc/moemp"] 10 | osu_proxy: Optional[Union[str, dict]] = None 11 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/data/osu/1849145.osz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/data/osu/1849145.osz -------------------------------------------------------------------------------- /nonebot_plugin_osubot/data/osu/1849145.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/data/osu/1849145.zip -------------------------------------------------------------------------------- /nonebot_plugin_osubot/database/__init__.py: -------------------------------------------------------------------------------- 1 | from .models import InfoData, UserData, SbUserData 2 | 3 | __all__ = ["UserData", "InfoData", "SbUserData"] 4 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/database/models.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from tortoise import Model, fields 4 | 5 | 6 | class UserData(Model): 7 | id: int = fields.IntField(pk=True, generated=True, auto_increment=True) 8 | """自增主键""" 9 | user_id: str = fields.TextField() 10 | """用户id""" 11 | osu_id: int = fields.IntField() 12 | """osu id""" 13 | osu_name: str = fields.TextField() 14 | """osu 用户名""" 15 | osu_mode: int = fields.IntField() 16 | """osu 模式""" 17 | lazer_mode: bool = fields.BooleanField(default=False, null=True) 18 | """是否启用lazer模式""" 19 | 20 | class Meta: 21 | table = "User" 22 | indexes = ("user_id",) 23 | 24 | 25 | class InfoData(Model): 26 | id: int = fields.IntField(pk=True, generated=True, auto_increment=True) 27 | """自增主键""" 28 | osu_id: int = fields.IntField() 29 | """osu id""" 30 | c_rank: Optional[int] = fields.IntField(null=True) 31 | """国家排名""" 32 | g_rank: Optional[int] = fields.IntField(null=True) 33 | """世界排名""" 34 | pp: float = fields.FloatField() 35 | """pp""" 36 | acc: float = fields.FloatField() 37 | """acc""" 38 | pc: int = fields.IntField() 39 | """游戏次数""" 40 | count: int = fields.IntField() 41 | """打击note数""" 42 | osu_mode: int = fields.IntField() 43 | """osu 模式""" 44 | date = fields.DateField() 45 | 46 | class Meta: 47 | table = "Info" 48 | indexes = ("id",) 49 | 50 | 51 | class SbUserData(Model): 52 | id: int = fields.IntField(pk=True, generated=True, auto_increment=True) 53 | """自增主键""" 54 | user_id: str = fields.TextField() 55 | """用户id""" 56 | osu_id: int = fields.IntField() 57 | """osu id""" 58 | osu_name: str = fields.TextField() 59 | """osu 用户名""" 60 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/__init__.py: -------------------------------------------------------------------------------- 1 | from .bp import draw_bp 2 | from .info import draw_info 3 | from .map import draw_map_info 4 | from .bmap import draw_bmap_info 5 | from .score import draw_score, get_score_data 6 | 7 | __all__ = ["draw_info", "draw_score", "draw_bp", "draw_map_info", "draw_bmap_info", "get_score_data"] 8 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import jinja2 4 | from nonebot_plugin_htmlrender import get_new_page 5 | 6 | from ..file import map_path, download_osu 7 | 8 | template_path = str(Path(__file__).parent / "catch_preview_templates") 9 | 10 | 11 | async def draw_cath_preview(beatmap_id, beatmapset_id, mods) -> bytes: 12 | path = map_path / str(beatmapset_id) 13 | if not path.exists(): 14 | path.mkdir(parents=True, exist_ok=True) 15 | osu = path / f"{beatmap_id}.osu" 16 | if not osu.exists(): 17 | await download_osu(beatmapset_id, beatmap_id) 18 | with open(osu, encoding="utf-8-sig") as f: 19 | osu_file = f.read() 20 | template_name = "pic.html" 21 | template_env = jinja2.Environment( # noqa: S701 22 | loader=jinja2.FileSystemLoader(template_path), 23 | enable_async=True, 24 | ) 25 | template = template_env.get_template(template_name) 26 | is_hr = 1 if "HR" in mods else 0 27 | is_ez = 1 if "EZ" in mods else 0 28 | is_dt = 1 if "DT" in mods else 0 29 | is_ht = 1 if "HT" in mods else 0 30 | async with get_new_page(2) as page: 31 | await page.goto(f"file://{template_path}") 32 | await page.set_content( 33 | await template.render_async( 34 | beatmap_id=beatmap_id, osu_file=osu_file, is_hr=is_hr, is_ez=is_ez, is_dt=is_dt, is_ht=is_ht 35 | ), 36 | wait_until="networkidle", 37 | ) 38 | return await page.screenshot(full_page=True, type="jpeg", quality=60, omit_background=True) 39 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/beatmap/hitobject.js: -------------------------------------------------------------------------------- 1 | function HitObject(data, beatmap) 2 | { 3 | this.beatmap = beatmap; 4 | 5 | this.position = new Point(data); 6 | this.endPosition = this.position.clone(); 7 | this.time = data[2] | 0; 8 | this.endTime = this.time; 9 | this.flag = data[3] | 0; 10 | this.hitSound = data[4] | 0; 11 | } 12 | HitObject.prototype.draw = undefined; 13 | HitObject.parse = function(line, beatmap) 14 | { 15 | var data = line.split(','); 16 | if (data.length < 5) 17 | { 18 | throw 'invalid data: ' + line; 19 | } 20 | 21 | var type = data[3] & beatmap.hitObjectTypeMask; 22 | if (!(type in beatmap.hitObjectTypes)) 23 | { 24 | // throw 'we do not support this hitobject type'; 25 | return new HitObject(data, beatmap); 26 | } 27 | 28 | return new beatmap.hitObjectTypes[type](data, beatmap); 29 | }; 30 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/beatmap/point.js: -------------------------------------------------------------------------------- 1 | function Point() 2 | { 3 | if (arguments.length >= 2) 4 | { 5 | this.x = +arguments[0]; 6 | this.y = +arguments[1]; 7 | } 8 | else if (arguments.length == 1) 9 | { 10 | this.x = +arguments[0][0]; 11 | this.y = +arguments[0][1]; 12 | } 13 | else 14 | { 15 | this.x = 0; 16 | this.y = 0; 17 | } 18 | } 19 | Point.prototype.distanceTo = function(point) 20 | { 21 | return Math.hypot(point.x - this.x, point.y - this.y); 22 | }; 23 | Point.prototype.equalTo = function(point) 24 | { 25 | return point instanceof Point && 26 | this.x == point.x && this.y == point.y; 27 | }; 28 | Point.prototype.angleTo = function(point) 29 | { 30 | return Math.atan2(point.y - this.y, point.x - this.x); 31 | }; 32 | Point.prototype.clone = function() 33 | { 34 | return new Point(this.x, this.y); 35 | }; 36 | Point.prototype.translate = function(n) 37 | { 38 | if (n instanceof Point) 39 | { 40 | this.x += n.x; 41 | this.y += n.y; 42 | } 43 | else 44 | { 45 | this.x += n; 46 | this.y += n; 47 | } 48 | return this; 49 | }; 50 | Point.prototype.scale = function(n) 51 | { 52 | this.x *= n; 53 | this.y *= n; 54 | return this; 55 | }; 56 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/beatmap/scroll.js: -------------------------------------------------------------------------------- 1 | function Scroll(osu) 2 | { 3 | Beatmap.call(this, osu); 4 | 5 | // dp for numerous call to this.scrollAt 6 | this.scrollAtTimingPointIndex = [ 0 ]; 7 | var currentIdx = this.timingPointIndexAt(0), 8 | current = this.TimingPoints[currentIdx], 9 | base = this.TimingPoints[0], 10 | scrollVelocity = base.beatLength / current.beatLength; 11 | this.scrollAtTimingPointIndex[currentIdx] = current.time * scrollVelocity; 12 | while (++currentIdx < this.TimingPoints.length) 13 | { 14 | var next = this.TimingPoints[currentIdx]; 15 | this.scrollAtTimingPointIndex[currentIdx] = (next.time - current.time) * scrollVelocity + 16 | this.scrollAtTimingPointIndex[currentIdx - 1]; 17 | current = next; 18 | scrollVelocity = base.beatLength / current.beatLength; 19 | } 20 | 21 | this.barLines = []; 22 | var endTime = (this.HitObjects.length ? this.HitObjects[this.HitObjects.length - 1].endTime : 0) + 1; 23 | for (var i = 0; i < this.TimingPoints.length; i++) 24 | { 25 | var current = this.TimingPoints[i], 26 | base = current.parent || current, 27 | barLength = base.beatLength * base.meter, 28 | next = this.TimingPoints[i + 1], 29 | barLineLimit = next ? (next.parent || next).time : endTime; 30 | for (var barTime = base.time; barTime < barLineLimit; barTime += barLength) 31 | { 32 | this.barLines.push(this.scrollAt(barTime)); 33 | } 34 | } 35 | } 36 | Scroll.prototype = Object.create(Beatmap.prototype); 37 | Scroll.prototype.constructor = Scroll; 38 | Scroll.prototype.scrollAt = function(time) 39 | { 40 | var currentIdx = this.timingPointIndexAt(time), 41 | current = this.TimingPoints[currentIdx], 42 | base = this.TimingPoints[0], 43 | scrollVelocity = base.beatLength / current.beatLength; 44 | return (time - current.time) * scrollVelocity + this.scrollAtTimingPointIndex[currentIdx]; 45 | }; 46 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/beatmap/timingpoint.js: -------------------------------------------------------------------------------- 1 | function TimingPoint(line) 2 | { 3 | var data = line.split(','); 4 | if (data.length < 2) 5 | { 6 | throw 'invalid data'; 7 | } 8 | 9 | this.time = data[0] | 0; 10 | this.beatLength = +data[1]; 11 | this.meter = (data[2] | 0) || 4; 12 | this.kiai = (data[7] | 0) % 2; 13 | 14 | // this is non-inherited timingPoint 15 | if (this.beatLength >= 0) 16 | { 17 | TimingPoint.parent = this; 18 | this.sliderVelocity = 1; 19 | } 20 | else 21 | { 22 | this.parent = TimingPoint.parent; 23 | this.sliderVelocity = -100 / this.beatLength; 24 | this.beatLength = this.parent.beatLength / this.sliderVelocity; 25 | this.meter = this.parent.meter; 26 | } 27 | } 28 | Object.defineProperties(TimingPoint.prototype, { 29 | bpm: { 30 | get: function() 31 | { 32 | return 60000 / this.beatLength; 33 | } 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/LegacyRandom.js: -------------------------------------------------------------------------------- 1 | class LegacyRandom{ 2 | constructor(x) { 3 | this.int_to_real = 1 / 2147483648; 4 | this.int_mask = 0x7FFFFFFF; 5 | this.x = x || Date.now(); 6 | this.x = this.ToUInt(this.x); 7 | this._X = this.x; 8 | this.y = 842502087; 9 | this._Y = this.y; 10 | this.z = 3579807591; 11 | this._Z = this.z; 12 | this.w = 273326509; 13 | this._W = this.w; 14 | 15 | this.bitBuffer; 16 | this.bitIndex = 32; 17 | } 18 | 19 | ToInt(num) { 20 | if (num > 2147483647) return this.ToInt(num - 4294967296); 21 | else if (num < -2147483648) return this.ToInt(4294967296 + num); 22 | else return parseInt(num); 23 | } 24 | 25 | ToUInt(num) { 26 | if (num > 4294967296) return this.ToUInt(num - 4294967296); 27 | if (num < 0) return this.ToUInt(4294967296 + num); 28 | else return num; 29 | } 30 | 31 | NextUInt() { 32 | let t = this.ToUInt(this._X ^ this.ToUInt(this._X << 11)); 33 | this._X = this._Y; 34 | this._Y = this._Z; 35 | this._Z = this._W; 36 | let tmp = this.ToUInt(this._W >>> 19); 37 | tmp = this.ToUInt(this._W ^ tmp); 38 | tmp = this.ToUInt(tmp ^ t); 39 | let tmp2 = this.ToUInt(t >>> 8); 40 | this._W = this.ToUInt(tmp ^ tmp2); 41 | return this._W; 42 | } 43 | 44 | Next() { 45 | if (arguments.length <= 0) { 46 | return this.ToInt(this.int_mask & this.NextUInt()); 47 | } 48 | else if (arguments.length === 1) { // upperBound 49 | return this.ToInt(this.NextDouble() * arguments[0]); 50 | } 51 | else { // lowerBound, upperBound 52 | return this.ToInt(arguments[0] + this.NextDouble() * (arguments[1] - arguments[0])); 53 | } 54 | } 55 | 56 | NextDouble() { 57 | return this.int_to_real * this.Next(); 58 | } 59 | 60 | NextBool() { 61 | if (this.bitIndex == 32) 62 | { 63 | this.bitBuffer = this.NextUInt(); 64 | this.bitIndex = 1; 65 | 66 | return ((this.bitBuffer & 1) == 1); 67 | } 68 | 69 | this.bitIndex++; 70 | this.bitBuffer = this.bitBuffer >>> 1; 71 | return ((this.bitBuffer & 1) == 1); 72 | } 73 | } 74 | /* 75 | let rng = new LegacyRandom(1337); 76 | for(let i = 0; i < 1000; i++) 77 | { 78 | rng.NextDouble(); 79 | } 80 | console.log(rng.NextDouble()); 81 | */ 82 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/bananashower.js: -------------------------------------------------------------------------------- 1 | function BananaShower(data, beatmap) { 2 | Spinner.call(this, data, beatmap); 3 | 4 | this.endTime = data[5] | 0; 5 | this.duration = this.endTime - this.time; 6 | 7 | this.nested = []; 8 | } 9 | BananaShower.prototype = Object.create(Spinner.prototype); 10 | BananaShower.prototype.constructor = BananaShower; 11 | BananaShower.ID = 8; 12 | Catch.prototype.hitObjectTypes[BananaShower.ID] = BananaShower; 13 | BananaShower.prototype.buildNested = function() { 14 | this.nested = []; 15 | let spacing = this.duration; 16 | while (spacing > 100) spacing /= 2; 17 | if (spacing <= 0) return; 18 | let time = this.time; 19 | let i = 0; 20 | while (time <= this.endTime) { 21 | this.nested.push(new PalpableCatchHitObject({ 22 | type: "Banana", 23 | time, 24 | x: 0, 25 | color: 'rgb(255,240,0)', 26 | radius: this.beatmap.bananaRadius, 27 | }, this.beatmap)); 28 | 29 | time += spacing; 30 | i++; 31 | } 32 | this.nested[this.nested.length - 1].hitSound = this.hitSound; 33 | return this; 34 | }; 35 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/fruit.js: -------------------------------------------------------------------------------- 1 | function Fruit(data, beatmap) 2 | { 3 | HitObject.call(this, data, beatmap); 4 | } 5 | Fruit.prototype = Object.create(HitObject.prototype, { 6 | newCombo: { 7 | get: function() 8 | { 9 | return this.flag & 4; 10 | } 11 | }, 12 | comboSkip: { 13 | get: function() 14 | { 15 | return this.flag >> 4; 16 | } 17 | } 18 | }); 19 | Fruit.prototype.constructor = Fruit; 20 | Fruit.ID = 1; 21 | Catch.prototype.hitObjectTypes[Fruit.ID] = Fruit; -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/fakeaudio.js: -------------------------------------------------------------------------------- 1 | function FakeAudio(length) { 2 | this.src = null; 3 | this.duration = length; 4 | this.length = length; // seconds 5 | this.currentTime = 0; // seconds 6 | this.volume = 1; 7 | this.paused = true; 8 | this.playbackRate = 1; 9 | this.playTimer = null; 10 | this.pause(); 11 | } 12 | 13 | FakeAudio.prototype.draw = function () { 14 | requestAnimationFrame(() => { 15 | window.preview.at(this.currentTime * 1000); 16 | }); 17 | } 18 | 19 | FakeAudio.prototype.play = function () { 20 | preview.beatmap.refresh(); 21 | this.draw(); 22 | let ts = 20; 23 | if (this.paused) { 24 | $('#play').removeClass('e'); 25 | this.paused = false; 26 | this.playTimer = setInterval(()=> { 27 | this.currentTime += ts / 1000 * this.playbackRate; 28 | if (this.currentTime >= this.length) this.pause(); 29 | if (this.currentTime % 1 < 0.1) { 30 | $('#progress').val(this.currentTime); 31 | $('#playtime').text(time2text(this.currentTime)); 32 | } 33 | this.draw(); 34 | }, ts); 35 | } 36 | }; 37 | 38 | FakeAudio.prototype.pause = function () { 39 | if (this.paused) return; 40 | $(document.body).trigger('mousemove'); 41 | $('#play').addClass('e'); 42 | this.paused = true; 43 | clearInterval(this.playTimer); 44 | }; -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/preview.js: -------------------------------------------------------------------------------- 1 | var bgblob; 2 | function Preview(dest) { 3 | this.container = dest; 4 | 5 | this.screen = document.createElement('canvas'); 6 | this.screen.width = Beatmap.WIDTH; 7 | this.screen.height = Beatmap.HEIGHT; 8 | this.ctx = this.screen.getContext('2d'); 9 | this.container.appendChild(this.screen); 10 | 11 | var self = this; 12 | this.background = new Image(); 13 | this.background.setAttribute('crossOrigin', 'anonymous'); 14 | let drawBG = function () { 15 | var canvas = document.createElement('canvas'); 16 | canvas.id = 'bgcanvas'; 17 | canvas.width = self.screen.width; 18 | canvas.height = self.screen.height; 19 | var ctx = canvas.getContext('2d'); 20 | 21 | // background-size: cover height 22 | var sWidth = this.height * (self.screen.width / self.screen.height); 23 | ctx.drawImage(this, (this.width - sWidth) / 2, 0, sWidth, this.height, 24 | 0, 0, self.screen.width, self.screen.height); 25 | // background dim 26 | ctx.fillStyle = 'rgba(0, 0, 0, .5)'; 27 | ctx.fillRect(0, 0, self.screen.width, self.screen.height); 28 | 29 | if (typeof self.beatmap.processBG != 'undefined') { 30 | self.beatmap.processBG(ctx); 31 | } 32 | canvas.toBlob(function (blob) { 33 | var url = URL.createObjectURL(blob); 34 | self.background.src = url; 35 | self.container.style.backgroundImage = 'url(' + url + ')'; 36 | }); 37 | } 38 | this.background.addEventListener('load', drawBG, { once: true }); 39 | this.background.addEventListener('error', function () { 40 | self.container.style.backgroundImage = 'none'; 41 | }); 42 | } 43 | Preview.prototype.load = function (bgblob, osufile, mod, success, fail) { 44 | if (typeof this.xhr != 'undefined') { 45 | this.xhr.abort(); 46 | } 47 | 48 | let mods = { 49 | HR: false, 50 | EZ: false 51 | } 52 | if (mod) mods[mod] = true; 53 | 54 | var self = this; 55 | try { 56 | self.beatmap = Beatmap.parse(osufile, mods); 57 | if (bgblob) self.background.src = bgblob; 58 | self.ctx.restore(); 59 | self.ctx.save(); 60 | self.beatmap.update(self.ctx); 61 | self.at(0); 62 | 63 | if (typeof success == 'function') { 64 | success.call(self); 65 | } 66 | } 67 | catch (e) { 68 | if (typeof fail == 'function') { 69 | fail.call(self, e); 70 | } 71 | } 72 | }; 73 | Preview.prototype.at = function (time) { 74 | this.ctx.save(); 75 | this.ctx.setTransform(1, 0, 0, 1, 0, 0); 76 | this.ctx.clearRect(0, 0, Beatmap.WIDTH, Beatmap.HEIGHT); 77 | this.ctx.restore(); 78 | this.beatmap.draw(time, this.ctx); 79 | }; 80 | 81 | Preview.prototype.output = function () { 82 | let SCALE = 0.2; 83 | let canvas2 = this.beatmap.draw2(SCALE); 84 | canvas2.toBlob(function (blob) { 85 | // 创建下载链接 86 | let link = document.createElement("a"); 87 | link.href = URL.createObjectURL(blob); 88 | link.download = "预览图.png"; 89 | 90 | // 触发下载 91 | link.click(); 92 | }, "image/png"); 93 | }; 94 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/bezier2.js: -------------------------------------------------------------------------------- 1 | function Bezier2(points) 2 | { 3 | // https://github.com/itdelatrisu/opsu/blob/master/src/itdelatrisu/opsu/objects/curves/Bezier2.java 4 | if (points.length < 2) 5 | { 6 | throw 'invalid data'; 7 | } 8 | 9 | this.points = points; 10 | var approxLength = 0; 11 | for (var i = 1; i < this.points.length; i++) 12 | { 13 | approxLength += this.points[i].distanceTo(this.points[i - 1]); 14 | } 15 | 16 | CurveType.call(this, approxLength); 17 | } 18 | Bezier2.prototype = Object.create(CurveType.prototype); 19 | Bezier2.prototype.constructor = Bezier2; 20 | Bezier2.prototype.pointAt = function(t) 21 | { 22 | var n = this.points.length - 1, 23 | point = new Point(), 24 | combination = 1; 25 | for (var i = 0; i <= n; i++) 26 | { 27 | var bernstein = combination * Math.pow(t, i) * Math.pow(1 - t, n - i); 28 | point.x += this.points[i].x * bernstein; 29 | point.y += this.points[i].y * bernstein; 30 | combination = combination * (n - i) / (i + 1); 31 | } 32 | return point; 33 | }; 34 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/catmullcurve.js: -------------------------------------------------------------------------------- 1 | function CatmullCurve(points, pixelLength) 2 | { 3 | // https://github.com/itdelatrisu/opsu/blob/master/src/itdelatrisu/opsu/objects/curves/CatmullCurve.java 4 | var catmulls = [], 5 | controls = []; 6 | if (!points[0].equalTo(points[1])) 7 | { 8 | controls.push(points[0]); 9 | } 10 | for (var i = 0; i < points.length; i++) 11 | { 12 | controls.push(points[i]); 13 | try 14 | { 15 | catmulls.push(new CentripetalCatmullRom(controls)); 16 | controls.shift(); 17 | } 18 | catch (e) {} 19 | } 20 | var point2 = points.slice(-2); 21 | if (!point2[1].equalTo(point2[0])) 22 | { 23 | controls.push(point2[1]); 24 | } 25 | try 26 | { 27 | catmulls.push(new CentripetalCatmullRom(controls)); 28 | } 29 | catch (e) {} 30 | 31 | EqualDistanceMultiCurve.call(this, catmulls, pixelLength); 32 | }; 33 | CatmullCurve.prototype = Object.create(EqualDistanceMultiCurve.prototype); 34 | CatmullCurve.prototype.constructor = CatmullCurve; 35 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/centripetalcatmullrom.js: -------------------------------------------------------------------------------- 1 | function CentripetalCatmullRom(points) 2 | { 3 | // https://github.com/itdelatrisu/opsu/blob/master/src/itdelatrisu/opsu/objects/curves/CentripetalCatmullRom.java 4 | // needs 4 points 5 | if (points.length != 4) 6 | { 7 | throw 'invalid data'; 8 | } 9 | 10 | this.points = points; 11 | var approxLength = 0; 12 | for (var i = 1; i < 4; i++) 13 | { 14 | approxLength += this.points[i].distanceTo(this.points[i - 1]); 15 | } 16 | 17 | CurveType.call(this, approxLength / 2); 18 | } 19 | CentripetalCatmullRom.prototype = Object.create(CurveType.prototype); 20 | CentripetalCatmullRom.prototype.constructor = CentripetalCatmullRom; 21 | CentripetalCatmullRom.prototype.pointAt = function(t) 22 | { 23 | t = Math.lerp(1, 2, t); 24 | var A1 = this.points[0].clone().scale(1 - t).translate(this.points[1].clone().scale(t)); 25 | var A2 = this.points[1].clone().scale(2 - t).translate(this.points[2].clone().scale(t - 1)); 26 | var A3 = this.points[2].clone().scale(3 - t).translate(this.points[3].clone().scale(t - 2)); 27 | var B1 = A1.clone().scale(2 - t).translate(A2.clone().scale(t)); 28 | var B2 = A2.clone().scale(3 - t).translate(A3.clone().scale(t - 1)); 29 | return B1.clone().scale(2 - t).translate(B2.clone().scale(t - 1)).scale(0.5); 30 | }; 31 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/circumstancedcircle.js: -------------------------------------------------------------------------------- 1 | function CircumscribedCircle(points, pixelLength) 2 | { 3 | var a = points[0].x - points[1].x, b = points[0].y - points[1].y, 4 | c = points[1].x - points[2].x, d = points[1].y - points[2].y, 5 | q = (a * d - b * c) * 2, 6 | l0 = points[0].x * points[0].x + points[0].y * points[0].y, 7 | l1 = points[1].x * points[1].x + points[1].y * points[1].y, 8 | l2 = points[2].x * points[2].x + points[2].y * points[2].y, 9 | x = ((l0 - l1) * d + (l1 - l2) * -b) / q, 10 | y = ((l0 - l1) * -c + (l1 - l2) * a) / q, 11 | dx = points[0].x - x, 12 | dy = points[0].y - y, 13 | r = Math.hypot(dx, dy), 14 | base = Math.atan2(dy, dx), 15 | t = pixelLength / r * Math.ccw(points[0], points[1], points[2]); 16 | if (!t) 17 | { 18 | // when points[2] is missing or vectors are parallel 19 | throw 'invalid data'; 20 | } 21 | this.circle = { 22 | x: x, 23 | y: y, 24 | radius: r 25 | }; 26 | this.angle = { 27 | base: base, 28 | delta: t 29 | }; 30 | 31 | var nCurve = pixelLength / Curve.PRECISION | 0; 32 | this.path = []; 33 | for (var i = 0; i <= nCurve; i++) 34 | { 35 | this.path[i] = this.pointAt(i / nCurve); 36 | } 37 | 38 | Curve.call(this); 39 | }; 40 | CircumscribedCircle.prototype = Object.create(Curve.prototype); 41 | CircumscribedCircle.prototype.constructor = CircumscribedCircle; 42 | CircumscribedCircle.prototype.pointAt = function(t) 43 | { 44 | var angle = this.angle.base + this.angle.delta * t; 45 | return new Point(this.circle.x + Math.cos(angle) * this.circle.radius, 46 | this.circle.y + Math.sin(angle) * this.circle.radius); 47 | }; 48 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/curve.js: -------------------------------------------------------------------------------- 1 | function Curve() 2 | { 3 | this.startAngle = this.path[0].angleTo(this.path[1]); 4 | var path2 = this.path.slice(-2); 5 | this.endAngle = path2[1].angleTo(path2[0]); 6 | } 7 | Curve.prototype.path = undefined; 8 | Curve.prototype.pointAt = undefined; 9 | Curve.PRECISION = 5; 10 | Curve.parse = function(sliderType, points, pixelLength) 11 | { 12 | try 13 | { 14 | if (sliderType == 'P') 15 | { 16 | return new CircumscribedCircle(points, pixelLength); 17 | } 18 | if (sliderType == 'C') 19 | { 20 | return new CatmullCurve(points, pixelLength); 21 | } 22 | } 23 | catch(e) {} 24 | try 25 | { 26 | return new LinearBezier(points, pixelLength, sliderType == 'L'); 27 | } 28 | catch(e) 29 | { 30 | return new SingleNoteCurve(points[0]); 31 | } 32 | } 33 | 34 | 35 | 36 | function SingleNoteCurve(point) 37 | { 38 | this.path = [point]; 39 | }; 40 | SingleNoteCurve.prototype = Object.create(Curve.prototype); 41 | SingleNoteCurve.prototype.constructor = SingleNoteCurve; 42 | SingleNoteCurve.prototype.pointAt = function(t) 43 | { 44 | return this.path[0]; 45 | }; 46 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/curvetype.js: -------------------------------------------------------------------------------- 1 | function CurveType(approxLength) 2 | { 3 | // https://github.com/itdelatrisu/opsu/blob/master/src/itdelatrisu/opsu/objects/curves/CurveType.java 4 | var points = (approxLength / 4 | 0) + 1; 5 | this.path = []; 6 | for (var i = 0; i <= points; i++) 7 | { 8 | this.path[i] = this.pointAt(i / points); 9 | } 10 | 11 | this.distance = [ 0 ]; 12 | for (var i = 1; i <= points; i++) 13 | { 14 | this.distance[i] = this.path[i].distanceTo(this.path[i - 1]); 15 | } 16 | } 17 | CurveType.prototype.pointAt = undefined; 18 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/equaldistancemulticurve.js: -------------------------------------------------------------------------------- 1 | function EqualDistanceMultiCurve(curves, pixelLength) 2 | { 3 | // https://github.com/itdelatrisu/opsu/blob/master/src/itdelatrisu/opsu/objects/curves/EqualDistanceMultiCurve.java 4 | var nCurve = pixelLength / Curve.PRECISION | 0; 5 | this.path = []; 6 | 7 | var distanceAt = 0, 8 | curveIndex = 0, 9 | curve = curves[curveIndex], 10 | pointIndex = 0, 11 | startPoint = curve.path[0], 12 | lastDistanceAt = 0; 13 | // for each distance, try to get in between the two points that are between it 14 | for (var i = 0; i <= nCurve; i++) 15 | { 16 | var prefDistance = i * pixelLength / nCurve | 0; 17 | while (distanceAt < prefDistance) 18 | { 19 | lastDistanceAt = distanceAt; 20 | startPoint = curve.path[pointIndex]; 21 | 22 | if (++pointIndex >= curve.path.length) 23 | { 24 | if (curveIndex + 1 < curves.length) 25 | { 26 | curve = curves[++curveIndex]; 27 | pointIndex = 0; 28 | } 29 | else 30 | { 31 | pointIndex = curve.path.length - 1; 32 | if (lastDistanceAt == distanceAt) 33 | { 34 | // out of points even though the preferred distance hasn't been reached 35 | break; 36 | } 37 | } 38 | } 39 | distanceAt += curve.distance[pointIndex]; 40 | } 41 | var endPoint = curve.path[pointIndex]; 42 | 43 | // interpolate the point between the two closest distances 44 | if (distanceAt - lastDistanceAt > 1) 45 | { 46 | this.path[i] = Math.lerp(startPoint, endPoint, (prefDistance - lastDistanceAt) / (distanceAt - lastDistanceAt)); 47 | } 48 | else 49 | { 50 | this.path[i] = endPoint; 51 | } 52 | } 53 | 54 | Curve.call(this); 55 | }; 56 | EqualDistanceMultiCurve.prototype = Object.create(Curve.prototype); 57 | EqualDistanceMultiCurve.prototype.constructor = EqualDistanceMultiCurve; 58 | EqualDistanceMultiCurve.prototype.pointAt = function(t) 59 | { 60 | var indexF = this.path.length * t, 61 | index = indexF | 0; 62 | if (index + 1 < this.path.length) 63 | { 64 | return Math.lerp(this.path[index], this.path[index + 1], indexF - index); 65 | } 66 | else 67 | { 68 | return this.path[this.path.length - 1]; 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/linearbezier.js: -------------------------------------------------------------------------------- 1 | function LinearBezier(points, pixelLength, linear) 2 | { 3 | // https://github.com/itdelatrisu/opsu/blob/master/src/itdelatrisu/opsu/objects/curves/LinearBezier.java 4 | var beziers = [], 5 | controls = [], 6 | last; 7 | for (var i = 0; i < points.length; i++) 8 | { 9 | var point = points[i]; 10 | if (linear) 11 | { 12 | if (typeof last != 'undefined') 13 | { 14 | controls.push(point); 15 | beziers.push(new Bezier2(controls)); 16 | controls = []; 17 | } 18 | } 19 | else if (point.equalTo(last)) 20 | { 21 | try 22 | { 23 | beziers.push(new Bezier2(controls)); 24 | } 25 | catch (e) {} 26 | controls = []; 27 | } 28 | controls.push(point); 29 | last = point; 30 | } 31 | try 32 | { 33 | beziers.push(new Bezier2(controls)); 34 | } 35 | catch (e) {} 36 | 37 | EqualDistanceMultiCurve.call(this, beziers, pixelLength); 38 | }; 39 | LinearBezier.prototype = Object.create(EqualDistanceMultiCurve.prototype); 40 | LinearBezier.prototype.constructor = LinearBezier; 41 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/hitcircle.js: -------------------------------------------------------------------------------- 1 | function HitCircle(data, beatmap) 2 | { 3 | HitObject.call(this, data, beatmap); 4 | 5 | this.stack = 0; 6 | } 7 | HitCircle.prototype = Object.create(HitObject.prototype, { 8 | newCombo: { 9 | get: function() 10 | { 11 | return this.flag & 4; 12 | } 13 | }, 14 | comboSkip: { 15 | get: function() 16 | { 17 | return this.flag >> 4; 18 | } 19 | } 20 | }); 21 | HitCircle.prototype.constructor = HitCircle; 22 | HitCircle.ID = 1; 23 | HitCircle.FADE_IN_TIME = 375; 24 | HitCircle.FADE_OUT_TIME = 200; 25 | HitCircle.prototype.draw = function(time, ctx) 26 | { 27 | var dt = this.time - time, 28 | opacity = 1; 29 | if (dt >= 0) 30 | { 31 | opacity = (this.beatmap.approachTime - dt) / HitCircle.FADE_IN_TIME; 32 | } 33 | else 34 | { 35 | opacity = 1 + dt / HitCircle.FADE_OUT_TIME; 36 | } 37 | ctx.globalAlpha = Math.max(0, Math.min(opacity, 1)); 38 | 39 | this.drawCircle(this.position, ctx); 40 | this.drawText(this.position, this.combo, 0, ctx); 41 | if (dt >= 0) 42 | { 43 | this.drawApproach(dt, ctx); 44 | } 45 | }; 46 | HitCircle.prototype.drawCircle = function(position, ctx) 47 | { 48 | // HitCircle 49 | ctx.beginPath(); 50 | ctx.arc(position.x - this.stack * this.beatmap.stackOffset, 51 | position.y - this.stack * this.beatmap.stackOffset, 52 | this.beatmap.circleRadius - this.beatmap.circleBorder / 2, -Math.PI, Math.PI); 53 | ctx.shadowBlur = 0; 54 | ctx.fillStyle = this.color; 55 | ctx.fill(); 56 | // Overlay 57 | ctx.shadowBlur = this.beatmap.shadowBlur; 58 | ctx.strokeStyle = '#fff'; 59 | ctx.lineWidth = this.beatmap.circleBorder; 60 | ctx.stroke(); 61 | }; 62 | HitCircle.prototype.drawText = function(position, text, deg, ctx) 63 | { 64 | ctx.shadowBlur = this.beatmap.shadowBlur; 65 | ctx.fillStyle = '#fff'; 66 | ctx.save(); 67 | ctx.translate(position.x - this.stack * this.beatmap.stackOffset, 68 | position.y - this.stack * this.beatmap.stackOffset); 69 | ctx.rotate(deg); 70 | ctx.fillText(text, 0, 0); 71 | ctx.restore(); 72 | }; 73 | HitCircle.prototype.drawApproach = function(dt, ctx) 74 | { 75 | var scale = 1 + dt / this.beatmap.approachTime * 3; 76 | ctx.beginPath(); 77 | ctx.arc(this.position.x - this.stack * this.beatmap.stackOffset, 78 | this.position.y - this.stack * this.beatmap.stackOffset, 79 | this.beatmap.circleRadius * scale - this.beatmap.circleBorder / 2, -Math.PI, Math.PI); 80 | ctx.shadowBlur = 0; 81 | ctx.strokeStyle = this.color; 82 | ctx.lineWidth = this.beatmap.circleBorder / 2 * scale; 83 | ctx.stroke(); 84 | }; 85 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/spinner.js: -------------------------------------------------------------------------------- 1 | function Spinner(data, beatmap) 2 | { 3 | HitCircle.call(this, data, beatmap); 4 | 5 | this.endTime = data[5] | 0; 6 | this.duration = this.endTime - this.time; 7 | } 8 | Spinner.prototype = Object.create(HitCircle.prototype); 9 | Spinner.prototype.constructor = Spinner; 10 | Spinner.ID = 8; 11 | Spinner.FADE_IN_TIME = 500; 12 | Spinner.FADE_OUT_TIME = 200; 13 | Spinner.RADIUS = Beatmap.MAX_Y / 2; 14 | Spinner.BORDER_WIDTH = Spinner.RADIUS / 20; 15 | Spinner.prototype.draw = function(time, ctx) 16 | { 17 | var dt = this.time - time, 18 | opacity = 1; 19 | if (dt >= 0) 20 | { 21 | opacity = (this.beatmap.approachTime - dt) / Spinner.FADE_IN_TIME; 22 | } 23 | else if (time > this.endTime) 24 | { 25 | opacity = 1 - (time - this.endTime) / Spinner.FADE_OUT_TIME; 26 | } 27 | ctx.globalAlpha = Math.max(0, Math.min(opacity, 1)); 28 | ctx.save(); 29 | // Spinner 30 | ctx.beginPath(); 31 | ctx.arc(this.position.x, this.position.y, 32 | Spinner.RADIUS - Spinner.BORDER_WIDTH / 2, -Math.PI, Math.PI); 33 | ctx.globalCompositeOperation = 'destination-over'; 34 | ctx.shadowBlur = 0; 35 | ctx.fillStyle = 'rgba(0,0,0,.4)'; 36 | ctx.fill(); 37 | // Border 38 | ctx.shadowBlur = Spinner.BORDER_WIDTH; 39 | ctx.strokeStyle = '#fff'; 40 | ctx.lineWidth = Spinner.BORDER_WIDTH; 41 | ctx.stroke(); 42 | ctx.restore(); 43 | // Approach 44 | if (dt < 0 && time <= this.endTime) 45 | { 46 | var scale = 1 + dt / this.duration; 47 | ctx.beginPath(); 48 | ctx.arc(this.position.x, this.position.y, 49 | (Spinner.RADIUS - Spinner.BORDER_WIDTH / 2) * scale, -Math.PI, Math.PI); 50 | ctx.shadowBlur = 3; 51 | ctx.strokeStyle = '#fff'; 52 | ctx.lineWidth = (Spinner.BORDER_WIDTH / 2) * scale; 53 | ctx.stroke(); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/util.js: -------------------------------------------------------------------------------- 1 | Math.hypot = Math.hypot || function() 2 | { 3 | var power = 0; 4 | for (var i = 0; i < arguments.length; i++) 5 | { 6 | power += arguments[i] * arguments[i]; 7 | } 8 | return Math.sqrt(power); 9 | }; 10 | Math.lerp = Math.lerp || function(a, b, t) 11 | { 12 | if (a instanceof Point) 13 | { 14 | return new Point(Math.lerp(a.x, b.x, t), Math.lerp(a.y, b.y, t)); 15 | } 16 | return a + (b - a) * t; 17 | }; 18 | /** Calculate point C's direction by line AB 19 | * @return {Number} 20 | * 1 C is counter-clockwise to AB 21 | * 0 C is parallel to AB 22 | * -1 C is clockwise to AB 23 | */ 24 | Math.ccw = function(a, b, c) 25 | { 26 | var ret = (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y); 27 | if (ret > 0) 28 | { 29 | return 1; 30 | } 31 | if (ret < 0) 32 | { 33 | return -1; 34 | } 35 | return 0; 36 | }; 37 | Math.clamp = function(num, min, max) { 38 | if (num < min) return min; 39 | if (num > max) return max; 40 | return num; 41 | } 42 | // https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Polyfill 43 | if (!HTMLCanvasElement.prototype.toBlob) 44 | { 45 | Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { 46 | value: function (callback, type, quality) 47 | { 48 | 49 | var binStr = atob(this.toDataURL(type, quality).split(',')[1]), 50 | len = binStr.length, 51 | arr = new Uint8Array(len); 52 | 53 | for (var i = 0; i < len; i++) 54 | { 55 | arr[i] = binStr.charCodeAt(i); 56 | } 57 | 58 | callback(new Blob([ arr ], { type: type || 'image/png' })); 59 | } 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/js/viewbox.js: -------------------------------------------------------------------------------- 1 | /** viewbox stretches child to fit parent proportionally. 2 | * 3 | * viewbox must have a child that has two attributes, 'width' and 'height'. 4 | */ 5 | document.addEventListener('DOMContentLoaded', function() 6 | { 7 | window.addEventListener('resize', thunk); 8 | thunk(); 9 | 10 | function thunk() 11 | { 12 | var viewboxes = document.getElementsByClassName('x-viewbox'); 13 | for (var i = 0; i < viewboxes.length; i++) 14 | { 15 | onresize.call(viewboxes[i]); 16 | } 17 | } 18 | 19 | function onresize() 20 | { 21 | var parent = this.parentElement, 22 | child = this.firstChild, 23 | w, h, cw, ch, sw, sh, s; 24 | 25 | if (!parent) 26 | { 27 | w = window.innerWidth; 28 | h = window.innerHeight; 29 | } 30 | else 31 | { 32 | w = parent.scrollWidth; 33 | h = parent.scrollHeight; 34 | } 35 | 36 | if (child) 37 | { 38 | child.style.transformOrigin = '0 0'; 39 | 40 | cw = child.width; 41 | ch = child.height; 42 | } 43 | sw = w / cw; 44 | sh = h / ch; 45 | // if viewbox has no child that has proper attributes, 46 | // ignore size of child and re-calculate scale. 47 | if ((sw || sh) == NaN) 48 | { 49 | sw = w; 50 | sh = h; 51 | } 52 | 53 | if (sw > sh) 54 | { 55 | this.style.width = sh * cw + 'px'; 56 | this.style.height = h + 'px'; 57 | s = sh; 58 | } 59 | else 60 | { 61 | this.style.width = w + 'px'; 62 | this.style.height = sw * ch + 'px'; 63 | s = sw; 64 | } 65 | if (child) 66 | { 67 | child.style.transform = 'scale(' + s + ')'; 68 | } 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/catch_preview_templates/pic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ctb图片预览 网址后加#bid(-EZ/HR) 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/echarts.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from nonebot_plugin_htmlrender import template_to_pic 4 | 5 | template_path = str(Path(__file__).parent / "templates") 6 | 7 | 8 | async def draw_history_plot(pp_ls, date_ls, rank_ls, title) -> bytes: 9 | template_name = "pp_rank_line_chart.html" 10 | pic = await template_to_pic( 11 | template_path, 12 | template_name, 13 | {"pp_ls": pp_ls, "date_ls": date_ls, "rank_ls": rank_ls, "title": title}, 14 | ) 15 | return pic 16 | 17 | 18 | async def draw_bpa_plot(name, pp_ls, length_ls, mod_pp_ls, mapper_pp_ls) -> bytes: 19 | template_name = "bpa_chart.html" 20 | pic = await template_to_pic( 21 | template_path, 22 | template_name, 23 | {"name": name, "pp_ls": pp_ls, "length_ls": length_ls, "mod_pp_ls": mod_pp_ls, "mapper_pp_ls": mapper_pp_ls}, 24 | ) 25 | return pic 26 | 27 | 28 | # async def draw_strains_plot(time, strains) -> bytes: 29 | # template_name = "basic_line_chart.html" 30 | # pic = await template_to_pic( 31 | # template_path, 32 | # template_name, 33 | # {"time": time, "strains": strains}, 34 | # ) 35 | # return pic 36 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/match_history.py: -------------------------------------------------------------------------------- 1 | from playwright.async_api import ViewportSize 2 | from nonebot_plugin_htmlrender import get_new_page 3 | 4 | 5 | async def draw_match_history(match_id: str) -> bytes: 6 | async with get_new_page( 7 | viewport=ViewportSize(width=900, height=1), extra_http_headers={"Accept-Language": "zh-CN"} 8 | ) as page: 9 | await page.goto(f"https://osu.ppy.sh/community/matches/{match_id}", wait_until="networkidle") 10 | await page.add_style_tag( 11 | content=""" 12 | .mp-history-event, .mp-history-content__item--event, .nav2, .audio-player { 13 | display: none !important; 14 | } 15 | """ 16 | ) 17 | while True: 18 | try: 19 | await page.wait_for_selector(".show-more-link", timeout=3000) 20 | await page.evaluate("document.querySelector('.show-more-link').click()") 21 | except Exception: 22 | break 23 | pic = await page.screenshot(omit_background=True, full_page=True, quality=60, type="jpeg") 24 | return pic 25 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/templates/mod_chart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Awesome-pyecharts 4 | 5 | 6 | 7 |
8 | 61 | 62 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/draw/templates/pp_rank_line_chart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Awesome-pyecharts 4 | 5 | 6 | 7 |
8 | 58 | 59 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/exceptions.py: -------------------------------------------------------------------------------- 1 | class NetworkError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/info/__init__.py: -------------------------------------------------------------------------------- 1 | from .bg import get_bg 2 | from .bind import bind_user_info, update_users_info 3 | 4 | __all__ = ["get_bg", "bind_user_info", "update_users_info"] 5 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/info/bg.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import datetime 3 | from io import BytesIO 4 | from pathlib import Path 5 | from typing import Union 6 | 7 | from PIL import Image, UnidentifiedImageError 8 | 9 | from ..api import osu_api, get_map_bg 10 | from ..exceptions import NetworkError 11 | from ..file import re_map, map_path, download_osu 12 | 13 | 14 | async def get_bg(mapid: Union[str, int], setid: int = None) -> BytesIO: 15 | if not setid: 16 | info = await osu_api("map", map_id=mapid) 17 | setid: int = info["beatmapset_id"] 18 | set_path = map_path / str(setid) 19 | if not set_path.exists(): 20 | set_path.mkdir(parents=True, exist_ok=True) 21 | osu = map_path / str(setid) / f"{mapid}.osu" 22 | if not osu.exists(): 23 | await download_osu(setid, mapid) 24 | cover = re_map(osu) 25 | cover_path = map_path / str(setid) / cover 26 | if not cover_path.exists(): 27 | if bg := await get_map_bg(mapid, setid, cover): 28 | with open(cover_path, "wb") as f: 29 | f.write(bg.getvalue()) 30 | try: 31 | img = Image.open(cover_path).convert("RGBA") 32 | except UnidentifiedImageError: 33 | cover_path.unlink() 34 | raise NetworkError("暂时无法下载背景图片>︿<") 35 | byt = BytesIO() 36 | img.save(byt, "png") 37 | _ = asyncio.create_task(update_bg(cover_path)) 38 | return byt 39 | 40 | 41 | async def update_bg(cover_path: Path): 42 | creation_time = cover_path.stat().st_ctime 43 | creation_datetime = datetime.datetime.fromtimestamp(creation_time) 44 | time_diff = datetime.datetime.now() - creation_datetime 45 | if time_diff > datetime.timedelta(days=1): 46 | cover_path.unlink() 47 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/__init__.py: -------------------------------------------------------------------------------- 1 | from .mu import mu 2 | from .info import info 3 | from .bp import bp, tbp 4 | from .getbg import getbg 5 | from .match import match 6 | from .medal import medal 7 | from .score import score 8 | from .pr import pr, recent 9 | from .rating import rating 10 | from .history import history 11 | from .bind import bind, unbind 12 | from .map import bmap, osu_map 13 | from .osu_help import osu_help 14 | from .rank import group_pp_rank 15 | from .recommend import recommend 16 | from .url_match import url_match 17 | from .bp_analyze import bp_analyze 18 | from .update_mode import update_mode 19 | from .preview import generate_preview 20 | from .map_convert import change, convert, generate_full_ln 21 | from .update import update_pic, update_info, clear_background 22 | from .guess import hint, pic_hint, guess_pic, guess_audio, word_matcher, pic_word_matcher 23 | 24 | __all__ = [ 25 | "guess_audio", 26 | "guess_pic", 27 | "word_matcher", 28 | "pic_word_matcher", 29 | "hint", 30 | "pic_hint", 31 | "medal", 32 | "bp_analyze", 33 | "pr", 34 | "recent", 35 | "osu_help", 36 | "url_match", 37 | "recommend", 38 | "update_info", 39 | "update_pic", 40 | "clear_background", 41 | "generate_preview", 42 | "getbg", 43 | "bind", 44 | "unbind", 45 | "bp", 46 | "tbp", 47 | "info", 48 | "osu_map", 49 | "bmap", 50 | "mu", 51 | "score", 52 | "update_mode", 53 | "history", 54 | "convert", 55 | "change", 56 | "generate_full_ln", 57 | "match", 58 | "rating", 59 | "group_pp_rank", 60 | ] 61 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/bind.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from nonebot import on_command 4 | from nonebot.params import CommandArg 5 | from nonebot_plugin_alconna import UniMessage 6 | from nonebot.internal.adapter import Event, Message 7 | 8 | from ..api import get_uid_by_name 9 | from ..info import bind_user_info 10 | from ..exceptions import NetworkError 11 | from ..database import UserData, SbUserData 12 | 13 | bind = on_command("bind", priority=11, block=True) 14 | unbind = on_command("unbind", priority=11, block=True) 15 | sbbind = on_command("sbbind", priority=11, block=True) 16 | sbunbind = on_command("sbunbind", priority=11, block=True) 17 | lock = asyncio.Lock() 18 | 19 | 20 | @bind.handle() 21 | async def _bind(event: Event, name: Message = CommandArg()): 22 | name = name.extract_plain_text().strip() 23 | if not name: 24 | await UniMessage.text("请在指令后输入您的 osuid").finish(reply_to=True) 25 | async with lock: 26 | if user := await UserData.get_or_none(user_id=event.get_user_id()): 27 | await UniMessage.text(f"您已绑定{user.osu_name},如需要解绑请输入/unbind").finish(reply_to=True) 28 | try: 29 | msg = await bind_user_info("bind", name, event.get_user_id()) 30 | except NetworkError: 31 | await UniMessage.text(f"绑定失败,找不到叫 {name} 的人哦").finish(reply_to=True) 32 | await UniMessage.text(msg).finish(reply_to=True) 33 | 34 | 35 | @sbbind.handle() 36 | async def _(event: Event, name: Message = CommandArg()): 37 | name = name.extract_plain_text().strip() 38 | if not name: 39 | await UniMessage.text("请在指令后输入您的 osuid").finish(reply_to=True) 40 | async with lock: 41 | if user := await SbUserData.get_or_none(user_id=event.get_user_id()): 42 | await UniMessage.text(f"您已绑定{user.osu_name},如需要解绑请输入/sbunbind").finish(reply_to=True) 43 | try: 44 | uid = await get_uid_by_name(name, "ppysb") 45 | except NetworkError: 46 | await UniMessage.text(f"绑定失败,找不到叫 {name} 的人哦").finish(reply_to=True) 47 | await SbUserData.create(user_id=event.get_user_id(), osu_id=uid, osu_name=name) 48 | await UniMessage.text(f"成功绑定 ppysb 服务器用户: {name}").finish(reply_to=True) 49 | 50 | 51 | @unbind.handle() 52 | async def _unbind(event: Event): 53 | if _ := await UserData.get_or_none(user_id=event.get_user_id()): 54 | await UserData.filter(user_id=event.get_user_id()).delete() 55 | await UniMessage.text("解绑成功!").send(reply_to=True) 56 | else: 57 | await UniMessage.text("尚未绑定,无需解绑").send(reply_to=True) 58 | 59 | 60 | @sbunbind.handle() 61 | async def _(event: Event): 62 | if _ := await SbUserData.get_or_none(user_id=event.get_user_id()): 63 | await SbUserData.filter(user_id=event.get_user_id()).delete() 64 | await UniMessage.text("解绑成功!").send(reply_to=True) 65 | else: 66 | await UniMessage.text("尚未绑定,无需解绑").send(reply_to=True) 67 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/getbg.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from nonebot.params import CommandArg 3 | from nonebot.internal.adapter import Message 4 | from nonebot_plugin_alconna import UniMessage 5 | 6 | from ..info import get_bg 7 | from ..exceptions import NetworkError 8 | 9 | getbg = on_command("getbg", priority=11, block=True) 10 | 11 | 12 | @getbg.handle() 13 | async def _get_bg(bg: Message = CommandArg()): 14 | bg = bg.extract_plain_text().strip() 15 | if not bg: 16 | await UniMessage.text("请输入需要提取BG的地图ID").finish(reply_to=True) 17 | try: 18 | byt = await get_bg(bg) 19 | except NetworkError as e: 20 | await UniMessage.text(f"获取图片时 {str(e)}").finish(reply_to=True) 21 | await UniMessage.image(raw=byt).finish(reply_to=True) 22 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/history.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from nonebot import on_command 4 | from nonebot.typing import T_State 5 | from nonebot_plugin_alconna import UniMessage 6 | 7 | from ..utils import NGM 8 | from .utils import split_msg 9 | from ..database import InfoData, UserData 10 | from ..draw.echarts import draw_history_plot 11 | 12 | history = on_command("history", priority=11, block=True) 13 | 14 | 15 | @history.handle(parameterless=[split_msg()]) 16 | async def _info(state: T_State): 17 | if "error" in state: 18 | await UniMessage.text(state["error"]).finish(reply_to=True) 19 | data = InfoData.filter(osu_id=state["user"], osu_mode=state["mode"]) 20 | user = await UserData.filter(osu_id=state["user"]).first() 21 | if not user: 22 | await UniMessage.text(f"没有{state['user']}的数据哦").finish(reply_to=True) 23 | if state["day"] > 0: 24 | data = data.filter(date__gte=datetime.date.today() - datetime.timedelta(days=state["day"])) 25 | data = await data.order_by("date").all() 26 | pp_ls = [i.pp for i in data] 27 | date_ls = [str(i.date) for i in data] 28 | rank_ls = [i.g_rank for i in data] 29 | # 使用列表推导式筛选出 rank_ls 不为 None 的索引 30 | filtered_indices = [index for index, rank in enumerate(rank_ls) if rank is not None and rank != 0] 31 | 32 | # 根据筛选出的索引生成新的列表 33 | pp_ls = [pp_ls[i] for i in filtered_indices] 34 | date_ls = [date_ls[i] for i in filtered_indices] 35 | rank_ls = [rank_ls[i] for i in filtered_indices] 36 | byt = await draw_history_plot(pp_ls, date_ls, rank_ls, f"{user.osu_name} {NGM[state['mode']]} pp/rank history") 37 | await UniMessage.image(raw=byt).finish(reply_to=True) 38 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/info.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from nonebot.typing import T_State 3 | from nonebot_plugin_alconna import UniMessage 4 | 5 | from ..utils import NGM 6 | from ..draw import draw_info 7 | from .utils import split_msg 8 | from ..exceptions import NetworkError 9 | 10 | info = on_command("info", priority=11, block=True, aliases={"Info", "INFO"}) 11 | 12 | 13 | @info.handle(parameterless=[split_msg()]) 14 | async def _info(state: T_State): 15 | if "error" in state: 16 | await UniMessage.text(state["error"]).finish(reply_to=True) 17 | try: 18 | data = await draw_info(state["user"], NGM[state["mode"]], state["day"], state["source"]) 19 | except NetworkError as e: 20 | await UniMessage.text( 21 | f"在查找用户:{state['username']} {NGM[state['mode']]}模式 {state['day']}日内 成绩时{str(e)}" 22 | ).finish(reply_to=True) 23 | await UniMessage.image(raw=data).finish(reply_to=True) 24 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/map.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from nonebot.typing import T_State 3 | from nonebot_plugin_alconna import UniMessage 4 | 5 | from .utils import split_msg 6 | from ..exceptions import NetworkError 7 | from ..draw import draw_map_info, draw_bmap_info 8 | 9 | osu_map = on_command("map", priority=11, block=True) 10 | bmap = on_command("bmap", priority=11, block=True) 11 | 12 | 13 | @osu_map.handle(parameterless=[split_msg()]) 14 | async def _map(state: T_State): 15 | map_id = state["target"] 16 | mods = state["mods"] 17 | if not map_id: 18 | await UniMessage.text("请输入地图ID").finish(reply_to=True) 19 | try: 20 | m = await draw_map_info(map_id, mods, state["is_lazer"]) 21 | except NetworkError as e: 22 | mods = f" mod:{state['mods']}" if state["mods"] else "" 23 | await UniMessage.text(f"在查找地图mapid:{state['target']}{mods}时 {str(e)}").finish(reply_to=True) 24 | await UniMessage.image(raw=m).finish(reply_to=True) 25 | 26 | 27 | @bmap.handle(parameterless=[split_msg()]) 28 | async def _bmap(state: T_State): 29 | set_id = state["target"] 30 | if not set_id: 31 | await UniMessage.text("请输入setID").finish(reply_to=True) 32 | try: 33 | m = await draw_bmap_info(set_id) 34 | except NetworkError as e: 35 | mods = f" mod:{state['mods']}" if state["mods"] else "" 36 | await UniMessage.text(f"在查找地图setid:{state['target']}{mods}时 {str(e)}").finish(reply_to=True) 37 | await UniMessage.image(raw=m).finish(reply_to=True) 38 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/match.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from nonebot.params import CommandArg 3 | from nonebot.internal.adapter import Message 4 | from nonebot_plugin_alconna import UniMessage 5 | 6 | from ..draw.match_history import draw_match_history 7 | 8 | match = on_command("match", priority=11, block=True) 9 | 10 | 11 | @match.handle() 12 | async def _help(arg: Message = CommandArg()): 13 | arg = arg.extract_plain_text().strip() 14 | img = await draw_match_history(arg) 15 | await UniMessage.image(raw=img).finish(reply_to=True) 16 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/medal.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | from pathlib import Path 4 | 5 | from nonebot import on_command 6 | from nonebot.params import CommandArg 7 | from nonebot.internal.adapter import Message 8 | from nonebot_plugin_alconna import UniMessage 9 | 10 | from ..api import safe_async_get 11 | 12 | medal_data_path = Path(__file__).parent.parent / "osufile" / "medals" / "medals.json" 13 | with open(medal_data_path) as file: 14 | medal_json = json.load(file) 15 | 16 | 17 | medal = on_command("medal", aliases={"成就"}, priority=11, block=True) 18 | 19 | 20 | @medal.handle() 21 | async def _(msg: Message = CommandArg()): 22 | name = msg.extract_plain_text() 23 | data = await safe_async_get(f"https://osekai.net/medals/api/public/get_medal.php?medal={name}") 24 | medal_data = data.json() 25 | if "MedalID" not in medal_data: 26 | await medal.finish("没有找到欸,看看是不是名字打错了") 27 | words = "" 28 | if medal_data["Restriction"] != "NULL": 29 | words += f"限制模式:{medal_data['Restriction']}\n" 30 | words += "获得方式:\n" 31 | if medal_data["Name"] in medal_json: 32 | words += medal_json[medal_data["Name"]]["MedalSolution"] 33 | else: 34 | words += medal_data["Solution"] if medal_data["Solution"] else medal_data["Instructions"] 35 | table_regex = r"]*>(.*?)<\/table>" 36 | table_match = re.search(table_regex, words, re.DOTALL) 37 | if table_match: 38 | table_text = table_match.group(1) 39 | 40 | # 使用正则表达式匹配并提取表格行部分 41 | row_regex = r"]*>(.*?)<\/tr>" 42 | rows = re.findall(row_regex, table_text, re.DOTALL) 43 | 44 | result = "" 45 | 46 | # 遍历表格行并提取单元格内容 47 | for row in rows: 48 | # 使用正则表达式匹配并提取单元格部分 49 | cell_regex = r"]*>(.*?)<\/t[hd]>" 50 | cells = re.findall(cell_regex, row, re.DOTALL) 51 | for cell in cells: 52 | # 去除单元格内的HTML标签 53 | cell_text = re.sub(r"<[^>]*>", "", cell) 54 | result += cell_text + " " 55 | result += "\n" 56 | 57 | # 将提取的表格文字替换回原文中 58 | words = re.sub(table_regex, result, words) 59 | style_regex = r"]*>(.*?)<\/style>" 60 | words = re.sub(style_regex, "", words) 61 | words = re.sub(r"<[^>]+>", "", words) 62 | if medal_data["PackID"]: 63 | words += f"\nhttps://osu.ppy.sh/beatmaps/packs/{medal_data['PackID'].rstrip(',,,')}" 64 | await (UniMessage.image(url=medal_data["Link"]) + words).send(reply_to=True) 65 | if medal_data["beatmaps"]: 66 | msg = UniMessage() 67 | if len(medal_data["beatmaps"]) > 5: 68 | medal_data["beatmaps"] = medal_data["beatmaps"][:5] 69 | for beatmap in medal_data["beatmaps"]: 70 | msg += ( 71 | f"{beatmap['SongTitle']} [{beatmap['DifficultyName']}]\n{beatmap['Difficulty']}⭐\n" 72 | + f"https://osu.ppy.sh/b/{beatmap['BeatmapID']}" 73 | ) 74 | await msg.send() 75 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/mu.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from nonebot.typing import T_State 3 | from nonebot_plugin_alconna import UniMessage 4 | 5 | from .utils import split_msg 6 | 7 | mu = on_command("mu", priority=11, block=True) 8 | 9 | 10 | @mu.handle(parameterless=[split_msg()]) 11 | async def _mu(state: T_State): 12 | if "error" in state: 13 | await UniMessage.text(state["error"]).finish(reply_to=True) 14 | await UniMessage.text(f"https://osu.ppy.sh/u/{state['user']}").finish(reply_to=True) 15 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/osu_help.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from nonebot import on_command 4 | from nonebot.params import CommandArg 5 | from nonebot.internal.adapter import Message 6 | from nonebot_plugin_alconna import UniMessage 7 | 8 | osu_help = on_command("osuhelp", aliases={"OSUBot", "osubot"}, priority=11, block=True) 9 | 10 | with open(Path(__file__).parent.parent / "osufile" / "help.png", "rb") as f: 11 | img1 = f.read() 12 | with open(Path(__file__).parent.parent / "osufile" / "detail.png", "rb") as f: 13 | img2 = f.read() 14 | 15 | 16 | @osu_help.handle() 17 | async def _help(arg: Message = CommandArg()): 18 | arg = arg.extract_plain_text().strip() 19 | if arg == "detail": 20 | await UniMessage.image(raw=img2).finish(reply_to=True) 21 | await UniMessage.image(raw=img1).finish(reply_to=True) 22 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/osudl.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from nonebot.params import CommandArg 3 | from nonebot.internal.adapter import Message 4 | from nonebot_plugin_alconna import UniMessage 5 | 6 | from ..file import download_map 7 | 8 | osudl = on_command("osudl", priority=11, block=True) 9 | 10 | 11 | @osudl.handle() 12 | async def _osudl(setid: Message = CommandArg()): 13 | setid = setid.extract_plain_text().strip() 14 | if not setid or not setid.isdigit(): 15 | await UniMessage.text("请输入正确的地图ID").send(reply_to=True) 16 | osz_path = await download_map(int(setid)) 17 | file_path = osz_path.absolute() 18 | try: 19 | await UniMessage.file(path=file_path).send() 20 | except Exception: 21 | await UniMessage.text("上传文件失败,可能是群空间满或没有权限导致的").send(reply_to=True) 22 | finally: 23 | try: 24 | osz_path.unlink() 25 | except PermissionError: 26 | ... 27 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/preview.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from nonebot.typing import T_State 3 | from nonebot_plugin_alconna import UniMessage 4 | 5 | from ..utils import NGM 6 | from ..api import osu_api 7 | from .utils import split_msg 8 | from ..file import download_tmp_osu 9 | from ..exceptions import NetworkError 10 | from ..mania import generate_preview_pic 11 | from ..draw.catch_preview import draw_cath_preview 12 | from ..draw.taiko_preview import parse_map, map_to_image 13 | 14 | generate_preview = on_command("预览", aliases={"preview", "完整预览"}, priority=11, block=True) 15 | 16 | 17 | @generate_preview.handle(parameterless=[split_msg()]) 18 | async def _(state: T_State): 19 | osu_id = state["target"] 20 | if not osu_id or not osu_id.isdigit(): 21 | await UniMessage.text("请输入正确的地图mapID").finish(reply_to=True) 22 | try: 23 | data = await osu_api("map", map_id=int(osu_id)) 24 | except NetworkError as e: 25 | await UniMessage.text(f"查找map_id:{osu_id} 信息时 {str(e)}").finish(reply_to=True) 26 | if state["mode"] == "3": 27 | osu = await download_tmp_osu(osu_id) 28 | if state["_prefix"]["command"][0] == "完整预览": 29 | pic = await generate_preview_pic(osu, True) 30 | else: 31 | pic = await generate_preview_pic(osu) 32 | await UniMessage.image(raw=pic).finish(reply_to=True) 33 | elif state["mode"] == "2": 34 | pic = await draw_cath_preview(int(osu_id), data["beatmapset_id"], state["mods"]) 35 | await UniMessage.image(raw=pic).finish(reply_to=True) 36 | elif state["mode"] == "1": 37 | osu = await download_tmp_osu(osu_id) 38 | beatmap = parse_map(osu) 39 | pic = map_to_image(beatmap) 40 | await UniMessage.image(raw=pic).finish(reply_to=True) 41 | elif not (0 <= int(state["mode"]) <= 3): 42 | await UniMessage.text("模式应为0-3!\n0: std\n1:taiko\n2:ctb\n3: mania").finish() 43 | else: 44 | await UniMessage.text(f"{NGM[state['mode']]}模式暂不支持预览").finish() 45 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/rating.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from nonebot.params import CommandArg 3 | from nonebot.internal.adapter import Message 4 | from nonebot_plugin_alconna import UniMessage 5 | 6 | from ..draw.rating import draw_rating 7 | 8 | rating = on_command("rating", priority=11, block=True) 9 | 10 | 11 | @rating.handle() 12 | async def _(arg: Message = CommandArg()): 13 | arg = arg.extract_plain_text().strip() 14 | img = await draw_rating(arg) 15 | await UniMessage.image(raw=img).finish(reply_to=True) 16 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/recommend.py: -------------------------------------------------------------------------------- 1 | import re 2 | from random import shuffle 3 | 4 | from nonebot import on_command 5 | from nonebot.log import logger 6 | from nonebot.typing import T_State 7 | from expiringdict import ExpiringDict 8 | from nonebot.internal.matcher import Matcher 9 | from nonebot_plugin_alconna import UniMessage 10 | 11 | from .utils import split_msg 12 | from ..api import get_recommend, update_recommend, get_sayo_map_info 13 | 14 | recommend = on_command("推荐", priority=11, block=True, aliases={"recommend", "推荐铺面", "推荐谱面"}) 15 | recommend_cache = ExpiringDict(1000, 60 * 60 * 12) 16 | 17 | 18 | async def handle_recommend(state: T_State, matcher: type[Matcher]): 19 | user = state["user"] 20 | mode = state["mode"] 21 | mods = state["mods"] 22 | if mods == ["4K"]: 23 | key_count = "4" 24 | elif mods == ["7K"]: 25 | key_count = "7" 26 | else: 27 | key_count = "4,7" 28 | if mode == "1" or mode == "2": 29 | await matcher.finish("很抱歉,该模式暂不支持推荐") 30 | if not recommend_cache.get(user): 31 | recommend_cache[user] = set() 32 | await update_recommend(user) 33 | recommend_data = await get_recommend(user, mode, key_count) 34 | if not recommend_data.data.list: 35 | await matcher.finish("没有可以推荐的图哦,自己多打打喜欢玩的图吧") 36 | shuffle(recommend_data.data.list) 37 | for i in recommend_data.data.list: 38 | if i.id not in recommend_cache[user]: 39 | recommend_cache[user].add(i.id) 40 | recommend_map = i 41 | break 42 | else: 43 | await matcher.finish("今天已经没有可以推荐的图啦,明天再来吧") 44 | bid = int(re.findall("https://osu.ppy.sh/beatmaps/(.*)", recommend_map.mapLink)[0]) 45 | map_info = await get_sayo_map_info(bid, 1) 46 | sid = map_info.data.sid 47 | for i in map_info.data.bid_data: 48 | if i.bid == bid: 49 | bg = i.bg 50 | break 51 | else: 52 | bg = "" 53 | logger.error(f"出现问题: 有问题的是{bid}, {sid}") 54 | s = ( 55 | f"推荐的铺面是{recommend_map.mapName} ⭐{round(recommend_map.difficulty, 2)}\n{''.join(recommend_map.mod)}\n" 56 | f"预计pp为{round(recommend_map.predictPP, 2)}\n提升概率为{round(recommend_map.passPercent * 100, 2)}%\n" 57 | f"{recommend_map.mapLink}\nhttps://kitsu.moe/api/d/{sid}\n" 58 | f"https://txy1.sayobot.cn/beatmaps/download/novideo/{sid}" 59 | ) 60 | pic_url = f"https://dl.sayobot.cn/beatmaps/files/{sid}/{bg}" 61 | return pic_url, s 62 | 63 | 64 | @recommend.handle(parameterless=[split_msg()]) 65 | async def _(state: T_State): 66 | if "error" in state: 67 | await UniMessage.text(state["error"]).finish(reply_to=True) 68 | pic_url, s = await handle_recommend(state, recommend) 69 | await (UniMessage.image(url=pic_url) + s).finish(reply_to=True) 70 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/score.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from nonebot.typing import T_State 3 | from nonebot_plugin_alconna import UniMessage 4 | 5 | from ..utils import NGM 6 | from .utils import split_msg 7 | from ..draw import get_score_data 8 | from ..exceptions import NetworkError 9 | 10 | score = on_command("score", priority=11, block=True) 11 | 12 | 13 | @score.handle(parameterless=[split_msg()]) 14 | async def _score(state: T_State): 15 | if "error" in state: 16 | await UniMessage.text(state["error"]).finish(reply_to=True) 17 | if not state["target"]: 18 | await UniMessage.text("请输入谱面ID").finish(reply_to=True) 19 | try: 20 | data = await get_score_data( 21 | state["user"], 22 | state["is_lazer"], 23 | NGM[state["mode"]], 24 | state["mods"], 25 | state["target"], 26 | state["source"], 27 | ) 28 | except NetworkError as e: 29 | lazer_mode = "lazer模式下" if state["is_lazer"] else "stable模式下" 30 | mods = f" mod:{state['mods']}" if state["mods"] else "" 31 | await UniMessage.text( 32 | f"在查找用户:{state['username']} {NGM[state['mode']]}模式 {lazer_mode}{mods} 成绩时 {str(e)}" 33 | ).finish(reply_to=True) 34 | await UniMessage.image(raw=data).finish(reply_to=True) 35 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/update.py: -------------------------------------------------------------------------------- 1 | from nonebot.typing import T_State 2 | from nonebot_plugin_waiter import waiter 3 | from nonebot.internal.adapter import Event 4 | from nonebot import Bot, get_driver, on_command 5 | from nonebot_plugin_alconna.uniseg import Image 6 | from nonebot_plugin_alconna import Target, UniMsg, UniMessage, SupportScope, image_fetch 7 | 8 | from .utils import split_msg 9 | from ..database import UserData 10 | from ..file import save_info_pic, user_cache_path 11 | 12 | update_pic = on_command("更新背景", priority=11, block=True, aliases={"更改背景"}) 13 | update_info = on_command("update", priority=11, block=True, aliases={"更新信息"}) 14 | clear_background = on_command("清空背景", priority=11, block=True, aliases={"清除背景", "重置背景"}) 15 | 16 | 17 | @update_pic.handle() 18 | async def _(event: Event, bot: Bot, state: T_State): 19 | qq = event.get_user_id() 20 | user_data = await UserData.get_or_none(user_id=qq) 21 | state["user"] = user_data.osu_id if user_data else 0 22 | if state["user"] == 0: 23 | await UniMessage.text("该账号尚未绑定,请输入 /bind 用户名 绑定账号").finish(reply_to=True) 24 | await UniMessage.text("请发送图片").send(reply_to=True) 25 | 26 | @waiter(waits=["message"], keep_session=True) 27 | async def check(pic: UniMsg): 28 | return pic 29 | 30 | async for resp in check(timeout=60, retry=5, prompt="输入错误,请发送图片。剩余次数:{count}"): 31 | if resp is None: 32 | await update_pic.finish("等待超时") 33 | break 34 | if not resp.has(Image): 35 | continue 36 | pic = await image_fetch(event, bot, state, resp.get(Image)[0]) 37 | if not pic: 38 | await UniMessage.text("图片下载失败,请重新发送").send() 39 | continue 40 | await save_info_pic(str(state["user"]), pic) 41 | msg = f"收到自{event.get_user_id()}的更新背景申请" + UniMessage.image(raw=pic) 42 | for superuser in get_driver().config.superusers: 43 | await Target.user(superuser, SupportScope.qq_client).send(msg) 44 | await UniMessage.text("更新背景成功").send() 45 | break 46 | else: 47 | await update_pic.finish("输入失败") 48 | 49 | 50 | @update_info.handle(parameterless=[split_msg()]) 51 | async def _(state: T_State): 52 | if "error" in state: 53 | await UniMessage.text(state["error"]).send(reply_to=True) 54 | return 55 | user = state["user"] 56 | user_path = user_cache_path / str(user) 57 | for file_path in user_path.glob("icon*.*"): 58 | # 检查文件是否为图片格式 59 | if file_path.suffix.lower() in [".jpg", ".png", ".jpeg", ".gif", ".bmp", ".peg"]: 60 | file_path.unlink() 61 | await UniMessage.text("个人信息更新成功").send(reply_to=True) 62 | 63 | 64 | @clear_background.handle(parameterless=[split_msg()]) 65 | async def _(state: T_State): 66 | if "error" in state: 67 | await UniMessage.text(state["error"]).send(reply_to=True) 68 | return 69 | user = state["user"] 70 | path = user_cache_path / str(user) / "info.png" 71 | if path.exists(): 72 | path.unlink() 73 | await UniMessage.text("背景图片清除成功").send(reply_to=True) 74 | return 75 | else: 76 | await UniMessage.text("您还没有设置背景或已成功清除背景").send(reply_to=True) 77 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/update_mode.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from nonebot.params import CommandArg 3 | from nonebot_plugin_alconna import UniMessage 4 | from nonebot.internal.adapter import Event, Message 5 | 6 | from ..utils import NGM 7 | from ..database import UserData 8 | 9 | update_mode = on_command("更新模式", priority=11, block=True) 10 | update_lazer = on_command("切换lazer", priority=11, block=True) 11 | 12 | 13 | @update_mode.handle() 14 | async def _(event: Event, mode: Message = CommandArg()): 15 | mode = mode.extract_plain_text().strip() 16 | user = await UserData.get_or_none(user_id=event.get_user_id()) 17 | if not user: 18 | await UniMessage.text("该账号尚未绑定,请输入 /bind 用户名 绑定账号").finish(reply_to=True) 19 | elif not mode: 20 | await UniMessage.text("请输入需要更新内容的模式").finish(reply_to=True) 21 | if mode.isdigit() and 0 <= int(mode) < 4: 22 | await UserData.filter(user_id=event.get_user_id()).update(osu_mode=int(mode)) 23 | msg = f"已将默认模式更改为 {NGM[mode]}" 24 | else: 25 | msg = "请输入正确的模式 0-3" 26 | await UniMessage.text(msg).finish(reply_to=True) 27 | 28 | 29 | @update_lazer.handle() 30 | async def _(event: Event): 31 | user = await UserData.get_or_none(user_id=event.get_user_id()) 32 | if not user: 33 | await UniMessage.text("该账号尚未绑定,请输入 /bind 用户名 绑定账号").finish(reply_to=True) 34 | await UserData.filter(user_id=event.get_user_id()).update(lazer_mode=not user.lazer_mode) 35 | if user.lazer_mode: 36 | msg = "已关闭lazer模式" 37 | else: 38 | msg = "已开启lazer模式" 39 | await UniMessage.text(msg).finish(reply_to=True) 40 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/matcher/url_match.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_regex 2 | from nonebot.params import RegexGroup 3 | from nonebot_plugin_alconna import UniMessage 4 | 5 | from ..draw import draw_map_info 6 | 7 | url_match = on_regex("https://osu.ppy.sh/beatmapsets/([0-9]+)#([^/]+/[0-9]+)") 8 | url = "https://catboy.best/d/" 9 | url_1 = "https://osu.direct/api/d/" 10 | url_2 = "https://txy1.sayobot.cn/beatmaps/download/novideo/" 11 | 12 | 13 | @url_match.handle() 14 | async def _url(bid: tuple = RegexGroup()): 15 | beatmap_id = bid[1].split("/")[1] 16 | url_total = f"镜像站1:{url}{bid[0]}\n镜像站2:{url_1}{bid[0]}\n小夜镜像站:{url_2}{bid[0]}" 17 | try: 18 | m = await draw_map_info(beatmap_id, [], True) 19 | except Exception: 20 | return 21 | await (UniMessage.image(raw=m) + "\n" + url_total).finish(reply_to=True) 22 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/mods.py: -------------------------------------------------------------------------------- 1 | from .schema.score import Mod, UnifiedScore 2 | 3 | mods_dic = { 4 | "CL": 0, 5 | "NO": 0, 6 | "NF": 1 << 0, 7 | "EZ": 1 << 1, 8 | "TD": 1 << 2, 9 | "HD": 1 << 3, 10 | "HR": 1 << 4, 11 | "SD": 1 << 5, 12 | "DT": 1 << 6, 13 | "RX": 1 << 7, 14 | "HT": 1 << 8, 15 | "NC": 1 << 9, 16 | "FL": 1 << 10, 17 | "AT": 1 << 11, 18 | "SO": 1 << 12, 19 | "RX2": 1 << 13, 20 | "PF": 1 << 14, 21 | "4K": 1 << 15, 22 | "5K": 1 << 16, 23 | "6K": 1 << 17, 24 | "7K": 1 << 18, 25 | "8K": 1 << 19, 26 | "FI": 1 << 20, 27 | "RD": 1 << 21, 28 | "Cinema": 1 << 22, 29 | "TG": 1 << 23, 30 | "9K": 1 << 24, 31 | "KC": 1 << 25, 32 | "1K": 1 << 26, 33 | "3K": 1 << 27, 34 | "2K": 1 << 28, 35 | "V2": 1 << 29, 36 | "MR": 1 << 30, 37 | } 38 | 39 | 40 | def get_mods(mods: int) -> list[Mod]: 41 | dic = mods_dic.copy() 42 | dic.pop("CL") 43 | dic.pop("NO") 44 | return [Mod(acronym=mod) for mod, bit in dic.items() if mods & bit] + [Mod(acronym="CL")] 45 | 46 | 47 | def get_mods_list(score_ls: list[UnifiedScore], mods: list[str]) -> list[int]: 48 | if not mods: 49 | return list(range(len(score_ls))) 50 | mods_index_ls = [] 51 | for i, score in enumerate(score_ls): 52 | if score.mods and set(mods).issubset(j.acronym for j in score.mods): 53 | mods_index_ls.append(i) 54 | return mods_index_ls 55 | 56 | 57 | def calc_mods(mods: list[Mod]) -> int: 58 | num = 0 59 | for mod in mods: 60 | num ^= mods_dic.get(mod.acronym, 0) 61 | return num 62 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/network/__init__.py: -------------------------------------------------------------------------------- 1 | from .auto_retry import auto_retry 2 | 3 | __all__ = ["auto_retry"] 4 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/network/auto_retry.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from typing import TypeVar, Callable 3 | from typing_extensions import ParamSpec 4 | 5 | from nonebot import logger 6 | 7 | T = TypeVar("T") 8 | P = ParamSpec("P") 9 | 10 | 11 | def auto_retry(func: Callable[P, T]) -> Callable[P, T]: 12 | @wraps(func) 13 | async def wrapper(*args, **kwargs): 14 | for i in range(5): 15 | try: 16 | return await func(*args, **kwargs) 17 | except Exception as e: 18 | logger.warning(f"{e} | Retrying... {i + 1}/5") 19 | logger.error("多次重试失败,请检查网络连接") 20 | 21 | return wrapper 22 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/network/first_response.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from httpx import AsyncClient 4 | from nonebot import get_plugin_config 5 | 6 | from ..config import Config 7 | 8 | plugin_config = get_plugin_config(Config) 9 | proxy = plugin_config.osu_proxy 10 | 11 | 12 | async def fetch_url(client: AsyncClient, url): 13 | try: 14 | response = await client.get(url) 15 | return response 16 | except Exception: 17 | return None 18 | 19 | 20 | async def get_first_response(urls: list[str]): 21 | async with AsyncClient(proxy=proxy, follow_redirects=True) as client: 22 | tasks = [asyncio.create_task(fetch_url(client, url)) for url in urls] 23 | while tasks: 24 | done, _ = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) 25 | for task in done: 26 | response = task.result() 27 | if response is not None and response.status_code == 200: 28 | return response.content 29 | tasks = [task for task in tasks if not task.done()] 30 | return None 31 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/network/manager.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import asyncio 3 | from ..config import Config 4 | from httpx import AsyncClient 5 | from nonebot import get_plugin_config 6 | 7 | 8 | plugin_config = get_plugin_config(Config) 9 | proxy = plugin_config.osu_proxy 10 | 11 | 12 | class NetworkManager: 13 | def __init__(self): 14 | self._client = None 15 | self._lock = asyncio.Lock() 16 | 17 | async def get_client(self) -> AsyncClient: 18 | if self._client is None: 19 | async with self._lock: 20 | if self._client is None: 21 | self._client = AsyncClient( 22 | proxy=proxy, 23 | follow_redirects=True, 24 | limits=httpx.Limits(max_keepalive_connections=20, max_connections=100, keepalive_expiry=30), 25 | timeout=httpx.Timeout(30.0), 26 | ) 27 | return self._client 28 | 29 | 30 | network_manager = NetworkManager() 31 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/Best Performance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/Best Performance.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/History Score.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/History Score.jpg -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/beatmapinfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/beatmapinfo.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/convert.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/convert.jpg -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/detail.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AD.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AF.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AI.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AL.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AQ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AS.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AT.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AU.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AW.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AX.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/AZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/AZ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BA.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BB.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BD.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BF.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BH.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BI.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BJ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BL.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BN.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BQ (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BQ (1).png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BQ (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BQ (2).png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BS.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BT.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BV.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BW.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BY.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/BZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/BZ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CA.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CD.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CF.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CH.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CI.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CK.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CL.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CN.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CU.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CV.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CX.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CY.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/CZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/CZ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/DE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/DE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/DJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/DJ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/DK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/DK.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/DM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/DM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/DO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/DO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/DZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/DZ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/EC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/EC.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/EE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/EE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/EG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/EG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/EH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/EH.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/ER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/ER.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/ES.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/ES.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/ET.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/ET.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/FI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/FI.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/FJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/FJ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/FK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/FK.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/FO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/FO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/FR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/FR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GA.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GB.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GD.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GF.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GH.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GI.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GL.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GN.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GP.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GQ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GS.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GT.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GU.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GW.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/GY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/GY.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/HK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/HK.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/HM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/HM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/HN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/HN.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/HR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/HR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/HT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/HT.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/HU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/HU.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/ID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/ID.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/IE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/IE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/IL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/IL.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/IM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/IM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/IN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/IN.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/IO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/IO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/IQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/IQ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/IR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/IR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/IS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/IS.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/IT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/IT.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/JE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/JE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/JM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/JM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/JO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/JO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/JP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/JP.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/KE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/KE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/KG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/KG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/KH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/KH.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/KI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/KI.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/KM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/KM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/KN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/KN.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/KP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/KP.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/KR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/KR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/KW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/KW.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/KY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/KY.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/KZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/KZ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/LA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/LA.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/LB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/LB.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/LC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/LC.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/LI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/LI.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/LK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/LK.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/LR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/LR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/LS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/LS.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/LT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/LT.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/LU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/LU.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/LV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/LV.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/LY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/LY.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MA.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MC.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MD.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/ME.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/ME.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MF.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MH.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MK.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/ML.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/ML.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MN.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MP.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MQ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MS.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MT.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MU.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MV.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MW.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MX.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MY.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/MZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/MZ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/NA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/NA.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/NC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/NC.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/NE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/NE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/NF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/NF.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/NG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/NG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/NI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/NI.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/NL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/NL.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/NO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/NO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/NP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/NP.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/NR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/NR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/NU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/NU.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/NZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/NZ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/OM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/OM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PA.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PF.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PH.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PK.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PL.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PN.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PS.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PT.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PW.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/PY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/PY.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/QA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/QA.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/RE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/RE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/RO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/RO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/RS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/RS.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/RU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/RU.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/RW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/RW.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SA.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SB.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SC.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SD.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SH.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SI.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SJ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SK.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SL.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SN.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SS.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/ST.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/ST.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SV.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SY.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/SZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/SZ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TC.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TD.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TF.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TH.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TJ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TK.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TL.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TN.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TT.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TV.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TW.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/TZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/TZ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/UA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/UA.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/UG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/UG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/UM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/UM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/US.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/UY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/UY.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/UZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/UZ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/VA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/VA.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/VC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/VC.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/VE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/VE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/VG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/VG.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/VI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/VI.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/VN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/VN.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/VU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/VU.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/WF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/WF.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/WS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/WS.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/YE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/YE.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/YT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/YT.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/ZA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/ZA.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/ZM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/ZM.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/flags/ZW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/flags/ZW.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/fonts/Extra.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/fonts/Extra.otf -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/fonts/Torus Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/fonts/Torus Regular.otf -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/fonts/Torus SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/fonts/Torus SemiBold.otf -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/fonts/Venera.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/fonts/Venera.otf -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/help.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/info.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/maniabeatmapinfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/maniabeatmapinfo.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/match/mplink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/match/mplink.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/match/mplink_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/match/mplink_map.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/match/team_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/match/team_blue.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/match/team_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/match/team_red.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/4K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/4K.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/5K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/5K.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/6K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/6K.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/7K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/7K.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/8K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/8K.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/9K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/9K.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/AP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/AP.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/CL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/CL.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/DT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/DT.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/EZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/EZ.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/FI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/FI.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/FL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/FL.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/HD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/HD.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/HR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/HR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/HT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/HT.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/MR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/MR.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/NC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/NC.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/NF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/NF.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/PF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/PF.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/RX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/RX.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/SD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/SD.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/SO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/SO.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/TD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/TD.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/mods/V2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/mods/V2.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/pfm_ctb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/pfm_ctb.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/pfm_mania.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/pfm_mania.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/pfm_std.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/pfm_std.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/pfm_taiko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/pfm_taiko.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/ranking/ranking-A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/ranking/ranking-A.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/ranking/ranking-B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/ranking/ranking-B.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/ranking/ranking-C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/ranking/ranking-C.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/ranking/ranking-D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/ranking/ranking-D.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/ranking/ranking-F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/ranking/ranking-F.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/ranking/ranking-S.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/ranking/ranking-S.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/ranking/ranking-SH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/ranking/ranking-SH.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/ranking/ranking-X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/ranking/ranking-X.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/ranking/ranking-XH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/ranking/ranking-XH.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/work/bmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/work/bmap.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/work/center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/work/center.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/work/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/work/left.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/work/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/work/right.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/work/stardiff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/work/stardiff.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/work/stars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/work/stars.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/work/stars_expertplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/work/stars_expertplus.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/osufile/work/suppoter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaowan233/nonebot-plugin-osubot/bc65bc8109156c8aac09a0516f3f7b191f2f8e90/nonebot_plugin_osubot/osufile/work/suppoter.png -------------------------------------------------------------------------------- /nonebot_plugin_osubot/pp.py: -------------------------------------------------------------------------------- 1 | import math 2 | import importlib.metadata 3 | 4 | from rosu_pp_py import Beatmap, Strains, GameMode, Performance, PerformanceAttributes 5 | 6 | from .schema.score import UnifiedScore 7 | 8 | is_v2 = importlib.metadata.version("pydantic").startswith("2") 9 | 10 | 11 | def cal_pp(score: UnifiedScore, path: str, is_lazer: bool) -> PerformanceAttributes: 12 | beatmap = Beatmap(path=path) 13 | convert_mode(score, beatmap) 14 | c = Performance( 15 | accuracy=score.accuracy, 16 | n_katu=score.statistics.good, 17 | n_geki=score.statistics.perfect, 18 | combo=score.max_combo, 19 | misses=score.statistics.miss, 20 | n50=score.statistics.meh, 21 | n100=score.statistics.ok, 22 | n300=score.statistics.great, 23 | small_tick_hits=score.statistics.small_tick_hit, 24 | large_tick_hits=score.statistics.large_tick_hit, 25 | slider_end_hits=score.statistics.slider_tail_hit, 26 | mods=[mod.model_dump() for mod in score.mods] if is_v2 else [mod.dict() for mod in score.mods], 27 | lazer=cal_lazer(score, is_lazer), 28 | ) 29 | return c.calculate(beatmap) 30 | 31 | 32 | def get_if_pp_ss_pp(score: UnifiedScore, path: str, is_lazer: bool) -> tuple: 33 | beatmap = Beatmap(path=path) 34 | convert_mode(score, beatmap) 35 | total = beatmap.n_objects 36 | passed = score.statistics.great + score.statistics.miss + score.statistics.ok + score.statistics.meh 37 | n300 = score.statistics.great + total - passed 38 | count_hits = total - score.statistics.miss 39 | ratio = 1 - n300 / count_hits 40 | new100s = int(ratio * score.statistics.miss) 41 | n300 += score.statistics.miss - new100s 42 | n100 = new100s + score.statistics.ok 43 | n300 = max(n300, 0) # 确保n300不会为负数 只有在 std 需要计算正确的 ifpp 44 | c = Performance( 45 | n50=score.statistics.meh, 46 | n100=n100, 47 | n300=n300, 48 | mods=[mod.model_dump() for mod in score.mods] if is_v2 else [mod.dict() for mod in score.mods], 49 | lazer=cal_lazer(score, is_lazer), 50 | ) 51 | if_pp = c.calculate(beatmap).pp 52 | c = Performance( 53 | accuracy=100, 54 | mods=[mod.model_dump() for mod in score.mods] if is_v2 else [mod.dict() for mod in score.mods], 55 | lazer=is_lazer, 56 | ) 57 | ss_pp = c.calculate(beatmap).pp 58 | if math.isnan(if_pp): 59 | return "nan", "nan" 60 | return str(int(round(if_pp, 0))), str(int(round(ss_pp, 0))) 61 | 62 | 63 | def get_ss_pp(path: str, mods: int, is_lazer) -> PerformanceAttributes: 64 | beatmap = Beatmap(path=path) 65 | c = Performance(accuracy=100, mods=mods, lazer=is_lazer) 66 | ss_pp_info = c.calculate(beatmap) 67 | return ss_pp_info 68 | 69 | 70 | def get_strains(path: str, mods: int) -> Strains: 71 | beatmap = Beatmap(path=path) 72 | c = Performance(accuracy=100, mods=mods) 73 | strains = c.difficulty().strains(beatmap) 74 | return strains 75 | 76 | 77 | def convert_mode(score: UnifiedScore, beatmap: Beatmap): 78 | if score.ruleset_id in {0, 4, 8}: 79 | mode = GameMode.Osu 80 | elif score.ruleset_id in {1, 5}: 81 | mode = GameMode.Taiko 82 | elif score.ruleset_id in {2, 6}: 83 | mode = GameMode.Catch 84 | else: 85 | mode = GameMode.Mania 86 | beatmap.convert(mode) 87 | 88 | 89 | def cal_lazer(score: UnifiedScore, is_lazer: bool): 90 | return is_lazer and not any(mod.acronym == "CL" for mod in score.mods) 91 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/schema/__init__.py: -------------------------------------------------------------------------------- 1 | from .user import User, Badge 2 | from .match import Game, Match 3 | from .alphaosu import RecommendData 4 | from .sayo_beatmap import SayoBeatmap 5 | from .score import Score, NewScore, BeatmapUserScore 6 | from .beatmap import Beatmap, Beatmapset, SeasonalBackgrounds 7 | 8 | __all__ = [ 9 | "Score", 10 | "BeatmapUserScore", 11 | "NewScore", 12 | "User", 13 | "Badge", 14 | "Beatmap", 15 | "Beatmapset", 16 | "SeasonalBackgrounds", 17 | "SayoBeatmap", 18 | "RecommendData", 19 | "Match", 20 | "Game", 21 | ] 22 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/schema/alphaosu.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from .basemodel import Base 4 | 5 | 6 | class ListData(Base): 7 | accurate: bool 8 | currentMod: Optional[list[str]] = None 9 | currentPP: Optional[float] = None 10 | currentScore: Optional[int] = None 11 | currentScoreLink: Optional[str] = None 12 | difficulty: float 13 | id: str 14 | mapCoverUrl: str 15 | mapLink: str 16 | mapName: str 17 | mod: list[str] 18 | newRecordPercent: float 19 | passPercent: float 20 | ppIncrement: float 21 | ppIncrementExpect: float 22 | predictPP: float 23 | 24 | 25 | class Data(Base): 26 | next: int 27 | prev: int 28 | total: int 29 | list: list[ListData] 30 | 31 | 32 | class RecommendData(Base): 33 | code: int 34 | message: str 35 | success: bool 36 | data: Optional[Data] = None 37 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/schema/basemodel.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class Base(BaseModel): 5 | class Config: 6 | extra = "ignore" 7 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/schema/beatmap.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Optional 2 | 3 | from .user import User 4 | from .basemodel import Base 5 | 6 | 7 | class Covers(Base): 8 | cover: str 9 | card: str 10 | list: str 11 | slimcover: str 12 | 13 | 14 | class Gds(Base): 15 | id: int 16 | username: str 17 | 18 | 19 | class BeatmapsetCompact(Base): 20 | artist: str 21 | artist_unicode: str 22 | covers: Covers 23 | creator: str 24 | favourite_count: int 25 | id: int 26 | nsfw: bool 27 | play_count: int 28 | preview_url: str 29 | source: str 30 | title: str 31 | title_unicode: str 32 | beatmapset_id: Optional[int] = None 33 | user_id: int 34 | status: str 35 | video: bool 36 | converts: Optional[str] = None 37 | description: Optional[str] = None 38 | has_favourited: Optional[bool] = None 39 | language: Optional[str] = None 40 | user: Optional[User] = None 41 | total_length: Optional[int] = None 42 | 43 | 44 | class Beatmapset(BeatmapsetCompact): 45 | bpm: Optional[float] = None 46 | can_be_hyped: Optional[bool] = None 47 | ranked: Optional[int] = None 48 | ranked_date: Optional[str] = None 49 | tags: Optional[str] = None 50 | 51 | 52 | class BeatmapCompact(Base): 53 | beatmapset_id: int 54 | difficulty_rating: float 55 | id: int 56 | mode: Literal["fruits", "mania", "osu", "taiko"] 57 | status: str 58 | total_length: float 59 | user_id: int 60 | version: str 61 | beatmapset: Optional[Beatmapset] = None 62 | checksum: Optional[str] = None 63 | max_combo: Optional[int] = None 64 | version: str 65 | 66 | 67 | class Beatmap(BeatmapCompact): 68 | accuracy: float 69 | ar: float 70 | bpm: Optional[float] = None 71 | convert: bool 72 | count_circles: int 73 | count_sliders: int 74 | count_spinners: int 75 | cs: float 76 | deleted_at: Optional[str] = None 77 | drain: float 78 | hit_length: int 79 | is_scoreable: bool 80 | last_updated: str 81 | mode_int: int 82 | passcount: int 83 | playcount: int 84 | ranked: int 85 | url: str 86 | owners: Optional[list[Gds]] = None 87 | 88 | 89 | class BackgroundsAttributes(Base): 90 | url: str 91 | user: dict 92 | 93 | 94 | class SeasonalBackgrounds(Base): 95 | ends_at: str 96 | backgrounds: list[BackgroundsAttributes] 97 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/schema/match.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from .user import User 4 | from .score import Score 5 | from .basemodel import Base 6 | from .beatmap import BeatmapCompact 7 | 8 | 9 | class Game(Base): 10 | beatmap_id: int 11 | mods: list[str] 12 | beatmap: BeatmapCompact 13 | scores: list[Score] 14 | team_type: str 15 | 16 | 17 | class Detail(Base): 18 | type: str 19 | 20 | 21 | class Event(Base): 22 | id: int 23 | game: Optional[Game] = None 24 | detail: Detail 25 | timestamp: str 26 | 27 | 28 | class Match(Base): 29 | match: dict 30 | events: list[Event] 31 | users: list[User] 32 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/schema/ppysb/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Beatmap(BaseModel): 7 | md5: str 8 | id: int 9 | set_id: int 10 | artist: str 11 | title: str 12 | version: str 13 | creator: str 14 | last_update: str 15 | total_length: int 16 | max_combo: int 17 | status: int 18 | plays: int 19 | passes: int 20 | mode: int 21 | bpm: float 22 | cs: float 23 | od: float 24 | ar: float 25 | hp: float 26 | diff: float 27 | 28 | 29 | class Score(BaseModel): 30 | id: int 31 | score: int 32 | pp: float 33 | acc: float 34 | max_combo: int 35 | mods: int 36 | n300: Optional[int] = 0 37 | n100: Optional[int] = 0 38 | n50: Optional[int] = 0 39 | nmiss: Optional[int] = 0 40 | ngeki: Optional[int] = 0 41 | nkatu: Optional[int] = 0 42 | grade: str 43 | status: int 44 | mode: int 45 | play_time: str 46 | time_elapsed: int 47 | perfect: int 48 | beatmap: Optional[Beatmap] = None 49 | 50 | 51 | class ScoresResponse(BaseModel): 52 | status: str 53 | scores: list[Score] 54 | 55 | 56 | class V2ScoresResponse(BaseModel): 57 | status: str 58 | data: list[Score] 59 | 60 | 61 | class Info(BaseModel): 62 | id: int 63 | name: str 64 | safe_name: str 65 | priv: int 66 | country: str 67 | silence_end: int 68 | donor_end: int 69 | creation_time: int 70 | latest_activity: int 71 | clan_id: int 72 | clan_priv: int 73 | preferred_mode: int 74 | play_style: int 75 | 76 | 77 | class Player(BaseModel): 78 | info: Info 79 | stats: dict 80 | 81 | 82 | class InfoResponse(BaseModel): 83 | status: str 84 | player: Player 85 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/schema/sayo_beatmap.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from .basemodel import Base 4 | 5 | 6 | class BidData(Base): 7 | bid: int 8 | mode: int 9 | version: str 10 | length: int 11 | CS: float 12 | AR: float 13 | OD: float 14 | HP: float 15 | star: float 16 | aim: float 17 | speed: float 18 | hit300window: float 19 | pp: float 20 | pp_aim: float 21 | pp_speed: float 22 | pp_acc: float 23 | circles: int 24 | sliders: int 25 | spinners: int 26 | maxcombo: int 27 | playcount: int 28 | passcount: int 29 | bg: str 30 | audio: str 31 | 32 | 33 | class MapInfo(Base): 34 | sid: int 35 | local_update: int 36 | bids_amount: int 37 | approved: int 38 | title: str 39 | artist: str 40 | titleU: str 41 | artistU: str 42 | creator: str 43 | creator_id: int 44 | source: str 45 | last_update: int 46 | approved_date: int 47 | bpm: float 48 | favourite_count: int 49 | video: int 50 | storyboard: int 51 | tags: str 52 | language: int 53 | genre: int 54 | bid_data: list[BidData] 55 | 56 | 57 | class SayoBeatmap(Base): 58 | status: int 59 | data: Optional[MapInfo] = None 60 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/schema/score.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Literal, Optional 3 | 4 | from pydantic.fields import Field 5 | 6 | from .basemodel import Base 7 | from .user import UserCompact 8 | from .beatmap import Beatmap, Beatmapset 9 | 10 | 11 | class Statistics(Base): 12 | count_50: Optional[int] = None 13 | count_100: Optional[int] = None 14 | count_300: Optional[int] = None 15 | count_geki: Optional[int] = None 16 | count_katu: Optional[int] = None 17 | count_miss: Optional[int] = None 18 | 19 | 20 | class Score(Base): 21 | id: Optional[int] = None 22 | best_id: Optional[int] = None 23 | user_id: int 24 | accuracy: float 25 | mods: list[str] 26 | score: int 27 | max_combo: int 28 | perfect: int 29 | statistics: Statistics 30 | passed: bool 31 | pp: Optional[float] = None 32 | rank: str 33 | created_at: str 34 | mode: Literal["fruits", "mania", "osu", "taiko"] 35 | mode_int: int 36 | beatmap: Optional[Beatmap] = None 37 | beatmapset: Optional[Beatmapset] = None 38 | match: Optional[dict] = None 39 | 40 | 41 | class BeatmapUserScore(Base): 42 | position: int 43 | score: Score 44 | 45 | 46 | class NewStatistics(Base): 47 | great: Optional[int] = Field(default=0) 48 | slider_tail_hit: Optional[int] = Field(default=0) 49 | large_tick_hit: Optional[int] = Field(default=0) 50 | small_tick_hit: Optional[int] = Field(default=0) 51 | small_tick_miss: Optional[int] = Field(default=0) 52 | miss: Optional[int] = Field(default=0) 53 | ok: Optional[int] = Field(default=0) 54 | meh: Optional[int] = Field(default=0) 55 | good: Optional[int] = Field(default=0) 56 | perfect: Optional[int] = Field(default=0) 57 | 58 | 59 | class Mod(Base): 60 | acronym: str 61 | settings: Optional[dict] = None 62 | 63 | 64 | class NewScore(Base): 65 | accuracy: float 66 | beatmap_id: int 67 | best_id: Optional[int] = None 68 | build_id: Optional[int] = None 69 | ended_at: str 70 | has_replay: bool 71 | id: int 72 | is_perfect_combo: bool 73 | legacy_perfect: bool 74 | legacy_score_id: Optional[int] = None 75 | legacy_total_score: int 76 | max_combo: int 77 | maximum_statistics: Optional[Statistics] = None 78 | mods: list[Mod] 79 | passed: bool 80 | playlist_item_id: Optional[int] = None 81 | pp: Optional[float] = None 82 | preserve: bool 83 | rank: str 84 | ranked: bool 85 | room_id: Optional[int] = None 86 | ruleset_id: int 87 | started_at: Optional[str] = None 88 | statistics: Optional[NewStatistics] = None 89 | total_score: int 90 | type: str 91 | user_id: int 92 | beatmap: Optional[Beatmap] = None 93 | beatmapset: Optional[Beatmapset] = None 94 | # current_user_attributes: Optional[int] 95 | position: Optional[int] = None 96 | rank_country: Optional[int] = None 97 | rank_global: Optional[int] = None 98 | user: Optional[UserCompact] = None 99 | 100 | 101 | class UnifiedBeatmap(Base): 102 | id: int 103 | set_id: int 104 | artist: str 105 | title: str 106 | version: str 107 | creator: str 108 | total_length: int 109 | mode: int 110 | bpm: float 111 | cs: float 112 | od: float 113 | ar: float 114 | hp: float 115 | stars: float 116 | user_id: Optional[int] = None 117 | 118 | 119 | class UnifiedScore(Base): 120 | mods: list[Mod] 121 | ruleset_id: int 122 | rank: str 123 | accuracy: float 124 | total_score: int 125 | legacy_total_score: Optional[int] = None 126 | ended_at: datetime.datetime 127 | max_combo: int 128 | statistics: NewStatistics 129 | beatmap: Optional[UnifiedBeatmap] = None 130 | passed: bool 131 | pp: Optional[float] = None 132 | beatmapset: Optional[Beatmapset] = None 133 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/schema/user.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Optional 2 | 3 | from .basemodel import Base 4 | 5 | 6 | class Badge(Base): 7 | awarded_at: str 8 | description: str 9 | image_url: str 10 | url: str 11 | 12 | 13 | class GradeCounts(Base): 14 | ssh: int 15 | ss: int 16 | sh: int 17 | s: int 18 | a: int 19 | 20 | 21 | class Level(Base): 22 | current: int 23 | progress: int 24 | 25 | 26 | class Variant(Base): 27 | mode: str 28 | variant: str 29 | country_rank: Optional[int] = None 30 | global_rank: Optional[int] = None 31 | pp: Optional[float] = None 32 | 33 | 34 | class UserStatistics(Base): 35 | grade_counts: GradeCounts 36 | hit_accuracy: float 37 | is_ranked: bool 38 | level: Level 39 | maximum_combo: int 40 | play_count: int 41 | play_time: int 42 | pp: float 43 | ranked_score: int 44 | replays_watched_by_others: int 45 | total_hits: int 46 | total_score: int 47 | global_rank: Optional[int] = None 48 | country_rank: Optional[int] = None 49 | badges: Optional[list[Badge]] = None 50 | variants: Optional[list[Variant]] = None 51 | 52 | 53 | class UserCompact(Base): 54 | avatar_url: str 55 | country_code: str 56 | default_group: str 57 | id: int 58 | is_active: bool 59 | is_bot: bool 60 | is_deleted: bool 61 | is_online: bool 62 | is_supporter: bool 63 | last_visit: Optional[str] = None 64 | profile_colour: Optional[str] = None 65 | username: str 66 | statistics: Optional[UserStatistics] = None 67 | 68 | 69 | class StatisticsRulesets(Base): 70 | osu: Optional[UserStatistics] = None 71 | taiko: Optional[UserStatistics] = None 72 | fruits: Optional[UserStatistics] = None 73 | mania: Optional[UserStatistics] = None 74 | 75 | 76 | class User(UserCompact): 77 | cover_url: Optional[str] = None 78 | has_supported: Optional[bool] = None 79 | join_date: Optional[str] = None 80 | location: Optional[str] = None 81 | occupation: Optional[str] = None 82 | playmode: Optional[Literal["fruits", "mania", "osu", "taiko"]] = None 83 | playstyle: Optional[list[str]] = None 84 | badges: Optional[list[Badge]] = None 85 | statistics_rulesets: Optional[StatisticsRulesets] = None 86 | 87 | 88 | class Team(Base): 89 | flag_url: Optional[str] = None 90 | id: int 91 | name: str 92 | short_name: str 93 | 94 | 95 | class UnifiedUser(Base): 96 | avatar_url: str 97 | country_code: str 98 | id: int 99 | username: str 100 | is_supporter: bool 101 | badges: Optional[list[Badge]] = None 102 | statistics: Optional[UserStatistics] = None 103 | team: Optional[Team] = None 104 | -------------------------------------------------------------------------------- /nonebot_plugin_osubot/utils/__init__.py: -------------------------------------------------------------------------------- 1 | GM = { 2 | 0: "osu", 3 | 1: "taiko", 4 | 2: "fruits", 5 | 3: "mania", 6 | 4: "osu", 7 | 5: "taiko", 8 | 6: "fruits", 9 | 8: "osu", 10 | } 11 | NGM = { 12 | "0": "osu", 13 | "1": "taiko", 14 | "2": "fruits", 15 | "3": "mania", 16 | "4": "rxosu", 17 | "5": "rxtaiko", 18 | "6": "rxfruits", 19 | "8": "aposu", 20 | } 21 | GMN = { 22 | "osu": "Std", 23 | "taiko": "Taiko", 24 | "fruits": "Ctb", 25 | "mania": "Mania", 26 | "rxosu": "RX Std", 27 | "rxtaiko": "RX Taiko", 28 | "rxfruits": "RX Ctb", 29 | "aposu": "AP Std", 30 | } 31 | FGM = { 32 | "osu": 0, 33 | "taiko": 1, 34 | "fruits": 2, 35 | "mania": 3, 36 | "rxosu": 4, 37 | "rxtaiko": 5, 38 | "rxfruits": 6, 39 | "aposu": 8, 40 | } 41 | 42 | 43 | def mods2list(args: str) -> list: 44 | args = args.replace(" ", "").replace(",", "").replace(",", "") 45 | args = args.upper() 46 | return [args[i : i + 2] for i in range(0, len(args), 2)] 47 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "nonebot-plugin-osubot" 3 | version = "6.22.3" 4 | description = "OSUbot in NoneBot2" 5 | authors = [ 6 | {name = "yaowan233", email = "572473053@qq.com"}, 7 | ] 8 | license = {text = "AGPL-3.0"} 9 | dependencies = [ 10 | "nonebot2>=2.3.0", 11 | "pydantic>=1.10.0,<3.0.0,!=2.5.0,!=2.5.1", 12 | "nonebot-plugin-alconna>=0.46.4", 13 | "nonebot-plugin-session~=0.3", 14 | "pillow>=9.2.0", 15 | "expiringdict>=1.2.2", 16 | "nonebot-plugin-apscheduler>=0.4.0", 17 | "tortoise-orm>=0.20.0", 18 | "nonebot-plugin-tortoise-orm>=0.1.4", 19 | "rosu-pp-py==3.0.0", 20 | "reamber>=0.2.1", 21 | "httpx>=0.23.3", 22 | "typing_extensions>=4.11.0", 23 | "matplotlib>=3.7.1", 24 | "nonebot-plugin-htmlrender>=0.3.1", 25 | "nonebot-plugin-waiter>=0.6.1", 26 | ] 27 | requires-python = ">=3.9.8,<3.13" 28 | readme = "README.md" 29 | 30 | [project.urls] 31 | Homepage = "https://github.com/yaowan233/nonebot-plugin-osubot" 32 | Repository = "https://github.com/yaowan233/nonebot-plugin-osubot" 33 | 34 | [build-system] 35 | requires = ["pdm-pep517>=0.12.0"] 36 | build-backend = "pdm.pep517.api" 37 | 38 | [tool.pdm.dev-dependencies] 39 | test = [ 40 | "isort>=5.13.2", 41 | "black>=24.4.2", 42 | "loguru>=0.7.2", 43 | "ruff>=0.4.2", 44 | "nonemoji>=0.1.4", 45 | "nonebug>=0.3.5", 46 | "pytest-asyncio>=0.21.1", 47 | "fastapi>=0.103.1", 48 | "uvicorn>=0.23.2", 49 | "pre-commit>=3.7.1", 50 | ] 51 | 52 | [tool.black] 53 | line-length = 120 54 | target-version = ["py39", "py310", "py311", "py312"] 55 | include = '\.pyi?$' 56 | extend-exclude = ''' 57 | ''' 58 | 59 | [tool.isort] 60 | profile = "black" 61 | line_length = 120 62 | length_sort = true 63 | skip_gitignore = true 64 | force_sort_within_sections = true 65 | extra_standard_library = ["typing_extensions"] 66 | 67 | [tool.ruff] 68 | line-length = 120 69 | target-version = "py39" 70 | 71 | [tool.ruff.lint] 72 | select = ["E", "W", "F", "UP", "C", "T", "PYI", "PT", "Q"] 73 | ignore = ["C901", "T201", "E731", "E402"] 74 | --------------------------------------------------------------------------------