From c0f7210725e83e84eae94eeba8ffd43e588d5438 Mon Sep 17 00:00:00 2001 From: Alexandre Teles Date: Sun, 4 Sep 2022 00:36:15 -0300 Subject: [PATCH] removes /app, adds /contributors and cleans-up code --- config.toml | 6 +- main.py | 22 ++++---- modules/Releases.py | 115 +++++++++++++++++++++----------------- modules/ResponseFields.py | 38 ++++++++++++- modules/ResponseModels.py | 53 ++++-------------- 5 files changed, 125 insertions(+), 109 deletions(-) diff --git a/config.toml b/config.toml index 2b483cd..4327c3a 100644 --- a/config.toml +++ b/config.toml @@ -10,9 +10,9 @@ The team also have a [Discord Server](https://revanced.app/discord) if you need ## API Endpoints -* [apps](/apps) - Returns all currently patchable apps * [tools](/tools) - Returns the latest version of all ReVanced tools and Vanced MicroG * [patches](/patches) - Returns the latest version of all ReVanced patches +* [contributors](/contributors) - Returns contributors for all ReVanced projects ## Additional Information @@ -28,7 +28,7 @@ The team also have a [Discord Server](https://revanced.app/discord) if you need Godspeed 💀 """ -version = "0.1 alpha" +version = "0.2 alpha" [license] @@ -49,4 +49,4 @@ expire = 60 [app] -repositories = ["TeamVanced/VancedMicroG", "revanced/revanced-cli", "revanced/revanced-patches", "revanced/revanced-integrations"] \ No newline at end of file +repositories = ["TeamVanced/VancedMicroG", "revanced/revanced-cli", "revanced/revanced-patches", "revanced/revanced-integrations", "revanced/revanced-manager"] \ No newline at end of file diff --git a/main.py b/main.py index 7af90e1..63cc660 100755 --- a/main.py +++ b/main.py @@ -77,17 +77,6 @@ async def tools(request: Request, response: Response) -> dict: """ return await releases.get_latest_releases(config['app']['repositories']) -@app.get('/apps', response_model=ResponseModels.AppsResponseModel) -@limiter.limit(config['slowapi']['limit']) -@cache(config['cache']['expire']) -async def apps(request: Request, response: Response) -> dict: - """Get patchable apps. - - Returns: - json: list of supported apps - """ - return await releases.get_patchable_apps() - @app.get('/patches', response_model=ResponseModels.PatchesResponseModel) @limiter.limit(config['slowapi']['limit']) @cache(config['cache']['expire']) @@ -100,6 +89,17 @@ async def patches(request: Request, response: Response) -> dict: return await releases.get_patches_json() +@app.get('/contributors', response_model=ResponseModels.ContributorsResponseModel) +@limiter.limit(config['slowapi']['limit']) +@cache(config['cache']['expire']) +async def contributors(request: Request, response: Response) -> dict: + """Get contributors. + + Returns: + json: list of contributors + """ + return await releases.get_contributors(config['app']['repositories']) + @app.on_event("startup") async def startup() -> None: redis_url = f"{redis_config['url']}:{redis_config['port']}/{redis_config['collection']}" diff --git a/modules/Releases.py b/modules/Releases.py index 91421de..aa1b504 100644 --- a/modules/Releases.py +++ b/modules/Releases.py @@ -12,33 +12,34 @@ class Releases: 'Authorization': "token " + os.environ['GITHUB_TOKEN'] } - async def get_release(self, client: httpx_cache.AsyncClient, repository: str) -> list: - """Get assets from latest release in a given repository. - - Args: - client (httpx_cache.AsyncClient): httpx_cache reusable async client - repository (str): Github's standard username/repository notation - - Returns: - dict: dictionary of filename and download url - """ + async def _get_release(self, client: httpx_cache.AsyncClient, repository: str) -> list: + # Get assets from latest release in a given repository. + # + # Args: + # client (httpx_cache.AsyncClient): httpx_cache reusable async client + # repository (str): Github's standard username/repository notation + # + # Returns: + # dict: dictionary of filename and download url + assets = [] response = await client.get(f"https://api.github.com/repos/{repository}/releases/latest") - release_assets = response.json()['assets'] + if response.status_code == 200: + release_assets = response.json()['assets'] - for asset in release_assets: - assets.append({ 'repository': repository, - 'name': asset['name'], - 'size': asset['size'], - 'browser_download_url': asset['browser_download_url'], - 'content_type': asset['content_type'] - }) - + for asset in release_assets: + assets.append({ 'repository': repository, + 'name': asset['name'], + 'size': asset['size'], + 'browser_download_url': asset['browser_download_url'], + 'content_type': asset['content_type'] + }) + return assets async def get_latest_releases(self, repositories: list) -> dict: - """Runs get_release() in parallel for each repository. + """Runs get_release() asynchronously for each repository. Args: repositories (list): List of repositories in Github's standard username/repository notation @@ -46,45 +47,18 @@ class Releases: Returns: dict: A dictionary containing assets from each repository """ + releases: Dict[str, List] = {} releases['tools'] = [] async with httpx_cache.AsyncClient(headers=self.headers, http2=True) as client: for repository in repositories: - files = await self.get_release(client, repository) - for file in files: - releases['tools'].append(file) + files = await self._get_release(client, repository) + if files: + for file in files: + releases['tools'].append(file) return releases - - async def _get_patches_readme(self, client: httpx_cache.AsyncClient) -> str: - # Get revanced-patches repository's README.md. - # - # Returns: - # str: README.md content - # - - response = await client.get(f"https://api.github.com/repos/revanced/revanced-patches/contents/README.md") - - return b64decode(response.json()['content']).decode('utf-8') - - async def get_patchable_apps(self) -> dict: - """Get patchable apps from revanced-patches repository. - - Returns: - dict: Apps available for patching - """ - packages: Dict[str, List] = {} - packages['apps'] = [] - - async with httpx_cache.AsyncClient(headers=self.headers, http2=True) as client: - content = await self._get_patches_readme(client) - - for line in content.splitlines(): - if line.startswith(u'###'): - packages['apps'].append(line.split('`')[1]) - - return packages async def _get_patches_json(self, client: httpx_cache.AsyncClient) -> dict: # Get revanced-patches repository's README.md. @@ -106,6 +80,7 @@ class Releases: Returns: dict: Patches available for a given app """ + async def generate_simplified_json(payload: dict) -> dict: return {} @@ -116,3 +91,39 @@ class Releases: return await generate_simplified_json(content) return content + + async def _get_contributors(self, client: httpx_cache.AsyncClient, repository: str) -> list: + # Get contributors from a given repository. + # + # Args: + # client (httpx_cache.AsyncClient): httpx_cache reusable async client + # repository (str): Github's standard username/repository notation + # + # Returns: + # list: a list of dictionaries containing the repository's contributors + + response = await client.get(f"https://api.github.com/repos/{repository}/contributors") + + return response.json() + + async def get_contributors(self, repositories: list) -> dict: + """Runs get_contributors() asynchronously for each repository. + + Args: + repositories (list): List of repositories in Github's standard username/repository notation + + Returns: + dict: A dictionary containing the contributors from each repository + """ + + contributors: Dict[str, List] = {} + contributors['repositories'] = [] + + async with httpx_cache.AsyncClient(headers=self.headers, http2=True) as client: + for repository in repositories: + if 'revanced' in repository: + repo_contributors = await self._get_contributors(client, repository) + data = { 'name': repository, 'contributors': repo_contributors } + contributors['repositories'].append(data) + + return contributors \ No newline at end of file diff --git a/modules/ResponseFields.py b/modules/ResponseFields.py index ba71048..1628123 100644 --- a/modules/ResponseFields.py +++ b/modules/ResponseFields.py @@ -30,4 +30,40 @@ class PatchesResponseFields(BaseModel): version: str excluded: bool dependencies: list[ str ] | None - compatiblePackages: list[ CompatiblePackagesResponseFields ] \ No newline at end of file + compatiblePackages: list[ CompatiblePackagesResponseFields ] + +class ContributorFields(BaseModel): + """Implements the fields for each contributor in the /contributors endpoint. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + login: str + id: str + node_id: str + avatar_url: str + gravatar_id: str + url: str + html_url: str + followers_url: str + following_url: str + gists_url: str + starred_url: str + subscriptions_url: str + organizations_url: str + repos_url: str + events_url: str + received_events_url: str + type: str + site_admin: str + contributions: int + +class ContributorsResponseFields(BaseModel): + """Implements the fields for each repository in the /contributors endpoint + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + name: str + contributors: list[ ContributorFields ] \ No newline at end of file diff --git a/modules/ResponseModels.py b/modules/ResponseModels.py index 9030fd7..98bab73 100644 --- a/modules/ResponseModels.py +++ b/modules/ResponseModels.py @@ -1,48 +1,8 @@ import modules.ResponseFields as ResponseFields -from pydantic import BaseModel, create_model +from pydantic import BaseModel """Implements pydantic models and model generator for the API's responses.""" -class ModelGenerator(): - - """Generates a pydantic model from a dictionary.""" - - def __make_model(self, v, name): - # Parses a dictionary and creates a pydantic model from it. - # - # Args: - # v: key value - # name: key name - # - # Returns: - # pydantic.BaseModel: Generated pydantic model - - if type(v) is dict: - return create_model(name, **{k: self.__make_model(v, k) for k, v in v.items()}), ... - return None, v - - def generate(self, v: dict, name: str): - - """Returns a pydantic model from a dictionary. - - Args: - v (Dict): JSON dictionary - name (str): Model name - - Returns: - pydantic.BaseModel: Generated pydantic model - """ - return self.__make_model(v, name)[0] - -class AppsResponseModel(BaseModel): - """Implements the JSON response model for the /apps endpoint. - - Args: - BaseModel (pydantic.BaseModel): BaseModel from pydantic - """ - - apps: list[str] - class ToolsResponseModel(BaseModel): """Implements the JSON response model for the /tools endpoint. @@ -59,4 +19,13 @@ class PatchesResponseModel(BaseModel): BaseModel (pydantic.BaseModel): BaseModel from pydantic """ - __root__: list[ ResponseFields.PatchesResponseFields ] \ No newline at end of file + __root__: list[ ResponseFields.PatchesResponseFields ] + +class ContributorsResponseModel(BaseModel): + """Implements the JSON response model for the /contributors endpoint. + + Args: + BaseModel (pydantic.BaseModel): BaseModel from pydantic + """ + + repositories: list[ ResponseFields.ContributorsResponseFields ] \ No newline at end of file