From 79094621bc22d1163ca95b24024b1d535711cb5d Mon Sep 17 00:00:00 2001 From: ScuroNeko Date: Sun, 12 Nov 2023 00:26:15 +0300 Subject: [PATCH] first commit --- .gitignore | 107 +++++++++++++++++++++++++++++++++++ .gitmodules | 3 + data | 1 + main.py | 46 +++++++++++++++ requirements.txt | 2 + src/constants.py | 6 ++ src/errors.py | 17 ++++++ src/routes/achievements.py | 63 +++++++++++++++++++++ src/routes/adventureranks.py | 34 +++++++++++ src/routes/characters.py | 34 +++++++++++ src/routes/elements.py | 41 ++++++++++++++ src/types/achievements.py | 36 ++++++++++++ src/types/adventureranks.py | 16 ++++++ src/types/characters.py | 69 ++++++++++++++++++++++ src/types/elements.py | 10 ++++ src/utils.py | 32 +++++++++++ 16 files changed, 517 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 160000 data create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 src/constants.py create mode 100644 src/errors.py create mode 100644 src/routes/achievements.py create mode 100644 src/routes/adventureranks.py create mode 100644 src/routes/characters.py create mode 100644 src/routes/elements.py create mode 100644 src/types/achievements.py create mode 100644 src/types/adventureranks.py create mode 100644 src/types/characters.py create mode 100644 src/types/elements.py create mode 100644 src/utils.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b652a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,107 @@ +.idea/ +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf +.idea/**/aws.xml +.idea/**/contentModel.xml +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml +.idea/**/gradle.xml +.idea/**/libraries +cmake-build-*/ +.idea/**/mongoSettings.xml +*.iws +out/ +.idea_modules/ +atlassian-ide-plugin.xml +.idea/replstate.xml +.idea/sonarlint/ +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +.idea/httpRequests +.idea/caches/build_file_checksums.ser +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +*.manifest +*.spec +pip-log.txt +pip-delete-this-directory.txt +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ +*.mo +*.pot +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +instance/ +.webassets-cache +.scrapy +docs/_build/ +.pybuilder/ +target/ +.ipynb_checkpoints +profile_default/ +ipython_config.py +.pdm.toml +__pypackages__/ +celerybeat-schedule +celerybeat.pid +*.sage.py +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +.spyderproject +.spyproject +.ropeproject +/site +.mypy_cache/ +.dmypy.json +dmypy.json +.pyre/ +.pytype/ +cython_debug/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6090ebd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "data"] + path = data + url = git@nix13.pw:gidb/gidb-data.git diff --git a/data b/data new file mode 160000 index 0000000..50e2b26 --- /dev/null +++ b/data @@ -0,0 +1 @@ +Subproject commit 50e2b26da886b97536db8c511394145e06b2c597 diff --git a/main.py b/main.py new file mode 100644 index 0000000..7199e30 --- /dev/null +++ b/main.py @@ -0,0 +1,46 @@ +from fastapi import FastAPI +from fastapi.openapi.utils import get_openapi +from fastapi.responses import JSONResponse + +from src.errors import Response, NOT_FOUND +from src.routes.achievements import achievements +from src.routes.adventureranks import adventure_ranks +from src.routes.elements import elements +from src.routes.characters import characters + +app = FastAPI(title='Genshin Impact DB') + + +@app.exception_handler(404) +async def not_found(r, e): + return JSONResponse( + status_code=404, + content=Response(error=True, response=NOT_FOUND).model_dump() + ) + + +app.include_router(achievements) +app.include_router(adventure_ranks) +app.include_router(characters) +app.include_router(elements) + + +def custom_openapi(): + if app.openapi_schema: + return app.openapi_schema + + openapi_schema = get_openapi( + title="Genshin Impact DB", + version="4.2.0", + summary="This is a very custom OpenAPI schema", + description="Here's a longer description of the custom **OpenAPI** schema", + routes=app.routes + ) + openapi_schema["info"]["x-logo"] = { + "url": "https://upload.wikimedia.org/wikipedia/en/thumb/5/5d/Genshin_Impact_logo.svg/2560px-Genshin_Impact_logo.svg.png" + } + app.openapi_schema = openapi_schema + return app.openapi_schema + + +app.openapi = custom_openapi diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f0615cf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +fastapi +uvicorn \ No newline at end of file diff --git a/src/constants.py b/src/constants.py new file mode 100644 index 0000000..bfb9dea --- /dev/null +++ b/src/constants.py @@ -0,0 +1,6 @@ +version = '1.0' +DEFAULT_QUERY = ['English'] +DEFAULT_RESULT = 'Russian' + +LANGUAGES = ['English', 'Russian', 'Japanese'] +DATA_FOLDER = 'data' diff --git a/src/errors.py b/src/errors.py new file mode 100644 index 0000000..d83ad0b --- /dev/null +++ b/src/errors.py @@ -0,0 +1,17 @@ +from typing import Any, Optional + +from pydantic import BaseModel + + +class ErrorData(BaseModel): + error_code: int + error_text: str + + +class Response[T](BaseModel): + error: bool = False + response: T | ErrorData + + +# Constants +NOT_FOUND = ErrorData(error_code=1, error_text='Not Found') diff --git a/src/routes/achievements.py b/src/routes/achievements.py new file mode 100644 index 0000000..d8434e1 --- /dev/null +++ b/src/routes/achievements.py @@ -0,0 +1,63 @@ +from http.client import HTTPException + +from fastapi import APIRouter + +from src.errors import Response +from src.types.achievements import AchievementGroups, Achievement +from src.utils import parse_query_langs, load_index, load_file, parse_result_lang + +achievements = APIRouter(prefix='/achievements', tags=['Achievements']) + + +@achievements.get('/') +async def get_achievements(query_field: str = 'names', query_languages: str = 'eng') -> Response: + query_langs = parse_query_langs(query_languages) + response = [] + for query_lang in query_langs: + chars = load_index(query_lang, 'achievements') + response.append({query_lang: list(chars[query_field].keys())}) + return Response(error=False, response=response) + + +@achievements.get('/{name}') +async def get_achievement_group(name: str, query_languages: str = 'eng', result_language: str = 'ru') -> Response[Achievement]: + query_langs = parse_query_langs(query_languages) + result_lang = parse_result_lang(result_language) + + filename = '' + for lang in query_langs: + index: dict[str, str] = load_index(lang, 'achievements')['names'] + for key, value in index.items(): + if key.lower().startswith(name.lower()): + filename = value + break + else: + raise HTTPException(404) + return Response[Achievement](response=load_file(result_lang, 'achievements', filename)) + + +@achievements.get('/groups') +async def get_achievement_groups(query_field: str = 'names', query_languages: str = 'eng') -> Response: + query_langs = parse_query_langs(query_languages) + response = [] + for query_lang in query_langs: + chars = load_index(query_lang, 'achievementgroups') + response.append({query_lang: list(chars[query_field].keys())}) + return Response(error=False, response=response) + + +@achievements.get('/groups/{name}') +async def get_achievement_group(name: str, query_languages: str = 'eng', result_language: str = 'ru') -> Response[AchievementGroups]: + query_langs = parse_query_langs(query_languages) + result_lang = parse_result_lang(result_language) + + filename = '' + for lang in query_langs: + index: dict[str, str] = load_index(lang, 'achievementgroups')['names'] + for key, value in index.items(): + if key.lower().startswith(query.lower()): + filename = value + break + else: + raise HTTPException(404) + return Response[AchievementGroups](response=load_file(result_lang, 'achievementgroups', filename)) diff --git a/src/routes/adventureranks.py b/src/routes/adventureranks.py new file mode 100644 index 0000000..7743867 --- /dev/null +++ b/src/routes/adventureranks.py @@ -0,0 +1,34 @@ +from fastapi import APIRouter, HTTPException + +from src.errors import Response +from src.types.adventureranks import AdventureRank +from src.utils import parse_query_langs, load_index, parse_result_lang, load_file + +adventure_ranks = APIRouter(prefix='/adventure_ranks', tags=['Adventure Ranks']) + + +@adventure_ranks.get('/') +async def query_characters(query_field: str = 'names', query_languages: str = 'eng') -> Response: + query_langs = parse_query_langs(query_languages) + response = [] + for query_lang in query_langs: + chars = load_index(query_lang, 'adventureranks') + response.append({query_lang: list(chars[query_field].keys())}) + return Response(error=False, response=response) + + +@adventure_ranks.get('/{query}') +async def query_character(query: str, query_languages: str = 'eng', result_language: str = 'ru') -> Response[AdventureRank]: + query_langs = parse_query_langs(query_languages) + result_lang = parse_result_lang(result_language) + + filename = '' + for lang in query_langs: + index: dict[str, str] = load_index(lang, 'adventureranks')['names'] + for key, value in index.items(): + if key.lower().startswith(query): + filename = value + break + else: + raise HTTPException(404) + return Response[AdventureRank](response=load_file(result_lang, 'adventureranks', filename)) diff --git a/src/routes/characters.py b/src/routes/characters.py new file mode 100644 index 0000000..f264cfd --- /dev/null +++ b/src/routes/characters.py @@ -0,0 +1,34 @@ +from fastapi import APIRouter, HTTPException + +from src.errors import Response +from src.types.characters import Character +from src.utils import load_index, parse_query_langs, parse_result_lang, load_file + +characters = APIRouter(prefix='/characters', tags=['Characters']) + + +@characters.get('/') +async def query_characters(query_field: str = 'names', query_languages: str = 'eng') -> Response: + query_langs = parse_query_langs(query_languages) + response = [] + for query_lang in query_langs: + chars = load_index(query_lang, 'characters') + response.append({query_lang: list(chars[query_field].keys())}) + return Response(error=False, response=response) + + +@characters.get('/{query}') +async def query_character(query: str, query_languages: str = 'eng', result_language: str = 'ru') -> Response[Character]: + query_langs = parse_query_langs(query_languages) + result_lang = parse_result_lang(result_language) + + filename = '' + for lang in query_langs: + index: dict[str, str] = load_index(lang, 'characters')['names'] + for key, value in index.items(): + if key.lower().startswith(query): + filename = value + break + else: + raise HTTPException(404) + return Response[Character](response=load_file(result_lang, 'characters', filename)) diff --git a/src/routes/elements.py b/src/routes/elements.py new file mode 100644 index 0000000..95afde5 --- /dev/null +++ b/src/routes/elements.py @@ -0,0 +1,41 @@ +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel + +from src.constants import LANGUAGES +from src.errors import Response +from src.types.characters import Character +from src.types.elements import Element +from src.utils import load_index, parse_query_langs, parse_result_lang, load_file + +elements = APIRouter(prefix='/elements', tags=['Elements']) + + +class QueryCharacters(BaseModel): + res: dict[str, list[str]] + + +@elements.get('/') +async def query_elements(query_field: str = 'names', query_languages: str = 'eng') -> Response: + query_langs = parse_query_langs(query_languages) + response: list[dict[str, list[str]]] = [] + for query_lang in query_langs: + chars = load_index(query_lang, 'elements') + response.append({query_lang: list(chars[query_field].keys())}) + return Response[list[dict[str, list[str]]]](error=False, response=response) + + +@elements.get('/{query}') +async def query_element(query: str, query_languages: str = 'eng', result_language: str = 'ru') -> Response[Element]: + query_langs = parse_query_langs(query_languages) + result_lang = parse_result_lang(result_language) + + filename = '' + for lang in query_langs: + index: dict[str, str] = load_index(lang, 'elements')['names'] + for key, value in index.items(): + if key.lower().startswith(query): + filename = value + break + else: + raise HTTPException(404) + return Response[Element](response=load_file(result_lang, 'elements', filename)) diff --git a/src/types/achievements.py b/src/types/achievements.py new file mode 100644 index 0000000..2e67b6c --- /dev/null +++ b/src/types/achievements.py @@ -0,0 +1,36 @@ +from typing import Optional + +from pydantic import BaseModel + + +class GroupReward(BaseModel): + name: str + + +class AchievementGroups(BaseModel): + name: str + sortorder: int + reward: GroupReward + + +# Achievement +class StageReward(BaseModel): + name: str + count: int + + +class Stage(BaseModel): + title: str + description: str + progress: int + reward: StageReward + + +class Achievement(BaseModel): + name: str + achievementgroup: str + sortorder: int + stages: int + stage1: Stage + stage2: Optional[Stage] + stage3: Optional[Stage] diff --git a/src/types/adventureranks.py b/src/types/adventureranks.py new file mode 100644 index 0000000..319f81f --- /dev/null +++ b/src/types/adventureranks.py @@ -0,0 +1,16 @@ +from typing import List, Optional + +from pydantic import BaseModel + + +class RewardItem(BaseModel): + name: str + count: int + type: str + + +class AdventureRank(BaseModel): + name: str + exp: Optional[int] = 0 + unlockdescription: str + reward: List[RewardItem] diff --git a/src/types/characters.py b/src/types/characters.py new file mode 100644 index 0000000..ecd3715 --- /dev/null +++ b/src/types/characters.py @@ -0,0 +1,69 @@ +from pydantic import BaseModel +from typing import List + + +class Cv(BaseModel): + english: str + chinese: str + japanese: str + korean: str + + +class Ascend1Item(BaseModel): + name: str + count: int + + +class Ascend2Item(BaseModel): + name: str + count: int + + +class Ascend3Item(BaseModel): + name: str + count: int + + +class Ascend4Item(BaseModel): + name: str + count: int + + +class Ascend5Item(BaseModel): + name: str + count: int + + +class Ascend6Item(BaseModel): + name: str + count: int + + +class Costs(BaseModel): + ascend1: List[Ascend1Item] + ascend2: List[Ascend2Item] + ascend3: List[Ascend3Item] + ascend4: List[Ascend4Item] + ascend5: List[Ascend5Item] + ascend6: List[Ascend6Item] + + +class Character(BaseModel): + name: str + fullname: str + title: str + description: str + rarity: str + element: str + weapontype: str + substat: str + gender: str + body: str + association: str + region: str + affiliation: str + birthdaymmdd: str + birthday: str + constellation: str + cv: Cv + costs: Costs \ No newline at end of file diff --git a/src/types/elements.py b/src/types/elements.py new file mode 100644 index 0000000..ec29f66 --- /dev/null +++ b/src/types/elements.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel + + +class Element(BaseModel): + name: str + type: str + color: str + region: str + archon: str + theme: str \ No newline at end of file diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..9f5a7e1 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,32 @@ +from json import load +from os.path import join + +from src.constants import LANGUAGES, DEFAULT_RESULT, DEFAULT_QUERY, DATA_FOLDER + + +def load_index(language: str, category: str): + with open(join(DATA_FOLDER, 'index', language, f'{category}.json'), 'r', encoding='utf-8') as f: + return load(f) + + +def load_file(lang: str, category: str, name: str) -> dict: + with open(join(DATA_FOLDER, lang, category, f'{name}.json'), 'r', encoding='utf-8') as f: + return load(f) + + +def parse_query_langs(langs: str) -> list[str]: + languages = langs.replace(' ', '').split(',') + query = [] + for i in languages: + for j in LANGUAGES: + if j.lower().startswith(i): + query.append(j) + return query if len(query) > 0 else DEFAULT_QUERY + + +def parse_result_lang(lang: str) -> str: + for l in LANGUAGES: + if l.lower().startswith(lang): + return l + else: + return DEFAULT_RESULT