{"id":51148436,"url":"https://github.com/ruflas/crunchyexporter-cli","last_synced_at":"2026-06-26T04:01:53.449Z","repository":{"id":357456244,"uuid":"1237046652","full_name":"ruflas/crunchyexporter-cli","owner":"ruflas","description":"CLI tool to sync Crunchyroll watch history to AniList, MyAnimeList and MAL XML, with automatic series matching and real watch dates.","archived":false,"fork":false,"pushed_at":"2026-06-24T17:38:17.000Z","size":467,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-24T19:14:27.882Z","etag":null,"topics":["anilist","anime-tracker","cli","crunchyroll","myanimelist","python","watch-history"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ruflas.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-12T20:30:40.000Z","updated_at":"2026-06-24T17:38:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ruflas/crunchyexporter-cli","commit_stats":null,"previous_names":["ruflas/crunchyexporter","ruflas/crunchyexporter-cli"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/ruflas/crunchyexporter-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ruflas%2Fcrunchyexporter-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ruflas%2Fcrunchyexporter-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ruflas%2Fcrunchyexporter-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ruflas%2Fcrunchyexporter-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ruflas","download_url":"https://codeload.github.com/ruflas/crunchyexporter-cli/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ruflas%2Fcrunchyexporter-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34802385,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anilist","anime-tracker","cli","crunchyroll","myanimelist","python","watch-history"],"created_at":"2026-06-26T04:01:52.649Z","updated_at":"2026-06-26T04:01:53.427Z","avatar_url":"https://github.com/ruflas.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"crunchyexporterlogo.png\" alt=\"CrunchyExporter\" width=\"300\"/\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eCrunchyExporter\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/ruflas/CrunchyExporter\" alt=\"License\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/ruflas/CrunchyExporter-cli/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/ruflas/CrunchyExporter-cli\" alt=\"Release\"\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/python-3.11%2B-blue\" alt=\"Python\"\u003e\n  \u003ca href=\"https://github.com/ruflas/CrunchyExporter/releases/latest-cli\"\u003e\u003cimg src=\"https://img.shields.io/github/downloads/ruflas/CrunchyExporter-cli/total\" alt=\"Downloads\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003eFetches your Crunchyroll watch history and exports it to \u003cb\u003eAniList\u003c/b\u003e, \u003cb\u003eMyAnimeList\u003c/b\u003e, and a local \u003cb\u003eMAL-compatible XML\u003c/b\u003e file.\u003c/p\u003e\n\nExports include watch progress, series status (watching/completed), and real start/finish dates from your Crunchyroll history.\n\n## Requirements\n\n- Python 3.11+\n- A Crunchyroll account (active browser session required for auth)\n\n## Setup\n\n```bash\npip install -r requirements.txt\n```\n\nOn Windows (PowerShell):\n```powershell\nCopy-Item config.example.yaml config.yaml\n```\n\nOn Mac/Linux:\n```bash\ncp config.example.yaml config.yaml\n```\n\n---\n\n## Step 1 — Get your Crunchyroll session cookie\n\nCrunchyExporter authenticates using the `etp_rt` session cookie from your browser. No password is stored or required.\n\n1. Open [crunchyroll.com](https://www.crunchyroll.com) and log in\n2. Press `F12` to open DevTools\n3. Go to the **Application** tab (Chrome/Edge) or **Storage** tab (Firefox)\n4. In the left panel expand **Cookies → https://www.crunchyroll.com**\n5. Find the row named `etp_rt` and copy its **Value**\n\nThen fetch your history:\n\n```bash\npython src/main.py fetch --etp-rt \"paste-your-etp-rt-value-here\"\n```\n\nOr save it in `config.yaml` to avoid typing it each time:\n\n```yaml\ncrunchyroll:\n  etp_rt: \"paste-your-etp-rt-value-here\"\n```\n\n```bash\npython src/main.py fetch\n```\n\nOn success you'll see something like:\n```\nLogged in. Account ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\nSync complete. 1513 new episodes added. Total: 1513 episodes across 28 series.\n```\n\nHistory is saved to `data/history.json`. Re-running `fetch` only adds new episodes — it never duplicates.\n\n\u003e **Note:** The `etp_rt` cookie expires when your browser session ends. If `fetch` starts failing with a 401 error, just grab a fresh cookie from DevTools.\n\n---\n\n## Step 2 — View your history\n\n```bash\npython src/main.py status\n```\n\nShows a table with each series, number of episodes watched, and the highest episode number.\n\n---\n\n## Step 3 — Export\n\n### Option A: MAL XML (no account needed, fastest)\n\n```bash\npython src/main.py export --target xml\n```\n\nGenerates `data/animelist.xml`. Import it at:\n- MyAnimeList: [myanimelist.net/import.php](https://myanimelist.net/import.php)\n- AniList: [anilist.co/settings/import](https://anilist.co/settings/import) — select MAL format\n- Kitsu and most other tracking sites\n\n\u003e **Note:** The XML does not include MAL IDs (Crunchyroll doesn't provide them). MAL and AniList resolve entries by title on import.\n\n---\n\n### Option B: AniList API\n\nSyncs progress, status and real completion dates directly via the AniList API.\n\n**1. Create an API client**\n- Go to [anilist.co/settings/developer](https://anilist.co/settings/developer)\n- Click **Create new client**\n- Set **Redirect URL** to exactly: `https://anilist.co/api/v2/oauth/pin`\n- Copy the **Client ID**\n\n**2. Add it to `config.yaml`**\n```yaml\nexporters:\n  anilist:\n    client_id: \"123456\"\n    access_token: \"\"\n```\n\n**3. Run the export**\n```bash\npython src/main.py export --target anilist\n```\n\nThe script prints an authorization URL. Open it, click **Authorize**, and AniList will redirect you to a page showing your `access_token`. Copy it.\n\n**4. Save the token**\n```yaml\nexporters:\n  anilist:\n    client_id: \"123456\"\n    access_token: \"eyJ...\"\n```\n\nFrom now on the export runs without any browser interaction.\n\n---\n\n### Option C: MyAnimeList API\n\nSyncs progress, status, start date and finish date directly via the MAL API.\n\n**1. Create an API client**\n- Go to [myanimelist.net/apiconfig](https://myanimelist.net/apiconfig)\n- Click **Create ID**\n- Fill in the required fields:\n  - **App Type**: `web` — required for OAuth. This also gives you a Client Secret.\n  - **App Redirect URL**: `http://localhost`\n  - **Purpose of Use**: `hobbyist`\n- Submit and copy both the **Client ID** and **Client Secret**\n\n**2. Add them to `config.yaml`**\n```yaml\nexporters:\n  mal:\n    client_id: \"your_client_id\"\n    client_secret: \"your_client_secret\"\n    access_token: \"\"\n```\n\n**3. Run the export**\n```bash\npython src/main.py export --target mal\n```\n\nThe script prints an authorization URL. Open it and click **Allow**. MAL will redirect you to `http://localhost/?code=XXXX` — the page won't load, that's expected. Copy the `code=` value from the browser's address bar and paste it into the terminal.\n\n**4. Save the token**\n\nThe script will display the obtained `access_token`. Add it to `config.yaml`:\n```yaml\nexporters:\n  mal:\n    client_id: \"your_client_id\"\n    client_secret: \"your_client_secret\"\n    access_token: \"the_token_shown_in_terminal\"\n```\n\nFrom now on the export runs without any browser interaction.\n\n---\n\n### Export all targets at once\n\n```bash\npython src/main.py export\n```\n\n---\n\n## Step 4 — Auto-sync (optional)\n\n### One-shot: fetch + export in a single command\n\n```bash\npython src/main.py sync                      # fetch + export all targets\npython src/main.py sync --target anilist     # fetch + export AniList only\n```\n\nRequires `etp_rt` set in `config.yaml` (no interactive prompts).\n\n### Schedule a daily background task\n\n```bash\n# Register a daily task at 08:00 (default)\npython src/main.py schedule\n\n# Choose a different time\npython src/main.py schedule --time 20:00\n\n# Only sync to a specific target\npython src/main.py schedule --target anilist --time 09:00\n\n# Remove the scheduled task\npython src/main.py schedule --remove\n```\n\nOn **Windows** this creates a Windows Task Scheduler entry (`schtasks`).  \nOn **Linux/Mac** it adds an entry to your crontab.\n\nVerify it was created (Windows):\n```powershell\nschtasks /Query /TN CrunchyExporter\n```\n\nRun it manually to test:\n```powershell\nschtasks /Run /TN CrunchyExporter\n```\n\n---\n\n## Config reference\n\n```yaml\nlocale: \"en-US\"           # Language for series titles from CR\n\nstorage:\n  path: \"data/history.json\"\n\ncrunchyroll:\n  etp_rt: \"\"              # Session cookie from browser (see Step 1)\n  client_id: \"\"           # Leave blank to use built-in default\n  client_secret: \"\"       # Leave blank (public client, no secret needed)\n\nexporters:\n  mal_xml:\n    path: \"data/animelist.xml\"\n\n  anilist:\n    client_id: \"\"\n    access_token: \"\"\n\n  mal:\n    client_id: \"\"\n    client_secret: \"\"     # Required for web app type\n    access_token: \"\"\n```\n\n---\n\n## Troubleshooting\n\n**`Login failed (400): unsupported_grant_type`**\nCR no longer supports email/password login via the API. Use the `etp_rt` cookie method described in Step 1.\n\n**`Login failed (400): missing_required_field`**\nThe `etp_rt` value is missing or empty. Make sure you copied the full cookie value from DevTools.\n\n**`fetch` returns 401 after working before**\nThe `etp_rt` cookie expired. Log into Crunchyroll again and copy a fresh value from DevTools.\n\n**`invalid_client` error on AniList**\nThe `client_id` in `config.yaml` is wrong, or the redirect URL in your AniList app is not exactly `https://anilist.co/api/v2/oauth/pin`.\n\n**MAL authorization page shows 400 Bad Request**\nYour MAL app type is set to `other`. Change it to `web` in [myanimelist.net/apiconfig](https://myanimelist.net/apiconfig) — only `web` type supports OAuth authorization code flow.\n\n**MAL token exchange fails with `Failed to verify code_verifier`**\nThis is a known MAL quirk — their PKCE implementation uses the `plain` method, not S256. This is already handled correctly in the current code.\n\n**Some series not found on AniList or MAL**\nCrunchyroll sometimes uses different titles than AniList/MAL. The exporter automatically retries with a normalized title as fallback. If a series still fails, add it manually on the tracking site.\n\n**One Piece or other long-running series matched to a movie**\nThe exporter prefers TV/ONA/OVA results over movies when searching. If a wrong match still occurs, correct it manually on the tracking site.\n\n---\n\n## Project structure\n\n```\nCrunchyExporter/\n├── src/\n│   ├── crunchyroll/\n│   │   ├── auth.py              # CR authentication (etp_rt_cookie grant)\n│   │   ├── history.py           # Watch history fetcher (paginated)\n│   │   └── models.py            # Data classes\n│   ├── exporters/\n│   │   ├── anilist.py           # AniList GraphQL exporter\n│   │   ├── mal.py               # MyAnimeList REST exporter\n│   │   └── mal_xml.py           # Local MAL XML exporter\n│   ├── storage/\n│   │   └── history_store.py     # JSON persistence\n│   └── main.py                  # CLI (click + rich)\n├── data/                        # Generated files — gitignored\n├── config.example.yaml\n└── requirements.txt\n```\n\n---\n\n## Contributing\n\nContributions are welcome. Here's how to get started:\n\n**1. Fork the repo and clone it**\n```bash\ngit clone https://github.com/your-username/CrunchyExporter.git\ncd CrunchyExporter\npip install -r requirements.txt\ncp config.example.yaml config.yaml\n```\n\n**2. Make your changes**\n\nThe codebase is straightforward — each exporter lives in `src/exporters/`, CR auth and history fetching in `src/crunchyroll/`, and the CLI commands in `src/main.py`.\n\n**3. Test manually**\n```bash\npython src/main.py fetch\npython src/main.py status\npython src/main.py export --target xml\n```\n\n**4. Open a pull request** with a clear description of what you changed and why.\n\n### Good areas to contribute\n\n- **New exporters** — Kitsu, Anime-Planet, Shikimori\n- **Better title matching** — fuzzy search or manual override mappings\n- **Movie detection** — improve handling of films vs series\n- **Bug reports** — if a series fails to match or exports incorrectly, open an issue with the series title and the error\n\n### Please avoid\n\n- Breaking the existing CLI interface without discussion\n- Adding dependencies that aren't strictly necessary\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fruflas%2Fcrunchyexporter-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fruflas%2Fcrunchyexporter-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fruflas%2Fcrunchyexporter-cli/lists"}