removes /app, adds /contributors and cleans-up code

This commit is contained in:
Alexandre Teles 2022-09-04 00:36:15 -03:00
parent 6d9a6d0ff6
commit c0f7210725
5 changed files with 125 additions and 109 deletions

View File

@ -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"]
repositories = ["TeamVanced/VancedMicroG", "revanced/revanced-cli", "revanced/revanced-patches", "revanced/revanced-integrations", "revanced/revanced-manager"]

22
main.py
View File

@ -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']}"

View File

@ -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.
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
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,46 +47,19 @@ 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

View File

@ -31,3 +31,39 @@ class PatchesResponseFields(BaseModel):
excluded: bool
dependencies: list[ str ] | None
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 ]

View File

@ -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.
@ -60,3 +20,12 @@ class PatchesResponseModel(BaseModel):
"""
__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 ]