# telestore

A lightweight Python client library for [TeleStore](https://telestore.onrender.com) — a free file-storage service that uses Telegram channels as its backend.

---

## 1. What is TeleStore?

TeleStore is a REST API service that lets you store files up to **2 GB** inside Telegram channels — for free.  It exposes a simple HTTP API that is authentication via an API key, making it easy to use as a drop-in file-storage backend for lightweight SaaS products or hobby projects.  This library wraps that API into an ergonomic Python interface so you can go from zero to first upload in under 5 minutes.

→ Live instance: <https://telestore.onrender.com>

---

## 2. Installation

```bash
pip install telestore
```

For better MIME-type detection (strongly recommended), also install `python-magic`:

```bash
pip install telestore python-magic
# On Windows you may also need the libmagic binary:
pip install python-magic-bin
```

`python-magic` is entirely optional — the library falls back to filename-based detection via the standard library `mimetypes` module when it is not installed.

---

## 3. Getting Your API Key

1. Sign up at <https://telestore.onrender.com/auth/signup/>
2. Log in and navigate to <https://telestore.onrender.com/api-keys/>
3. Click **Generate Key**, give it a name, and copy the key starting with `sk-`

> **Keep your API key secret.**  Anyone who has it can upload files on your behalf.

---

## 4. Quickstart

From zero to first upload in under 10 lines:

```python
from telestore import TeleStoreClient

client = TeleStoreClient(api_key="sk-your-api-key-here")

# Upload a file
result = client.upload("photo.jpg")
print(result.view_url)   # share this link

# Upload JSON data directly
result = client.upload_json({"user": "alice", "score": 42})
data = client.download_json(result.file_id)

# Upload raw text
result = client.upload_text("Hello, TeleStore!")
text = client.download_text(result.file_id)
```

---

## 5. Usage Examples

### Creating the client

```python
from telestore import TeleStoreClient

client = TeleStoreClient(
    api_key="sk-your-api-key-here",
    base_url="https://telestore.onrender.com",  # default — change for self-hosted
    timeout=120,                                 # seconds; default is 120
)
```

---

### Uploading files

**From a file path:**
```python
result = client.upload("path/to/document.pdf")
print(result.file_id)       # opaque identifier — keep this private
print(result.view_url)      # publicly accessible inline URL
print(result.download_url)  # publicly accessible attachment URL
print(result.mime_type)     # e.g. "application/pdf"
print(result.size_bytes)    # size in bytes
```

**From bytes:**
```python
data = b"raw binary content"
result = client.upload(data, filename="data.bin")
```

**From a file-like object:**
```python
with open("archive.zip", "rb") as fh:
    result = client.upload(fh)
```

**From a remote URL:**
```python
result = client.url_upload("https://example.com/image.png")
```

---

### Uploading typed content

**Image (validates MIME type):**
```python
# Raises UploadError if the file is not actually an image.
result = client.upload_image("photo.jpg")
```

**Plain text:**
```python
result = client.upload_text("Hello, world!", filename="greeting.txt")
# Custom encoding:
result = client.upload_text("こんにちは", filename="jp.txt", encoding="utf-8")
```

**JSON dict:**
```python
result = client.upload_json({"key": "value", "items": [1, 2, 3]})
```

---

### Downloading files

**Into memory:**
```python
raw: bytes = client.download("abc123")
```

**To disk:**
```python
from pathlib import Path

path = client.download("abc123", destination="downloads/file.bin")
# path is a pathlib.Path pointing to the saved file
```

**As text:**
```python
text: str = client.download_text("abc123")
```

**As JSON:**
```python
data: dict = client.download_json("abc123")
```

---

### Getting URLs without a network call

```python
view_url     = client.get_url("abc123", mode="view")      # inline
download_url = client.get_url("abc123", mode="download")  # attachment
```

---

## 6. Configuration

| Parameter | Default | Description |
|---|---|---|
| `api_key` | *(required)* | Your TeleStore API key (`sk-...`). |
| `base_url` | `https://telestore.onrender.com` | Change this only when using a self-hosted TeleStore instance. |
| `timeout` | `120` | Request timeout in seconds.  Files larger than 50 MB use a slower Telethon-based upload path on the server, so the default is intentionally generous. |

**Loading credentials from the environment:**

```python
import os
from dotenv import load_dotenv
from telestore import TeleStoreClient

load_dotenv()  # reads .env file

client = TeleStoreClient(
    api_key=os.environ["TELESTORE_API_KEY"],
    base_url=os.getenv("TELESTORE_BASE_URL", "https://telestore.onrender.com"),
)
```

Copy `.env.example` to `.env` and fill it in:

```
TELESTORE_API_KEY=sk-your-api-key-here
TELESTORE_BASE_URL=https://telestore.onrender.com
```

---

## 7. Error Handling

All exceptions inherit from `TeleStoreError` so you can catch them at any level of specificity:

```python
from telestore import (
    TeleStoreClient,
    TeleStoreError,       # base — catches everything below
    AuthenticationError,  # 401 — bad or missing API key
    UploadError,          # upload failed
    DownloadError,        # download failed
    FileSizeError,        # file exceeds 2 GiB
    FileNotFoundError,    # file_id not found on server
    ServerError,          # 5xx from TeleStore
)

client = TeleStoreClient(api_key="sk-...")

try:
    result = client.upload("huge_file.iso")
except FileSizeError as exc:
    print(f"File too large: {exc}")
except AuthenticationError:
    print("Check your API key.")
except UploadError as exc:
    print(f"Upload failed: {exc}")
except ServerError as exc:
    print(f"TeleStore server error: {exc}")
except TeleStoreError as exc:
    # Catch-all for any other library error
    print(f"TeleStore error: {exc}")
```

> **Note:** `telestore.FileNotFoundError` shadows the Python built-in in this
> namespace.  Import it explicitly by name if you need both in the same scope.

---

## 8. File Deletion

Delete files from TeleStore using the delete endpoint:

```python
# Delete a file
client.delete("abc123")

# The file is removed from both Telegram and TeleStore database
```

---

## 9. Advanced Patterns

### Retry Logic with Tenacity

```python
from tenacity import retry, stop_after_attempt, wait_exponential
from telestore import TeleStoreClient, TeleStoreError

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=2, max=10),
    reraise=True
)
def upload_with_retry(client: TeleStoreClient, path: str):
    return client.upload(path)

client = TeleStoreClient(api_key="sk-...")
result = upload_with_retry(client, "large_file.iso")
```

### Batch Upload with Progress Tracking

```python
from pathlib import Path
from telestore import TeleStoreClient

client = TeleStoreClient(api_key="sk-...")
files_dir = Path("./documents")

results = []
for i, file_path in enumerate(files_dir.glob("*.pdf"), 1):
    print(f"Uploading {i}/{len(list(files_dir.glob('*.pdf')))} — {file_path.name}")
    result = client.upload(file_path)
    results.append(result)
    print(f"  ✓ {result.view_url}")

# Save file_id mappings for later retrieval
with open("uploaded_files.txt", "w") as f:
    for result in results:
        f.write(f"{result.original_filename},{result.file_id}\n")
```

### Monitoring Upload Progress

For real-time progress during large file uploads, implement custom streaming:

```python
import requests
from pathlib import Path

client = TeleStoreClient(api_key="sk-...")
file_path = Path("large_video.mp4")
file_size = file_path.stat().st_size

# Upload with callback
with open(file_path, "rb") as f:
    # Implement custom progress tracking here
    result = client.upload(f, filename=file_path.name)
```

### Integration with Django

```python
# In a Django view
from django.http import JsonResponse
from telestore import TeleStoreClient, UploadError

def upload_document(request):
    if not request.FILES.get('document'):
        return JsonResponse({'error': 'No file'}, status=400)
    
    try:
        client = TeleStoreClient(api_key=request.user.telestore_api_key)
        result = client.upload(request.FILES['document'])
        
        # Save mapping in your database
        UserFile.objects.create(
            user=request.user,
            filename=result.original_filename,
            file_id=result.file_id,
            size_bytes=result.size_bytes
        )
        
        return JsonResponse({
            'success': True,
            'view_url': result.view_url,
            'file_id': result.file_id
        })
    except UploadError as e:
        return JsonResponse({'error': str(e)}, status=400)
```

### Integration with FastAPI

```python
from fastapi import FastAPI, UploadFile, Depends, HTTPException
from telestore import TeleStoreClient

app = FastAPI()

def get_telestore_client() -> TeleStoreClient:
    return TeleStoreClient(api_key="sk-...")

@app.post("/upload/")
async def upload_file(
    file: UploadFile,
    client: TeleStoreClient = Depends(get_telestore_client)
):
    try:
        content = await file.read()
        result = client.upload(content, filename=file.filename)
        return {
            "file_id": result.file_id,
            "view_url": result.view_url,
            "download_url": result.download_url
        }
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))
```

---

## 10. Limitations & Known Issues

### Current Limitations

- **Files larger than 50 MB** are uploaded through a slower Telethon MTProto path on the server side, which is significantly slower than the Bot API path. The default 120-second timeout accommodates this, but very large files on slow connections may still time out — increase `timeout` as needed.
- **View and download URLs are publicly accessible** — no authentication required. Anyone who knows a `file_id` can access the file. Treat `file_id` values as secrets for sensitive content.
- **No file listing on anonymous access** — `list_files()` requires authentication. You must track `file_id` values yourself if uploading anonymously.
- **No async support in v0.1** — fully synchronous. Async support (`asyncio` / `httpx`) planned for v0.2.
- **No built-in retry logic** — failed requests raise immediately. Use `tenacity` for automatic retries.
- **`python-magic` is optional** — without it, MIME detection falls back to filename extension, less reliable for binary.

### Planned Features

- Async/await support
- Streaming uploads with progress callbacks
- File expiration and TTL
- Signed/time-limited URLs
- Multi-part uploads for large files
- Batch operations
- WebSocket support for real-time updates

---

## 11. Troubleshooting

### "Invalid or missing API key (HTTP 401)"

**Cause:** API key not set, empty, or revoked.

**Fix:**
```python
# Make sure your API key is valid
client = TeleStoreClient(api_key="sk-abc1234...")  # Must start with 'sk-'

# Regenerate key at https://telestore.onrender.com/api-keys/
```

### "File size exceeds 2 GB"

**Cause:** Attempting to upload a file larger than Telegram's limit.

**Fix:**
```python
# Split large files or use cloud storage instead
from telestore import FileSizeError

try:
    result = client.upload("huge_file.iso")
except FileSizeError:
    print("Use a different storage backend for files > 2 GB")
```

### "Upload request failed: Connection timeout"

**Cause:** Server taking too long (slow network or large file via Telethon).

**Fix:**
```python
# Increase timeout for large files
client = TeleStoreClient(api_key="sk-...", timeout=300)  # 5 minutes
```

### "The requested file was not found (HTTP 404)"

**Cause:** File was deleted or `file_id` is incorrect.

**Fix:**
```python
# Verify file_id before download
from telestore import FileNotFoundError

try:
    data = client.download("invalid_file_id")
except FileNotFoundError:
    print("File has been deleted or never existed")
```

### "python-magic not installed"

**Cause:** Optional dependency missing.

**Fix:**
```bash
# Install for better MIME detection
pip install python-magic
pip install python-magic-bin  # Windows only
```

---

## 12. Performance Tips

1. **Use `python-magic` for accurate MIME detection** — without it, guessing fails for binary files.
2. **Set appropriate timeout** — 120s default, but increase for files >100 MB.
3. **Batch operations** — upload multiple files sequentially rather than in parallel (Telegram rate limits).
4. **Stream large downloads to disk** — don't load into memory:
   ```python
   path = client.download("abc123", destination="cache/file.bin")
   ```
5. **Cache `file_id` values** — avoid re-uploading same content.
6. **Use `get_url()` for public links** — no network call, instant URL generation.

---

## 13. Contributing

Contributions are welcome! Please follow these guidelines:

### Setting Up Development Environment

```bash
git clone <repo-url>
cd Telestore_library_v0.1
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

# Install development dependencies
pip install -e ".[dev]"
```

### Running Tests

```bash
# Run all tests
pytest tests/

# Run with coverage
pytest --cov=telestore tests/

# Run specific test
pytest tests/test_client.py::test_upload
```

### Code Style

```bash
# Format code
black telestore tests

# Lint
flake8 telestore tests

# Type checking
mypy telestore
```

### Submitting Changes

1. Create a feature branch: `git checkout -b feature/my-feature`
2. Write tests for new functionality
3. Ensure all tests pass: `pytest`
4. Format code: `black . && flake8 .`
5. Commit: `git commit -am "Add my feature"`
6. Push: `git push origin feature/my-feature`
7. Open a pull request with clear description

**Requirements for PR acceptance:**
- All tests must pass
- Type hints for all new functions
- Docstrings for all public methods
- No breaking changes to existing API

---

## 14. API Reference

### TeleStoreClient

```python
class TeleStoreClient:
    """
    Synchronous HTTP client for TeleStore REST API.
    
    Attributes:
        api_key (str): Your TeleStore API key
        base_url (str): Server URL
        timeout (int): Request timeout in seconds
    """
    
    def __init__(
        self,
        api_key: str,
        base_url: str = "https://telestore.onrender.com",
        timeout: int = 120,
    ) -> None: ...
    
    def upload(
        self,
        source: Union[str, Path, bytes, IO[bytes]],
        filename: Optional[str] = None,
    ) -> UploadedFile: ...
    
    def download(
        self,
        file_id: str,
        destination: Optional[Union[str, Path]] = None,
    ) -> Union[bytes, Path]: ...
    
    def delete(self, file_id: str) -> None: ...
    
    def list_files(self, ids_only: bool = False) -> List[Dict]: ...
    
    def list_file_ids(self) -> List[str]: ...
    
    def get_url(self, file_id: str, mode: str = "view") -> str: ...
    
    def url_upload(
        self,
        url: str,
        filename: Optional[str] = None,
    ) -> UploadedFile: ...
    
    def upload_image(
        self,
        source: Union[str, Path, bytes, IO[bytes]],
        filename: Optional[str] = None,
    ) -> UploadedFile: ...
    
    def upload_text(
        self,
        text: str,
        filename: str = "file.txt",
        encoding: str = "utf-8",
    ) -> UploadedFile: ...
    
    def upload_json(
        self,
        data: Dict,
        filename: str = "data.json",
    ) -> UploadedFile: ...
    
    def download_text(
        self,
        file_id: str,
        encoding: str = "utf-8",
    ) -> str: ...
    
    def download_json(self, file_id: str) -> Dict: ...
```

### UploadedFile

```python
@dataclass
class UploadedFile:
    """
    Metadata for a successfully uploaded file.
    
    Attributes:
        file_id (str): Opaque file identifier
        view_url (str): Inline viewing URL (no auth required)
        download_url (str): Download/attachment URL (no auth required)
        original_filename (str): Filename used at upload
        size_bytes (int): File size in bytes
        mime_type (str): Detected MIME type
    """
    file_id: str
    view_url: str
    download_url: str
    original_filename: str
    size_bytes: int
    mime_type: str
```

### Exception Hierarchy

```
TeleStoreError (base)
├── AuthenticationError          # 401, bad/missing API key
├── UploadError                  # Upload failed
├── DownloadError                # Download failed
├── FileSizeError                # File > 2 GB
├── FileNotFoundError             # 404, file_id not found
└── ServerError                  # 5xx, server error
```

---

## 15. FAQ

**Q: Can I use TeleStore in production?**  
A: Yes, but it's a self-hosted service. TeleStore is production-ready for small-to-medium workloads. For mission-critical systems, implement backups and redundancy at the server level.

**Q: What happens if Telegram deletes/bans my channel?**  
A: Files become inaccessible. The server tracks this via the `is_banned` flag on channels. TeleStore includes automatic backup to secondary channels to mitigate this.

**Q: Can I encrypt files before uploading?**  
A: Not built-in, but you can encrypt client-side before calling `upload()`. Decrypt after downloading.

**Q: Is there a quota per user?**  
A: Not enforced in v0.1. Server admin can implement rate limiting if needed.

**Q: Can I run my own TeleStore instance?**  
A: Yes! Fork the server repository and self-host. See server [README](../TeleStore/README.md) for setup.

---

## License

MIT License — see [LICENSE](../LICENSE) for details.

---

**Made with ❤️ for developers who want free file storage**
