started docs migration to docusaurus
96
.github/workflows/ci.yml
vendored
@ -1,96 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
id: setup-python
|
||||
with:
|
||||
python-version: "3.10"
|
||||
cache: pipenv
|
||||
|
||||
- name: Install pipenv
|
||||
run: pip install pipenv
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.setup-python.outputs.cache-hit != 'true'
|
||||
run: pipenv install --deploy --dev
|
||||
|
||||
- name: Build gettext strings
|
||||
run: pipenv run sphinx-build -b gettext source build/locale
|
||||
|
||||
- name: Push and Pull strings from the Crowdin
|
||||
uses: crowdin/github-action@v1
|
||||
if: github.ref == 'refs/heads/master'
|
||||
with:
|
||||
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
upload_sources: ${{ github.event_name == 'push' }}
|
||||
|
||||
download_translations: true
|
||||
push_translations: false
|
||||
download_language: en # Temporary limit only to English
|
||||
|
||||
- name: Fix permissions to the locale dir
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: sudo chown -R $USER:$USER locale
|
||||
|
||||
- name: Build docs
|
||||
run: pipenv run python build-multilang.py
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
retention-days: 7
|
||||
|
||||
- id: detect-diffs
|
||||
name: Detect diffs
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
git fetch origin
|
||||
gh_pages_exists=$(git ls-remote --heads origin gh-pages)
|
||||
if [[ -z ${gh_pages_exists} ]]; then
|
||||
echo "::warning::gh_pages branch doesn't exists"
|
||||
echo "should-continue=false" >> $GITHUB_OUTPUT
|
||||
exit
|
||||
fi
|
||||
|
||||
tmp_dir=$(mktemp -du)
|
||||
git worktree add "$tmp_dir" origin/gh-pages
|
||||
|
||||
diff_detected=false
|
||||
for l in locale/*; do
|
||||
l=$(basename $l)
|
||||
if ! diff -qbB -- "build/$l" "$tmp_dir/$l" > /dev/null; then
|
||||
diff_detected=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "should-continue=$diff_detected" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Deploy to the GitHub Pages
|
||||
uses: JamesIves/github-pages-deploy-action@v4
|
||||
if: |
|
||||
(github.event_name == 'push' && github.ref == 'refs/heads/master') ||
|
||||
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master') ||
|
||||
(github.event_name == 'schedule' && steps.detect-diffs.outputs.should-continue == 'true')
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: build
|
||||
single-commit: true
|
23
.gitignore
vendored
@ -1,11 +1,20 @@
|
||||
### .idea folder
|
||||
/.idea
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
### BUILD FOLDER
|
||||
# Production
|
||||
/build
|
||||
|
||||
### venv folder
|
||||
/venv
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
### The directory for the locales from the Crowdin
|
||||
/locale
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
181
Makefile
@ -1,181 +0,0 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " livehtml to run local webserver on 8000 port with authbuild feature"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
livehtml:
|
||||
sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Elybydocs.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Elybydocs.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Elybydocs"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Elybydocs"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
16
Pipfile
@ -1,16 +0,0 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
Sphinx = ">=3.0,<4.0"
|
||||
sphinx-rtd-theme = "~=0.5.1"
|
||||
|
||||
[dev-packages]
|
||||
lxml = ">=4.6.2,<5.0"
|
||||
sphinx-autobuild = ">=2020.09.01"
|
||||
sphinx-intl = ">=2.0,<3.0"
|
||||
|
||||
[requires]
|
||||
python_version = "3.10"
|
1048
Pipfile.lock
generated
59
README.md
@ -1,36 +1,41 @@
|
||||
# Документация Ely.by
|
||||
# Website
|
||||
|
||||
В этом репозитории находятся исходные файлы [сайта документации](http://docs.ely.by) проекта [Ely.by](http://ely.by)
|
||||
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
||||
|
||||
В этой документации вы найдёте информацию о публичных сервисах проекта Ely.by, ознакомившись с которой вы сможете
|
||||
самостоятельно реализовать свои программные продукты для совместной работы с сервисом Ely.by.
|
||||
|
||||
**Вы можете свободно улучшать и вносить предложения по изменениям в документацию.**
|
||||
|
||||
# Установка зависимостей
|
||||
|
||||
Для ведения документации мы используем [генератор документации Sphinx](http://sphinx-doc.org/) и тему
|
||||
[Read the Docs Sphinx Theme](https://github.com/snide/sphinx_rtd_theme) с небольшими доработками под стиль сайта.
|
||||
|
||||
Для компиляции вам понадобится установленный Python 2.7 (теоретически будет работать и на 3.4, но мы не проверяли) и
|
||||
менеджер зависимостей pip. Убедитесь, что они доступны глобально.
|
||||
|
||||
Для начала форкните и склонируйте этот репозиторий к себе на компьютер. За тем установите зависимости. Для этого,
|
||||
находясь в папке проекта, откройте консоль и выполните:
|
||||
### Installation
|
||||
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
$ yarn
|
||||
```
|
||||
|
||||
# Компиляция и разработка
|
||||
### Local Development
|
||||
|
||||
После установки необходимых зависимостей вам необходимо запустить непосредственно процесс компиляции. Вы можете вручную
|
||||
настроить свой веб-сервер для работы со скомпилированными шаблонами, скомпилировав их командой ```make html``` или же
|
||||
воспользуйтесь командой ```make livehtml``` для запуска локальное веб-сервера и автоматической рекомпиляции шаблонов,
|
||||
при их изменении. Сайт станет доступен по адресу ```127.0.0.1:8000```.
|
||||
```
|
||||
$ yarn start
|
||||
```
|
||||
|
||||
# Примечание
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
На операционных системах Windows Python не очень любит неанглийские символы в пути к файлам, так что при разработке под
|
||||
Windows убедитесь, что в пути нет русских, белорусских, китайских или иных других символов, отличных от англисйкого
|
||||
алфавита.
|
||||
### Build
|
||||
|
||||
```
|
||||
$ yarn build
|
||||
```
|
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
|
||||
### Deployment
|
||||
|
||||
Using SSH:
|
||||
|
||||
```
|
||||
$ USE_SSH=true yarn deploy
|
||||
```
|
||||
|
||||
Not using SSH:
|
||||
|
||||
```
|
||||
$ GIT_USER=<Your GitHub username> yarn deploy
|
||||
```
|
||||
|
||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||
|
@ -1,169 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from glob import glob
|
||||
from pathlib import Path
|
||||
from lxml import html, etree
|
||||
|
||||
base_lang: str = "ru"
|
||||
build_dir: str = "build"
|
||||
per_lang_static: list[str] = [
|
||||
r"documentation_options\.js",
|
||||
r"language_data\.js",
|
||||
r"^(?!basic)\w+-stemmer\.js",
|
||||
r"translations\.js",
|
||||
]
|
||||
|
||||
# Cleanup
|
||||
shutil.rmtree(build_dir, True)
|
||||
os.mkdir(build_dir)
|
||||
|
||||
# Build languages
|
||||
def sphinx_build(lang: str) -> None:
|
||||
subprocess.run([
|
||||
sys.executable, "-m", "sphinx",
|
||||
"-b", "html",
|
||||
"-D", f"language={lang}",
|
||||
"source",
|
||||
f"{build_dir}/{lang}",
|
||||
])
|
||||
|
||||
languages: list[str] = [base_lang]
|
||||
if os.path.isdir("locale"):
|
||||
languages += os.listdir("locale")
|
||||
|
||||
index_page_lang: str = "en" if "en" in languages else base_lang
|
||||
|
||||
for lang in languages:
|
||||
sphinx_build(lang)
|
||||
|
||||
# Extract common assets
|
||||
for i, lang in enumerate(languages):
|
||||
if i == 0:
|
||||
os.rename(f"{build_dir}/{lang}/_downloads", f"{build_dir}/_downloads")
|
||||
os.rename(f"{build_dir}/{lang}/_images", f"{build_dir}/_images")
|
||||
os.rename(f"{build_dir}/{lang}/CNAME", f"{build_dir}/CNAME")
|
||||
os.rename(f"{build_dir}/{lang}/.nojekyll", f"{build_dir}/.nojekyll")
|
||||
else:
|
||||
shutil.rmtree(f"{build_dir}/{lang}/_downloads")
|
||||
shutil.rmtree(f"{build_dir}/{lang}/_images")
|
||||
os.remove(f"{build_dir}/{lang}/CNAME")
|
||||
os.remove(f"{build_dir}/{lang}/.nojekyll")
|
||||
|
||||
for static_file in glob(f"{build_dir}/{lang}/_static/**/*", recursive=True):
|
||||
if not os.path.isfile(static_file):
|
||||
continue
|
||||
|
||||
relative_path = static_file.removeprefix(f"{build_dir}/{lang}/_static/")
|
||||
matched: bool = False
|
||||
for pattern in per_lang_static:
|
||||
if re.match(pattern, relative_path):
|
||||
matched = True
|
||||
break
|
||||
|
||||
# I have no idea how to make "continue 2" from the loop above so...
|
||||
if matched:
|
||||
continue
|
||||
|
||||
dir = os.path.dirname(relative_path)
|
||||
os.makedirs(f"{build_dir}/_static/{dir}", exist_ok=True)
|
||||
os.rename(static_file, f"{build_dir}/_static/{relative_path}")
|
||||
|
||||
shutil.rmtree(f"{build_dir}/{lang}/_sources")
|
||||
shutil.rmtree(f"{build_dir}/{lang}/.doctrees")
|
||||
os.remove(f"{build_dir}/{lang}/.buildinfo")
|
||||
os.remove(f"{build_dir}/{lang}/objects.inv")
|
||||
|
||||
# Convert all relative routes into absolute
|
||||
def make_links_absolute(file_path: str, link: str) -> str:
|
||||
# Skip local anchor links
|
||||
if link.startswith("#"):
|
||||
return link
|
||||
|
||||
# Skip external links (including links without protocol "//ely.by")
|
||||
if link.startswith("//") or re.match(r"^https?://", link):
|
||||
return link
|
||||
|
||||
if ".html" in link:
|
||||
working_directory = os.getcwd()
|
||||
os.chdir(Path(file_path).parent.absolute())
|
||||
resolved = str(Path(link).resolve())
|
||||
os.chdir(working_directory)
|
||||
|
||||
link = resolved.removeprefix(f"{working_directory}/{build_dir}")
|
||||
link = link.replace("\\", "/") # fix for the Windows
|
||||
|
||||
return link
|
||||
|
||||
# Other links are links to some static assets
|
||||
# There is no need to resolve relative links since _static is placed in the root directory,
|
||||
# so after removing all ../ parts we can safely append / to make the path absolute
|
||||
while link.startswith("../"):
|
||||
link = link.removeprefix("../")
|
||||
|
||||
no_static_link = link.removeprefix("_static/")
|
||||
for pattern in per_lang_static:
|
||||
if re.match(pattern, no_static_link):
|
||||
lang = re.match(fr"^{build_dir}/(\w+)/", file_path).group(1)
|
||||
|
||||
return f"/{lang}/{link}"
|
||||
|
||||
return f"/{link}"
|
||||
|
||||
for file in glob(f"{build_dir}/**/*.html", recursive=True):
|
||||
tree = html.parse(file) # type: etree._ElementTree
|
||||
root = tree.getroot() # type: html.HtmlElement
|
||||
|
||||
root.rewrite_links(lambda link: make_links_absolute(file, link))
|
||||
|
||||
tree.write(file, method="html", encoding="UTF-8")
|
||||
|
||||
# Create index.html for the site root
|
||||
shutil.copyfile(f"{build_dir}/{index_page_lang}/index.html", f"{build_dir}/index.html")
|
||||
index_file_tree = html.parse(f"{build_dir}/index.html") # type: etree._ElementTree
|
||||
index_file_root = index_file_tree.getroot() # type: html.HtmlElement
|
||||
index_file_last_toctree = index_file_root.find_class("toctree-wrapper")[0] # type: html.HtmlElement
|
||||
|
||||
for lang in languages:
|
||||
if lang == index_page_lang:
|
||||
continue
|
||||
|
||||
tree = html.parse(f"{build_dir}/{lang}/index.html") # type: etree._ElementTree
|
||||
root = tree.getroot() # type: html.HtmlElement
|
||||
|
||||
toctree = root.find_class("toctree-wrapper")[0] # type: html.HtmlElement
|
||||
index_file_last_toctree.addnext(toctree)
|
||||
|
||||
index_file_last_toctree = index_file_root.find_class("toctree-wrapper")[-1] # type: html.HtmlElement
|
||||
|
||||
index_file_tree.write(f"{build_dir}/index.html", method="html", encoding="UTF-8")
|
||||
|
||||
# Add cross-lang links to the sidebar
|
||||
sidebar_menus: dict[str, html.HtmlElement] = {}
|
||||
for lang in languages:
|
||||
tree = html.parse(f"{build_dir}/{lang}/index.html") # type: etree._ElementTree
|
||||
root = tree.getroot() # type: html.HtmlElement
|
||||
|
||||
sidebar_menus[lang] = root.find_class("wy-menu")[0]
|
||||
|
||||
for file in glob(f"{build_dir}/**/*.html") + [f"{build_dir}/index.html"]:
|
||||
result = re.match(fr"^{build_dir}/(\w+)/", file)
|
||||
lang: str = result.group(1) if result is not None else index_page_lang
|
||||
|
||||
tree = html.parse(file) # type: etree._ElementTree
|
||||
root = tree.getroot() # type: html.HtmlElement
|
||||
|
||||
sidebar_menu_last = root.find_class("wy-menu")[0] # type: html.HtmlElement
|
||||
for menuLang in sidebar_menus:
|
||||
if menuLang == lang:
|
||||
continue
|
||||
|
||||
sidebar_menu_last.addnext(sidebar_menus[menuLang])
|
||||
sidebar_menu_last = root.find_class("wy-menu")[-1] # type: html.HtmlElement
|
||||
|
||||
tree.write(file, method="html", encoding="UTF-8")
|
@ -1,8 +0,0 @@
|
||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
||||
base_url: https://ely.crowdin.com
|
||||
project_id : 2
|
||||
preserve_hierarchy: true
|
||||
|
||||
files:
|
||||
- source: build/locale/*.pot
|
||||
translation: locale/%two_letters_code%/LC_MESSAGES/%file_name%.po
|
107
docs/api.md
Normal file
@ -0,0 +1,107 @@
|
||||
# Ely.by API (симуляция Mojang API)
|
||||
|
||||
Здесь приведена информация об API, совместимом с функционалом [Mojang API](http://wiki.vg/Mojang_API) Обращаем ваше внимание на то, что это не полноценное API Ely.by, а только набор дополнительных запросов, реализованных на базе нашего [сервера авторизации](./minecraft-auth.md).
|
||||
|
||||
## Запросы
|
||||
:::note
|
||||
API не имеет ограничения на количество запросов. У нас есть просто настроенный fail2ban, который будет банить особо надоедливых клиентов. Такие дела.
|
||||
:::
|
||||
|
||||
В этой секции будут описаны запросы и их же варианты для Mojang API. Все запросы выполняются на базовый url `https://authserver.ely.by`.
|
||||
|
||||
### UUID по нику на время {#uuid-by-username}
|
||||
Данный запрос позволяет узнать UUID пользователя по его нику на указанный момент времени. Время задаётся через GET параметр `at` с unix timestamp.
|
||||
|
||||
> **GET /api/users/profiles/minecraft/\{username\}**
|
||||
>
|
||||
> Где `{username}` — искомый ник пользователя. Он может быть передан в любом регистре (В Mojang API только строгое совпадение).
|
||||
> Обратите так же внимание, что параметры legacy и demo никогда не будут возвращены, т.к. эти параметры не имеют в Ely альтернативы и специфичны только для сервисов Mojang.
|
||||
|
||||
В случае успешного запроса вы получите следующий ответ сервера:
|
||||
```json
|
||||
{
|
||||
"id": "ffc8fdc95824509e8a57c99b940fb996",
|
||||
"name": "ErickSkrauch"
|
||||
}
|
||||
```
|
||||
|
||||
В случае, если переданный ник не будет найден, вы получите ответ с `204` статусом и пустым телом.
|
||||
|
||||
### Никнейм по UUID + история изменений {#username-by-uuid}
|
||||
Данный запрос позволяет узнать все ники, использованные пользователем по его UUID.
|
||||
|
||||
> **GET /api/user/profiles/\{uuid\}/names**
|
||||
>
|
||||
> Где `{uuid}` — валидный UUID. Валидным будет считаться UUID, написанный через дефисы или без них. В случае передачи невалидной строки, будет возвращён [IllegalArgumentException](#illegal-argument-exception) с сообщением `"Invalid uuid format."`.
|
||||
|
||||
В случае успешного запроса вы получите следующий ответ сервера:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "Admin"
|
||||
},
|
||||
{
|
||||
"name": "ErickSkrauch",
|
||||
"changedToAt": 1440707723000
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
:::note
|
||||
Т.к. на Ely.by не реализован алгоритм запоминания момента смены ника, будет возвращаться только 1 элемент. Чуть позже мы добавим полноценную поддержку запоминания момента смены ника.
|
||||
:::
|
||||
|
||||
В случае, если переданный UUID не будет найден, вы получите ответ с `204` статусом и пустым телом.
|
||||
|
||||
### Список никнеймов в их UUID {#usernames-to-uuids}
|
||||
Этот запрос позволяет запросить список UUID пользователей по списку ников.
|
||||
|
||||
> **POST /api/profiles/minecraft**
|
||||
>
|
||||
> В теле запроса или POST параметрах необходимо передать валидный JSON массив искомых ников.
|
||||
>
|
||||
> В массиве должно быть не более 100 ников, в противном случае будет возвращён [IllegalArgumentException](#illegal-argument-exception) с сообщением `"Not more than that 100 profile names per call is allowed."`. В случае, если переданная строка окажется невалидным JSON объектом, будет возвращено это же исключение, только с текстом `"Passed array of profile names is an invalid JSON string."`.
|
||||
>
|
||||
> Пример тела запроса:
|
||||
> ```json
|
||||
> ["ErickSkrauch", "EnoTiK", "KmotherfuckerF"]
|
||||
> ```
|
||||
|
||||
В случае успешного запроса вы получите следующий ответ сервера:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "ffc8fdc95824509e8a57c99b940fb996",
|
||||
"name": "ErickSkrauch"
|
||||
},
|
||||
{
|
||||
"id": "b8407ae8218658ef96bb0cb3813acdfd",
|
||||
"name": "EnoTiK"
|
||||
},
|
||||
{
|
||||
"id": "39f42ba723de56d98867eabafc5e8e91",
|
||||
"name": "KmotherfuckerF"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Данные возвращаются в том же порядке, в каком и были запрошены.
|
||||
|
||||
В случае, если один из переданных никнеймов не найден в базе данных, для него не будет возвращено значения (он будет просто пропущен). Учитывайте эту ситуацию при парсинге ответа.
|
||||
|
||||
### Запрос информации о профиле по UUID {#profile-by-uuid}
|
||||
См. [запрос профиля для сервера авторизации](./minecraft-auth.mdx#profile-request).
|
||||
|
||||
## Возможные ошибки {#possible-errors}
|
||||
|
||||
### IllegalArgumentException {#illegal-argument-exception}
|
||||
Данная ошибка возникает при попытке передать на сервер данные в неправильном формате.
|
||||
|
||||
Пример подобной ошибки:
|
||||
```json
|
||||
{
|
||||
"error": "IllegalArgumentException",
|
||||
"errorMessage": "Invalid uuid format."
|
||||
}
|
||||
```
|
||||
`errorMessage` не всегда совпадает с таковым у Mojang, но в основном это касается только специфичных только для Ely ошибок. Оригинальные же запросы и ожидаемые от них ошибки повторяют тексты Mojang.
|
58
docs/authlib-injector.md
Normal file
@ -0,0 +1,58 @@
|
||||
# Authlib-Injector
|
||||
**authlib-injector** — это библиотека, позволяющая подменить адреса серверов авторизации и сессии в Authlib, не модифицируя непосредственно саму библиотеку. Выполнена как javaagent.
|
||||
|
||||
Данная библиотека значительно упрощает установку альтернативных сервисов авторизации в игровой клиент и сервер, поскольку она универсально применяет трансформацию в процессе работы программы.
|
||||
|
||||
Скачать последнюю версию можно со [страницы релизов на GitHub](https://github.com/yushijinhun/authlib-injector/releases/latest).
|
||||
|
||||
Здесь приведена документация к ключевым аспектам установки и использования библиотеки. Для более подробной информации обратитесь к [оригинальной документации на китайском языке](https://github.com/yushijinhun/authlib-injector/wiki).
|
||||
|
||||
## Установка в игровой клиент {#client}
|
||||
:::warning
|
||||
Обратите внимание, что этот раздел описывает установку **authlib-injector** в игру. Игровой лаунчер по-прежнему должен самостоятельно реализовать процесс авторизации, чтобы после передать `accessToken` в игру.
|
||||
:::
|
||||
Для применения библиотеки, необходимо указать её в качестве javaagent для игры. Сделать это можно, добавив в начало команды запуска игры строку `-javaagent:/путь/до/файла/authlib-injector.jar=ely.by`. В результате изменений строка запуска игры должна выглядеть следующим образом:
|
||||
```bash
|
||||
java -javaagent:/путь/к/файлу/authlib-injector.jar=ely.by -jar minecraft.jar
|
||||
```
|
||||
Если вы запускаете игру через лаунчер, то в его настройках необходимо найти поле для указания дополнительных аргументов JVM, куда необходимо в самое начало вставить строку, приведённую выше.
|
||||
|
||||

|
||||
|
||||
## Установка на сервер {#server}
|
||||
Также как и в случае с игровым клиентом, библиотеку необходимо указать в качестве javaagent. [Скачайте библиотеку](https://github.com/yushijinhun/authlib-injector/releases/latest) и поместите её в директорию с сервером. После этого добавьте вызов javaagent в команду запуска сервера:
|
||||
```bash
|
||||
# До
|
||||
java -jar minecraft_server.jar
|
||||
# После
|
||||
java -javaagent:authlib-injector.jar=ely.by -jar minecraft_server.jar
|
||||
```
|
||||
При запуске сервера вы должны увидеть сообщение об активации authlib-injector:
|
||||
|
||||

|
||||
|
||||
### BungeeCord {#bungeecord}
|
||||
authlib-injector должен быть установлен непосредственно на сам BungeeCord, а также **на все сервера** позади него. Обратите внимание на конфигурацию параметра online‑mode:
|
||||
* В конфигурации BungeeCord (`config.yml`) должно стоять значение `online_mode=true`.
|
||||
* В конфигурации всех серверов позади прокси (`server.properties`) должно быть указано значение `online-mode=false`.
|
||||
|
||||
Благодаря такой конфигурации установки, авторизация будет работать для всех входящих игроков, а на внутренних серверах будут корректно отображаться скины игроков.
|
||||
|
||||
### LaunchHelper {#launchhelper}
|
||||
Не все игровые хостинги позволяют напрямую модифицировать аргументы, с которыми запускается сервер. Чтобы обойти это ограничение, можно использовать специальный сервер, который запускает игровой сервер, подмешивая туда authlib-injector. Для установки следуйте инструкции:
|
||||
|
||||
1. Скачайте версию LaunchHelper для вашей операционной системы со [страницы загрузок](https://github.com/Codex-in-somnio/LaunchHelper/releases/latest).
|
||||
1. Загрузите скачанный файл и файл `authlib-injector.jar` в папку сервера на вашем хостинге.
|
||||
1. Там же создайте файл `launchhelper.properties` и поместите в него следующее содержимое:
|
||||
```properties
|
||||
javaAgentJarPath=authlib-injector.jar
|
||||
javaAgentOptions=ely.by
|
||||
execJarPath=minecraft_server.jar
|
||||
```
|
||||
Где `javaAgentJarPath` содержит путь до файла `authlib-injector.jar`, а `execJarPath` содержит имя файла сервера.
|
||||
1. В панели управления хостингом укажите `LaunchHelper.jar` в качестве запускаемого файла сервера.
|
||||
Если возможности указать исполнимый файл явно нет, то следует переименовать файл `LaunchHelper.jar` в соответствие с требованиями вашего хостинга (обычно, это `server.jar`). В этом случае у вас должна получиться следующая структура файлов:
|
||||
* `server.jar` - файл LaunchHelper.
|
||||
* `minecraft_server.jar` - предпочитаемое ядро сервера.
|
||||
* `authlib-injector.jar` - файл authlib-injector.
|
||||
* `launchhelper.properties` - файл конфигурации для LaunchHelper.
|
9
docs/intro.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
slug: /
|
||||
sidebar_position: 1
|
||||
---
|
||||
# Добро пожаловать в документацию Ely.by! {#welcome}
|
||||
|
||||
В этой документации вы найдете информацию о публичных сервисах проекта Ely.by, используя которую вы сможете интегрировать свои проекты с сервисами Ely.by
|
||||
|
||||
Вы всегда можете помочь нам улучшить эту документацию
|
317
docs/minecraft-auth.mdx
Normal file
@ -0,0 +1,317 @@
|
||||
# Авторизация для Minecraft {#minecraft}
|
||||
Здесь приведена информация по авторизации для лаунчеров и серверов Minecraft через сервис авторизации Ely.by.
|
||||
|
||||
Протокол авторизации реализован максимально похожим на [оригинальный протокол авторизации Mojang](http://wiki.vg/Authentication), но тем не менее эта документация описывает все доступные функции конкретно сервиса авторизации Ely.by.
|
||||
|
||||
## Общие положения {#definitions}
|
||||
* Все запросы должны выполняться на URL `https://authserver.ely.by`.
|
||||
* При успешном запросе, сервер вернёт ответ со статусом `200`. Любой другой код свидетельствует об ошибке.
|
||||
* Сервер всегда отвечает JSON данными, кроме случаев системных ошибок и ответов на legacy запросы. Учитывайте это для отображения пользователю правильного сообщения об ошибке.
|
||||
* В случае стандартной ошибки, вы получите следующие данные:
|
||||
```json
|
||||
{
|
||||
"error": "Краткое описание ошибки",
|
||||
"errorMessage": "Более длинное описание ошибки на английском языке, пригодное для отображения пользователю."
|
||||
}
|
||||
```
|
||||
|
||||
### Предусмотренные ошибки {#errors}
|
||||
В отличие от оригинального протокола, на Ely применяется меньший зоопарк ошибок:
|
||||
|
||||
#### IllegalArgumentException
|
||||
**Причина**: Вы передали неполный список данных для выполнения запроса.
|
||||
**Решение**: Внимательно перепроверьте что вы шлёте в запросе и что указано в документации.
|
||||
|
||||
#### ForbiddenOperationException
|
||||
**Причина**: Пользователь ввёл/разработчик передал неверные значения.
|
||||
**Решение**: Необходимо вывести пользователю уведомление о неправильно введённых данных.
|
||||
|
||||
{#not-found}Для индикации ошибки **Not Found** используется ответ с `404` статусом
|
||||
|
||||
## Авторизация в лаунчере {#launcher}
|
||||
В этом разделе описана авторизация для игрового лаунчера и описывает действия, необходимые для получения `accessToken` для игрового клиента Minecraft. В результате авторизации будет получен **JWT-токен** с [правами доступа](./oauth.md#available-scopes) `minecraft_server_session`.
|
||||
|
||||
:::warning
|
||||
Мы рекомендуем использовать [протокол авторизации OAuth 2.0](./oauth.md) с запросом [прав доступа](./oauth.md#available-scopes) `minecraft_server_session`, как **более безопасный и удобный** для пользователя метод.
|
||||
:::
|
||||
|
||||
### Авторизация пользователя {#auth-authenticate}
|
||||
Непосредственная авторизация пользователя, используя его логин (ник или E‑mail), пароль и токен двухфакторной аутентификации.
|
||||
|
||||
> **POST /auth/authenticate**
|
||||
>
|
||||
> **Параметры**:
|
||||
> * **username** (*string*) – Никнейм пользователя или его E‑mail (более предпочтительно).
|
||||
> * **password** (*string*) – Пароль пользователя или комбинация `пароль:токен`.
|
||||
> * **clientToken** (*string*) – Уникальный токен лаунчера пользователя.
|
||||
> * **requestUser** (*bool*) – Если поле передано как `true`, то в ответе сервера будет присутствовать поле `user`.
|
||||
|
||||
Система аккаунтов Ely.by поддерживает защиту пользователей посредством двухфакторной аутентификации. В оригинальном протоколе авторизации Mojang не предусмотрено возможности для передачи TOTP-токенов. Для решения этой проблемы и сохранения совместимости с реализацией сервера [Yggdrasil](https://minecraft.wiki/w/Yggdrasil), мы предлагаем передавать токен в поле `password` в формате `пароль:токен`.
|
||||
|
||||
К сожалению, не все пользователи осведомлены об этой возможности, поэтому будет лучше при получении ошибки о защищённости аккаунта пользователя двухфакторной аутентификацией явно запросить у него токен и склеить его программно.
|
||||
|
||||
Логика следующая:
|
||||
1. Если пользователь указал верные логин и пароль, но для его аккаунта включена двухфакторная аутентификация, вы получите ответ с `401` статусом и следующим содержимым:
|
||||
```json
|
||||
{
|
||||
"error": "ForbiddenOperationException",
|
||||
"errorMessage": "Account protected with two factor auth."
|
||||
}
|
||||
```
|
||||
2. При получении этой ошибки, необходимо запросить у пользователя ввод **TOTP‑токена**, после чего повторить запрос на авторизацию с теми же учётными данными, добавив к паролю постфикс в виде `:токен`, где `токен` — это значение, введённое пользователем.
|
||||
Если пароль пользователя был `password123`, а токен `123456`, то после склейки поле `password` примет значение `password123:123456`.
|
||||
3. Если в результате этих действий вы получите ответ с `401` статутом и `errorMessage` «Invalid credentials. Invalid email or password.», то это будет свидетельствовать о том, что переданный токен неверен и его нужно перезапросить у пользователя.
|
||||
|
||||
Если все данные будут переданы верно, вы получите следующий ответ:
|
||||
```json
|
||||
{
|
||||
"accessToken": "Длинная_строка_содержащая_access_token",
|
||||
"clientToken": "Переданный_в_запросе_client_token",
|
||||
"availableProfiles": [
|
||||
{
|
||||
"id": "UUID_пользователя_без_дефисов",
|
||||
"name": "Текущий_username_пользователя"
|
||||
}
|
||||
],
|
||||
"selectedProfile": {
|
||||
"id": "UUID_пользователя_без_дефисов",
|
||||
"name": "Текущий_username_пользователя"
|
||||
},
|
||||
"user": { /* Только если передан параметр requestUser */
|
||||
"id": "UUID_пользователя_без_дефисов",
|
||||
"username": "Текущий_username_пользователя",
|
||||
"properties": [
|
||||
{
|
||||
"name": "preferredLanguage",
|
||||
"value": "ru"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Обновление токена {#auth-refresh}
|
||||
Обновляет валидный `accessToken`. Этот запрос позволяет не хранить на клиенте его пароль, а оперировать только сохранённым значением `accessToken` для практически бесконечной возможности проходить авторизацию.
|
||||
|
||||
> **POST /auth/refresh**
|
||||
>
|
||||
> **Параметры**:
|
||||
> * **accessToken** (*string*) – Уникальный ключ, полученный после авторизации.
|
||||
> * **clientToken** (*string*) – Уникальный идентификатор клиента, относительно которого получен accessToken.
|
||||
> * **requestUser** (*bool*) – Если поле передано как `true`, то в ответе сервера будет присутствовать поле `user`.
|
||||
|
||||
::note
|
||||
В оригинальном протоколе так же передаётся значение `selectedProfile`, но в реализации Mojang он не влияет ни на что. Наша реализация сервера авторизации игнорирует этот параметр и опирается на значения `accessToken` и `clientToken`.
|
||||
:::
|
||||
|
||||
В случае получения какой-либо предусмотренной ошибки, следует заново запросить пароль пользователя и произвести обычную авторизацию.
|
||||
|
||||
Успешный ответ:
|
||||
```json
|
||||
{
|
||||
"accessToken": "Новая_длинная_строка_ содержащая_access_token",
|
||||
"clientToken": "Переданный_в_запросе_client_token",
|
||||
"selectedProfile": {
|
||||
"id": "UUID_пользователя_без_дефисов",
|
||||
"name": "Текущий_username_пользователя"
|
||||
},
|
||||
"user": { /* Только если передан параметр requestUser */
|
||||
"id": "UUID_пользователя_без_дефисов",
|
||||
"username": "Текущий_username_пользователя",
|
||||
"properties": [
|
||||
{
|
||||
"name": "preferredLanguage",
|
||||
"value": "ru"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Проверка токена {#auth-validate}
|
||||
Этот запрос позволяет проверить валиден ли указанный accessToken или нет. Этот запрос не обновляет токен и его время жизни, а только позволяет удостовериться, что он ещё действительный.
|
||||
|
||||
> **POST /auth/validate**
|
||||
>
|
||||
> **Параметры**:
|
||||
> * **accessToken** (*string*) – Токен доступа, полученный после авторизации
|
||||
|
||||
Успешным ответом будет являться пустое тело. При ошибке будет получен `400` или `401` статус. Пример ответа сервера при отправке истёкшего токена:
|
||||
```json
|
||||
{
|
||||
"error": "ForbiddenOperationException",
|
||||
"errorMessage": "Token expired."
|
||||
}
|
||||
```
|
||||
|
||||
### Инвалидация всех токенов пользователя {#auth-signout}
|
||||
Этот запрос позволяет выполнить инвалидацию всех выданных пользователю токенов.
|
||||
|
||||
> **POST /auth/signout**
|
||||
>
|
||||
> **Параметры**:
|
||||
> * **username** (*string*) – Никнейм пользователя или его E-mail (более предпочтительно).
|
||||
> * **password** (*string*) – Пароль пользователя.
|
||||
|
||||
Успешным ответом будет являться пустое тело. Ориентируйтесь на поле `error` в теле ответа.
|
||||
|
||||
### Инвалидация токена {#auth-invalidate}
|
||||
Запрос позволяет инвалидировать **accessToken**. В случае, если переданный токен не удастся найти в хранилище токенов, **ошибка не будет сгенерирована** и вы получите успешный ответ.
|
||||
|
||||
> **POST /auth/invalidate**
|
||||
>
|
||||
> **Параметры**:
|
||||
> * **accessToken** (*string*) – Уникальный ключ, полученный после авторизации.
|
||||
> * **clientToken** (*string*) – Уникальный идентификатор клиента, относительно которого получен accessToken.
|
||||
|
||||
Успешным ответом будет являться пустое тело. Ориентируйтесь на поле error в теле ответа.
|
||||
|
||||
## Авторизация на сервере {#server}
|
||||
Эти запросы выполняются непосредственно клиентом и сервером при помощи внутреннего кода или библиотеки **authlib** (начиная с версии 1.7.2). Они актуальны только в том случае, если вы уже произвели авторизацию и запустили игру с валидным **accessToken**. Вам остаётся только заменить пути внутри игры/библиотеки на приведённые ниже пути.
|
||||
|
||||
Поскольку непосредственно изменить что-либо в работе **authlib** или игры вы не можете, здесь не приводятся передаваемые значения и ответы сервера. При необходимости вы сможете найти эту информацию самостоятельно в интернете.
|
||||
|
||||
### Через authlib {#authlib}
|
||||
:::info
|
||||
Эта часть документации описывает запросы, выполняемые через **authlib** в версии игры **1.7.2+**. Для более старых версий смотрите [раздел ниже](#legacy).
|
||||
:::
|
||||
|
||||
Все запросы из этой категории выполняются на подуровень `/session`. Перед каждым из запросов указан тип отправляемого запроса.
|
||||
|
||||
> **POST /session/join**
|
||||
>
|
||||
> Запрос на этот URL производится клиентом в момент подключения к серверу с online-mode=true.
|
||||
|
||||
> **GET /session/hasJoined**
|
||||
>
|
||||
> Запрос на этот URL выполняет сервер с online-mode=true после того, как клиент, пытающийся к нему подключиться, успешно выполнит join запрос. Текстуры будут подписаны ключом Ely.by.
|
||||
|
||||
:::tip
|
||||
Ключ для проверки подписи можно получить через [сервер системы скинов](./skins-system.md#signature-verification-key-request).
|
||||
:::
|
||||
:::danger
|
||||
В редких случаях поле `signature` будет иметь значение `Cg==``. При таком значении поля подписи проводить её проверку **не нужно**, т.к. она всегда будет некорректной.
|
||||
:::
|
||||
|
||||
### Для старых версий {#legacy}
|
||||
:::info
|
||||
Эта часть документации описывает запросы, выполняемые более старыми версиями Minecraft, в которых не применялась библиотека **Authlib**. Это все версии **ниже 1.7.2**.
|
||||
:::
|
||||
|
||||
Все запросы из этой категории выполняются на подуровень `/session/legacy`. Перед каждым из запросов указан тип отправляемого запроса.
|
||||
|
||||
Принцип обработки этих запросов такой же, как и для **authlib**, отличие только во входных параметрах и возвращаемых значения.
|
||||
|
||||
> **GET /session/legacy/join**
|
||||
>
|
||||
> Запрос на этот URL производится клиентом в момент подключения к серверу с online-mode=true.
|
||||
|
||||
> **GET /session/legacy/hasJoined**
|
||||
>
|
||||
> Запрос на этот URL выполняет сервер с online-mode=true после того, как клиент, пытающийся к нему подключиться, успешно выполнит join запрос.
|
||||
|
||||
Важно не потерять GET параметр `?user=` в конце обоих запросов, чтобы получились следующие URL: `https://authserver.ely.by/session/legacy/hasJoined?user=`.
|
||||
|
||||
## Одиночная игра {#singleplayer}
|
||||
По сути, одиночная игра - это локальный сервер, созданный для одного игрока. По крайней мере это так, начиная с **версии 1.6**, в которой и был представлен механизм локальных серверов.
|
||||
|
||||
Тем не менее, описанный ниже запрос актуален только для **Minecraft 1.7.6+**, когда для загрузки скинов стала использоваться Authlib.
|
||||
|
||||
> {#profile-request}**GET /session/profile/\{uuid\}**
|
||||
>
|
||||
> Запрос на этот URL выполняется клиентом в одиночной игре на локальном сервере (созданном посредством самой игры). В URL передаётся UUID пользователя, с которым был запущен клиент, а в ответ получается информация о текстурах игрока в таком же формате, как и при `hasJoined` запросе.
|
||||
|
||||
## Готовые библиотеки authlib {#premade-authlib}
|
||||
:::tip
|
||||
Ely.by поддерживает библиотеку authlib-injector. Это наиболее простой и универсальный способ установки системы авторизации в игру и игровые сервера. За подробностями обратитесь в [соответствующий раздел документации](./authlib-injector.md).
|
||||
:::
|
||||
|
||||
Поскольку самостоятельная реализация связана с трудностями поиска исходников, подключения зависимостей и в конце-концов с процессом компиляции, на [странице загрузок нашей системы скинов](https://ely.by/load) вы можете загрузить уже готовые библиотеки со всеми необходимыми изменениями. Выберите в выпадающем списке необходимую версию и следуйте инструкции по установке, размещённой на той же странице ниже.
|
||||
|
||||
В более ранних версиях игры система скинов находилась внутри игрового клиента, так что библиотеки ниже обеспечивают лишь авторизацию:
|
||||
* Minecraft 1.7.5 - [authlib 1.3.1](/authlib/authlib-1.3.1.jar)
|
||||
* Minecraft 1.7.2 - [authlib 1.3](/authlib/authlib-1.3.jar)
|
||||
|
||||
Для установки вам необходимо заменить оригинальную библиотеку, располагающуюся по пути `<директория установки minecraft>/libraries/com/mojang/authlib/`. Убедитесь в том, что версии скачанного и заменяемого файлов совпадают.
|
||||
:::warning
|
||||
Многие лаунчеры и скрипты запуска серверов проверяют целостность файлов игры при запуске и могут не допускать подобную модификацию
|
||||
:::
|
||||
|
||||
### Установка authlib на сервер {#authlib-server}
|
||||
Сервер также использует **authlib** для выполнения авторизации игрока, поэтому соответствующие изменения должны быть также применены и к нему. Ниже приведены инструкции по установки **authlib** для различных реализаций сервера Minecraft.
|
||||
|
||||
#### Оригинальный сервер {#authlib-server-vanilla}
|
||||
С помощью архиватора откройте файл сервера `minecraft_server.ВЕРСИЯ.jar`. Таким же образом откройте архив с authlib для соответствующей версии сервера. Перед вами будет два окна: одно с файлами сервера, другое с файлами authlib. Вам необходимо «перетащить» из архива с authlib все файлы и папки, **за исключением директории META-INF**, и подтвердить замену.
|
||||
<figure>
|
||||

|
||||
<figcaption>Обратите внимание: «перетягивать» содержимое нужно ниже папок сервера (в область файлов .class).</figcaption>
|
||||
</figure>
|
||||
После этих действий вы можете закрыть оба окна и в файле `server.properties` установить значение `online-mode=true`.
|
||||
|
||||
#### Bukkit/Spigot {#authlib-server-spigot}
|
||||
Сперва выполните установку, как она описана для [оригинального сервера](#authlib-server-vanilla). Затем скачайте библиотеки [commons-io](https://repo1.maven.org/maven2/commons-io/commons-io/2.5/commons-io-2.5.jar) и [commons-lang3](https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar), после чего аналогичным с authlib образом последовательно переместите содержимое скачанных архивов в файлы сервера.
|
||||
|
||||
#### Forge/Sponge {#authlib-server-forge}
|
||||
Прежде чем производить установку, необходимо определить, какой именно файл подлежит модификации:
|
||||
* **>=1.16**: `libraries/net/minecraft/server/ВЕРСИЯ-ЦИФРЫ/server-ВЕРСИЯ-ЦИФРЫ-extra.jar`.
|
||||
* **1.13-1.15**: `libraries/net/minecraft/server/ВЕРСИЯ/server-ВЕРСИЯ-extra.jar`.
|
||||
* **<=1.12**: `minecraft_server.ВЕРСИЯ.jar`.
|
||||
|
||||
Когда необходимый файл найден, выполните для него установку authlib, аналогично [оригинальному серверу](#authlib-server-vanilla).
|
||||
|
||||
#### Paper (PaperSpigot и производные) {#authlib-server-paper}
|
||||
Установка производится по аналогии с [Bukkit/Spigot](#authlib-server-spigot) в файл `cache/patched-ВЕРСИЯ.jar`. После внесения изменений, запускать сервер нужно через jar-файл из директории cache, поскольку в противном случае Paper восстановит исходное состояние файла:
|
||||
```bash
|
||||
# До
|
||||
java -jar paper-ВЕРСИЯ-БИЛД.jar
|
||||
# После
|
||||
java -jar cache/patched-ВЕРСИЯ.jar
|
||||
```
|
||||
|
||||
#### BungeeCord (и производные) {#authlib-server-bungeecord}
|
||||
:::tip
|
||||
Вы можете воспользоваться библиотекой [authlib-injector](./authlib-injector.md) для установки системы авторизации без модификации внутренностей сервера.
|
||||
:::
|
||||
|
||||
Хотя BungeeCord и является проксирующим сервером, авторизацию игроков он выполняет самостоятельно. К сожалению, BungeeCord не опирается на использование Authlib, а реализует процесс авторизации самостоятельно, поэтому для установки системы авторизации Ely.by вам понадобится модифицировать скомпилированные `.class` файлы.
|
||||
|
||||
Для установки следуйте инструкции ниже:
|
||||
1. Скачайте программу InClassTranslator (прямых ссылок не даём, но его легко найти).
|
||||
2. С помощью архиватора откройте файл `BungeeCord.jar`.
|
||||
3. Перейдите по пути `net/md_5/bungee/connection` и найдите там файл `InitialHandler.class` (без каких-либо символов `$`).
|
||||
4. Распакуйте этот файл. В самом простом случае сделать это можно просто «вытянув» его из окна архиватора.
|
||||
5. Откройте распакованный файл в программе **InClassTranslator** и замените в нём строку `https://sessionserver.mojang.com/session/minecraft/hasJoined?username=` на `https://authserver.ely.by/session/hasJoined?username=`, как показано на рисунке ниже:
|
||||

|
||||
6. Сохраните изменения и перетащите измененный файл обратно в архив сервера. Подтвердите замену.
|
||||

|
||||
|
||||
После выполнения этих действий вы можете указать в файле конфигурации BungeeCord (`config.yml`) значение `online_mode=true`.
|
||||
|
||||
:::info
|
||||
Мы также рекомендуем выполнить установку Authlib на все сервера позади BungeeCord. Это может быть необходимо для плагинов, которые используют API Mojang. Инструкция по установке на конечные сервера [приведена выше](#authlib-server).
|
||||
|
||||
При этом все сервера должны иметь в своей конфигурации (`server.properties`) значение `online-mode=false`, поскольку пользователи уже авторизованы силами BungeeCord.
|
||||
:::
|
||||
|
||||
## Установка на версии ниже 1.7.2 {#install-legacy}
|
||||
Для более старых версий существует достаточно большое многообразие различных случаев, раскрыть которые в этой документации не представляется возможным. Вся установка заключается в замене определённых строк в определённых классах через **InClassTranslator**.
|
||||
|
||||
На форуме RuBukkit есть [отличный пост](http://www.rubukkit.org/threads/spisok-klassov-i-klientov-dlja-mcp.25108/#post-303710), в котором собрана вся нужна информация по именам классов на различных версиях Minecraft. Переписывать его сюда не имеет смысла, так что просто перейдите на его страницу и найдите нужную версию.
|
||||
|
||||
### Пример установки {#install-legacy-example}
|
||||
Предположим, что вы хотите установить авторизацию на сервер версии **1.5.2**.
|
||||
|
||||
Сначала вы переходите по вышеприведённой ссылке, выбираете нужную версию (1.5.2) и видите список классов:
|
||||
* `bdk.class` - путь до `joinserver`
|
||||
* `jg.class` - путь до `checkserver`
|
||||
|
||||
Затем вы должны взять .jar файл клиента и открыть его любым архиватором. После чего вам необходимо найти файл **bdk.class**. Для этого удобно воспользоваться поиском.
|
||||
После того, как вы нашли файл, его нужно извлечь из архива - просто перетащите его оттуда в удобную для вас дирикторию.
|
||||
Дальше запустите **InClassTranslator** и в нём откройте этот класс. Слева будет список найденных в файле строк, которые вы можете изменить. Нужно заменить только строку, отвечающую за запрос на подключение к серверу:
|
||||
|
||||

|
||||
После этого вам нужно положить изменённый .class обратно в .jar файл игры.
|
||||
|
||||
Ту же самую операцию вам необходимо провести и с сервером, только заменить ссылку на `hasJoined`.
|
||||
|
||||
После этих действий вам нужно в настройках включить online-mode=true и сервер станет пускать на себя только тех игроков, которые будут авторизованы через Ely.by.
|
99
docs/oauth.md
Normal file
@ -0,0 +1,99 @@
|
||||
# Авторизация по протоколу OAuth2
|
||||
На этой странице вы найдёте информацию о реализации авторизации по протоколу OAuth2 на вашем проекте через сервис Аккаунты Ely.by. Реализация этого протокола позволяет вашим пользователям производить авторизацию с использованием своего аккаунта Ely.by.
|
||||
|
||||
## Регистрация приложения {#app-registration}
|
||||
Для начала вам необходимо создать новое приложение. Выберите тип приложения Веб‑сайт. В качестве *адреса переадресации* можно указать только домен, но для повышения безопасности лучше использовать полный путь переадресации. Примеры допустимых адресов:
|
||||
* `https://example.com`
|
||||
* `https://example.com/oauth/ely`
|
||||
* `https://example.com/oauth.php?provider=ely`
|
||||
|
||||
После успешного добавления приложения вы попадёте на страницу со списком всех ваших приложений. Кликнув по названию приложения вы увидите его идентификатор `clientId` и секрет `clientSecret`. Они буду использоваться на следующих шагах.
|
||||
|
||||
## Инициализация авторизации {#auth-init}
|
||||
Для инициализации процесса авторизации вам необходимо перенаправить пользователя по следующему URL:
|
||||
```url
|
||||
https://account.ely.by/oauth2/v1?client_id=<clientId>&redirect_uri=<redirectUri>&response_type=code&scope=<scopesList>
|
||||
```
|
||||
|
||||
Сформировав ссылку, разместите её в вашем шаблоне:
|
||||
```html
|
||||
<a href="<ваша_ссылка>">Войти через Ely.by</a>
|
||||
```
|
||||
По нажатию на ссылку, пользователь попадёт на нашу страницу авторизации, откуда после он будет перенаправлен обратно по адресу, указанному в параметре redirect_uri.
|
||||
|
||||
Обратная переадресация выполняется в виде `<redirect_uri>?code=<код авторизации>&state=<state>` для успешной авторизации и `<redirect_uri>?error=<идентификатор ошибки>&error_message=<описание ошибки>` для неудачной.
|
||||
|
||||
Пример успешного и неудачного редиректов:
|
||||
```url
|
||||
https://example.com/oauth/ely.php?code=dkpEEVtXBdIcgdQWak4SOPEpTJIvYa8KIq5cW9GJ&state=ajckasdcjasndckbsadc
|
||||
https://example.com/oauth/ely.php?error=access_denied&error_message=The+resource+owner+or+authorization+server+denied+the+request.
|
||||
```
|
||||
|
||||
### Допустимые параметры запроса {#auth-init-params}
|
||||
| **Параметр** | **Пример значения** | **Описание** |
|
||||
|--------------------|-----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| *clientId* | `ely` | **Обязательное**. ClientId, полученный при регистрации. |
|
||||
| *redirect_uri* | `http://site.com/oauth.php` | **Обязательное**. Адрес обратной переадресации, совпадающий с адресом, указанным при регистрации приложения. |
|
||||
| *response_type* | `code` | **Обязательное**. Тип ответа. На данный момент поддерживается только `code`. |
|
||||
| *scope* | `account_info account_email` | **Обязательное**. Перечень разрешений, доступ к которым вы хотите получить, разделённые пробелом. Смотрите все доступные права в [разделе ниже](#available-scopes). |
|
||||
| *state* | `isfvubuysdboinsbdfvit` | Случайно сгенерированная строка. Используется для увеличения безопасности в качестве идентификатора сессии. Будет возвращена в неизменённом виде после завершения авторизации. |
|
||||
| *description* | `यो अनुप्रयोग विवरण` | Если ваше приложение доступно на нескольких языках, то используя это поле вы можете переопределить стандартное описание в соответствии с предпочтительным языком пользователя. |
|
||||
| *prompt* | `consent` или `select_account` | Принудительно отобразить запрос прав (`consent`) или принудительно запросить выбор аккаунта (`select_account`). |
|
||||
| *login_hint* | `erickskrauch` или `erickskrauch@ely.by` | Если у пользователя есть несколько аккаунтов, то указав в этом параметре username или E-mail пользователя, вы автоматически выберете аккаунт за него. Это полезно в случае повторного входа, когда токен истёк. |
|
||||
|
||||
### Перечень доступных scopes {#oauth-scopes}
|
||||
|
||||
| **Scope** | **Описание** |
|
||||
|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **account_info** | Получение информации о пользователе. |
|
||||
| **account_email** | В ответе на запрос информации о пользователе будет также присутствовать его email. |
|
||||
| **offline_access** | Вместе с `access_token` вы также получите и `refresh_token`. Смотрите подробнее в [соответствующем разделе](#refresh-token-grant). |
|
||||
| **minecraft_server_session** | `access_token` можно будет использовать в качестве сессии для Minecraft. |
|
||||
|
||||
## Обмен кода на ключ {#access_key}
|
||||
После получения кода авторизации (`auth_code`), вам необходимо обменять его на ключ авторизации (`access_key`). Для этого необходимо выполнить POST запрос на URL:
|
||||
```url
|
||||
https://account.ely.by/api/oauth2/v1/token
|
||||
```
|
||||
И передать туда следующие параметры:
|
||||
* **client_id**: ClientID, полученный при регистрации приложения.
|
||||
* **client_secret**: ClientSecret, полученный при регистрации приложения.
|
||||
* **redirect_uri**: Точный адрес, использованный для переадресации пользователя.
|
||||
* **grant_type**: В данном случае указывается authorization_code.
|
||||
* **code**: Код авторизации, полученный в GET-параметрах после успешной переадресации.
|
||||
|
||||
```php title="Пример реализации обмена на PHP"
|
||||
<?php
|
||||
// В этой переменной будут храниться ваши параметры OAuth2.
|
||||
$oauthParams = [
|
||||
'client_id' => 'ely', // Ваш ClientId, полученный при регистрации.
|
||||
'client_secret' => 'Pk4uCtZw5WVlSUpvteJuTZkVqHXZ6aNtTaLPXa7X', // Ваш ClientSecret, полученный при регистрации
|
||||
'redirect_uri' => 'http://someresource.by/oauth/some.php', // Адрес, на который вы ожидаете получить пользователя обратно (текущий url).
|
||||
'grant_type' => 'authorization_code',
|
||||
];
|
||||
|
||||
// Если возникла ошибка, то прерываем выполнение скрипта.
|
||||
if (isset($_GET['error'])) {
|
||||
echo $_GET['error_message'];
|
||||
return;
|
||||
}
|
||||
|
||||
// Выполняем код ниже только если пришёл код авторизации.
|
||||
if (!is_null($_GET['code'])) {
|
||||
$oauthParams['code'] = $_GET['code'];
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, 'https://account.ely.by/api/oauth2/v1/token');
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_POST, true);
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($oauthParams));
|
||||
$out = json_decode(curl_exec($curl), true);
|
||||
curl_close($curl);
|
||||
}
|
||||
```
|
||||
Пояснение к коду:
|
||||
Сначала мы объявляем переменную `$oauthParams`, в которую заносим значения, полученные после регистрации приложения.
|
||||
Затем проверяем, не возникла-ли ошибка. В этом случае сразу же прерываем выполнение.
|
||||
Формируем POST запрос к форме обмена `code` на `access_token`, передавая необходимые поля.
|
||||
Выполняем запрос, получаем ответ, переводим его из JSON в ассоциативный массив.
|
||||
|
145
docusaurus.config.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import {themes as prismThemes} from 'prism-react-renderer';
|
||||
import type {Config} from '@docusaurus/types';
|
||||
import type * as Preset from '@docusaurus/preset-classic';
|
||||
|
||||
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
||||
|
||||
const config: Config = {
|
||||
title: 'Документация Ely.by',
|
||||
tagline: 'Документация публичных проектов Ely.by',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
// Set the production url of your site here
|
||||
url: 'https://docs.ely.by',
|
||||
// Set the /<baseUrl>/ pathname under which your site is served
|
||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||
baseUrl: '/',
|
||||
|
||||
// GitHub pages deployment config.
|
||||
// If you aren't using GitHub pages, you don't need these.
|
||||
organizationName: 'elyby', // Usually your GitHub org/user name.
|
||||
projectName: 'todo', // Usually your repo name.
|
||||
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'throw',
|
||||
|
||||
// Even if you don't use internationalization, you can use this field to set
|
||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
||||
// may want to replace "en" with "zh-Hans".
|
||||
i18n: {
|
||||
defaultLocale: 'ru',
|
||||
locales: ['en', 'ru'],
|
||||
},
|
||||
|
||||
presets: [
|
||||
[
|
||||
'@docusaurus/preset-classic',
|
||||
{
|
||||
docs: {
|
||||
routeBasePath: '/',
|
||||
// Please change this to your repo.
|
||||
// Remove this to remove the "edit this page" links.
|
||||
// editUrl:
|
||||
// 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
|
||||
},
|
||||
theme: {
|
||||
customCss: './src/css/custom.css',
|
||||
},
|
||||
blog: false,
|
||||
} satisfies Preset.Options,
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
// Replace with your project's social card
|
||||
image: 'img/docusaurus-social-card.jpg',
|
||||
navbar: {
|
||||
title: 'Документация Ely.by',
|
||||
logo: {
|
||||
alt: 'Логотип Ely.by',
|
||||
src: 'img/logo.svg',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'localeDropdown',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: 'dark',
|
||||
links: [
|
||||
{
|
||||
title: 'Проекты',
|
||||
items: [
|
||||
{
|
||||
label: 'Система скинов Ely.by',
|
||||
to: 'https://ely.by',
|
||||
},
|
||||
{
|
||||
label: 'Аккаунты Ely.by',
|
||||
to: 'https://account.ely.by',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Сообщество',
|
||||
items: [
|
||||
{
|
||||
label: 'X (Twitter)',
|
||||
href: 'https://x.com/erickskrauch',
|
||||
},
|
||||
{
|
||||
label: 'YouTube',
|
||||
href: 'https://www.youtube.com/c/ElyByOfficial',
|
||||
},
|
||||
{
|
||||
label: 'VK',
|
||||
href: 'https://vk.com/elyby',
|
||||
},
|
||||
{
|
||||
label: 'Discord',
|
||||
// todo
|
||||
href: 'https://ely.by',
|
||||
},
|
||||
{
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/elyby',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Ely.by. Built with Docusaurus.`,
|
||||
},
|
||||
prism: {
|
||||
theme: prismThemes.github,
|
||||
darkTheme: prismThemes.dracula,
|
||||
additionalLanguages: [
|
||||
'bash',
|
||||
'properties',
|
||||
'json',
|
||||
'batch',
|
||||
'url',
|
||||
'html',
|
||||
'php',
|
||||
],
|
||||
},
|
||||
zoom: {
|
||||
selector: '.markdown :not(em) > img',
|
||||
config: {
|
||||
container: {
|
||||
top: 72,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 16,
|
||||
},
|
||||
}
|
||||
},
|
||||
} satisfies Preset.ThemeConfig,
|
||||
|
||||
plugins: [
|
||||
'docusaurus-plugin-image-zoom'
|
||||
]
|
||||
};
|
||||
|
||||
export default config;
|
313
i18n/en/code.json
Normal file
@ -0,0 +1,313 @@
|
||||
{
|
||||
"theme.ErrorPageContent.title": {
|
||||
"message": "This page crashed.",
|
||||
"description": "The title of the fallback page when the page crashed"
|
||||
},
|
||||
"theme.blog.archive.title": {
|
||||
"message": "Archive",
|
||||
"description": "The page & hero title of the blog archive page"
|
||||
},
|
||||
"theme.blog.archive.description": {
|
||||
"message": "Archive",
|
||||
"description": "The page & hero description of the blog archive page"
|
||||
},
|
||||
"theme.BackToTopButton.buttonAriaLabel": {
|
||||
"message": "Scroll back to top",
|
||||
"description": "The ARIA label for the back to top button"
|
||||
},
|
||||
"theme.blog.paginator.navAriaLabel": {
|
||||
"message": "Blog list page navigation",
|
||||
"description": "The ARIA label for the blog pagination"
|
||||
},
|
||||
"theme.blog.paginator.newerEntries": {
|
||||
"message": "Newer entries",
|
||||
"description": "The label used to navigate to the newer blog posts page (previous page)"
|
||||
},
|
||||
"theme.blog.paginator.olderEntries": {
|
||||
"message": "Older entries",
|
||||
"description": "The label used to navigate to the older blog posts page (next page)"
|
||||
},
|
||||
"theme.blog.post.paginator.navAriaLabel": {
|
||||
"message": "Blog post page navigation",
|
||||
"description": "The ARIA label for the blog posts pagination"
|
||||
},
|
||||
"theme.blog.post.paginator.newerPost": {
|
||||
"message": "Newer post",
|
||||
"description": "The blog post button label to navigate to the newer/previous post"
|
||||
},
|
||||
"theme.blog.post.paginator.olderPost": {
|
||||
"message": "Older post",
|
||||
"description": "The blog post button label to navigate to the older/next post"
|
||||
},
|
||||
"theme.tags.tagsPageLink": {
|
||||
"message": "View all tags",
|
||||
"description": "The label of the link targeting the tag list page"
|
||||
},
|
||||
"theme.colorToggle.ariaLabel": {
|
||||
"message": "Switch between dark and light mode (currently {mode})",
|
||||
"description": "The ARIA label for the navbar color mode toggle"
|
||||
},
|
||||
"theme.colorToggle.ariaLabel.mode.dark": {
|
||||
"message": "dark mode",
|
||||
"description": "The name for the dark color mode"
|
||||
},
|
||||
"theme.colorToggle.ariaLabel.mode.light": {
|
||||
"message": "light mode",
|
||||
"description": "The name for the light color mode"
|
||||
},
|
||||
"theme.docs.breadcrumbs.navAriaLabel": {
|
||||
"message": "Breadcrumbs",
|
||||
"description": "The ARIA label for the breadcrumbs"
|
||||
},
|
||||
"theme.docs.DocCard.categoryDescription.plurals": {
|
||||
"message": "1 item|{count} items",
|
||||
"description": "The default description for a category card in the generated index about how many items this category includes"
|
||||
},
|
||||
"theme.docs.paginator.navAriaLabel": {
|
||||
"message": "Docs pages",
|
||||
"description": "The ARIA label for the docs pagination"
|
||||
},
|
||||
"theme.docs.paginator.previous": {
|
||||
"message": "Previous",
|
||||
"description": "The label used to navigate to the previous doc"
|
||||
},
|
||||
"theme.docs.paginator.next": {
|
||||
"message": "Next",
|
||||
"description": "The label used to navigate to the next doc"
|
||||
},
|
||||
"theme.docs.tagDocListPageTitle.nDocsTagged": {
|
||||
"message": "One doc tagged|{count} docs tagged",
|
||||
"description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
|
||||
},
|
||||
"theme.docs.tagDocListPageTitle": {
|
||||
"message": "{nDocsTagged} with \"{tagName}\"",
|
||||
"description": "The title of the page for a docs tag"
|
||||
},
|
||||
"theme.docs.versionBadge.label": {
|
||||
"message": "Version: {versionLabel}"
|
||||
},
|
||||
"theme.docs.versions.unreleasedVersionLabel": {
|
||||
"message": "This is unreleased documentation for {siteTitle} {versionLabel} version.",
|
||||
"description": "The label used to tell the user that he's browsing an unreleased doc version"
|
||||
},
|
||||
"theme.docs.versions.unmaintainedVersionLabel": {
|
||||
"message": "This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.",
|
||||
"description": "The label used to tell the user that he's browsing an unmaintained doc version"
|
||||
},
|
||||
"theme.docs.versions.latestVersionSuggestionLabel": {
|
||||
"message": "For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).",
|
||||
"description": "The label used to tell the user to check the latest version"
|
||||
},
|
||||
"theme.docs.versions.latestVersionLinkLabel": {
|
||||
"message": "latest version",
|
||||
"description": "The label used for the latest version suggestion link label"
|
||||
},
|
||||
"theme.common.editThisPage": {
|
||||
"message": "Edit this page",
|
||||
"description": "The link label to edit the current page"
|
||||
},
|
||||
"theme.common.headingLinkTitle": {
|
||||
"message": "Direct link to {heading}",
|
||||
"description": "Title for link to heading"
|
||||
},
|
||||
"theme.lastUpdated.atDate": {
|
||||
"message": " on {date}",
|
||||
"description": "The words used to describe on which date a page has been last updated"
|
||||
},
|
||||
"theme.lastUpdated.byUser": {
|
||||
"message": " by {user}",
|
||||
"description": "The words used to describe by who the page has been last updated"
|
||||
},
|
||||
"theme.lastUpdated.lastUpdatedAtBy": {
|
||||
"message": "Last updated{atDate}{byUser}",
|
||||
"description": "The sentence used to display when a page has been last updated, and by who"
|
||||
},
|
||||
"theme.navbar.mobileVersionsDropdown.label": {
|
||||
"message": "Versions",
|
||||
"description": "The label for the navbar versions dropdown on mobile view"
|
||||
},
|
||||
"theme.NotFound.title": {
|
||||
"message": "Page Not Found",
|
||||
"description": "The title of the 404 page"
|
||||
},
|
||||
"theme.tags.tagsListLabel": {
|
||||
"message": "Tags:",
|
||||
"description": "The label alongside a tag list"
|
||||
},
|
||||
"theme.admonition.caution": {
|
||||
"message": "caution",
|
||||
"description": "The default label used for the Caution admonition (:::caution)"
|
||||
},
|
||||
"theme.admonition.danger": {
|
||||
"message": "danger",
|
||||
"description": "The default label used for the Danger admonition (:::danger)"
|
||||
},
|
||||
"theme.admonition.info": {
|
||||
"message": "info",
|
||||
"description": "The default label used for the Info admonition (:::info)"
|
||||
},
|
||||
"theme.admonition.note": {
|
||||
"message": "note",
|
||||
"description": "The default label used for the Note admonition (:::note)"
|
||||
},
|
||||
"theme.admonition.tip": {
|
||||
"message": "tip",
|
||||
"description": "The default label used for the Tip admonition (:::tip)"
|
||||
},
|
||||
"theme.admonition.warning": {
|
||||
"message": "warning",
|
||||
"description": "The default label used for the Warning admonition (:::warning)"
|
||||
},
|
||||
"theme.AnnouncementBar.closeButtonAriaLabel": {
|
||||
"message": "Close",
|
||||
"description": "The ARIA label for close button of announcement bar"
|
||||
},
|
||||
"theme.blog.sidebar.navAriaLabel": {
|
||||
"message": "Blog recent posts navigation",
|
||||
"description": "The ARIA label for recent posts in the blog sidebar"
|
||||
},
|
||||
"theme.CodeBlock.copied": {
|
||||
"message": "Copied",
|
||||
"description": "The copied button label on code blocks"
|
||||
},
|
||||
"theme.CodeBlock.copyButtonAriaLabel": {
|
||||
"message": "Copy code to clipboard",
|
||||
"description": "The ARIA label for copy code blocks button"
|
||||
},
|
||||
"theme.CodeBlock.copy": {
|
||||
"message": "Copy",
|
||||
"description": "The copy button label on code blocks"
|
||||
},
|
||||
"theme.CodeBlock.wordWrapToggle": {
|
||||
"message": "Toggle word wrap",
|
||||
"description": "The title attribute for toggle word wrapping button of code block lines"
|
||||
},
|
||||
"theme.DocSidebarItem.expandCategoryAriaLabel": {
|
||||
"message": "Expand sidebar category '{label}'",
|
||||
"description": "The ARIA label to expand the sidebar category"
|
||||
},
|
||||
"theme.DocSidebarItem.collapseCategoryAriaLabel": {
|
||||
"message": "Collapse sidebar category '{label}'",
|
||||
"description": "The ARIA label to collapse the sidebar category"
|
||||
},
|
||||
"theme.NavBar.navAriaLabel": {
|
||||
"message": "Main",
|
||||
"description": "The ARIA label for the main navigation"
|
||||
},
|
||||
"theme.navbar.mobileLanguageDropdown.label": {
|
||||
"message": "Languages",
|
||||
"description": "The label for the mobile language switcher dropdown"
|
||||
},
|
||||
"theme.NotFound.p1": {
|
||||
"message": "We could not find what you were looking for.",
|
||||
"description": "The first paragraph of the 404 page"
|
||||
},
|
||||
"theme.NotFound.p2": {
|
||||
"message": "Please contact the owner of the site that linked you to the original URL and let them know their link is broken.",
|
||||
"description": "The 2nd paragraph of the 404 page"
|
||||
},
|
||||
"theme.TOCCollapsible.toggleButtonLabel": {
|
||||
"message": "On this page",
|
||||
"description": "The label used by the button on the collapsible TOC component"
|
||||
},
|
||||
"theme.blog.post.readMore": {
|
||||
"message": "Read more",
|
||||
"description": "The label used in blog post item excerpts to link to full blog posts"
|
||||
},
|
||||
"theme.blog.post.readMoreLabel": {
|
||||
"message": "Read more about {title}",
|
||||
"description": "The ARIA label for the link to full blog posts from excerpts"
|
||||
},
|
||||
"theme.blog.post.readingTime.plurals": {
|
||||
"message": "One min read|{readingTime} min read",
|
||||
"description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
|
||||
},
|
||||
"theme.docs.breadcrumbs.home": {
|
||||
"message": "Home page",
|
||||
"description": "The ARIA label for the home page in the breadcrumbs"
|
||||
},
|
||||
"theme.docs.sidebar.collapseButtonTitle": {
|
||||
"message": "Collapse sidebar",
|
||||
"description": "The title attribute for collapse button of doc sidebar"
|
||||
},
|
||||
"theme.docs.sidebar.collapseButtonAriaLabel": {
|
||||
"message": "Collapse sidebar",
|
||||
"description": "The title attribute for collapse button of doc sidebar"
|
||||
},
|
||||
"theme.docs.sidebar.navAriaLabel": {
|
||||
"message": "Docs sidebar",
|
||||
"description": "The ARIA label for the sidebar navigation"
|
||||
},
|
||||
"theme.docs.sidebar.closeSidebarButtonAriaLabel": {
|
||||
"message": "Close navigation bar",
|
||||
"description": "The ARIA label for close button of mobile sidebar"
|
||||
},
|
||||
"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": {
|
||||
"message": "← Back to main menu",
|
||||
"description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)"
|
||||
},
|
||||
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": {
|
||||
"message": "Toggle navigation bar",
|
||||
"description": "The ARIA label for hamburger menu button of mobile navigation"
|
||||
},
|
||||
"theme.docs.sidebar.expandButtonTitle": {
|
||||
"message": "Expand sidebar",
|
||||
"description": "The ARIA label and title attribute for expand button of doc sidebar"
|
||||
},
|
||||
"theme.docs.sidebar.expandButtonAriaLabel": {
|
||||
"message": "Expand sidebar",
|
||||
"description": "The ARIA label and title attribute for expand button of doc sidebar"
|
||||
},
|
||||
"theme.blog.post.plurals": {
|
||||
"message": "One post|{count} posts",
|
||||
"description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
|
||||
},
|
||||
"theme.blog.tagTitle": {
|
||||
"message": "{nPosts} tagged with \"{tagName}\"",
|
||||
"description": "The title of the page for a blog tag"
|
||||
},
|
||||
"theme.blog.author.pageTitle": {
|
||||
"message": "{authorName} - {nPosts}",
|
||||
"description": "The title of the page for a blog author"
|
||||
},
|
||||
"theme.blog.authorsList.pageTitle": {
|
||||
"message": "Authors",
|
||||
"description": "The title of the authors page"
|
||||
},
|
||||
"theme.blog.authorsList.viewAll": {
|
||||
"message": "View all authors",
|
||||
"description": "The label of the link targeting the blog authors page"
|
||||
},
|
||||
"theme.blog.author.noPosts": {
|
||||
"message": "This author has not written any posts yet.",
|
||||
"description": "The text for authors with 0 blog post"
|
||||
},
|
||||
"theme.contentVisibility.unlistedBanner.title": {
|
||||
"message": "Unlisted page",
|
||||
"description": "The unlisted content banner title"
|
||||
},
|
||||
"theme.contentVisibility.unlistedBanner.message": {
|
||||
"message": "This page is unlisted. Search engines will not index it, and only users having a direct link can access it.",
|
||||
"description": "The unlisted content banner message"
|
||||
},
|
||||
"theme.contentVisibility.draftBanner.title": {
|
||||
"message": "Draft page",
|
||||
"description": "The draft content banner title"
|
||||
},
|
||||
"theme.contentVisibility.draftBanner.message": {
|
||||
"message": "This page is a draft. It will only be visible in dev and be excluded from the production build.",
|
||||
"description": "The draft content banner message"
|
||||
},
|
||||
"theme.ErrorPageContent.tryAgain": {
|
||||
"message": "Try again",
|
||||
"description": "The label of the button to try again rendering when the React error boundary captures an error"
|
||||
},
|
||||
"theme.common.skipToMainContent": {
|
||||
"message": "Skip to main content",
|
||||
"description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation"
|
||||
},
|
||||
"theme.tags.tagsPageTitle": {
|
||||
"message": "Tags",
|
||||
"description": "The title of the tag list page"
|
||||
}
|
||||
}
|
6
i18n/en/docusaurus-plugin-content-docs/current.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"version.label": {
|
||||
"message": "Next",
|
||||
"description": "The label for version current"
|
||||
}
|
||||
}
|
107
i18n/en/docusaurus-plugin-content-docs/current/api.md
Normal file
@ -0,0 +1,107 @@
|
||||
# Ely.by API (Mojang API simulation)
|
||||
|
||||
This article contains information about the API compatible with the [Mojang API](http://wiki.vg/Mojang_API) functionality. Please note that this is not a full-fledged API of Ely.by, but only a set of additional requests implemented based on our `[authorization server](./minecraft-auth.md)`.
|
||||
|
||||
## Requests
|
||||
:::note
|
||||
The API has no rate limit. We just have a configured fail2ban that will ban especially annoying clients. That’s the way.
|
||||
:::
|
||||
|
||||
This section will describe the requests and their corresponding variants for Mojang API. Base URL for requests is `https://authserver.ely.by`.
|
||||
|
||||
### UUID by username at a time {#uuid-by-username}
|
||||
This request allows you to find out the UUID of a user by their username at a specified point in time. The time is specified via GET parameter at as a Unix timestamp.
|
||||
|
||||
> **GET /api/users/profiles/minecraft/\{username\}**
|
||||
>
|
||||
> Where `{username}` is the searched username. It can be passed in any case (in the Mojang API, only strict match).
|
||||
> Note that the legacy and demo params will never be returned, as these parameters have no alternative in Ely and are specific only for Mojang services.
|
||||
|
||||
In case of a successful request you will receive the following response:
|
||||
```json
|
||||
{
|
||||
"id": "ffc8fdc95824509e8a57c99b940fb996",
|
||||
"name": "ErickSkrauch"
|
||||
}
|
||||
```
|
||||
|
||||
When the passed username isn’t found, you will receive a response with `204` status code and an empty body.
|
||||
|
||||
### Username by UUID + history of changes {#username-by-uuid}
|
||||
This request allows you to find out all usernames used by a user by their UUID.
|
||||
|
||||
> **GET /api/user/profiles/\{uuid\}/names**
|
||||
>
|
||||
> Where `{uuid}` is a valid UUID. UUID might be written with or without hyphens. If an invalid string is passed, [IllegalArgumentException](#illegal-argument-exception) will be returned with the message `"Invalid uuid format."`.
|
||||
|
||||
In case of a successful request you will receive the following response:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "Admin"
|
||||
},
|
||||
{
|
||||
"name": "ErickSkrauch",
|
||||
"changedToAt": 1440707723000
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
:::note
|
||||
Since Ely.by doesn’t store the moment of username change only 1 username will always be returned. We may add full support for remembering when a username was changed in the future.
|
||||
:::
|
||||
|
||||
When the passed UUID isn’t found, you will receive a response with `204` status code and an empty body.
|
||||
|
||||
### Usernames list to their UUIDs {#usernames-to-uuids}
|
||||
This request allows you to query a list of users’ UUIDs by their usernames.
|
||||
|
||||
> **POST /api/profiles/minecraft**
|
||||
>
|
||||
> In the request body or POST parameters you need to pass a valid JSON array of the searched usernames.
|
||||
>
|
||||
> The array must contain no more than 100 usernames, otherwise [IllegalArgumentException](#illegal-argument-exception) will be returned with the message `"Not more than that 100 profile names per call is allowed."`. In case the passed string is an invalid JSON object, the same exception will be returned, but with the text `"Passed array of profile names is an invalid JSON string."`.
|
||||
>
|
||||
> Example of a request body:
|
||||
> ```json
|
||||
> ["ErickSkrauch", "EnoTiK", "KmotherfuckerF"]
|
||||
> ```
|
||||
|
||||
In case of a successful request you will receive the following response:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "ffc8fdc95824509e8a57c99b940fb996",
|
||||
"name": "ErickSkrauch"
|
||||
},
|
||||
{
|
||||
"id": "b8407ae8218658ef96bb0cb3813acdfd",
|
||||
"name": "EnoTiK"
|
||||
},
|
||||
{
|
||||
"id": "39f42ba723de56d98867eabafc5e8e91",
|
||||
"name": "KmotherfuckerF"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The data is returned in the same order they were requested.
|
||||
|
||||
If one of the passed usernames isn’t found in the database, no value will be returned for it (it will be skipped). Keep this in mind when parsing the response.
|
||||
|
||||
### Profile info by UUID {#profile-by-uuid}
|
||||
See the `[profile request for the authorization server](./minecraft-auth.md#profile-request)`.
|
||||
|
||||
## Possible errors {#possible-errors}
|
||||
|
||||
### IllegalArgumentException {#illegal-argument-exception}
|
||||
This error occurs when attempting to send data to the server in an incorrect format.
|
||||
|
||||
An error example:
|
||||
```json
|
||||
{
|
||||
"error": "IllegalArgumentException",
|
||||
"errorMessage": "Invalid uuid format."
|
||||
}
|
||||
```
|
||||
The `errorMessage` is not always matches Mojang’s strings, but the differences are only apparent to Ely-specific errors. The original requests and the errors expected from them repeat Mojang texts.
|
@ -0,0 +1,59 @@
|
||||
# Authlib-Injector
|
||||
**authlib-injector** is a library that allows you to spoof authorization and session server addresses in the Authlib without modifying the library itself. It’s designed as an javaagent.
|
||||
|
||||
This library significantly simplifies the installation of an alternative authorization service in the game client and server, since transformation occurs during application bootstrap process.
|
||||
|
||||
You can download the latest version from the [releases page on GitHub](https://github.com/yushijinhun/authlib-injector/releases/latest).
|
||||
|
||||
Here is the documentation of the key aspects of installing and using the library. For more information, see the [original documentation in Chinese](https://github.com/yushijinhun/authlib-injector/wiki).
|
||||
|
||||
## Installing in a game client {#client}
|
||||
:::warning
|
||||
This section describes how to install the **authlib-injector** into the game. The game launcher still needs to implement the authorization flow itself in order to pass the `accessToken` to the game.
|
||||
:::
|
||||
|
||||
To install the library, you need to specify it as a javaagent for the game. You can do this by prepending the line `-javaagent:/path/to/file/authlib-injector.jar=ely.by` as a game launching param. As the result, the launch command should look like this:
|
||||
```bash
|
||||
java -javaagent:/path/to/authlib-injector.jar=ely.by -jar minecraft.jar
|
||||
```
|
||||
If you run the game via launcher, then in “settings” you need to find a field for specifying additional JVM arguments, where you need to insert the line above.
|
||||
|
||||

|
||||
|
||||
## Installing on a server {#server}
|
||||
Just as in the case with the game client, the library must be specified as javaagent. [Download the library](https://github.com/yushijinhun/authlib-injector/releases/latest) and put in the server’s directory. Then add the javaagent call to the server launch command:
|
||||
```bash
|
||||
# Before
|
||||
java -jar minecraft_server.jar
|
||||
# After
|
||||
java -javaagent:authlib-injector.jar=ely.by -jar minecraft_server.jar
|
||||
```
|
||||
During server startup you should see a message about the activation of the authlib-injector:
|
||||
|
||||

|
||||
|
||||
### BungeeCord {#bungeecord}
|
||||
The authlib-injector must be installed directly on the BungeeCord itself, as well as **on all backends** behind it. Note the configuration of the online-mode parameter:
|
||||
* The BungeeCord’s configuration (`config.yml`) should contain `online_mode=true`.
|
||||
* The servers behind the proxy must contain in their configuration (`server.properties`) the value `online-mode=false`.
|
||||
|
||||
Using such configuration authorization will work for all logging in players and the internal servers will correctly display player skins.
|
||||
|
||||
### LaunchHelper {#launchhelper}
|
||||
Not all game hostings allow direct modifications of launch arguments. To get around this limitation, you can use a special server that runs the game server by mixing authlib-injector into it. To install, follow these instructions:
|
||||
|
||||
1. Download the corresponding LaunchHelper for your operating system from the [releases page](https://github.com/Codex-in-somnio/LaunchHelper/releases/latest).
|
||||
1. Upload this file and the `authlib-injector.jar` file to the server folder on your hosting site.
|
||||
1. Also create a `launchhelper.properties` file and put the following contents into it:
|
||||
```properties
|
||||
javaAgentJarPath=authlib-injector.jar
|
||||
javaAgentOptions=ely.by
|
||||
execJarPath=minecraft_server.jar
|
||||
```
|
||||
Where `javaAgentJarPath` contains the path to the `authlib-injector.jar` file and `execJarPath` contains the name of the server file.
|
||||
1. In the hosting control panel, specify the `LaunchHelper.jar` as the server file.
|
||||
If you can’t change the executable file, you should rename the `LaunchHelper.jar` file to match your hosting requirements (usually, `server.jar`). In this case, you should have the following file structure:
|
||||
* `server.jar` - the LaunchHelper file.
|
||||
* `minecraft_server.jar` - your server core.
|
||||
* `authlib-injector.jar` - the authlib-injector file.
|
||||
* `launchhelper.properties` - the configuration file for the LaunchHelper.
|
8
i18n/en/docusaurus-plugin-content-docs/current/intro.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
slug: /
|
||||
---
|
||||
# Welcome to the Ely.by documentation! {#welcome}
|
||||
|
||||
In this documentation you will find information about the public services of the Ely.by project, using which you’ll be able to integrate your projects with the Ely.by services.
|
||||
|
||||
You are free to improve this documentation in the documentation’s repository.
|
42
i18n/en/docusaurus-theme-classic/footer.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"link.title.Проекты": {
|
||||
"message": "Projects",
|
||||
"description": "The title of the footer links column with title=Проекты in the footer"
|
||||
},
|
||||
"link.title.Сообщество": {
|
||||
"message": "Community",
|
||||
"description": "The title of the footer links column with title=Сообщество in the footer"
|
||||
},
|
||||
"link.item.label.Система скинов Ely.by": {
|
||||
"message": "Ely.by Skin System",
|
||||
"description": "The label of footer link with label=Система скинов Ely.by linking to https://ely.by"
|
||||
},
|
||||
"link.item.label.Аккаунты Ely.by": {
|
||||
"message": "Ely.by Accounts",
|
||||
"description": "The label of footer link with label=Аккаунты Ely.by linking to https://account.ely.by"
|
||||
},
|
||||
"link.item.label.X (Twitter)": {
|
||||
"message": "X (Twitter)",
|
||||
"description": "The label of footer link with label=X (Twitter) linking to https://x.com/erickskrauch"
|
||||
},
|
||||
"link.item.label.YouTube": {
|
||||
"message": "YouTube",
|
||||
"description": "The label of footer link with label=YouTube linking to https://www.youtube.com/c/ElyByOfficial"
|
||||
},
|
||||
"link.item.label.VK": {
|
||||
"message": "VK",
|
||||
"description": "The label of footer link with label=VK linking to https://vk.com/elyby"
|
||||
},
|
||||
"link.item.label.Discord": {
|
||||
"message": "Discord",
|
||||
"description": "The label of footer link with label=Discord linking to https://ely.by"
|
||||
},
|
||||
"link.item.label.GitHub": {
|
||||
"message": "GitHub",
|
||||
"description": "The label of footer link with label=GitHub linking to https://github.com/elyby"
|
||||
},
|
||||
"copyright": {
|
||||
"message": "Copyright © 2024 Ely.by. Built with Docusaurus.",
|
||||
"description": "The footer copyright"
|
||||
}
|
||||
}
|
10
i18n/en/docusaurus-theme-classic/navbar.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"title": {
|
||||
"message": "Документация Ely.by",
|
||||
"description": "The title in the navbar"
|
||||
},
|
||||
"logo.alt": {
|
||||
"message": "Логотип Ely.by",
|
||||
"description": "The alt text of navbar logo"
|
||||
}
|
||||
}
|
247
make.bat
@ -1,247 +0,0 @@
|
||||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% source
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. livehtml to run local webserver on 8000 port with authbuild feature
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. xml to make Docutils-native XML files
|
||||
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "livehtml" (
|
||||
sphinx-autobuild -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Elybydocs.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Elybydocs.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdf" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf
|
||||
cd %BUILDDIR%/..
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdfja" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf-ja
|
||||
cd %BUILDDIR%/..
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "xml" (
|
||||
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pseudoxml" (
|
||||
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
18274
package-lock.json
generated
Normal file
48
package.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "elyby-docusaurus",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.6.3",
|
||||
"@docusaurus/preset-classic": "3.6.3",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"docusaurus-plugin-image-zoom": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.6.3",
|
||||
"@docusaurus/tsconfig": "3.6.3",
|
||||
"@docusaurus/types": "3.6.3",
|
||||
"typescript": "~5.6.2"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.5%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 3 chrome version",
|
||||
"last 3 firefox version",
|
||||
"last 5 safari version"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
}
|
||||
}
|
33
sidebars.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
||||
|
||||
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
||||
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
const sidebars: SidebarsConfig = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
|
||||
|
||||
// But you can create a sidebar manually
|
||||
/*
|
||||
tutorialSidebar: [
|
||||
'intro',
|
||||
'hello',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tutorial',
|
||||
items: ['tutorial-basics/create-a-document'],
|
||||
},
|
||||
],
|
||||
*/
|
||||
};
|
||||
|
||||
export default sidebars;
|
Before Width: | Height: | Size: 198 B |
@ -1,52 +0,0 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto+Condensed&subset=cyrillic,latin);
|
||||
|
||||
body {
|
||||
background: #ebe8e1!important;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
legend,
|
||||
.wy-side-nav-search > a,
|
||||
.wy-nav-top a,
|
||||
.caption-text {
|
||||
font-family: "Roboto Condensed", "Roboto Slab", sans-serif;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.wy-side-nav-search,
|
||||
.wy-nav-top {
|
||||
background-color: #207E5C;
|
||||
}
|
||||
|
||||
.wy-menu-vertical a:active {
|
||||
background-color: #1A6449;
|
||||
}
|
||||
|
||||
.wy-nav-side {
|
||||
background-color: #232323;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.wy-table-responsive table td,
|
||||
.wy-table-responsive table th {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.wy-side-nav-search > a {
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.wy-nav-top {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.wy-menu .caption-text {
|
||||
color: #379070;
|
||||
font-size: 18px;
|
||||
text-transform: none;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
{# layout.html #}
|
||||
{# Import the theme's layout. #}
|
||||
{% extends "!layout.html" %}
|
||||
|
||||
{% set css_files = css_files + ['_static/style.css'] %}
|
||||
|
||||
{% block footer %}
|
||||
{{ super() }}
|
||||
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-45299905-2"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'UA-45299905-2');
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
126
source/api.rst
@ -1,126 +0,0 @@
|
||||
Ely.by API (симуляция Mojang API)
|
||||
---------------------------------
|
||||
|
||||
Здесь приведена информация об API, совместимом с функционалом `Mojang Api <http://wiki.vg/Mojang_API>`_. Обращаем ваше внимание на то, что это не полноценное API Ely.by, а только набор дополнительных запросов, реализованных на базе нашего :doc:`сервера авторизации <minecraft-auth>`.
|
||||
|
||||
Запросы
|
||||
=======
|
||||
|
||||
.. note:: API не имеет ограничения на количество запросов. У нас есть просто настроенный fail2ban, который будет банить особо надоедливых клиентов. Такие дела.
|
||||
|
||||
В этой секции будут описаны запросы и их же варианты для Mojang API. Все запросы выполняются на базовый url ``https://authserver.ely.by``.
|
||||
|
||||
UUID по нику на время
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Данный запрос позволяет узнать UUID пользователя по его нику на указанный момент времени. Время задаётся через GET параметр at с unix timestamp.
|
||||
|
||||
.. function:: GET /api/users/profiles/minecraft/{username}
|
||||
|
||||
Где ``{username}`` — искомый ник пользователя. Он может быть передан в любом регистре (В Mojang API только строгое совпадение).
|
||||
|
||||
Обратите так же внимание, что параметры legacy и demo никогда не будут возвращены, т.к. эти параметры не имеют в Ely альтернативы и специфичны только для сервисов Mojang.
|
||||
|
||||
В случае успешного запроса вы получите следующий ответ сервера:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"id": "ffc8fdc95824509e8a57c99b940fb996",
|
||||
"name": "ErickSkrauch"
|
||||
}
|
||||
|
||||
В случае, если переданный ник не будет найден, вы получите ответ с ``204`` статусом и пустым телом.
|
||||
|
||||
Никнейм по UUID + история изменений
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Данный запрос позволяет узнать все ники, использованные пользователем по его UUID.
|
||||
|
||||
.. function:: GET /api/user/profiles/{uuid}/names
|
||||
|
||||
Где ``{uuid}`` — валидный UUID. Валидным будет считаться UUID, написанный через дефисы или без них. В случае передачи невалидной строки, будет возвращён IllegalArgumentException_ с сообщением ``"Invalid uuid format."``.
|
||||
|
||||
В случае успешного запроса вы получите следующий ответ сервера:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
[
|
||||
{
|
||||
"name": "Admin"
|
||||
},
|
||||
{
|
||||
"name": "ErickSkrauch",
|
||||
"changedToAt": 1440707723000
|
||||
}
|
||||
]
|
||||
|
||||
.. note:: Т.к. на Ely.by не реализован алгоритм запоминания момента смены ника, будет возвращаться только 1 элемент. Чуть позже мы добавим полноценную поддержку запоминания момента смены ника.
|
||||
|
||||
В случае, если переданный UUID не будет найден, вы получите ответ с ``204`` статусом и пустым телом.
|
||||
|
||||
Список никнеймов в их UUID
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Этот запрос позволяет запросить список UUID пользователей по списку ников.
|
||||
|
||||
.. function:: POST /api/profiles/minecraft
|
||||
|
||||
В теле запроса или POST параметрах необходимо передать валидный JSON массив искомых ников.
|
||||
|
||||
В массиве должно быть не более 100 ников, в противном случае будет возвращён IllegalArgumentException_ с сообщением ``"Not more that 100 profile name per call is allowed."``. В случае, если переданная строка окажется невалидным JSON объектом, будет возвращено это же исключение, только с текстом ``"Passed array of profile names is an invalid JSON string."``.
|
||||
|
||||
Пример тела запроса:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
["ErickSkrauch", "EnoTiK", "KmotherfuckerF"]
|
||||
|
||||
В случае успешного запроса вы получите следующий ответ сервера:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
[
|
||||
{
|
||||
"id": "ffc8fdc95824509e8a57c99b940fb996",
|
||||
"name": "ErickSkrauch"
|
||||
},
|
||||
{
|
||||
"id": "b8407ae8218658ef96bb0cb3813acdfd",
|
||||
"name": "EnoTiK"
|
||||
},
|
||||
{
|
||||
"id": "39f42ba723de56d98867eabafc5e8e91",
|
||||
"name": "KmotherfuckerF"
|
||||
}
|
||||
]
|
||||
|
||||
Данные возвращаются в том же порядке, в каком и были запрошены.
|
||||
|
||||
В случае, если один из переданных никнеймов не найден в базе данных, для него не будет возвращено значения (он будет просто пропущен). Учитывайте эту ситуацию при парсинге ответа.
|
||||
|
||||
Запрос информации о профиле по UUID
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
См. :ref:`запрос профиля для сервера авторизации <profile-request>`.
|
||||
|
||||
Возможные ошибки
|
||||
================
|
||||
|
||||
.. _IllegalArgumentException:
|
||||
|
||||
IllegalArgumentException
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Данная ошибка возникает при попытке передать на сервер данные в неправильном формате.
|
||||
|
||||
Пример подобной ошибки:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"error": "IllegalArgumentException",
|
||||
"errorMessage": "Invalid uuid format."
|
||||
}
|
||||
|
||||
``errorMessage`` не всегда совпадает с таковым у Mojang, но в основном это касается только специфичных только для Ely ошибок. Оригинальные же запросы и ожидаемые от них ошибки повторяют тексты Mojang.
|
@ -1,83 +0,0 @@
|
||||
Authlib-injector
|
||||
----------------
|
||||
|
||||
**authlib-injector** — это библиотека, позволяющая подменить адреса серверов авторизации и сессии в Authlib, не модифицируя непосредственно саму библиотеку. Выполнена как javaagent.
|
||||
|
||||
Данная библиотека значительно упрощает установку альтернативных сервисов авторизации в игровой клиент и сервер, поскольку она универсально применяет трансформацию в процессе работы программы.
|
||||
|
||||
Скачать последнюю версию можно со `страницы релизов на GitHub <https://github.com/yushijinhun/authlib-injector/releases/latest>`_.
|
||||
|
||||
Здесь приведена документация к ключевым аспектам установки и использования библиотеки. Для более подробной информации обратитесь к `оригинальной документации на китайском языке <https://github.com/yushijinhun/authlib-injector/wiki>`_.
|
||||
|
||||
.. _client:
|
||||
|
||||
Установка в игровой клиент
|
||||
==========================
|
||||
|
||||
.. attention:: Обратите внимание, что этот раздел описывает установку authlib-injector в игру. Игровой лаунчер по-прежнему должен самостоятельно реализовать процесс авторизации, чтобы после передать ``accessToken`` в игру.
|
||||
|
||||
Для применения библиотеки, необходимо указать её в качестве javaagent для игры. Сделать это можно, добавив в начало команды запуска игры строку ``-javaagent:/путь/до/файла/authlib-injector.jar=ely.by``. В результате изменений строка запуска игры должна выглядеть следующим образом:
|
||||
|
||||
.. code-block::
|
||||
|
||||
java -javaagent:/путь/до/файла/authlib-injector.jar=ely.by -jar minecraft.jar
|
||||
|
||||
Если вы запускаете игру через лаунчер, то в его настройках необходимо найти поле для указания дополнительных аргументов JVM, куда необходимо в самое начало вставить строку, приведённую выше.
|
||||
|
||||
.. figure:: images/authlib-injector/launcher-jvm-options.png
|
||||
:align: center
|
||||
:alt: Редактирование аргументов JVM
|
||||
|
||||
.. _server:
|
||||
|
||||
Установка на сервер
|
||||
===================
|
||||
|
||||
Также как и в случае с игровым клиентом, библиотеку необходимо указать в качестве javaagent. `Скачайте библиотеку <https://github.com/yushijinhun/authlib-injector/releases/latest>`_ и поместите её в директорию с сервером. После этого добавьте вызов javaagent в команду запуска сервера:
|
||||
|
||||
| До: ``java -jar minecraft_server.jar``
|
||||
| После: ``java -javaagent:authlib-injector.jar=ely.by -jar minecraft_server.jar``
|
||||
|
||||
При запуске сервера вы должны увидеть сообщение об активации authlib-injector:
|
||||
|
||||
.. figure:: images/authlib-injector/server-startup-messages.png
|
||||
:align: center
|
||||
:alt: Сообщение при запуске сервера
|
||||
|
||||
BungeeCord
|
||||
~~~~~~~~~~
|
||||
|
||||
authlib-injector должен быть установлен непосредственно на сам BungeeCord, а также **на все сервера** позади него. Обратите внимание на конфигурацию параметра online‑mode:
|
||||
|
||||
* В конфигурации BungeeCord (``config.yml``) должно стоять значение ``online_mode=true``.
|
||||
* В конфигурации всех серверов позади прокси (``server.properties``) должно быть указано значение ``online-mode=false``.
|
||||
|
||||
Благодаря такой конфигурации установки, авторизация будет работать для всех входящих игроков, а на внутренних серверах будут корректно отображаться скины игроков.
|
||||
|
||||
LaunchHelper
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Не все игровые хостинги позволяют напрямую модифицировать аргументы, с которыми запускается сервер. Чтобы обойти это ограничение, можно использовать специальный сервер, который запускает игровой сервер, подмешивая туда authlib-injector. Для установки следуйте инструкции:
|
||||
|
||||
#. Скачайте версию LaunchHelper для вашей операционной системы со `страницы загрузок <https://github.com/Codex-in-somnio/LaunchHelper/releases/latest>`_.
|
||||
|
||||
#. Загрузите скачанный файл и файл ``authlib-injector.jar`` в папку сервера на вашем хостинге.
|
||||
|
||||
#. Там же создайте файл ``launchhelper.properties`` и поместите в него следующее содержимое:
|
||||
|
||||
.. code-block::
|
||||
|
||||
javaAgentJarPath=authlib-injector.jar
|
||||
javaAgentOptions=ely.by
|
||||
execJarPath=minecraft_server.jar
|
||||
|
||||
Где ``javaAgentJarPath`` содержит путь до файла authlib-injector.jar, а ``execJarPath`` содержит имя файла сервера.
|
||||
|
||||
#. В панели управления хостингом укажите ``LaunchHelper.jar`` в качестве запускаемого файла сервера.
|
||||
|
||||
Если возможности указать исполнимый файл явно нет, то следует переименовать файл ``LaunchHelper.jar`` в соответствие с требованиями вашего хостинга (обычно, это ``server.jar``). В этом случае у вас должна получиться следующая структура файлов:
|
||||
|
||||
* ``server.jar`` - файл LaunchHelper.
|
||||
* ``minecraft_server.jar`` - предпочитаемое ядро сервера.
|
||||
* ``authlib-injector.jar`` - файл authlib-injector.
|
||||
* ``launchhelper.properties`` - файл конфигурации для LaunchHelper.
|
291
source/conf.py
@ -1,291 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ely.by docs documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sun Feb 1 22:04:01 2015.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sphinx_rtd_theme
|
||||
import datetime
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = ['sphinx.ext.ifconfig']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'Ely.by Documentation'
|
||||
copyright = str(datetime.datetime.now().year) + ', Ely.by'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = ''
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = ''
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
language = 'ru'
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'monokai'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
html_title = 'Ely.by Docs'
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
html_favicon = '_static/favicon.ico'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = False
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Elybydocsdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'Elybydocs.tex', 'Ely.by docs Documentation',
|
||||
'ErickSkrauch', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'elybydocs', 'Ely.by docs Documentation',
|
||||
['ErickSkrauch'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Elybydocs', 'Ely.by docs Documentation',
|
||||
'ErickSkrauch', 'Elybydocs', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
# -- Options for sphinx-intl ----------------------------------------------
|
||||
|
||||
locale_dirs = ['../locale/']
|
||||
gettext_compact = False
|
||||
|
||||
# -- Static files generation rules ----------------------------------------
|
||||
|
||||
static_files = [
|
||||
'../CNAME',
|
||||
'../.nojekyll',
|
||||
]
|
||||
|
||||
from shutil import copyfile
|
||||
from os import path
|
||||
from sphinx.application import Sphinx
|
||||
|
||||
def copy_static_files(app: Sphinx, _) -> None:
|
||||
if app.builder.name != "html":
|
||||
return
|
||||
|
||||
for src_name in static_files:
|
||||
src_path = path.join(app.srcdir, src_name)
|
||||
target_path = path.join(app.outdir, src_name)
|
||||
if path.dirname(path.normpath(target_path)) == path.dirname(app.outdir):
|
||||
target_path = path.join(app.outdir, path.basename(target_path))
|
||||
|
||||
if path.isfile(src_path):
|
||||
copyfile(src_path, target_path)
|
||||
|
||||
def setup(app):
|
||||
app.connect("build-finished", copy_static_files)
|
@ -1,13 +0,0 @@
|
||||
Добро пожаловать в документацию Ely.by!
|
||||
=======================================
|
||||
|
||||
В этой документации вы найдёте информацию о публичных сервисах проекта Ely.by, используя которые вы сможете самостоятельно реализовать свои программные продукты для совместной работы с сервисом Ely.by.
|
||||
|
||||
Вы можете свободно улучшать и вносить предложения по изменениям в документацию в `репозитории проекта <https://github.com/elyby/docs>`_.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Статьи на русском:
|
||||
:glob:
|
||||
|
||||
*
|
@ -1,380 +0,0 @@
|
||||
Авторизация для Minecraft
|
||||
-------------------------
|
||||
|
||||
Здесь приведена информация по авторизации для лаунчеров и серверов Minecraft через сервис авторизации Ely.by.
|
||||
|
||||
Протокол авторизации реализован максимально похожим на `оригинальный протокол авторизации Mojang <http://wiki.vg/Authentication>`_, но тем не менее эта документация описывает все доступные функции конкретно сервиса авторизации Ely.by.
|
||||
|
||||
Общие положения
|
||||
===============
|
||||
|
||||
* Все запросы должны выполняться на URL **https://authserver.ely.by**.
|
||||
|
||||
* При успешном запросе, сервер вернёт ответ со статусом 200. Любой другой код свидетельствует об ошибке.
|
||||
|
||||
* Сервер всегда отвечает JSON данными, кроме случаев системных ошибок и ответов на legacy запросы. Учитывайте это для отображения пользователю правильного сообщения об ошибке.
|
||||
|
||||
* В случае стандартной ошибки, вы получите следующие данные:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"error": "Краткое описание ошибки",
|
||||
"errorMessage": "Более длинное описание ошибки на английском языке, пригодное для отображения пользователю."
|
||||
}
|
||||
|
||||
Предусмотренные ошибки
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
В отличие от оригинального протокола, на Ely применяется меньший зоопарк ошибок:
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 50 30
|
||||
:header-rows: 1
|
||||
|
||||
* - Ошибка (error)
|
||||
- Причина
|
||||
- Решение
|
||||
* - IllegalArgumentException
|
||||
- Вы передали неполный список данных для выполнения запроса.
|
||||
- Внимательно перепроверьте что вы шлёте в запросе и что указано в документации.
|
||||
* - ForbiddenOperationException
|
||||
- Пользователь ввёл/разработчик передал неверные значения.
|
||||
- Необходимо вывести пользователю уведомление о неправильно введённых данных.
|
||||
|
||||
Для индикации ошибки Not Found используется ответ с 404 статусом.
|
||||
|
||||
Авторизация в лаунчере
|
||||
======================
|
||||
|
||||
В этом разделе описана авторизация для игрового лаунчера и описывает действия, необходимые для получения ``accessToken`` для игрового клиента Minecraft. В результате авторизации будет получен JWT-токен с :ref:`правами доступа <available_scopes>` ``minecraft_server_session``.
|
||||
|
||||
.. attention:: Мы рекомендуем использовать :doc:`протокол авторизации OAuth 2.0 <oauth>` с запросом :ref:`прав доступа <available_scopes>` ``minecraft_server_session``, как более безопасный и удобный для пользователя метод.
|
||||
|
||||
.. function:: POST /auth/authenticate
|
||||
|
||||
Непосредственная авторизация пользователя, используя его логин (ник или E‑mail), пароль и токен двухфакторной аутентификации.
|
||||
|
||||
:param string username: Никнейм пользователя или его E‑mail (более предпочтительно).
|
||||
:param string password: Пароль пользователя или комбинация ``пароль:токен``.
|
||||
:param string clientToken: Уникальный токен лаунчера пользователя.
|
||||
:param bool requestUser: Если поле передано как ``true``, то в ответе сервера будет присутствовать поле ``user``.
|
||||
|
||||
Система аккаунтов Ely.by поддерживает защиту пользователей посредством двухфакторной аутентификации. В оригинальном протоколе авторизации Mojang не предусмотрено возможности для передачи TOTP-токенов. Для решения этой проблемы и сохранения совместимости с реализацией сервера `Yggdrasil <https://minecraft.gamepedia.com/Yggdrasil>`_, мы предлагаем передавать токен в поле ``password`` в формате ``пароль:токен``.
|
||||
|
||||
К сожалению, не все пользователи осведомлены об этой возможности, поэтому будет лучше при получении ошибки о защищённости аккаунта пользователя двухфакторной аутентификацией явно запросить у него токен и склеить его программно.
|
||||
|
||||
Логика следующая:
|
||||
|
||||
#. Если пользователь указал верные логин и пароль, но для его аккаунта включена двухфакторная аутентификация, вы получите ответ с ``401`` статусом и следующим содержимым:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"error": "ForbiddenOperationException",
|
||||
"errorMessage": "Account protected with two factor auth."
|
||||
}
|
||||
|
||||
#. При получении этой ошибки, необходимо запросить у пользователя ввод TOTP‑токена, после чего повторить запрос на авторизацию с теми же учётными данными, добавив к паролю постфикс в виде ``:токен``, где ``токен`` — это значение, введённое пользователем.
|
||||
|
||||
Если пароль пользователя был "password123", а токен "123456", то после склейки поле ``password`` примет значение "password123:123456".
|
||||
|
||||
#. Если в результате этих действий вы получите ответ с ``401`` статутом и ``errorMessage`` "Invalid credentials. Invalid email or password.", то это будет свидетельствовать о том, что переданный токен неверен и его нужно перезапросить у пользователя.
|
||||
|
||||
Если все данные будут переданы верно, вы получите следующий ответ:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"accessToken": "Длинная_строка_содержащая_access_token",
|
||||
"clientToken": "Переданный_в_запросе_client_token",
|
||||
"availableProfiles": [
|
||||
{
|
||||
"id": "UUID_пользователя_без_дефисов",
|
||||
"name": "Текущий_username_пользователя"
|
||||
}
|
||||
],
|
||||
"selectedProfile": {
|
||||
"id": "UUID_пользователя_без_дефисов",
|
||||
"name": "Текущий_username_пользователя"
|
||||
},
|
||||
"user": { /* Только если передан параметр requestUser */
|
||||
"id": "UUID_пользователя_без_дефисов",
|
||||
"username": "Текущий_username_пользователя",
|
||||
"properties": [
|
||||
{
|
||||
"name": "preferredLanguage",
|
||||
"value": "ru"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
.. function:: POST /auth/refresh
|
||||
|
||||
Обновляет валидный ``accessToken``. Этот запрос позволяет не хранить на клиенте его пароль, а оперировать только сохранённым значением ``accessToken`` для практически бесконечной возможности проходить авторизацию.
|
||||
|
||||
:param string accessToken: Уникальный ключ, полученный после авторизации.
|
||||
:param string clientToken: Уникальный идентификатор клиента, относительно которого получен accessToken.
|
||||
:param bool requestUser: Если поле передано как ``true``, то в ответе сервера будет присутствовать поле ``user``.
|
||||
|
||||
.. note:: В оригинальном протоколе так же передаётся значение ``selectedProfile``, но в реализации Mojang он не влияет ни на что. Наша реализация сервера авторизации игнорирует этот параметр и опирается на значения ``accessToken`` и ``clientToken``.
|
||||
|
||||
В случае получения какой-либо предусмотренной ошибки, следует заново запросить пароль пользователя и произвести обычную авторизацию.
|
||||
|
||||
Успешный ответ:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"accessToken": "Новая_длинная_строка_ содержащая_access_token",
|
||||
"clientToken": "Переданный_в_запросе_client_token",
|
||||
"selectedProfile": {
|
||||
"id": "UUID_пользователя_без_дефисов",
|
||||
"name": "Текущий_username_пользователя"
|
||||
},
|
||||
"user": { /* Только если передан параметр requestUser */
|
||||
"id": "UUID_пользователя_без_дефисов",
|
||||
"username": "Текущий_username_пользователя",
|
||||
"properties": [
|
||||
{
|
||||
"name": "preferredLanguage",
|
||||
"value": "ru"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
.. function:: POST /auth/validate
|
||||
|
||||
Этот запрос позволяет проверить валиден ли указанный accessToken или нет. Этот запрос не обновляет токен и его время жизни, а только позволяет удостовериться, что он ещё действительный.
|
||||
|
||||
:param string accessToken: Токен доступа, полученный после авторизации.
|
||||
|
||||
Успешным ответом будет являться пустое тело. При ошибке будет получен **400** или **401** статус. Пример ответа сервера при отправке истёкшего токена:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"error": "ForbiddenOperationException",
|
||||
"errorMessage": "Token expired."
|
||||
}
|
||||
|
||||
.. function:: POST /auth/signout
|
||||
|
||||
Этот запрос позволяет выполнить инвалидацию всех выданных пользователю токенов.
|
||||
|
||||
:param string username: Никнейм пользователя или его E-mail (более предпочтительно).
|
||||
:param string password: Пароль пользователя.
|
||||
|
||||
Успешным ответом будет являться пустое тело. Ориентируйтесь на поле **error** в теле ответа.
|
||||
|
||||
.. function:: POST /auth/invalidate
|
||||
|
||||
Запрос позволяет инвалидировать accessToken. В случае, если переданный токен не удастся найти в хранилище токенов, ошибка не будет сгенерирована и вы получите успешный ответ.
|
||||
|
||||
Входные параметры:
|
||||
|
||||
:param string accessToken: Уникальный ключ, полученный после авторизации.
|
||||
:param string clientToken: Уникальный идентификатор клиента, относительно которого получен accessToken.
|
||||
|
||||
Успешным ответом будет являться пустое тело. Ориентируйтесь на поле **error** в теле ответа.
|
||||
|
||||
Авторизация на сервере
|
||||
======================
|
||||
|
||||
Эти запросы выполняются непосредственно клиентом и сервером при помощи внутреннего кода или библиотеки authlib (начиная с версии 1.7.2). Они актуальны только в том случае, если вы уже произвели авторизацию и запустили игру с валидным accessToken. Вам остаётся только заменить пути внутри игры/библиотеки на приведённые ниже пути.
|
||||
|
||||
Поскольку непосредственно изменить что-либо в работе authlib или игры вы не можете, здесь не приводятся передаваемые значения и ответы сервера. При необходимости вы сможете найти эту информацию самостоятельно в интернете.
|
||||
|
||||
Через authlib
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.. important:: Эта часть документации описывает запросы, выполняемые через authlib в версии игры 1.7.2+. Для более старых версий смотрите раздел ниже.
|
||||
|
||||
Все запросы из этой категории выполняются на подуровень /session. Перед каждым из запросов указан тип отправляемого запроса.
|
||||
|
||||
.. function:: POST /session/join
|
||||
|
||||
Запрос на этот URL производится клиентом в момент подключения к серверу с online-mode=true.
|
||||
|
||||
.. function:: GET /session/hasJoined
|
||||
|
||||
Запрос на этот URL выполняет сервер с online-mode=true после того, как клиент, пытающийся к нему подключиться, успешно выполнит join запрос. Текстуры будут подписаны ключом Ely.by.
|
||||
|
||||
Ключ для проверки подписи можно получить через :ref:`сервер системы скинов <signature-verification-key-request>`.
|
||||
|
||||
.. attention:: В редких случаях поле ``signature`` будет иметь значение ``Cg==``. При таком значении поля подписи проводить её проверку не нужно, т.к. она всегда будет некорректной.
|
||||
|
||||
Для старых версий
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. important:: Эта часть документации описывает запросы, выполняемые более старыми версиями Minecraft, в которых не применялась библиотека Authlib. Это все версии ниже 1.7.2.
|
||||
|
||||
Все запросы из этой категории выполняются на подуровень /session/legacy. Перед каждым из запросов указан тип отправляемого запроса.
|
||||
|
||||
Принцип обработки этих запросов такой же, как и для authlib, отличие только во входных параметрах и возвращаемых значения.
|
||||
|
||||
.. function:: GET /session/legacy/join
|
||||
|
||||
Запрос на этот URL производится клиентом в момент подключения к серверу с online-mode=true.
|
||||
|
||||
.. function:: GET /session/legacy/hasJoined
|
||||
|
||||
Запрос на этот URL выполняет сервер с online-mode=true после того, как клиент, пытающийся к нему подключится, успешно выполнит join запрос.
|
||||
|
||||
Важно не потерять GET параметр **?user=** в конце обоих запросов, чтобы получились следующие URL: ``https://authserver.ely.by/session/legacy/hasJoined?user=``.
|
||||
|
||||
Одиночная игра
|
||||
==============
|
||||
|
||||
По сути, одиночная игра - это локальный сервер, созданный для одного игрока. По крайней мере это так, начиная с версии 1.6, в которой и был представлен механизм локальных серверов.
|
||||
|
||||
Тем не менее, описанный ниже запрос актуален только для Minecraft 1.7.6+, когда для загрузки скинов стала использоваться так же Authlib.
|
||||
|
||||
.. _profile-request:
|
||||
|
||||
.. function:: GET /session/profile/{uuid}
|
||||
|
||||
Запрос на этот URL выполняется клиентом в одиночной игре на локальном сервере (созданном посредством самой игры). В URL передаётся UUID пользователя, с которым был запущен клиент, а в ответ получается информация о текстурах игрока в таком же формате, как и при hasJoined запросе.
|
||||
|
||||
Готовые библиотеки authlib
|
||||
==========================
|
||||
|
||||
.. attention:: Ely.by поддерживает библиотеку authlib-injector. Это наиболее простой и универсальный способ установки системы авторизации в игру и игровые сервера. За подробностями обратитесь в :doc:`соответствующий раздел документации <authlib-injector>`.
|
||||
|
||||
Поскольку самостоятельная реализация связана с трудностями поиска исходников, подключения зависимостей и в конце-концов с процессом компиляции, на `странице загрузок нашей системы скинов <https://ely.by/load>`_ вы можете загрузить уже готовые библиотеки со всеми необходимыми изменениями. Выберите в выпадающем списке необходимую версию и следуйте инструкции по установке, размещённой на той же странице ниже.
|
||||
|
||||
В более ранних версиях игры система скинов находилась внутри игрового клиента, так что библиотеки ниже обеспечивают лишь авторизацию:
|
||||
|
||||
* Minecraft 1.7.5 - :download:`authlib 1.3.1 <files/authlib/authlib-1.3.1.jar>`
|
||||
|
||||
* Minecraft 1.7.2 - :download:`authlib 1.3 <files/authlib/authlib-1.3.jar>`
|
||||
|
||||
Для установки вам необходимо заменить оригинальную библиотеку, располагающуюся по пути ``<директория установки minecraft>/libraries/com/mojang/authlib/``. Убедитесь в том, что версии скачанного и заменяемого файлов совпадают.
|
||||
|
||||
.. _install-server:
|
||||
|
||||
Установка authlib на сервер
|
||||
===========================
|
||||
|
||||
Сервер также использует authlib для выполнения авторизации игрока, поэтому соответствующие изменения должны быть также применены и к нему. Ниже приведены инструкции по установки authlib для различных реализаций сервера Minecraft.
|
||||
|
||||
.. note:: Если ни одна из приведённых ниже инструкций не подошла для вашей реализации сервера, пожалуйста, создайте `новый issue <https://github.com/elyby/docs/issues/new>`_ и мы допишем инструкцию для вашего сервера.
|
||||
|
||||
.. _vanilla:
|
||||
|
||||
Оригинальный сервер
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
С помощью архиватора откройте файл сервера ``minecraft_server.ВЕРСИЯ.jar``. Таким же образом откройте архив с authlib для соответствующей версии сервера. Перед вами будет два окна: одно с файлами сервера, другое с файлами authlib. Вам необходимо "перетащить" из архива с authlib все файлы и папки, **за исключением директории META-INF**, и подтвердить замену.
|
||||
|
||||
.. figure:: images/minecraft-auth/authlib-install.png
|
||||
:align: center
|
||||
:alt: Процесс установки Authlib
|
||||
|
||||
Обратите внимание: "перетягивать" содержимое нужно ниже папок сервера (в область файлов .class).
|
||||
|
||||
После этих действий вы можете закрыть оба окна и в файле ``server.properties`` установить значение ``online-mode=true``.
|
||||
|
||||
Bukkit/Spigot
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Сперва выполните установку, как она описана для `оригинального сервера <#vanilla>`_. Затем скачайте библиотеки `commons-io <https://repo1.maven.org/maven2/commons-io/commons-io/2.5/commons-io-2.5.jar>`_ и `commons-lang3 <https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar>`_, после чего аналогичным с authlib образом последовательно переместите содержимое скачанных архивов в файлы сервера.
|
||||
|
||||
Forge/Sponge
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Прежде чем производить установку, необходимо определить, какой именно файл подлежит модификации:
|
||||
|
||||
* **>=1.16**: ``libraries/net/minecraft/server/ВЕРСИЯ-ЦИФРЫ/server-ВЕРСИЯ-ЦИФРЫ-extra.jar``.
|
||||
* **1.13-1.15**: ``libraries/net/minecraft/server/ВЕРСИЯ/server-ВЕРСИЯ-extra.jar``.
|
||||
* **<=1.12**: ``minecraft_server.ВЕРСИЯ.jar``.
|
||||
|
||||
Когда необходимый файл найден, выполните для него установку authlib, аналогично `оригинальному серверу <#vanilla>`_.
|
||||
|
||||
Paper (PaperSpigot)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Установка производится по аналогии с `Bukkit/Spigot <#bukkit-spigot>`_ в файл ``cache/patched-ВЕРСИЯ.jar``. После внесения изменений, запускать сервер нужно через jar-файл из директории ``cache``, поскольку в противном случае **Paper восстановит исходное состояние файла**:
|
||||
|
||||
| До: ``java -jar paper-ВЕРСИЯ-БИЛД.jar``
|
||||
| После: ``java -jar cache/patched-ВЕРСИЯ.jar``
|
||||
|
||||
BungeeCord
|
||||
==========
|
||||
|
||||
.. hint:: Вы можете воспользоваться библиотекой :doc:`authlib-injector <authlib-injector>` для установки системы авторизации без модификации внутренностей сервера.
|
||||
|
||||
Хотя BungeeCord и является проксирующим сервером, авторизацию игроков он выполняет самостоятельно. К сожалению, BungeeCord не опирается на использование Authlib, а реализует процесс авторизации самостоятельно, поэтому для установки системы авторизации Ely.by вам понадобится модифицировать скомпилированные ``.class`` файлы.
|
||||
|
||||
Для установки следуйте инструкции ниже:
|
||||
|
||||
#. Скачайте программу InClassTranslator (прямых ссылок не даём, но его легко найти).
|
||||
|
||||
#. С помощью архиватора откройте файл ``BungeeCord.jar``.
|
||||
|
||||
#. Перейдите по пути ``net/md_5/bungee/connection`` и найдите там файл ``InitialHandler.class`` (без каких-либо символов $).
|
||||
|
||||
#. Распакуйте этот файл. В самом простом случае сделать это можно просто "вытянув" его из окна архиватора.
|
||||
|
||||
#. Откройте распакованный файл в программе InClassTranslator и замените в нём строку ``https://sessionserver.mojang.com/session/minecraft/hasJoined?username=`` на ``https://authserver.ely.by/session/hasJoined?username=``, как показано на рисунке ниже:
|
||||
|
||||
.. figure:: images/minecraft-auth/bungeecord_inclasstranslator.png
|
||||
:align: center
|
||||
:alt: Редактирование в InClassTranslator
|
||||
|
||||
#. Сохраните изменения и перетащите измененный файл обратно в архив сервера. Подтвердите замену.
|
||||
|
||||
.. figure:: images/minecraft-auth/bungeecord_move.png
|
||||
:align: center
|
||||
:alt: Перетаскивание отредактированного файла назад в архив
|
||||
|
||||
После выполнения этих действий вы можете указать в файле конфигурации BungeeCord (``config.yml``) значение ``online_mode=true``.
|
||||
|
||||
.. important:: Мы также рекомендуем выполнить установку Authlib на все сервера позади BungeeCord. Это может быть необходимо для плагинов, которые используют API Mojang. Инструкция по установке на конечные сервера приведена `выше <#install-server>`_.
|
||||
|
||||
При этом все сервера должны иметь в своей конфигурации (``server.properties``) значение ``online-mode=false``, поскольку пользователи уже авторизованы силами BungeeCord.
|
||||
|
||||
Установка на версии ниже 1.7.2
|
||||
==============================
|
||||
|
||||
Для более старых версий существует достаточно большое многообразие различных случаев, раскрыть которые в этой документации не представляется возможным. Вся установка заключается в замене определённых строк в определённых классах через InClassTranslator.
|
||||
|
||||
На форуме RuBukkit есть отличный пост, в котором собрана вся нужна информация по именам классов на различных версиях Minecraft. Переписывать его сюда не имеет смысла, так что просто перейдите на его страницу и найдите нужную версию.
|
||||
|
||||
|rubukkit_link|.
|
||||
|
||||
.. |rubukkit_link| raw:: html
|
||||
|
||||
<a href="http://www.rubukkit.org/threads/spisok-klassov-i-klientov-dlja-mcp.25108/#post-303710" target="_blank">RuBukkit -
|
||||
Список классов и клиентов для MCP</a>
|
||||
|
||||
Пример установки
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Предположим, что вы хотите установить авторизацию на сервер версии 1.5.2.
|
||||
|
||||
Сначала вы переходите по вышеприведённой ссылке, выбираете нужную версию (1.5.2) и видите список классов:
|
||||
|
||||
* **bdk.class** - путь до joinserver
|
||||
|
||||
* **jg.class** - путь до checkserver
|
||||
|
||||
Затем вы должны взять .jar файл клиента и открыть его любым архиватором. После чего вам необходимо найти файл **bdk.class**. Для этого удобно воспользоваться поиском.
|
||||
|
||||
После того, как вы нашли файл, его нужно извлечь из архива - просто перетащите его оттуда в удобную для вас дирикторию.
|
||||
|
||||
Дальше запустите InClassTranslator и в нём откройте этот класс. Слева будет список найденных в файле строк, которые вы можете изменить. Нужно заменить только строку, отвечающую за запрос на подключение к серверу:
|
||||
|
||||
.. figure:: images/minecraft-auth/installing_by_inclasstranslator.png
|
||||
:align: center
|
||||
:alt: Порядок редактирования: выбрать нужную строку, изменить, сохранить.
|
||||
|
||||
После этого вам нужно положить изменённый .class обратно в .jar файл игры.
|
||||
|
||||
Ту же самую операцию вам необходимо провести и с сервером, только заменить ссылку на hasJoined.
|
||||
|
||||
-----------------------
|
||||
|
||||
После этих действий вам нужно в настройках включить online-mode=true и сервер станет пускать на себя только тех игроков, которые будут авторизованы через Ely.by.
|
391
source/oauth.rst
@ -1,391 +0,0 @@
|
||||
Авторизация по протоколу OAuth2
|
||||
-------------------------------
|
||||
|
||||
На этой странице вы найдёте информацию о реализации авторизации по протоколу OAuth2 на вашем проекте через сервис Аккаунты Ely.by. Реализация этого протокола позволяет вашим пользователям производить авторизацию с использованием своего аккаунта Ely.by.
|
||||
|
||||
Регистрация приложения
|
||||
======================
|
||||
|
||||
Для начала вам необходимо `создать новое приложение <https://account.ely.by/dev/applications/new>`_. Выберите тип приложения **Веб‑сайт**. В качестве *адреса переадресации* можно указать только домен, но для повышения безопасности лучше использовать полный путь переадресации. Примеры допустимых адресов:
|
||||
|
||||
* ``http://site.com``
|
||||
* ``http://site.com/oauth/ely``
|
||||
* ``http://site.com/oauth.php?provider=ely``
|
||||
|
||||
После успешного добавления приложения вы попадёте на страницу со списком всех ваших приложений. Кликнув по названию приложения вы увидите его идентификатор ``clientId`` и секрет ``clientSecret``. Они буду использоваться на следующих шагах.
|
||||
|
||||
Инициализация авторизации
|
||||
=========================
|
||||
|
||||
Для инициализации процесса авторизации вам необходимо перенаправить пользователя по следующему URL:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
https://account.ely.by/oauth2/v1?client_id=<clientId>&redirect_uri=<redirectUri>&response_type=code&scope=<scopesList>
|
||||
|
||||
.. list-table:: Допустимые параметры запроса
|
||||
:widths: 1 1 98
|
||||
:header-rows: 1
|
||||
|
||||
* - Параметр
|
||||
- Пример значения
|
||||
- Описание
|
||||
* - *clientId*
|
||||
- ``ely``
|
||||
- **Обязательное**. ClientId, полученный при регистрации.
|
||||
* - *redirect_uri*
|
||||
- ``http://site.com/oauth.php``
|
||||
- **Обязательное**. Адрес обратной переадресации, совпадающий с адресом, указанным при регистрации приложения
|
||||
* - *response_type*
|
||||
- ``code``
|
||||
- **Обязательное**. Тип ответа. На данный момент поддерживается только ``code``.
|
||||
* - *scope*
|
||||
- ``account_info account_email``
|
||||
- **Обязательное**. Перечень разрешений, доступ к которым вы хотите получить, разделённые пробелом. Смотрите все доступные права в `разделе ниже <#available-scopes>`_.
|
||||
* - *state*
|
||||
- ``isfvubuysdboinsbdfvit``
|
||||
- Случайно сгенерированная строка. Используется для увеличения безопасности в качестве идентификатора сессии. Будет возвращена в неизменённом виде после завершения авторизации.
|
||||
* - *description*
|
||||
- ``यो अनुप्रयोग विवरण``
|
||||
- Если ваше приложение доступно на нескольких языках, то используя это поле вы можете переопределить стандартное описание в соответствии с предпочтительным языком пользователя.
|
||||
* - *prompt*
|
||||
- ``consent`` или ``select_account``
|
||||
- Принудительно отобразить запрос прав (``consent``) или принудительно запросить выбор аккаунта (``select_account``).
|
||||
* - *login_hint*
|
||||
- ``erickskrauch`` или ``erickskrauch@ely.by``
|
||||
- Если у пользователя есть несколько аккаунтов, то указав этот в этом параметре username или E-mail пользователя вы автоматически выберете аккаунт за него. Это полезно в случае повторного входа, когда токен истёк.
|
||||
|
||||
.. _available_scopes:
|
||||
.. list-table:: Перечень доступных scopes
|
||||
:widths: 1 99
|
||||
:header-rows: 0
|
||||
|
||||
* - **account_info**
|
||||
- Получение информации о пользователе.
|
||||
* - **account_email**
|
||||
- В ответе на запрос информации о пользователе будет также присутствовать его email.
|
||||
* - **offline_access**
|
||||
- Вместе с ``access_token`` вы также получите и ``refresh_token``. Смотрите подробнее `соответствующем разделе <#refresh-token-grant>`_.
|
||||
* - **minecraft_server_session**
|
||||
- ``access_token`` можно будет использовать в качестве сессии для Minecraft.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Сформировав ссылку, разместите её в вашем шаблоне:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<a href="<ваша_ссылка>">Войти через Ely.by</a>
|
||||
|
||||
По нажатию на ссылку, пользователь попадёт на нашу страницу авторизации, откуда после он будет перенаправлен обратно по адресу, указанному в параметре ``redirect_uri``.
|
||||
|
||||
Обратная переадресация выполняется в виде ``<redirect_uri>?code=<код авторизации>&state=<state>`` для успешной авторизации и ``<redirect_uri?error=<идентификатор ошибки>&error_message=<описание ошибки>`` для неудачной.
|
||||
|
||||
Пример успешного и неудачного редиректов:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
http://site.com/oauth/ely.php?code=dkpEEVtXBdIcgdQWak4SOPEpTJIvYa8KIq5cW9GJ&state=ajckasdcjasndckbsadc
|
||||
http://site.com/oauth/ely.php?error=access_denied&error_message=The+resource+owner+or+authorization+server+denied+the+request.
|
||||
|
||||
.. _authorization-code-grant:
|
||||
|
||||
Обмен кода на ключ
|
||||
==================
|
||||
|
||||
После получения кода авторизации (``auth_code``), вам необходимо обменять его на ключ авторизации (``access_key``). Для этого необходимо выполнить POST запрос на URL:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
https://account.ely.by/api/oauth2/v1/token
|
||||
|
||||
И передать туда следующие параметры:
|
||||
|
||||
.. list-table::
|
||||
:widths: 1 99
|
||||
:header-rows: 0
|
||||
|
||||
* - ``client_id``
|
||||
- ClientID, полученный при регистрации приложения.
|
||||
* - ``client_secret``
|
||||
- ClientSecret, полученный при регистрации приложения.
|
||||
* - ``redirect_uri``
|
||||
- Точный адрес, использованный для переадресации пользователя.
|
||||
* - ``grant_type``
|
||||
- В данном случае указывается ``authorization_code``.
|
||||
* - ``code``
|
||||
- Код авторизации, полученный в GET-параметрах после успешной переадресации.
|
||||
|
||||
**Пример реализации обмена на PHP:**
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// В этой переменной будут храниться ваши параметры OAuth2
|
||||
$oauthParams = [
|
||||
'client_id' => 'ely', // Ваш ClientId, полученный при регистрации
|
||||
'client_secret' => 'Pk4uCtZw5WVlSUpvteJuTZkVqHXZ6aNtTaLPXa7X', // Ваш ClientSecret, полученный при регистрации
|
||||
'redirect_uri' => 'http://someresource.by/oauth/some.php', // Адрес, на который вы ожидаете получить пользователя обратно (текущий url)
|
||||
'grant_type' => 'authorization_code',
|
||||
];
|
||||
|
||||
// Если возникла ошибка, то прерываем выполнение скрипта
|
||||
if (isset($_GET['error'])) {
|
||||
echo $_GET['error_message'];
|
||||
return;
|
||||
}
|
||||
|
||||
// Выполняем код ниже только если пришёл код авторизации
|
||||
if (!is_null($_GET['code'])) {
|
||||
$oauthParams['code'] = $_GET['code'];
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, 'https://account.ely.by/api/oauth2/v1/token');
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_POST, true);
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($oauthParams));
|
||||
$out = json_decode(curl_exec($curl), true);
|
||||
curl_close($curl);
|
||||
}
|
||||
|
||||
Пояснение к коду:
|
||||
|
||||
* Сначала мы объявляем переменную ``$oauthParams``, в которую заносим значения, полученные после регистрации приложения.
|
||||
|
||||
* Затем проверяем, не возникла-ли ошибка. В этом случае сразу же прерываем выполнение.
|
||||
|
||||
* Формируем POST запрос к форме обмена ``code`` на ``access_token``, передавая необходимые поля.
|
||||
|
||||
* Выполняем запрос, получаем ответ, переводим его из JSON в ассоциативный массив.
|
||||
|
||||
.. _authorization-code-grant-response:
|
||||
|
||||
Ответ сервера
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
В случае успешного запроса в теле ответа будет находиться результат обмена кода авторизации на ``access_token``. Данные являются JSON документом и могут быть легко интерпретированы средствами используемого языка программирования.
|
||||
|
||||
Тело JSON документа содержит следующие поля:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"access_token": "4qlktsEiwgspKEAotazem0APA99Ee7E6jNryVBrZ",
|
||||
"refresh_token": "m0APA99Ee7E6jNryVBrZ4qlktsEiwgspKEAotaze", // Представлен только в случае запроса с правами offline_access
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 86400 // Количество секунд, на которое выдан токен
|
||||
}
|
||||
|
||||
На этом процедура авторизации закончена. Полученный ``access_token`` может быть использован для получения информации о пользователе и взаимодействия с нашим API.
|
||||
|
||||
Получение информации о пользователе
|
||||
===================================
|
||||
|
||||
Если полученный токен имеет scope ``account_info``, то вы можете запросить информацию об аккаунте пользователя. Для этого необходимо отправить запрос на URL:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
https://account.ely.by/api/account/v1/info
|
||||
|
||||
Для передачи ``access_token`` используется заголовок ``Authorization`` со значением ``Bearer {access_token}``.
|
||||
|
||||
**Пример реализации получения информации о пользователе на PHP:**
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$accessToken = 'some_access_token_value';
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, 'https://account.ely.by/api/account/v1/info');
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer ' . $accessToken,
|
||||
]);
|
||||
$result = json_decode(curl_exec($curl), true);
|
||||
curl_close($curl);
|
||||
|
||||
В ответ вы получите JSON документ со следующим содержимым:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "ffc8fdc9-5824-509e-8a57-c99b940fb996",
|
||||
"username": "ErickSkrauch",
|
||||
"registeredAt": 1470566470,
|
||||
"profileLink": "http:\/\/ely.by\/u1",
|
||||
"preferredLanguage": "be",
|
||||
"email": "erickskrauch@ely.by"
|
||||
}
|
||||
|
||||
Обратите внимание, что поле ``email`` будет присутствовать лишь в том случае, когда был запрошен scope ``account_email``.
|
||||
|
||||
.. note:: В ходе дальнейшего развития сервиса, количество возвращаемых полей может увеличиться, но уже существующие останутся теми же.
|
||||
|
||||
.. _refresh-token-grant:
|
||||
|
||||
Обновление токена доступа
|
||||
=========================
|
||||
|
||||
Если при выполнении авторизации вами было запрошено право на получение scope ``offline_access``, то вместе с ``access_token`` вы также получите и ``refresh_token``. Данный токен не истекает и может быть использован для получения нового токена доступа, когда он истечёт.
|
||||
|
||||
Для выполнения операции обновления токена необходимо отправить POST запрос на тот же URL, что использовался и `при обмене кода на ключ доступа <#authorization-code-grant>`_, но со следующими параметрами:
|
||||
|
||||
.. list-table::
|
||||
:widths: 1 99
|
||||
:header-rows: 0
|
||||
|
||||
* - ``client_id``
|
||||
- ClientID, полученный при регистрации приложения.
|
||||
* - ``client_secret``
|
||||
- ClientSecret, полученный при регистрации приложения.
|
||||
* - ``scope``
|
||||
- Те же scope, что были запрошены и при получении начального токена доступа. Попытка запросить большее количество прав приведёт к ошибке.
|
||||
* - ``refresh_token``
|
||||
- Непосредственно токен, полученный вместе с начальным токеном доступа.
|
||||
|
||||
**Пример реализации обновления токена доступа на PHP:**
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// refresh_token, полученный при завершении авторизации
|
||||
$refreshToken = 'm0APA99Ee7E6jNryVBrZ4qlktsEiwgspKEAotaze';
|
||||
|
||||
$requestParams = [
|
||||
'client_id' => 'ely', // Ваш ClientId, полученный при регистрации
|
||||
'client_secret' => 'Pk4uCtZw5WVlSUpvteJuTZkVqHXZ6aNtTaLPXa7X', // Ваш ClientSecret, полученный при регистрации
|
||||
'scope' => 'account_info account_email',
|
||||
'refresh_token' => $refreshToken,
|
||||
'grant_type' => 'refresh_token',
|
||||
];
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, 'https://account.ely.by/api/oauth2/v1/token');
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_POST, true);
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($requestParams));
|
||||
$result = json_decode(curl_exec($curl), true);
|
||||
curl_close($curl);
|
||||
|
||||
В качестве ответа будет точно такое же тело, какое было получено в результате `обмена кода на ключ доступа <#authorization-code-grant-response>`_. Поле ``refresh_token`` будет отсутствовать.
|
||||
|
||||
Готовые библиотеки
|
||||
==================
|
||||
|
||||
Более простым способом будет использовать уже готовую библиотеку, которой будет необходимо передать лишь регистрационные параметры. Ниже перечислены библиотеки для различных языков программирования. Вы можете дополнить этот список своей библиотекой.
|
||||
|
||||
* **PHP**:
|
||||
|
||||
- [Официальная] https://github.com/elyby/league-oauth2-provider
|
||||
|
||||
* **Ruby**:
|
||||
|
||||
- [Официальная] https://github.com/elyby/omniauth-ely
|
||||
|
||||
* **Node.js**:
|
||||
|
||||
- [Сообщество] https://github.com/GGSkyOne/elyby
|
||||
|
||||
Возможные ошибки
|
||||
================
|
||||
|
||||
Ниже приведены стандартные ошибки, которые вы можете получить в случае неправильной передачи данных на сервер авторизации. Если вы столкнулись с ошибкой, не описанной в этой документации, пожалуйста, сообщите о ней через `форму обратной связи <https://ely.by/site/contact>`_.
|
||||
|
||||
.. _auth-start-errors:
|
||||
|
||||
Ошибки при инициализации авторизации
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Этот раздел описывает ошибки, отображаемые при переадресации пользователя с вашего сайта на нашу страницу инициализации авторизации.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Invalid request ({parameter} required).
|
||||
|
||||
Данная ошибка означает, что вы передали не все необходимые параметры. Чтобы решить эту ошибку просто добавьте недостающий параметр.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Invalid response type '{invalid_response_type_value}'.
|
||||
|
||||
Данная ошибка означает, что вы передали неподдерживаемый тип ``response_type``. На данный момент поддерживается только значение ``code``.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Invalid scope '{invalid_scope}'.
|
||||
|
||||
Ошибка указывает на то, что было запрошено неизвестный ``scope``. Убедитесь, что вы запрашиваете `поддерживаемые права <#available-scopes>`_.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
Can not find application you are trying to authorize.
|
||||
|
||||
Данная ошибка говорит о том, что переданные параметры не соответствуют ни одному из зарегистрированных приложений. Для решения проблемы исправьте ваши значения ``client_id`` и ``redirect_uri``.
|
||||
|
||||
.. _issue-token-errors:
|
||||
|
||||
Ошибки при обмене кода на ключ
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
В случае возникновения ошибки вместо ожидаемого ответа с ``200`` статусом вы получите ``40x`` код и следующие 2 поля:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Check the \"code\" parameter."
|
||||
}
|
||||
|
||||
В поле ``error`` находится системный идентификатор ошибки, в ``error_description`` — описание ошибки на английском языке.
|
||||
|
||||
**Возможные значения error:**
|
||||
|
||||
.. list-table::
|
||||
:widths: 1 99
|
||||
:header-rows: 0
|
||||
|
||||
* - ``invalid_request``
|
||||
- Переданы не все необходимые параметры запроса или значение ``code`` не был найден в базе выданных кодов.
|
||||
* - ``unsupported_grant_type``
|
||||
- Данная ошибка сигнализирует о том, что вы попытались произвести авторизацию по неизвестному для нашего OAuth2 сервера типу Grant.
|
||||
* - ``invalid_client``
|
||||
- Эта ошибка возникает в случае, когда трио значений ``client_id``, ``client_secret`` и ``redirect_uri`` не совпали ни с одним из зарегистрированных приложений.
|
||||
|
||||
Ошибки при запросе информации о пользователе
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Ответ со статусом ``401`` указывает на то, что заголовок ``Authorization`` не присутствует в запросе или его значение сформировано неверно. Тело ответа будет следующим:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name": "Unauthorized",
|
||||
"status": 401,
|
||||
"message": "Your request was made with invalid credentials."
|
||||
}
|
||||
|
||||
Ответ со статусом ``403`` сигнализирует о том, что переданный в заголовке ``Authorization`` токен не содержит scope ``account_info`` или он истёк. Получаемый ответ будет иметь следующий формат:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name": "Forbidden",
|
||||
"status": 403,
|
||||
"message": "You are not allowed to perform this action."
|
||||
}
|
||||
|
||||
Ошибки при обновлении токена доступа
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
При выполнении обновления токена доступа вам могут встретиться те же ошибки, что и при `обмене кода на ключ доступа <#issue-token-errors>`_, а также несколько новых:
|
||||
|
||||
.. list-table::
|
||||
:widths: 1 99
|
||||
:header-rows: 0
|
||||
|
||||
* - ``invalid_request``
|
||||
- Переданы не все необходимые параметры запроса или значение ``refresh_token`` не был найден в базе выданных токенов.
|
||||
* - ``invalid_scope``
|
||||
- Были перечислены неподдерживаемые scope или запрошено больше, чем было у изначального токена.
|
@ -1,168 +0,0 @@
|
||||
Система скинов
|
||||
--------------
|
||||
|
||||
На этой странице вы найдёте информацию о доступных запросах к сервису системы скинов Ely.by. Вы можете использовать любой из них как дополнительный или основной источник скинов для своего проекта.
|
||||
|
||||
Сервис системы скинов Ely.by обеспечивает `проксирование текстур владельцев лицензии Minecraft <#textures-proxy>`_, что означает, что при использовании этого сервиса игроки будут видеть как скины премиум пользователей Minecraft, так и скины пользователей сервиса Ely.by.
|
||||
|
||||
Мы стремимся соответствовать официальной системе скинов и не поддерживаем ушки и HD-скины. Система поддерживает плащи, но не позволяет игрокам самостоятельно их надевать.
|
||||
|
||||
Если у вас есть предложения по развитию существующего функционала, пожалуйста, `создайте новый Issue <https://github.com/elyby/chrly/issues/new>`_ в `репозитории проекта Chrly <https://github.com/elyby/chrly>`_.
|
||||
|
||||
.. note:: Вы можете найти более подробную информацию о реализации сервера системы скинов в `репозитории проекта Chrly <https://github.com/elyby/chrly>`_.
|
||||
|
||||
URL-адреса запросов
|
||||
===================
|
||||
|
||||
Система скинов размещена на домене ``http://skinsystem.ely.by``.
|
||||
|
||||
Во всех запросах параметр ``nickname`` должен быть заменён на ник игрока. Значение не чувствительно к регистру.
|
||||
|
||||
.. _skin-request:
|
||||
.. function:: /skins/{nickname}.png
|
||||
|
||||
URL для загрузки текстуры скина. Расширение ``.png`` опционально. Если текстура не будет найдена, сервер вернёт ответ с ``404`` статусом.
|
||||
|
||||
.. _cape-request:
|
||||
.. function:: /cloaks/{nickname}.png
|
||||
|
||||
URL для загрузки текстуры плаща. Расширение ``.png`` опционально. Если текстура не будет найдена, сервер вернёт ответ с ``404`` статусом.
|
||||
|
||||
.. function:: /textures/{nickname}
|
||||
|
||||
По этому URL вы можете получить текстуры в формате, указанному в поле ``textures`` одноимённого property в `ответе на запрос подписанных текстур <https://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape>`_:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"SKIN": {
|
||||
"url": "http://example.com/skin.png",
|
||||
"metadata": {
|
||||
"model": "slim"
|
||||
}
|
||||
},
|
||||
"CAPE": {
|
||||
"url": "http://example.com/cape.png"
|
||||
}
|
||||
}
|
||||
|
||||
В зависимости от доступных игроку текстур могут отсутствовать поля ``SKIN`` или ``CAPE``. Если модель скина не является ``slim``, то поле ``metadata`` также будет отсутствовать.
|
||||
|
||||
Если текстуры не будут найдены, сервер вернёт пустой ответ с ``204`` статусом.
|
||||
|
||||
.. function:: /profile/{nickname}
|
||||
|
||||
Данный запрос является аналогом запроса `профиля игрока в API Mojang <https://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape>`_, только вместо идентификации пользователя по UUID используется его ник. Также, как и в API Mojang, вы можете добавить к запросу ``?unsigned=false``, чтобы получить текстуры с подписью. В ответе также будет присутствовать дополнительное property с ``name`` равным **ely**.
|
||||
|
||||
Если у пользователя нет текстур, то они будут запрошены через прокси Mojang, после чего переподписаны с использованием `нашего ключа подписи <#signature-verification-key-request>`_.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"id": "ffc8fdc95824509e8a57c99b940fb996",
|
||||
"name": "ErickSkrauch",
|
||||
"properties": [
|
||||
{
|
||||
"name": "textures",
|
||||
"signature": "eks3dLJWzod92dLfWH6Z8uc6l3+IvrZtTj3zjwnj0AdVt44ODKoL50N+RabYxf7zF3C7tlJwT1oAtydONrxXUarqUlpVeQzLlfsuqUKBLi0L+/Y9yQLG3AciNqzEWq3hYaOsJrsaJday/hQmKFnpXEFCThTMpSuZhoAZIiH4VG48NhP70U93ejyXF9b1nPYnXP6k7BVB8LYSzcjZfdqY88jQJbbvRzOyX14ZSD0Ma92jceLNKmkTVc2UfRLUNXtQKtVSFUzlAjCXPJW89IIOZTRqLg65qstWwBvn6VuikyUB5EIxM8vuCh7zTkrMOx1v2Q0xIj8YSFcbnBH2bo87SYOIe1bOK57ZEeUJqY6uSgMlWs7dI5D3nmhFptErm72hg55Axdo1xbG4mvnmLYF7SA4yMDSytPPL+kA+sw3pafnvU2IZo38gqJSDOOpkOpdhUoHx85fzRJL8AcLSJiFlCZDl4pSi3cVuKy/xY5ohT/fJ6GEqpbZp3gACymn47zzI42VSh6j1DQnx2wnhqalTv0kE3qpAFpK/htSboQkFCW/bULO3b+vgU87XPlReT7UtH4yGLtixgs5GC8AzBraN8vOMv8TZCX9ab6mBBjOoDJjXa8Tq637TC75GxRHlpAN2jRHYvyp2zJwjUrML3u4eD4osHW+VBfl8D2l3nLJuemQ=",
|
||||
"value": "eyJ0aW1lc3RhbXAiOjE2MTQ5MzczMjc0MzcsInByb2ZpbGVJZCI6ImZmYzhmZGM5NTgyNDUwOWU4YTU3Yzk5Yjk0MGZiOTk2IiwicHJvZmlsZU5hbWUiOiJFcmlja1NrcmF1Y2giLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly9lbHkuYnkvc3RvcmFnZS9za2lucy82OWM2NzQwZDI5OTNlNWQ2ZjZhN2ZjOTI0MjBlZmMyOS5wbmcifX19"
|
||||
},
|
||||
{
|
||||
"name": "ely",
|
||||
"value": "but why are you asking?"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Если запрошенный никнейм не будет найден ни в локальном хранилище, ни у Mojang, сервер вернёт пустой ответ с ``204`` статусом.
|
||||
|
||||
.. _signature-verification-key-request:
|
||||
.. function:: /signature-verification-key.der
|
||||
|
||||
Данный запрос возвращает публичный ключ, который может быть использован для проверки подписи текстур. Ключ предоставляется в формате ``DER``. Этот формат используется внутри Authlib, поэтому ключ может быть в ней использован без модификации алгоритма проверки подписи.
|
||||
|
||||
.. function:: /signature-verification-key.pem
|
||||
|
||||
Такой же запрос, что и предыдущий, но возвращает ключ в формате ``PEM``.
|
||||
|
||||
.. function:: /textures/signed/{nickname}
|
||||
|
||||
Этот запрос используется в нашем `плагине серверной системы скинов <https://ely.by/server-skins-system>`_ для загрузки текстур с оригинальной подписью Mojang. Полученные в ответе текстуры могут быть без изменений переданы в немодифицированный игровой клиент. В ответе также будет присутствовать дополнительное property с ``name`` равным **ely**.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"id": "ffc8fdc95824509e8a57c99b940fb996",
|
||||
"name": "ErickSkrauch",
|
||||
"properties": [
|
||||
{
|
||||
"name": "textures",
|
||||
"signature": "QH+1rlQJYk8tW+8WlSJnzxZZUL5RIkeOO33dq84cgNoxwCkzL95Zy5pbPMFhoiMXXablqXeqyNRZDQa+OewgDBSZxm0BmkNmwdTLzCPHgnlNYhwbO4sirg3hKjCZ82ORZ2q7VP2NQIwNvc3befiCakhDlMWUuhjxe7p/HKNtmKA7a/JjzmzwW7BWMv8b88ZaQaMaAc7puFQcu2E54G2Zk2kyv3T1Bm7bV4m7ymbL8McOmQc6Ph7C95/EyqIK1a5gRBUHPEFIEj0I06YKTHsCRFU1U/hJpk98xXHzHuULJobpajqYXuVJ8QEVgF8k8dn9VkS8BMbXcjzfbb6JJ36v7YIV6Rlt75wwTk2wr3C3P0ij55y0iXth1HjwcEKsg54n83d9w8yQbkUCiTpMbOqxTEOOS7G2O0ZDBJDXAKQ4n5qCiCXKZ4febv4+dWVQtgfZHnpGJUD3KdduDKslMePnECOXMjGSAOQou//yze2EkL2rBpJtAAiOtvBlm/aWnDZpij5cQk+pWmeHWZIf0LSSlsYRUWRDk/VKBvUTEAO9fqOxWqmSgQRUY2Ea56u0ZsBb4vEa1UY6mlJj3+PNZaWu5aP2E9Unh0DIawV96eW8eFQgenlNXHMmXd4aOra4sz2eeOnY53JnJP+eVE4cB1hlq8RA2mnwTtcy3lahzZonOWc=",
|
||||
"value": "eyJ0aW1lc3RhbXAiOjE0ODYzMzcyNTQ4NzIsInByb2ZpbGVJZCI6ImM0ZjFlNTZmNjFkMTQwYTc4YzMyOGQ5MTY2ZWVmOWU3IiwicHJvZmlsZU5hbWUiOiJXaHlZb3VSZWFkVGhpcyIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS83Mzk1NmE4ZTY0ZWU2ZDhlYzY1NmFkYmI0NDA0ZjhlYmZmMzQxMWIwY2I5MGIzMWNiNDc2ZWNiOTk2ZDNiOCJ9fX0="
|
||||
},
|
||||
{
|
||||
"name": "ely",
|
||||
"value": "but why are you asking?"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
По умолчанию для этого запроса не применяется проксирование текстур. Чтобы его включить, добавьте дополнительный GET параметр ``?proxy=true``.
|
||||
|
||||
Если текстуры не будут найдены, сервер вернёт пустой ответ с ``204`` статусом.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
При совершении любого из вышеописанных запросов вы также можете передать ряд дополнительных GET параметров. Они будут использованы для анализа использования сервиса разными версиями игры.
|
||||
|
||||
:version: Версия протокола, по которому идёт запрос на скины. На данный момент это версия ``2`` , т.е. вам необходимо указать ``version=2``.
|
||||
|
||||
:minecraft_version: Версия Minecraft, с которой идёт запрос.
|
||||
|
||||
:authlib_version: Версия используемой Authlib. Этот параметр актуален для версий Minecraft 1.7.6+, где для загрузки скинов стала использоваться отдельная библиотека, а не внутриигровой код.
|
||||
|
||||
Пример запроса текстур с передачей вышеописанных параметров:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
http://skinsystem.ely.by/textures/erickskrauch?version=2&minecraft_version=1.14.0&authlib_version=1.5.25
|
||||
|
||||
Вспомогательные URL
|
||||
+++++++++++++++++++
|
||||
|
||||
Также запрос скина и плаща можно выполнить, передавая ник через GET параметр. Эта возможность используется для передачи аналитических параметров в версиях игры до 1.5.2, когда ник просто добавлялся в конец строки. Для этого вся строка выстраивается таким образом, чтобы последним параметром шёл ``name``, после добавления ника к которому получался полный запрос на текстуру.
|
||||
|
||||
.. function:: /skins?name={nickname}.png
|
||||
|
||||
Смотрите `запрос на получение скина <#skin-request>`_.
|
||||
|
||||
.. function:: /cloaks?name={nickname}.png
|
||||
|
||||
Смотрите `запрос на получение плаща <#cape-request>`_.
|
||||
|
||||
Пример запросов на текстуры с передачей параметров выше:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
http://skinsystem.ely.by/skins?version=2&minecraft_version=1.5.2&name=erickskrauch.png
|
||||
http://skinsystem.ely.by/cloaks?version=2&minecraft_version=1.4.7&name=notch
|
||||
|
||||
.. _textures-proxy:
|
||||
|
||||
Проксирование текстур
|
||||
=====================
|
||||
|
||||
Сервис системы скинов Ely.by получает текстуры из официальной системы скинов в случае, если в базе данных не было найдено информации о текстурах для запрошенного имени пользователя. Также запрос будет проксирован, если запись о скине будет найдена, но он будет стандартным.
|
||||
|
||||
Для улучшения пропускной способности проксирующего алгоритма, информация о текстурах кешируется в 2 стадии:
|
||||
|
||||
* Соответствие ника и UUID хранится в `течение 30 дней <https://help.minecraft.net/hc/en-us/articles/360034636712-Minecraft-Usernames#article-container:~:text=How%20often%20can%20I%20change%20my%20username%3F>`_.
|
||||
|
||||
* Информация о текстурах обновляется не чаще `раза в минуту <https://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape:~:text=You%20can%20request%20the%20same%20profile%20once%20per%20minute>`_.
|
||||
|
||||
Если вы владеете лицензионным аккаунтом Minecraft, но ваш ник занят, пожалуйста, обратитесь в `службу поддержки <https://ely.by/site/contact>`_ и после небольшой проверки мы передадим ник в ваше пользование.
|
||||
|
||||
Готовые реализации
|
||||
==================
|
||||
|
||||
Готовые реализации патчей и инструкции по их установке могут быть найдены в `разделе загрузок на главном сайте Ely.by <https://ely.by/load>`_.
|
70
src/components/HomepageFeatures/index.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import clsx from 'clsx';
|
||||
import Heading from '@theme/Heading';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
type FeatureItem = {
|
||||
title: string;
|
||||
Svg: React.ComponentType<React.ComponentProps<'svg'>>;
|
||||
description: JSX.Element;
|
||||
};
|
||||
|
||||
const FeatureList: FeatureItem[] = [
|
||||
{
|
||||
title: 'Easy to Use',
|
||||
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Docusaurus was designed from the ground up to be easily installed and
|
||||
used to get your website up and running quickly.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Focus on What Matters',
|
||||
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Docusaurus lets you focus on your docs, and we'll do the chores. Go
|
||||
ahead and move your docs into the <code>docs</code> directory.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Powered by React',
|
||||
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Extend or customize your website layout by reusing React. Docusaurus can
|
||||
be extended while reusing the same header and footer.
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
function Feature({title, Svg, description}: FeatureItem) {
|
||||
return (
|
||||
<div className={clsx('col col--4')}>
|
||||
<div className="text--center">
|
||||
<Svg className={styles.featureSvg} role="img" />
|
||||
</div>
|
||||
<div className="text--center padding-horiz--md">
|
||||
<Heading as="h3">{title}</Heading>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomepageFeatures(): JSX.Element {
|
||||
return (
|
||||
<section className={styles.features}>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
{FeatureList.map((props, idx) => (
|
||||
<Feature key={idx} {...props} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
11
src/components/HomepageFeatures/styles.module.css
Normal file
@ -0,0 +1,11 @@
|
||||
.features {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.featureSvg {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
30
src/css/custom.css
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Any CSS included here will be global. The classic template
|
||||
* bundles Infima by default. Infima is a CSS framework designed to
|
||||
* work well for content-centric websites.
|
||||
*/
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-color-primary: #2e8555;
|
||||
--ifm-color-primary-dark: #29784c;
|
||||
--ifm-color-primary-darker: #277148;
|
||||
--ifm-color-primary-darkest: #205d3b;
|
||||
--ifm-color-primary-light: #33925d;
|
||||
--ifm-color-primary-lighter: #359962;
|
||||
--ifm-color-primary-lightest: #3cad6e;
|
||||
--ifm-code-font-size: 95%;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||
[data-theme='dark'] {
|
||||
--ifm-color-primary: #25c2a0;
|
||||
--ifm-color-primary-dark: #21af90;
|
||||
--ifm-color-primary-darker: #1fa588;
|
||||
--ifm-color-primary-darkest: #1a8870;
|
||||
--ifm-color-primary-light: #29d5b0;
|
||||
--ifm-color-primary-lighter: #32d8b4;
|
||||
--ifm-color-primary-lightest: #4fddbf;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
}
|
23
src/pages/index.module.css
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* CSS files with the .module.css suffix will be treated as CSS modules
|
||||
* and scoped locally.
|
||||
*/
|
||||
|
||||
.heroBanner {
|
||||
padding: 4rem 0;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 996px) {
|
||||
.heroBanner {
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
43
src/pages/index.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import clsx from 'clsx';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import Layout from '@theme/Layout';
|
||||
import HomepageFeatures from '@site/src/components/HomepageFeatures';
|
||||
import Heading from '@theme/Heading';
|
||||
|
||||
import styles from './index.module.css';
|
||||
|
||||
function HomepageHeader() {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
return (
|
||||
<header className={clsx('hero hero--primary', styles.heroBanner)}>
|
||||
<div className="container">
|
||||
<Heading as="h1" className="hero__title">
|
||||
{siteConfig.title}
|
||||
</Heading>
|
||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
||||
<div className={styles.buttons}>
|
||||
<Link
|
||||
className="button button--secondary button--lg"
|
||||
to="/docs/intro">
|
||||
Docusaurus Tutorial - 5min ⏱️
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home(): JSX.Element {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
return (
|
||||
<Layout
|
||||
title={`Hello from ${siteConfig.title}`}
|
||||
description="Description will go into a meta tag in <head />">
|
||||
<HomepageHeader />
|
||||
<main>
|
||||
<HomepageFeatures />
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
7
src/pages/markdown-page.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Markdown page example
|
||||
---
|
||||
|
||||
# Markdown page example
|
||||
|
||||
You don't need React to write simple standalone pages.
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
BIN
static/img/docusaurus-social-card.jpg
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
static/img/docusaurus.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
static/img/favicon.ico
Normal file
After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
1
static/img/logo.svg
Normal file
After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
171
static/img/undraw_docusaurus_mountain.svg
Normal file
@ -0,0 +1,171 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1088" height="687.962" viewBox="0 0 1088 687.962">
|
||||
<title>Easy to Use</title>
|
||||
<g id="Group_12" data-name="Group 12" transform="translate(-57 -56)">
|
||||
<g id="Group_11" data-name="Group 11" transform="translate(57 56)">
|
||||
<path id="Path_83" data-name="Path 83" d="M1017.81,560.461c-5.27,45.15-16.22,81.4-31.25,110.31-20,38.52-54.21,54.04-84.77,70.28a193.275,193.275,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.282,657.282,0,0,0-104.09-13.16q-14.97-.675-29.97-.67c-15.42.02-293.07,5.29-360.67-131.57-16.69-33.76-28.13-75-32.24-125.27-11.63-142.12,52.29-235.46,134.74-296.47,155.97-115.41,369.76-110.57,523.43,7.88C941.15,276.621,1036.99,396.031,1017.81,560.461Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||
<path id="Path_84" data-name="Path 84" d="M986.56,670.771c-20,38.52-47.21,64.04-77.77,80.28a193.272,193.272,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.3,657.3,0,0,0-104.09-13.16q-14.97-.675-29.97-.67-23.13.03-46.25,1.72c-100.17,7.36-253.82-6.43-321.42-143.29L382,283.981,444.95,445.6l20.09,51.59,55.37-75.98L549,381.981l130.2,149.27,36.8-81.27L970.78,657.9l14.21,11.59Z" transform="translate(-56 -106.019)" fill="#f2f2f2"/>
|
||||
<path id="Path_85" data-name="Path 85" d="M302,282.962l26-57,36,83-31-60Z" opacity="0.1"/>
|
||||
<path id="Path_86" data-name="Path 86" d="M610.5,753.821q-14.97-.675-29.97-.67L465.04,497.191Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
||||
<path id="Path_87" data-name="Path 87" d="M464.411,315.191,493,292.962l130,150-132-128Z" opacity="0.1"/>
|
||||
<path id="Path_88" data-name="Path 88" d="M908.79,751.051a193.265,193.265,0,0,1-27.46,11.94L679.2,531.251Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
||||
<circle id="Ellipse_11" data-name="Ellipse 11" cx="3" cy="3" r="3" transform="translate(479 98.962)" fill="#f2f2f2"/>
|
||||
<circle id="Ellipse_12" data-name="Ellipse 12" cx="3" cy="3" r="3" transform="translate(396 201.962)" fill="#f2f2f2"/>
|
||||
<circle id="Ellipse_13" data-name="Ellipse 13" cx="2" cy="2" r="2" transform="translate(600 220.962)" fill="#f2f2f2"/>
|
||||
<circle id="Ellipse_14" data-name="Ellipse 14" cx="2" cy="2" r="2" transform="translate(180 265.962)" fill="#f2f2f2"/>
|
||||
<circle id="Ellipse_15" data-name="Ellipse 15" cx="2" cy="2" r="2" transform="translate(612 96.962)" fill="#f2f2f2"/>
|
||||
<circle id="Ellipse_16" data-name="Ellipse 16" cx="2" cy="2" r="2" transform="translate(736 192.962)" fill="#f2f2f2"/>
|
||||
<circle id="Ellipse_17" data-name="Ellipse 17" cx="2" cy="2" r="2" transform="translate(858 344.962)" fill="#f2f2f2"/>
|
||||
<path id="Path_89" data-name="Path 89" d="M306,121.222h-2.76v-2.76h-1.48v2.76H299V122.7h2.76v2.759h1.48V122.7H306Z" fill="#f2f2f2"/>
|
||||
<path id="Path_90" data-name="Path 90" d="M848,424.222h-2.76v-2.76h-1.48v2.76H841V425.7h2.76v2.759h1.48V425.7H848Z" fill="#f2f2f2"/>
|
||||
<path id="Path_91" data-name="Path 91" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||
<path id="Path_92" data-name="Path 92" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
||||
<ellipse id="Ellipse_18" data-name="Ellipse 18" cx="544" cy="30" rx="544" ry="30" transform="translate(0 583.962)" fill="#3f3d56"/>
|
||||
<path id="Path_93" data-name="Path 93" d="M624,677.981c0,33.137-14.775,24-33,24s-33,9.137-33-24,33-96,33-96S624,644.844,624,677.981Z" transform="translate(-56 -106.019)" fill="#ff6584"/>
|
||||
<path id="Path_94" data-name="Path 94" d="M606,690.66c0,15.062-6.716,10.909-15,10.909s-15,4.153-15-10.909,15-43.636,15-43.636S606,675.6,606,690.66Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
||||
<rect id="Rectangle_97" data-name="Rectangle 97" width="92" height="18" rx="9" transform="translate(489 604.962)" fill="#2f2e41"/>
|
||||
<rect id="Rectangle_98" data-name="Rectangle 98" width="92" height="18" rx="9" transform="translate(489 586.962)" fill="#2f2e41"/>
|
||||
<path id="Path_95" data-name="Path 95" d="M193,596.547c0,55.343,34.719,100.126,77.626,100.126" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||
<path id="Path_96" data-name="Path 96" d="M270.626,696.673c0-55.965,38.745-101.251,86.626-101.251" transform="translate(-56 -106.019)" fill="#6c63ff"/>
|
||||
<path id="Path_97" data-name="Path 97" d="M221.125,601.564c0,52.57,22.14,95.109,49.5,95.109" transform="translate(-56 -106.019)" fill="#6c63ff"/>
|
||||
<path id="Path_98" data-name="Path 98" d="M270.626,696.673c0-71.511,44.783-129.377,100.126-129.377" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||
<path id="Path_99" data-name="Path 99" d="M254.3,697.379s11.009-.339,14.326-2.7,16.934-5.183,17.757-1.395,16.544,18.844,4.115,18.945-28.879-1.936-32.19-3.953S254.3,697.379,254.3,697.379Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
||||
<path id="Path_100" data-name="Path 100" d="M290.716,710.909c-12.429.1-28.879-1.936-32.19-3.953-2.522-1.536-3.527-7.048-3.863-9.591l-.368.014s.7,8.879,4.009,10.9,19.761,4.053,32.19,3.953c3.588-.029,4.827-1.305,4.759-3.2C294.755,710.174,293.386,710.887,290.716,710.909Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
||||
<path id="Path_101" data-name="Path 101" d="M777.429,633.081c0,38.029,23.857,68.8,53.341,68.8" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||
<path id="Path_102" data-name="Path 102" d="M830.769,701.882c0-38.456,26.623-69.575,59.525-69.575" transform="translate(-56 -106.019)" fill="#6c63ff"/>
|
||||
<path id="Path_103" data-name="Path 103" d="M796.755,636.528c0,36.124,15.213,65.354,34.014,65.354" transform="translate(-56 -106.019)" fill="#6c63ff"/>
|
||||
<path id="Path_104" data-name="Path 104" d="M830.769,701.882c0-49.139,30.773-88.9,68.8-88.9" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||
<path id="Path_105" data-name="Path 105" d="M819.548,702.367s7.565-.233,9.844-1.856,11.636-3.562,12.2-.958,11.368,12.949,2.828,13.018-19.844-1.33-22.119-2.716S819.548,702.367,819.548,702.367Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
||||
<path id="Path_106" data-name="Path 106" d="M844.574,711.664c-8.54.069-19.844-1.33-22.119-2.716-1.733-1.056-2.423-4.843-2.654-6.59l-.253.01s.479,6.1,2.755,7.487,13.579,2.785,22.119,2.716c2.465-.02,3.317-.9,3.27-2.2C847.349,711.159,846.409,711.649,844.574,711.664Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
||||
<path id="Path_107" data-name="Path 107" d="M949.813,724.718s11.36-1.729,14.5-4.591,16.89-7.488,18.217-3.667,19.494,17.447,6.633,19.107-30.153,1.609-33.835-.065S949.813,724.718,949.813,724.718Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
||||
<path id="Path_108" data-name="Path 108" d="M989.228,734.173c-12.86,1.659-30.153,1.609-33.835-.065-2.8-1.275-4.535-6.858-5.2-9.45l-.379.061s1.833,9.109,5.516,10.783,20.975,1.725,33.835.065c3.712-.479,4.836-1.956,4.529-3.906C993.319,732.907,991.991,733.817,989.228,734.173Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
||||
<path id="Path_109" data-name="Path 109" d="M670.26,723.9s9.587-1.459,12.237-3.875,14.255-6.32,15.374-3.095,16.452,14.725,5.6,16.125-25.448,1.358-28.555-.055S670.26,723.9,670.26,723.9Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
||||
<path id="Path_110" data-name="Path 110" d="M703.524,731.875c-10.853,1.4-25.448,1.358-28.555-.055-2.367-1.076-3.827-5.788-4.39-7.976l-.32.051s1.547,7.687,4.655,9.1,17.7,1.456,28.555.055c3.133-.4,4.081-1.651,3.822-3.3C706.977,730.807,705.856,731.575,703.524,731.875Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
||||
<path id="Path_111" data-name="Path 111" d="M178.389,719.109s7.463-1.136,9.527-3.016,11.1-4.92,11.969-2.409,12.808,11.463,4.358,12.553-19.811,1.057-22.23-.043S178.389,719.109,178.389,719.109Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
|
||||
<path id="Path_112" data-name="Path 112" d="M204.285,725.321c-8.449,1.09-19.811,1.057-22.23-.043-1.842-.838-2.979-4.506-3.417-6.209l-.249.04s1.2,5.984,3.624,7.085,13.781,1.133,22.23.043c2.439-.315,3.177-1.285,2.976-2.566C206.973,724.489,206.1,725.087,204.285,725.321Z" transform="translate(-56 -106.019)" opacity="0.2"/>
|
||||
<path id="Path_113" data-name="Path 113" d="M439.7,707.337c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873,42.118-36.793,93.694-36.793S439.7,677.117,439.7,707.337Z" transform="translate(-56 -106.019)" opacity="0.1"/>
|
||||
<path id="Path_114" data-name="Path 114" d="M439.7,699.9c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873S295.04,663.1,346.616,663.1,439.7,669.676,439.7,699.9Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||
</g>
|
||||
<g id="docusaurus_keytar" transform="translate(312.271 493.733)">
|
||||
<path id="Path_40" data-name="Path 40" d="M99,52h91.791V89.153H99Z" transform="translate(5.904 -14.001)" fill="#fff" fill-rule="evenodd"/>
|
||||
<path id="Path_41" data-name="Path 41" d="M24.855,163.927A21.828,21.828,0,0,1,5.947,153a21.829,21.829,0,0,0,18.908,32.782H46.71V163.927Z" transform="translate(-3 -4.634)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||
<path id="Path_42" data-name="Path 42" d="M121.861,61.1l76.514-4.782V45.39A21.854,21.854,0,0,0,176.52,23.535H78.173L75.441,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L64.513,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L53.586,18.8a3.154,3.154,0,0,0-5.464,0L45.39,23.535c-.024,0-.046,0-.071,0l-4.526-4.525a3.153,3.153,0,0,0-5.276,1.414l-1.5,5.577-5.674-1.521a3.154,3.154,0,0,0-3.863,3.864L26,34.023l-5.575,1.494a3.155,3.155,0,0,0-1.416,5.278l4.526,4.526c0,.023,0,.046,0,.07L18.8,48.122a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,59.05a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,69.977a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,80.9a3.154,3.154,0,0,0,0,5.464L23.535,89.1,18.8,91.832a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,102.76a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,113.687a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,124.615a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,135.542a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,146.469a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,157.4a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,168.324a3.154,3.154,0,0,0,0,5.464l4.732,2.732A21.854,21.854,0,0,0,45.39,198.375H176.52a21.854,21.854,0,0,0,21.855-21.855V89.1l-76.514-4.782a11.632,11.632,0,0,1,0-23.219" transform="translate(-1.681 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||
<path id="Path_43" data-name="Path 43" d="M143,186.71h32.782V143H143Z" transform="translate(9.984 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||
<path id="Path_44" data-name="Path 44" d="M196.71,159.855a5.438,5.438,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(10.912 -6.025)" fill="#44d860" fill-rule="evenodd"/>
|
||||
<path id="Path_45" data-name="Path 45" d="M153,124.855h32.782V103H153Z" transform="translate(10.912 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||
<path id="Path_46" data-name="Path 46" d="M194.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.814,2.814,0,0,0,.349.035" transform="translate(12.767 -9.377)" fill="#44d860" fill-rule="evenodd"/>
|
||||
<path id="Path_47" data-name="Path 47" d="M65.087,56.891a2.732,2.732,0,0,1-2.732-2.732,8.2,8.2,0,0,0-16.391,0,2.732,2.732,0,0,1-5.464,0,13.659,13.659,0,0,1,27.319,0,2.732,2.732,0,0,1-2.732,2.732" transform="translate(0.478 -15.068)" fill-rule="evenodd"/>
|
||||
<path id="Path_48" data-name="Path 48" d="M103,191.347h65.565a21.854,21.854,0,0,0,21.855-21.855V93H124.855A21.854,21.854,0,0,0,103,114.855Z" transform="translate(6.275 -10.199)" fill="#ffff50" fill-rule="evenodd"/>
|
||||
<path id="Path_49" data-name="Path 49" d="M173.216,129.787H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0-54.434H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.652H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186M189.585,61.611c-.013,0-.024-.007-.037-.005-3.377.115-4.974,3.492-6.384,6.472-1.471,3.114-2.608,5.139-4.473,5.078-2.064-.074-3.244-2.406-4.494-4.874-1.436-2.835-3.075-6.049-6.516-5.929-3.329.114-4.932,3.053-6.346,5.646-1.5,2.762-2.529,4.442-4.5,4.364-2.106-.076-3.225-1.972-4.52-4.167-1.444-2.443-3.112-5.191-6.487-5.1-3.272.113-4.879,2.606-6.3,4.808-1.5,2.328-2.552,3.746-4.551,3.662-2.156-.076-3.27-1.65-4.558-3.472-1.447-2.047-3.077-4.363-6.442-4.251-3.2.109-4.807,2.153-6.224,3.954-1.346,1.709-2.4,3.062-4.621,2.977a1.093,1.093,0,0,0-.079,2.186c3.3.11,4.967-1.967,6.417-3.81,1.286-1.635,2.4-3.045,4.582-3.12,2.1-.09,3.091,1.218,4.584,3.327,1.417,2,3.026,4.277,6.263,4.394,3.391.114,5.022-2.42,6.467-4.663,1.292-2,2.406-3.734,4.535-3.807,1.959-.073,3.026,1.475,4.529,4.022,1.417,2.4,3.023,5.121,6.324,5.241,3.415.118,5.064-2.863,6.5-5.5,1.245-2.282,2.419-4.437,4.5-4.509,1.959-.046,2.981,1.743,4.492,4.732,1.412,2.79,3.013,5.95,6.365,6.071l.185,0c3.348,0,4.937-3.36,6.343-6.331,1.245-2.634,2.423-5.114,4.444-5.216Z" transform="translate(7.109 -13.11)" fill-rule="evenodd"/>
|
||||
<path id="Path_50" data-name="Path 50" d="M83,186.71h43.71V143H83Z" transform="translate(4.42 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 109.327, 91.085)">
|
||||
<rect id="Rectangle_3" data-name="Rectangle 3" width="92.361" height="36.462" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
|
||||
<g id="Group_2" data-name="Group 2" transform="translate(1.531 23.03)">
|
||||
<rect id="Rectangle_4" data-name="Rectangle 4" width="5.336" height="5.336" rx="1" transform="translate(16.797 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_5" data-name="Rectangle 5" width="5.336" height="5.336" rx="1" transform="translate(23.12 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_6" data-name="Rectangle 6" width="5.336" height="5.336" rx="1" transform="translate(29.444 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_7" data-name="Rectangle 7" width="5.336" height="5.336" rx="1" transform="translate(35.768 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_8" data-name="Rectangle 8" width="5.336" height="5.336" rx="1" transform="translate(42.091 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_9" data-name="Rectangle 9" width="5.336" height="5.336" rx="1" transform="translate(48.415 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_10" data-name="Rectangle 10" width="5.336" height="5.336" rx="1" transform="translate(54.739 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_11" data-name="Rectangle 11" width="5.336" height="5.336" rx="1" transform="translate(61.063 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_12" data-name="Rectangle 12" width="5.336" height="5.336" rx="1" transform="translate(67.386 0)" fill="#4a4a4a"/>
|
||||
<path id="Path_51" data-name="Path 51" d="M1.093,0H14.518a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0ZM75,0H88.426a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H75a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,75,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||
</g>
|
||||
<g id="Group_3" data-name="Group 3" transform="translate(1.531 10.261)">
|
||||
<path id="Path_52" data-name="Path 52" d="M1.093,0H6.218A1.093,1.093,0,0,1,7.31,1.093V4.242A1.093,1.093,0,0,1,6.218,5.335H1.093A1.093,1.093,0,0,1,0,4.242V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||
<rect id="Rectangle_13" data-name="Rectangle 13" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_14" data-name="Rectangle 14" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_15" data-name="Rectangle 15" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_16" data-name="Rectangle 16" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_17" data-name="Rectangle 17" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_18" data-name="Rectangle 18" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_19" data-name="Rectangle 19" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_20" data-name="Rectangle 20" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_21" data-name="Rectangle 21" width="5.336" height="5.336" rx="1" transform="translate(58.888 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_22" data-name="Rectangle 22" width="5.336" height="5.336" rx="1" transform="translate(65.212 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_23" data-name="Rectangle 23" width="5.336" height="5.336" rx="1" transform="translate(71.536 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_24" data-name="Rectangle 24" width="5.336" height="5.336" rx="1" transform="translate(77.859 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_25" data-name="Rectangle 25" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
|
||||
</g>
|
||||
<g id="Group_4" data-name="Group 4" transform="translate(91.05 9.546) rotate(180)">
|
||||
<path id="Path_53" data-name="Path 53" d="M1.093,0H6.219A1.093,1.093,0,0,1,7.312,1.093v3.15A1.093,1.093,0,0,1,6.219,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||
<rect id="Rectangle_26" data-name="Rectangle 26" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_27" data-name="Rectangle 27" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_28" data-name="Rectangle 28" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_29" data-name="Rectangle 29" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_30" data-name="Rectangle 30" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_31" data-name="Rectangle 31" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_32" data-name="Rectangle 32" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_33" data-name="Rectangle 33" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_34" data-name="Rectangle 34" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_35" data-name="Rectangle 35" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_36" data-name="Rectangle 36" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_37" data-name="Rectangle 37" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_38" data-name="Rectangle 38" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_39" data-name="Rectangle 39" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_40" data-name="Rectangle 40" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_41" data-name="Rectangle 41" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_42" data-name="Rectangle 42" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_43" data-name="Rectangle 43" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_44" data-name="Rectangle 44" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_45" data-name="Rectangle 45" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_46" data-name="Rectangle 46" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_47" data-name="Rectangle 47" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_48" data-name="Rectangle 48" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_49" data-name="Rectangle 49" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_50" data-name="Rectangle 50" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_51" data-name="Rectangle 51" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
|
||||
</g>
|
||||
<g id="Group_6" data-name="Group 6" transform="translate(1.531 16.584)">
|
||||
<path id="Path_54" data-name="Path 54" d="M1.093,0h7.3A1.093,1.093,0,0,1,9.485,1.093v3.15A1.093,1.093,0,0,1,8.392,5.336h-7.3A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||
<g id="Group_5" data-name="Group 5" transform="translate(10.671 0)">
|
||||
<rect id="Rectangle_52" data-name="Rectangle 52" width="5.336" height="5.336" rx="1" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_53" data-name="Rectangle 53" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_54" data-name="Rectangle 54" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_55" data-name="Rectangle 55" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_56" data-name="Rectangle 56" width="5.336" height="5.336" rx="1" transform="translate(25.295 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_57" data-name="Rectangle 57" width="5.336" height="5.336" rx="1" transform="translate(31.619 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_58" data-name="Rectangle 58" width="5.336" height="5.336" rx="1" transform="translate(37.942 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_59" data-name="Rectangle 59" width="5.336" height="5.336" rx="1" transform="translate(44.265 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_60" data-name="Rectangle 60" width="5.336" height="5.336" rx="1" transform="translate(50.589 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_61" data-name="Rectangle 61" width="5.336" height="5.336" rx="1" transform="translate(56.912 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_62" data-name="Rectangle 62" width="5.336" height="5.336" rx="1" transform="translate(63.236 0)" fill="#4a4a4a"/>
|
||||
</g>
|
||||
<path id="Path_55" data-name="Path 55" d="M1.094,0H8A1.093,1.093,0,0,1,9.091,1.093v3.15A1.093,1.093,0,0,1,8,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(80.428 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||
</g>
|
||||
<g id="Group_7" data-name="Group 7" transform="translate(1.531 29.627)">
|
||||
<rect id="Rectangle_63" data-name="Rectangle 63" width="5.336" height="5.336" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_64" data-name="Rectangle 64" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_65" data-name="Rectangle 65" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_66" data-name="Rectangle 66" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
|
||||
<path id="Path_56" data-name="Path 56" d="M1.093,0H31.515a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.244V1.093A1.093,1.093,0,0,1,1.093,0ZM34.687,0h3.942a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H34.687a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,34.687,0Z" transform="translate(25.294 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||
<rect id="Rectangle_67" data-name="Rectangle 67" width="5.336" height="5.336" rx="1" transform="translate(66.003 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_68" data-name="Rectangle 68" width="5.336" height="5.336" rx="1" transform="translate(72.327 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_69" data-name="Rectangle 69" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
|
||||
<path id="Path_57" data-name="Path 57" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(83.59 2.273) rotate(180)" fill="#4a4a4a"/>
|
||||
<path id="Path_58" data-name="Path 58" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(78.255 3.063)" fill="#4a4a4a"/>
|
||||
</g>
|
||||
<rect id="Rectangle_70" data-name="Rectangle 70" width="88.927" height="2.371" rx="1.085" transform="translate(1.925 1.17)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_71" data-name="Rectangle 71" width="4.986" height="1.581" rx="0.723" transform="translate(4.1 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_72" data-name="Rectangle 72" width="4.986" height="1.581" rx="0.723" transform="translate(10.923 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_73" data-name="Rectangle 73" width="4.986" height="1.581" rx="0.723" transform="translate(16.173 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_74" data-name="Rectangle 74" width="4.986" height="1.581" rx="0.723" transform="translate(21.421 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_75" data-name="Rectangle 75" width="4.986" height="1.581" rx="0.723" transform="translate(26.671 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_76" data-name="Rectangle 76" width="4.986" height="1.581" rx="0.723" transform="translate(33.232 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_77" data-name="Rectangle 77" width="4.986" height="1.581" rx="0.723" transform="translate(38.48 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_78" data-name="Rectangle 78" width="4.986" height="1.581" rx="0.723" transform="translate(43.73 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_79" data-name="Rectangle 79" width="4.986" height="1.581" rx="0.723" transform="translate(48.978 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_80" data-name="Rectangle 80" width="4.986" height="1.581" rx="0.723" transform="translate(55.54 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_81" data-name="Rectangle 81" width="4.986" height="1.581" rx="0.723" transform="translate(60.788 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_82" data-name="Rectangle 82" width="4.986" height="1.581" rx="0.723" transform="translate(66.038 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_83" data-name="Rectangle 83" width="4.986" height="1.581" rx="0.723" transform="translate(72.599 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_84" data-name="Rectangle 84" width="4.986" height="1.581" rx="0.723" transform="translate(77.847 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_85" data-name="Rectangle 85" width="4.986" height="1.581" rx="0.723" transform="translate(83.097 1.566)" fill="#d8d8d8" opacity="0.136"/>
|
||||
</g>
|
||||
<path id="Path_59" data-name="Path 59" d="M146.71,159.855a5.439,5.439,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(6.275 -6.025)" fill="#44d860" fill-rule="evenodd"/>
|
||||
<path id="Path_60" data-name="Path 60" d="M83,124.855h43.71V103H83Z" transform="translate(4.42 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||
<path id="Path_61" data-name="Path 61" d="M134.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.811,2.811,0,0,0,.349.035" transform="translate(7.202 -9.377)" fill="#44d860" fill-rule="evenodd"/>
|
||||
<path id="Path_62" data-name="Path 62" d="M143.232,42.33a2.967,2.967,0,0,1-.535-.055,2.754,2.754,0,0,1-.514-.153,2.838,2.838,0,0,1-.471-.251,4.139,4.139,0,0,1-.415-.339,3.2,3.2,0,0,1-.338-.415A2.7,2.7,0,0,1,140.5,39.6a2.968,2.968,0,0,1,.055-.535,3.152,3.152,0,0,1,.152-.514,2.874,2.874,0,0,1,.252-.47,2.633,2.633,0,0,1,.753-.754,2.837,2.837,0,0,1,.471-.251,2.753,2.753,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,4.019,4.019,0,0,1,.339.415,2.786,2.786,0,0,1,.251.47,2.864,2.864,0,0,1,.208,1.049,2.77,2.77,0,0,1-.8,1.934,4.139,4.139,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459m21.855-1.366a2.789,2.789,0,0,1-1.935-.8,4.162,4.162,0,0,1-.338-.415,2.7,2.7,0,0,1-.459-1.519,2.789,2.789,0,0,1,.8-1.934,4.139,4.139,0,0,1,.415-.339,2.838,2.838,0,0,1,.471-.251,2.752,2.752,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,2.79,2.79,0,0,1,.8,1.934,3.069,3.069,0,0,1-.055.535,2.779,2.779,0,0,1-.153.514,3.885,3.885,0,0,1-.251.47,4.02,4.02,0,0,1-.339.415,4.138,4.138,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459" transform="translate(9.753 -15.532)" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 31 KiB |
170
static/img/undraw_docusaurus_react.svg
Normal file
@ -0,0 +1,170 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1041.277" height="554.141" viewBox="0 0 1041.277 554.141">
|
||||
<title>Powered by React</title>
|
||||
<g id="Group_24" data-name="Group 24" transform="translate(-440 -263)">
|
||||
<g id="Group_23" data-name="Group 23" transform="translate(439.989 262.965)">
|
||||
<path id="Path_299" data-name="Path 299" d="M1040.82,611.12q-1.74,3.75-3.47,7.4-2.7,5.67-5.33,11.12c-.78,1.61-1.56,3.19-2.32,4.77-8.6,17.57-16.63,33.11-23.45,45.89A73.21,73.21,0,0,1,942.44,719l-151.65,1.65h-1.6l-13,.14-11.12.12-34.1.37h-1.38l-17.36.19h-.53l-107,1.16-95.51,1-11.11.12-69,.75H429l-44.75.48h-.48l-141.5,1.53-42.33.46a87.991,87.991,0,0,1-10.79-.54h0c-1.22-.14-2.44-.3-3.65-.49a87.38,87.38,0,0,1-51.29-27.54C116,678.37,102.75,655,93.85,629.64q-1.93-5.49-3.6-11.12C59.44,514.37,97,380,164.6,290.08q4.25-5.64,8.64-11l.07-.08c20.79-25.52,44.1-46.84,68.93-62,44-26.91,92.75-34.49,140.7-11.9,40.57,19.12,78.45,28.11,115.17,30.55,3.71.24,7.42.42,11.11.53,84.23,2.65,163.17-27.7,255.87-47.29,3.69-.78,7.39-1.55,11.12-2.28,66.13-13.16,139.49-20.1,226.73-5.51a189.089,189.089,0,0,1,26.76,6.4q5.77,1.86,11.12,4c41.64,16.94,64.35,48.24,74,87.46q1.37,5.46,2.37,11.11C1134.3,384.41,1084.19,518.23,1040.82,611.12Z" transform="translate(-79.34 -172.91)" fill="#f2f2f2"/>
|
||||
<path id="Path_300" data-name="Path 300" d="M576.36,618.52a95.21,95.21,0,0,1-1.87,11.12h93.7V618.52Zm-78.25,62.81,11.11-.09V653.77c-3.81-.17-7.52-.34-11.11-.52ZM265.19,618.52v11.12h198.5V618.52ZM1114.87,279h-74V191.51q-5.35-2.17-11.12-4V279H776.21V186.58c-3.73.73-7.43,1.5-11.12,2.28V279H509.22V236.15c-3.69-.11-7.4-.29-11.11-.53V279H242.24V217c-24.83,15.16-48.14,36.48-68.93,62h-.07v.08q-4.4,5.4-8.64,11h8.64V618.52h-83q1.66,5.63,3.6,11.12h79.39v93.62a87,87,0,0,0,12.2,2.79c1.21.19,2.43.35,3.65.49h0a87.991,87.991,0,0,0,10.79.54l42.33-.46v-97H498.11v94.21l11.11-.12V629.64H765.09V721l11.12-.12V629.64H1029.7v4.77c.76-1.58,1.54-3.16,2.32-4.77q2.63-5.45,5.33-11.12,1.73-3.64,3.47-7.4v-321h76.42Q1116.23,284.43,1114.87,279ZM242.24,618.52V290.08H498.11V618.52Zm267,0V290.08H765.09V618.52Zm520.48,0H776.21V290.08H1029.7Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||
<path id="Path_301" data-name="Path 301" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" fill="#65617d"/>
|
||||
<path id="Path_302" data-name="Path 302" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" opacity="0.2"/>
|
||||
<path id="Path_303" data-name="Path 303" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
|
||||
<path id="Path_304" data-name="Path 304" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||
<path id="Path_305" data-name="Path 305" d="M377.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_137" data-name="Rectangle 137" width="47.17" height="31.5" transform="translate(680.92 483.65)" fill="#3f3d56"/>
|
||||
<rect id="Rectangle_138" data-name="Rectangle 138" width="47.17" height="31.5" transform="translate(680.92 483.65)" opacity="0.1"/>
|
||||
<rect id="Rectangle_139" data-name="Rectangle 139" width="47.17" height="31.5" transform="translate(678.92 483.65)" fill="#3f3d56"/>
|
||||
<path id="Path_306" data-name="Path 306" d="M298.09,483.65v4.97l-47.17,1.26v-6.23Z" opacity="0.1"/>
|
||||
<path id="Path_307" data-name="Path 307" d="M460.69,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6a4,4,0,0,1,3.95,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
|
||||
<path id="Path_308" data-name="Path 308" d="M265.19,481.32v181.2h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||
<path id="Path_309" data-name="Path 309" d="M194.59,319.15h177.5V467.4l-177.5,4Z" fill="#39374d"/>
|
||||
<path id="Path_310" data-name="Path 310" d="M726.09,483.65v6.41l-47.17-1.26v-5.15Z" opacity="0.1"/>
|
||||
<path id="Path_311" data-name="Path 311" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0L672,657.42a4,4,0,0,1-3.85-3.95V485.27a4,4,0,0,1,3.95-3.95H863.7a4,4,0,0,1,3.99,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
|
||||
<path id="Path_312" data-name="Path 312" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0V481.32h0a4,4,0,0,1,4,3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||
<path id="Path_313" data-name="Path 313" d="M775.59,319.15H598.09V467.4l177.5,4Z" fill="#39374d"/>
|
||||
<path id="Path_314" data-name="Path 314" d="M663.19,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h0a4,4,0,0,1-4-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6A4,4,0,0,1,663.19,485.27Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
|
||||
<path id="Path_315" data-name="Path 315" d="M397.09,319.15h177.5V467.4l-177.5,4Z" fill="#4267b2"/>
|
||||
<path id="Path_316" data-name="Path 316" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l202.51-1.33h.48l40.99-.28h.19l283.08-1.87h.29l.17-.01h.47l4.79-.03h1.46l74.49-.5,4.4-.02.98-.01Z" opacity="0.1"/>
|
||||
<circle id="Ellipse_111" data-name="Ellipse 111" cx="51.33" cy="51.33" r="51.33" transform="translate(435.93 246.82)" fill="#fbbebe"/>
|
||||
<path id="Path_317" data-name="Path 317" d="M617.94,550.07s-99.5,12-90,0c3.44-4.34,4.39-17.2,4.2-31.85-.06-4.45-.22-9.06-.45-13.65-1.1-22-3.75-43.5-3.75-43.5s87-41,77-8.5c-4,13.13-2.69,31.57.35,48.88.89,5.05,1.92,10,3,14.7a344.66,344.66,0,0,0,9.65,33.92Z" transform="translate(-79.34 -172.91)" fill="#fbbebe"/>
|
||||
<path id="Path_318" data-name="Path 318" d="M585.47,546c11.51-2.13,23.7-6,34.53-1.54,2.85,1.17,5.47,2.88,8.39,3.86s6.12,1.22,9.16,1.91c10.68,2.42,19.34,10.55,24.9,20s8.44,20.14,11.26,30.72l6.9,25.83c6,22.45,12,45.09,13.39,68.3a2437.506,2437.506,0,0,1-250.84,1.43c5.44-10.34,11-21.31,10.54-33s-7.19-23.22-4.76-34.74c1.55-7.34,6.57-13.39,9.64-20.22,8.75-19.52,1.94-45.79,17.32-60.65,6.92-6.68,17-9.21,26.63-8.89,12.28.41,24.85,4.24,37,6.11C555.09,547.48,569.79,548.88,585.47,546Z" transform="translate(-79.34 -172.91)" fill="#ff6584"/>
|
||||
<path id="Path_319" data-name="Path 319" d="M716.37,657.17l-.1,1.43v.1l-.17,2.3-1.33,18.51-1.61,22.3-.46,6.28-1,13.44v.17l-107,1-175.59,1.9v.84h-.14v-1.12l.45-14.36.86-28.06.74-23.79.07-2.37a10.53,10.53,0,0,1,11.42-10.17c4.72.4,10.85.89,18.18,1.41l3,.22c42.33,2.94,120.56,6.74,199.5,2,1.66-.09,3.33-.19,5-.31,12.24-.77,24.47-1.76,36.58-3a10.53,10.53,0,0,1,11.6,11.23Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||
<path id="Path_320" data-name="Path 320" d="M429.08,725.44v-.84l175.62-1.91,107-1h.3v-.17l1-13.44.43-6,1.64-22.61,1.29-17.9v-.44a10.617,10.617,0,0,0-.11-2.47.3.3,0,0,0,0-.1,10.391,10.391,0,0,0-2-4.64,10.54,10.54,0,0,0-9.42-4c-12.11,1.24-24.34,2.23-36.58,3-1.67.12-3.34.22-5,.31-78.94,4.69-157.17.89-199.5-2l-3-.22c-7.33-.52-13.46-1-18.18-1.41a10.54,10.54,0,0,0-11.24,8.53,11,11,0,0,0-.18,1.64l-.68,22.16L429.54,710l-.44,14.36v1.12Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
|
||||
<path id="Path_321" data-name="Path 321" d="M716.67,664.18l-1.23,15.33-1.83,22.85-.46,5.72-1,12.81-.06.64v.17h0l-.15,1.48.11-1.48h-.29l-107,1-175.65,1.9v-.28l.49-14.36,1-28.06.64-18.65A6.36,6.36,0,0,1,434.3,658a6.25,6.25,0,0,1,3.78-.9c2.1.17,4.68.37,7.69.59,4.89.36,10.92.78,17.94,1.22,13,.82,29.31,1.7,48,2.42,52,2,122.2,2.67,188.88-3.17,3-.26,6.1-.55,9.13-.84a6.26,6.26,0,0,1,3.48.66,5.159,5.159,0,0,1,.86.54,6.14,6.14,0,0,1,2,2.46,3.564,3.564,0,0,1,.25.61A6.279,6.279,0,0,1,716.67,664.18Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||
<path id="Path_322" data-name="Path 322" d="M377.44,677.87v3.19a6.13,6.13,0,0,1-3.5,5.54l-40.1.77a6.12,6.12,0,0,1-3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||
<path id="Path_323" data-name="Path 323" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
|
||||
<path id="Path_324" data-name="Path 324" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" opacity="0.1"/>
|
||||
<path id="Path_325" data-name="Path 325" d="M300.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
|
||||
<path id="Path_326" data-name="Path 326" d="M758.56,679.87v3.19a6.13,6.13,0,0,0,3.5,5.54l40.1.77a6.12,6.12,0,0,0,3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
|
||||
<path id="Path_327" data-name="Path 327" d="M678.72,517.57l52.25,1V509.9l-52.25-1Z" opacity="0.1"/>
|
||||
<path id="Path_328" data-name="Path 328" d="M676.72,517.57l52.25,1V509.9l-52.25-1Z" fill="#3f3d56"/>
|
||||
<path id="Path_329" data-name="Path 329" d="M534.13,486.79c.08,7-3.16,13.6-5.91,20.07a163.491,163.491,0,0,0-12.66,74.71c.73,11,2.58,22,.73,32.9s-8.43,21.77-19,24.9c17.53,10.45,41.26,9.35,57.76-2.66,8.79-6.4,15.34-15.33,21.75-24.11a97.86,97.86,0,0,1-13.31,44.75A103.43,103.43,0,0,0,637,616.53c4.31-5.81,8.06-12.19,9.72-19.23,3.09-13-1.22-26.51-4.51-39.5a266.055,266.055,0,0,1-6.17-33c-.43-3.56-.78-7.22.1-10.7,1-4.07,3.67-7.51,5.64-11.22,5.6-10.54,5.73-23.3,2.86-34.88s-8.49-22.26-14.06-32.81c-4.46-8.46-9.3-17.31-17.46-22.28-5.1-3.1-11-4.39-16.88-5.64l-25.37-5.43c-5.55-1.19-11.26-2.38-16.87-1.51-9.47,1.48-16.14,8.32-22,15.34-4.59,5.46-15.81,15.71-16.6,22.86-.72,6.59,5.1,17.63,6.09,24.58,1.3,9,2.22,6,7.3,11.52C532,478.05,534.07,482,534.13,486.79Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
|
||||
</g>
|
||||
<g id="docusaurus_keytar" transform="translate(670.271 615.768)">
|
||||
<path id="Path_40" data-name="Path 40" d="M99,52h43.635V69.662H99Z" transform="translate(-49.132 -33.936)" fill="#fff" fill-rule="evenodd"/>
|
||||
<path id="Path_41" data-name="Path 41" d="M13.389,158.195A10.377,10.377,0,0,1,4.4,153a10.377,10.377,0,0,0,8.988,15.584H23.779V158.195Z" transform="translate(-3 -82.47)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||
<path id="Path_42" data-name="Path 42" d="M66.967,38.083l36.373-2.273V30.615A10.389,10.389,0,0,0,92.95,20.226H46.2l-1.3-2.249a1.5,1.5,0,0,0-2.6,0L41,20.226l-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-.034,0-2.152-2.151a1.5,1.5,0,0,0-2.508.672L25.21,21.4l-2.7-.723a1.5,1.5,0,0,0-1.836,1.837l.722,2.7-2.65.71a1.5,1.5,0,0,0-.673,2.509l2.152,2.152c0,.011,0,.022,0,.033l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6L20.226,41l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3A10.389,10.389,0,0,0,30.615,103.34H92.95A10.389,10.389,0,0,0,103.34,92.95V51.393L66.967,49.12a5.53,5.53,0,0,1,0-11.038" transform="translate(-9.836 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||
<path id="Path_43" data-name="Path 43" d="M143,163.779h15.584V143H143Z" transform="translate(-70.275 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||
<path id="Path_44" data-name="Path 44" d="M173.779,148.389a2.582,2.582,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-75.08 -75.262)" fill="#44d860" fill-rule="evenodd"/>
|
||||
<path id="Path_45" data-name="Path 45" d="M153,113.389h15.584V103H153Z" transform="translate(-75.08 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||
<path id="Path_46" data-name="Path 46" d="M183.389,108.944a1.3,1.3,0,1,0,0-2.6,1.336,1.336,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.337,1.337,0,0,0,.166.017" transform="translate(-84.691 -57.894)" fill="#44d860" fill-rule="evenodd"/>
|
||||
<path id="Path_47" data-name="Path 47" d="M52.188,48.292a1.3,1.3,0,0,1-1.3-1.3,3.9,3.9,0,0,0-7.792,0,1.3,1.3,0,1,1-2.6,0,6.493,6.493,0,0,1,12.987,0,1.3,1.3,0,0,1-1.3,1.3" transform="translate(-21.02 -28.41)" fill-rule="evenodd"/>
|
||||
<path id="Path_48" data-name="Path 48" d="M103,139.752h31.168a10.389,10.389,0,0,0,10.389-10.389V93H113.389A10.389,10.389,0,0,0,103,103.389Z" transform="translate(-51.054 -53.638)" fill="#ffff50" fill-rule="evenodd"/>
|
||||
<path id="Path_49" data-name="Path 49" d="M141.1,94.017H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0-25.877H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.293H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m7.782-47.993c-.006,0-.011,0-.018,0-1.605.055-2.365,1.66-3.035,3.077-.7,1.48-1.24,2.443-2.126,2.414-.981-.035-1.542-1.144-2.137-2.317-.683-1.347-1.462-2.876-3.1-2.819-1.582.054-2.344,1.451-3.017,2.684-.715,1.313-1.2,2.112-2.141,2.075-1-.036-1.533-.938-2.149-1.981-.686-1.162-1.479-2.467-3.084-2.423-1.555.053-2.319,1.239-2.994,2.286-.713,1.106-1.213,1.781-2.164,1.741-1.025-.036-1.554-.784-2.167-1.65-.688-.973-1.463-2.074-3.062-2.021a3.815,3.815,0,0,0-2.959,1.879c-.64.812-1.14,1.456-2.2,1.415a.52.52,0,0,0-.037,1.039,3.588,3.588,0,0,0,3.05-1.811c.611-.777,1.139-1.448,2.178-1.483,1-.043,1.47.579,2.179,1.582.674.953,1.438,2.033,2.977,2.089,1.612.054,2.387-1.151,3.074-2.217.614-.953,1.144-1.775,2.156-1.81.931-.035,1.438.7,2.153,1.912.674,1.141,1.437,2.434,3.006,2.491,1.623.056,2.407-1.361,3.09-2.616.592-1.085,1.15-2.109,2.14-2.143.931-.022,1.417.829,2.135,2.249.671,1.326,1.432,2.828,3.026,2.886l.088,0c1.592,0,2.347-1.6,3.015-3.01.592-1.252,1.152-2.431,2.113-2.479Z" transform="translate(-55.378 -38.552)" fill-rule="evenodd"/>
|
||||
<path id="Path_50" data-name="Path 50" d="M83,163.779h20.779V143H83Z" transform="translate(-41.443 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 51.971, 43.3)">
|
||||
<rect id="Rectangle_3" data-name="Rectangle 3" width="43.906" height="17.333" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
|
||||
<g id="Group_2" data-name="Group 2" transform="translate(0.728 10.948)">
|
||||
<rect id="Rectangle_4" data-name="Rectangle 4" width="2.537" height="2.537" rx="1" transform="translate(7.985 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_5" data-name="Rectangle 5" width="2.537" height="2.537" rx="1" transform="translate(10.991 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_6" data-name="Rectangle 6" width="2.537" height="2.537" rx="1" transform="translate(13.997 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_7" data-name="Rectangle 7" width="2.537" height="2.537" rx="1" transform="translate(17.003 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_8" data-name="Rectangle 8" width="2.537" height="2.537" rx="1" transform="translate(20.009 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_9" data-name="Rectangle 9" width="2.537" height="2.537" rx="1" transform="translate(23.015 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_10" data-name="Rectangle 10" width="2.537" height="2.537" rx="1" transform="translate(26.021 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_11" data-name="Rectangle 11" width="2.537" height="2.537" rx="1" transform="translate(29.028 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_12" data-name="Rectangle 12" width="2.537" height="2.537" rx="1" transform="translate(32.034 0)" fill="#4a4a4a"/>
|
||||
<path id="Path_51" data-name="Path 51" d="M.519,0H6.9A.519.519,0,0,1,7.421.52v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0ZM35.653,0h6.383a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H35.652a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,35.652,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||
</g>
|
||||
<g id="Group_3" data-name="Group 3" transform="translate(0.728 4.878)">
|
||||
<path id="Path_52" data-name="Path 52" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||
<rect id="Rectangle_13" data-name="Rectangle 13" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_14" data-name="Rectangle 14" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_15" data-name="Rectangle 15" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_16" data-name="Rectangle 16" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_17" data-name="Rectangle 17" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_18" data-name="Rectangle 18" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_19" data-name="Rectangle 19" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_20" data-name="Rectangle 20" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_21" data-name="Rectangle 21" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_22" data-name="Rectangle 22" width="2.537" height="2.537" rx="1" transform="translate(31 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_23" data-name="Rectangle 23" width="2.537" height="2.537" rx="1" transform="translate(34.006 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_24" data-name="Rectangle 24" width="2.537" height="2.537" rx="1" transform="translate(37.012 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_25" data-name="Rectangle 25" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
|
||||
</g>
|
||||
<g id="Group_4" data-name="Group 4" transform="translate(43.283 4.538) rotate(180)">
|
||||
<path id="Path_53" data-name="Path 53" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||
<rect id="Rectangle_26" data-name="Rectangle 26" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_27" data-name="Rectangle 27" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_28" data-name="Rectangle 28" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_29" data-name="Rectangle 29" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_30" data-name="Rectangle 30" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_31" data-name="Rectangle 31" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_32" data-name="Rectangle 32" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_33" data-name="Rectangle 33" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_34" data-name="Rectangle 34" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_35" data-name="Rectangle 35" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_36" data-name="Rectangle 36" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_37" data-name="Rectangle 37" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_38" data-name="Rectangle 38" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_39" data-name="Rectangle 39" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_40" data-name="Rectangle 40" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_41" data-name="Rectangle 41" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_42" data-name="Rectangle 42" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_43" data-name="Rectangle 43" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_44" data-name="Rectangle 44" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_45" data-name="Rectangle 45" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_46" data-name="Rectangle 46" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_47" data-name="Rectangle 47" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_48" data-name="Rectangle 48" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_49" data-name="Rectangle 49" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_50" data-name="Rectangle 50" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_51" data-name="Rectangle 51" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
|
||||
</g>
|
||||
<g id="Group_6" data-name="Group 6" transform="translate(0.728 7.883)">
|
||||
<path id="Path_54" data-name="Path 54" d="M.519,0h3.47a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||
<g id="Group_5" data-name="Group 5" transform="translate(5.073 0)">
|
||||
<rect id="Rectangle_52" data-name="Rectangle 52" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_53" data-name="Rectangle 53" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_54" data-name="Rectangle 54" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_55" data-name="Rectangle 55" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_56" data-name="Rectangle 56" width="2.537" height="2.537" rx="1" transform="translate(12.025 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_57" data-name="Rectangle 57" width="2.537" height="2.537" rx="1" transform="translate(15.031 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_58" data-name="Rectangle 58" width="2.537" height="2.537" rx="1" transform="translate(18.037 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_59" data-name="Rectangle 59" width="2.537" height="2.537" rx="1" transform="translate(21.042 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_60" data-name="Rectangle 60" width="2.537" height="2.537" rx="1" transform="translate(24.049 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_61" data-name="Rectangle 61" width="2.537" height="2.537" rx="1" transform="translate(27.055 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_62" data-name="Rectangle 62" width="2.537" height="2.537" rx="1" transform="translate(30.061 0)" fill="#4a4a4a"/>
|
||||
</g>
|
||||
<path id="Path_55" data-name="Path 55" d="M.52,0H3.8a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(38.234 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||
</g>
|
||||
<g id="Group_7" data-name="Group 7" transform="translate(0.728 14.084)">
|
||||
<rect id="Rectangle_63" data-name="Rectangle 63" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_64" data-name="Rectangle 64" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_65" data-name="Rectangle 65" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_66" data-name="Rectangle 66" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
|
||||
<path id="Path_56" data-name="Path 56" d="M.519,0H14.981A.519.519,0,0,1,15.5.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.018V.519A.519.519,0,0,1,.519,0Zm15.97,0h1.874a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H16.489a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,16.489,0Z" transform="translate(12.024 0)" fill="#4a4a4a" fill-rule="evenodd"/>
|
||||
<rect id="Rectangle_67" data-name="Rectangle 67" width="2.537" height="2.537" rx="1" transform="translate(31.376 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_68" data-name="Rectangle 68" width="2.537" height="2.537" rx="1" transform="translate(34.382 0)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_69" data-name="Rectangle 69" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
|
||||
<path id="Path_57" data-name="Path 57" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(39.736 1.08) rotate(180)" fill="#4a4a4a"/>
|
||||
<path id="Path_58" data-name="Path 58" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(37.2 1.456)" fill="#4a4a4a"/>
|
||||
</g>
|
||||
<rect id="Rectangle_70" data-name="Rectangle 70" width="42.273" height="1.127" rx="0.564" transform="translate(0.915 0.556)" fill="#4a4a4a"/>
|
||||
<rect id="Rectangle_71" data-name="Rectangle 71" width="2.37" height="0.752" rx="0.376" transform="translate(1.949 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_72" data-name="Rectangle 72" width="2.37" height="0.752" rx="0.376" transform="translate(5.193 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_73" data-name="Rectangle 73" width="2.37" height="0.752" rx="0.376" transform="translate(7.688 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_74" data-name="Rectangle 74" width="2.37" height="0.752" rx="0.376" transform="translate(10.183 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_75" data-name="Rectangle 75" width="2.37" height="0.752" rx="0.376" transform="translate(12.679 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_76" data-name="Rectangle 76" width="2.37" height="0.752" rx="0.376" transform="translate(15.797 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_77" data-name="Rectangle 77" width="2.37" height="0.752" rx="0.376" transform="translate(18.292 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_78" data-name="Rectangle 78" width="2.37" height="0.752" rx="0.376" transform="translate(20.788 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_79" data-name="Rectangle 79" width="2.37" height="0.752" rx="0.376" transform="translate(23.283 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_80" data-name="Rectangle 80" width="2.37" height="0.752" rx="0.376" transform="translate(26.402 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_81" data-name="Rectangle 81" width="2.37" height="0.752" rx="0.376" transform="translate(28.897 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_82" data-name="Rectangle 82" width="2.37" height="0.752" rx="0.376" transform="translate(31.393 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_83" data-name="Rectangle 83" width="2.37" height="0.752" rx="0.376" transform="translate(34.512 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_84" data-name="Rectangle 84" width="2.37" height="0.752" rx="0.376" transform="translate(37.007 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
<rect id="Rectangle_85" data-name="Rectangle 85" width="2.37" height="0.752" rx="0.376" transform="translate(39.502 0.744)" fill="#d8d8d8" opacity="0.136"/>
|
||||
</g>
|
||||
<path id="Path_59" data-name="Path 59" d="M123.779,148.389a2.583,2.583,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-51.054 -75.262)" fill="#44d860" fill-rule="evenodd"/>
|
||||
<path id="Path_60" data-name="Path 60" d="M83,113.389h20.779V103H83Z" transform="translate(-41.443 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
|
||||
<path id="Path_61" data-name="Path 61" d="M123.389,108.944a1.3,1.3,0,1,0,0-2.6,1.338,1.338,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.335,1.335,0,0,0,.166.017" transform="translate(-55.859 -57.894)" fill="#44d860" fill-rule="evenodd"/>
|
||||
<path id="Path_62" data-name="Path 62" d="M141.8,38.745a1.41,1.41,0,0,1-.255-.026,1.309,1.309,0,0,1-.244-.073,1.349,1.349,0,0,1-.224-.119,1.967,1.967,0,0,1-.2-.161,1.52,1.52,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.41,1.41,0,0,1,.026-.255,1.5,1.5,0,0,1,.072-.244,1.364,1.364,0,0,1,.12-.223,1.252,1.252,0,0,1,.358-.358,1.349,1.349,0,0,1,.224-.119,1.309,1.309,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.968,1.968,0,0,1,.2.161,1.908,1.908,0,0,1,.161.2,1.322,1.322,0,0,1,.12.223,1.361,1.361,0,0,1,.1.5,1.317,1.317,0,0,1-.379.919,1.968,1.968,0,0,1-.2.161,1.346,1.346,0,0,1-.223.119,1.332,1.332,0,0,1-.5.1m10.389-.649a1.326,1.326,0,0,1-.92-.379,1.979,1.979,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.326,1.326,0,0,1,.379-.919,1.967,1.967,0,0,1,.2-.161,1.351,1.351,0,0,1,.224-.119,1.308,1.308,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.967,1.967,0,0,1,.2.161,1.326,1.326,0,0,1,.379.919,1.461,1.461,0,0,1-.026.255,1.323,1.323,0,0,1-.073.244,1.847,1.847,0,0,1-.119.223,1.911,1.911,0,0,1-.161.2,1.967,1.967,0,0,1-.2.161,1.294,1.294,0,0,1-.722.218" transform="translate(-69.074 -26.006)" fill-rule="evenodd"/>
|
||||
</g>
|
||||
<g id="React-icon" transform="translate(906.3 541.56)">
|
||||
<path id="Path_330" data-name="Path 330" d="M263.668,117.179c0-5.827-7.3-11.35-18.487-14.775,2.582-11.4,1.434-20.477-3.622-23.382a7.861,7.861,0,0,0-4.016-1v4a4.152,4.152,0,0,1,2.044.466c2.439,1.4,3.5,6.724,2.672,13.574-.2,1.685-.52,3.461-.914,5.272a86.9,86.9,0,0,0-11.386-1.954,87.469,87.469,0,0,0-7.459-8.965c5.845-5.433,11.332-8.41,15.062-8.41V78h0c-4.931,0-11.386,3.514-17.913,9.611-6.527-6.061-12.982-9.539-17.913-9.539v4c3.712,0,9.216,2.959,15.062,8.356a84.687,84.687,0,0,0-7.405,8.947,83.732,83.732,0,0,0-11.4,1.972c-.412-1.793-.717-3.532-.932-5.2-.843-6.85.2-12.175,2.618-13.592a3.991,3.991,0,0,1,2.062-.466v-4h0a8,8,0,0,0-4.052,1c-5.039,2.9-6.168,11.96-3.568,23.328-11.153,3.443-18.415,8.947-18.415,14.757,0,5.828,7.3,11.35,18.487,14.775-2.582,11.4-1.434,20.477,3.622,23.382a7.882,7.882,0,0,0,4.034,1c4.931,0,11.386-3.514,17.913-9.611,6.527,6.061,12.982,9.539,17.913,9.539a8,8,0,0,0,4.052-1c5.039-2.9,6.168-11.96,3.568-23.328C256.406,128.511,263.668,122.988,263.668,117.179Zm-23.346-11.96c-.663,2.313-1.488,4.7-2.421,7.083-.735-1.434-1.506-2.869-2.349-4.3-.825-1.434-1.7-2.833-2.582-4.2C235.517,104.179,237.974,104.645,240.323,105.219Zm-8.212,19.1c-1.4,2.421-2.833,4.716-4.321,6.85-2.672.233-5.379.359-8.1.359-2.708,0-5.415-.126-8.069-.341q-2.232-3.2-4.339-6.814-2.044-3.523-3.73-7.136c1.112-2.4,2.367-4.805,3.712-7.154,1.4-2.421,2.833-4.716,4.321-6.85,2.672-.233,5.379-.359,8.1-.359,2.708,0,5.415.126,8.069.341q2.232,3.2,4.339,6.814,2.044,3.523,3.73,7.136C234.692,119.564,233.455,121.966,232.11,124.315Zm5.792-2.331c.968,2.4,1.793,4.805,2.474,7.136-2.349.574-4.823,1.058-7.387,1.434.879-1.381,1.757-2.8,2.582-4.25C236.4,124.871,237.167,123.419,237.9,121.984ZM219.72,141.116a73.921,73.921,0,0,1-4.985-5.738c1.614.072,3.263.126,4.931.126,1.685,0,3.353-.036,4.985-.126A69.993,69.993,0,0,1,219.72,141.116ZM206.38,130.555c-2.546-.377-5-.843-7.352-1.417.663-2.313,1.488-4.7,2.421-7.083.735,1.434,1.506,2.869,2.349,4.3S205.5,129.192,206.38,130.555ZM219.63,93.241a73.924,73.924,0,0,1,4.985,5.738c-1.614-.072-3.263-.126-4.931-.126-1.686,0-3.353.036-4.985.126A69.993,69.993,0,0,1,219.63,93.241ZM206.362,103.8c-.879,1.381-1.757,2.8-2.582,4.25-.825,1.434-1.6,2.869-2.331,4.3-.968-2.4-1.793-4.805-2.474-7.136C201.323,104.663,203.8,104.179,206.362,103.8Zm-16.227,22.449c-6.348-2.708-10.454-6.258-10.454-9.073s4.106-6.383,10.454-9.073c1.542-.663,3.228-1.255,4.967-1.811a86.122,86.122,0,0,0,4.034,10.92,84.9,84.9,0,0,0-3.981,10.866C193.38,127.525,191.694,126.915,190.134,126.252Zm9.647,25.623c-2.439-1.4-3.5-6.724-2.672-13.574.2-1.686.52-3.461.914-5.272a86.9,86.9,0,0,0,11.386,1.954,87.465,87.465,0,0,0,7.459,8.965c-5.845,5.433-11.332,8.41-15.062,8.41A4.279,4.279,0,0,1,199.781,151.875Zm42.532-13.663c.843,6.85-.2,12.175-2.618,13.592a3.99,3.99,0,0,1-2.062.466c-3.712,0-9.216-2.959-15.062-8.356a84.689,84.689,0,0,0,7.405-8.947,83.731,83.731,0,0,0,11.4-1.972A50.194,50.194,0,0,1,242.313,138.212Zm6.9-11.96c-1.542.663-3.228,1.255-4.967,1.811a86.12,86.12,0,0,0-4.034-10.92,84.9,84.9,0,0,0,3.981-10.866c1.775.556,3.461,1.165,5.039,1.829,6.348,2.708,10.454,6.258,10.454,9.073C259.67,119.994,255.564,123.562,249.216,126.252Z" fill="#61dafb"/>
|
||||
<path id="Path_331" data-name="Path 331" d="M320.8,78.4Z" transform="translate(-119.082 -0.328)" fill="#61dafb"/>
|
||||
<circle id="Ellipse_112" data-name="Ellipse 112" cx="8.194" cy="8.194" r="8.194" transform="translate(211.472 108.984)" fill="#61dafb"/>
|
||||
<path id="Path_332" data-name="Path 332" d="M520.5,78.1Z" transform="translate(-282.975 -0.082)" fill="#61dafb"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 35 KiB |
40
static/img/undraw_docusaurus_tree.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1129" height="663" viewBox="0 0 1129 663">
|
||||
<title>Focus on What Matters</title>
|
||||
<circle cx="321" cy="321" r="321" fill="#f2f2f2" />
|
||||
<ellipse cx="559" cy="635.49998" rx="514" ry="27.50002" fill="#3f3d56" />
|
||||
<ellipse cx="558" cy="627" rx="460" ry="22" opacity="0.2" />
|
||||
<rect x="131" y="152.5" width="840" height="50" fill="#3f3d56" />
|
||||
<path d="M166.5,727.3299A21.67009,21.67009,0,0,0,188.1701,749H984.8299A21.67009,21.67009,0,0,0,1006.5,727.3299V296h-840Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
|
||||
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
|
||||
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" opacity="0.2" />
|
||||
<circle cx="181" cy="147.5" r="13" fill="#3f3d56" />
|
||||
<circle cx="217" cy="147.5" r="13" fill="#3f3d56" />
|
||||
<circle cx="253" cy="147.5" r="13" fill="#3f3d56" />
|
||||
<rect x="168" y="213.5" width="337" height="386" rx="5.33505" fill="#606060" />
|
||||
<rect x="603" y="272.5" width="284" height="22" rx="5.47638" fill="#2e8555" />
|
||||
<rect x="537" y="352.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
|
||||
<rect x="537" y="396.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
|
||||
<rect x="537" y="440.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
|
||||
<rect x="537" y="484.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
|
||||
<rect x="865" y="552.5" width="88" height="26" rx="7.02756" fill="#3ecc5f" />
|
||||
<path d="M1088.60287,624.61594a30.11371,30.11371,0,0,0,3.98291-15.266c0-13.79652-8.54358-24.98081-19.08256-24.98081s-19.08256,11.18429-19.08256,24.98081a30.11411,30.11411,0,0,0,3.98291,15.266,31.248,31.248,0,0,0,0,30.53213,31.248,31.248,0,0,0,0,30.53208,31.248,31.248,0,0,0,0,30.53208,30.11408,30.11408,0,0,0-3.98291,15.266c0,13.79652,8.54353,24.98081,19.08256,24.98081s19.08256-11.18429,19.08256-24.98081a30.11368,30.11368,0,0,0-3.98291-15.266,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53213Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
|
||||
<ellipse cx="1038.00321" cy="460.31783" rx="19.08256" ry="24.9808" fill="#3f3d56" />
|
||||
<ellipse cx="1038.00321" cy="429.78574" rx="19.08256" ry="24.9808" fill="#3f3d56" />
|
||||
<path d="M1144.93871,339.34489a91.61081,91.61081,0,0,0,7.10658-10.46092l-50.141-8.23491,54.22885.4033a91.566,91.566,0,0,0,1.74556-72.42605l-72.75449,37.74139,67.09658-49.32086a91.41255,91.41255,0,1,0-150.971,102.29805,91.45842,91.45842,0,0,0-10.42451,16.66946l65.0866,33.81447-69.40046-23.292a91.46011,91.46011,0,0,0,14.73837,85.83669,91.40575,91.40575,0,1,0,143.68892,0,91.41808,91.41808,0,0,0,0-113.02862Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
||||
<path d="M981.6885,395.8592a91.01343,91.01343,0,0,0,19.56129,56.51431,91.40575,91.40575,0,1,0,143.68892,0C1157.18982,436.82067,981.6885,385.60008,981.6885,395.8592Z" transform="translate(-35.5 -118.5)" opacity="0.1" />
|
||||
<path d="M365.62,461.43628H477.094v45.12043H365.62Z" transform="translate(-35.5 -118.5)" fill="#fff" fill-rule="evenodd" />
|
||||
<path d="M264.76252,608.74122a26.50931,26.50931,0,0,1-22.96231-13.27072,26.50976,26.50976,0,0,0,22.96231,39.81215H291.304V608.74122Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
||||
<path d="M384.17242,468.57061l92.92155-5.80726V449.49263a26.54091,26.54091,0,0,0-26.54143-26.54143H331.1161l-3.31768-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622-3.31767-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622L301.257,417.205a3.83043,3.83043,0,0,0-6.63536,0L291.304,422.9512c-.02919,0-.05573.004-.08625.004l-5.49674-5.49541a3.8293,3.8293,0,0,0-6.4071,1.71723l-1.81676,6.77338L270.607,424.1031a3.82993,3.82993,0,0,0-4.6912,4.69253l1.84463,6.89148-6.77072,1.81411a3.8315,3.8315,0,0,0-1.71988,6.40975l5.49673,5.49673c0,.02787-.004.05574-.004.08493l-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74621,3.31768L259.0163,466.081a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768L259.0163,558.976a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768A26.54091,26.54091,0,0,0,291.304,635.28265H450.55254A26.5409,26.5409,0,0,0,477.094,608.74122V502.5755l-92.92155-5.80727a14.12639,14.12639,0,0,1,0-28.19762" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
||||
<path d="M424.01111,635.28265h39.81214V582.19979H424.01111Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
||||
<path d="M490.36468,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15393-.59852A6.62668,6.62668,0,1,0,482.80568,590.21q-.2203-.22491-.44457-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39414-.10218-.59056-.15262a6.63957,6.63957,0,1,0-13.10086,0c-.1964.05042-.39414.09687-.59056.15262a6.62767,6.62767,0,1,0-11.39688,6.56369,26.52754,26.52754,0,1,0,44.23127,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
|
||||
<path d="M437.28182,555.65836H477.094V529.11693H437.28182Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
||||
<path d="M490.36468,545.70532a3.31768,3.31768,0,0,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
|
||||
<path d="M317.84538,466.081a3.31768,3.31768,0,0,1-3.31767-3.31768,9.953,9.953,0,1,0-19.90608,0,3.31768,3.31768,0,1,1-6.63535,0,16.58839,16.58839,0,1,1,33.17678,0,3.31768,3.31768,0,0,1-3.31768,3.31768" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
|
||||
<path d="M370.92825,635.28265h79.62429A26.5409,26.5409,0,0,0,477.094,608.74122v-92.895H397.46968a26.54091,26.54091,0,0,0-26.54143,26.54143Z" transform="translate(-35.5 -118.5)" fill="#ffff50" fill-rule="evenodd" />
|
||||
<path d="M457.21444,556.98543H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0-66.10674H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.29459H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414M477.094,474.19076c-.01592,0-.0292-.008-.04512-.00663-4.10064.13934-6.04083,4.24132-7.75274,7.86024-1.78623,3.78215-3.16771,6.24122-5.43171,6.16691-2.50685-.09024-3.94007-2.92222-5.45825-5.91874-1.74377-3.44243-3.73438-7.34667-7.91333-7.20069-4.04227.138-5.98907,3.70784-7.70631,6.857-1.82738,3.35484-3.07084,5.39455-5.46887,5.30033-2.55727-.09289-3.91619-2.39536-5.48877-5.06013-1.75306-2.96733-3.77951-6.30359-7.8775-6.18946-3.97326.13669-5.92537,3.16507-7.64791,5.83912-1.82207,2.82666-3.09872,4.5492-5.52725,4.447-2.61832-.09289-3.9706-2.00388-5.53522-4.21611-1.757-2.4856-3.737-5.299-7.82308-5.16231-3.88567.13271-5.83779,2.61434-7.559,4.80135-1.635,2.07555-2.9116,3.71846-5.61218,3.615a1.32793,1.32793,0,1,0-.09555,2.65414c4.00377.134,6.03154-2.38873,7.79257-4.6275,1.562-1.9853,2.91027-3.69855,5.56441-3.78879,2.55594-.10882,3.75429,1.47968,5.56707,4.04093,1.7212,2.43385,3.67465,5.19416,7.60545,5.33616,4.11789.138,6.09921-2.93946,7.8536-5.66261,1.56861-2.43385,2.92221-4.53461,5.50734-4.62352,2.37944-.08892,3.67466,1.79154,5.50072,4.885,1.72121,2.91557,3.67069,6.21865,7.67977,6.36463,4.14709.14332,6.14965-3.47693,7.89475-6.68181,1.51155-2.77092,2.93814-5.38791,5.46621-5.4755,2.37944-.05573,3.62025,2.11668,5.45558,5.74622,1.71459,3.388,3.65875,7.22591,7.73019,7.37321l.22429.004c4.06614,0,5.99571-4.08074,7.70364-7.68905,1.51154-3.19825,2.94211-6.21069,5.3972-6.33411Z" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
|
||||
<path d="M344.38682,635.28265h53.08286V582.19979H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
||||
<path d="M424.01111,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15394-.59852A6.62667,6.62667,0,1,0,416.45211,590.21q-.2203-.22491-.44458-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39413-.10218-.59054-.15262a6.63957,6.63957,0,1,0-13.10084,0c-.19641.05042-.39414.09687-.59055.15262a6.62767,6.62767,0,1,0-11.39689,6.56369,26.52755,26.52755,0,1,0,44.2313,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
|
||||
<path d="M344.38682,555.65836h53.08286V529.11693H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
|
||||
<path d="M410.74039,545.70532a3.31768,3.31768,0,1,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
|
||||
<path d="M424.01111,447.8338a3.60349,3.60349,0,0,1-.65028-.06636,3.34415,3.34415,0,0,1-.62372-.18579,3.44679,3.44679,0,0,1-.572-.30522,5.02708,5.02708,0,0,1-.50429-.4114,3.88726,3.88726,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.60248,3.60248,0,0,1,.06636-.65027,3.82638,3.82638,0,0,1,.18447-.62373,3.48858,3.48858,0,0,1,.30656-.57064,3.197,3.197,0,0,1,.91436-.91568,3.44685,3.44685,0,0,1,.572-.30523,3.344,3.344,0,0,1,.62372-.18578,3.06907,3.06907,0,0,1,1.30053,0,3.22332,3.22332,0,0,1,1.19436.491,5.02835,5.02835,0,0,1,.50429.41139,4.8801,4.8801,0,0,1,.41139.50429,3.38246,3.38246,0,0,1,.30522.57064,3.47806,3.47806,0,0,1,.25215,1.274A3.36394,3.36394,0,0,1,426.36,446.865a5.02708,5.02708,0,0,1-.50429.4114,3.3057,3.3057,0,0,1-1.84463.55737m26.54143-1.65884a3.38754,3.38754,0,0,1-2.35024-.96877,5.04185,5.04185,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.38659,3.38659,0,0,1,.96744-2.34892,5.02559,5.02559,0,0,1,.50429-.41139,3.44685,3.44685,0,0,1,.572-.30523,3.3432,3.3432,0,0,1,.62373-.18579,3.06952,3.06952,0,0,1,1.30052,0,3.22356,3.22356,0,0,1,1.19436.491,5.02559,5.02559,0,0,1,.50429.41139,3.38792,3.38792,0,0,1,.96876,2.34892,3.72635,3.72635,0,0,1-.06636.65026,3.37387,3.37387,0,0,1-.18579.62373,4.71469,4.71469,0,0,1-.30522.57064,4.8801,4.8801,0,0,1-.41139.50429,5.02559,5.02559,0,0,1-.50429.41139,3.30547,3.30547,0,0,1-1.84463.55737" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
8
tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
// This file is not used in compilation. It is here just for a nice editor experience.
|
||||
"extends": "@docusaurus/tsconfig",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
},
|
||||
"exclude": [".docusaurus", "build"]
|
||||
}
|