├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml └── workflows │ ├── pypi-publish.yml │ └── ruff.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .python-version ├── LICENSE ├── README.md ├── docs └── example.png ├── nonebot_plugin_skland ├── __init__.py ├── api │ ├── __init__.py │ ├── login.py │ └── request.py ├── config.py ├── db_handler.py ├── download.py ├── exception.py ├── filters.py ├── hook.py ├── migrations │ ├── 02e0764f579e_fix_model_type.py │ └── 997049a57a3a_first_revision.py ├── model.py ├── render.py ├── resources │ ├── fonts │ │ ├── Akrobat-Bold.otf │ │ ├── Bender-Bold.otf │ │ └── Bender.otf │ ├── images │ │ ├── ark_card │ │ │ ├── assist_title.png │ │ │ ├── building │ │ │ │ ├── dorm.png │ │ │ │ ├── labor.png │ │ │ │ ├── manufact.png │ │ │ │ ├── meeting.png │ │ │ │ ├── tired.png │ │ │ │ └── trading.png │ │ │ ├── card_img │ │ │ │ ├── ap.png │ │ │ │ ├── daily.png │ │ │ │ ├── hire.png │ │ │ │ ├── jade.png │ │ │ │ ├── recruit.png │ │ │ │ ├── tower.png │ │ │ │ ├── train.png │ │ │ │ └── weekly.png │ │ │ ├── career │ │ │ │ ├── career_1.png │ │ │ │ ├── career_2.png │ │ │ │ ├── career_3.png │ │ │ │ ├── career_4.png │ │ │ │ └── career_5.png │ │ │ ├── elite │ │ │ │ ├── elite_0.png │ │ │ │ ├── elite_1.png │ │ │ │ └── elite_2.png │ │ │ ├── icon_control.png │ │ │ ├── potential │ │ │ │ ├── potential_0.png │ │ │ │ ├── potential_1.png │ │ │ │ ├── potential_2.png │ │ │ │ ├── potential_3.png │ │ │ │ ├── potential_4.png │ │ │ │ └── potential_5.png │ │ │ └── uniequip │ │ │ │ ├── TRP-D.png │ │ │ │ ├── aft-d.png │ │ │ │ ├── aft-x.png │ │ │ │ ├── aft-y.png │ │ │ │ ├── age-x.png │ │ │ │ ├── alc-x.png │ │ │ │ ├── amb-x.png │ │ │ │ ├── amb-y.png │ │ │ │ ├── arc-x.png │ │ │ │ ├── arc-y.png │ │ │ │ ├── art-x.png │ │ │ │ ├── art-y.png │ │ │ │ ├── bar-x.png │ │ │ │ ├── bea-x.png │ │ │ │ ├── bea-y.png │ │ │ │ ├── bla-d.png │ │ │ │ ├── bla-x.png │ │ │ │ ├── bls-x.png │ │ │ │ ├── bom-x.png │ │ │ │ ├── ccr-d.png │ │ │ │ ├── ccr-x.png │ │ │ │ ├── ccr-y.png │ │ │ │ ├── cen-x.png │ │ │ │ ├── cen-y.png │ │ │ │ ├── cha-x.png │ │ │ │ ├── cha-y.png │ │ │ │ ├── chg-x.png │ │ │ │ ├── chg-y.png │ │ │ │ ├── cra-x.png │ │ │ │ ├── cra-y.png │ │ │ │ ├── cru-x.png │ │ │ │ ├── dea-x.png │ │ │ │ ├── dea-y.png │ │ │ │ ├── dec-x.png │ │ │ │ ├── dec-y.png │ │ │ │ ├── dre-x.png │ │ │ │ ├── dre-y.png │ │ │ │ ├── exe-x.png │ │ │ │ ├── exe-y.png │ │ │ │ ├── fgt-x.png │ │ │ │ ├── fgt-y.png │ │ │ │ ├── for-x.png │ │ │ │ ├── for-y.png │ │ │ │ ├── fun-x.png │ │ │ │ ├── fun-y.png │ │ │ │ ├── gee-x.png │ │ │ │ ├── gua-x.png │ │ │ │ ├── gua-y.png │ │ │ │ ├── ham-x.png │ │ │ │ ├── hes-x.png │ │ │ │ ├── hes-y.png │ │ │ │ ├── hok-x.png │ │ │ │ ├── hok-y.png │ │ │ │ ├── hun-x.png │ │ │ │ ├── inc-x.png │ │ │ │ ├── ins-x.png │ │ │ │ ├── ins-y.png │ │ │ │ ├── isw-a.png │ │ │ │ ├── lor-x.png │ │ │ │ ├── lor-y.png │ │ │ │ ├── mar-x.png │ │ │ │ ├── mar-y.png │ │ │ │ ├── mer-x.png │ │ │ │ ├── mer-y.png │ │ │ │ ├── msc-d.png │ │ │ │ ├── msc-x.png │ │ │ │ ├── msc-y.png │ │ │ │ ├── original.png │ │ │ │ ├── phy-x.png │ │ │ │ ├── phy-y.png │ │ │ │ ├── plx-x.png │ │ │ │ ├── plx-y.png │ │ │ │ ├── pro-x.png │ │ │ │ ├── pro-y.png │ │ │ │ ├── pum-x.png │ │ │ │ ├── pum-y.png │ │ │ │ ├── pus-x.png │ │ │ │ ├── pus-y.png │ │ │ │ ├── ra-a.png │ │ │ │ ├── rea-x.png │ │ │ │ ├── rin-x.png │ │ │ │ ├── rin-y.png │ │ │ │ ├── rit-x.png │ │ │ │ ├── rpr-x.png │ │ │ │ ├── sbl-x.png │ │ │ │ ├── sbl-y.png │ │ │ │ ├── sie-x.png │ │ │ │ ├── sol-x.png │ │ │ │ ├── sol-y.png │ │ │ │ ├── spc-x.png │ │ │ │ ├── spc-y.png │ │ │ │ ├── spt-x.png │ │ │ │ ├── sum-x.png │ │ │ │ ├── sum-y.png │ │ │ │ ├── swo-x.png │ │ │ │ ├── swo-y.png │ │ │ │ ├── tac-x.png │ │ │ │ ├── tac-y.png │ │ │ │ ├── trp-y.png │ │ │ │ ├── umd-x.png │ │ │ │ ├── umd-y.png │ │ │ │ ├── uny-x.png │ │ │ │ ├── uny-y.png │ │ │ │ ├── wah-x.png │ │ │ │ ├── wah-y.png │ │ │ │ └── wdm-x.png │ │ └── background │ │ │ ├── bg.jpg │ │ │ ├── exusiai_1.jpg │ │ │ ├── miumiu_1.jpg │ │ │ ├── pepe_1.jpg │ │ │ ├── pepe_2.jpg │ │ │ ├── susie_1.jpg │ │ │ └── susie_2.jpg │ └── templates │ │ ├── ark_card.html.jinja2 │ │ ├── index.css │ │ └── macros.html.jinja2 ├── schemas │ ├── __init__.py │ ├── ark_card.py │ ├── ark_models │ │ ├── __init__.py │ │ ├── assist_chars.py │ │ ├── base.py │ │ ├── building.py │ │ ├── buildings │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── control.py │ │ │ ├── dormitory.py │ │ │ ├── hire.py │ │ │ ├── manufacture.py │ │ │ ├── meeting.py │ │ │ ├── power.py │ │ │ ├── tired.py │ │ │ ├── trading.py │ │ │ └── training.py │ │ ├── campaign.py │ │ ├── chars.py │ │ ├── medal.py │ │ ├── recruit.py │ │ ├── routine.py │ │ ├── skins.py │ │ ├── status.py │ │ └── tower.py │ ├── ark_sign.py │ ├── cred.py │ └── rogue.py └── utils.py ├── package-lock.json ├── package.json ├── pyproject.toml ├── tailwind.css └── uv.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://afdian.com/a/FrostN0v0"] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 错误报告 2 | title: "Bug: 出现异常" 3 | description: 提交 Bug 反馈以帮助我们改进代码 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | ## 注意事项 10 | [GitHub Issues](../issues) 专门用于错误报告和功能需求,这意味着我们不接受使用问题。如果你打开的问题不符合要求,它将会被无条件关闭。 11 | 12 | 有关使用问题,请通过以下途径: 13 | - 阅读文档以解决 14 | - 在社区内寻求他人解答 15 | - 在网络中搜索是否有人遇到过类似的问题 16 | 17 | 如果你不知道如何有效、精准地提出一个问题,我们建议你先阅读[《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)。 18 | 19 | 最后,请记得遵守我们的社区准则,友好交流。 20 | 21 | - type: checkboxes 22 | id: terms 23 | attributes: 24 | label: 确认事项 25 | description: 请确认你已遵守所有必选项。 26 | options: 27 | - label: 我已仔细阅读并了解上述注意事项。 28 | required: true 29 | - label: 我已使用最新版本测试过,确认问题依旧存在。 30 | required: true 31 | - label: 我确定在 [GitHub Issues](../issues) 中没有相同或相似的问题。 32 | required: true 33 | 34 | - type: input 35 | id: env-nonebot-ver 36 | attributes: 37 | label: NoneBot 版本 38 | description: 填写 NoneBot 版本 39 | placeholder: e.g. 2.4.1 40 | validations: 41 | required: true 42 | 43 | - type: input 44 | id: env-plugin-ver 45 | attributes: 46 | label: NoneBot skland 插件版本或 Commit ID 47 | description: 填写 NoneBot skland 插件版本或 Commit ID 48 | placeholder: e.g. 0.1.0 49 | validations: 50 | required: true 51 | 52 | - type: input 53 | id: env-protocol 54 | attributes: 55 | label: 协议端 56 | description: 填写连接 NoneBot 的协议端及版本 57 | placeholder: e.g. NapCat 4.4.15 58 | 59 | 60 | - type: textarea 61 | id: describe 62 | attributes: 63 | label: 描述问题 64 | description: 清晰简洁地说明问题是什么 65 | validations: 66 | required: true 67 | 68 | - type: textarea 69 | id: reproduction 70 | attributes: 71 | label: 复现步骤 72 | description: 提供能复现此问题的详细操作步骤 73 | placeholder: | 74 | 1. 首先…… 75 | 2. 然后…… 76 | 3. 发生…… 77 | validations: 78 | required: true 79 | 80 | - type: textarea 81 | id: expected 82 | attributes: 83 | label: 期望的结果 84 | description: 清晰简洁地描述你期望发生的事情 85 | 86 | - type: textarea 87 | id: logs 88 | attributes: 89 | label: 截图或日志 90 | description: 提供有助于诊断问题的任何日志和截图 91 | 92 | - type: checkboxes 93 | id: contribute 94 | attributes: 95 | label: 参与贡献 96 | description: 欢迎加入我们的贡献者行列! 97 | options: 98 | - label: 我有足够的时间和能力,愿意为此提交 PR 来修复问题。 99 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: ✨ 功能需求 2 | title: "Feature: " 3 | description: 为项目提出一个新的想法或建议 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | ## 注意事项 10 | [GitHub Issues](../issues) 专门用于错误报告和功能需求,这意味着我们不接受使用问题。如果你打开的问题不符合要求,它将会被无条件关闭。 11 | 12 | 有关使用问题,请通过以下途径: 13 | - 阅读文档以解决 14 | - 在社区内寻求他人解答 15 | - 在网络中搜索是否有人遇到过类似的问题 16 | 17 | 最后,请记得遵守我们的社区准则,友好交流。 18 | 19 | - type: checkboxes 20 | id: terms 21 | attributes: 22 | label: 确认事项 23 | description: 请确认你已遵守所有必选项。 24 | options: 25 | - label: 我已仔细阅读并了解上述注意事项。 26 | required: true 27 | - label: 我已使用最新版本测试过,确认功能并未实现。 28 | required: true 29 | - label: 我确定在 [GitHub Issues](../issues) 中没有相同或相似的需求。 30 | required: true 31 | 32 | - type: textarea 33 | id: problem 34 | attributes: 35 | label: 你希望能解决什么样的问题? 36 | description: 请简要地说明是什么问题导致你想要一个新功能。也许我们可以提出一种现有的解决办法。 37 | validations: 38 | required: true 39 | 40 | - type: textarea 41 | id: solution 42 | attributes: 43 | label: 你想要的解决方案 44 | description: 请说明你希望使用什么样的方法解决上述问题。 45 | validations: 46 | required: true 47 | 48 | - type: textarea 49 | id: alternatives 50 | attributes: 51 | label: 你考虑过的替代方案 52 | description: 除了上述方法以外,你还考虑过哪些其他的实现方式? 53 | 54 | - type: textarea 55 | id: usecase 56 | attributes: 57 | label: 实现的功能是什么样的? 58 | description: | 59 | 提供功能在实现后如何使用的代码示例。请注意,你可以使用 Markdown 来设置代码块的格式。 60 | 尽可能多地提供细节。你希望它如何使用的示例代码会有所帮助。 61 | 62 | - type: textarea 63 | id: context 64 | attributes: 65 | label: 还有什么要补充的吗? 66 | description: 在此处添加相关的任何其他上下文或截图,或者你觉得有帮助的信息。 67 | 68 | - type: checkboxes 69 | id: contribute 70 | attributes: 71 | label: 参与贡献 72 | description: 欢迎加入我们的贡献者行列! 73 | options: 74 | - label: 我有足够的时间和能力,愿意为此提交 PR 来实现功能。 -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # poetry 102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 103 | # This is especially recommended for binary packages to ensure reproducibility, and is more 104 | # commonly ignored for libraries. 105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 106 | #poetry.lock 107 | 108 | # pdm 109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 110 | #pdm.lock 111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 112 | # in version control. 113 | # https://pdm.fming.dev/#use-with-ide 114 | .pdm.toml 115 | 116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 117 | __pypackages__/ 118 | 119 | # Celery stuff 120 | celerybeat-schedule 121 | celerybeat.pid 122 | 123 | # SageMath parsed files 124 | *.sage.py 125 | 126 | # Environments 127 | .env 128 | .venv 129 | env/ 130 | venv/ 131 | ENV/ 132 | env.bak/ 133 | venv.bak/ 134 | 135 | # Spyder project settings 136 | .spyderproject 137 | .spyproject 138 | 139 | # Rope project settings 140 | .ropeproject 141 | 142 | # mkdocs documentation 143 | /site 144 | 145 | # mypy 146 | .mypy_cache/ 147 | .dmypy.json 148 | dmypy.json 149 | 150 | # Pyre type checker 151 | .pyre/ 152 | 153 | # pytype static type analyzer 154 | .pytype/ 155 | 156 | # Cython debug symbols 157 | cython_debug/ 158 | 159 | # PyCharm 160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 162 | # and can be added to the global gitignore or merged into this file. For a more nuclear 163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 164 | #.idea/ 165 | 166 | ### Python Patch ### 167 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 168 | poetry.toml 169 | 170 | # ruff 171 | .ruff_cache/ 172 | 173 | # LSP config files 174 | pyrightconfig.json 175 | 176 | # node_modules 177 | node_modules/ 178 | 179 | # End of https://www.toptal.com/developers/gitignore/api/python 180 | -------------------------------------------------------------------------------- /.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, --exit-non-zero-on-fix] 14 | stages: [pre-commit] 15 | - id: ruff-format 16 | stages: [pre-commit] 17 | 18 | - repo: https://github.com/pycqa/isort 19 | rev: 6.0.1 20 | hooks: 21 | - id: isort 22 | stages: [pre-commit] 23 | 24 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.10 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 FrostN0v0 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | NoneBotPluginLogo 4 |
5 |
6 | 7 |
8 | 9 | # nonebot-plugin-skland 10 | 11 | _✨ 通过森空岛查询游戏数据 ✨_ 12 | 13 | 14 | license 15 | 16 | 17 | pypi 18 | 19 | python 20 |
21 | 22 | pre-commit.ci status 23 | 24 | 25 | NoneBot Registry 26 | 27 | 28 | uv 29 | 30 | 31 | ruff 32 | 33 | CodeFactor 34 | 35 | 36 |
37 | 38 | 📸 演示与预览 39 | 40 |   |   41 | 42 | 📦️ 下载插件 43 | 44 |   |   45 | 46 | 💬 加入交流群 47 | 48 | 49 |
50 | 51 | ## 📖 介绍 52 | 53 | 通过森空岛查询游戏数据 54 | 55 | > [!NOTE] 56 | > 本插件存在大量未经验证的数据结构~~以及 💩 山~~ 57 | > 58 | > 如在使用过程中遇到问题,欢迎提 [issue](https://github.com/FrostN0v0/nonebot-plugin-skland/issues/new/choose) 帮助改进项目 59 | 60 | starify 61 | 62 |
63 | Star History 64 | 65 | Star History Chart 66 | 67 |
68 | 69 | ## 💿 安装 70 | 71 |
72 | 使用 nb-cli 安装 73 | 在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装 74 | 75 | nb plugin install nonebot-plugin-skland 76 | 77 |
78 | 79 |
80 | 使用包管理器安装 81 | 在 nonebot2 项目的插件目录下, 打开命令行, 根据你使用的包管理器, 输入相应的安装命令 82 | 83 |
84 | pip 85 | 86 | pip install nonebot-plugin-skland 87 | 88 |
89 |
90 | pdm 91 | 92 | pdm add nonebot-plugin-skland 93 | 94 |
95 |
96 | uv 97 | 98 | uv add nonebot-plugin-skland 99 | 100 |
101 |
102 | poetry 103 | 104 | poetry add nonebot-plugin-skland 105 | 106 |
107 |
108 | conda 109 | 110 | conda install nonebot-plugin-skland 111 | 112 |
113 | 114 | 打开 nonebot2 项目根目录下的 `pyproject.toml` 文件, 在 `[tool.nonebot]` 部分追加写入 115 | 116 | plugins = ["nonebot_plugin_skland"] 117 | 118 |
119 | 120 | ## ⚙️ 配置 121 | 122 | ### 配置表 123 | 124 | 在 nonebot2 项目的`.env`文件中修改配置项 125 | 126 | | 配置项 | 必填 | 默认值 | 说明 | 127 | | :-------------------------: | :--: | :---------: | :----------------------: | 128 | | `skland__github_proxy_url` | 否 | `""` | GitHub 代理 URL | 129 | | `skland__github_token` | 否 | `""` | GitHub Token | 130 | | `skland__check_res_update` | 否 | `False` | 是否在启动时检查资源更新 | 131 | | `skland__background_source` | 否 | `"default"` | 背景图片来源 | 132 | | `skland__argot_expire` | 否 | `300` | 暗语消息过期时间(秒) | 133 | 134 | > [!TIP] 135 | > 以上配置项均~~没什么用~~按需填写,GitHub Token 用于解决 fetch_file_list 接口到达免费调用上限,但不会有那么频繁的更新频率,99.98%的概率是用不上的。~~只是因为我开发测试的时候上限了,所以有了这项~~, 136 | > 137 | > 本插件所使用的`干员半身像`、`技能图标`等资源,均优先调用本地,不存在则从网络请求获取,所以本地资源更新非必要选项,按需填写,不想过多请求网络资源可以自动或指令手动更新下载本地资源。 138 | 139 | ### background_source 140 | 141 | `skland__background_source` 为背景图来源,可选值为字面量 `default` / `Lolicon` / `random` 或者结构 `CustomSource` 。 `Lolicon` 为网络请求获取随机带`arknights`tag 的背景图,`random`为从[默认背景目录](/nonebot_plugin_skland/resources/images/background/)中随机, `CustomSource` 用于自定义背景图。 默认为 `default`。 142 | 143 | 以下是 `CustomSource` 用法 144 | 145 | 在配置文件中设置 `skland__background_source` 为 `CustomSource`结构的字典 146 | 147 |
148 | CustomSource配置示例 149 | 150 | - 网络链接 151 | 152 | - `uri` 可为网络图片 API,只要返回的是图片即可 153 | - `uri` 也可以为 base64 编码的图片,如 `data:image/png;base64,xxxxxx` ~~(一般也没人这么干)~~ 154 | 155 | ```env 156 | skland__background_source = '{"uri": "https://example.com/image.jpg"}' 157 | ``` 158 | 159 | - 本地图片 160 | 161 | > - `uri` 也可以为本地图片路径,如 `imgs/image.jpg`、`/path/to/image.jpg` 162 | > - 如果本地图片路径是相对路径,会使用 [`nonebot-plugin-localstore`](https://github.com/nonebot/plugin-localstore) 指定的 data 目录作为根目录 163 | > - 如果本地图片路径是目录,会随机选择目录下的一张图片作为背景图 164 | 165 | ```env 166 | skland__background_source = '{"uri": "/imgs/image.jpg"}' 167 | ``` 168 | 169 |
170 | 171 | ## 🎉 使用 172 | 173 | > [!NOTE] 174 | > 记得使用[命令前缀](https://nonebot.dev/docs/appendices/config#command-start-%E5%92%8C-command-separator)哦 175 | 176 | ### 🪧 指令表 177 | 178 | | 指令 | 权限 | 参数 | 说明 | 179 | | :-----------------------: | :------: | :---------------: | :-----------------------: | 180 | | `skland` | 所有 | 无 or `@` | 角色信息卡片 | 181 | | `skland bind` | 所有 | `token` or `cred` | 绑定森空岛账号 | 182 | | `skland bind -u` | 所有 | `token` or `cred` | 更新绑定的 token 或 cred | 183 | | `skland qrcode` | 所有 | 无 | 扫码绑定森空岛账号 | 184 | | `skland arksign` | 所有 | 无 | 明日方舟签到 | 185 | | `skland arksign -u ` | 所有 | `uid` | 指定绑定角色 UID 进行签到 | 186 | | `skland arksign --all` | 所有 | 无 | 签到所有绑定角色 | 187 | | `skland char update` | 所有 | 无 | 更新森空岛绑定角色信息 | 188 | | `skland sync` | 超级用户 | 无 | 本地资源更新 | 189 | | `skland rogue` | 所有 | `@` \| `topic` | 肉鸽战绩查询(暂未完成) | 190 | 191 | > [!NOTE] 192 | > Token 获取相关文档还没写~~才不是懒得写~~ 193 | > 194 | > 可以参考[`token获取`](https://docs.qq.com/doc/p/2f705965caafb3ef342d4a979811ff3960bb3c17)获取 195 | > 196 | > 本插件支持 cred 和 token 两种方式手动绑定,使用二维码绑定时会提供 token,请勿将 token 提供给不信任的 Bot 所有者 197 | 198 | ### 🎯 快捷指令 199 | 200 | | 触发词 | 执行指令 | 201 | | :----------: | :---------------------------: | 202 | | 森空岛绑定 | `skland bind` | 203 | | 扫码绑定 | `skland qrcode` | 204 | | 明日方舟签到 | `skland arksign` | 205 | | 角色更新 | `skland char update` | 206 | | 资源更新 | `skland sync` | 207 | | 萨卡兹肉鸽 | `skland rogue --topic 萨卡兹` | 208 | | 萨米肉鸽 | `skland rogue --topic 萨米` | 209 | 210 | ### 🫣 暗语表 211 | 212 | > [!NOTE] 213 | > 🧭 暗语使用~~指北~~ 214 | > 215 | > 暗语消息来自 [nonebot-plugin-argot](https://github.com/KomoriDev/nonebot-plugin-argot) 插件 216 | > 217 | > 对暗语对象`回复对应的暗语指令`即可获取暗语消息 218 | 219 | | 暗语指令 | 对象 | 说明 | 220 | | :----------: | :--------------------: | :--------: | 221 | | `background` | [`信息卡片`](#-效果图) | 查看背景图 | 222 | 223 | ### 📸 效果图 224 | 225 | ![示例图1](docs/example.png) 226 | 227 | ## 💖 鸣谢 228 | 229 | - [`Alconna`](https://github.com/ArcletProject/Alconna): 简单、灵活、高效的命令参数解析器 230 | - [`NoneBot2`](https://nonebot.dev/): 跨平台 Python 异步机器人框架 231 | - [`yuanyan3060/ArknightsGameResource`](https://github.com/yuanyan3060/ArknightsGameResource): 明日方舟常用素材 232 | - [`KomoriDev/Starify`](https://github.com/KomoriDev/Starify):超棒的 GitHub Star Trace 工具 🌟📈 233 | - [`KomoriDev/nonebot-plugin-argot`](https://github.com/KomoriDev/nonebot-plugin-argot): 优秀的 NoneBot2 暗语支持 234 | 235 | ## 📢 声明 236 | 237 | 本插件仅供学习交流使用,数据由 [森空岛](https://skland.com/) 提供,请勿用于商业用途。 238 | 239 | 使用过程中,任何涉及个人账号隐私信息(如账号 token、cred 等)的数据,请勿提供给不信任的 Bot 所有者(尤其是 token)。 240 | 241 | ## 📋 TODO 242 | 243 | - [x] 完善用户接口返回数据解析 244 | - [x] 使用[`nonebot-plugin-htmlrender`](https://github.com/kexue-z/nonebot-plugin-htmlrender)渲染信息卡片 245 | - [x] 从[`yuanyan3060/ArknightsGameResource`](https://github.com/yuanyan3060/ArknightsGameResource)下载游戏数据、检查数据更新 246 | - [x] 绘制渲染粥游信息卡片 247 | - [x] 支持扫码绑定 248 | - [x] 优化资源获取形式 249 | - [ ] 粥游签到自动化 250 | - [ ] 细化粥游信息卡片的部分信息展示 251 | - [ ] 完善肉鸽战绩返回信息解析 252 | - [ ] 绘制渲染肉鸽战绩卡片 253 | - [ ] ~~扬了不必要的 💩~~ 254 | - [ ] 待补充,欢迎 pr 255 | -------------------------------------------------------------------------------- /docs/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/docs/example.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/__init__.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from io import BytesIO 3 | from datetime import datetime, timedelta 4 | 5 | import qrcode 6 | from nonebot.params import Depends 7 | from nonebot import logger, require 8 | from nonebot.permission import SuperUser 9 | from nonebot.plugin import PluginMetadata, inherit_supported_adapters 10 | 11 | require("nonebot_plugin_orm") 12 | require("nonebot_plugin_user") 13 | require("nonebot_plugin_argot") 14 | require("nonebot_plugin_alconna") 15 | require("nonebot_plugin_localstore") 16 | require("nonebot_plugin_htmlrender") 17 | from nonebot_plugin_orm import async_scoped_session 18 | from nonebot_plugin_user import UserSession, get_user 19 | from nonebot_plugin_argot import Text, Argot, Image, ArgotExtension 20 | from nonebot_plugin_alconna import ( 21 | At, 22 | Args, 23 | Field, 24 | Match, 25 | Option, 26 | Alconna, 27 | Arparma, 28 | MsgTarget, 29 | Subcommand, 30 | UniMessage, 31 | CommandMeta, 32 | on_alconna, 33 | message_reaction, 34 | ) 35 | 36 | from .model import User 37 | from . import hook as hook 38 | from .render import render_ark_card 39 | from .exception import RequestException 40 | from .api import SklandAPI, SklandLoginAPI 41 | from .download import GameResourceDownloader 42 | from .schemas import CRED, Topics, ArkSignResponse 43 | from .config import RESOURCE_ROUTES, Config, config 44 | from .db_handler import get_arknights_characters, get_arknights_character_by_uid, get_default_arknights_character 45 | from .utils import ( 46 | get_background_image, 47 | get_characters_and_bind, 48 | refresh_cred_token_if_needed, 49 | refresh_access_token_if_needed, 50 | ) 51 | 52 | __plugin_meta__ = PluginMetadata( 53 | name="森空岛", 54 | description="通过森空岛查询游戏数据", 55 | usage="/skland", 56 | config=Config, 57 | type="application", 58 | homepage="https://github.com/FrostN0v0/nonebot-plugin-skland", 59 | supported_adapters=inherit_supported_adapters("nonebot_plugin_alconna", "nonebot_plugin_user"), 60 | extra={ 61 | "author": "FrostN0v0 <1614591760@qq.com>", 62 | "version": "0.2.1", 63 | }, 64 | ) 65 | 66 | skland = on_alconna( 67 | Alconna( 68 | "skland", 69 | Args["target?#目标", At | int], 70 | Subcommand( 71 | "-b|--bind|bind", 72 | Args["token", str, Field(completion=lambda: "请输入 token 或 cred 完成绑定")], 73 | Option("-u|--update|update", help_text="更新绑定的 token 或 cred"), 74 | help_text="绑定森空岛账号", 75 | ), 76 | Subcommand("-q|--qrcode|qrcode", help_text="获取二维码进行扫码绑定"), 77 | Subcommand( 78 | "arksign", 79 | Option( 80 | "-u|--uid|uid", 81 | Args["uid", str, Field(completion=lambda: "请输入指定绑定角色uid")], 82 | help_text="指定绑定角色uid进行签到", 83 | ), 84 | Option("--all", help_text="签到所有绑定角色"), 85 | help_text="明日方舟签到", 86 | ), 87 | Subcommand("char", Option("-u|--update|update"), help_text="更新绑定角色信息"), 88 | Subcommand("sync", help_text="更新图片资源(仅超管可用)"), 89 | Subcommand( 90 | "rogue", 91 | Args["target?#目标", At | int], 92 | Option( 93 | "-t|--topic|topic", 94 | Args["topic_name?#主题", ["萨米", "萨卡兹"], Field(completion=lambda: "请输入指定topic_id")], 95 | help_text="指定主题进行肉鸽战绩查询", 96 | ), 97 | help_text="肉鸽战绩查询", 98 | ), 99 | meta=CommandMeta( 100 | description=__plugin_meta__.description, 101 | usage=__plugin_meta__.usage, 102 | example="/skland", 103 | ), 104 | ), 105 | comp_config={"lite": True}, 106 | skip_for_unmatch=False, 107 | block=True, 108 | use_cmd_start=True, 109 | extensions=[ArgotExtension], 110 | ) 111 | 112 | skland.shortcut("森空岛绑定", {"command": "skland bind", "fuzzy": True, "prefix": True}) 113 | skland.shortcut("扫码绑定", {"command": "skland qrcode", "fuzzy": False, "prefix": True}) 114 | skland.shortcut("明日方舟签到", {"command": "skland arksign --all", "fuzzy": True, "prefix": True}) 115 | skland.shortcut("萨卡兹肉鸽", {"command": "skland rogue --topic 萨卡兹", "fuzzy": True, "prefix": True}) 116 | skland.shortcut("萨米肉鸽", {"command": "skland rogue --topic 萨米", "fuzzy": True, "prefix": True}) 117 | skland.shortcut("角色更新", {"command": "skland char update", "fuzzy": False, "prefix": True}) 118 | skland.shortcut("资源更新", {"command": "skland sync", "fuzzy": False, "prefix": True}) 119 | 120 | 121 | @skland.assign("$main") 122 | async def _(session: async_scoped_session, user_session: UserSession, target: Match[At | int]): 123 | @refresh_cred_token_if_needed 124 | @refresh_access_token_if_needed 125 | async def get_character_info(user: User, uid: str): 126 | return await SklandAPI.ark_card(CRED(cred=user.cred, token=user.cred_token), uid) 127 | 128 | if target.available: 129 | target_platform_id = target.result.target if isinstance(target.result, At) else target.result 130 | target_id = (await get_user(user_session.platform, str(target_platform_id))).id 131 | else: 132 | target_id = user_session.user_id 133 | 134 | user = await session.get(User, target_id) 135 | if not user: 136 | await UniMessage("未绑定 skland 账号").finish(at_sender=True) 137 | ark_characters = await get_default_arknights_character(user, session) 138 | if not ark_characters: 139 | await UniMessage("未绑定 arknights 账号").finish(at_sender=True) 140 | if user_session.platform == "qq": 141 | await message_reaction("66") 142 | else: 143 | await message_reaction("❤") 144 | 145 | info = await get_character_info(user, str(ark_characters.uid)) 146 | if not info: 147 | return 148 | background = await get_background_image() 149 | image = await render_ark_card(info, background) 150 | if str(background).startswith("http"): 151 | argot_seg = [Text(str(background)), Image(url=str(background))] 152 | else: 153 | argot_seg = Image(path=str(background)) 154 | msg = UniMessage.image(raw=image) + Argot( 155 | "background", argot_seg, command="background", expired_at=config.argot_expire 156 | ) 157 | await msg.send(reply_to=True) 158 | await session.commit() 159 | 160 | 161 | @skland.assign("bind") 162 | async def _( 163 | token: Match[str], 164 | result: Arparma, 165 | user_session: UserSession, 166 | msg_target: MsgTarget, 167 | session: async_scoped_session, 168 | ): 169 | """绑定森空岛账号""" 170 | 171 | if not msg_target.private: 172 | await UniMessage("绑定指令只允许在私聊中使用").finish(at_sender=True) 173 | 174 | if user := await session.get(User, user_session.user_id): 175 | if result.find("bind.update"): 176 | if len(token.result) == 24: 177 | grant_code = await SklandLoginAPI.get_grant_code(token.result) 178 | cred = await SklandLoginAPI.get_cred(grant_code) 179 | user.access_token = token.result 180 | user.cred = cred.cred 181 | user.cred_token = cred.token 182 | elif len(token.result) == 32: 183 | cred_token = await SklandLoginAPI.refresh_token(token.result) 184 | user.cred = token.result 185 | user.cred_token = cred_token 186 | else: 187 | await UniMessage("token 或 cred 错误,请检查格式").finish(at_sender=True) 188 | await get_characters_and_bind(user, session) 189 | await UniMessage("更新成功").finish(at_sender=True) 190 | await UniMessage("已绑定过 skland 账号").finish(at_sender=True) 191 | 192 | if token.available: 193 | try: 194 | if len(token.result) == 24: 195 | grant_code = await SklandLoginAPI.get_grant_code(token.result) 196 | cred = await SklandLoginAPI.get_cred(grant_code) 197 | user = User( 198 | access_token=token.result, 199 | cred=cred.cred, 200 | cred_token=cred.token, 201 | id=user_session.user_id, 202 | user_id=cred.userId, 203 | ) 204 | elif len(token.result) == 32: 205 | cred_token = await SklandLoginAPI.refresh_token(token.result) 206 | user_id = await SklandAPI.get_user_ID(CRED(cred=token.result, token=cred_token)) 207 | user = User( 208 | cred=token.result, 209 | cred_token=cred_token, 210 | id=user_session.user_id, 211 | user_id=user_id, 212 | ) 213 | else: 214 | await UniMessage("token 或 cred 错误,请检查格式").finish(at_sender=True) 215 | session.add(user) 216 | await get_characters_and_bind(user, session) 217 | await UniMessage("绑定成功").finish(at_sender=True) 218 | except RequestException as e: 219 | await UniMessage(f"绑定失败,错误信息:{e}").finish(at_sender=True) 220 | 221 | 222 | @skland.assign("qrcode") 223 | async def _( 224 | user_session: UserSession, 225 | session: async_scoped_session, 226 | ): 227 | """二维码绑定森空岛账号""" 228 | scan_id = await SklandLoginAPI.get_scan() 229 | scan_url = f"hypergryph://scan_login?scanId={scan_id}" 230 | qr_code = qrcode.make(scan_url) 231 | result_stream = BytesIO() 232 | qr_code.save(result_stream, "PNG") 233 | msg = UniMessage("请使用森空岛app扫描二维码绑定账号\n二维码有效时间两分钟,请不要扫描他人的登录二维码进行绑定~") 234 | msg += UniMessage.image(raw=result_stream.getvalue()) 235 | qr_msg = await msg.send(reply_to=True) 236 | end_time = datetime.now() + timedelta(seconds=100) 237 | scan_code = None 238 | while datetime.now() < end_time: 239 | try: 240 | scan_code = await SklandLoginAPI.get_scan_status(scan_id) 241 | break 242 | except RequestException: 243 | pass 244 | await asyncio.sleep(2) 245 | if qr_msg.recallable: 246 | await qr_msg.recall(index=0) 247 | if scan_code: 248 | if user_session.platform == "qq": 249 | await message_reaction("124") 250 | else: 251 | await message_reaction("👌") 252 | token = await SklandLoginAPI.get_token_by_scan_code(scan_code) 253 | grant_code = await SklandLoginAPI.get_grant_code(token) 254 | cred = await SklandLoginAPI.get_cred(grant_code) 255 | if user := await session.get(User, user_session.user_id): 256 | user.access_token = token 257 | user.cred = cred.cred 258 | user.cred_token = cred.token 259 | else: 260 | user = User( 261 | access_token=token, 262 | cred=cred.cred, 263 | cred_token=cred.token, 264 | id=user_session.user_id, 265 | user_id=cred.userId, 266 | ) 267 | session.add(user) 268 | await get_characters_and_bind(user, session) 269 | await UniMessage("绑定成功").finish(at_sender=True) 270 | else: 271 | await UniMessage("二维码超时,请重新获取并扫码").finish(at_sender=True) 272 | 273 | 274 | @skland.assign("arksign") 275 | async def _(user_session: UserSession, session: async_scoped_session, uid: Match[str], result: Arparma): 276 | """明日方舟森空岛签到""" 277 | 278 | @refresh_cred_token_if_needed 279 | @refresh_access_token_if_needed 280 | async def sign_in(user: User, uid: str, channel_master_id: str): 281 | """执行签到逻辑""" 282 | cred = CRED(cred=user.cred, token=user.cred_token) 283 | return await SklandAPI.ark_sign(cred, uid, channel_master_id=channel_master_id) 284 | 285 | user = await session.get(User, user_session.user_id) 286 | if not user: 287 | await UniMessage("未绑定 skland 账号").finish(at_sender=True) 288 | 289 | sign_result: dict[str, ArkSignResponse] = {} 290 | if uid.available: 291 | character = await get_arknights_character_by_uid(user, uid.result, session) 292 | sign_result[character.nickname] = await sign_in(user, uid.result, character.channel_master_id) 293 | else: 294 | if result.find("arksign.all"): 295 | characters = await get_arknights_characters(user, session) 296 | for character in characters: 297 | sign_result[character.nickname] = await sign_in(user, str(character.uid), character.channel_master_id) 298 | else: 299 | character = await get_default_arknights_character(user, session) 300 | if not character: 301 | await UniMessage("未绑定 arknights 账号").finish(at_sender=True) 302 | 303 | sign_result[character.nickname] = await sign_in(user, str(character.uid), character.channel_master_id) 304 | 305 | if sign_result[character.nickname]: 306 | await UniMessage( 307 | "\n".join( 308 | f"角色: {nickname} 签到成功,获得了:\n" 309 | + "\n".join(f"{award.resource.name} x {award.count}" for award in sign.awards) 310 | for nickname, sign in sign_result.items() 311 | ) 312 | ).send(at_sender=True) 313 | 314 | await session.commit() 315 | 316 | 317 | @skland.assign("char.update") 318 | async def _(user_session: UserSession, session: async_scoped_session): 319 | """更新森空岛角色信息""" 320 | 321 | @refresh_cred_token_if_needed 322 | @refresh_access_token_if_needed 323 | async def refresh_characters(user: User): 324 | await get_characters_and_bind(user, session) 325 | await UniMessage("更新成功").send(at_sender=True) 326 | 327 | if user := await session.get(User, user_session.user_id): 328 | await refresh_characters(user) 329 | 330 | 331 | @skland.assign("sync") 332 | async def _(is_superuser: bool = Depends(SuperUser())): 333 | if not is_superuser: 334 | await UniMessage.text("该指令仅超管可用").finish() 335 | try: 336 | logger.info("开始下载游戏资源") 337 | for route in RESOURCE_ROUTES: 338 | logger.info(f"正在下载: {route}") 339 | await GameResourceDownloader.download_all( 340 | owner="yuanyan3060", repo="ArknightsGameResource", route=route, branch="main" 341 | ) 342 | version = await GameResourceDownloader.get_version() 343 | GameResourceDownloader.update_version_file(version) 344 | await UniMessage.text(f"资源更新成功,版本:{version}").send() 345 | except RequestException as e: 346 | logger.error(f"下载游戏资源失败: {e}") 347 | await UniMessage.text(f"资源更新失败:{e.args[0]}").send() 348 | 349 | 350 | @skland.assign("rogue") 351 | async def _(user_session: UserSession, session: async_scoped_session, result: Arparma, target: Match[At | int]): 352 | """获取明日方舟肉鸽战绩""" 353 | 354 | # Not Finished 355 | @refresh_cred_token_if_needed 356 | @refresh_access_token_if_needed 357 | async def get_rogue_info(user: User, uid: str, topic_id: str): 358 | return await SklandAPI.get_rogue( 359 | CRED(cred=user.cred, token=user.cred_token, userId=str(user.user_id)), uid, topic_id 360 | ) 361 | 362 | if target.available: 363 | target_platform_id = target.result.target if isinstance(target.result, At) else target.result 364 | target_id = (await get_user(user_session.platform, str(target_platform_id))).id 365 | else: 366 | target_id = user_session.user_id 367 | 368 | user = await session.get(User, target_id) 369 | if not user: 370 | await UniMessage("未绑定 skland 账号").finish(at_sender=True) 371 | character = await get_default_arknights_character(user, session) 372 | if not character: 373 | await UniMessage("未绑定 arknights 账号").finish(at_sender=True) 374 | 375 | topic_id = Topics(str(result.query("rogue.topic.topic_name"))).topic_id if result.find("rogue.topic") else "" 376 | # TODO: 渲染肉鸽战绩卡片,完善指令逻辑 377 | rogue = await get_rogue_info(user, str(character.uid), topic_id) # noqa: F841 378 | # await UniMessage(rogue.model_dump_json()).send() 379 | await UniMessage("功能开发中(救命!来画渲染模板").send() 380 | await session.commit() 381 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/api/__init__.py: -------------------------------------------------------------------------------- 1 | from .request import SklandAPI as SklandAPI 2 | from .login import SklandLoginAPI as SklandLoginAPI 3 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/api/login.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | 3 | from ..schemas import CRED 4 | from ..exception import RequestException 5 | 6 | app_code = "4ca99fa6b56cc2ba" 7 | 8 | 9 | class SklandLoginAPI: 10 | _headers = { 11 | "User-Agent": ("Skland/1.32.1 (com.hypergryph.skland; build:103201004; Android 33; ) Okhttp/4.11.0"), 12 | "Accept-Encoding": "gzip", 13 | "Connection": "close", 14 | } 15 | 16 | @classmethod 17 | async def get_grant_code(cls, token: str) -> str: 18 | async with httpx.AsyncClient() as client: 19 | response = await client.post( 20 | "https://as.hypergryph.com/user/oauth2/v2/grant", 21 | json={"appCode": app_code, "token": token, "type": 0}, 22 | headers={**cls._headers}, 23 | ) 24 | 25 | if status := response.json().get("status"): 26 | if status != 0: 27 | raise RequestException(f"使用token获得认证代码失败:{response.json().get('msg')}") 28 | return response.json()["data"]["code"] 29 | 30 | @classmethod 31 | async def get_cred(cls, grant_code: str) -> CRED: 32 | async with httpx.AsyncClient() as client: 33 | response = await client.post( 34 | "https://zonai.skland.com/api/v1/user/auth/generate_cred_by_code", 35 | json={"code": grant_code, "kind": 1}, 36 | headers={**cls._headers}, 37 | ) 38 | if status := response.json().get("status"): 39 | if status != 0: 40 | raise RequestException(f"获得cred失败:{response.json().get('messgae')}") 41 | return CRED(**response.json().get("data")) 42 | 43 | @classmethod 44 | async def refresh_token(cls, cred: str) -> str: 45 | async with httpx.AsyncClient() as client: 46 | refresh_url = "https://zonai.skland.com/api/v1/auth/refresh" 47 | try: 48 | response = await client.get( 49 | refresh_url, 50 | headers={**cls._headers, "cred": cred}, 51 | ) 52 | response.raise_for_status() 53 | if status := response.json().get("status"): 54 | if status != 0: 55 | raise RequestException(f"刷新token失败:{response.json().get('message')}") 56 | token = response.json().get("data").get("token") 57 | return token 58 | except (httpx.HTTPStatusError, httpx.ConnectError) as e: 59 | raise RequestException(f"刷新token失败:{str(e)}") 60 | 61 | @classmethod 62 | async def get_scan(cls) -> str: 63 | async with httpx.AsyncClient() as client: 64 | get_scan_url = "https://as.hypergryph.com/general/v1/gen_scan/login" 65 | response = await client.post( 66 | get_scan_url, 67 | json={"appCode": app_code}, 68 | ) 69 | if status := response.json().get("status"): 70 | if status != 0: 71 | raise RequestException(f"获取登录二维码失败:{response.json().get('msg')}") 72 | return response.json()["data"]["scanId"] 73 | 74 | @classmethod 75 | async def get_scan_status(cls, scan_id: str) -> str: 76 | async with httpx.AsyncClient() as client: 77 | get_scan_status_url = "https://as.hypergryph.com/general/v1/scan_status" 78 | response = await client.get( 79 | get_scan_status_url, 80 | params={"scanId": scan_id}, 81 | ) 82 | if status := response.json().get("status"): 83 | if status != 0: 84 | raise RequestException(f"获取二维码 scanCode 失败:{response.json().get('msg')}") 85 | return response.json()["data"]["scanCode"] 86 | 87 | @classmethod 88 | async def get_token_by_scan_code(cls, scan_code: str) -> str: 89 | async with httpx.AsyncClient() as client: 90 | get_token_by_scan_code_url = "https://as.hypergryph.com/user/auth/v1/token_by_scan_code" 91 | response = await client.post( 92 | get_token_by_scan_code_url, 93 | json={"scanCode": scan_code}, 94 | ) 95 | if status := response.json().get("status"): 96 | if status != 0: 97 | raise RequestException(f"获取token失败:{response.json().get('msg')}") 98 | return response.json()["data"]["token"] 99 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/api/request.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import json 3 | import hashlib 4 | from typing import Literal 5 | from datetime import datetime 6 | from urllib.parse import urlparse 7 | 8 | import httpx 9 | from nonebot import logger 10 | 11 | from ..schemas import CRED, ArkCard, RogueHistory, ArkSignResponse 12 | from ..exception import LoginException, RequestException, UnauthorizedException 13 | 14 | base_url = "https://zonai.skland.com/api/v1" 15 | 16 | 17 | class SklandAPI: 18 | _headers = { 19 | "User-Agent": ("Skland/1.32.1 (com.hypergryph.skland; build:103201004; Android 33; ) Okhttp/4.11.0"), 20 | "Accept-Encoding": "gzip", 21 | "Connection": "close", 22 | } 23 | 24 | _header_for_sign = {"platform": "", "timestamp": "", "dId": "", "vName": ""} 25 | 26 | @classmethod 27 | async def get_binding(cls, cred: CRED) -> list: 28 | """获取绑定的角色""" 29 | binding_url = f"{base_url}/game/player/binding" 30 | async with httpx.AsyncClient() as client: 31 | try: 32 | response = await client.get( 33 | binding_url, 34 | headers=cls.get_sign_header(cred, binding_url, method="get"), 35 | ) 36 | if status := response.json().get("code"): 37 | if status == 10000: 38 | raise UnauthorizedException(f"获取绑定角色失败:{response.json().get('message')}") 39 | elif status == 10002: 40 | raise LoginException(f"获取绑定角色失败:{response.json().get('message')}") 41 | if status != 0: 42 | raise RequestException(f"获取绑定角色失败:{response.json().get('message')}") 43 | return response.json()["data"]["list"] 44 | except httpx.HTTPError as e: 45 | raise RequestException(f"获取绑定角色失败: {e}") 46 | 47 | @classmethod 48 | def get_sign_header( 49 | cls, 50 | cred: CRED, 51 | url: str, 52 | method: Literal["get", "post"], 53 | query_body: dict | None = None, 54 | ) -> dict: 55 | """获取带sign请求头""" 56 | timestamp = int(datetime.now().timestamp()) - 1 57 | header_ca = {**cls._header_for_sign, "timestamp": str(timestamp)} 58 | parsed_url = urlparse(url) 59 | query_params = json.dumps(query_body) if method == "post" else parsed_url.query 60 | header_ca_str = json.dumps( 61 | {**cls._header_for_sign, "timestamp": str(timestamp)}, 62 | separators=(",", ":"), 63 | ) 64 | secret = f"{parsed_url.path}{query_params}{timestamp}{header_ca_str}" 65 | hex_secret = hmac.new(cred.token.encode("utf-8"), secret.encode("utf-8"), hashlib.sha256).hexdigest() 66 | signature = hashlib.md5(hex_secret.encode("utf-8")).hexdigest() 67 | return {"cred": cred.cred, **cls._headers, "sign": signature, **header_ca} 68 | 69 | @classmethod 70 | async def ark_sign(cls, cred: CRED, uid: str, channel_master_id: str) -> ArkSignResponse: 71 | """进行明日方舟签到""" 72 | body = {"uid": uid, "gameId": channel_master_id} 73 | json_body = json.dumps(body, ensure_ascii=False, separators=(", ", ": "), allow_nan=False) 74 | sign_url = f"{base_url}/game/attendance" 75 | headers = cls.get_sign_header( 76 | cred, 77 | sign_url, 78 | method="post", 79 | query_body=body, 80 | ) 81 | async with httpx.AsyncClient() as client: 82 | try: 83 | response = await client.post( 84 | sign_url, 85 | headers={**headers, "Content-Type": "application/json"}, 86 | content=json_body, 87 | ) 88 | logger.debug(f"签到回复:{response.json()}") 89 | if status := response.json().get("code"): 90 | if status == 10000: 91 | raise UnauthorizedException(f"角色 {uid} 签到失败:{response.json().get('message')}") 92 | elif status == 10002: 93 | raise LoginException(f"角色 {uid} 签到失败:{response.json().get('message')}") 94 | elif status != 0: 95 | raise RequestException(f"角色 {uid} 签到失败:{response.json().get('message')}") 96 | except httpx.HTTPError as e: 97 | raise RequestException(f"角色 {uid} 签到失败: {e}") 98 | return ArkSignResponse(**response.json()["data"]) 99 | 100 | @classmethod 101 | async def get_user_ID(cls, cred: CRED) -> str: 102 | uid_url = f"{base_url}/user/teenager" 103 | async with httpx.AsyncClient() as client: 104 | try: 105 | response = await client.get( 106 | uid_url, 107 | headers=cls.get_sign_header(cred, uid_url, method="get"), 108 | ) 109 | if status := response.json().get("code"): 110 | if status == 10000: 111 | raise UnauthorizedException(f"获取账号 userId 失败:{response.json().get('message')}") 112 | elif status == 10002: 113 | raise LoginException(f"获取账号 userId 失败:{response.json().get('message')}") 114 | if status != 0: 115 | raise RequestException(f"获取账号 userId 失败:{response.json().get('message')}") 116 | return response.json()["data"]["teenager"]["userId"] 117 | except httpx.HTTPError as e: 118 | raise RequestException(f"获取账号 userId 失败: {e}") 119 | 120 | @classmethod 121 | async def ark_card(cls, cred: CRED, uid: str) -> ArkCard: 122 | """获取明日方舟角色信息""" 123 | game_info_url = f"{base_url}/game/player/info?uid={uid}" 124 | async with httpx.AsyncClient() as client: 125 | try: 126 | response = await client.get( 127 | game_info_url, 128 | headers=cls.get_sign_header(cred, game_info_url, method="get"), 129 | ) 130 | if status := response.json().get("code"): 131 | if status == 10000: 132 | raise UnauthorizedException(f"获取账号 game_info 失败:{response.json().get('message')}") 133 | elif status == 10002: 134 | raise LoginException(f"获取账号 game_info 失败:{response.json().get('message')}") 135 | if status != 0: 136 | raise RequestException(f"获取账号 game_info 失败:{response.json().get('message')}") 137 | return ArkCard(**response.json()["data"]) 138 | except httpx.HTTPError as e: 139 | raise RequestException(f"获取账号 userId 失败: {e}") 140 | 141 | @classmethod 142 | async def get_rogue(cls, cred: CRED, uid: str, topic_id: str) -> RogueHistory: 143 | """获取肉鸽数据""" 144 | rogue_url = f"{base_url}/game/arknights/rogue?uid={uid}&targetUserId={cred.userId}&topicId={topic_id}" 145 | async with httpx.AsyncClient() as client: 146 | try: 147 | response = await client.get( 148 | rogue_url, 149 | headers=cls.get_sign_header(cred, rogue_url, method="get"), 150 | ) 151 | logger.debug(f"肉鸽数据:{response.json()}") 152 | if status := response.json().get("code"): 153 | if status == 10000: 154 | raise UnauthorizedException(f"获取肉鸽数据失败:{response.json().get('message')}") 155 | elif status == 10002: 156 | raise LoginException(f"获取肉鸽数据失败:{response.json().get('message')}") 157 | if status != 0: 158 | raise RequestException(f"获取肉鸽数据失败:{response.json().get('message')}") 159 | return RogueHistory(**response.json()["data"]["history"]) 160 | except httpx.HTTPError as e: 161 | raise RequestException(f"获取肉鸽数据失败: {e}") 162 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/config.py: -------------------------------------------------------------------------------- 1 | import random 2 | from pathlib import Path 3 | from typing import Any, Literal 4 | 5 | from nonebot import logger 6 | from pydantic import Field 7 | from pydantic import BaseModel 8 | from pydantic import AnyUrl as Url 9 | from nonebot.compat import PYDANTIC_V2 10 | import nonebot_plugin_localstore as store 11 | from nonebot.plugin import get_plugin_config 12 | 13 | RES_DIR: Path = Path(__file__).parent / "resources" 14 | TEMPLATES_DIR: Path = RES_DIR / "templates" 15 | CACHE_DIR = store.get_plugin_cache_dir() 16 | RESOURCE_ROUTES = ["portrait", "skill"] 17 | 18 | 19 | class CustomSource(BaseModel): 20 | uri: Url | Path 21 | 22 | def to_uri(self) -> Any: 23 | if isinstance(self.uri, Path): 24 | uri = self.uri 25 | if not uri.is_absolute(): 26 | uri = Path(store.get_plugin_data_dir() / uri) 27 | 28 | if uri.is_dir(): 29 | # random pick a file 30 | files = [f for f in uri.iterdir() if f.is_file()] 31 | logger.debug(f"CustomSource: {uri} is a directory, random pick a file: {files}") 32 | if PYDANTIC_V2: 33 | return Url((uri / random.choice(files)).as_posix()) 34 | else: 35 | return Url((uri / random.choice(files)).as_posix(), scheme="file") # type: ignore 36 | 37 | if not uri.exists(): 38 | raise FileNotFoundError(f"CustomSource: {uri} not exists") 39 | if PYDANTIC_V2: 40 | return Url(uri.as_posix()) 41 | else: 42 | return Url(uri.as_posix(), scheme="file") # type: ignore 43 | 44 | return self.uri 45 | 46 | 47 | class ScopedConfig(BaseModel): 48 | github_proxy_url: str = "" 49 | """GitHub 代理 URL""" 50 | github_token: str = "" 51 | """GitHub Token""" 52 | check_res_update: bool = False 53 | """启动时检查资源更新""" 54 | background_source: Literal["default", "Lolicon", "random"] | CustomSource = "default" 55 | """背景图片来源""" 56 | argot_expire: int = 300 57 | """Argot 缓存过期时间""" 58 | 59 | 60 | class Config(BaseModel): 61 | skland: ScopedConfig = Field(default_factory=ScopedConfig) 62 | 63 | 64 | config = get_plugin_config(Config).skland 65 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/db_handler.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import delete, select 2 | from nonebot_plugin_orm import async_scoped_session 3 | 4 | from .model import User, Character 5 | 6 | 7 | async def get_arknights_characters(user: User, session: async_scoped_session) -> list[Character]: 8 | characters = ( 9 | ( 10 | await session.execute( 11 | select(Character).where(Character.id == user.id).where(Character.app_code == "arknights") 12 | ) 13 | ) 14 | .scalars() 15 | .all() 16 | ) 17 | return list(characters) 18 | 19 | 20 | async def get_default_arknights_character(user: User, session: async_scoped_session) -> Character: 21 | character = ( 22 | await session.execute( 23 | select(Character).where( 24 | Character.id == user.id, 25 | Character.isdefault, 26 | Character.app_code == "arknights", 27 | ) 28 | ) 29 | ).scalar_one() 30 | return character 31 | 32 | 33 | async def get_arknights_character_by_uid(user: User, uid: str, session: async_scoped_session) -> Character: 34 | character = ( 35 | await session.execute( 36 | select(Character).where( 37 | Character.id == user.id, 38 | Character.uid == int(uid), 39 | Character.app_code == "arknights", 40 | ) 41 | ) 42 | ).scalar_one() 43 | return character 44 | 45 | 46 | async def delete_characters(user: User, session: async_scoped_session): 47 | await session.execute(delete(Character).where(Character.id == user.id)) 48 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/download.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from pathlib import Path 3 | from itertools import islice 4 | from datetime import datetime 5 | from urllib.parse import quote 6 | from collections.abc import Iterable 7 | 8 | from nonebot import logger 9 | from rich.text import Text 10 | from rich.panel import Panel 11 | from rich.table import Table 12 | from pydantic import BaseModel 13 | from httpx import HTTPError, AsyncClient 14 | from nonebot.compat import model_validator 15 | from rich.progress import ( 16 | Task, 17 | TaskID, 18 | Progress, 19 | BarColumn, 20 | TextColumn, 21 | DownloadColumn, 22 | TimeRemainingColumn, 23 | TransferSpeedColumn, 24 | ) 25 | 26 | from .config import CACHE_DIR, config 27 | from .exception import RequestException 28 | 29 | 30 | class File(BaseModel): 31 | name: str 32 | download_url: str 33 | 34 | @model_validator(mode="before") 35 | def modify_download_url(cls, values): 36 | values["download_url"] = quote(values["download_url"], safe="/:") 37 | if config.github_proxy_url: 38 | values["download_url"] = f"{config.github_proxy_url}{values['download_url']}" 39 | return values 40 | return values 41 | 42 | 43 | class DownloadProgress(Progress): 44 | """下载进度条""" 45 | 46 | STATUS_DL = TextColumn("[blue]Downloading...") 47 | STATUS_FIN = TextColumn("[green]Complete!") 48 | STATUS_ROW = ( 49 | TextColumn("[progress.percentage]{task.percentage:>3.0f}%", justify="center"), 50 | TimeRemainingColumn(compact=True), 51 | ) 52 | PROG_ROW = (DownloadColumn(binary_units=True), BarColumn(), TransferSpeedColumn()) 53 | 54 | MAX_VISIBLE_TASKS = 10 55 | 56 | def make_tasks_table(self, tasks: Iterable[Task]) -> Table: 57 | table = Table.grid(padding=(0, 1), expand=self.expand) 58 | tasks_table = Table.grid(padding=(0, 1), expand=self.expand) 59 | all_tasks_finished = True 60 | visible_tasks = list(islice((task for task in tasks if task.visible), self.MAX_VISIBLE_TASKS)) 61 | 62 | for task in visible_tasks: 63 | status = self.STATUS_FIN if task.finished else self.STATUS_DL 64 | itable = Table.grid(padding=(0, 1), expand=self.expand) 65 | filename_column = Text(f"{task.fields['filename']}") 66 | itable.add_row(filename_column, *(column(task) for column in [status, *self.STATUS_ROW])) 67 | itable.add_row(*(column(task) for column in self.PROG_ROW)) 68 | tasks_table.add_row(itable) 69 | if not task.finished: 70 | all_tasks_finished = False 71 | 72 | if any(not task.finished for task in tasks): 73 | all_tasks_finished = False 74 | 75 | if all_tasks_finished: 76 | return table 77 | else: 78 | table.add_row(Panel(tasks_table, title="Downloading Files", title_align="left", padding=(1, 2))) 79 | 80 | return table 81 | 82 | 83 | class GameResourceDownloader: 84 | """游戏数据下载""" 85 | 86 | DOWNLOAD_COUNT: int = 0 87 | DOWNLOAD_TIME: datetime 88 | SEMAPHORE = asyncio.Semaphore(100) 89 | RAW_BASE_URL = "https://raw.githubusercontent.com/{owner}/{repo}/{branch}/" 90 | VERSION_URL = "https://raw.githubusercontent.com/yuanyan3060/ArknightsGameResource/refs/heads/main/version" 91 | BASE_URL = "https://api.github.com/repos/{owner}/{repo}/git/trees/{branch}?recursive=1" 92 | 93 | @classmethod 94 | async def get_version(cls) -> str: 95 | """获取最新版本""" 96 | url = config.github_proxy_url + cls.VERSION_URL if config.github_proxy_url else cls.VERSION_URL 97 | try: 98 | async with AsyncClient() as client: 99 | response = await client.get(url) 100 | response.raise_for_status() 101 | origin_version = response.content.decode() 102 | return origin_version 103 | except HTTPError as e: 104 | raise RequestException(f"检查更新失败: {type(e).__name__}: {e}") 105 | 106 | @classmethod 107 | async def check_update(cls) -> str: 108 | """检查更新""" 109 | origin_version = await cls.get_version() 110 | version_file = CACHE_DIR.joinpath("version") 111 | if not version_file.exists(): 112 | return origin_version 113 | local_version = version_file.read_text(encoding="utf-8").strip() 114 | if origin_version != local_version: 115 | return origin_version 116 | return "" 117 | 118 | @classmethod 119 | def update_version_file(cls, version: str): 120 | """更新本地版本文件""" 121 | version_file = CACHE_DIR.joinpath("version") 122 | version_file.write_text(version, encoding="utf-8") 123 | 124 | @classmethod 125 | async def fetch_file_list(cls, url: str, dl_url: str, route: str) -> list[File]: 126 | """获取 GitHub 仓库下的所有文件,并返回可下载的 URL""" 127 | headers = {} 128 | if config.github_token: 129 | headers = {"Authorization": f"{config.github_token}"} 130 | try: 131 | async with AsyncClient() as client: 132 | response = await client.get(url, headers=headers) 133 | response.raise_for_status() 134 | data = response.json() 135 | route = route.rstrip("/") + "/" 136 | files = [ 137 | File(name=item["path"].split("/")[-1], download_url=f"{dl_url}{item['path']}") 138 | for item in data.get("tree", []) 139 | if item["type"] == "blob" and item["path"].startswith(route) 140 | ] 141 | return files 142 | except HTTPError as e: 143 | raise RequestException(f"获取文件列表失败: {type(e).__name__}: {e}") 144 | 145 | @classmethod 146 | async def download_all(cls, owner: str, repo: str, route: str, branch: str = "main"): 147 | """并行下载 GitHub 目录下的所有文件""" 148 | cls.download_count = 0 149 | cls.download_time = datetime.now() 150 | url = cls.BASE_URL.format(owner=owner, repo=repo, branch=branch) 151 | dl_url = cls.RAW_BASE_URL.format(owner=owner, repo=repo, branch=branch) 152 | files = await cls.fetch_file_list(url=url, dl_url=dl_url, route=route) 153 | save_path = CACHE_DIR / route 154 | save_path.mkdir(parents=True, exist_ok=True) 155 | 156 | async with AsyncClient() as client: 157 | with DownloadProgress( 158 | "[cyan]{task.fields[filename]}", 159 | BarColumn(), 160 | DownloadColumn(), 161 | TransferSpeedColumn(), 162 | TimeRemainingColumn(), 163 | ) as progress: 164 | 165 | async def worker(file: File): 166 | """每个文件下载任务""" 167 | if (save_path / file.name).exists(): 168 | return 169 | async with cls.SEMAPHORE: 170 | task_id = progress.add_task("Downloading", filename=file.name, total=0) 171 | await cls.download_file(client, file, save_path, progress, task_id=task_id, timeout=300) 172 | progress.remove_task(task_id) 173 | cls.download_count += 1 174 | 175 | await asyncio.gather(*(worker(file) for file in files)) 176 | time_consumed = datetime.now() - cls.download_time 177 | if cls.download_count == 0: 178 | logger.info(f"资源 {route} 无新增文件") 179 | else: 180 | logger.success(f"资源 {route} 下载完成,共下载 {cls.download_count} 个文件,耗时 {time_consumed}") 181 | 182 | @classmethod 183 | async def download_file( 184 | cls, 185 | client: AsyncClient, 186 | file: File, 187 | save_path: Path, 188 | progress: Progress, 189 | *, 190 | task_id: TaskID, 191 | **kwargs, 192 | ): 193 | """下载单个文件""" 194 | 195 | file_path = save_path / file.name 196 | try: 197 | async with client.stream("GET", file.download_url, **kwargs) as response: 198 | file_size = int(response.headers.get("Content-Length", 0)) 199 | progress.update(task_id, total=file_size) 200 | 201 | with file_path.open("wb") as f: 202 | async for data in response.aiter_bytes(1024): 203 | f.write(data) 204 | progress.update(task_id, advance=len(data)) 205 | except HTTPError as e: 206 | raise RequestException(f"下载文件{file.name}失败: {type(e).__name__}: {e}") 207 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/exception.py: -------------------------------------------------------------------------------- 1 | from nonebot.exception import NoneBotException 2 | 3 | 4 | class Exception(NoneBotException): 5 | """异常基类""" 6 | 7 | 8 | class RequestException(Exception): 9 | """请求错误""" 10 | 11 | 12 | class UnauthorizedException(Exception): 13 | """登录授权错误""" 14 | 15 | 16 | class LoginException(Exception): 17 | """登录错误""" 18 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/filters.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | 3 | 4 | def format_timestamp(timestamp: float) -> str: 5 | delta = timedelta(seconds=timestamp) 6 | days = delta.days 7 | hours, remainder = divmod(delta.seconds, 3600) 8 | minutes = remainder // 60 9 | 10 | if days > 0: 11 | return f"{days}天{hours}小时{minutes}分钟" 12 | elif hours > 0: 13 | return f"{hours}小时{minutes}分钟" 14 | else: 15 | return f"{minutes}分钟" 16 | 17 | 18 | def time_to_next_monday_4am(now_ts: float) -> str: 19 | now = datetime.fromtimestamp(now_ts) 20 | days_until_monday = (7 - now.weekday()) % 7 21 | next_monday = now + timedelta(days=days_until_monday) 22 | next_monday_4am = next_monday.replace(hour=4, minute=0, second=0, microsecond=0) 23 | if now > next_monday_4am: 24 | next_monday_4am += timedelta(weeks=1) 25 | return format_timestamp((next_monday_4am - now).total_seconds()) 26 | 27 | 28 | def time_to_next_4am(now_ts: float) -> str: 29 | now = datetime.fromtimestamp(now_ts) 30 | next_4am = now.replace(hour=4, minute=0, second=0, microsecond=0) 31 | if now > next_4am: 32 | next_4am += timedelta(days=1) 33 | return format_timestamp((next_4am - now).total_seconds()) 34 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/hook.py: -------------------------------------------------------------------------------- 1 | from nonebot import logger, get_driver 2 | 3 | from .exception import RequestException 4 | from .config import RESOURCE_ROUTES, config 5 | from .download import GameResourceDownloader 6 | 7 | driver = get_driver() 8 | 9 | 10 | @driver.on_startup 11 | async def startup(): 12 | if config.check_res_update: 13 | try: 14 | if version := await GameResourceDownloader.check_update(): 15 | logger.info("开始下载游戏资源") 16 | for route in RESOURCE_ROUTES: 17 | logger.info(f"正在下载: {route}") 18 | await GameResourceDownloader.download_all( 19 | owner="yuanyan3060", repo="ArknightsGameResource", route=route, branch="main" 20 | ) 21 | except RequestException as e: 22 | logger.error(f"下载游戏资源失败: {e}") 23 | return 24 | if version: 25 | GameResourceDownloader.update_version_file(version) 26 | logger.success(f"游戏资源已更新到版本:{version}") 27 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/migrations/02e0764f579e_fix_model_type.py: -------------------------------------------------------------------------------- 1 | """fix model type 2 | 3 | 迁移 ID: 02e0764f579e 4 | 父迁移: 997049a57a3a 5 | 创建时间: 2025-04-03 08:37:11.577500 6 | 7 | """ 8 | 9 | from __future__ import annotations 10 | 11 | from collections.abc import Sequence 12 | 13 | import sqlalchemy as sa 14 | from alembic import op 15 | 16 | revision: str = "02e0764f579e" 17 | down_revision: str | Sequence[str] | None = "997049a57a3a" 18 | branch_labels: str | Sequence[str] | None = None 19 | depends_on: str | Sequence[str] | None = None 20 | 21 | 22 | def upgrade(name: str = "") -> None: 23 | if name: 24 | return 25 | # ### commands auto generated by Alembic - please adjust! ### 26 | with op.batch_alter_table("skland_characters", schema=None) as batch_op: 27 | batch_op.alter_column("uid", existing_type=sa.INTEGER(), type_=sa.String(), existing_nullable=False) 28 | 29 | # ### end Alembic commands ### 30 | 31 | 32 | def downgrade(name: str = "") -> None: 33 | if name: 34 | return 35 | # ### commands auto generated by Alembic - please adjust! ### 36 | with op.batch_alter_table("skland_characters", schema=None) as batch_op: 37 | batch_op.alter_column("uid", existing_type=sa.String(), type_=sa.INTEGER(), existing_nullable=False) 38 | 39 | # ### end Alembic commands ### 40 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/migrations/997049a57a3a_first_revision.py: -------------------------------------------------------------------------------- 1 | """first_revision 2 | 3 | 迁移 ID: 997049a57a3a 4 | 父迁移: 5 | 创建时间: 2025-02-26 09:22:45.310083 6 | 7 | """ 8 | 9 | from __future__ import annotations 10 | 11 | from collections.abc import Sequence 12 | 13 | import sqlalchemy as sa 14 | from alembic import op 15 | 16 | revision: str = "997049a57a3a" 17 | down_revision: str | Sequence[str] | None = None 18 | branch_labels: str | Sequence[str] | None = ("nonebot_plugin_skland",) 19 | depends_on: str | Sequence[str] | None = None 20 | 21 | 22 | def upgrade(name: str = "") -> None: 23 | if name: 24 | return 25 | # ### commands auto generated by Alembic - please adjust! ### 26 | op.create_table( 27 | "skland_characters", 28 | sa.Column("id", sa.Integer(), nullable=False), 29 | sa.Column("uid", sa.Integer(), nullable=False), 30 | sa.Column("app_code", sa.Text(), nullable=False), 31 | sa.Column("channel_master_id", sa.Text(), nullable=False), 32 | sa.Column("nickname", sa.Text(), nullable=False), 33 | sa.Column("isdefault", sa.Boolean(), nullable=False), 34 | sa.PrimaryKeyConstraint("id", "uid", name=op.f("pk_skland_characters")), 35 | info={"bind_key": "nonebot_plugin_skland"}, 36 | ) 37 | op.create_table( 38 | "skland_user", 39 | sa.Column("id", sa.Integer(), nullable=False), 40 | sa.Column("access_token", sa.Text(), nullable=True), 41 | sa.Column("cred", sa.Text(), nullable=False), 42 | sa.Column("cred_token", sa.Text(), nullable=False), 43 | sa.Column("user_id", sa.Text(), nullable=True), 44 | sa.PrimaryKeyConstraint("id", name=op.f("pk_skland_user")), 45 | info={"bind_key": "nonebot_plugin_skland"}, 46 | ) 47 | # ### end Alembic commands ### 48 | 49 | 50 | def downgrade(name: str = "") -> None: 51 | if name: 52 | return 53 | # ### commands auto generated by Alembic - please adjust! ### 54 | op.drop_table("skland_user") 55 | op.drop_table("skland_characters") 56 | # ### end Alembic commands ### 57 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/model.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Text 2 | from nonebot_plugin_orm import Model 3 | from sqlalchemy.orm import Mapped, mapped_column 4 | 5 | 6 | class User(Model): 7 | __tablename__ = "skland_user" 8 | 9 | id: Mapped[int] = mapped_column(primary_key=True) 10 | """User ID""" 11 | access_token: Mapped[str] = mapped_column(Text, nullable=True) 12 | """Skland Access Token""" 13 | cred: Mapped[str] = mapped_column(Text) 14 | """Skland Login Credential""" 15 | cred_token: Mapped[str] = mapped_column(Text) 16 | """Skland Login Credential Token""" 17 | user_id: Mapped[str] = mapped_column(Text, nullable=True) 18 | """Skland User ID""" 19 | 20 | 21 | class Character(Model): 22 | __tablename__ = "skland_characters" 23 | 24 | id: Mapped[int] = mapped_column(primary_key=True) 25 | """Character ID""" 26 | uid: Mapped[str] = mapped_column(primary_key=True) 27 | """Character UID""" 28 | app_code: Mapped[str] = mapped_column(Text) 29 | """APP Code""" 30 | channel_master_id: Mapped[str] = mapped_column(Text) 31 | """Channel Master ID""" 32 | nickname: Mapped[str] = mapped_column(Text) 33 | """Character Nickname""" 34 | isdefault: Mapped[bool] = mapped_column(default=False) 35 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/render.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from pydantic import AnyUrl as Url 4 | from nonebot_plugin_htmlrender import template_to_pic 5 | 6 | from .schemas import ArkCard 7 | from .config import TEMPLATES_DIR 8 | from .filters import format_timestamp, time_to_next_4am, time_to_next_monday_4am 9 | 10 | 11 | async def render_ark_card(props: ArkCard, bg: str | Url) -> bytes: 12 | return await template_to_pic( 13 | template_path=str(TEMPLATES_DIR), 14 | template_name="ark_card.html.jinja2", 15 | templates={ 16 | "now_ts": datetime.now().timestamp(), 17 | "background_image": bg, 18 | "status": props.status, 19 | "employed_chars": len(props.chars), 20 | "skins": len(props.skins), 21 | "building": props.building, 22 | "medals": props.medal.total, 23 | "assist_chars": props.assistChars, 24 | "recruit_finished": props.recruit_finished, 25 | "recruit_max": len(props.recruit), 26 | "recruit_complete_time": props.recruit_complete_time, 27 | "campaign": props.campaign, 28 | "routine": props.routine, 29 | "tower": props.tower, 30 | "training_char": props.trainee_char, 31 | }, 32 | filters={ 33 | "format_timestamp": format_timestamp, 34 | "time_to_next_4am": time_to_next_4am, 35 | "time_to_next_monday_4am": time_to_next_monday_4am, 36 | }, 37 | pages={ 38 | "viewport": {"width": 706, "height": 1160}, 39 | "base_url": f"file://{TEMPLATES_DIR}", 40 | }, 41 | ) 42 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/fonts/Akrobat-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/fonts/Akrobat-Bold.otf -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/fonts/Bender-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/fonts/Bender-Bold.otf -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/fonts/Bender.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/fonts/Bender.otf -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/assist_title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/assist_title.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/building/dorm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/building/dorm.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/building/labor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/building/labor.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/building/manufact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/building/manufact.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/building/meeting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/building/meeting.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/building/tired.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/building/tired.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/building/trading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/building/trading.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/card_img/ap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/card_img/ap.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/card_img/daily.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/card_img/daily.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/card_img/hire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/card_img/hire.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/card_img/jade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/card_img/jade.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/card_img/recruit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/card_img/recruit.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/card_img/tower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/card_img/tower.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/card_img/train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/card_img/train.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/card_img/weekly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/card_img/weekly.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/career/career_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/career/career_1.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/career/career_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/career/career_2.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/career/career_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/career/career_3.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/career/career_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/career/career_4.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/career/career_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/career/career_5.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/elite/elite_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/elite/elite_0.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/elite/elite_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/elite/elite_1.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/elite/elite_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/elite/elite_2.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/icon_control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/icon_control.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/potential/potential_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/potential/potential_0.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/potential/potential_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/potential/potential_1.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/potential/potential_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/potential/potential_2.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/potential/potential_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/potential/potential_3.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/potential/potential_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/potential/potential_4.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/potential/potential_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/potential/potential_5.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/TRP-D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/TRP-D.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/aft-d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/aft-d.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/aft-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/aft-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/aft-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/aft-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/age-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/age-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/alc-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/alc-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/amb-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/amb-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/amb-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/amb-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/arc-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/arc-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/arc-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/arc-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/art-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/art-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/art-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/art-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/bar-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/bar-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/bea-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/bea-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/bea-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/bea-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/bla-d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/bla-d.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/bla-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/bla-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/bls-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/bls-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/bom-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/bom-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/ccr-d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/ccr-d.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/ccr-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/ccr-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/ccr-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/ccr-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/cen-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/cen-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/cen-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/cen-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/cha-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/cha-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/cha-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/cha-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/chg-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/chg-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/chg-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/chg-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/cra-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/cra-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/cra-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/cra-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/cru-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/cru-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/dea-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/dea-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/dea-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/dea-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/dec-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/dec-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/dec-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/dec-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/dre-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/dre-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/dre-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/dre-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/exe-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/exe-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/exe-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/exe-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/fgt-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/fgt-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/fgt-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/fgt-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/for-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/for-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/for-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/for-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/fun-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/fun-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/fun-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/fun-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/gee-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/gee-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/gua-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/gua-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/gua-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/gua-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/ham-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/ham-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/hes-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/hes-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/hes-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/hes-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/hok-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/hok-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/hok-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/hok-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/hun-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/hun-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/inc-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/inc-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/ins-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/ins-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/ins-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/ins-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/isw-a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/isw-a.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/lor-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/lor-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/lor-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/lor-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/mar-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/mar-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/mar-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/mar-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/mer-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/mer-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/mer-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/mer-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/msc-d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/msc-d.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/msc-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/msc-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/msc-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/msc-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/original.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/phy-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/phy-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/phy-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/phy-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/plx-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/plx-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/plx-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/plx-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/pro-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/pro-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/pro-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/pro-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/pum-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/pum-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/pum-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/pum-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/pus-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/pus-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/pus-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/pus-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/ra-a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/ra-a.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/rea-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/rea-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/rin-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/rin-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/rin-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/rin-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/rit-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/rit-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/rpr-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/rpr-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/sbl-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/sbl-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/sbl-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/sbl-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/sie-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/sie-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/sol-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/sol-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/sol-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/sol-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/spc-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/spc-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/spc-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/spc-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/spt-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/spt-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/sum-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/sum-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/sum-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/sum-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/swo-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/swo-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/swo-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/swo-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/tac-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/tac-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/tac-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/tac-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/trp-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/trp-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/umd-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/umd-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/umd-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/umd-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/uny-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/uny-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/uny-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/uny-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/wah-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/wah-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/wah-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/wah-y.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/ark_card/uniequip/wdm-x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/ark_card/uniequip/wdm-x.png -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/background/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/background/bg.jpg -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/background/exusiai_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/background/exusiai_1.jpg -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/background/miumiu_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/background/miumiu_1.jpg -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/background/pepe_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/background/pepe_1.jpg -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/background/pepe_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/background/pepe_2.jpg -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/background/susie_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/background/susie_1.jpg -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/images/background/susie_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrostN0v0/nonebot-plugin-skland/e9cf0f1f94a6053347e4044dfcab5ac69bd5446c/nonebot_plugin_skland/resources/images/background/susie_2.jpg -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/templates/ark_card.html.jinja2: -------------------------------------------------------------------------------- 1 | {% from 'macros.html.jinja2' import assist_char_info %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Ark Card 10 | 13 | 14 | 15 | 16 |
18 |
19 | 20 |
21 | 22 |
23 |
24 | avatar 25 |
27 | {{ status.level }} 28 |
29 |
30 |
31 |

Dr. {{ status.name }}

32 |
33 |
34 | 入职日 35 |
36 | {{ status.register_time }} 37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 | 45 | 46 | 作战进度 47 | 48 | {{ status.mainStageProgress | default("全部完成", true) }} 49 | 50 |
51 |
52 | 53 | 54 | 雇佣干员 55 | {{ employed_chars }} 56 |
57 |
58 | 59 | 60 | 时装数量 61 | {{ skins }} 62 |
63 |
64 | 65 | 66 | 家具保有 67 | {{ building.furniture.total }} 68 |
69 |
70 | 71 | 72 | 蚀刻章 73 | {{ medals }} 74 |
75 |
76 |
77 | 78 |
79 | 80 |
81 |
82 | 83 | 助战干员 84 |
85 |
86 | {% for char in assist_chars %} 87 | {{ assist_char_info(char) }} 88 | {% endfor %} 89 |
90 |
91 | 92 |
93 |
94 | 95 | 基建信息 96 |
97 |
98 |
100 | 无人机 101 | 102 | {{ building.labor.labor_now }}/{{ building.labor.maxValue 103 | }} 104 | 105 | 106 |
107 |
109 | 休息进度 110 | 111 | {{ building.rested_chars }}/{{ building.dorm_chars }} 112 | 113 | 114 |
115 |
117 | 订单进度 118 | 119 | {{ building.trading_stock }}/{{ building.trading_stock_limit 120 | }} 121 | 122 | 123 |
124 |
126 | 制造进度 127 | 128 | {{ building.manufacture_stoke.current }}/{{ building.manufacture_stoke.total }} 129 | 130 | 131 |
132 |
134 | 干员疲劳 135 | {{ building.tiredChars | length 136 | }} 137 | 138 |
139 |
141 | 线索交流 142 | 143 | {% if building.meeting.clue.sharing %} 144 | 交流中 145 | {% else %} 146 | {{ building.meeting.clue.board | length }}/7 147 | {% endif %} 148 | 149 | 151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
160 | 161 |
162 | 理智 163 | 164 | {{ status.ap.ap_now }}/{{ status.ap.max }} 165 | 166 |
167 |
168 |
170 |
171 |
172 | {{ (status.ap.completeRecoveryTime - now_ts) | 173 | format_timestamp ~ '后全部恢复' if (status.ap.completeRecoveryTime - now_ts) > 0 else '已全部恢复' }} 174 |
175 |
176 |
178 | 179 |
180 | 公开招募 181 | 182 | {{ recruit_finished }}/{{ recruit_max }} 183 | 184 |
185 |
186 |
188 |
189 |
190 | {{ recruit_complete_time }} 191 |
192 |
193 |
195 | 196 |
197 | 公招刷新 198 | 199 | {{ building.hire.refreshCount }}/3 200 | 201 |
202 |
203 |
205 |
206 |
207 |
208 | {{ building.hire.refresh_complete_time }} 209 |
210 |
211 |
213 | 214 |
215 | 剿灭奖励 216 | 217 | {{ campaign.reward.current }}/{{ campaign.reward.total }} 218 | 219 |
220 |
221 |
223 |
224 |
225 | {{ now_ts | time_to_next_monday_4am }}后刷新 226 |
227 |
228 |
230 | 231 |
232 | 每日任务 233 | 234 | {{ routine.daily.current }}/{{ routine.daily.total }} 235 | 236 |
237 |
238 |
240 |
241 |
242 | {{ now_ts | time_to_next_4am }}后刷新 243 |
244 |
245 |
247 | 249 |
250 | 每周任务 251 | 252 | {{ routine.weekly.current }}/{{ routine.weekly.total }} 253 | 254 |
255 |
256 |
258 |
259 |
260 | {{ now_ts | time_to_next_monday_4am }}后刷新 261 |
262 | 263 |
264 |
266 | 268 |
269 | 数据增补仪 270 | 271 | {{ tower.reward.higherItem.current }}/{{ 272 | tower.reward.higherItem.total 273 | }} 274 | 275 |
276 |
277 |
279 |
280 |
281 | {{ (tower.reward.termTs - now_ts) | format_timestamp 282 | }}后刷新 283 |
284 |
285 |
287 | 289 |
290 | 数据增补条 291 | 292 | {{ tower.reward.lowerItem.current }}/{{ 293 | tower.reward.lowerItem.total }} 294 | 295 |
296 |
297 |
299 |
300 |
301 | {{ (tower.reward.termTs - now_ts) | format_timestamp 302 | }}后刷新 303 |
304 |
305 |
307 | 309 |
310 | 训练室 311 | {{ training_char }} 312 | 313 | {{ building.training.training_state }} 314 | 315 |
316 |
317 | {% if building.training.training_state == "空闲中" %} 318 |
319 | {% else %} 320 |
321 | {% endif %} 322 |
323 |
324 | 325 | {% if building.training.training_state == "空闲中" %} 326 | 空闲中 327 | {% else %} 328 | {{ building.training.remainSecs | format_timestamp }}后训练结束 329 | {% endif %} 330 | 331 |
332 |
333 |
334 |
335 | Generated by 336 | nonebot-plugin-skland 337 |
338 |
339 |
340 | 341 | 342 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/templates/index.css: -------------------------------------------------------------------------------- 1 | /*! tailwindcss v4.0.14 | MIT License | https://tailwindcss.com */ 2 | @layer theme, base, components, utilities; 3 | @layer theme { 4 | :root, :host { 5 | --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", 6 | "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 7 | --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", 8 | "Courier New", monospace; 9 | --color-white: #fff; 10 | --spacing: 0.25rem; 11 | --text-xs: 0.75rem; 12 | --text-xs--line-height: calc(1 / 0.75); 13 | --text-sm: 0.875rem; 14 | --text-sm--line-height: calc(1.25 / 0.875); 15 | --text-base: 1rem; 16 | --text-base--line-height: calc(1.5 / 1); 17 | --text-lg: 1.125rem; 18 | --text-lg--line-height: calc(1.75 / 1.125); 19 | --text-xl: 1.25rem; 20 | --text-xl--line-height: calc(1.75 / 1.25); 21 | --font-weight-bold: 700; 22 | --tracking-wider: 0.05em; 23 | --radius-md: 0.375rem; 24 | --radius-lg: 0.5rem; 25 | --blur-xs: 4px; 26 | --blur-sm: 8px; 27 | --default-font-family: var(--font-sans); 28 | --default-font-feature-settings: var(--font-sans--font-feature-settings); 29 | --default-font-variation-settings: var( 30 | --font-sans--font-variation-settings 31 | ); 32 | --default-mono-font-family: var(--font-mono); 33 | --default-mono-font-feature-settings: var( 34 | --font-mono--font-feature-settings 35 | ); 36 | --default-mono-font-variation-settings: var( 37 | --font-mono--font-variation-settings 38 | ); 39 | } 40 | } 41 | @layer base { 42 | *, ::after, ::before, ::backdrop, ::file-selector-button { 43 | box-sizing: border-box; 44 | margin: 0; 45 | padding: 0; 46 | border: 0 solid; 47 | } 48 | html, :host { 49 | line-height: 1.5; 50 | -webkit-text-size-adjust: 100%; 51 | tab-size: 4; 52 | font-family: var( --default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" ); 53 | font-feature-settings: var(--default-font-feature-settings, normal); 54 | font-variation-settings: var( --default-font-variation-settings, normal ); 55 | -webkit-tap-highlight-color: transparent; 56 | } 57 | body { 58 | line-height: inherit; 59 | } 60 | hr { 61 | height: 0; 62 | color: inherit; 63 | border-top-width: 1px; 64 | } 65 | abbr:where([title]) { 66 | -webkit-text-decoration: underline dotted; 67 | text-decoration: underline dotted; 68 | } 69 | h1, h2, h3, h4, h5, h6 { 70 | font-size: inherit; 71 | font-weight: inherit; 72 | } 73 | a { 74 | color: inherit; 75 | -webkit-text-decoration: inherit; 76 | text-decoration: inherit; 77 | } 78 | b, strong { 79 | font-weight: bolder; 80 | } 81 | code, kbd, samp, pre { 82 | font-family: var( --default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace ); 83 | font-feature-settings: var( --default-mono-font-feature-settings, normal ); 84 | font-variation-settings: var( --default-mono-font-variation-settings, normal ); 85 | font-size: 1em; 86 | } 87 | small { 88 | font-size: 80%; 89 | } 90 | sub, sup { 91 | font-size: 75%; 92 | line-height: 0; 93 | position: relative; 94 | vertical-align: baseline; 95 | } 96 | sub { 97 | bottom: -0.25em; 98 | } 99 | sup { 100 | top: -0.5em; 101 | } 102 | table { 103 | text-indent: 0; 104 | border-color: inherit; 105 | border-collapse: collapse; 106 | } 107 | :-moz-focusring { 108 | outline: auto; 109 | } 110 | progress { 111 | vertical-align: baseline; 112 | } 113 | summary { 114 | display: list-item; 115 | } 116 | ol, ul, menu { 117 | list-style: none; 118 | } 119 | img, svg, video, canvas, audio, iframe, embed, object { 120 | display: block; 121 | vertical-align: middle; 122 | } 123 | img, video { 124 | max-width: 100%; 125 | height: auto; 126 | } 127 | button, input, select, optgroup, textarea, ::file-selector-button { 128 | font: inherit; 129 | font-feature-settings: inherit; 130 | font-variation-settings: inherit; 131 | letter-spacing: inherit; 132 | color: inherit; 133 | border-radius: 0; 134 | background-color: transparent; 135 | opacity: 1; 136 | } 137 | :where(select:is([multiple], [size])) optgroup { 138 | font-weight: bolder; 139 | } 140 | :where(select:is([multiple], [size])) optgroup option { 141 | padding-inline-start: 20px; 142 | } 143 | ::file-selector-button { 144 | margin-inline-end: 4px; 145 | } 146 | ::placeholder { 147 | opacity: 1; 148 | color: color-mix(in oklab, currentColor 50%, transparent); 149 | } 150 | textarea { 151 | resize: vertical; 152 | } 153 | ::-webkit-search-decoration { 154 | -webkit-appearance: none; 155 | } 156 | ::-webkit-date-and-time-value { 157 | min-height: 1lh; 158 | text-align: inherit; 159 | } 160 | ::-webkit-datetime-edit { 161 | display: inline-flex; 162 | } 163 | ::-webkit-datetime-edit-fields-wrapper { 164 | padding: 0; 165 | } 166 | ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { 167 | padding-block: 0; 168 | } 169 | :-moz-ui-invalid { 170 | box-shadow: none; 171 | } 172 | button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button { 173 | appearance: button; 174 | } 175 | ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { 176 | height: auto; 177 | } 178 | [hidden]:where(:not([hidden="until-found"])) { 179 | display: none !important; 180 | } 181 | } 182 | @layer utilities { 183 | .absolute { 184 | position: absolute; 185 | } 186 | .relative { 187 | position: relative; 188 | } 189 | .inset-0 { 190 | inset: calc(var(--spacing) * 0); 191 | } 192 | .top-0\.5 { 193 | top: calc(var(--spacing) * 0.5); 194 | } 195 | .top-1\.75 { 196 | top: calc(var(--spacing) * 1.75); 197 | } 198 | .top-1\/2 { 199 | top: calc(1/2 * 100%); 200 | } 201 | .top-2 { 202 | top: calc(var(--spacing) * 2); 203 | } 204 | .top-4 { 205 | top: calc(var(--spacing) * 4); 206 | } 207 | .top-6 { 208 | top: calc(var(--spacing) * 6); 209 | } 210 | .top-\[-20px\] { 211 | top: -20px; 212 | } 213 | .top-\[-33px\] { 214 | top: -33px; 215 | } 216 | .top-\[50px\] { 217 | top: 50px; 218 | } 219 | .right-2\.5 { 220 | right: calc(var(--spacing) * 2.5); 221 | } 222 | .right-4 { 223 | right: calc(var(--spacing) * 4); 224 | } 225 | .right-\[-20px\] { 226 | right: -20px; 227 | } 228 | .bottom-0\.5 { 229 | bottom: calc(var(--spacing) * 0.5); 230 | } 231 | .bottom-2 { 232 | bottom: calc(var(--spacing) * 2); 233 | } 234 | .bottom-2\.5 { 235 | bottom: calc(var(--spacing) * 2.5); 236 | } 237 | .bottom-15\.5 { 238 | bottom: calc(var(--spacing) * 15.5); 239 | } 240 | .left-1\/2 { 241 | left: calc(1/2 * 100%); 242 | } 243 | .left-2 { 244 | left: calc(var(--spacing) * 2); 245 | } 246 | .left-2\.5 { 247 | left: calc(var(--spacing) * 2.5); 248 | } 249 | .left-3 { 250 | left: calc(var(--spacing) * 3); 251 | } 252 | .left-4 { 253 | left: calc(var(--spacing) * 4); 254 | } 255 | .left-19 { 256 | left: calc(var(--spacing) * 19); 257 | } 258 | .left-21 { 259 | left: calc(var(--spacing) * 21); 260 | } 261 | .left-22 { 262 | left: calc(var(--spacing) * 22); 263 | } 264 | .left-\[30\%\] { 265 | left: 30%; 266 | } 267 | .left-\[50\%\] { 268 | left: 50%; 269 | } 270 | .left-\[50px\] { 271 | left: 50px; 272 | } 273 | .left-\[70\%\] { 274 | left: 70%; 275 | } 276 | .-mt-1 { 277 | margin-top: calc(var(--spacing) * -1); 278 | } 279 | .mt-0\.5 { 280 | margin-top: calc(var(--spacing) * 0.5); 281 | } 282 | .mt-4 { 283 | margin-top: calc(var(--spacing) * 4); 284 | } 285 | .mt-12 { 286 | margin-top: calc(var(--spacing) * 12); 287 | } 288 | .mt-14 { 289 | margin-top: calc(var(--spacing) * 14); 290 | } 291 | .mb-2 { 292 | margin-bottom: calc(var(--spacing) * 2); 293 | } 294 | .ml-2 { 295 | margin-left: calc(var(--spacing) * 2); 296 | } 297 | .block { 298 | display: block; 299 | } 300 | .flex { 301 | display: flex; 302 | } 303 | .table { 304 | display: table; 305 | } 306 | .h-1\.25 { 307 | height: calc(var(--spacing) * 1.25); 308 | } 309 | .h-2\.5 { 310 | height: calc(var(--spacing) * 2.5); 311 | } 312 | .h-3\.75 { 313 | height: calc(var(--spacing) * 3.75); 314 | } 315 | .h-4 { 316 | height: calc(var(--spacing) * 4); 317 | } 318 | .h-5\.5 { 319 | height: calc(var(--spacing) * 5.5); 320 | } 321 | .h-6 { 322 | height: calc(var(--spacing) * 6); 323 | } 324 | .h-8 { 325 | height: calc(var(--spacing) * 8); 326 | } 327 | .h-10 { 328 | height: calc(var(--spacing) * 10); 329 | } 330 | .h-18\.75 { 331 | height: calc(var(--spacing) * 18.75); 332 | } 333 | .h-28\.25 { 334 | height: calc(var(--spacing) * 28.25); 335 | } 336 | .h-33\.25 { 337 | height: calc(var(--spacing) * 33.25); 338 | } 339 | .h-42 { 340 | height: calc(var(--spacing) * 42); 341 | } 342 | .h-75 { 343 | height: calc(var(--spacing) * 75); 344 | } 345 | .h-110 { 346 | height: calc(var(--spacing) * 110); 347 | } 348 | .h-\[25px\] { 349 | height: 25px; 350 | } 351 | .h-\[1160px\] { 352 | height: 1160px; 353 | } 354 | .h-auto { 355 | height: auto; 356 | } 357 | .h-full { 358 | height: 100%; 359 | } 360 | .w-0 { 361 | width: calc(var(--spacing) * 0); 362 | } 363 | .w-1\.25 { 364 | width: calc(var(--spacing) * 1.25); 365 | } 366 | .w-3\.75 { 367 | width: calc(var(--spacing) * 3.75); 368 | } 369 | .w-5 { 370 | width: calc(var(--spacing) * 5); 371 | } 372 | .w-6 { 373 | width: calc(var(--spacing) * 6); 374 | } 375 | .w-8 { 376 | width: calc(var(--spacing) * 8); 377 | } 378 | .w-10 { 379 | width: calc(var(--spacing) * 10); 380 | } 381 | .w-13\.5 { 382 | width: calc(var(--spacing) * 13.5); 383 | } 384 | .w-15 { 385 | width: calc(var(--spacing) * 15); 386 | } 387 | .w-16 { 388 | width: calc(var(--spacing) * 16); 389 | } 390 | .w-16\.25 { 391 | width: calc(var(--spacing) * 16.25); 392 | } 393 | .w-18 { 394 | width: calc(var(--spacing) * 18); 395 | } 396 | .w-18\.75 { 397 | width: calc(var(--spacing) * 18.75); 398 | } 399 | .w-24 { 400 | width: calc(var(--spacing) * 24); 401 | } 402 | .w-24\.5 { 403 | width: calc(var(--spacing) * 24.5); 404 | } 405 | .w-37\.5 { 406 | width: calc(var(--spacing) * 37.5); 407 | } 408 | .w-40 { 409 | width: calc(var(--spacing) * 40); 410 | } 411 | .w-43\.75 { 412 | width: calc(var(--spacing) * 43.75); 413 | } 414 | .w-101\.75 { 415 | width: calc(var(--spacing) * 101.75); 416 | } 417 | .w-152\.25 { 418 | width: calc(var(--spacing) * 152.25); 419 | } 420 | .w-153 { 421 | width: calc(var(--spacing) * 153); 422 | } 423 | .w-\[25px\] { 424 | width: 25px; 425 | } 426 | .w-\[706px\] { 427 | width: 706px; 428 | } 429 | .w-full { 430 | width: 100%; 431 | } 432 | .flex-1 { 433 | flex: 1; 434 | } 435 | .-translate-x-1\/2 { 436 | --tw-translate-x: calc(calc(1/2 * 100%) * -1); 437 | translate: var(--tw-translate-x) var(--tw-translate-y); 438 | } 439 | .-translate-y-1 { 440 | --tw-translate-y: calc(var(--spacing) * -1); 441 | translate: var(--tw-translate-x) var(--tw-translate-y); 442 | } 443 | .-translate-y-1\/2 { 444 | --tw-translate-y: calc(calc(1/2 * 100%) * -1); 445 | translate: var(--tw-translate-x) var(--tw-translate-y); 446 | } 447 | .-translate-y-4 { 448 | --tw-translate-y: calc(var(--spacing) * -4); 449 | translate: var(--tw-translate-x) var(--tw-translate-y); 450 | } 451 | .flex-col { 452 | flex-direction: column; 453 | } 454 | .flex-wrap { 455 | flex-wrap: wrap; 456 | } 457 | .items-center { 458 | align-items: center; 459 | } 460 | .items-start { 461 | align-items: flex-start; 462 | } 463 | .justify-between { 464 | justify-content: space-between; 465 | } 466 | .justify-center { 467 | justify-content: center; 468 | } 469 | .gap-4\.75 { 470 | gap: calc(var(--spacing) * 4.75); 471 | } 472 | .space-y-0\.5 { 473 | :where(& > :not(:last-child)) { 474 | --tw-space-y-reverse: 0; 475 | margin-block-start: calc(calc(var(--spacing) * 0.5) * var(--tw-space-y-reverse)); 476 | margin-block-end: calc(calc(var(--spacing) * 0.5) * calc(1 - var(--tw-space-y-reverse))); 477 | } 478 | } 479 | .space-y-1 { 480 | :where(& > :not(:last-child)) { 481 | --tw-space-y-reverse: 0; 482 | margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse)); 483 | margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse))); 484 | } 485 | } 486 | .space-y-6 { 487 | :where(& > :not(:last-child)) { 488 | --tw-space-y-reverse: 0; 489 | margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse)); 490 | margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse))); 491 | } 492 | } 493 | .space-x-2 { 494 | :where(& > :not(:last-child)) { 495 | --tw-space-x-reverse: 0; 496 | margin-inline-start: calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse)); 497 | margin-inline-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse))); 498 | } 499 | } 500 | .space-x-4 { 501 | :where(& > :not(:last-child)) { 502 | --tw-space-x-reverse: 0; 503 | margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse)); 504 | margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse))); 505 | } 506 | } 507 | .space-x-6\.75 { 508 | :where(& > :not(:last-child)) { 509 | --tw-space-x-reverse: 0; 510 | margin-inline-start: calc(calc(var(--spacing) * 6.75) * var(--tw-space-x-reverse)); 511 | margin-inline-end: calc(calc(var(--spacing) * 6.75) * calc(1 - var(--tw-space-x-reverse))); 512 | } 513 | } 514 | .space-x-7 { 515 | :where(& > :not(:last-child)) { 516 | --tw-space-x-reverse: 0; 517 | margin-inline-start: calc(calc(var(--spacing) * 7) * var(--tw-space-x-reverse)); 518 | margin-inline-end: calc(calc(var(--spacing) * 7) * calc(1 - var(--tw-space-x-reverse))); 519 | } 520 | } 521 | .space-x-7\.5 { 522 | :where(& > :not(:last-child)) { 523 | --tw-space-x-reverse: 0; 524 | margin-inline-start: calc(calc(var(--spacing) * 7.5) * var(--tw-space-x-reverse)); 525 | margin-inline-end: calc(calc(var(--spacing) * 7.5) * calc(1 - var(--tw-space-x-reverse))); 526 | } 527 | } 528 | .rounded-\[10px\] { 529 | border-radius: 10px; 530 | } 531 | .rounded-full { 532 | border-radius: calc(infinity * 1px); 533 | } 534 | .rounded-lg { 535 | border-radius: var(--radius-lg); 536 | } 537 | .rounded-md { 538 | border-radius: var(--radius-md); 539 | } 540 | .border-l-3 { 541 | border-left-style: var(--tw-border-style); 542 | border-left-width: 3px; 543 | } 544 | .border-\[\#6DADDC\] { 545 | border-color: #6DADDC; 546 | } 547 | .border-\[\#98E37B\] { 548 | border-color: #98E37B; 549 | } 550 | .border-\[\#D0685E\] { 551 | border-color: #D0685E; 552 | } 553 | .border-\[\#DC92FF\] { 554 | border-color: #DC92FF; 555 | } 556 | .border-\[\#DE9A67\] { 557 | border-color: #DE9A67; 558 | } 559 | .border-\[\#E9CF63\] { 560 | border-color: #E9CF63; 561 | } 562 | .bg-\[\#23BAFC\]\/80 { 563 | background-color: color-mix(in oklab, #23BAFC 80%, transparent); 564 | } 565 | .bg-\[\#080309\]\/80 { 566 | background-color: color-mix(in oklab, #080309 80%, transparent); 567 | } 568 | .bg-\[\#080808\]\/40 { 569 | background-color: color-mix(in oklab, #080808 40%, transparent); 570 | } 571 | .bg-\[\#080808\]\/45 { 572 | background-color: color-mix(in oklab, #080808 45%, transparent); 573 | } 574 | .bg-\[\#080808\]\/55 { 575 | background-color: color-mix(in oklab, #080808 55%, transparent); 576 | } 577 | .bg-\[\#080808\]\/80 { 578 | background-color: color-mix(in oklab, #080808 80%, transparent); 579 | } 580 | .bg-\[\#f0f0f0\]\/40 { 581 | background-color: color-mix(in oklab, #f0f0f0 40%, transparent); 582 | } 583 | .bg-white { 584 | background-color: var(--color-white); 585 | } 586 | .bg-white\/20 { 587 | background-color: color-mix(in oklab, var(--color-white) 20%, transparent); 588 | } 589 | .bg-white\/30 { 590 | background-color: color-mix(in oklab, var(--color-white) 30%, transparent); 591 | } 592 | .bg-white\/40 { 593 | background-color: color-mix(in oklab, var(--color-white) 40%, transparent); 594 | } 595 | .bg-gradient-to-b { 596 | --tw-gradient-position: to bottom in oklab; 597 | background-image: linear-gradient(var(--tw-gradient-stops)); 598 | } 599 | .bg-gradient-to-r { 600 | --tw-gradient-position: to right in oklab; 601 | background-image: linear-gradient(var(--tw-gradient-stops)); 602 | } 603 | .from-\[\#6DADDC\]\/40 { 604 | --tw-gradient-from: color-mix(in oklab, #6DADDC 40%, transparent); 605 | --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); 606 | } 607 | .from-\[\#98E37B\]\/40 { 608 | --tw-gradient-from: color-mix(in oklab, #98E37B 40%, transparent); 609 | --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); 610 | } 611 | .from-\[\#D0685E\]\/40 { 612 | --tw-gradient-from: color-mix(in oklab, #D0685E 40%, transparent); 613 | --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); 614 | } 615 | .from-\[\#DC92FF\]\/40 { 616 | --tw-gradient-from: color-mix(in oklab, #DC92FF 40%, transparent); 617 | --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); 618 | } 619 | .from-\[\#DE9A67\]\/40 { 620 | --tw-gradient-from: color-mix(in oklab, #DE9A67 40%, transparent); 621 | --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); 622 | } 623 | .from-\[\#E9CF63\]\/40 { 624 | --tw-gradient-from: color-mix(in oklab, #E9CF63 40%, transparent); 625 | --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); 626 | } 627 | .from-transparent { 628 | --tw-gradient-from: transparent; 629 | --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); 630 | } 631 | .to-transparent { 632 | --tw-gradient-to: transparent; 633 | --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); 634 | } 635 | .bg-cover { 636 | background-size: cover; 637 | } 638 | .bg-center { 639 | background-position: center; 640 | } 641 | .bg-no-repeat { 642 | background-repeat: no-repeat; 643 | } 644 | .object-contain { 645 | object-fit: contain; 646 | } 647 | .p-4 { 648 | padding: calc(var(--spacing) * 4); 649 | } 650 | .p-5 { 651 | padding: calc(var(--spacing) * 5); 652 | } 653 | .text-center { 654 | text-align: center; 655 | } 656 | .font-\[Akrobat\] { 657 | font-family: Akrobat; 658 | } 659 | .font-\[Bender\] { 660 | font-family: Bender; 661 | } 662 | .text-base { 663 | font-size: var(--text-base); 664 | line-height: var(--tw-leading, var(--text-base--line-height)); 665 | } 666 | .text-lg { 667 | font-size: var(--text-lg); 668 | line-height: var(--tw-leading, var(--text-lg--line-height)); 669 | } 670 | .text-sm { 671 | font-size: var(--text-sm); 672 | line-height: var(--tw-leading, var(--text-sm--line-height)); 673 | } 674 | .text-xl { 675 | font-size: var(--text-xl); 676 | line-height: var(--tw-leading, var(--text-xl--line-height)); 677 | } 678 | .text-xs { 679 | font-size: var(--text-xs); 680 | line-height: var(--tw-leading, var(--text-xs--line-height)); 681 | } 682 | .text-\[10px\] { 683 | font-size: 10px; 684 | } 685 | .font-bold { 686 | --tw-font-weight: var(--font-weight-bold); 687 | font-weight: var(--font-weight-bold); 688 | } 689 | .tracking-wider { 690 | --tw-tracking: var(--tracking-wider); 691 | letter-spacing: var(--tracking-wider); 692 | } 693 | .whitespace-nowrap { 694 | white-space: nowrap; 695 | } 696 | .text-\[\#6DADDC\] { 697 | color: #6DADDC; 698 | } 699 | .text-\[\#98E37B\] { 700 | color: #98E37B; 701 | } 702 | .text-\[\#080309\]\/80 { 703 | color: color-mix(in oklab, #080309 80%, transparent); 704 | } 705 | .text-\[\#D0685E\] { 706 | color: #D0685E; 707 | } 708 | .text-\[\#DC92FF\] { 709 | color: #DC92FF; 710 | } 711 | .text-\[\#DE9A67\] { 712 | color: #DE9A67; 713 | } 714 | .text-\[\#E9CF63\] { 715 | color: #E9CF63; 716 | } 717 | .text-white { 718 | color: var(--color-white); 719 | } 720 | .text-white\/60 { 721 | color: color-mix(in oklab, var(--color-white) 60%, transparent); 722 | } 723 | .text-white\/80 { 724 | color: color-mix(in oklab, var(--color-white) 80%, transparent); 725 | } 726 | .opacity-60 { 727 | opacity: 60%; 728 | } 729 | .opacity-80 { 730 | opacity: 80%; 731 | } 732 | .outline-2 { 733 | outline-style: var(--tw-outline-style); 734 | outline-width: 2px; 735 | } 736 | .outline-white\/45 { 737 | outline-color: color-mix(in oklab, var(--color-white) 45%, transparent); 738 | } 739 | .backdrop-blur-sm { 740 | --tw-backdrop-blur: blur(var(--blur-sm)); 741 | -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); 742 | backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); 743 | } 744 | .backdrop-blur-xs { 745 | --tw-backdrop-blur: blur(var(--blur-xs)); 746 | -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); 747 | backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); 748 | } 749 | } 750 | @layer utilities { 751 | .avater-shadow { 752 | box-shadow: 0px 0px 0px 3px #fbfbf3, 0px 4px 6px -1px rgba(6, 24, 44, 0.65), inset 0px 1px 0px 0px rgba(255, 255, 255, 0.08); 753 | } 754 | .rank-shadow { 755 | box-shadow: 0px 0px 0px 3px rgba(98, 93, 98, 0.6); 756 | } 757 | .reg-shadow { 758 | box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.3), 0px 14px 28px 0px rgba(0, 0, 0, 0.25), 0px 10px 10px 0px rgba(0, 0, 0, 0.22), inset 0px 0px 10px 0px rgba(128, 128, 128, 0.5); 759 | } 760 | .reg-shadow-blue { 761 | box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.3), 6px 2px 16px 0px rgba(136, 165, 191, 0.48), inset -6px -2px 16px 0px rgba(240, 240, 240, 0.15); 762 | } 763 | .card-shadow { 764 | box-shadow: 0px 54px 55px 0px rgba(0, 0, 0, 0.25), 0px -12px 30px 0px rgba(0, 0, 0, 0.12), 0px 4px 6px 0px rgba(0, 0, 0, 0.12), 0px 12px 13px 0px rgba(0, 0, 0, 0.17), 0px -3px 5px 0px rgba(0, 0, 0, 0.09); 765 | } 766 | .info-shadow { 767 | box-shadow: 0px 0px 10px 0px rgba(240, 240, 240, 0.3); 768 | } 769 | .info-content-shadow { 770 | box-shadow: 0px 2px 1px 0px rgba(0, 0, 0, 0.09), 0px 4px 2px 0px rgba(0, 0, 0, 0.09), 0px 8px 4px 0px rgba(0, 0, 0, 0.09), 0px 16px 8px 0px rgba(0, 0, 0, 0.09), 0px 32px 16px 0px rgba(0, 0, 0, 0.09); 771 | } 772 | .gradient-assist { 773 | background: linear-gradient(180deg, rgba(0, 0, 0, 0) 64%, rgba(0, 0, 0, 0.55) 75%, rgba(0, 0, 0, 0.8) 100%); 774 | } 775 | .dorm-filter { 776 | filter: brightness(0) saturate(100%) invert(92%) sepia(11%) saturate(1724%) hue-rotate(45deg) brightness(94%) contrast(88%); 777 | } 778 | .tired-filter { 779 | filter: brightness(0) saturate(100%) invert(46%) sepia(16%) saturate(2179%) hue-rotate(317deg) brightness(102%) contrast(70%); 780 | } 781 | .meeting-filter { 782 | filter: brightness(0) saturate(100%) invert(69%) sepia(28%) saturate(739%) hue-rotate(341deg) brightness(93%) contrast(87%); 783 | } 784 | .blue-bar { 785 | background: linear-gradient(270deg, rgba(26, 140, 216, 0.6) 0%, rgba(35, 186, 252, 0.6) 100%); 786 | box-shadow: 0px 0px 10px 0px rgba(35, 186, 252, 0.6); 787 | } 788 | .green-bar { 789 | background: linear-gradient(270deg, rgba(47, 168, 102, 0.6) 0%, rgba(61, 220, 132, 0.6) 100%); 790 | box-shadow: 0px 0px 10px 0px rgba(61, 220, 132, 0.6); 791 | } 792 | } 793 | @font-face { 794 | font-family: "Akrobat"; 795 | src: url("../fonts/Akrobat-Bold.otf") format("opentype"); 796 | } 797 | @font-face { 798 | font-family: "Bender"; 799 | src: url("../fonts/Bender.otf") format("opentype"); 800 | } 801 | @property --tw-translate-x { 802 | syntax: "*"; 803 | inherits: false; 804 | initial-value: 0; 805 | } 806 | @property --tw-translate-y { 807 | syntax: "*"; 808 | inherits: false; 809 | initial-value: 0; 810 | } 811 | @property --tw-translate-z { 812 | syntax: "*"; 813 | inherits: false; 814 | initial-value: 0; 815 | } 816 | @property --tw-space-y-reverse { 817 | syntax: "*"; 818 | inherits: false; 819 | initial-value: 0; 820 | } 821 | @property --tw-space-x-reverse { 822 | syntax: "*"; 823 | inherits: false; 824 | initial-value: 0; 825 | } 826 | @property --tw-border-style { 827 | syntax: "*"; 828 | inherits: false; 829 | initial-value: solid; 830 | } 831 | @property --tw-gradient-position { 832 | syntax: "*"; 833 | inherits: false; 834 | } 835 | @property --tw-gradient-from { 836 | syntax: ""; 837 | inherits: false; 838 | initial-value: #0000; 839 | } 840 | @property --tw-gradient-via { 841 | syntax: ""; 842 | inherits: false; 843 | initial-value: #0000; 844 | } 845 | @property --tw-gradient-to { 846 | syntax: ""; 847 | inherits: false; 848 | initial-value: #0000; 849 | } 850 | @property --tw-gradient-stops { 851 | syntax: "*"; 852 | inherits: false; 853 | } 854 | @property --tw-gradient-via-stops { 855 | syntax: "*"; 856 | inherits: false; 857 | } 858 | @property --tw-gradient-from-position { 859 | syntax: ""; 860 | inherits: false; 861 | initial-value: 0%; 862 | } 863 | @property --tw-gradient-via-position { 864 | syntax: ""; 865 | inherits: false; 866 | initial-value: 50%; 867 | } 868 | @property --tw-gradient-to-position { 869 | syntax: ""; 870 | inherits: false; 871 | initial-value: 100%; 872 | } 873 | @property --tw-font-weight { 874 | syntax: "*"; 875 | inherits: false; 876 | } 877 | @property --tw-tracking { 878 | syntax: "*"; 879 | inherits: false; 880 | } 881 | @property --tw-outline-style { 882 | syntax: "*"; 883 | inherits: false; 884 | initial-value: solid; 885 | } 886 | @property --tw-backdrop-blur { 887 | syntax: "*"; 888 | inherits: false; 889 | } 890 | @property --tw-backdrop-brightness { 891 | syntax: "*"; 892 | inherits: false; 893 | } 894 | @property --tw-backdrop-contrast { 895 | syntax: "*"; 896 | inherits: false; 897 | } 898 | @property --tw-backdrop-grayscale { 899 | syntax: "*"; 900 | inherits: false; 901 | } 902 | @property --tw-backdrop-hue-rotate { 903 | syntax: "*"; 904 | inherits: false; 905 | } 906 | @property --tw-backdrop-invert { 907 | syntax: "*"; 908 | inherits: false; 909 | } 910 | @property --tw-backdrop-opacity { 911 | syntax: "*"; 912 | inherits: false; 913 | } 914 | @property --tw-backdrop-saturate { 915 | syntax: "*"; 916 | inherits: false; 917 | } 918 | @property --tw-backdrop-sepia { 919 | syntax: "*"; 920 | inherits: false; 921 | } 922 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/resources/templates/macros.html.jinja2: -------------------------------------------------------------------------------- 1 | {% macro assist_char_info(char) %} 2 |
3 | 4 |
5 |
6 |
7 | {% if char.uniequip.endswith("original.png") %} 8 | 10 | {% else %} 11 | 13 | {% endif %} 14 | 15 |
16 |
17 | 18 |
19 | {% set classes = ["bg-white/40", "bg-white/40", "bg-white/40"] %} 20 | {% for i in range(char.specializeLevel) %} 21 | {% set _ = classes.__setitem__(i, "bg-white") %} 22 | {% endfor %} 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 |
34 | {{ char.level }} 35 |
36 |
37 | 38 |
39 |
40 | {% endmacro %} -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | from .cred import CRED as CRED 2 | from .rogue import Topics as Topics 3 | from .ark_card import ArkCard as ArkCard 4 | from .rogue import RogueHistory as RogueHistory 5 | from .ark_models import AssistChar as AssistChar 6 | from .ark_sign import ArkSignResponse as ArkSignResponse 7 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_card.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from datetime import datetime 3 | 4 | from pydantic import BaseModel 5 | from nonebot.compat import model_validator 6 | 7 | from .ark_models import ( 8 | Skin, 9 | Medal, 10 | Tower, 11 | Status, 12 | Recruit, 13 | Routine, 14 | Building, 15 | Campaign, 16 | BaseCount, 17 | Character, 18 | Equipment, 19 | AssistChar, 20 | ManufactureFormulaInfo, 21 | ) 22 | 23 | 24 | class CharInfo(BaseModel): 25 | id: str 26 | name: str 27 | 28 | 29 | class ArkCard(BaseModel): 30 | status: Status 31 | medal: Medal 32 | assistChars: list[AssistChar] 33 | chars: list[Character] 34 | skins: list[Skin] 35 | recruit: list[Recruit] 36 | campaign: Campaign 37 | tower: Tower 38 | routine: Routine 39 | building: Building 40 | equipmentInfoMap: dict[str, Equipment] 41 | manufactureFormulaInfoMap: dict[str, ManufactureFormulaInfo] 42 | charInfoMap: dict[str, CharInfo] 43 | 44 | @property 45 | def recruit_finished(self) -> int: 46 | return len([recruit for recruit in self.recruit if recruit.state == 1]) 47 | 48 | @property 49 | def recruit_complete_time(self) -> str: 50 | from ..render import format_timestamp 51 | 52 | finish_ts = max([recruit.finishTs for recruit in self.recruit]) 53 | if finish_ts == -1: 54 | return "招募已全部完成" 55 | format_time = format_timestamp(finish_ts - datetime.now().timestamp()) 56 | return f"{format_time}后全部完成" 57 | 58 | @property 59 | def trainee_char(self) -> str: 60 | trainee = self.building.training.trainee 61 | if self.building.training.training_state == "training" and trainee: 62 | return self.charInfoMap[trainee.charId].name 63 | return "" 64 | 65 | @model_validator(mode="after") 66 | def inject_uniequip_uris(cls, values) -> Any: 67 | from ..config import RES_DIR 68 | 69 | if isinstance(values, dict): 70 | assist_chars = values.get("assistChars", []) 71 | equipment_map = values.get("equipmentInfoMap", {}) 72 | else: 73 | assist_chars = values.assistChars 74 | equipment_map = values.equipmentInfoMap 75 | 76 | for char in assist_chars: 77 | if char.equip and (equip := equipment_map.get(char.equip.id)): 78 | equip_id = equip.typeIcon 79 | else: 80 | equip_id = "original" 81 | 82 | char.uniequip = (RES_DIR / "images" / "ark_card" / "uniequip" / f"{equip_id}.png").as_uri() 83 | if isinstance(values, dict): 84 | values["assistChars"] = assist_chars 85 | return values 86 | else: 87 | return values 88 | 89 | @model_validator(mode="after") 90 | def inject_manufacture_stoke(cls, values) -> Any: 91 | if isinstance(values, dict): 92 | building = values.get("building") 93 | formula_map = values.get("manufactureFormulaInfoMap") 94 | else: 95 | building = values.building 96 | formula_map = values.manufactureFormulaInfoMap 97 | 98 | if not building or not formula_map: 99 | return values 100 | 101 | stoke_max = 0 102 | stoke_count = 0 103 | for manu in building.manufactures: 104 | if manu.formulaId in formula_map: 105 | formula_weight = formula_map[manu.formulaId].weight 106 | stoke_max += int(manu.capacity / formula_weight) 107 | elapsed_time = datetime.now().timestamp() - manu.lastUpdateTime 108 | cost_time = formula_map[manu.formulaId].costPoint / manu.speed 109 | additional_complete = round(elapsed_time / cost_time) 110 | if datetime.now().timestamp() >= manu.completeWorkTime: 111 | stoke_count += manu.capacity // formula_weight 112 | else: 113 | to_be_processed = (manu.completeWorkTime - manu.lastUpdateTime) / (cost_time / manu.speed) 114 | has_processed = to_be_processed - int(to_be_processed) 115 | additional_complete = (elapsed_time - has_processed * cost_time) / cost_time 116 | stoke_count += manu.complete + int(additional_complete) + 1 117 | 118 | manufacture_stoke = BaseCount(current=stoke_count, total=stoke_max) 119 | 120 | if isinstance(values, dict): 121 | values["building"].manufacture_stoke = manufacture_stoke 122 | return values 123 | else: 124 | values.building.manufacture_stoke = manufacture_stoke 125 | return values 126 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/__init__.py: -------------------------------------------------------------------------------- 1 | from .skins import Skin as Skin 2 | from .medal import Medal as Medal 3 | from .tower import Tower as Tower 4 | from .status import Status as Status 5 | from .recruit import Recruit as Recruit 6 | from .routine import Routine as Routine 7 | from .base import BaseCount as BaseCount 8 | from .chars import Character as Character 9 | from .building import Building as Building 10 | from .campaign import Campaign as Campaign 11 | from .assist_chars import Equipment as Equipment 12 | from .assist_chars import AssistChar as AssistChar 13 | from .buildings import ManufactureFormulaInfo as ManufactureFormulaInfo 14 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/assist_chars.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import quote 2 | 3 | from nonebot import logger 4 | from pydantic import BaseModel 5 | 6 | from .base import Equip 7 | from ...config import RES_DIR, CACHE_DIR 8 | 9 | 10 | class AssistChar(BaseModel): 11 | """ 12 | 助战干员 13 | 14 | Attributes: 15 | charId : 干员 ID 16 | skinId : 皮肤 ID 17 | level : 等级 18 | evolvePhase : 升级阶段 19 | potentialRank : 潜能等级 20 | skillId : 技能 ID 21 | mainSkillLvl : 主技能等级 22 | specializeLevel : 专精等级 23 | equip : 装备技能 24 | """ 25 | 26 | charId: str 27 | skinId: str 28 | level: int 29 | evolvePhase: int 30 | potentialRank: int 31 | skillId: str 32 | mainSkillLvl: int 33 | specializeLevel: int 34 | equip: Equip | None = None 35 | uniequip: str | None = None 36 | 37 | @property 38 | def portrait(self) -> str: 39 | for symbol in ["@", "#"]: 40 | if symbol in self.skinId: 41 | portrait_id = self.skinId.replace(symbol, "_", 1) 42 | break 43 | img_path = CACHE_DIR / "portrait" / f"{portrait_id}.png" 44 | if not img_path.exists(): 45 | encoded_id = quote(self.skinId, safe="") 46 | img_path = f"https://web.hycdn.cn/arknights/game/assets/char_skin/portrait/{encoded_id}.png" 47 | logger.debug(f"Portrait not found locally, using URL: {img_path}") 48 | return img_path 49 | return img_path.as_uri() 50 | 51 | @property 52 | def potential(self) -> str: 53 | img_path = RES_DIR / "images" / "ark_card" / "potential" / f"potential_{self.potentialRank}.png" 54 | return img_path.as_uri() 55 | 56 | @property 57 | def skill(self) -> str: 58 | img_path = CACHE_DIR / "skill" / f"skill_icon_{self.skillId}.png" 59 | if not img_path.exists(): 60 | encoded_id = quote(self.skillId, safe="") 61 | img_path = f"https://web.hycdn.cn/arknights/game/assets/char_skill/{encoded_id}.png" 62 | logger.debug(f"Skill icon not found locally, using URL: {img_path}") 63 | return img_path 64 | return img_path.as_uri() 65 | 66 | @property 67 | def evolve(self) -> str: 68 | img_path = RES_DIR / "images" / "ark_card" / "elite" / f"elite_{self.evolvePhase}.png" 69 | return img_path.as_uri() 70 | 71 | 72 | class Equipment(BaseModel): 73 | id: str 74 | name: str 75 | typeIcon: str 76 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/base.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class BaseCount(BaseModel): 5 | """ 6 | 获取/完成进度 7 | 8 | Attributes: 9 | current (int): 当前值。 10 | total (int): 总值/上限。 11 | """ 12 | 13 | current: int 14 | total: int 15 | 16 | 17 | class Equip(BaseModel): 18 | """ 19 | 干员装备技能 20 | 21 | Attributes: 22 | id : 技能 ID 23 | level : 等级 24 | """ 25 | 26 | id: str 27 | level: int 28 | locked: bool 29 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/building.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from pydantic import BaseModel 4 | 5 | from .base import BaseCount 6 | from .buildings import ( 7 | Hire, 8 | Labor, 9 | Power, 10 | Control, 11 | Meeting, 12 | Trading, 13 | Training, 14 | Dormitory, 15 | Furniture, 16 | TiredChar, 17 | Manufacture, 18 | ) 19 | 20 | 21 | class Building(BaseModel): 22 | """ 23 | 基建信息 24 | 25 | Attributes: 26 | tiredChars : 疲劳干员 27 | powers : 发电站 28 | manufactures : 制造站 29 | tradings : 交易站 30 | dormitories : 宿舍 31 | meeting : 会客室 32 | hire : 人力办公室 33 | training : 训练室 34 | labor : 无人机 35 | furniture : 家具 36 | control : 控制中枢 37 | """ 38 | 39 | tiredChars: list[TiredChar] 40 | powers: list[Power] 41 | manufactures: list[Manufacture] 42 | tradings: list[Trading] 43 | dormitories: list[Dormitory] 44 | meeting: Meeting 45 | hire: Hire 46 | training: Training 47 | labor: Labor 48 | furniture: Furniture 49 | control: Control 50 | manufacture_stoke: BaseCount | None = None 51 | 52 | @property 53 | def rested_chars(self): 54 | """此处未计算基建技能影响,因此实际休息进度可能有差异""" 55 | rested_count = 0 56 | for dorm in self.dormitories: 57 | for char in dorm.chars: 58 | ap_gain_rate = 1.5 + dorm.level * 0.1 + 0.0004 * dorm.comfort * 100 59 | time_diff = datetime.now().timestamp() - char.lastApAddTime 60 | ap_now = min(char.ap + time_diff * ap_gain_rate, 8640000) 61 | if ap_now == 8640000: 62 | rested_count += 1 63 | return rested_count 64 | 65 | @property 66 | def dorm_chars(self): 67 | dorm_char_count = 0 68 | for dorm in self.dormitories: 69 | dorm_char_count += len(dorm.chars) 70 | return dorm_char_count 71 | 72 | @property 73 | def trading_stock(self): 74 | """获取交易站库存""" 75 | stock_count = 0 76 | for trading in self.tradings: 77 | if trading.completeWorkTime >= datetime.now().timestamp(): 78 | stock_count += 1 79 | stock_count += len(trading.stock) 80 | return stock_count 81 | 82 | @property 83 | def trading_stock_limit(self): 84 | """获取交易站库存上限""" 85 | stock_limit = 0 86 | for trading in self.tradings: 87 | stock_limit += trading.stockLimit 88 | return stock_limit 89 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/buildings/__init__.py: -------------------------------------------------------------------------------- 1 | from .hire import Hire as Hire 2 | from .base import Labor as Labor 3 | from .power import Power as Power 4 | from .control import Control as Control 5 | from .meeting import Meeting as Meeting 6 | from .trading import Trading as Trading 7 | from .base import Furniture as Furniture 8 | from .tired import TiredChar as TiredChar 9 | from .training import Training as Training 10 | from .dormitory import Dormitory as Dormitory 11 | from .manufacture import Manufacture as Manufacture 12 | from .manufacture import ManufactureFormulaInfo as ManufactureFormulaInfo 13 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/buildings/base.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class BubbleInfo(BaseModel): 7 | add: int 8 | ts: int 9 | 10 | 11 | class Bubble(BaseModel): 12 | normal: BubbleInfo 13 | assist: BubbleInfo 14 | 15 | 16 | class BuildingChar(BaseModel): 17 | """基建进驻干员信息""" 18 | 19 | charId: str 20 | ap: int 21 | lastApAddTime: int 22 | index: int 23 | bubble: Bubble 24 | workTime: int 25 | 26 | 27 | class Labor(BaseModel): 28 | """无人机""" 29 | 30 | maxValue: int 31 | value: int 32 | lastUpdateTime: int 33 | remainSecs: int 34 | 35 | @property 36 | def labor_now(self) -> int: 37 | if self.maxValue == self.value: 38 | return self.maxValue 39 | elapsed_time = datetime.now().timestamp() - self.lastUpdateTime 40 | labor_increment = elapsed_time / (self.remainSecs / (self.maxValue - self.value)) 41 | return min(int(labor_increment + self.value), self.maxValue) 42 | 43 | 44 | class Furniture(BaseModel): 45 | """家具持有数""" 46 | 47 | total: int 48 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/buildings/control.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from .base import BuildingChar 4 | 5 | 6 | class Control(BaseModel): 7 | """控制中枢""" 8 | 9 | slotId: str 10 | slotState: int 11 | level: int 12 | chars: list[BuildingChar] 13 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/buildings/dormitory.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from .base import BuildingChar 4 | 5 | 6 | class Dormitory(BaseModel): 7 | """宿舍""" 8 | 9 | slotId: str 10 | level: int 11 | chars: list[BuildingChar] 12 | comfort: int 13 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/buildings/hire.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from pydantic import BaseModel 4 | 5 | from .base import BuildingChar 6 | 7 | 8 | class Hire(BaseModel): 9 | """人事办公室""" 10 | 11 | slotId: str 12 | level: int 13 | chars: list[BuildingChar] 14 | state: int 15 | refreshCount: int 16 | completeWorkTime: int 17 | slotState: int 18 | 19 | @property 20 | def refresh_complete_time(self) -> str: 21 | from ....render import format_timestamp 22 | 23 | if self.refreshCount == 3: 24 | return "可进行公开招募标签刷新" 25 | format_time = format_timestamp(self.completeWorkTime - datetime.now().timestamp()) 26 | return f"{format_time}后获取刷新次数" 27 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/buildings/manufacture.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from .base import BuildingChar 4 | 5 | 6 | class Manufacture(BaseModel): 7 | """制造站""" 8 | 9 | slotId: str 10 | level: int 11 | chars: list[BuildingChar] 12 | completeWorkTime: int 13 | lastUpdateTime: int 14 | formulaId: str 15 | capacity: int 16 | weight: int 17 | complete: int 18 | remain: int 19 | speed: float 20 | 21 | 22 | class ManufactureFormulaInfo(BaseModel): 23 | """制造站配方""" 24 | 25 | id: str 26 | itemId: str 27 | weight: int 28 | costPoint: int 29 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/buildings/meeting.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from .base import BuildingChar 4 | 5 | 6 | class Clue(BaseModel): 7 | """线索信息""" 8 | 9 | own: int 10 | received: int 11 | dailyReward: bool 12 | needReceive: int 13 | board: list[str] 14 | sharing: bool 15 | shareCompleteTime: int 16 | 17 | 18 | class Meeting(BaseModel): 19 | """会客厅""" 20 | 21 | slotId: str 22 | level: int 23 | chars: list[BuildingChar] 24 | clue: Clue 25 | lastUpdateTime: int 26 | completeWorkTime: int 27 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/buildings/power.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from .base import BuildingChar 4 | 5 | 6 | class Power(BaseModel): 7 | """发电站""" 8 | 9 | slotId: str 10 | level: int 11 | chars: list[BuildingChar] 12 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/buildings/tired.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from .base import Bubble 4 | 5 | 6 | class TiredChar(BaseModel): 7 | """疲劳干员""" 8 | 9 | charId: str 10 | ap: int 11 | lastApAddTime: int 12 | roomSlotId: str 13 | index: int 14 | bubble: Bubble 15 | workTime: int 16 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/buildings/trading.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from .base import BuildingChar 4 | 5 | 6 | class DeliveryItem(BaseModel): 7 | id: str 8 | count: int 9 | type: str 10 | 11 | 12 | class Gain(BaseModel): 13 | id: str 14 | count: int 15 | type: str 16 | 17 | 18 | class StockItem(BaseModel): 19 | """储存订单""" 20 | 21 | instId: int 22 | type: str 23 | delivery: list[DeliveryItem] 24 | gain: Gain 25 | isViolated: bool 26 | 27 | 28 | class Trading(BaseModel): 29 | """贸易站""" 30 | 31 | slotId: str 32 | level: int 33 | chars: list[BuildingChar] 34 | completeWorkTime: int 35 | lastUpdateTime: int 36 | strategy: str 37 | stock: list[StockItem] 38 | stockLimit: int 39 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/buildings/training.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class Trainee(BaseModel): 5 | charId: str 6 | targetSkill: int 7 | ap: int 8 | lastApAddTime: int 9 | 10 | 11 | class Trainer(BaseModel): 12 | charId: str 13 | ap: int 14 | lastApAddTime: int 15 | 16 | 17 | class Training(BaseModel): 18 | slotId: str 19 | level: int 20 | trainee: Trainee | None = None 21 | trainer: Trainer | None = None 22 | remainPoint: float 23 | speed: float 24 | lastUpdateTime: int 25 | remainSecs: int 26 | slotState: int 27 | 28 | @property 29 | def training_state(self) -> str: 30 | if self.trainee: 31 | if self.trainee.targetSkill == -1: 32 | return "空闲中" 33 | else: 34 | match self.trainee.targetSkill: 35 | case -1: 36 | return "空闲中" 37 | case 0: 38 | return "1技能" 39 | case 1: 40 | return "2技能" 41 | case 2: 42 | return "3技能" 43 | return "空闲中" 44 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/campaign.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from .base import BaseCount 4 | 5 | 6 | class CampaignRecord(BaseModel): 7 | """ 8 | 剿灭记录 9 | 10 | Attributes: 11 | campaignId : 剿灭 ID 12 | maxKills : 最大击杀数 13 | """ 14 | 15 | campaignId: str 16 | maxKills: int 17 | 18 | 19 | class Campaign(BaseModel): 20 | """剿灭作战信息""" 21 | 22 | records: list[CampaignRecord] 23 | reward: BaseCount 24 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/chars.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from .base import Equip 4 | 5 | 6 | class Skill(BaseModel): 7 | """干员技能""" 8 | 9 | id: str 10 | specializeLevel: int 11 | 12 | 13 | class Character(BaseModel): 14 | """持有干员""" 15 | 16 | charId: str 17 | skinId: str 18 | level: int 19 | evolvePhase: int 20 | potentialRank: int 21 | mainSkillLvl: int 22 | skills: list[Skill] 23 | equip: list[Equip] 24 | favorPercent: int 25 | defaultSkillId: str 26 | gainTime: int 27 | defaultEquipId: str 28 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/medal.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class MedalLayout(BaseModel): 5 | """ 6 | 蚀刻章布局 7 | 8 | Attributes: 9 | id (str): 奖章的唯一标识符 10 | pos (List[int]): 奖章的坐标位置,包含两个整数值(x, y) 11 | """ 12 | 13 | id: str 14 | pos: list[int] 15 | 16 | 17 | class Medal(BaseModel): 18 | """ 19 | 佩戴蚀刻章信息。 20 | 21 | Attributes: 22 | type (str): 佩戴蚀刻章类型,自定义或者套装 23 | template (str): 蚀刻章模板 24 | templateMedalList (List): 模板蚀刻章列表 25 | customMedalLayout (List[MedalLayout]): 蚀刻章的自定义布局 26 | total (int): 拥有的蚀刻章总数 27 | """ 28 | 29 | type: str 30 | template: str 31 | templateMedalList: list[str] 32 | customMedalLayout: list[MedalLayout] 33 | total: int 34 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/recruit.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class Recruit(BaseModel): 5 | """ 6 | 公招信息 7 | 8 | Attributes: 9 | startTs : 开始时间戳 10 | finishTs : 结束时间戳 11 | state : 状态 12 | """ 13 | 14 | startTs: int 15 | finishTs: int 16 | state: int 17 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/routine.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from .base import BaseCount 4 | 5 | 6 | class Routine(BaseModel): 7 | """ 8 | 日/周常任务完成进度 9 | 10 | Attributes: 11 | daily : 日常任务进度 12 | weekly : 周常任务进度 13 | """ 14 | 15 | daily: BaseCount 16 | weekly: BaseCount 17 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/skins.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class Skin(BaseModel): 5 | """持有皮肤""" 6 | 7 | id: str 8 | ts: int 9 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/status.py: -------------------------------------------------------------------------------- 1 | import math 2 | from datetime import datetime 3 | 4 | from pydantic import BaseModel 5 | 6 | 7 | class Avatar(BaseModel): 8 | """角色头像信息""" 9 | 10 | type: str 11 | id: str 12 | url: str 13 | 14 | 15 | class Secretary(BaseModel): 16 | """助理干员信息""" 17 | 18 | charId: str 19 | skinId: str 20 | 21 | 22 | class AP(BaseModel): 23 | """理智""" 24 | 25 | current: int 26 | max: int 27 | lastApAddTime: int 28 | completeRecoveryTime: int 29 | 30 | @property 31 | def ap_now(self) -> int: 32 | """计算当前理智 ap_now ,并确保不超过最大理智值。""" 33 | current_time = datetime.now().timestamp() 34 | ap_now = self.max - math.ceil((self.completeRecoveryTime - current_time) / 360) 35 | ap_now = min(ap_now, self.max) 36 | 37 | return ap_now 38 | 39 | 40 | class Exp(BaseModel): 41 | """经验值""" 42 | 43 | current: int 44 | max: int 45 | 46 | 47 | class Status(BaseModel): 48 | """ 49 | 角色状态信息 50 | 51 | Attributes: 52 | uid : 角色 UID 53 | name : 角色名称 54 | level : 等级 55 | avatar : 头像信息 56 | registerTs : 注册时间戳 57 | secretary : 助理干员信息 58 | ap :理智信息 59 | lastOnlineTs : 角色最后在线时间戳 60 | exp : 经验值 61 | """ 62 | 63 | uid: str 64 | name: str 65 | level: int 66 | avatar: Avatar 67 | registerTs: int 68 | mainStageProgress: str 69 | secretary: Secretary 70 | resume: str 71 | subscriptionEnd: int 72 | ap: AP 73 | storeTs: int 74 | lastOnlineTs: int 75 | charCnt: int 76 | furnitureCnt: int 77 | skinCnt: int 78 | exp: Exp 79 | 80 | @property 81 | def register_time(self) -> str: 82 | return datetime.fromtimestamp(self.registerTs).strftime("%Y-%m-%d") 83 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_models/tower.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from .base import BaseCount 4 | 5 | 6 | class TowerRecord(BaseModel): 7 | """ 8 | 保全派驻记录 9 | 10 | Attributes: 11 | towerId : 保全派驻 ID 12 | best : 最高进度 13 | """ 14 | 15 | towerId: str 16 | best: int 17 | 18 | 19 | class TowerReward(BaseModel): 20 | """保全派驻奖励进度""" 21 | 22 | higherItem: BaseCount 23 | lowerItem: BaseCount 24 | termTs: int 25 | 26 | 27 | class Tower(BaseModel): 28 | """保全派驻信息""" 29 | 30 | records: list[TowerRecord] 31 | reward: TowerReward 32 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/ark_sign.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class Resource(BaseModel): 5 | name: str 6 | 7 | 8 | class Award(BaseModel): 9 | resource: Resource 10 | count: int 11 | 12 | 13 | class ArkSignResponse(BaseModel): 14 | awards: list[Award] 15 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/cred.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class CRED: 6 | cred: str 7 | """登录凭证""" 8 | token: str 9 | """登录凭证对应的token""" 10 | userId: str | None = None 11 | """用户ID""" 12 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/schemas/rogue.py: -------------------------------------------------------------------------------- 1 | from dataclasses import field, dataclass 2 | 3 | from nonebot.compat import PYDANTIC_V2 4 | from pydantic import HttpUrl, BaseModel, ConfigDict 5 | 6 | 7 | @dataclass 8 | class Topics: 9 | topic: str 10 | topic_id: str = field(init=False) 11 | 12 | _MAPPING = {"萨米": "rogue_3", "萨卡兹": "rogue_4"} 13 | 14 | def __post_init__(self): 15 | self.topic_id = self._MAPPING[self.topic] 16 | 17 | 18 | class Dynamic(BaseModel): 19 | type: int 20 | url: HttpUrl 21 | videoId: str 22 | kind: int 23 | filename: str 24 | transcodeStatus: int 25 | 26 | 27 | class Char(BaseModel): 28 | id: str 29 | rarity: int 30 | profession: str 31 | type: str 32 | upgradePhase: int 33 | evolvePhase: int 34 | level: int 35 | name: str 36 | 37 | 38 | class Tag(BaseModel): 39 | name: str 40 | icon: HttpUrl 41 | description: str 42 | id: int 43 | 44 | 45 | class Band(BaseModel): 46 | id: str 47 | name: str 48 | 49 | 50 | class Totem(BaseModel): 51 | id: str 52 | count: int 53 | 54 | 55 | class Record(BaseModel): 56 | id: str 57 | modeGrade: int 58 | mode: str 59 | success: int 60 | lastChars: list[Char] 61 | initChars: list[Char] 62 | troopChars: list[Char] 63 | gainRelicList: list 64 | cntCrossedZone: int 65 | cntArrivedNode: int 66 | cntBattleNormal: int 67 | cntBattleElite: int 68 | cntBattleBoss: int 69 | cntGainRelicItem: int 70 | cntRecruitUpgrade: int 71 | totemList: list[Totem] 72 | seed: str 73 | tagList: list[Tag] 74 | lastStage: str 75 | score: int 76 | band: Band 77 | startTs: str 78 | endTs: str 79 | endingText: str 80 | isCollect: bool 81 | 82 | 83 | class Medal(BaseModel): 84 | count: int 85 | current: int 86 | 87 | 88 | class RogueHistory(BaseModel): 89 | medal: Medal 90 | modeGrade: int 91 | mode: str 92 | score: int 93 | bpLevel: int 94 | chars: list[Char] 95 | tagList: list[Tag] 96 | records: list[Record] 97 | favourRecords: list 98 | 99 | if PYDANTIC_V2: 100 | model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) 101 | else: 102 | 103 | class Config: 104 | extra = "allow" 105 | arbitrary_types_allowed = True 106 | -------------------------------------------------------------------------------- /nonebot_plugin_skland/utils.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | from nonebot import logger 3 | from pydantic import AnyUrl as Url 4 | from nonebot_plugin_alconna import UniMessage 5 | from nonebot_plugin_orm import async_scoped_session 6 | 7 | from .schemas import CRED 8 | from .model import User, Character 9 | from .db_handler import delete_characters 10 | from .api import SklandAPI, SklandLoginAPI 11 | from .config import RES_DIR, CustomSource, config 12 | from .exception import LoginException, RequestException, UnauthorizedException 13 | 14 | 15 | async def get_characters_and_bind(user: User, session: async_scoped_session): 16 | await delete_characters(user, session) 17 | 18 | cred = CRED(cred=user.cred, token=user.cred_token) 19 | binding_app_list = await SklandAPI.get_binding(cred) 20 | for app in binding_app_list: 21 | for character in app["bindingList"]: 22 | character_model = Character( 23 | id=user.id, 24 | uid=character["uid"], 25 | nickname=character["nickName"], 26 | app_code=app["appCode"], 27 | channel_master_id=character["channelMasterId"], 28 | isdefault=character["isDefault"], 29 | ) 30 | if len(app["bindingList"]) == 1: 31 | character_model.isdefault = True 32 | session.add(character_model) 33 | await session.commit() 34 | 35 | 36 | def refresh_access_token_if_needed(func): 37 | """装饰器:如果 access_token 失效,刷新后重试""" 38 | 39 | async def wrapper(user: User, *args, **kwargs): 40 | try: 41 | return await func(user, *args, **kwargs) 42 | except LoginException: 43 | if not user.access_token: 44 | await UniMessage("cred失效,用户没有绑定token,无法自动刷新cred").send(at_sender=True) 45 | 46 | try: 47 | grant_code = await SklandLoginAPI.get_grant_code(user.access_token) 48 | new_cred = await SklandLoginAPI.get_cred(grant_code) 49 | user.cred, user.cred_token = new_cred.cred, new_cred.token 50 | logger.info("access_token 失效,已自动刷新") 51 | return await func(user, *args, **kwargs) 52 | except (RequestException, LoginException, UnauthorizedException) as e: 53 | await UniMessage(f"接口请求失败,{e.args[0]}").send(at_sender=True) 54 | except RequestException as e: 55 | await UniMessage(f"接口请求失败,{e.args[0]}").send(at_sender=True) 56 | 57 | return wrapper 58 | 59 | 60 | def refresh_cred_token_if_needed(func): 61 | """装饰器:如果 cred_token 失效,刷新后重试""" 62 | 63 | async def wrapper(user: User, *args, **kwargs): 64 | try: 65 | return await func(user, *args, **kwargs) 66 | except UnauthorizedException: 67 | try: 68 | new_token = await SklandLoginAPI.refresh_token(user.cred) 69 | user.cred_token = new_token 70 | logger.info("cred_token 失效,已自动刷新") 71 | return await func(user, *args, **kwargs) 72 | except (RequestException, LoginException, UnauthorizedException) as e: 73 | await UniMessage(f"接口请求失败,{e.args[0]}").send(at_sender=True) 74 | except RequestException as e: 75 | await UniMessage(f"接口请求失败,{e.args[0]}").send(at_sender=True) 76 | 77 | return wrapper 78 | 79 | 80 | async def get_lolicon_image() -> str: 81 | async with httpx.AsyncClient() as client: 82 | response = await client.get("https://api.lolicon.app/setu/v2?tag=arknights") 83 | return response.json()["data"][0]["urls"]["original"] 84 | 85 | 86 | async def get_background_image() -> str | Url: 87 | default_background = RES_DIR / "images" / "background" / "bg.jpg" 88 | 89 | match config.background_source: 90 | case "default": 91 | background_image = default_background.as_posix() 92 | case "Lolicon": 93 | background_image = await get_lolicon_image() 94 | case "random": 95 | background_image = CustomSource(uri=RES_DIR / "images" / "background").to_uri() 96 | case CustomSource() as cs: 97 | background_image = cs.to_uri() 98 | case _: 99 | background_image = default_background.as_posix() 100 | 101 | return background_image 102 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nonebot-plugin-skland", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "tailwindcss": "^4.0.14" 9 | } 10 | }, 11 | "node_modules/tailwindcss": { 12 | "version": "4.0.14", 13 | "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.0.14.tgz", 14 | "integrity": "sha512-92YT2dpt671tFiHH/e1ok9D987N9fHD5VWoly1CdPD/Cd1HMglvZwP3nx2yTj2lbXDAHt8QssZkxTLCCTNL+xw==", 15 | "license": "MIT" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "tailwindcss": "^4.0.14" 4 | } 5 | } -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "nonebot-plugin-skland" 3 | version = "0.2.1" 4 | description = "通过森空岛查询游戏数据" 5 | readme = "README.md" 6 | license = { text = "MIT" } 7 | authors = [{ name = "FrostN0v0", email = "1614591760@qq.com" }] 8 | requires-python = ">=3.10" 9 | dependencies = [ 10 | "httpx>=0.28.1", 11 | "nonebot-plugin-alconna>=0.57.1", 12 | "nonebot-plugin-argot>=0.1.7", 13 | "nonebot-plugin-htmlrender>=0.6.5", 14 | "nonebot-plugin-localstore>=0.7.3", 15 | "nonebot-plugin-orm>=0.7.6", 16 | "nonebot-plugin-user>=0.4.4", 17 | "nonebot2>=2.4.2", 18 | "qrcode[pil]>=7.4.2", 19 | "rich>=13.9.4", 20 | ] 21 | 22 | [project.urls] 23 | homepage = "https://github.com/FrostN0v0/nonebot-plugin-skland" 24 | repository = "https://github.com/FrostN0v0/nonebot-plugin-skland" 25 | 26 | [project.optional-dependencies] 27 | dev = [ 28 | "nonebot-adapter-discord>=0.1.8", 29 | "nonebot-adapter-onebot>=2.4.6", 30 | "nonebot-adapter-telegram>=0.1.0b20", 31 | "nonebot-plugin-orm[default]>=0.7.6", 32 | "nonebot2[fastapi,aiohttp]>=2.4.1", 33 | ] 34 | 35 | [build-system] 36 | requires = ["pdm-backend"] 37 | build-backend = "pdm.backend" 38 | 39 | [tool.nonebot] 40 | plugins = ["nonebot_plugin_skland"] 41 | adapters = [ 42 | { name = "OneBot V11", module_name = "nonebot.adapters.onebot.v11" }, 43 | { name = "Telegram", module_name = "nonebot.adapters.telegram" }, 44 | { name = "Discord", module_name = "nonebot.adapters.discord" }, 45 | ] 46 | 47 | 48 | [tool.isort] 49 | profile = "black" 50 | line_length = 120 51 | length_sort = true 52 | skip_gitignore = true 53 | force_sort_within_sections = true 54 | extra_standard_library = ["typing_extensions"] 55 | 56 | [tool.ruff] 57 | line-length = 120 58 | target-version = "py310" 59 | 60 | [tool.ruff.lint] 61 | select = ["E", "W", "F", "UP", "C", "T", "PYI", "PT", "Q"] 62 | ignore = ["E402", "C901"] 63 | 64 | [tool.pyright] 65 | pythonVersion = "3.10" 66 | pythonPlatform = "All" 67 | typeCheckingMode = "basic" 68 | 69 | [tool.pdm.scripts] 70 | build_css = "tailwindcss -i ./tailwind.css -o ./nonebot_plugin_skland/resources/templates/index.css -w" 71 | -------------------------------------------------------------------------------- /tailwind.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @layer utilities { 4 | .avater-shadow { 5 | box-shadow: 0px 0px 0px 3px #fbfbf3, 0px 4px 6px -1px rgba(6, 24, 44, 0.65), 6 | inset 0px 1px 0px 0px rgba(255, 255, 255, 0.08); 7 | } 8 | .rank-shadow { 9 | box-shadow: 0px 0px 0px 3px rgba(98, 93, 98, 0.6); 10 | } 11 | .reg-shadow { 12 | box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.3), 0px 14px 28px 0px rgba(0, 0, 0, 0.25), 13 | 0px 10px 10px 0px rgba(0, 0, 0, 0.22), inset 0px 0px 10px 0px rgba(128, 128, 128, 0.5); 14 | } 15 | .reg-shadow-blue { 16 | box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.3), 6px 2px 16px 0px rgba(136, 165, 191, 0.48), 17 | inset -6px -2px 16px 0px rgba(240, 240, 240, 0.15); 18 | } 19 | .card-shadow { 20 | box-shadow: 0px 54px 55px 0px rgba(0, 0, 0, 0.25), 0px -12px 30px 0px rgba(0, 0, 0, 0.12), 21 | 0px 4px 6px 0px rgba(0, 0, 0, 0.12), 0px 12px 13px 0px rgba(0, 0, 0, 0.17), 0px -3px 5px 0px rgba(0, 0, 0, 0.09); 22 | } 23 | .info-shadow { 24 | box-shadow: 0px 0px 10px 0px rgba(240, 240, 240, 0.3); 25 | } 26 | .info-content-shadow { 27 | box-shadow: 0px 2px 1px 0px rgba(0, 0, 0, 0.09), 0px 4px 2px 0px rgba(0, 0, 0, 0.09), 28 | 0px 8px 4px 0px rgba(0, 0, 0, 0.09), 0px 16px 8px 0px rgba(0, 0, 0, 0.09), 0px 32px 16px 0px rgba(0, 0, 0, 0.09); 29 | } 30 | .gradient-assist { 31 | background: linear-gradient(180deg, rgba(0, 0, 0, 0) 64%, rgba(0, 0, 0, 0.55) 75%, rgba(0, 0, 0, 0.8) 100%); 32 | } 33 | .dorm-filter { 34 | filter: brightness(0) saturate(100%) invert(92%) sepia(11%) saturate(1724%) hue-rotate(45deg) brightness(94%) 35 | contrast(88%); 36 | } 37 | .tired-filter { 38 | filter: brightness(0) saturate(100%) invert(46%) sepia(16%) saturate(2179%) hue-rotate(317deg) brightness(102%) 39 | contrast(70%); 40 | } 41 | .meeting-filter { 42 | filter: brightness(0) saturate(100%) invert(69%) sepia(28%) saturate(739%) hue-rotate(341deg) brightness(93%) 43 | contrast(87%); 44 | } 45 | .blue-bar { 46 | background: linear-gradient(270deg, rgba(26, 140, 216, 0.6) 0%, rgba(35, 186, 252, 0.6) 100%); 47 | box-shadow: 0px 0px 10px 0px rgba(35, 186, 252, 0.6); 48 | } 49 | .green-bar { 50 | background: linear-gradient(270deg, rgba(47, 168, 102, 0.6) 0%, rgba(61, 220, 132, 0.6) 100%); 51 | box-shadow: 0px 0px 10px 0px rgba(61, 220, 132, 0.6); 52 | } 53 | } 54 | 55 | @font-face { 56 | font-family: "Akrobat"; 57 | src: url("../fonts/Akrobat-Bold.otf") format("opentype"); 58 | } 59 | 60 | @font-face { 61 | font-family: "Bender"; 62 | src: url("../fonts/Bender.otf") format("opentype"); 63 | } 64 | --------------------------------------------------------------------------------