Metadata-Version: 2.4
Name: tello-renewal
Version: 0.5.1
Summary: Tello mobile plan automatic renewal system
Author-email: Oaklight <oaklight@gmx.com>
License: MIT
Project-URL: Homepage, https://github.com/Oaklight/tello-renewal
Project-URL: Repository, https://github.com/Oaklight/tello-renewal
Project-URL: Documentation, https://github.com/Oaklight/tello-renewal#readme
Project-URL: Bug Tracker, https://github.com/Oaklight/tello-renewal/issues
Keywords: tello,mobile,renewal,automation
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications :: Telephony
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: pydantic>=2.10.0
Requires-Dist: pydantic-settings>=2.7.0
Requires-Dist: cloakbrowser>=0.1.0
Requires-Dist: typing-extensions>=4.0.0; python_version < "3.11"
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: ruff==0.15.20; extra == "dev"
Requires-Dist: ty==0.0.54; extra == "dev"
Requires-Dist: pre-commit>=3.0.0; extra == "dev"
Requires-Dist: build>=1.0.0; extra == "dev"
Requires-Dist: twine>=4.0.0; extra == "dev"
Provides-Extra: test
Requires-Dist: pytest>=7.0.0; extra == "test"
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
Requires-Dist: pytest-mock>=3.10.0; extra == "test"

# Tello-Renewal

[![GitHub release](https://img.shields.io/github/v/release/Oaklight/Tello-Renewal?color=green)](https://github.com/Oaklight/Tello-Renewal/releases)
[![Docker Image](https://img.shields.io/docker/v/oaklight/tello-renewal?label=docker&color=green)](https://hub.docker.com/r/oaklight/tello-renewal)

[中文版](README_zh.md) | English Version

Automatic renewal system for Tello mobile plans. Uses
[CloakBrowser](https://github.com/CloakHQ/CloakBrowser) (headless Chromium with 57 source-level
patches) to bypass Cloudflare managed challenges on Tello's checkout endpoint.

## Features

- 🔄 Automatic plan renewal via CloakBrowser
- 🛡️ Cloudflare bypass via real browser fingerprinting (CloakBrowser)
- 📋 Plan catalog check — monitor available plans without login
- 📧 Email notifications for success / failure
- 🧪 Dry-run mode for safe testing
- 📡 JSON-first CLI output — machine-readable by default, `--human` for text
- ⚙️ YAML configuration with pydantic validation
- 📊 Structured logging with rotation
- 🐳 Docker support — single image with CloakBrowser pre-bundled

## Installation

### From PyPI

```bash
pip install tello-renewal
python -m cloakbrowser install   # download patched Chromium (~200 MB, one-time)
```

### Requirements

- Python 3.10+
- CloakBrowser's Chromium is self-contained (downloaded via `python -m cloakbrowser install`)

## Quick Start

1. **Create configuration file:**

   ```bash
   tello-renewal config-init
   ```

2. **Edit `config.yaml`** with your Tello credentials:

   ```yaml
   tello:
     email: "you@example.com"
     password: "your_password"
     card_expiration: "1/27"      # MM/YY
   ```

3. **Test:**

   ```bash
   tello-renewal config-validate
   tello-renewal email-test
   tello-renewal status
   ```

4. **Renew:**

   ```bash
   tello-renewal renew --dry-run   # test without charging
   tello-renewal renew             # real renewal
   ```

## Configuration Reference

```yaml
tello:
  email: "you@example.com"
  password: "your_password"
  card_expiration: "1/27"        # MM/YY, MM/YYYY, or YYYY-MM-DD
  base_url: "https://tello.com"
  login_timeout: 30

browser:
  browser_type: "cloakbrowser"
  headless: true                   # false for headed mode (helps with CF)
  timeout: 60
  # proxy_server: "http://10.0.0.1:7890"

# Expected plan (optional) — status/renew warn on mismatch
plan:
  data: "2 GB"                     # "2 GB", "10 GB", "20 GB", "Unlimited"
  call: "Unlimited"                # "300 min", "Unlimited"

renewal:
  auto_renew: true
  days_before_renewal: 1         # start attempting N days before due date
  max_retries: 3
  retry_delay: 300               # seconds between retries
  dry_run: false
  state_folder_path: ".tello_state"

smtp:
  server: "smtp.gmail.com"
  port: 587
  username: "you@gmail.com"
  password: "app_password"
  from_email: '"Tello Renewal" <you@gmail.com>'
  use_tls: true

notifications:
  email_enabled: true
  recipients:
    - "admin@example.com"
  send_on_success: true
  send_on_failure: true
  send_on_not_due: false

logging:
  level: "INFO"
  format: "detailed"             # simple | detailed | json
  file: "tello_renewal.log"
  max_size: "10MB"
  backup_count: 5
  console_output: true
```

## CLI Commands

```bash
tello-renewal renew [--dry-run] [--force]   # execute renewal
tello-renewal status                         # account status and balance
tello-renewal plan-check                     # list available plan options
tello-renewal config-init [--output FILE]   # generate example config
tello-renewal config-validate               # validate config file
tello-renewal email-test                    # send test notification
```

**Global options:** `--config FILE`, `--verbose`, `--human`, `--version`

Output is **JSON by default** (machine-readable). Add `--human` for a
human-friendly table/text view:

```bash
tello-renewal status                        # JSON output
tello-renewal --human status                # human-readable text
tello-renewal --human plan-check            # plan table
```

## Exit Codes

| Code | Meaning             |
| ---- | ------------------- |
| 0    | Success             |
| 1    | General error       |
| 2    | Configuration error |
| 5    | Renewal failed      |
| 6    | Not due for renewal |
| 7    | Skipped (cached)    |

## Docker

The Docker image (`python:3.12-slim` base) includes CloakBrowser's Chromium pre-downloaded.

```bash
docker run --rm --shm-size=256m \
  -v /path/to/config:/app/config:ro \
  -v /path/to/logs:/app/logs \
  oaklight/tello-renewal:latest \
  tello-renewal --config /app/config/config.yaml renew
```

> **`--shm-size=256m`** is required — Chromium uses `/dev/shm` for IPC and crashes with the
> default 64 MB Docker limit.

### Build from source

```bash
make build-package   # build Python wheel
make build-docker    # build Docker image from local wheel

# Optional overrides
make build-docker MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple
make build-docker REGISTRY_MIRROR=docker.1ms.run
```

### Cron with Docker

```bash
0 0 * * * docker run --rm --shm-size=256m \
  -v ~/.config/tello-renewal:/app/config:ro \
  -v /var/log/tello-renewal:/app/logs \
  oaklight/tello-renewal:latest \
  tello-renewal --config /app/config/config.yaml renew >/dev/null 2>&1
```

## Scheduling (without Docker)

### Cron

```bash
0 0 * * * /path/to/venv/bin/tello-renewal renew >> /var/log/tello-renewal.log 2>&1
```

### Systemd timer

```ini
# /etc/systemd/system/tello-renewal.service
[Unit]
Description=Tello Plan Auto Renewal
After=network.target

[Service]
Type=oneshot
User=tello
ExecStart=/opt/tello-renewal/venv/bin/tello-renewal renew
```

```ini
# /etc/systemd/system/tello-renewal.timer
[Unit]
Description=Run Tello renewal daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
```

```bash
sudo systemctl enable --now tello-renewal.timer
```

## Troubleshooting

**CloakBrowser binary missing:**

```bash
python -m cloakbrowser install
```

**Email sending failures:**
Use app-specific passwords. Test with `tello-renewal email-test`.

**Debug mode:**

```bash
tello-renewal --verbose renew --dry-run
```

## Development

```bash
git clone https://github.com/Oaklight/Tello-Renewal.git
cd Tello-Renewal
pip install -e ".[dev]"
python -m cloakbrowser install
pytest
ruff check src/
```

## Changelog

See [CHANGELOG.md](CHANGELOG.md).

## License

MIT — see [LICENSE](LICENSE).

## Disclaimer

Provided as-is for personal automation. You are responsible for complying with Tello's terms of
service and for the security of your credentials. The authors are not liable for failed renewals
or service interruptions.
