All apps · 0 apps
AniList-MAL-Sync
Docker app from Tareku99's Repository
Overview
Readme
View on GitHubAniList ⇄ MyAnimeList Sync
A service to synchronize anime lists between AniList and MyAnimeList with support for bidirectional sync.
Credits
Inspired by the original idea from bigspawn/anilist-mal-sync.
This is a clean-room rewrite in Python with a fresh codebase and architecture.
Features
- Bidirectional sync with conflict resolution (latest update wins)
- One-way sync modes: AniList → MAL or MAL → AniList
- OAuth authentication for both services
- Docker deployment with auto-configuration
- YAML-based configuration with validation
- Rate limiting and automatic retry logic
- Web UI for monitoring and configuration
Planned Features
- Manga sync support
- Custom sync schedules per list type
- Webhooks for sync notifications
Quick Start (Deployment)
1. Get API Credentials
AniList:
- Visit https://anilist.co/settings/developer
- Click "Create Client"
- Set Redirect URL to (comma-separated, no spaces):
http://localhost:18080/callback,http://YOUR-SERVER-IP:18080/callback - Copy the Client ID and Client Secret
MyAnimeList:
- Visit https://myanimelist.net/apiconfig
- Click "Create ID"
- Set App Redirect URL to (one per line):
http://localhost:18080/callback http://YOUR-SERVER-IP:18080/callback - Copy the Client ID and Client Secret
2. Deploy
Option A: Docker
docker pull ghcr.io/tareku99/anilist-mal-sync:latest
docker-compose up -d
Option B: Unraid
- Via Community Applications: Search for "AniList MAL Sync" in the Community Apps store
- Manual Install: Copy
anilist-mal-sync.xmlfrom this repo to/boot/config/plugins/dockerMan/templates-user/on your Unraid server
3. Configure
The container creates data/config.yaml on first run. You can edit it through the Web UI at http://YOUR-SERVER-IP:23080 (recommended) or directly on the filesystem:
nano data/config.yaml # Or your preferred editor
oauth:
port: 18080 # REQUIRED
redirect_uri: "http://YOUR-SERVER-IP:18080/callback" # REQUIRED - Use your server's IP
anilist:
client_id: "your_anilist_client_id" # REQUIRED
client_secret: "your_anilist_client_secret" # REQUIRED
username: "your_anilist_username" # REQUIRED
# auth_url: "https://anilist.co/api/v2/oauth/authorize" # OPTIONAL - has defaults
# token_url: "https://anilist.co/api/v2/oauth/token" # OPTIONAL - has defaults
myanimelist:
client_id: "your_mal_client_id" # REQUIRED
client_secret: "your_mal_client_secret" # REQUIRED
username: "your_mal_username" # REQUIRED
# auth_url: "https://myanimelist.net/v1/oauth2/authorize" # OPTIONAL - has defaults
# token_url: "https://myanimelist.net/v1/oauth2/token" # OPTIONAL - has defaults
sync:
mode: "bidirectional" # OPTIONAL
interval: 360 # OPTIONAL - Minutes between syncs
log_level: "INFO" # OPTIONAL
dry_run: false # OPTIONAL
# token_file_path: "/app/data/tokens.json" # OPTIONAL - has default
Restart: docker-compose restart (or wait 60s for auto-reload)
4. Authenticate
Check container logs for OAuth URLs, open them in your browser to authorize, and the service will start syncing automatically.
5. Access Web UI
Once running, access the web dashboard at:
http://YOUR-SERVER-IP:23080
The web UI provides:
- Real-time status: View current sync status, last sync time, next scheduled sync
- Configuration editor: Edit config.yaml directly from the browser with validation
- Manual sync trigger: Force an immediate sync without waiting for the schedule
- Live monitoring: See total sync count and last result
Local Development
Python (Direct)
git clone https://github.com/Tareku99/anilist-mal-sync
cd anilist-mal-sync
py install.py # Creates venv and installs dependencies
# Activate virtual environment
.venv\Scripts\activate # Windows
source .venv/bin/activate # Linux/macOS
# Configure
cp config.example.yaml data/config.yaml
notepad data/config.yaml # Edit with your credentials
# Authenticate
anilist-mal-sync auth
# Run
anilist-mal-sync run
Note: Use
http://localhost:18080/callbackforredirect_uriwhen testing locally.
Docker (Local Build)
# In docker-compose.yml, uncomment: build: .
docker-compose build
docker-compose up
# View logs for OAuth URLs, authorize in browser
Useful for testing Docker builds before deploying.
Available Commands
auth - Interactive authentication setup for AniList and MyAnimeList
Options:
--service(default: both) Options: anilist, mal, both - Which service to authenticate with
# Authenticate both services
anilist-mal-sync auth
# Authenticate individual services
anilist-mal-sync auth --service anilist # AniList only
anilist-mal-sync auth --service mal # MyAnimeList only
Opens browser for OAuth authorization. Tokens are saved to data/tokens.json.
Token Expiration:
- MyAnimeList: Tokens expire after 31 days, auto-refresh using refresh token
- AniList: Tokens expire after 1 year, requires manual re-auth
run - Run sync service (default: continuous sync with web UI)
Default: Runs continuous sync at specified interval with web UI. Use --once for single sync, --no-web-ui for headless mode.
Options:
--mode(default: bidirectional) Options: anilist-to-mal, mal-to-anilist, bidirectional - Sync mode: one-way or bidirectional--dry-run- Simulate sync without making changes--interval(default: 360) - Sync interval in minutes (default: 360 = 6 hours)--log-level(default: INFO) Options: DEBUG, INFO, WARNING, ERROR - Logging level--once- Run sync once and exit (no web UI, no continuous loop)--no-web-ui- Disable web UI (continuous sync only)--port(default: 23080) - Web UI port (default: 23080)--host(default: 0.0.0.0) - Web UI host
# Run with default settings (continuous sync with web UI)
anilist-mal-sync run
# Run sync once and exit
anilist-mal-sync run --once
# Run continuous sync without web UI
anilist-mal-sync run --no-web-ui
# Custom interval (in minutes)
anilist-mal-sync run --interval 60 # Every hour
anilist-mal-sync run --interval 1440 # Daily
Press Ctrl+C to stop.
Sync configuration is set in config.yaml:
sync:
mode: "bidirectional" # Options: anilist-to-mal, mal-to-anilist, bidirectional
dry_run: false # Set to true to preview changes without applying
How It Works
The service follows a unified flow for all commands, with automatic configuration validation, token management, and error handling.
High-Level Execution Flow
flowchart TD
Start([User Runs Command]) --> Init[1. INITIALIZATION<br/>Setup logging]
Init --> ConfigExists{2. CONFIG FILE EXISTS?}
ConfigExists -->|No| CreateTemplate[Create template from config.example<br/>Show message: Please edit config]
ConfigExists -->|Yes| LoadConfig[Load config from YAML]
CreateTemplate --> ConfigValidate
LoadConfig --> ConfigValidate
ConfigValidate{3. CONFIG VALIDATION<br/>Validate credentials}
ConfigValidate -->|Valid| CommandSplit{4. Which command?}
ConfigValidate -->|Not Valid| WaitLoop[WAIT 60s retry loop<br/>Show error, wait 60s<br/>All commands wait<br/>Ctrl+C to exit]
WaitLoop --> ConfigValidate
CommandSplit -->|AUTH| AuthFlow[OAuth Flow: Auth both services, Save tokens, Exit]
CommandSplit -->|RUN| RunModeCheck{5. RUN MODE?}
RunModeCheck -->|--once| ExecuteSync[6. EXECUTE SYNC]
RunModeCheck -->|default| WebUIStart[Start Web UI server]
RunModeCheck -->|--no-web-ui| MainLoop[7. RUN MAIN LOOP]
WebUIStart --> MainLoop
ExecuteSync --> TokenSyncDetails[6. EXECUTE SYNC<br/>See Token Loading & Sync Execution below]
MainLoop --> ConfigCheck{Check config valid?}
ConfigCheck -->|Not Valid| Wait10[Wait 10s<br/>gives user time to update/fix config]
ConfigCheck -->|Valid| ExecuteSyncLoop[6. EXECUTE SYNC<br/>See Token Loading & Sync Execution below]
Wait10 --> ConfigCheck
TokenSyncDetails --> SyncResult{Sync Result}
ExecuteSyncLoop --> SyncResultLoop{Sync Result}
SyncResult -->|SUCCESS| PrintResults[Print results]
SyncResult -->|ERROR| ExitOnce[Exit with error]
SyncResultLoop -->|SUCCESS| WaitInterval[Wait for interval]
SyncResultLoop -->|ERROR| WaitInterval
PrintResults --> ExitOnce[Exit 0 or 1]
WaitInterval --> ConfigCheck
style ExecuteSync fill:#0969da,stroke:#0550ae,stroke-width:2px,color:#fff
style ExecuteSyncLoop fill:#0969da,stroke:#0550ae,stroke-width:2px,color:#fff
style TokenSyncDetails fill:#0969da,stroke:#0550ae,stroke-width:2px,color:#fff
style Start fill:#1f883d,stroke:#1a7f37,stroke-width:2px,color:#fff
style Init fill:#9a6700,stroke:#7c4a00,stroke-width:2px,color:#fff
style ConfigCheck fill:#9a6700,stroke:#7c4a00,stroke-width:2px,color:#fff
style ConfigValidate fill:#9a6700,stroke:#7c4a00,stroke-width:2px,color:#fff
style AuthFlow fill:#1f883d,stroke:#1a7f37,stroke-width:2px,color:#fff
style MainLoop fill:#cf222e,stroke:#a40e26,stroke-width:2px,color:#fff
style WebUIStart fill:#0969da,stroke:#0550ae,stroke-width:2px,color:#fff
style RunModeCheck fill:#9a6700,stroke:#7c4a00,stroke-width:2px,color:#fff
style WaitLoop fill:#bc4c00,stroke:#953800,stroke-width:2px,color:#fff
style ExitOnce fill:#82071e,stroke:#660018,stroke-width:2px,color:#fff
Token Loading & Sync Execution
All sync operations follow this internal flow:
flowchart TD
TokenSyncFlow[START: Token Loading & Sync Execution] --> TokenLoad[5. TOKEN LOADING]
TokenLoad --> TokensFound{Tokens found?}
TokensFound -->|Yes| CheckExpired{Token expired?}
TokensFound -->|No| AutoAuth[Auto-authenticate authenticate_services]
AutoAuth -->|Success| TokenReady
AutoAuth -->|Fail| ExitAuth[EXIT or Continue Loop]
CheckExpired -->|Not Expired| TokenReady
CheckExpired -->|Expired| RefreshToken{Try refresh MAL only}
RefreshToken -->|Success| TokenReady
RefreshToken -->|Fail| AutoAuth
TokenReady[Tokens Ready] --> ClientCreate[6. CLIENT CREATION<br/>AniListClient, MALClient, SyncEngine]
ClientCreate --> SyncExecute[7. EXECUTE SYNC<br/>engine.sync mode]
SyncExecute --> SyncResult{Sync Result}
SyncResult -->|SUCCESS| ReturnSuccess[Return Success]
SyncResult -->|HTTP ERROR 401/403| ReAuth[Re-authenticate reauthenticate_services]
SyncResult -->|OTHER ERROR| ReturnError[Return Error]
ReAuth -->|Success| RetrySync[Retry sync once with new tokens]
ReAuth -->|Fail| ReturnError
RetrySync -->|Success| ReturnSuccess
RetrySync -->|Fail| ReturnError
style TokenSyncFlow fill:#0969da,stroke:#0550ae,stroke-width:2px,color:#fff
style TokenLoad fill:#0969da,stroke:#0550ae,stroke-width:2px,color:#fff
style ClientCreate fill:#0969da,stroke:#0550ae,stroke-width:2px,color:#fff
style SyncExecute fill:#0969da,stroke:#0550ae,stroke-width:2px,color:#fff
style ReturnSuccess fill:#1f883d,stroke:#1a7f37,stroke-width:2px,color:#fff
style ReturnError fill:#cf222e,stroke:#a40e26,stroke-width:2px,color:#fff
style ExitAuth fill:#cf222e,stroke:#a40e26,stroke-width:2px,color:#fff
Key Behaviors:
- Config Validation: All commands wait for valid configuration (60s retry loop)
- Token Management: Automatic token refresh for MAL, manual re-auth for AniList
- Error Recovery: Auto re-authentication on 401/403 errors with single retry
- Continuous Operation:
runcommand continuously validates config and adapts to changes
Error Scenarios
Scenario 1: No Config File
- Config file doesn't exist → Creates template from
config.example.yaml - Shows message: "Please edit the config file with your credentials"
- All commands continue after template creation
Scenario 2: Invalid Config (Initial Validation)
- Config has placeholder values → Shows detailed error message
- All commands wait in retry loop (60s intervals) until config is valid
- User can press Ctrl+C to exit
Scenario 3: Invalid Config (During RUN Loop)
- Config becomes invalid while
runcommand is running - Service pauses sync, waits 10s, then retries validation
- Service stays running, waiting for config to be fixed
Scenario 4: No Tokens
- Token file doesn't exist or tokens missing
- Auto-triggers OAuth authentication flow
- Continues with sync if auth succeeds
Scenario 5: Expired Tokens
- MAL: Auto-refreshes using refresh token
- AniList: Requires manual re-auth (no refresh token available)
Scenario 6: Authentication Error During Sync
- HTTP 401/403 during sync → Auto re-authenticates
- Retries sync once with new tokens
- Exits on failure (--once mode) or continues loop (continuous modes)
Scenario 7: Config Changes During RUN
- Config file modified while service running
- Reloads and revalidates config automatically
- Resumes sync if valid, pauses if invalid
Token Refresh Logic
MAL Token Refresh
flowchart TD
A[Token expired?] -->|No| B[Use existing token]
A -->|Yes| C{Has refresh token?}
C -->|Yes| D[Refresh via API]
C -->|No| B
D --> E{Success?}
E -->|Yes| F[Save new tokens]
E -->|No| G[Return None, trigger re-auth]
AniList Token Refresh
flowchart TD
A[Token expired?] --> B[AniList has NO refresh tokens: 1 year expiry, manual re-auth required]
B --> C[Return existing token: will fail on API call, trigger re-auth]
Project Structure
src/anilist_mal_sync/
├── config.py # Pydantic-based YAML configuration
├── cli.py # Click CLI commands
├── anilist_client.py # AniList API client
├── mal_client.py # MyAnimeList API client
├── sync_engine.py # Sync logic and conflict resolution
└── oauth.py # OAuth flow and token management
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Additional Resources
- SUPPORT.md - Get help and support
- SECURITY.md - Security policy and vulnerability reporting
- CODE_OF_CONDUCT.md - Community guidelines
- LICENSE - MIT License
Support the Project
If you find this project useful, consider supporting its development:
Install AniList-MAL-Sync on Unraid in a few clicks.
Find AniList-MAL-Sync in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Categories
Related apps
Explore more like this
Explore allDetails
ghcr.io/tareku99/anilist-mal-sync:latestRuntime arguments
- Web UI
http://[IP]:[PORT:23080]- Network
bridge- Shell
sh- Privileged
- false
- Extra Params
--restart unless-stopped
Template configuration
OAuth callback port for authentication
- Target
- 18080
- Default
- 18080
- Value
- 18080
Web UI dashboard port
- Target
- 23080
- Default
- 23080
- Value
- 23080
Contains config.yaml and tokens.json
- Target
- /app/data
- Default
- /mnt/user/appdata/anilist-mal-sync
- Value
- /mnt/user/appdata/anilist-mal-sync
Container timezone (e.g., America/New_York, Europe/London, Asia/Tokyo)
- Target
- TZ
- Default
- America/Boise
- Value
- America/Boise