-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
179 additions
and
106 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,137 +1,118 @@ | ||
import os | ||
from dataclasses import asdict, dataclass | ||
from typing import List, Literal, Optional, Dict | ||
from typing import Dict, List, Literal, Optional | ||
|
||
import aiohttp | ||
import urllib.request | ||
import logging | ||
|
||
ENDPOINT = "https://api.cobalt.tools" | ||
import aiohttp | ||
|
||
ENDPOINT = os.environ.get("COBALT_URL") or "http://cobalt-api:9000" | ||
FALLBACK_ENDPOINT = "https://api.cobalt.tools/" | ||
|
||
DEFAULT_HEADERS = {"Accept": "application/json", "Content-Type": "application/json", "User-Agent": "alexBot/1.0"} | ||
|
||
# comments are from https://github.com/wukko/cobalt/blob/current/docs/api.md | ||
|
||
|
||
# ## POST: `/api/json` | ||
# cobalt's main processing endpoint. | ||
|
||
# request body type: `application/json` | ||
# response body type: `application/json` | ||
|
||
# ``` | ||
# ⚠️ you must include Accept and Content-Type headers with every POST /api/json request. | ||
|
||
# Accept: application/json | ||
# Content-Type: application/json | ||
# ``` | ||
|
||
|
||
# ### request body variables | ||
# | key | type | variables | default | description | | ||
# |:------------------|:----------|:-----------------------------------|:----------|:--------------------------------------------------------------------------------| | ||
# | `url` | `string` | URL encoded as URI | `null` | **must** be included in every request. | | ||
# | `vCodec` | `string` | `h264 / av1 / vp9` | `h264` | applies only to youtube downloads. `h264` is recommended for phones. | | ||
# | `vQuality` | `string` | `144 / ... / 2160 / max` | `720` | `720` quality is recommended for phones. | | ||
# | `aFormat` | `string` | `best / mp3 / ogg / wav / opus` | `mp3` | | | ||
# | `filenamePattern` | `string` | `classic / pretty / basic / nerdy` | `classic` | changes the way files are named. previews can be seen in the web app. | | ||
# | `isAudioOnly` | `boolean` | `true / false` | `false` | | | ||
# | `isTTFullAudio` | `boolean` | `true / false` | `false` | enables download of original sound used in a tiktok video. | | ||
# | `isAudioMuted` | `boolean` | `true / false` | `false` | disables audio track in video downloads. | | ||
# | `dubLang` | `boolean` | `true / false` | `false` | backend uses Accept-Language header for youtube video audio tracks when `true`. | | ||
# | `disableMetadata` | `boolean` | `true / false` | `false` | disables file metadata when set to `true`. | | ||
# | `twitterGif` | `boolean` | `true / false` | `false` | changes whether twitter gifs are converted to .gif | | ||
log = logging.getLogger(__name__) | ||
|
||
|
||
@dataclass | ||
class RequestBody: | ||
url: str | ||
vCodec: Literal["h264", "av1", "vp9"] = "h264" | ||
vQuality: Literal["max", "4320", "2160", "1440", "1080", "720", "480", "360", "240", "144"] = "720" | ||
aFormat: Literal["best", "mp3", "ogg", "wav", "opus"] = "mp3" | ||
filenamePattern: Literal["classic", "pretty", "basic", "nerdy"] = "classic" | ||
isAudioOnly: bool = False | ||
isTTFullAudio: bool = False | ||
isAudioMuted: bool = False | ||
dubLang: bool = False | ||
videoQuality: Literal["max", "4320", "2160", "1440", "1080", "720", "480", "360", "240", "144"] = "720" | ||
audioFormat: Literal["best", "mp3", "ogg", "wav", "opus"] = "mp3" | ||
filenameStyle: Literal["classic", "pretty", "basic", "nerdy"] = "classic" | ||
downloadMode: Literal["auto", "audio", "mute"] = "auto" | ||
youtubeVideoCodec: Literal["h264", "av1", "vp9"] = "h264" | ||
youtubeDubLang: Optional[Literal["en", "ru", "cs", "ja"]] = None | ||
youtubeDubBrowserLang: bool = False | ||
alwaysProxy: bool = False | ||
disableMetadata: bool = False | ||
twitterGif: bool = False | ||
|
||
tiktokFullAudio: bool = False | ||
tiktokH265: bool = False | ||
twitterGif: bool = True | ||
|
||
# | key | type | variables | description | | ||
# |:--------|:---------|:--------------------------------------------------------|:---------------------------------------| | ||
# | `type` | `string` | `video` | used only if `pickerType`is `various`. | | ||
# | `url` | `string` | direct link to a file or a link to cobalt's live render | | | ||
# | `thumb` | `string` | item thumbnail that's displayed in the picker | used only for `video` type. | | ||
def dict(self): | ||
without_none = {k: v for k, v in asdict(self).items() if v is not None} | ||
return without_none | ||
|
||
|
||
@dataclass | ||
class Picker: | ||
url: str | ||
type: Optional[Literal["video"]] = None | ||
type: Optional[Literal["video", "photo", "gif"]] = None | ||
thumb: Optional[str] = None | ||
data: Optional[bytes] = None | ||
|
||
async def fetch(self, session: aiohttp.ClientSession) -> bytes: | ||
async with session.get(self.url) as response: | ||
self.data = await response.read() | ||
return self.data | ||
|
||
|
||
# ### response body variables | ||
# | key | type | variables | | ||
# |:-------------|:---------|:------------------------------------------------------------| | ||
# | `status` | `string` | `error / redirect / stream / success / rate-limit / picker` | | ||
# | `text` | `string` | various text, mostly used for errors | | ||
# | `url` | `string` | direct link to a file or a link to cobalt's live render | | ||
# | `pickerType` | `string` | `various / images` | | ||
# | `picker` | `array` | array of picker items | | ||
# | `audio` | `string` | direct link to a file or a link to cobalt's live render | | ||
@dataclass | ||
class ResponceBody: | ||
status: Literal["error", "redirect", "stream", "success", "rate-limit", "picker"] | ||
status: Literal["error", "redirect", "tunnel", "picker"] | ||
error: Optional[str] = None | ||
|
||
# status: "redirect", "tunnel" | ||
url: Optional[str] = None | ||
text: Optional[str] = None | ||
pickerType: Optional[Literal["various", "images"]] = None | ||
picker: Optional[List[Picker]] = None | ||
audio: Optional[str] = None | ||
raw_picker: Optional[List[dict]] = None | ||
filename: Optional[str] = None | ||
|
||
# status: "picker" | ||
audio: Optional[str] = None | ||
audioFilename: Optional[str] = None | ||
picker: Optional[List[Picker]] = None | ||
_picker: Optional[List[Dict]] = None | ||
|
||
# ## GET: `/api/serverInfo` | ||
# returns current basic server info. | ||
# response body type: `application/json` | ||
|
||
# ### response body variables | ||
# | key | type | variables | | ||
# |:------------|:---------|:------------------| | ||
# | `version` | `string` | cobalt version | | ||
# | `commit` | `string` | git commit | | ||
# | `branch` | `string` | git branch | | ||
# | `name` | `string` | server name | | ||
# | `url` | `string` | server url | | ||
# | `cors` | `int` | cors status | | ||
# | `startTime` | `string` | server start time | | ||
@dataclass | ||
class CobaltServerData: | ||
version: str | ||
url: str | ||
startTime: str | ||
durationLimit: int | ||
services: List[str] | ||
|
||
|
||
@dataclass | ||
class ServerInfo: | ||
version: str | ||
class GitServerData: | ||
commit: str | ||
branch: str | ||
name: str | ||
url: str | ||
cors: int | ||
startTime: str | ||
remote: str | ||
|
||
|
||
@dataclass | ||
class ServerInfo: | ||
cobalt: CobaltServerData | ||
git: GitServerData | ||
|
||
|
||
class Cobalt: | ||
HEADERS = DEFAULT_HEADERS | ||
def __init__(self) -> None: | ||
# make a request to ENDPOINT and check if it's up, if not, set to fallback server | ||
try: | ||
contents = urllib.request.urlopen(ENDPOINT).read() | ||
except Exception: | ||
contents = None | ||
if contents is None: | ||
self.endpoint = FALLBACK_ENDPOINT | ||
log.warning(f"Cobalt API {ENDPOINT} is down, using fallback server ({FALLBACK_ENDPOINT})") | ||
else: | ||
self.endpoint = ENDPOINT | ||
self.headers = DEFAULT_HEADERS | ||
|
||
async def get_server_info(self): | ||
async with aiohttp.ClientSession(headers=self.HEADERS) as session: | ||
async with session.get(ENDPOINT + "/api/serverInfo") as resp: | ||
return ServerInfo(**await resp.json()) | ||
async with aiohttp.ClientSession(headers=self.headers) as session: | ||
async with session.get(self.endpoint) as resp: | ||
data = await resp.json() | ||
return ServerInfo(cobalt=CobaltServerData(**data['cobalt']), git=GitServerData(**data['git'])) | ||
|
||
async def process(self, request_body: RequestBody): | ||
async with aiohttp.ClientSession(headers=self.HEADERS) as session: | ||
async with session.post(ENDPOINT + "/api/json", json=asdict(request_body)) as resp: | ||
async with aiohttp.ClientSession(headers=self.headers) as session: | ||
async with session.post(self.endpoint, json=request_body.dict()) as resp: | ||
rb = ResponceBody(**await resp.json()) | ||
if rb.picker: | ||
rb.raw_picker: List[Dict] = rb.picker # type: ignore | ||
rb.picker = [ | ||
Picker(picker['url'], picker.get("type"), picker.get("thumb")) for picker in rb.raw_picker | ||
] | ||
if rb.status == "picker": | ||
rb._picker = rb.picker | ||
rb.picker = [Picker(**p) for p in rb._picker] # type: ignore | ||
|
||
return rb |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.