|
| 1 | +# PunchPlay Scrobble — Kodi Addon |
| 2 | + |
| 3 | +Automatically tracks movies and TV episodes you watch in Kodi and posts them to your **[PunchPlay.tv](https://punchplay.tv)** account in real time. |
| 4 | + |
| 5 | +Supported Kodi versions: **Nexus (20)** and **Omega (21)**, Python 3 only. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## Installation |
| 10 | + |
| 11 | +### 1. Zip the addon |
| 12 | + |
| 13 | +```bash |
| 14 | +# From the parent directory (one level above script.punchplay/): |
| 15 | +zip -r script.punchplay.zip script.punchplay/ \ |
| 16 | + --exclude "*.pyc" --exclude "*/__pycache__/*" |
| 17 | +``` |
| 18 | + |
| 19 | +### 2. Sideload into Kodi |
| 20 | + |
| 21 | +1. Copy `script.punchplay.zip` to the device running Kodi (USB, network share, or `adb push`). |
| 22 | +2. In Kodi: **Settings → Add-ons → Install from zip file**. |
| 23 | +3. Navigate to the zip and confirm. Kodi will install and start the service immediately. |
| 24 | + |
| 25 | +> Once the addon is approved on the Kodi addon store, you'll be able to install it directly from **Settings → Add-ons → Install from repository**. |
| 26 | +
|
| 27 | +--- |
| 28 | + |
| 29 | +## Configuration |
| 30 | + |
| 31 | +Open **Settings → Add-ons → My add-ons → Services → PunchPlay Scrobble → Configure**. |
| 32 | + |
| 33 | +| Setting | Default | Description | |
| 34 | +|---|---|---| |
| 35 | +| **Backend URL** | `https://punchplay.tv` | Base URL of the PunchPlay API. Leave as-is unless self-hosting. | |
| 36 | +| **Watched threshold (%)** | 70 | Minimum play percentage before an item is marked as watched. | |
| 37 | +| **Minimum file length (min)** | 5 | Files shorter than this are ignored (trailers, clips). | |
| 38 | +| **Heartbeat interval (sec)** | 30 | How often progress is reported during playback. | |
| 39 | +| **Scrobble movies** | On | Toggle movie tracking. | |
| 40 | +| **Scrobble TV shows** | On | Toggle TV episode tracking. | |
| 41 | +| **Scrobble anime** | On | Toggle anime tracking (detected by genre tag). | |
| 42 | + |
| 43 | +--- |
| 44 | + |
| 45 | +## Logging In |
| 46 | + |
| 47 | +1. Open the addon settings. |
| 48 | +2. Click **Login to PunchPlay**. |
| 49 | +3. A dialog will show a short code and a URL: |
| 50 | + |
| 51 | + ``` |
| 52 | + Visit: https://punchplay.tv/link |
| 53 | + Enter code: ABCD-1234 |
| 54 | + ``` |
| 55 | + |
| 56 | +4. Open the URL on any device, sign in to your PunchPlay account, and approve the request. |
| 57 | +5. Kodi polls automatically — you'll see a "Login successful!" notification within seconds. |
| 58 | + |
| 59 | +Tokens are stored in the Kodi addon data directory (`userdata/addon_data/script.punchplay/`) and refreshed automatically. You only need to log in once. |
| 60 | + |
| 61 | +To log out, click **Logout** in the addon settings. |
| 62 | + |
| 63 | +--- |
| 64 | + |
| 65 | +## How It Works |
| 66 | + |
| 67 | +``` |
| 68 | +Kodi player event |
| 69 | + │ |
| 70 | + ▼ |
| 71 | + PunchPlayPlayer (player.py) |
| 72 | + │ identify via Kodi library metadata → identifier.py |
| 73 | + │ fallback: regex filename parser → identifier.py |
| 74 | + │ cache lookup/store → cache.py (SQLite) |
| 75 | + │ |
| 76 | + ▼ |
| 77 | + APIClient (api.py) |
| 78 | + │ POST /api/scrobble/start|pause|resume|stop|progress |
| 79 | + │ Bearer token attached automatically |
| 80 | + │ 401 → refresh token and retry once |
| 81 | + │ network error → write to offline queue (SQLite) |
| 82 | + │ |
| 83 | + ▼ |
| 84 | + PunchPlay REST API |
| 85 | +``` |
| 86 | + |
| 87 | +### Media identification |
| 88 | + |
| 89 | +1. **Kodi library metadata** — if the item is in your library, Kodi provides the title, year, TMDB/TVDB IDs directly. Most accurate. |
| 90 | +2. **Regex filename parser** — extracts title, year, and episode info from scene-style filenames (e.g. `Show.S01E02.1080p.WEB-DL.mkv`). |
| 91 | +3. **Server-side TMDB search** — if neither method yields a TMDB ID, the server searches TMDB by title and year as a final fallback. |
| 92 | + |
| 93 | +### Scrobble events |
| 94 | + |
| 95 | +| Event | Endpoint | Triggered when | |
| 96 | +|---|---|---| |
| 97 | +| Start | `POST /api/scrobble/start` | Playback begins | |
| 98 | +| Pause | `POST /api/scrobble/pause` | Player paused | |
| 99 | +| Resume | `POST /api/scrobble/resume` | Player resumed | |
| 100 | +| Progress | `POST /api/scrobble/progress` | Every N seconds during playback | |
| 101 | +| Stop | `POST /api/scrobble/stop` | User stops or file ends | |
| 102 | + |
| 103 | +Stop events include `"watched": true` when the play percentage meets or exceeds the configured threshold, triggering a full scrobble to your watch history. Partial stops (below threshold) save your position for the "Continue Watching" section on your profile. |
| 104 | + |
| 105 | +All requests share the same JSON payload: |
| 106 | + |
| 107 | +```json |
| 108 | +{ |
| 109 | + "media_type": "movie", |
| 110 | + "title": "Inception", |
| 111 | + "year": 2010, |
| 112 | + "tmdb_id": 27205, |
| 113 | + "imdb_id": "tt1375666", |
| 114 | + "progress": 0.72, |
| 115 | + "duration_seconds": 8880, |
| 116 | + "position_seconds": 6394, |
| 117 | + "device_id": "uuid-stored-per-device", |
| 118 | + "client_version": "1.0.0" |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +Optional fields (`imdb_id`, `tmdb_id`, `tvdb_id`, `season`, `episode`, `year`) are omitted when unavailable. |
| 123 | + |
| 124 | +### Offline resilience |
| 125 | + |
| 126 | +Failed POSTs are written to a local SQLite queue (capped at 200 events). The queue flushes every 60 seconds and also immediately before each new start event. Events replay in order; unrecoverable 4xx errors are discarded so they don't block the queue. |
| 127 | + |
| 128 | +--- |
| 129 | + |
| 130 | +## File layout |
| 131 | + |
| 132 | +``` |
| 133 | +script.punchplay/ |
| 134 | +├── addon.xml Addon metadata and extension points |
| 135 | +├── default.py Entry point — launches the background service |
| 136 | +├── service.py xbmc.Monitor — main loop, login/logout, queue flush |
| 137 | +├── player.py xbmc.Player — playback events and heartbeat thread |
| 138 | +├── api.py HTTP client (auth, token refresh, offline queue) |
| 139 | +├── identifier.py Media identification (library metadata + regex parser) |
| 140 | +├── cache.py SQLite: identifier cache + offline scrobble queue |
| 141 | +├── icon.png Addon icon (256×256) |
| 142 | +├── fanart.jpg Addon fanart (1280×720) |
| 143 | +├── changelog.txt Version history |
| 144 | +├── LICENSE.txt GPL-2.0 |
| 145 | +└── resources/ |
| 146 | + └── settings.xml Addon settings UI |
| 147 | +``` |
| 148 | + |
| 149 | +--- |
| 150 | + |
| 151 | +## License |
| 152 | + |
| 153 | +GPL-2.0 — see [LICENSE.txt](LICENSE.txt). |
0 commit comments