├── .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 |

4 |
5 |
6 |
7 |
50 |
51 | ## 📖 介绍
52 |
53 | 通过森空岛查询游戏数据
54 |
55 | > [!NOTE]
56 | > 本插件存在大量未经验证的数据结构~~以及 💩 山~~
57 | >
58 | > 如在使用过程中遇到问题,欢迎提 [issue](https://github.com/FrostN0v0/nonebot-plugin-skland/issues/new/choose) 帮助改进项目
59 |
60 |
61 |
62 |
63 | Star History
64 |
65 |
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 | 
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 |

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 |
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 |
189 |
190 |
{{ recruit_complete_time }}
191 |
192 |
193 |
195 |

196 |
197 | 公招刷新
198 |
199 | {{ building.hire.refreshCount }}/3
200 |
201 |
202 |
207 |
208 |
{{ building.hire.refresh_complete_time }}
209 |
210 |
211 |
213 |

214 |
215 | 剿灭奖励
216 |
217 | {{ campaign.reward.current }}/{{ campaign.reward.total }}
218 |
219 |
220 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------