Initial commit

This commit is contained in:
garnajee
2025-12-10 16:37:49 +01:00
parent 21e6af7b8c
commit 8d89a84739
7 changed files with 424 additions and 0 deletions

33
.github/workflows/build_release.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Build and Release
on:
push:
tags:
- 'v*'
paths-ignore:
- "README.md"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.9"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Archive artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

113
README.md Normal file
View File

@@ -0,0 +1,113 @@
# GofilePy
A Python library and CLI tool for [Gofile.io](https://gofile.io).
It supports the free API tiers, streaming uploads (low memory usage for large files), and script-friendly JSON output.
## Features
- 🚀 **Streaming Uploads**: Upload 100GB+ files without loading them into RAM.
- 📂 **Folder Management**: Upload to specific folders or create new ones automatically.
- 🤖 **Script Ready**: JSON output mode for easy parsing in pipelines.
- 🆓 **Free Tier Support**: Handles Guest accounts and Standard tokens.
- 📊 **Progress Bar**: Visual feedback for long uploads.
## Installation
### From Source
1. Clone the repository.
2. Install via pip:
```bash
pip install .
```
## Usage (CLI)
### Basic Upload
Upload a single file. A new public folder will be created automatically if you don't provide one.
```bash
gofilepy video.mp4
```
### Upload with Token
Export your token (Get it from your Gofile Profile) to access your account storage.
```bash
export GOFILE_TOKEN="your_token_here"
gofilepy my_file.zip
```
### Upload to a Specific Folder
If you have an existing folder ID:
```bash
gofilepy -f "folder-uuid-123" image.png
```
### Group Upload (Single Folder)
Upload multiple files. The first file creates a folder, and the rest are uploaded into it.
```bash
gofilepy -s part1.rar part2.rar part3.rar
```
### Scripting Mode (JSON Output)
Use `--json` to suppress human-readable text and output a JSON array.
```bash
gofilepy --json file.txt
# Output: [{"file": "file.txt", "status": "success", "downloadPage": "...", ...}]
```
### Verbose Mode
Debug connection issues or API responses.
```bash
gofilepy -vv big_file.iso
```
## Usage (Library)
You can use `gofilepy` in your own Python scripts.
```python
from gofilepy import GofileClient
# Initialize (Token optional for guest upload)
client = GofileClient(token="your_token")
# Simple Callback for progress
def my_progress(bytes_read):
print(f"Read: {bytes_read} bytes")
# Upload (Streaming)
try:
response = client.upload_file(
file_path="/path/to/movie.mkv",
folder_id=None, # None = Create new folder
callback=my_progress
)
print(f"Uploaded! Download here: {response['downloadPage']}")
except Exception as e:
print(f"Error: {e}")
```
## Building for Release
To build a `.whl` (Wheel) file and a source distribution:
1. Install build tools:
```bash
pip install build twine
```
2. Run build:
```bash
python -m build
```
3. Artifacts will be in `dist/`.
## License
This project is licensed under the [MIT](LICENSE) License.

24
pyproject.toml Normal file
View File

@@ -0,0 +1,24 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "gofilepy"
version = "1.0.0"
description = "A Python CLI and Library for gofile.io"
readme = "README.md"
authors = [{ name = "Garnajee", email = "62147746+garnajee@users.noreply.github.com" }]
license = { text = "MIT" }
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
requires-python = ">=3.7"
dependencies = [
"httpx", # The modern HTTP client
"tqdm" # Progress bar
]
[project.scripts]
gofilepy = "gofilepy.cli:main"

7
src/gofilepy/__init__.py Normal file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env python3
from .client import GofileClient
__version__ = "1.0.0"
__all__ = ["GofileClient"]

122
src/gofilepy/cli.py Normal file
View File

@@ -0,0 +1,122 @@
#!/usr/bin/env python3
import argparse
import os
import json
import logging
from tqdm import tqdm
from .client import GofileClient
# Configure Logging
logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(message)s')
logger = logging.getLogger("gofilepy")
def main():
parser = argparse.ArgumentParser(description="Gofile.io CLI Uploader (HTTPX Edition)")
parser.add_argument("files", nargs='+', help="Files to upload")
parser.add_argument("-s", "--to-single-folder", action="store_true",
help="Upload multiple files to the same folder.")
parser.add_argument("-f", "--folder-id", type=str, default=None,
help="ID of an existing Gofile folder.")
parser.add_argument("-vv", "--verbose", action="store_true",
help="Show detailed debug info.")
parser.add_argument("--json", action="store_true",
help="Output result as JSON for scripts.")
args = parser.parse_args()
# Log Level Handling
if args.verbose:
logger.setLevel(logging.DEBUG)
# HTTPX can be verbose, enable if needed
# logging.getLogger("httpx").setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
logging.getLogger("httpx").setLevel(logging.WARNING)
# Token Logic
token = os.environ.get("GOFILE_TOKEN")
if not token and args.to_single_folder and not args.folder_id:
logger.debug("No token found. Proceeding as Guest.")
client = GofileClient(token=token)
target_folder_id = args.folder_id
results = []
for file_path in args.files:
if not os.path.exists(file_path):
res_err = {"file": file_path, "status": "error", "message": "File not found"}
results.append(res_err)
if not args.json:
logger.error(f"File not found: {file_path}")
continue
file_size = os.path.getsize(file_path)
filename = os.path.basename(file_path)
# Init Progress Bar (Only if not JSON mode)
pbar = None
if not args.json:
pbar = tqdm(total=file_size, unit='B', unit_scale=True, desc=f"Uploading {filename}")
def progress_update(chunk_size):
if pbar:
pbar.update(chunk_size)
try:
data = client.upload_file(
file_path=file_path,
folder_id=target_folder_id,
callback=progress_update
)
# --- Auto-Folder Management for Guests ---
# If we are in single folder mode and it's the first upload
if args.to_single_folder and target_folder_id is None:
if 'parentFolder' in data:
target_folder_id = data['parentFolder']
logger.debug(f"Parent folder set to: {target_folder_id}")
# If guest, capture the guestToken to write to the same folder next time
if 'guestToken' in data and not client.token:
client.token = data['guestToken']
# Re-auth client with new token
client.client.headers.update({"Authorization": f"Bearer {client.token}"})
logger.debug(f"Guest token applied: {client.token}")
results.append({
"file": filename,
"status": "success",
"downloadPage": data.get("downloadPage"),
"directLink": data.get("directLink", "N/A"), # Sometimes available
"parentFolder": data.get("parentFolder")
})
except Exception as e:
err_msg = str(e)
results.append({"file": filename, "status": "error", "message": err_msg})
if not args.json:
logger.error(f"Upload failed: {err_msg}")
finally:
if pbar:
pbar.close()
# Output
if args.json:
print(json.dumps(results, indent=2))
else:
print("\n--- Summary ---")
for res in results:
if res['status'] == 'success':
print(f"{res['file']} -> {res['downloadPage']}")
else:
print(f"{res['file']} -> {res['message']}")
if __name__ == "__main__":
main()

97
src/gofilepy/client.py Normal file
View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python3
import httpx
import logging
import os
from typing import Optional, List, Dict, Callable
from .utils import ProgressFileReader
logger = logging.getLogger(__name__)
class GofileClient:
API_ROOT = "https://api.gofile.io"
UPLOAD_SERVER_URL = "https://upload.gofile.io"
def __init__(self, token: Optional[str] = None):
self.token = token
# Increase timeout for large API operations, though uploads handle their own timeout
self.client = httpx.Client(timeout=30.0)
if self.token:
logger.debug(f"Initialized with token: {self.token[:4]}***")
self.client.headers.update({"Authorization": f"Bearer {self.token}"})
def _handle_response(self, response: httpx.Response) -> Dict:
try:
data = response.json()
except Exception:
logger.error(f"Failed to parse JSON: {response.text}")
response.raise_for_status()
return {}
if data.get("status") != "ok":
logger.error(f"API Error: {data}")
raise Exception(f"Gofile API Error: {data.get('status')} - {data.get('data')}")
return data.get("data", {})
def get_server(self) -> str:
"""
Gofile suggests using specific servers (availables in their doc),
but 'upload.gofile.io' uses DNS geo-routing automatically.
We stick to the best practice default.
"""
return self.UPLOAD_SERVER_URL
def create_folder(self, parent_folder_id: str, folder_name: str) -> Dict:
logger.debug(f"Creating folder '{folder_name}' in '{parent_folder_id}'")
url = f"{self.API_ROOT}/contents/createFolder"
payload = {
"parentFolderId": parent_folder_id,
"folderName": folder_name
}
res = self.client.post(url, json=payload)
return self._handle_response(res)
def delete_content(self, content_ids: List[str]) -> Dict:
logger.debug(f"Deleting content IDs: {content_ids}")
url = f"{self.API_ROOT}/contents"
# HTTPX needs 'content' or 'json' for DELETE requests explicitly if body is required
res = self.client.request("DELETE", url, json={"contentsId": ",".join(content_ids)})
return self._handle_response(res)
def upload_file(self,
file_path: str,
folder_id: Optional[str] = None,
callback: Optional[Callable[[int], None]] = None) -> Dict:
server_url = f"{self.get_server()}/uploadfile"
file_name = os.path.basename(file_path)
# Prepare parameters
data = {}
if self.token:
data["token"] = self.token
if folder_id:
data["folderId"] = folder_id
# Use our custom ProgressFileReader
# If no callback is provided, we use a dummy lambda to avoid errors
progress_callback = callback if callback else lambda x: None
logger.info(f"Starting upload: {file_name} -> {server_url}")
# Open file using our wrapper
with ProgressFileReader(file_path, progress_callback) as f:
files = {'file': (file_name, f)}
# Use a longer timeout for the upload specifically (None = infinite)
# This is crucial for 2000GB files
res = self.client.post(
server_url,
data=data,
files=files,
timeout=None
)
return self._handle_response(res)

28
src/gofilepy/utils.py Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
import typing
import io
class ProgressFileReader(io.BufferedReader):
"""
Wraps a file object to trigger a callback when data is read.
This allows monitoring upload progress in httpx without loading the file into RAM.
"""
def __init__(self, filename: str, callback: typing.Callable[[int], None]):
self._f = open(filename, 'rb')
self._callback = callback
# Get file size for verification if needed, or just standard init
super().__init__(self._f)
def read(self, size: int = -1) -> bytes:
# Read the chunk from disk
chunk = self._f.read(size)
# Update the progress bar with the length of the chunk read
if chunk:
self._callback(len(chunk))
return chunk
def close(self) -> None:
if hasattr(self, '_f'):
self._f.close()