Metadata-Version: 2.4
Name: onlycats-bot
Version: 0.1.0
Summary: Python client for the OnlyCats bot HTTP API.
Author: OnlyCats
License: MIT
Project-URL: Homepage, https://onlycats.example
Project-URL: Documentation, https://onlycats.example/docs/bots
Keywords: onlycats,bot,api,client
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28
Dynamic: license-file

# onlycats-bot

A small Python client for the [OnlyCats](https://onlycats.example) bot HTTP
API. Wrap your bot token in a `Client`, post photos and videos to your bot's
profile, and let the library handle auth, MIME inference, and signed uploads
for you.

- Synchronous, dependency-light (only `requests`).
- Type-hinted, Python 3.9+.
- Typed exceptions for auth, validation, rate limits, and spam guards.

## Install

```bash
pip install onlycats-bot
```

## Get a token

Visit the **Developer Dashboard** on OnlyCats and click **Create bot**. Copy
the generated token (it starts with `ocb_`) and keep it secret. Treat it like
a password: store it in an environment variable or a secrets manager rather
than hard-coding it.

## Quickstart

### a) Post a hosted URL

```python
import os
from onlycats_bot import Client

with Client(token=os.environ["ONLYCATS_BOT_TOKEN"]) as bot:
    post = bot.create_post(
        file_url="https://cdn.example.com/cats/loaf.jpg",
        caption="behold: the loaf",
    )
    print(post["id"])
```

### b) Post a local file

```python
from onlycats_bot import Client

with Client(token="ocb_...") as bot:
    post = bot.post_file("./cat.jpg", caption="morning stretch")
    print(post["file_url"])
```

`post_file` calls `upload_file` (which signs an upload URL and `PUT`s the
bytes) and then `create_post`, in one go. The MIME type is inferred from the
file extension.

### c) Handling rate limits

```python
import time
from onlycats_bot import Client, RateLimitError, SpamError

bot = Client(token="ocb_...")

try:
    bot.create_post(file_url=url, caption="hello")
except SpamError as exc:
    # Identical caption posted within the last hour.
    print(f"Duplicate caption; wait {exc.retry_after}s or change the text.")
except RateLimitError as exc:
    print(f"Rate limited; sleeping {exc.retry_after}s")
    time.sleep(exc.retry_after or 60)

# Inspect the most recent rate-limit headers any time:
print(bot.rate_limit)  # {'limit': 50, 'remaining': 49, 'window': 3600}
```

If you'd rather have the library sleep and retry once for you, pass
`auto_retry=True` to the constructor.

## Limits

- **50 requests per hour per bot.** A 429 with `code="RATE_LIMITED"` is
  raised as `RateLimitError`; the `Retry-After` header is exposed as
  `exc.retry_after`.
- **No duplicate captions within an hour.** Posting the same caption twice
  in 60 minutes returns 429 with `code="SPAM_DUPLICATE"` and is raised as
  `SpamError`.
- **20 MB max upload size.** Larger files raise `ValueError` before any
  network call.

## Endpoint reference

All endpoints require `Authorization: Bearer ocb_...`.

| Method | Path                       | Purpose                                     |
| ------ | -------------------------- | ------------------------------------------- |
| GET    | `/bot/v1/me`               | Fetch the bot profile                       |
| POST   | `/bot/v1/uploads/sign`     | Get a signed `PUT` URL for a media upload   |
| POST   | `/bot/v1/posts`            | Create a post from a public file URL        |
| DELETE | `/bot/v1/posts/{post_id}`  | Delete one of the bot's posts               |

## Exceptions

| Exception           | Raised on                               |
| ------------------- | --------------------------------------- |
| `AuthError`         | 401 (UNAUTHORIZED, INVALID_TOKEN)       |
| `BadRequestError`   | 400 (BAD_REQUEST, BAD_TYPE, TOO_LARGE)  |
| `RateLimitError`    | 429 (RATE_LIMITED)                      |
| `SpamError`         | 429 (SPAM_DUPLICATE) — subclass above   |
| `ServerError`       | 5xx                                     |
| `OnlyCatsError`     | Base class / unclassified failures      |

Each carries `.status_code`, `.code`, and `.message`. Rate-limit errors also
carry `.retry_after`.

## License

MIT. See [LICENSE](./LICENSE).
