mirror of
https://github.com/revanced/revanced-releases-api.git
synced 2025-04-30 22:44:25 +02:00
removes /app, adds /contributors and cleans-up code
This commit is contained in:
parent
6d9a6d0ff6
commit
c0f7210725
@ -10,9 +10,9 @@ The team also have a [Discord Server](https://revanced.app/discord) if you need
|
|||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
* [apps](/apps) - Returns all currently patchable apps
|
|
||||||
* [tools](/tools) - Returns the latest version of all ReVanced tools and Vanced MicroG
|
* [tools](/tools) - Returns the latest version of all ReVanced tools and Vanced MicroG
|
||||||
* [patches](/patches) - Returns the latest version of all ReVanced patches
|
* [patches](/patches) - Returns the latest version of all ReVanced patches
|
||||||
|
* [contributors](/contributors) - Returns contributors for all ReVanced projects
|
||||||
|
|
||||||
## Additional Information
|
## Additional Information
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ The team also have a [Discord Server](https://revanced.app/discord) if you need
|
|||||||
Godspeed 💀
|
Godspeed 💀
|
||||||
|
|
||||||
"""
|
"""
|
||||||
version = "0.1 alpha"
|
version = "0.2 alpha"
|
||||||
|
|
||||||
[license]
|
[license]
|
||||||
|
|
||||||
@ -49,4 +49,4 @@ expire = 60
|
|||||||
|
|
||||||
[app]
|
[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
22
main.py
@ -77,17 +77,6 @@ async def tools(request: Request, response: Response) -> dict:
|
|||||||
"""
|
"""
|
||||||
return await releases.get_latest_releases(config['app']['repositories'])
|
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)
|
@app.get('/patches', response_model=ResponseModels.PatchesResponseModel)
|
||||||
@limiter.limit(config['slowapi']['limit'])
|
@limiter.limit(config['slowapi']['limit'])
|
||||||
@cache(config['cache']['expire'])
|
@cache(config['cache']['expire'])
|
||||||
@ -100,6 +89,17 @@ async def patches(request: Request, response: Response) -> dict:
|
|||||||
|
|
||||||
return await releases.get_patches_json()
|
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")
|
@app.on_event("startup")
|
||||||
async def startup() -> None:
|
async def startup() -> None:
|
||||||
redis_url = f"{redis_config['url']}:{redis_config['port']}/{redis_config['collection']}"
|
redis_url = f"{redis_config['url']}:{redis_config['port']}/{redis_config['collection']}"
|
||||||
|
@ -12,33 +12,34 @@ class Releases:
|
|||||||
'Authorization': "token " + os.environ['GITHUB_TOKEN']
|
'Authorization': "token " + os.environ['GITHUB_TOKEN']
|
||||||
}
|
}
|
||||||
|
|
||||||
async def get_release(self, client: httpx_cache.AsyncClient, repository: str) -> list:
|
async def _get_release(self, client: httpx_cache.AsyncClient, repository: str) -> list:
|
||||||
"""Get assets from latest release in a given repository.
|
# 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 = []
|
assets = []
|
||||||
response = await client.get(f"https://api.github.com/repos/{repository}/releases/latest")
|
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:
|
for asset in release_assets:
|
||||||
assets.append({ 'repository': repository,
|
assets.append({ 'repository': repository,
|
||||||
'name': asset['name'],
|
'name': asset['name'],
|
||||||
'size': asset['size'],
|
'size': asset['size'],
|
||||||
'browser_download_url': asset['browser_download_url'],
|
'browser_download_url': asset['browser_download_url'],
|
||||||
'content_type': asset['content_type']
|
'content_type': asset['content_type']
|
||||||
})
|
})
|
||||||
|
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
async def get_latest_releases(self, repositories: list) -> dict:
|
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:
|
Args:
|
||||||
repositories (list): List of repositories in Github's standard username/repository notation
|
repositories (list): List of repositories in Github's standard username/repository notation
|
||||||
@ -46,46 +47,19 @@ class Releases:
|
|||||||
Returns:
|
Returns:
|
||||||
dict: A dictionary containing assets from each repository
|
dict: A dictionary containing assets from each repository
|
||||||
"""
|
"""
|
||||||
|
|
||||||
releases: Dict[str, List] = {}
|
releases: Dict[str, List] = {}
|
||||||
releases['tools'] = []
|
releases['tools'] = []
|
||||||
|
|
||||||
async with httpx_cache.AsyncClient(headers=self.headers, http2=True) as client:
|
async with httpx_cache.AsyncClient(headers=self.headers, http2=True) as client:
|
||||||
for repository in repositories:
|
for repository in repositories:
|
||||||
files = await self.get_release(client, repository)
|
files = await self._get_release(client, repository)
|
||||||
for file in files:
|
if files:
|
||||||
releases['tools'].append(file)
|
for file in files:
|
||||||
|
releases['tools'].append(file)
|
||||||
|
|
||||||
return releases
|
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:
|
async def _get_patches_json(self, client: httpx_cache.AsyncClient) -> dict:
|
||||||
# Get revanced-patches repository's README.md.
|
# Get revanced-patches repository's README.md.
|
||||||
#
|
#
|
||||||
@ -106,6 +80,7 @@ class Releases:
|
|||||||
Returns:
|
Returns:
|
||||||
dict: Patches available for a given app
|
dict: Patches available for a given app
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def generate_simplified_json(payload: dict) -> dict:
|
async def generate_simplified_json(payload: dict) -> dict:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@ -116,3 +91,39 @@ class Releases:
|
|||||||
return await generate_simplified_json(content)
|
return await generate_simplified_json(content)
|
||||||
|
|
||||||
return 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
|
@ -31,3 +31,39 @@ class PatchesResponseFields(BaseModel):
|
|||||||
excluded: bool
|
excluded: bool
|
||||||
dependencies: list[ str ] | None
|
dependencies: list[ str ] | None
|
||||||
compatiblePackages: list[ CompatiblePackagesResponseFields ]
|
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 ]
|
@ -1,48 +1,8 @@
|
|||||||
import modules.ResponseFields as ResponseFields
|
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."""
|
"""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):
|
class ToolsResponseModel(BaseModel):
|
||||||
"""Implements the JSON response model for the /tools endpoint.
|
"""Implements the JSON response model for the /tools endpoint.
|
||||||
|
|
||||||
@ -60,3 +20,12 @@ class PatchesResponseModel(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__root__: list[ ResponseFields.PatchesResponseFields ]
|
__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 ]
|
Loading…
x
Reference in New Issue
Block a user