From 093ba3c27da0980843fb0d67aace3a4361befaf2 Mon Sep 17 00:00:00 2001 From: hyugogirubato <65763543+hyugogirubato@users.noreply.github.com> Date: Mon, 3 Apr 2023 16:45:43 +0200 Subject: [PATCH] update to v1.0.1 --- .idea/.gitignore | 8 + .idea/inspectionProfiles/Project_Default.xml | 15 ++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/pyuptobox.iml | 8 + CHANGELOG.md | 9 +- README.md | 95 +++++---- UptoboxSDK/__init__.py | 3 - UptoboxSDK/client.py | 107 ---------- UptoboxSDK/exceptions.py | 10 - pyuptobox/__init__.py | 4 + pyuptobox/client.py | 189 ++++++++++++++++++ pyuptobox/exceptions.py | 10 + {UptoboxSDK => pyuptobox}/utils.py | 12 +- setup.py | 6 +- 16 files changed, 320 insertions(+), 174 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/pyuptobox.iml delete mode 100644 UptoboxSDK/__init__.py delete mode 100644 UptoboxSDK/client.py delete mode 100644 UptoboxSDK/exceptions.py create mode 100644 pyuptobox/__init__.py create mode 100644 pyuptobox/client.py create mode 100644 pyuptobox/exceptions.py rename {UptoboxSDK => pyuptobox}/utils.py (74%) diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..1c36fcb --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a2e120d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5167b6e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/pyuptobox.iml b/.idea/pyuptobox.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/pyuptobox.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a26d4ea..aa83a1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.1] - 2023-04-03 + +### Changed + +- Update all endpoint according to [uptobox](https://docs.uptobox.com). + ## [1.0.0] - 2023-04-02 Initial Release. -[1.0.0]: https://github.com/hyugogirubato/UptoboxSDK/releases/tag/v1.0.0 +[1.0.1]: https://github.com/hyugogirubato/pyuptobox/releases/tag/v1.0.0 +[1.0.0]: https://github.com/hyugogirubato/pyuptobox/releases/tag/v1.0.0 diff --git a/README.md b/README.md index 48421be..4bef5b1 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,93 @@

- UptoboxSDK + pyuptobox
Python SDK to interact with Uptobox API.

- + Python version - - DeepSource + + DeepSource

-UptoboxSDK is a powerful, user-friendly Python package designed for seamless interaction with Uptobox API. It allows users to perform various operations such as multiple connection methods, unrestricted direct use, easy implementation with other programs, and a simple plug-and-play installation. UptoboxSDK is forever free and open-source software. ## Features -* 🛡️ Multiple connection methods support -* 📦 Unrestricted direct use of the service -* 🛠️ Simple integration with other applications -* 🧩 Easy plug-and-play installation via setup.py -* ❤️ Always free and open-source software (FOSS) +- 🛡️ Methode de connexion multiple +- 📦 Direct use of the service, without restrictions +- 🛠️ Easy implementation in other programs +- 🧩 Plug-and-play installation via setup.py +- ❤️ Forever FOSS! -## Installation +## Installation -**Note**: UptoboxSDK requires [Python](https://python.org/) 3.7.0 or newer with PIP installed. +*Note: Requires [Python] 3.7.0 or newer with PIP installed.* -### With setup.py - -```sh +```shell $ python setup.py install ``` -You now have the `UptoboxSDK` package installed, and a `UptoboxSDK` executable is available. + +You now have the `pyuptobox` package installed and a `pyuptobox` executable is now available. + ### From Source Code -The following steps provide instructions on downloading, preparing, and running the code under a Venv environment. You can skip steps 3-5 with a simple `pip install .` call instead, but you will miss out on a wide array of benefits. -```sh -$ git clone https://github.com/hyugogirubato/UptoboxSDK -$ cd UptoboxSDK -$ python -m venv env -$ source env/bin/activate -$ python setup.py install -``` -As seen in Step 5, running the `UptoboxSDK` executable is somewhat different to a normal PIP installation. -See [Venv's Docs](https://docs.python.org/3/tutorial/venv.html) on various ways of making calls under the virtual-environment. +The following steps are instructions on download, preparing, and running the code under a Venv environment. +You can skip steps 3-5 with a simple `pip install .` call instead, but you miss out on a wide array of benefits. + +1. `git clone https://github.com/hyugogirubato/pyuptobox` +2. `cd pyuptobox` +3. `python -m venv env` +4. `source env/bin/activate` +5. `python setup.py install` + +As seen in Step 5, running the `pyuptobox` executable is somewhat different to a normal PIP installation. +See [Venv's Docs] on various ways of making calls under the virtual-environment. -### Usage -The following is a minimal example of using UptoboxSDK in a script. It retrieves the download link of a file. There are various other functionalities not shown in this specific example, such as: + [Python]: + [Venv's]: + [Venv's Docs]: -* Searching for a file -* Uploading a file -* User information retrieval -* and much more! +## Usage + +The following is a minimal example of using pyuptobox in a script. It gets the download link of a +file. There's various stuff not shown in this specific example like: + +- Searching for a file +- Uploading a file +- User information +- and much more! + +Just take a look around the Client code to see what stuff does. Everything is documented quite well. +There's also various functions in `utils.py` that showcases a lot of features. -Explore the Client code to discover additional features. Everything is well-documented. Also, check out the various functions in [utils.py](UptoboxSDK/utils.py) that showcase many other capabilities. ```py -from UptoboxSDK.client import Client +from pyuptobox.client import Client +from pyuptobox import utils # Demo: https://uptobox.com/5w4rff6r17oz if __name__ == "__main__": - # Create client + # create client client = Client() + file_code = utils.get_code(value="https://uptobox.com/5w4rff6r17oz") - # Login + # login data = client.login(token="USER_TOKEN") - - # File code - file_code = "5w4rff6r17oz" - # Get file info - info = client.get_file_info(code=file_code) + # get file info + info = client.get_file_info(file_codes=file_code) - # Get file download link - link = client.get_link(code=file_code) + # get file download link + link = client.get_file_link(file_code=file_code) print("I: Subscription: {}".format("PREMIUM" if data["premium"] == 1 else "FREE")) print("I: Name: {}".format(info["file_name"])) print("I: Size: {}".format(info["file_size"])) print("I: Link: {}".format(link)) - ``` + ## Credit - Uptobox Icon © Uptobox. diff --git a/UptoboxSDK/__init__.py b/UptoboxSDK/__init__.py deleted file mode 100644 index 7e004ac..0000000 --- a/UptoboxSDK/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .client import Client - -__version__ = "1.0.0" diff --git a/UptoboxSDK/client.py b/UptoboxSDK/client.py deleted file mode 100644 index 165b308..0000000 --- a/UptoboxSDK/client.py +++ /dev/null @@ -1,107 +0,0 @@ -import json -from pathlib import Path - -import requests -from bs4 import BeautifulSoup -from requests_toolbelt import MultipartEncoder - -from UptoboxSDK.exceptions import PremiumRequired -from UptoboxSDK.utils import get_size, get_code, get_input_bool, countdown - - -class Client: - HEADERS = { - "accept": "*/*", - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63" - } - - def __init__(self): - self._token = None - self._session = requests.Session() - self._web = "https://uptobox.com" - self._api = f"{self._web}/api" - - def _request(self, **kwargs) -> dict: - params = kwargs.get("params", {}) - params["token"] = self._token - - r = self._session.request( - method=kwargs.get("method", "GET").upper(), - url=kwargs.get("url", self._api), - params=params, - data=kwargs.get("data", None), - headers=kwargs.get("headers", Client.HEADERS)) - content = r.json() - code = content.get("statusCode", 1) - if not r.ok or (code != 0 and code != 39): - raise Exception(r.text) - return content.get("data", None) or content - - def login(self, **kwargs) -> dict: - login = kwargs.get("login", None) - password = kwargs.get("password", None) - token = kwargs.get("token", None) - xfss = kwargs.get("xfss", None) - if login is not None and password is not None: - r = self._session.request( - method="POST", - url=f"{self._web}/login", - data={"login": login, "password": password} - ) - if "My account" not in r.text: - raise Exception("Invalid password/login") - elif token is not None: - self._token = token - elif xfss is not None: - self._session.cookies.set("xfss", xfss) - else: - raise Exception("Invalid login credentials") - - if token is None: - r = self._session.request(method="GET", url=f"{self._web}/my_account") - soup = BeautifulSoup(r.content, "html.parser") - content_wrapper_div = soup.find("div", {"id": "content-wrapper"}) - data_ui = json.loads(content_wrapper_div.select_one('.width-content')['data-ui']) - self._token = data_ui["token"] - return self.get_user() - - def get_user(self) -> dict: - return self._request(url=f"{self._api}/user/me") - - def get_file_info(self, code: str) -> dict: - data = self._request(url=f"{self._api}/link/info", params={"fileCodes": get_code(code)})["list"][0] - data["file_size"] = get_size(data["file_size"]) - return data - - def file_search(self, path: str, search: str, limit: int = 10) -> dict: - return self._request(url=f"{self._api}/user/files", params={ - "path": path, - "limit": limit, - "searchField": "file_name", - "search": search - }) - - def get_link(self, code: str): - code = get_code(code) - data = self._request(url=f"{self._api}/link", params={"file_code": code}) - link = data.get("dlLink", None) - if link is None: - waiting_time = data["waiting"] + 1 - waiting_token = data["waitingToken"] - print(f"W: You have to wait {waiting_time} seconds to generate a new link") - if not get_input_bool(message="Do you want to wait?", default=True): - raise PremiumRequired("Free account, you have to wait before you can generate a new link.") - countdown(waiting_time) - link = self._request(url=f"{self._api}/link", params={"file_code": code, "waiting_token": waiting_token})["dlLink"] - return link - - def upload(self, file: Path) -> dict: - multi = MultipartEncoder(fields={"files": (file.name, open(file, "rb"))}) - headers = Client.HEADERS.copy() - headers["content-type"] = multi.content_type - return self._request( - method="POST", - url=self._request(url=f"{self._api}/upload")["uploadLink"], - data=multi, - headers=headers - )["files"] diff --git a/UptoboxSDK/exceptions.py b/UptoboxSDK/exceptions.py deleted file mode 100644 index d1c6a56..0000000 --- a/UptoboxSDK/exceptions.py +++ /dev/null @@ -1,10 +0,0 @@ -class UptoboxSDK(Exception): - """Exceptions used by UptoboxSDK.""" - - -class InvalidFileCode(UptoboxSDK): - """The file code in the url or passed as a parameter is incorrect.""" - - -class PremiumRequired(UptoboxSDK): - """Request file requires premium account.""" diff --git a/pyuptobox/__init__.py b/pyuptobox/__init__.py new file mode 100644 index 0000000..0fac95b --- /dev/null +++ b/pyuptobox/__init__.py @@ -0,0 +1,4 @@ +from .client import Client +from .utils import * + +__version__ = "1.0.1" diff --git a/pyuptobox/client.py b/pyuptobox/client.py new file mode 100644 index 0000000..4fbd303 --- /dev/null +++ b/pyuptobox/client.py @@ -0,0 +1,189 @@ +from __future__ import annotations + +import json +from pathlib import Path +import requests +from bs4 import BeautifulSoup +from requests_toolbelt import MultipartEncoder +from pyuptobox.exceptions import InvalidCredentials + + +class Client: + HEADERS = { + "accept": "*/*", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63" + } + + def __init__(self): + self.token = None + self._web = "https://uptobox.com" + self._api = f"{self._web}/api" + self._session = requests.Session() + + def _request(self, **kwargs) -> dict: + params = kwargs.get("params", {}) + if self.token is not None: + params["token"] = self.token + + r = self._session.request( + method=kwargs.get("method", "GET").upper(), + url=kwargs.get("url", self._api), + params=params, + data=kwargs.get("data", None), + headers=kwargs.get("headers", Client.HEADERS)) + + content = r.json() + if content.get("statusCode", 0) in [0, 18, 22, 24, 39]: + return content + raise Exception(r.text) + + def login(self, **kwargs) -> dict: + login = kwargs.get("login", None) + password = kwargs.get("password", None) + token = kwargs.get("token", None) + xfss = kwargs.get("xfss", None) + if login is not None and password is not None: + r = self._session.request( + method="POST", + url=f"{self._web}/login", + data={"login": login, "password": password} + ) + if "My account" not in r.text: + raise InvalidCredentials("Invalid password/login") + elif token is not None: + self._token = token + elif xfss is not None: + self._session.cookies.set("xfss", xfss) + else: + raise InvalidCredentials("Invalid login credentials") + + if token is None: + r = self._session.request(method="GET", url=f"{self._web}/my_account") + soup = BeautifulSoup(r.content, "html.parser") + content_wrapper_div = soup.find("div", {"id": "content-wrapper"}) + data_ui = json.loads(content_wrapper_div.select_one(".width-content")["data-ui"]) + self._token = data_ui["token"] + return self.get_user() + + def get_user(self) -> dict: + return self._request(url=f"{self._api}/user/me")["data"] + + def set_ssl_download(self, ssl: bool = True) -> None: + self._request(method="PATCH", url=f"{self._api}/user/settings", params={"ssl": int(ssl)}) + + def set_direct_download(self, direct: bool = True) -> None: + self._request(method="PATCH", url=f"{self._api}/user/settings", params={"directDownload": int(direct)}) + + def set_miniature_uts(self, miniature: bool = True) -> None: + self._request(method="PATCH", url=f"{self._api}/user/settings", params={"miniatureUts": int(miniature)}) + + def set_notif_deletions(self, notif: bool = False) -> None: + self._request(method="PATCH", url=f"{self._api}/user/settings", params={"notifDeletions": int(notif)}) + + def set_security_lock(self, lock: bool = False) -> None: + self._request(method="PATCH", url=f"{self._api}/user/securityLock", params={"securityLock": int(lock)}) + + def get_point_conversion(self, points: int = 10) -> dict: + if points not in [10, 25, 50, 100]: + raise Exception("The number of points passed in parameter is incompatible.") + return self._request(url=f"{self._api}/user/requestPremium", params={"points": points})["data"] + + def get_voucher(self, quantity: int = 1) -> list: + return self._request(url=f"{self._api}/user/createVoucher", params={ + "time": "30d", + "quantity": quantity + })["data"] + + def get_file_link(self, file_code: str, waiting_token: str = None) -> dict: + params = {"file_code": file_code} + if waiting_token is not None: + params["waitingToken"] = waiting_token + return self._request(url=f"{self._api}/link")["data"] + + def get_file_info(self, file_codes: list) -> list: + return self._request(url=f"{self._api}/link/info", params={"fileCodes": ",".join(file_codes)})["data"]["list"] + + def get_public_files(self, folder: str, hash: str, limit: int = 10, offset: int = 0) -> list: + return self._request(url=f"{self._api}/user/public", params={ + "folder": folder, + "hash": hash, + "limit": limit, + "offset": offset + })["data"]["list"] + + def get_public_folders(self, path: str, order: str, dir: str, limit: int = 10, offset: int = 0) -> dict: + return self._request(url=f"{self._api}/user/files", params={ + "path": path, + "limit": limit, + "offset": offset, + "orderBy": order, + "dir": dir + })["data"] + + def set_file_info(self, file_code: str, public: bool = None, name: str = None, description: str = None, password: str = None) -> bool: + params = {"file_code": file_code} + if public is not None: + params["public"] = int(public) + if name is not None: + params["new_name"] = name + if description is not None: + params["description"] = description + if password is not None: + params["password"] = password + return self._request(method="PATCH", url=f"{self._api}/user/files", params=params)["data"]["updated"] == 1 + + def set_public_files(self, file_codes: list, public: bool = False) -> bool: + return self._request(method="PATCH", url=f"{self._api}/user/files", params={ + "file_codes": ",".join(file_codes), + "public": int(public) + })["data"]["updated"] == len(file_codes) + + def move_folder(self, source: int, destination: int) -> None: + self._request(method="PATCH", url=f"{self._api}/user/files", params={ + "fld_id": source, + "destination_fld_id": destination, + "action": "move" + }) + + def move_files(self, file_codes: list, folder: int) -> bool: + return self._request(method="PATCH", url=f"{self._api}/user/files", params={ + "file_codes": ",".join(file_codes), + "destination_fld_id": folder, + "action": "move" + })["data"]["updated"] == len(file_codes) + + def copy_files(self, file_codes: list, folder: int) -> bool: + return self._request(method="PATCH", url=f"{self._api}/user/files", params={ + "file_codes": ",".join(file_codes), + "destination_fld_id": folder, + "action": "copy" + })["data"]["updated"] == len(file_codes) + + def rename_folder(self, folder: int, name: str) -> None: + self._request(method="PATCH", url=f"{self._api}/user/files", params={"fld_id": folder, "new_name": name}) + + def create_folder(self, path: str) -> None: + self._request(method="PUT", url=f"{self._api}/user/files", params={"path": path, "name": "newFolder"}) + + def delete_files(self, file_codes: list) -> None: + self._request(method="DELETE", url=f"{self._api}/user/files", params={"file_codes": ",".join(file_codes)}) + + def delete_folder(self, folder: int) -> None: + self._request(method="DELETE", url=f"{self._api}/user/files", params={"fld_id": folder}) + + def upload(self, path: Path) -> dict: + multi = MultipartEncoder(fields={"files": (path.name, open(path, "rb"))}) + headers = Client.HEADERS.copy() + headers["content-type"] = multi.content_type + return self._request( + method="POST", + url="https:" + self._request(url=f"{self._api}/upload")["data"]["uploadLink"], + data=multi, + headers=headers + )["files"] + + def get_pin(self, file_code: str) -> dict: + return self._request(url=f"{self._api}/streaming", params={"file_code": file_code})["data"] + + def check_pin(self, pin: str, hash: str) -> dict: + return self._request(url=f"{self._api}/streaming", params={"pin": pin, "check": hash})["data"] diff --git a/pyuptobox/exceptions.py b/pyuptobox/exceptions.py new file mode 100644 index 0000000..3e1b58d --- /dev/null +++ b/pyuptobox/exceptions.py @@ -0,0 +1,10 @@ +class PyUptobox(Exception): + """Exceptions used by pyuptobox.""" + + +class InvalidCredentials(PyUptobox): + """Invalid Credentials.""" + + +class InvalidFileCode(PyUptobox): + """The file code in the url or passed as a parameter is incorrect.""" diff --git a/UptoboxSDK/utils.py b/pyuptobox/utils.py similarity index 74% rename from UptoboxSDK/utils.py rename to pyuptobox/utils.py index d5e366d..c51d0dd 100644 --- a/UptoboxSDK/utils.py +++ b/pyuptobox/utils.py @@ -2,7 +2,7 @@ import re import time -from UptoboxSDK.exceptions import InvalidFileCode +from pyuptobox.exceptions import InvalidFileCode def get_size(bytes_size: int) -> str: @@ -15,12 +15,12 @@ def get_size(bytes_size: int) -> str: return f"{s} {name[i]}" -def get_code(code: str) -> str: - if code.startswith("https://uptobox.com/") or code.startswith("https://uptostream.com/"): - code = re.search(r"\.com/(\w+)", code).group(1) - if code is None or len(code) != 12: +def get_code(value: str) -> str: + if value.startswith("https://"): + value = re.search(r"\.com/(\w+)", value).group(1) + if value is None or len(value) != 12: raise InvalidFileCode("The file code format is invalid") - return code + return value def get_input_bool(message: str, default=True) -> bool: diff --git a/setup.py b/setup.py index 43f1ca7..05fbdbc 100644 --- a/setup.py +++ b/setup.py @@ -6,16 +6,16 @@ LONG_DESCRIPTION = f.read() setup( - name="UptoboxSDK", + name="pyuptobox", version="1.0.0", description="Python SDK to interact with Uptobox API.", long_description=LONG_DESCRIPTION, long_description_content_type="text/markdown", - url="https://github.com/hyugogirubato/UptoboxSDK", + url="https://github.com/hyugogirubato/pyuptobox", author="hyugogirubato", author_email="hyugogirubato@gmail.com", license="GNU GPLv3", - packages=["UptoboxSDK"], + packages=["pyuptobox"], install_requires=["requests", "requests_toolbelt", "beautifulsoup4"], classifiers=[ "Environment :: Console",