mirror of
https://github.com/ReVanced/revanced-api.git
synced 2026-01-18 00:43:57 +00:00
feat: API rewrite (#2)
* feat: sanic framework settings * feat: initial implementation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor: backend changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: docstrings out of place * feat: more gh endpoints * ci: fix pre-commit issues * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * feat: app info * ci: merge CI and fix triggers * chore: bump deps * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: typing issues * chore: deps * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor: clean up returns * ci: spread jobs correctly * ci: move to quodana * ci: fix issues with python modules * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore: pycharm config * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor: improve code quality * feat: better README * ci: add quodana baseline config * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ci: fix quodana config * ci: more qodana stuff * ci: revert qodana changes * ci: python interpreter detection is broken * feat: tests * ci: testing * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ci: fix workflow names * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore: add deps * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * test: more tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * feat: /tools compat * feat: donations endpoint * feat: teams endpoint * fix: lock pydantic version * chore: deps * ci: docker builds * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ci: remove coverage action and others * ci: pre-commit fixes --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
cb52684edb
commit
45ef33741c
11
api/__init__.py
Normal file
11
api/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# api/__init__.py
|
||||
from sanic import Blueprint
|
||||
|
||||
from api.github import github
|
||||
from api.ping import ping
|
||||
from api.socials import socials
|
||||
from api.apkdl import apkdl
|
||||
from api.compat import github as old
|
||||
from api.donations import donations
|
||||
|
||||
api = Blueprint.group(ping, github, socials, donations, apkdl, old, url_prefix="/")
|
||||
32
api/apkdl.py
Normal file
32
api/apkdl.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
This module provides a blueprint for the app endpoint.
|
||||
|
||||
Routes:
|
||||
- GET /app/info: Get app info.
|
||||
"""
|
||||
|
||||
from sanic import Blueprint, Request
|
||||
from sanic.response import JSONResponse, json
|
||||
from sanic_ext import openapi
|
||||
|
||||
from api.backends.apkdl import ApkDl
|
||||
from api.backends.entities import AppInfo
|
||||
from api.models.appinfo import AppInfoModel
|
||||
|
||||
from config import api_version
|
||||
|
||||
apkdl: Blueprint = Blueprint("app", version=api_version)
|
||||
|
||||
apkdl_backend: ApkDl = ApkDl()
|
||||
|
||||
|
||||
@apkdl.get("/app/info/<app_id:str>")
|
||||
@openapi.definition(
|
||||
summary="Get information about an app",
|
||||
response=[AppInfoModel],
|
||||
)
|
||||
async def root(request: Request, app_id: str) -> JSONResponse:
|
||||
data: dict[str, AppInfo] = {
|
||||
"app_info": await apkdl_backend.get_app_info(package_name=app_id)
|
||||
}
|
||||
return json(data, status=200)
|
||||
0
api/backends/__init__.py
Normal file
0
api/backends/__init__.py
Normal file
64
api/backends/apkdl.py
Normal file
64
api/backends/apkdl.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from base64 import b64encode
|
||||
|
||||
from aiohttp import ClientResponse
|
||||
from bs4 import BeautifulSoup
|
||||
from sanic import SanicException
|
||||
from toolz.functoolz import compose
|
||||
|
||||
from api.backends.backend import AppInfoProvider
|
||||
from api.backends.entities import AppInfo
|
||||
from api.utils.http_utils import http_get
|
||||
|
||||
name: str = "apkdl"
|
||||
base_url: str = "https://apk-dl.com"
|
||||
|
||||
|
||||
class ApkDl(AppInfoProvider):
|
||||
def __init__(self):
|
||||
super().__init__(name, base_url)
|
||||
|
||||
async def get_app_info(self, package_name: str) -> AppInfo:
|
||||
"""Fetches information about an Android app from the ApkDl website.
|
||||
|
||||
Args:
|
||||
package_name (str): The package name of the app to fetch.
|
||||
|
||||
Returns:
|
||||
AppInfo: An AppInfo object containing the name, category, and logo of the app.
|
||||
|
||||
Raises:
|
||||
SanicException: If the HTTP request fails or the app data is incomplete or not found.
|
||||
"""
|
||||
app_url: str = f"{base_url}/{package_name}"
|
||||
response: ClientResponse = await http_get(headers={}, url=app_url)
|
||||
if response.status != 200:
|
||||
raise SanicException(
|
||||
f"ApkDl: {response.status}", status_code=response.status
|
||||
)
|
||||
page = BeautifulSoup(await response.read(), "lxml")
|
||||
find_div_text = compose(
|
||||
lambda d: d.find_next_sibling("div"),
|
||||
lambda d: page.find("div", text=d),
|
||||
)
|
||||
fetch_logo_url = compose(
|
||||
lambda div: div.img["src"],
|
||||
lambda _: page.find("div", {"class": "logo"}),
|
||||
)
|
||||
logo_response: ClientResponse = await http_get(
|
||||
headers={}, url=fetch_logo_url(None)
|
||||
)
|
||||
logo: str = (
|
||||
f"data:image/png;base64,{b64encode(await logo_response.content.read()).decode('utf-8')}"
|
||||
if logo_response.status == 200
|
||||
else ""
|
||||
)
|
||||
app_data = dict(
|
||||
name=find_div_text("App Name").text,
|
||||
category=find_div_text("Category").text,
|
||||
logo=logo,
|
||||
)
|
||||
if not all(app_data.values()):
|
||||
raise SanicException(
|
||||
"ApkDl: App data incomplete or not found", status_code=500
|
||||
)
|
||||
return AppInfo(**app_data)
|
||||
91
api/backends/backend.py
Normal file
91
api/backends/backend.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from abc import abstractmethod
|
||||
from typing import Any, Protocol
|
||||
|
||||
from api.backends.entities import *
|
||||
|
||||
|
||||
class Backend(Protocol):
|
||||
"""Interface for a generic backend.
|
||||
|
||||
Attributes:
|
||||
name (str): Name of the backend.
|
||||
base_url (str): Base URL of the backend.
|
||||
|
||||
Methods:
|
||||
list_releases: Retrieve a list of releases.
|
||||
get_release_by_tag_name: Retrieve a release by its tag name.
|
||||
get_latest_release: Retrieve the latest release.
|
||||
get_latest_pre_release: Retrieve the latest pre-release.
|
||||
get_release_notes: Retrieve the release notes of a specific release.
|
||||
get_contributors: Retrieve the list of contributors.
|
||||
get_patches: Retrieve the patches of a specific release.
|
||||
"""
|
||||
|
||||
name: str
|
||||
base_url: str
|
||||
|
||||
def __init__(self, name: str, base_url: str):
|
||||
self.name = name
|
||||
self.base_url = base_url
|
||||
|
||||
@abstractmethod
|
||||
async def list_releases(self, *args: Any, **kwargs: Any) -> list[Release]:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def get_release_by_tag_name(self, *args: Any, **kwargs: Any) -> Release:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def get_latest_release(self, *args: Any, **kwargs: Any) -> Release:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def get_latest_pre_release(self, *args: Any, **kwargs: Any) -> Release:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def get_contributors(self, *args: Any, **kwargs: Any) -> list[Contributor]:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def get_patches(self, *args: Any, **kwargs: Any) -> list[dict]:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def get_team_members(self, *args: Any, **kwargs: Any) -> list[Contributor]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Repository:
|
||||
"""A repository that communicates with a specific backend.
|
||||
|
||||
Attributes:
|
||||
backend (Backend): The backend instance used to communicate with the repository.
|
||||
"""
|
||||
|
||||
def __init__(self, backend: Backend):
|
||||
self.backend = backend
|
||||
|
||||
|
||||
class AppInfoProvider(Protocol):
|
||||
"""Interface for a generic app info provider.
|
||||
|
||||
Attributes:
|
||||
name (str): Name of the app info provider.
|
||||
base_url (str): Base URL of the app info provider.
|
||||
|
||||
Methods:
|
||||
get_app_info: Retrieve information about an app.
|
||||
"""
|
||||
|
||||
name: str
|
||||
base_url: str
|
||||
|
||||
def __init__(self, name: str, base_url: str):
|
||||
self.name = name
|
||||
self.base_url = base_url
|
||||
|
||||
@abstractmethod
|
||||
async def get_app_info(self, *args: Any, **kwargs: Any) -> AppInfo:
|
||||
raise NotImplementedError
|
||||
126
api/backends/entities.py
Normal file
126
api/backends/entities.py
Normal file
@@ -0,0 +1,126 @@
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Metadata(dict):
|
||||
"""
|
||||
Represents the metadata of a release.
|
||||
|
||||
Attributes:
|
||||
- tag_name (str): The name of the release tag.
|
||||
- name (str): The name of the release.
|
||||
- body (str): The body of the release.
|
||||
- draft (bool): Whether the release is a draft.
|
||||
- prerelease (bool): Whether the release is a prerelease.
|
||||
- created_at (str): The creation date of the release.
|
||||
- published_at (str): The publication date of the release.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tag_name: str,
|
||||
name: str,
|
||||
draft: bool,
|
||||
prerelease: bool,
|
||||
created_at: str,
|
||||
published_at: str,
|
||||
body: str,
|
||||
repository: Optional[str] = None,
|
||||
):
|
||||
dict.__init__(
|
||||
self,
|
||||
tag_name=tag_name,
|
||||
name=name,
|
||||
draft=draft,
|
||||
prerelease=prerelease,
|
||||
created_at=created_at,
|
||||
published_at=published_at,
|
||||
body=body,
|
||||
repository=repository,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Asset(dict):
|
||||
"""
|
||||
Represents an asset in a release.
|
||||
|
||||
Attributes:
|
||||
- name (str): The name of the asset.
|
||||
- content_type (str): The MIME type of the asset content.
|
||||
- download_url (str): The URL to download the asset.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, content_type: str, browser_download_url: str):
|
||||
dict.__init__(
|
||||
self,
|
||||
name=name,
|
||||
content_type=content_type,
|
||||
browser_download_url=browser_download_url,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Release(dict):
|
||||
"""
|
||||
Represents a release.
|
||||
|
||||
Attributes:
|
||||
- metadata (Metadata): The metadata of the release.
|
||||
- assets (list[Asset]): The assets of the release.
|
||||
"""
|
||||
|
||||
def __init__(self, metadata: Metadata, assets: list[Asset]):
|
||||
dict.__init__(self, metadata=metadata, assets=assets)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Contributor(dict):
|
||||
"""
|
||||
Represents a contributor to a repository.
|
||||
|
||||
Attributes:
|
||||
- login (str): The GitHub username of the contributor.
|
||||
- avatar_url (str): The URL to the contributor's avatar image.
|
||||
- html_url (str): The URL to the contributor's GitHub profile.
|
||||
- contributions (Optional[int]): The number of contributions the contributor has made to the repository.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
login: str,
|
||||
avatar_url: str,
|
||||
html_url: str,
|
||||
contributions: Optional[int] = None,
|
||||
):
|
||||
if contributions:
|
||||
dict.__init__(
|
||||
self,
|
||||
login=login,
|
||||
avatar_url=avatar_url,
|
||||
html_url=html_url,
|
||||
contributions=contributions,
|
||||
)
|
||||
else:
|
||||
dict.__init__(self, login=login, avatar_url=avatar_url, html_url=html_url)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AppInfo(dict):
|
||||
"""
|
||||
Represents the information of an app.
|
||||
|
||||
Attributes:
|
||||
- name (str): The name of the app.
|
||||
- category (str): The app category.
|
||||
- logo (str): The base64 enconded app logo.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, category: str, logo: str):
|
||||
dict.__init__(
|
||||
self,
|
||||
name=name,
|
||||
category=category,
|
||||
logo=logo,
|
||||
)
|
||||
355
api/backends/github.py
Normal file
355
api/backends/github.py
Normal file
@@ -0,0 +1,355 @@
|
||||
import asyncio
|
||||
import os
|
||||
from operator import eq
|
||||
from typing import Any, Optional
|
||||
|
||||
import ujson
|
||||
from aiohttp import ClientResponse
|
||||
from sanic import SanicException
|
||||
from toolz import filter, map
|
||||
from toolz.dicttoolz import get_in, keyfilter
|
||||
from toolz.itertoolz import mapcat
|
||||
|
||||
from api.backends.backend import Backend, Repository
|
||||
from api.backends.entities import *
|
||||
from api.backends.entities import Contributor
|
||||
from api.utils.http_utils import http_get
|
||||
|
||||
repo_name: str = "github"
|
||||
base_url: str = "https://api.github.com"
|
||||
|
||||
|
||||
class GithubRepository(Repository):
|
||||
"""
|
||||
A repository class that represents a Github repository.
|
||||
|
||||
Args:
|
||||
owner (str): The username of the owner of the Github repository.
|
||||
name (str): The name of the Github repository.
|
||||
"""
|
||||
|
||||
def __init__(self, owner: str, name: str):
|
||||
"""
|
||||
Initializes a new instance of the GithubRepository class.
|
||||
|
||||
Args:
|
||||
owner (str): The username of the owner of the Github repository.
|
||||
name (str): The name of the Github repository.
|
||||
"""
|
||||
super().__init__(Github())
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
|
||||
|
||||
class Github(Backend):
|
||||
"""
|
||||
A backend class that interacts with the Github API.
|
||||
|
||||
Attributes:
|
||||
name (str): The name of the Github backend.
|
||||
base_url (str): The base URL of the Github API.
|
||||
token (str): The Github access token used for authentication.
|
||||
headers (dict[str, str]): The HTTP headers to be sent with each request to the Github API.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initializes a new instance of the Github class.
|
||||
"""
|
||||
super().__init__(repo_name, base_url)
|
||||
self.token: Optional[str] = os.getenv("GITHUB_TOKEN")
|
||||
self.headers: dict[str, str] = {
|
||||
"Authorization": f"Bearer {self.token}",
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
}
|
||||
self.repositories_rest_endpoint: str = f"{base_url}/repos"
|
||||
|
||||
@staticmethod
|
||||
async def __assemble_release(release: dict) -> Release:
|
||||
async def __assemble_asset(asset: dict) -> Asset:
|
||||
asset_data: dict = keyfilter(
|
||||
lambda key: key in {"name", "content_type", "browser_download_url"},
|
||||
asset,
|
||||
)
|
||||
return Asset(**asset_data)
|
||||
|
||||
filter_metadata = keyfilter(
|
||||
lambda key: key
|
||||
in {
|
||||
"tag_name",
|
||||
"name",
|
||||
"draft",
|
||||
"prerelease",
|
||||
"created_at",
|
||||
"published_at",
|
||||
"body",
|
||||
},
|
||||
release,
|
||||
)
|
||||
metadata = Metadata(**filter_metadata)
|
||||
assets = await asyncio.gather(*map(__assemble_asset, release["assets"]))
|
||||
return Release(metadata=metadata, assets=assets)
|
||||
|
||||
@staticmethod
|
||||
async def __assemble_contributor(
|
||||
contributor: dict, team_view: bool = False
|
||||
) -> Contributor:
|
||||
if team_view:
|
||||
filter_contributor = keyfilter(
|
||||
lambda key: key in {"login", "avatar_url", "html_url"},
|
||||
contributor,
|
||||
)
|
||||
return Contributor(**filter_contributor)
|
||||
|
||||
filter_contributor = keyfilter(
|
||||
lambda key: key in {"login", "avatar_url", "html_url", "contributions"},
|
||||
contributor,
|
||||
)
|
||||
return Contributor(**filter_contributor)
|
||||
|
||||
async def list_releases(
|
||||
self, repository: GithubRepository, per_page: int = 30, page: int = 1
|
||||
) -> list[Release]:
|
||||
"""
|
||||
Returns a list of Release objects for a given Github repository.
|
||||
|
||||
Args:
|
||||
repository (GithubRepository): The Github repository for which to retrieve the releases.
|
||||
per_page (int): The number of releases to return per page.
|
||||
page (int): The page number of the releases to return.
|
||||
|
||||
Returns:
|
||||
list[Release]: A list of Release objects.
|
||||
"""
|
||||
list_releases_endpoint: str = f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases?per_page={per_page}&page={page}"
|
||||
response: ClientResponse = await http_get(
|
||||
headers=self.headers, url=list_releases_endpoint
|
||||
)
|
||||
if response.status != 200:
|
||||
raise SanicException(
|
||||
context=await response.json(loads=ujson.loads),
|
||||
status_code=response.status,
|
||||
)
|
||||
releases: list[Release] = await asyncio.gather(
|
||||
*map(
|
||||
lambda release: self.__assemble_release(release),
|
||||
await response.json(loads=ujson.loads),
|
||||
)
|
||||
)
|
||||
return releases
|
||||
|
||||
async def get_release_by_tag_name(
|
||||
self, repository: GithubRepository, tag_name: str
|
||||
) -> Release:
|
||||
"""
|
||||
Retrieves a specific release for a given Github repository by its tag name.
|
||||
|
||||
Args:
|
||||
repository (GithubRepository): The Github repository for which to retrieve the release.
|
||||
tag_name (str): The tag name of the release to retrieve.
|
||||
|
||||
Returns:
|
||||
Release: The Release object representing the retrieved release.
|
||||
"""
|
||||
release_by_tag_endpoint: str = f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases/tags/{tag_name}"
|
||||
response: ClientResponse = await http_get(
|
||||
headers=self.headers, url=release_by_tag_endpoint
|
||||
)
|
||||
if response.status != 200:
|
||||
raise SanicException(
|
||||
context=await response.json(loads=ujson.loads),
|
||||
status_code=response.status,
|
||||
)
|
||||
return await self.__assemble_release(await response.json(loads=ujson.loads))
|
||||
|
||||
async def get_latest_release(
|
||||
self,
|
||||
repository: GithubRepository,
|
||||
) -> Release:
|
||||
"""Get the latest release for a given repository.
|
||||
|
||||
Args:
|
||||
repository (GithubRepository): The Github repository for which to retrieve the release.
|
||||
|
||||
Returns:
|
||||
Release: The latest release for the given repository.
|
||||
"""
|
||||
latest_release_endpoint: str = f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases/latest"
|
||||
response: ClientResponse = await http_get(
|
||||
headers=self.headers, url=latest_release_endpoint
|
||||
)
|
||||
if response.status != 200:
|
||||
raise SanicException(
|
||||
context=await response.json(loads=ujson.loads),
|
||||
status_code=response.status,
|
||||
)
|
||||
return await self.__assemble_release(await response.json(loads=ujson.loads))
|
||||
|
||||
async def get_latest_pre_release(
|
||||
self,
|
||||
repository: GithubRepository,
|
||||
) -> Release:
|
||||
"""Get the latest pre-release for a given repository.
|
||||
|
||||
Args:
|
||||
repository (GithubRepository): The Github repository for which to retrieve the release.
|
||||
|
||||
Returns:
|
||||
Release: The latest pre-release for the given repository.
|
||||
"""
|
||||
list_releases_endpoint: str = f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/releases?per_page=10&page=1"
|
||||
response: ClientResponse = await http_get(
|
||||
headers=self.headers, url=list_releases_endpoint
|
||||
)
|
||||
if response.status != 200:
|
||||
raise SanicException(
|
||||
context=await response.json(loads=ujson.loads),
|
||||
status_code=response.status,
|
||||
)
|
||||
latest_pre_release = next(
|
||||
filter(
|
||||
lambda release: release["prerelease"],
|
||||
await response.json(loads=ujson.loads),
|
||||
)
|
||||
)
|
||||
return await self.__assemble_release(latest_pre_release)
|
||||
|
||||
async def get_contributors(self, repository: GithubRepository) -> list[Contributor]:
|
||||
"""Get a list of contributors for a given repository.
|
||||
|
||||
Args:
|
||||
repository (GithubRepository): The repository for which to retrieve contributors.
|
||||
|
||||
Returns:
|
||||
list[Contributor]: A list of contributors for the given repository.
|
||||
"""
|
||||
|
||||
contributors_endpoint: str = f"{self.repositories_rest_endpoint}/{repository.owner}/{repository.name}/contributors"
|
||||
response: ClientResponse = await http_get(
|
||||
headers=self.headers, url=contributors_endpoint
|
||||
)
|
||||
if response.status != 200:
|
||||
raise SanicException(
|
||||
context=await response.json(loads=ujson.loads),
|
||||
status_code=response.status,
|
||||
)
|
||||
contributors: list[Contributor] = await asyncio.gather(
|
||||
*map(self.__assemble_contributor, await response.json(loads=ujson.loads))
|
||||
)
|
||||
|
||||
return contributors
|
||||
|
||||
async def get_patches(
|
||||
self, repository: GithubRepository, tag_name: str
|
||||
) -> list[dict]:
|
||||
"""Get a dictionary of patch URLs for a given repository.
|
||||
|
||||
Args:
|
||||
repository (GithubRepository): The repository for which to retrieve patches.
|
||||
tag_name: The name of the release tag.
|
||||
|
||||
Returns:
|
||||
list[dict]: A JSON object containing the patches.
|
||||
"""
|
||||
|
||||
async def __fetch_download_url(release: Release) -> str:
|
||||
asset = get_in(["assets"], release)
|
||||
patch_asset = next(
|
||||
filter(lambda x: eq(get_in(["name"], x), "patches.json"), asset), None
|
||||
)
|
||||
return get_in(["browser_download_url"], patch_asset)
|
||||
|
||||
response: ClientResponse = await http_get(
|
||||
headers=self.headers,
|
||||
url=await __fetch_download_url(
|
||||
await self.get_release_by_tag_name(
|
||||
repository=repository, tag_name=tag_name
|
||||
)
|
||||
),
|
||||
)
|
||||
if response.status != 200:
|
||||
raise SanicException(
|
||||
context=await response.json(loads=ujson.loads),
|
||||
status_code=response.status,
|
||||
)
|
||||
return ujson.loads(await response.read())
|
||||
|
||||
async def get_team_members(self, repository: GithubRepository) -> list[Contributor]:
|
||||
"""Get the list of team members from the owner organization of a given repository.
|
||||
|
||||
Args:
|
||||
repository (GithubRepository): The repository for which to retrieve team members in the owner organization.
|
||||
|
||||
Returns:
|
||||
list[Contributor]: A list of members in the owner organization.
|
||||
"""
|
||||
team_members_endpoint: str = f"{self.base_url}/orgs/{repository.owner}/members"
|
||||
response: ClientResponse = await http_get(
|
||||
headers=self.headers, url=team_members_endpoint
|
||||
)
|
||||
if response.status != 200:
|
||||
raise SanicException(
|
||||
context=await response.json(loads=ujson.loads),
|
||||
status_code=response.status,
|
||||
)
|
||||
team_members: list[Contributor] = await asyncio.gather(
|
||||
*map(
|
||||
lambda member: self.__assemble_contributor(member, team_view=True),
|
||||
await response.json(loads=ujson.loads),
|
||||
)
|
||||
)
|
||||
|
||||
return team_members
|
||||
|
||||
async def compat_get_tools(
|
||||
self, repositories: list[GithubRepository], dev: bool
|
||||
) -> list:
|
||||
"""Get the latest releases for a set of repositories (v1 compat).
|
||||
|
||||
Args:
|
||||
repositories (set[GithubRepository]): The repositories for which to retrieve releases.
|
||||
dev: If we should get the latest pre-release instead.
|
||||
|
||||
Returns:
|
||||
list[dict[str, str]]: A JSON object containing the releases.
|
||||
"""
|
||||
|
||||
def transform(data, repository):
|
||||
"""Transforms a dictionary from the input list into a list of dictionaries with the desired structure.
|
||||
|
||||
Args:
|
||||
data(dict): A dictionary from the input list.
|
||||
|
||||
Returns:
|
||||
_[list]: A list of dictionaries with the desired structure.
|
||||
"""
|
||||
|
||||
def process_asset(asset):
|
||||
"""Transforms an asset dictionary into a new dictionary with the desired structure.
|
||||
|
||||
Args:
|
||||
asset(dict): An asset dictionary.
|
||||
|
||||
Returns:
|
||||
_[dict]: A new dictionary with the desired structure.
|
||||
"""
|
||||
return {
|
||||
"repository": f"{repository.owner}/{repository.name}",
|
||||
"version": data["metadata"]["tag_name"],
|
||||
"timestamp": data["metadata"]["published_at"],
|
||||
"name": asset["name"],
|
||||
"browser_download_url": asset["browser_download_url"],
|
||||
"content_type": asset["content_type"],
|
||||
}
|
||||
|
||||
return map(process_asset, data["assets"])
|
||||
|
||||
results = await asyncio.gather(
|
||||
*map(
|
||||
lambda release: self.get_latest_release(release),
|
||||
repositories,
|
||||
)
|
||||
)
|
||||
|
||||
return list(mapcat(lambda pair: transform(*pair), zip(results, repositories)))
|
||||
61
api/compat.py
Normal file
61
api/compat.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
This module provides endpoints for compatibility with the old API.
|
||||
|
||||
Routes:
|
||||
- GET /<repo:str>/releases: Retrieve a list of releases for a Github repository.
|
||||
- GET /<repo:str>/releases/latest: Retrieve the latest release for a Github repository.
|
||||
- GET /<repo:str>/releases/tag/<tag:str>: Retrieve a specific release for a Github repository by its tag name.
|
||||
- GET /<repo:str>/contributors: Retrieve a list of contributors for a Github repository.
|
||||
- GET /patches/<tag:str>: Retrieve a list of patches for a given release tag.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from sanic import Blueprint, Request
|
||||
from sanic.response import JSONResponse, json
|
||||
from sanic_ext import openapi
|
||||
|
||||
from api.backends.github import Github, GithubRepository
|
||||
from api.models.github import *
|
||||
from api.models.compat import ToolsResponseModel
|
||||
from config import compat_repositories, owner
|
||||
|
||||
github: Blueprint = Blueprint("old")
|
||||
|
||||
github_backend: Github = Github()
|
||||
|
||||
|
||||
@github.get("/tools")
|
||||
@openapi.definition(
|
||||
summary="Get patching tools' latest version.", response=[ToolsResponseModel]
|
||||
)
|
||||
async def tools(request: Request) -> JSONResponse:
|
||||
"""
|
||||
Retrieve a list of releases for a Github repository.
|
||||
|
||||
**Args:**
|
||||
- repo (str): The name of the Github repository to retrieve releases for.
|
||||
|
||||
**Query Parameters:**
|
||||
- per_page (int): The number of releases to retrieve per page.
|
||||
- page (int): The page number of the releases to retrieve.
|
||||
|
||||
**Returns:**
|
||||
- JSONResponse: A Sanic JSONResponse object containing the list of releases.
|
||||
|
||||
**Raises:**
|
||||
- HTTPException: If there is an error retrieving the releases.
|
||||
"""
|
||||
|
||||
data: dict[str, list] = {
|
||||
"tools": await github_backend.compat_get_tools(
|
||||
repositories=[
|
||||
GithubRepository(owner=owner, name=repo)
|
||||
for repo in compat_repositories
|
||||
if repo not in ["revanced-releases-api", "revanced-website"]
|
||||
],
|
||||
dev=True if request.args.get("dev") else False,
|
||||
)
|
||||
}
|
||||
|
||||
return json(data, status=200)
|
||||
31
api/donations.py
Normal file
31
api/donations.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
This module provides a blueprint for the donations endpoint.
|
||||
|
||||
Routes:
|
||||
- GET /donations: Get ReVanced donation links and wallets.
|
||||
"""
|
||||
|
||||
from sanic import Blueprint, Request
|
||||
from sanic.response import JSONResponse, json
|
||||
from sanic_ext import openapi
|
||||
|
||||
from api.models.donations import DonationsResponseModel
|
||||
from config import donation_info, api_version
|
||||
|
||||
donations: Blueprint = Blueprint("donations", version=api_version)
|
||||
|
||||
|
||||
@donations.get("/donations")
|
||||
@openapi.definition(
|
||||
summary="Get ReVanced donation links and wallets",
|
||||
response=[DonationsResponseModel],
|
||||
)
|
||||
async def root(request: Request) -> JSONResponse:
|
||||
"""
|
||||
Returns a JSONResponse with a dictionary containing ReVanced donation links and wallets.
|
||||
|
||||
**Returns:**
|
||||
- JSONResponse: A Sanic JSONResponse instance containing a dictionary with the donation links and wallets.
|
||||
"""
|
||||
data: dict[str, dict] = {"donations": donation_info}
|
||||
return json(data, status=200)
|
||||
208
api/github.py
Normal file
208
api/github.py
Normal file
@@ -0,0 +1,208 @@
|
||||
"""
|
||||
This module provides endpoints for interacting with the Github API.
|
||||
|
||||
Routes:
|
||||
- GET /<repo:str>/releases: Retrieve a list of releases for a Github repository.
|
||||
- GET /<repo:str>/releases/latest: Retrieve the latest release for a Github repository.
|
||||
- GET /<repo:str>/releases/tag/<tag:str>: Retrieve a specific release for a Github repository by its tag name.
|
||||
- GET /<repo:str>/contributors: Retrieve a list of contributors for a Github repository.
|
||||
- GET /patches/<tag:str>: Retrieve a list of patches for a given release tag.
|
||||
|
||||
"""
|
||||
|
||||
from sanic import Blueprint, Request
|
||||
from sanic.response import JSONResponse, json
|
||||
from sanic_ext import openapi
|
||||
|
||||
from api.backends.entities import Release, Contributor
|
||||
from api.backends.github import Github, GithubRepository
|
||||
from api.models.github import *
|
||||
from config import owner, default_repository, api_version
|
||||
|
||||
github: Blueprint = Blueprint("github", version=api_version)
|
||||
|
||||
github_backend: Github = Github()
|
||||
|
||||
|
||||
@github.get("/<repo:str>/releases")
|
||||
@openapi.definition(
|
||||
summary="Get releases for a repository", response=[ReleaseListResponseModel]
|
||||
)
|
||||
async def list_releases(request: Request, repo: str) -> JSONResponse:
|
||||
"""
|
||||
Retrieve a list of releases for a Github repository.
|
||||
|
||||
**Args:**
|
||||
- repo (str): The name of the Github repository to retrieve releases for.
|
||||
|
||||
**Query Parameters:**
|
||||
- per_page (int): The number of releases to retrieve per page.
|
||||
- page (int): The page number of the releases to retrieve.
|
||||
|
||||
**Returns:**
|
||||
- JSONResponse: A Sanic JSONResponse object containing the list of releases.
|
||||
|
||||
**Raises:**
|
||||
- HTTPException: If there is an error retrieving the releases.
|
||||
"""
|
||||
|
||||
per_page = int(request.args.get("per_page")) if request.args.get("per_page") else 30
|
||||
page = int(request.args.get("page")) if request.args.get("page") else 1
|
||||
|
||||
data: dict[str, list[Release]] = {
|
||||
"releases": await github_backend.list_releases(
|
||||
repository=GithubRepository(owner=owner, name=repo),
|
||||
per_page=per_page,
|
||||
page=page,
|
||||
)
|
||||
}
|
||||
|
||||
return json(data, status=200)
|
||||
|
||||
|
||||
@github.get("/<repo:str>/releases/latest")
|
||||
@openapi.definition(
|
||||
summary="Get the latest release for a repository",
|
||||
response=SingleReleaseResponseModel,
|
||||
)
|
||||
async def latest_release(request: Request, repo: str) -> JSONResponse:
|
||||
"""
|
||||
Retrieve the latest release for a Github repository.
|
||||
|
||||
**Args:**
|
||||
- repo (str): The name of the Github repository to retrieve the release for.
|
||||
|
||||
**Query Parameters:**
|
||||
- dev (bool): Whether or not to retrieve the latest development release.
|
||||
|
||||
**Returns:**
|
||||
- JSONResponse: A Sanic JSONResponse object containing the release.
|
||||
|
||||
**Raises:**
|
||||
- HTTPException: If there is an error retrieving the releases.
|
||||
"""
|
||||
|
||||
data: dict[str, Release] = {
|
||||
"release": await github_backend.get_latest_pre_release(
|
||||
repository=GithubRepository(owner=owner, name=repo)
|
||||
)
|
||||
if request.args.get("dev") == "true"
|
||||
else await github_backend.get_latest_release(
|
||||
repository=GithubRepository(owner=owner, name=repo)
|
||||
)
|
||||
}
|
||||
|
||||
return json(data, status=200)
|
||||
|
||||
|
||||
@github.get("/<repo:str>/releases/tag/<tag:str>")
|
||||
@openapi.definition(
|
||||
summary="Retrieve a release for a Github repository by its tag name.",
|
||||
response=SingleReleaseResponseModel,
|
||||
)
|
||||
async def get_release_by_tag_name(
|
||||
request: Request, repo: str, tag: str
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
Retrieve a release for a Github repository by its tag name.
|
||||
|
||||
**Args:**
|
||||
- repo (str): The name of the Github repository to retrieve the release for.
|
||||
- tag (str): The tag for the release to be retrieved.
|
||||
|
||||
**Returns:**
|
||||
- JSONResponse: A Sanic JSONResponse object containing the release.
|
||||
|
||||
**Raises:**
|
||||
- HTTPException: If there is an error retrieving the releases.
|
||||
"""
|
||||
|
||||
data: dict[str, Release] = {
|
||||
"release": await github_backend.get_release_by_tag_name(
|
||||
repository=GithubRepository(owner=owner, name=repo), tag_name=tag
|
||||
)
|
||||
}
|
||||
|
||||
return json(data, status=200)
|
||||
|
||||
|
||||
@github.get("/<repo:str>/contributors")
|
||||
@openapi.definition(
|
||||
summary="Retrieve a list of contributors for a repository.",
|
||||
response=ContributorsModel,
|
||||
)
|
||||
async def get_contributors(request: Request, repo: str) -> JSONResponse:
|
||||
"""
|
||||
Retrieve a list of contributors for a repository.
|
||||
|
||||
**Args:**
|
||||
- repo (str): The name of the Github repository to retrieve the contributors for.
|
||||
|
||||
**Returns:**
|
||||
- JSONResponse: A Sanic JSONResponse object containing the list of contributors.
|
||||
|
||||
**Raises:**
|
||||
- HTTPException: If there is an error retrieving the contributors.
|
||||
"""
|
||||
|
||||
data: dict[str, list[Contributor]] = {
|
||||
"contributors": await github_backend.get_contributors(
|
||||
repository=GithubRepository(owner=owner, name=repo)
|
||||
)
|
||||
}
|
||||
|
||||
return json(data, status=200)
|
||||
|
||||
|
||||
@github.get("/patches/<tag:str>")
|
||||
@openapi.definition(
|
||||
summary="Retrieve a list of patches for a release.", response=PatchesModel
|
||||
)
|
||||
async def get_patches(request: Request, tag: str) -> JSONResponse:
|
||||
"""
|
||||
Retrieve a list of patches for a release.
|
||||
|
||||
**Args:**
|
||||
- tag (str): The tag for the patches to be retrieved.
|
||||
|
||||
**Returns:**
|
||||
- JSONResponse: A Sanic JSONResponse object containing the list of patches.
|
||||
|
||||
**Raises:**
|
||||
- HTTPException: If there is an error retrieving the patches.
|
||||
"""
|
||||
|
||||
repo: str = "revanced-patches"
|
||||
|
||||
data: dict[str, list[dict]] = {
|
||||
"patches": await github_backend.get_patches(
|
||||
repository=GithubRepository(owner=owner, name=repo), tag_name=tag
|
||||
)
|
||||
}
|
||||
|
||||
return json(data, status=200)
|
||||
|
||||
|
||||
@github.get("/team/members")
|
||||
@openapi.definition(
|
||||
summary="Retrieve a list of team members for the Revanced organization.",
|
||||
response=TeamMembersModel,
|
||||
)
|
||||
async def get_team_members(request: Request) -> JSONResponse:
|
||||
"""
|
||||
Retrieve a list of team members for the Revanced organization.
|
||||
|
||||
**Returns:**
|
||||
- JSONResponse: A Sanic JSONResponse object containing the list of team members.
|
||||
|
||||
**Raises:**
|
||||
- HTTPException: If there is an error retrieving the team members.
|
||||
"""
|
||||
|
||||
data: dict[str, list[Contributor]] = {
|
||||
"members": await github_backend.get_team_members(
|
||||
repository=GithubRepository(owner=owner, name=default_repository)
|
||||
)
|
||||
}
|
||||
|
||||
return json(data, status=200)
|
||||
0
api/models/__init__.py
Normal file
0
api/models/__init__.py
Normal file
19
api/models/appinfo.py
Normal file
19
api/models/appinfo.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AppInfoFields(BaseModel):
|
||||
"""
|
||||
Fields for the AppInfo endpoint.
|
||||
"""
|
||||
|
||||
name: str
|
||||
category: str
|
||||
logo: str
|
||||
|
||||
|
||||
class AppInfoModel(BaseModel):
|
||||
"""
|
||||
Response model app info.
|
||||
"""
|
||||
|
||||
app_info: AppInfoFields
|
||||
27
api/models/compat.py
Normal file
27
api/models/compat.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ToolsResponseFields(BaseModel):
|
||||
"""Implements the fields for the /tools endpoint.
|
||||
|
||||
Args:
|
||||
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||
"""
|
||||
|
||||
repository: str
|
||||
version: str
|
||||
timestamp: str
|
||||
name: str
|
||||
size: str | None = None
|
||||
browser_download_url: str
|
||||
content_type: str
|
||||
|
||||
|
||||
class ToolsResponseModel(BaseModel):
|
||||
"""Implements the JSON response model for the /tools endpoint.
|
||||
|
||||
Args:
|
||||
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||
"""
|
||||
|
||||
tools: list[ToolsResponseFields]
|
||||
13
api/models/donations.py
Normal file
13
api/models/donations.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class DonationsResponseModel(BaseModel):
|
||||
"""
|
||||
A Pydantic BaseModel that represents a dictionary of donation links.
|
||||
"""
|
||||
|
||||
donations: dict[str, str]
|
||||
"""
|
||||
A dictionary where the keys are the names of the donation destinations, and
|
||||
the values are the links to services or wallet addresses.
|
||||
"""
|
||||
127
api/models/github.py
Normal file
127
api/models/github.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from typing import Any, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class MetadataFields(BaseModel):
|
||||
"""
|
||||
Metadata fields for a GitHub release.
|
||||
"""
|
||||
|
||||
tag_name: str
|
||||
name: str
|
||||
draft: bool
|
||||
prerelease: bool
|
||||
created_at: str
|
||||
published_at: str
|
||||
body: str
|
||||
|
||||
|
||||
class AssetFields(BaseModel):
|
||||
"""
|
||||
Asset fields for a GitHub release.
|
||||
"""
|
||||
|
||||
name: str
|
||||
content_type: str
|
||||
browser_download_url: str
|
||||
|
||||
|
||||
class ReleaseResponseModel(BaseModel):
|
||||
"""
|
||||
Response model for a GitHub release.
|
||||
"""
|
||||
|
||||
metadata: MetadataFields
|
||||
assets: list[AssetFields]
|
||||
|
||||
|
||||
class SingleReleaseResponseModel(BaseModel):
|
||||
"""
|
||||
Response model for a GitHub release.
|
||||
"""
|
||||
|
||||
release: ReleaseResponseModel
|
||||
|
||||
|
||||
class ReleaseListResponseModel(BaseModel):
|
||||
"""
|
||||
Response model for a list of GitHub releases.
|
||||
"""
|
||||
|
||||
releases: list[ReleaseResponseModel]
|
||||
|
||||
|
||||
class CompatiblePackagesResponseFields(BaseModel):
|
||||
"""
|
||||
Implements the fields for compatible packages in the PatchesResponseFields class.
|
||||
"""
|
||||
|
||||
name: str
|
||||
versions: list[str] | None
|
||||
|
||||
|
||||
class PatchesOptionsResponseFields(BaseModel):
|
||||
key: str
|
||||
title: str
|
||||
description: str
|
||||
required: bool
|
||||
choices: list[Any] | None
|
||||
|
||||
|
||||
class PatchesResponseFields(BaseModel):
|
||||
"""
|
||||
Implements the fields for the /patches endpoint.
|
||||
"""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
version: str
|
||||
excluded: bool
|
||||
dependencies: list[str] | None
|
||||
options: list[PatchesOptionsResponseFields] | None
|
||||
compatiblePackages: list[CompatiblePackagesResponseFields]
|
||||
|
||||
|
||||
class PatchesModel(BaseModel):
|
||||
"""
|
||||
Response model for a list of GitHub releases.
|
||||
"""
|
||||
|
||||
patches: list[PatchesResponseFields]
|
||||
|
||||
|
||||
class ContributorsFields(BaseModel):
|
||||
"""
|
||||
Implements the fields for a contributor.
|
||||
"""
|
||||
|
||||
login: str
|
||||
avatar_url: str
|
||||
html_url: str
|
||||
contributions: Optional[int]
|
||||
|
||||
|
||||
class ContributorsModel(BaseModel):
|
||||
"""
|
||||
Response model for a list of contributors.
|
||||
"""
|
||||
|
||||
contributors: list[ContributorsFields]
|
||||
|
||||
|
||||
class TeamMemberFields(BaseModel):
|
||||
"""
|
||||
Implements the fields for a team member.
|
||||
"""
|
||||
|
||||
login: str
|
||||
avatar_url: str
|
||||
html_url: str
|
||||
|
||||
|
||||
class TeamMembersModel(BaseModel):
|
||||
"""
|
||||
Responde model for a list of team members.
|
||||
"""
|
||||
|
||||
members: list[TeamMemberFields]
|
||||
13
api/models/socials.py
Normal file
13
api/models/socials.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class SocialsResponseModel(BaseModel):
|
||||
"""
|
||||
A Pydantic BaseModel that represents a dictionary of social links.
|
||||
"""
|
||||
|
||||
socials: dict[str, str]
|
||||
"""
|
||||
A dictionary where the keys are the names of the social networks, and
|
||||
the values are the links to the profiles or pages.
|
||||
"""
|
||||
24
api/ping.py
Normal file
24
api/ping.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
This module provides endpoints for pinging the API.
|
||||
|
||||
Routes:
|
||||
- HEAD /ping: Ping the API.
|
||||
"""
|
||||
|
||||
from sanic import Blueprint, HTTPResponse, Request, response
|
||||
from sanic_ext import openapi
|
||||
from config import api_version
|
||||
|
||||
ping: Blueprint = Blueprint("ping", version=api_version)
|
||||
|
||||
|
||||
@ping.head("/ping")
|
||||
@openapi.summary("Ping the API")
|
||||
async def root(request: Request) -> HTTPResponse:
|
||||
"""
|
||||
Endpoint for pinging the API.
|
||||
|
||||
**Returns:**
|
||||
- Empty response with status code 204.
|
||||
"""
|
||||
return response.empty(status=204)
|
||||
31
api/socials.py
Normal file
31
api/socials.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
This module provides a blueprint for the socials endpoint.
|
||||
|
||||
Routes:
|
||||
- GET /socials: Get ReVanced socials.
|
||||
"""
|
||||
|
||||
from sanic import Blueprint, Request
|
||||
from sanic.response import JSONResponse, json
|
||||
from sanic_ext import openapi
|
||||
|
||||
from api.models.socials import SocialsResponseModel
|
||||
from config import social_links, api_version
|
||||
|
||||
socials: Blueprint = Blueprint("socials", version=api_version)
|
||||
|
||||
|
||||
@socials.get("/socials")
|
||||
@openapi.definition(
|
||||
summary="Get ReVanced socials",
|
||||
response=[SocialsResponseModel],
|
||||
)
|
||||
async def root(request: Request) -> JSONResponse:
|
||||
"""
|
||||
Returns a JSONResponse with a dictionary containing ReVanced social links.
|
||||
|
||||
**Returns:**
|
||||
- JSONResponse: A Sanic JSONResponse instance containing a dictionary with the social links.
|
||||
"""
|
||||
data: dict[str, dict] = {"socials": social_links}
|
||||
return json(data, status=200)
|
||||
0
api/utils/__init__.py
Normal file
0
api/utils/__init__.py
Normal file
26
api/utils/http_utils.py
Normal file
26
api/utils/http_utils.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from typing import Optional
|
||||
|
||||
import ujson
|
||||
from aiohttp import ClientSession
|
||||
|
||||
_client: Optional[ClientSession] = None
|
||||
|
||||
|
||||
async def http_get(headers, url):
|
||||
"""
|
||||
Performs a GET HTTP request to a given URL with the provided headers.
|
||||
|
||||
Args:
|
||||
headers (dict): A dictionary containing headers to be included in the HTTP request.
|
||||
url (str): The URL to which the HTTP request will be made.
|
||||
|
||||
Returns:
|
||||
The HTTP response returned by the server.
|
||||
"""
|
||||
global _client
|
||||
if _client is None:
|
||||
_client = ClientSession(json_serialize=ujson.dumps)
|
||||
return await _client.get(url, headers=headers)
|
||||
else:
|
||||
assert isinstance(_client, ClientSession)
|
||||
return await _client.get(url, headers=headers)
|
||||
Reference in New Issue
Block a user