From 3a128c4661b161dc9961837056a3b64b84824162 Mon Sep 17 00:00:00 2001 From: Alexandre Teles Date: Wed, 22 Nov 2023 11:43:57 -0300 Subject: [PATCH] feat: manager-related endpoints --- api/backends/github.py | 45 ++++++++++++++++++++++++++++++- api/connections.py | 32 ---------------------- api/manager.py | 61 ++++++++++++++++++++++++++++++++++++++++++ api/models/manager.py | 32 ++++++++++++++++++++++ api/models/socials.py | 14 +--------- api/socials.py | 4 +-- config.py | 12 +++++++-- 7 files changed, 150 insertions(+), 50 deletions(-) delete mode 100644 api/connections.py create mode 100644 api/manager.py create mode 100644 api/models/manager.py diff --git a/api/backends/github.py b/api/backends/github.py index 3ef60a7..08ee8df 100644 --- a/api/backends/github.py +++ b/api/backends/github.py @@ -6,7 +6,7 @@ from typing import Optional import ujson from aiohttp import ClientResponse from sanic import SanicException -from cytoolz import filter, map, partial +from cytoolz import filter, map, partial, curry, pipe from cytoolz.dicttoolz import get_in, keyfilter from cytoolz.itertoolz import mapcat, pluck @@ -395,3 +395,46 @@ class Github(Backend): ) return list(map(lambda pair: transform(*pair), zip(results, repositories))) + + async def generate_custom_sources( + self, repositories: list[GithubRepository], dev: bool + ) -> dict[str, dict[str, str]]: + """Generate a custom sources dictionary for a set of repositories. + + Args: + repositories (list[GithubRepository]): The repositories for which to generate the sources. + dev (bool): If we should get the latest pre-release instead. + + Returns: + dict[str, dict[str, str]]: A dictionary containing the custom sources. + """ + + # Helper functions + filter_by_name = curry(lambda name, item: name in item["name"]) + filter_patches_jar = curry( + lambda item: "patches" in item["name"] and item["name"].endswith(".jar") + ) + get_fields = curry( + lambda fields, item: {field: item[field] for field in fields} + ) + rename_key = curry( + lambda old, new, d: {new if k == old else k: v for k, v in d.items()} + ) + + sources = await self.compat_get_tools(repositories, dev) + + patches = pipe( + sources, + lambda items: next(filter(filter_patches_jar, items), None), + get_fields(["version", "browser_download_url"]), + rename_key("browser_download_url", "url"), + ) + + integrations = pipe( + sources, + lambda items: next(filter(filter_by_name("integrations"), items), None), + get_fields(["version", "browser_download_url"]), + rename_key("browser_download_url", "url"), + ) + + return {"patches": patches, "integrations": integrations} diff --git a/api/connections.py b/api/connections.py deleted file mode 100644 index 0d1595f..0000000 --- a/api/connections.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -This module provides a blueprint for the connections endpoint. - -Routes: - - GET /connections: Get ReVanced connection links. -""" - -import os -from sanic import Blueprint, Request -from sanic.response import JSONResponse, json -from sanic_ext import openapi - -from api.models.socials import ConnectionsResponseModel -from config import social_links - -connections: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) - - -@connections.get("/connections") -@openapi.definition( - summary="Get ReVanced connection links", - response=[ConnectionsResponseModel], -) -async def root(request: Request) -> JSONResponse: - """ - Returns a JSONResponse with a dictionary containing ReVanced connection links. - - **Returns:** - - JSONResponse: A Sanic JSONResponse instance containing a dictionary with the connection links. - """ - data: dict[str, dict] = {"connections": social_links} - return json(data, status=200) diff --git a/api/manager.py b/api/manager.py new file mode 100644 index 0000000..87a3e60 --- /dev/null +++ b/api/manager.py @@ -0,0 +1,61 @@ +""" +This module provides ReVanced Manager specific endpoints. + +Routes: + - GET /manager/bootstrap: Get a list of the main ReVanced tools. + - GET /manager/sources: Get a list of ReVanced sources. +""" + +import os +from sanic import Blueprint, Request +from sanic.response import JSONResponse, json +from sanic_ext import openapi + +from api.backends.github import GithubRepository, Github + +from api.models.manager import BootsrapResponseModel, CustomSourcesResponseModel +from config import compat_repositories, owner + +manager: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) + +github_backend: Github = Github() + + +@manager.get("/manager/bootstrap") +@openapi.definition( + summary="Get a list of the main ReVanced tools", + response=[BootsrapResponseModel], +) +async def bootstrap(request: Request) -> JSONResponse: + """ + Returns a JSONResponse with a list of the main ReVanced tools. + + **Returns:** + - JSONResponse: A Sanic JSONResponse instance containing a list with the tool names. + """ + data: dict[str, dict] = {"tools": compat_repositories} + return json(data, status=200) + + +@manager.get("/manager/custom-source") +@openapi.definition( + summary="Get a list of ReVanced sources", + response=[CustomSourcesResponseModel], +) +async def custom_sources(request: Request) -> JSONResponse: + """ + Returns a JSONResponse with a list of the main ReVanced sources. + + **Returns:** + - JSONResponse: A Sanic JSONResponse instance containing a list with the source names. + """ + data = await github_backend.generate_custom_sources( + repositories=[ + GithubRepository(owner=owner, name=repo) + for repo in compat_repositories + if "patches" in repo or "integrations" in repo + ], + dev=True if request.args.get("dev") else False, + ) + + return json(data, status=200) diff --git a/api/models/manager.py b/api/models/manager.py new file mode 100644 index 0000000..c7e39b3 --- /dev/null +++ b/api/models/manager.py @@ -0,0 +1,32 @@ +from pydantic import BaseModel + + +class BootsrapResponseModel(BaseModel): + """ + A Pydantic BaseModel that represents a list of available tools. + """ + + tools: list[str] + """ + A list of available tools. + """ + + +class CustomSourcesFields(BaseModel): + """ + Implements the fields for a source. + """ + + url: str + preferred: bool + + +class CustomSourcesResponseModel(BaseModel): + """ + A Pydantic BaseModel that represents a list of available sources. + """ + + _: dict[str, CustomSourcesFields] + """ + A list of available sources. + """ diff --git a/api/models/socials.py b/api/models/socials.py index c62a57e..45edad0 100644 --- a/api/models/socials.py +++ b/api/models/socials.py @@ -11,7 +11,7 @@ class SocialFields(BaseModel): preferred: bool -class ConnectionsResponseModel(BaseModel): +class SocialsResponseModel(BaseModel): """ A Pydantic BaseModel that represents a dictionary of social links. """ @@ -21,15 +21,3 @@ class ConnectionsResponseModel(BaseModel): A dictionary where the keys are the names of the social networks, and the values are the links to the profiles or pages. """ - - -class ConnectionsResponseModel(BaseModel): - """ - A Pydantic BaseModel that represents a dictionary of connection links. - """ - - connections: list[SocialFields] - """ - A dictionary where the keys are the names of the social networks, and - the values are the links to the profiles or pages. - """ diff --git a/api/socials.py b/api/socials.py index 1924139..f58dcc6 100644 --- a/api/socials.py +++ b/api/socials.py @@ -10,7 +10,7 @@ from sanic import Blueprint, Request from sanic.response import JSONResponse, json from sanic_ext import openapi -from api.models.socials import ConnectionsResponseModel +from api.models.socials import SocialsResponseModel from config import social_links socials: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) @@ -19,7 +19,7 @@ socials: Blueprint = Blueprint(os.path.basename(__file__).strip(".py")) @socials.get("/socials") @openapi.definition( summary="Get ReVanced socials", - response=[ConnectionsResponseModel], + response=[SocialsResponseModel], ) async def root(request: Request) -> JSONResponse: """ diff --git a/config.py b/config.py index 02ea01b..d430b9e 100644 --- a/config.py +++ b/config.py @@ -18,8 +18,16 @@ default_repository: str = ".github" api_versions: dict[str, list[str]] = { "old": ["compat"], - "v2": ["announcements", "donations", "github", "info", "login", "ping", "socials"], - "v3": ["connections"], + "v2": [ + "announcements", + "donations", + "github", + "info", + "login", + "ping", + "socials", + "manager", + ], } api_version: str = "v2"