diff --git a/src/renders/images.py b/src/renders/images.py index 5a1aa03..fffcf2f 100644 --- a/src/renders/images.py +++ b/src/renders/images.py @@ -20,6 +20,10 @@ def __init__(self) -> None: async def get_pokemon_left_sprites(self) -> dict[PokemonType, tuple[str, str]]: return {pokemon: await self._create_left_sprite_urls(pokemon.national_no) for pokemon in PokemonType} + @file_cache("pokemon_right_sprites") + async def get_pokemon_right_sprites(self) -> dict[PokemonType, tuple[str, str]]: + return {pokemon: await self._create_right_sprite_urls(pokemon.national_no) for pokemon in PokemonType} + @file_cache("pokeball") async def get_pokeball(self) -> str: return await self._get_as_base64(POKEBALL_URL) @@ -41,3 +45,16 @@ async def _create_left_sprite_urls(self, national_no: int) -> tuple[str, str]: frame_2 = poke_url + "_2.png" return (await self._get_as_base64(frame_1), await self._get_as_base64(frame_2)) + + async def _create_right_sprite_urls(self, national_no: int) -> tuple[str, str]: + start_url = f"{SPRITE_BASE_URL}/o-r_hgss" + poke_url = start_url + "/o-r_hs_" + str(national_no).zfill(3) + + if national_no in [3, 25]: + frame_1 = poke_url + "_m-1.png" + frame_2 = poke_url + "_m-2.png" + else: + frame_1 = poke_url + "_1.png" + frame_2 = poke_url + "_2.png" + + return (await self._get_as_base64(frame_1), await self._get_as_base64(frame_2)) diff --git a/src/renders/svg.py b/src/renders/svg.py index 4027754..96236b9 100644 --- a/src/renders/svg.py +++ b/src/renders/svg.py @@ -2,10 +2,14 @@ import random from typing import Generator +from src.exceptions.base import InternalError from src.models.pokemon_type import PokemonType from src.renders.images import ImageLoader +from src.schemas.pokemons import PokemonFace from src.template import svgs as svgs_templates +pokemon_templates = {PokemonFace.LEFT: svgs_templates.pokemon_left, PokemonFace.RIGHT: svgs_templates.pokemon_right} + class SVGRenderer: pokeball: str @@ -16,26 +20,32 @@ async def prepare(cls, *, image_loader: ImageLoader): logging.info("Load images start") cls.pokeball = await image_loader.get_pokeball() cls.pokemon_left_sprites = await image_loader.get_pokemon_left_sprites() + cls.pokemon_right_sprites = await image_loader.get_pokemon_right_sprites() logging.info("Load images end") - def render_svg(self, *, pokemons: list[PokemonType], commit_point: int, username: str) -> str: + def render_svg(self, *, pokemons: list[PokemonType], commit_point: int, username: str, face: PokemonFace) -> str: return svgs_templates.base.format( username=username, commit_point=commit_point, n_pokemons=len(pokemons), - pokemons="\n".join(self._render_pokemons(pokemons)), + pokemons="\n".join(self._render_pokemons(pokemons, face)), poke_ball_url=self.pokeball, ) - def _render_pokemons(self, pokemons: list[PokemonType]) -> Generator[str, None, None]: + def _render_pokemons(self, pokemons: list[PokemonType], face: PokemonFace) -> Generator[str, None, None]: for idx, pokemon in enumerate(pokemons): num = idx + 1 duration = random.uniform(10, 15) offset = random.randint(-75, 80) delay = random.uniform(0, 10) - frame_1, frame_2 = self.pokemon_left_sprites[pokemon] + if face is PokemonFace.LEFT: + frame_1, frame_2 = self.pokemon_left_sprites[pokemon] + elif face is PokemonFace.RIGHT: + frame_1, frame_2 = self.pokemon_right_sprites[pokemon] + else: + raise InternalError - yield svgs_templates.pokemon.format( + yield pokemon_templates[face].format( num=num, duration=duration, offset=offset, delay=delay, frame_1=frame_1, frame_2=frame_2 ) diff --git a/src/routes/pokemons.py b/src/routes/pokemons.py index 659e0db..a12fc40 100644 --- a/src/routes/pokemons.py +++ b/src/routes/pokemons.py @@ -1,19 +1,18 @@ -from fastapi import Response +from fastapi import Query, Response from fastapi.routing import APIRouter from src.dependencies.db import SessionDep from src.dependencies.facades import PokemonsFacadeDep +from src.schemas.pokemons import PokemonFace router = APIRouter() @router.get("/pokemons/{username}") async def get_pokemons_svg( - username: str, - session: SessionDep, - pokemons_facade: PokemonsFacadeDep, + username: str, session: SessionDep, pokemons_facade: PokemonsFacadeDep, face: PokemonFace = Query(PokemonFace.LEFT) ): return Response( - content=await pokemons_facade.render_pokemons(session=session, username=username), + content=await pokemons_facade.render_pokemons(session=session, username=username, face=face), headers={"content-type": "image/svg+xml", "Cache-Control": "max-age=3600"}, ) diff --git a/src/schemas/pokemons.py b/src/schemas/pokemons.py new file mode 100644 index 0000000..7074d68 --- /dev/null +++ b/src/schemas/pokemons.py @@ -0,0 +1,6 @@ +from enum import Enum + + +class PokemonFace(str, Enum): + LEFT = "left" + RIGHT = "right" diff --git a/src/services/pokemons_facade.py b/src/services/pokemons_facade.py index 4a0e7e1..1046196 100644 --- a/src/services/pokemons_facade.py +++ b/src/services/pokemons_facade.py @@ -6,6 +6,7 @@ from src.exceptions.external import GithubAPIRequestFailedError, GithubAPIUnavailableError from src.external.github_api import GithubAPI from src.renders.svg import SVGRenderer +from src.schemas.pokemons import PokemonFace from src.services.user import UserService from src.setting import settings @@ -24,7 +25,7 @@ def __init__( self._renderer = renderer self._background_tasks = background_tasks - async def render_pokemons(self, *, session: AsyncSession, username: str) -> str: + async def render_pokemons(self, *, session: AsyncSession, username: str, face: PokemonFace) -> str: user = await self._user_service.get_user(session=session, username=username) if user is not None: @@ -43,7 +44,7 @@ async def render_pokemons(self, *, session: AsyncSession, username: str) -> str: raise GithubAPIUnavailableError from e return self._renderer.render_svg( - pokemons=user.pokemon_types, commit_point=user.total_commit_point, username=username + pokemons=user.pokemon_types, commit_point=user.total_commit_point, username=username, face=face ) async def _get_commit_points(self, username: str) -> dict[int, int]: diff --git a/src/template/svgs.py b/src/template/svgs.py index f80e56d..460fa54 100644 --- a/src/template/svgs.py +++ b/src/template/svgs.py @@ -8,4 +8,5 @@ def load_svg_template(path: str) -> str: base = load_svg_template("templates/svgs/base.svg") -pokemon = load_svg_template("templates/svgs/pokemon.svg") +pokemon_left = load_svg_template("templates/svgs/pokemon-left.svg") +pokemon_right = load_svg_template("templates/svgs/pokemon-right.svg") diff --git a/templates/svgs/base.svg b/templates/svgs/base.svg index 2748a3b..041c22f 100644 --- a/templates/svgs/base.svg +++ b/templates/svgs/base.svg @@ -25,16 +25,28 @@ .pokemon { opacity: 0; - animation-name: move-pokemon; + animation-name: move-pokemon-left; animation-duration: var(--pokemon-move-duration); animation-delay: var(--pokemon-move-delay); animation-iteration-count: infinite; animation-timing-function: linear; } - @keyframes move-pokemon { + + .pokemon.left-face { + animation-name: move-pokemon-left; + } + .pokemon.right-face { + animation-name: move-pokemon-right; + } + + @keyframes move-pokemon-left { 0% {opacity: 1; transform: translate(120%, calc(64px + var(--pokemon-y-offsets)));} 100% {opacity: 1; transform: translate(-120%, calc(64px + var(--pokemon-y-offsets)));} } + @keyframes move-pokemon-right { + 0% {opacity: 1; transform: translate(-120%, calc(64px + var(--pokemon-y-offsets)));} + 100% {opacity: 1; transform: translate(120%, calc(64px + var(--pokemon-y-offsets)));} + } diff --git a/templates/svgs/pokemon.svg b/templates/svgs/pokemon-left.svg similarity index 92% rename from templates/svgs/pokemon.svg rename to templates/svgs/pokemon-left.svg index f6e6519..bc2214a 100644 --- a/templates/svgs/pokemon.svg +++ b/templates/svgs/pokemon-left.svg @@ -8,7 +8,7 @@ } - + diff --git a/templates/svgs/pokemon-right.svg b/templates/svgs/pokemon-right.svg new file mode 100644 index 0000000..05c0216 --- /dev/null +++ b/templates/svgs/pokemon-right.svg @@ -0,0 +1,17 @@ + + + + + + + + + + \ No newline at end of file