Metadata-Version: 2.4
Name: moodlesolver
Version: 1.0.0
Summary: Async Moodle quiz automation client with a Telethon-style session API
License: MIT
Keywords: moodle,async,automation,quiz
Classifier: Programming Language :: Python :: 3
Classifier: Framework :: AsyncIO
Classifier: License :: OSI Approved :: MIT License
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: aiohttp>=3.9

# MoodleSolver

Async Moodle quiz automation client with a Telethon-style session API.

## Features

- **Telethon-style session**: `start()`, `connect()`, `disconnect()`, `is_connected`
- **Fully async** on top of `aiohttp` and a Camofox browser API
- **Multi-client**: run several accounts in parallel, each with its own browser profile; `disconnect()` only touches that client's tabs
- **All question types**: single choice, multiple choice, text answers, matching, image questions
- **Question dumps**: every question saved as JSON (+ optional JPG image) for offline analysis
- **Review parser**: fetch attempt reviews after submission
- **Result tracking**: read the final grade table

## Requirements

- Python 3.10+
- A running Camofox browser API (default `http://localhost:9377`)

## Install

```sh
pip install moodlesolver
```

## Quick start

```python
import asyncio
from moodlesolver import AsyncMoodle

async def main():
    async with AsyncMoodle(
        site="https://moodle.example.edu",
        username="user@example.edu",
        password="secret",
    ) as m:
        print("logged in as", await m.whoami())

        await m.resume_attempt(quiz_id=123)
        q = await m.current_page()
        print(q["number"], q["text"], [o["label"] for o in q["options"]])

        r, nxt = await m.answer_step(["correct answer label"])
        print("answered", r, "timer left", nxt["timer"])

        await m.finish_attempt()
        print(await m.quiz_result(quiz_id=123))

asyncio.run(main())
```

## Session lifecycle

```python
m = AsyncMoodle(site=..., username=..., password=...)
await m.connect()            # open HTTP session to the browser API
await m.start()              # login (or reuse an existing session)
...
await m.disconnect()         # close this client's tabs and session
```

`AsyncMoodle` also works as an async context manager (`async with`).

## API overview

| Method | Description |
|---|---|
| `start() / connect() / disconnect()` | Session lifecycle |
| `login(u, p) / whoami()` | Authentication |
| `new_tab(url) / pick(substr) / tabs() / close_stale()` | Tab management |
| `current_page()` | Parse the current question (type, options, timer) |
| `answer_step(labels)` | Answer by label text, then submit ("next") in one step |
| `answer_text(text)` | Fill a text-answer input |
| `answer_match(pairs)` | Fill matching selects |
| `download_image(path) / download_all_images(prefix)` | Export question images |
| `dump_question(q, quiz_id) / step_quiz(quiz_id, bank)` | Dump questions and drive a full quiz from an answer bank |
| `resume_attempt(quiz_id) / finish_attempt() / quiz_result(quiz_id) / review(attempt, cmid)` | Quiz lifecycle |

## Question bank format

`step_quiz` accepts a dict mapping question text (case-insensitive substring) to the answer:

```python
bank = {
    "Some question text": "single answer label",
    "Pick all that apply": ["first", "second"],
}
```

## License

MIT
