gofile

Module and tool to upload files to gofile.io
git clone https://code.alwayswait.ing/gofile.git
Log | Files | Refs

commit 3ec9bf27bd6deb9d8ff3934af900892add1d1d56
Author: archiveanon <>
Date:   Thu, 21 Sep 2023 02:33:40 +0000

Initial commit

Diffstat:
Asrc/gofile/__init__.py | 0
Asrc/gofile/api.py | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 161 insertions(+), 0 deletions(-)

diff --git a/src/gofile/__init__.py b/src/gofile/__init__.py diff --git a/src/gofile/api.py b/src/gofile/api.py @@ -0,0 +1,161 @@ +#!/usr/bin/python3 + +import argparse +import collections +import enum +import pathlib +from typing import Generic, Iterator, Optional, TypeVar + +import msgspec + +# normally I'd prefer using standard modules but streaming POST data is really important +import requests +from requests_toolbelt.multipart.encoder import MultipartEncoder + +T = TypeVar("T") + +GofileUpload = collections.namedtuple("GofileUpload", ["file", "result"]) + + +class GofileStatus(enum.Enum): + OK = "ok" + + # header is incorrect + # this may happen if uploadFile was called without using multipart/form-data + ERROR_HEADERS = "headersError" + + # no file was provided + # POST data may be malformed + ERROR_NOFILE = "error-noFile" + + # no owner token was provided + # happens if a folderId was provided without the correct owner + ERROR_OWNER = "error-owner" + + +class GofileServerResult(msgspec.Struct): + server: str + + +class GofileUploadResult(msgspec.Struct): + download_page: str = msgspec.field(name="downloadPage") + code: str = msgspec.field(name="code") + parent_folder: str = msgspec.field(name="parentFolder") + file_id: str = msgspec.field(name="fileId") + file_name: str = msgspec.field(name="fileName") + md5_hash: str = msgspec.field(name="md5") + + # a guestToken field is provided if no access token was given and no folderID was specified + guest_token: Optional[str] = msgspec.field(default=None, name="guestToken") + + +class GofileServerResponse(msgspec.Struct, Generic[T]): + status: GofileStatus + data: T + + +def _gofile_api_get(*args, type: T, **kwargs) -> T: + # performs a GET request and extracts the 'data' property from the response as a given type + # if the status is not 'ok', an exception is raised + r = requests.get(*args, **kwargs) + result = msgspec.json.decode(r.text, type=GofileServerResponse[type]) + + if result.status != GofileStatus.OK: + raise Exception(result) + return result.data + + +def _gofile_api_post(*args, type: T, **kwargs) -> T: + # performs a POST request and extracts the 'data' property from the response as a given type + # if the status is not 'ok', an exception is raised + r = requests.post(*args, **kwargs) + result = msgspec.json.decode(r.text, type=GofileServerResponse[type]) + + if result.status != GofileStatus.OK: + raise Exception(result) + return result.data + + +def get_upload_server() -> GofileServerResult: + return _gofile_api_get("https://api.gofile.io/getServer", type=GofileServerResult) + + +def upload_single( + file: pathlib.Path, + token: Optional[str] = None, + folder_id: Optional[str] = None, + server: Optional[str] = None, +) -> GofileUpload: + # we return a GofileUpload instead of a GofileUploadResult so there's consistency between upload_single / upload_multiple + if not server: + upload_server_result = get_upload_server() + server = upload_server_result.server + + post_data = { + "file": (file.name, file.open("rb"), "application/octet-stream"), + } + + if token: + post_data["token"] = token + if folder_id: + post_data["folderId"] = folder_id + + multipart_data = MultipartEncoder(fields=post_data) + upload_result = _gofile_api_post( + f"https://{server}.gofile.io/uploadFile", + data=multipart_data, + headers={"Content-Type": multipart_data.content_type}, + type=GofileUploadResult, + ) + + return GofileUpload(file, upload_result) + + +def upload_multiple( + files: Iterator[pathlib.Path], token: Optional[str] = None, folder_id: Optional[str] = None +) -> list[GofileUpload]: + # returns a generator of GofileUpload instances in the same order files were given + first_file, *other_files = files + + upload_server_result = get_upload_server() + server = upload_server_result.server + + first_upload = upload_single(first_file, token, folder_id, server) + + if not token: + token = first_upload.result.guest_token + if not folder_id: + folder_id = first_upload.result.parent_folder + + yield first_upload + + for file in other_files: + upload = upload_single(file, token, folder_id, server) + yield upload + + +def main(): + # you may save and reuse a guest token + # there's no clear indicator on whether or not it expires + + parser = argparse.ArgumentParser() + parser.add_argument("path", type=pathlib.Path, help="Path to single file or folder") + + args = parser.parse_args() + + upload = None + + if not args.path.exists(): + print(f"Path '{args.path}' does not exist") + return + elif args.path.is_dir(): + results = upload_multiple(args.path.glob("*")) + upload, *other_uploads = results + elif args.path.is_file(): + upload = upload_single(args.path) + + print(upload.result) + + +if __name__ == "__main__": + main()