All checks were successful
Build and Publish Docker Image / docker (push) Successful in 22s
MBi Poller — Maloja to ListenBrainz Importer
A lightweight Python service that polls a Maloja music scrobbling server and forwards scrobbled tracks to ListenBrainz.
Runs as a one-shot Docker container — designed to be triggered on a schedule via cron, systemd timer, or another orchestrator.
How It Works
- Validates the ListenBrainz API token
- Fetches recently scrobbled tracks from the Maloja API
- Transforms and sanitizes track data (artist, title, album)
- Submits listens to ListenBrainz in batches of up to 100
- Rate-limits to 5 requests/second to respect ListenBrainz API limits
- Logs all activity to stdout and
./logs/scrobble_import.log
Quick Start
cp .env.example .env
# Edit .env with your real tokens
docker compose up
Configuration
Environment Variables
| Variable | Required | Description |
|---|---|---|
LISTENBRAINZ_TOKEN |
Yes | ListenBrainz API token (format: lbp_xxxxxxxxxxxxxxxxx) |
SOURCE_API_KEY |
Yes | API key for authenticating with the Maloja instance |
SOURCE_API_URL |
Yes | Maloja API endpoint (default: https://maloja.example.com/api/trackscrobble) |
LOG_DIR |
No | Log directory inside the container (default: /app/logs) |
.env.example
LISTENBRAINZ_TOKEN=lbp_xxxxxxxxxxxxxxxxx
SOURCE_API_KEY=xxxxxxxxxxxxxxxxx
Copy this to .env and fill in your values. Set SOURCE_API_URL in the docker-compose.yaml to point at your Maloja instance.
Command-Line Flags
| Flag | Description |
|---|---|
--mode api |
Use the Maloja API to fetch scrobbles (default, used in compose) |
--dry-run |
Parse and validate data without submitting to ListenBrainz |
Docker Compose
services:
maloja-listenbrainz-importer:
image: yourname/maloja-listenbrainz-importer:latest
container_name: maloja-listenbrainz-importer
environment:
LISTENBRAINZ_TOKEN: "${LISTENBRAINZ_TOKEN}"
SOURCE_API_URL: "https://maloja.example.com/api/trackscrobble"
SOURCE_API_KEY: "${SOURCE_API_KEY}"
LOG_DIR: "/app/logs"
volumes:
- ./logs:/app/logs
command: ["--mode", "api"]
restart: "no"
Key Details
- Restart policy:
no— container runs once and exits - Ports: None exposed (outbound HTTPS only)
- Volumes:
./logspersists log output across runs - Networks: Uses the default bridge network; no custom network required
Scheduling
This service does not run continuously. Trigger it periodically with a host-level scheduler:
Example crontab entry (every 6 hours):
0 */6 * * * cd /path/to/mbi-poller && docker compose up --remove-orphans
Building
docker compose build
Or pull the pre-built image from the Gitea registry:
docker pull gitea.kansaigaijin.com/Jamie/maloja-listenbrainz-importer:latest
CI/CD
A Gitea Actions workflow (.gitea/workflows/docker.yml) automatically builds and pushes the Docker image on pushes to main or version tags (v*).
Logs
Logs are written to:
- Stdout — visible via
docker compose logs - File —
./logs/scrobble_import.log(persisted on the host)
Project Structure
mbi-poller/
├── docker-compose.yaml # Service definition
├── Dockerfile # Python 3.12-slim image build
├── importer.py # Core application logic
├── requirements.txt # Python dependencies (requests)
├── .env.example # Environment variable template
├── .dockerignore # Build context exclusions
├── .gitea/workflows/ # CI/CD pipeline
└── logs/ # Persisted log output
Description
Languages
Python
94.9%
Dockerfile
5.1%