penguwatch 0.15.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. penguwatch-0.15.0/LICENSE +21 -0
  2. penguwatch-0.15.0/PKG-INFO +259 -0
  3. penguwatch-0.15.0/README.md +234 -0
  4. penguwatch-0.15.0/pyproject.toml +56 -0
  5. penguwatch-0.15.0/setup.cfg +4 -0
  6. penguwatch-0.15.0/src/homeguard/__init__.py +10 -0
  7. penguwatch-0.15.0/src/homeguard/_vendored.py +13 -0
  8. penguwatch-0.15.0/src/homeguard/db.py +281 -0
  9. penguwatch-0.15.0/src/homeguard/security.py +905 -0
  10. penguwatch-0.15.0/src/penguwatch/__init__.py +3 -0
  11. penguwatch-0.15.0/src/penguwatch/activity.py +96 -0
  12. penguwatch-0.15.0/src/penguwatch/api/__init__.py +13 -0
  13. penguwatch-0.15.0/src/penguwatch/api/catalog.py +53 -0
  14. penguwatch-0.15.0/src/penguwatch/api/component.py +13 -0
  15. penguwatch-0.15.0/src/penguwatch/api/decide.py +57 -0
  16. penguwatch-0.15.0/src/penguwatch/api/deps.py +73 -0
  17. penguwatch-0.15.0/src/penguwatch/api/discover.py +203 -0
  18. penguwatch-0.15.0/src/penguwatch/api/drivers.py +176 -0
  19. penguwatch-0.15.0/src/penguwatch/api/exports.py +107 -0
  20. penguwatch-0.15.0/src/penguwatch/api/ical.py +78 -0
  21. penguwatch-0.15.0/src/penguwatch/api/insights.py +16 -0
  22. penguwatch-0.15.0/src/penguwatch/api/notifications.py +40 -0
  23. penguwatch-0.15.0/src/penguwatch/api/planner.py +27 -0
  24. penguwatch-0.15.0/src/penguwatch/api/ratings.py +208 -0
  25. penguwatch-0.15.0/src/penguwatch/api/router.py +9 -0
  26. penguwatch-0.15.0/src/penguwatch/api/schedule.py +86 -0
  27. penguwatch-0.15.0/src/penguwatch/api/schemas.py +110 -0
  28. penguwatch-0.15.0/src/penguwatch/api/services.py +60 -0
  29. penguwatch-0.15.0/src/penguwatch/api/shows.py +267 -0
  30. penguwatch-0.15.0/src/penguwatch/api/stable.py +66 -0
  31. penguwatch-0.15.0/src/penguwatch/api/sync_review.py +98 -0
  32. penguwatch-0.15.0/src/penguwatch/api/system.py +247 -0
  33. penguwatch-0.15.0/src/penguwatch/api/titles.py +136 -0
  34. penguwatch-0.15.0/src/penguwatch/api/upnext.py +145 -0
  35. penguwatch-0.15.0/src/penguwatch/api/webhooks.py +62 -0
  36. penguwatch-0.15.0/src/penguwatch/cli.py +346 -0
  37. penguwatch-0.15.0/src/penguwatch/clock.py +49 -0
  38. penguwatch-0.15.0/src/penguwatch/components.py +139 -0
  39. penguwatch-0.15.0/src/penguwatch/config.example.yaml +161 -0
  40. penguwatch-0.15.0/src/penguwatch/config.py +339 -0
  41. penguwatch-0.15.0/src/penguwatch/db/__init__.py +1 -0
  42. penguwatch-0.15.0/src/penguwatch/db/_common.py +7 -0
  43. penguwatch-0.15.0/src/penguwatch/db/episodes.py +221 -0
  44. penguwatch-0.15.0/src/penguwatch/db/events.py +51 -0
  45. penguwatch-0.15.0/src/penguwatch/db/migrations/0001_initial.py +131 -0
  46. penguwatch-0.15.0/src/penguwatch/db/migrations/0002_legacy_columns.py +31 -0
  47. penguwatch-0.15.0/src/penguwatch/db/migrations/0003_episode_watched_index.py +9 -0
  48. penguwatch-0.15.0/src/penguwatch/db/migrations/0004_service_bundles.py +10 -0
  49. penguwatch-0.15.0/src/penguwatch/db/migrations/0005_billing_period.py +10 -0
  50. penguwatch-0.15.0/src/penguwatch/db/migrations/0006_service_price_history.py +22 -0
  51. penguwatch-0.15.0/src/penguwatch/db/migrations/0007_english_titles.py +18 -0
  52. penguwatch-0.15.0/src/penguwatch/db/migrations/0008_drop_tmdb_state.py +7 -0
  53. penguwatch-0.15.0/src/penguwatch/db/migrations/0009_clear_stale_providers.py +15 -0
  54. penguwatch-0.15.0/src/penguwatch/db/migrations/0010_neutral_household_tags.py +11 -0
  55. penguwatch-0.15.0/src/penguwatch/db/migrations/0011_drop_rotten_tomatoes.py +9 -0
  56. penguwatch-0.15.0/src/penguwatch/db/migrations/0012_service_titles.py +19 -0
  57. penguwatch-0.15.0/src/penguwatch/db/migrations/0013_drop_partner_rating.py +10 -0
  58. penguwatch-0.15.0/src/penguwatch/db/migrations/0014_rating_sources.py +37 -0
  59. penguwatch-0.15.0/src/penguwatch/db/migrations/0015_title_ids.py +122 -0
  60. penguwatch-0.15.0/src/penguwatch/db/migrations/0016_outbox_ops.py +87 -0
  61. penguwatch-0.15.0/src/penguwatch/db/migrations/0017_driver_state.py +31 -0
  62. penguwatch-0.15.0/src/penguwatch/db/migrations/0018_staged_cursor.py +20 -0
  63. penguwatch-0.15.0/src/penguwatch/db/migrations/__init__.py +81 -0
  64. penguwatch-0.15.0/src/penguwatch/db/outbox.py +242 -0
  65. penguwatch-0.15.0/src/penguwatch/db/prices.py +31 -0
  66. penguwatch-0.15.0/src/penguwatch/db/ratings.py +311 -0
  67. penguwatch-0.15.0/src/penguwatch/db/reports.py +215 -0
  68. penguwatch-0.15.0/src/penguwatch/db/repository.py +163 -0
  69. penguwatch-0.15.0/src/penguwatch/db/schema.py +22 -0
  70. penguwatch-0.15.0/src/penguwatch/db/services.py +126 -0
  71. penguwatch-0.15.0/src/penguwatch/db/shows.py +618 -0
  72. penguwatch-0.15.0/src/penguwatch/db/state.py +50 -0
  73. penguwatch-0.15.0/src/penguwatch/db/storage.py +21 -0
  74. penguwatch-0.15.0/src/penguwatch/db/titles.py +88 -0
  75. penguwatch-0.15.0/src/penguwatch/db/views.py +310 -0
  76. penguwatch-0.15.0/src/penguwatch/doctor.py +140 -0
  77. penguwatch-0.15.0/src/penguwatch/drivers/__init__.py +76 -0
  78. penguwatch-0.15.0/src/penguwatch/drivers/base.py +476 -0
  79. penguwatch-0.15.0/src/penguwatch/drivers/local/__init__.py +111 -0
  80. penguwatch-0.15.0/src/penguwatch/drivers/ratelimit.py +33 -0
  81. penguwatch-0.15.0/src/penguwatch/drivers/simkl/__init__.py +42 -0
  82. penguwatch-0.15.0/src/penguwatch/drivers/simkl/auth.py +208 -0
  83. penguwatch-0.15.0/src/penguwatch/drivers/simkl/client.py +689 -0
  84. penguwatch-0.15.0/src/penguwatch/drivers/simkl/mapper.py +354 -0
  85. penguwatch-0.15.0/src/penguwatch/drivers/simkl/options.py +14 -0
  86. penguwatch-0.15.0/src/penguwatch/drivers/simkl/payloads.py +96 -0
  87. penguwatch-0.15.0/src/penguwatch/drivers/simkl/service.py +508 -0
  88. penguwatch-0.15.0/src/penguwatch/drivers/tvmaze/__init__.py +32 -0
  89. penguwatch-0.15.0/src/penguwatch/drivers/tvmaze/client.py +107 -0
  90. penguwatch-0.15.0/src/penguwatch/drivers/tvmaze/mapper.py +108 -0
  91. penguwatch-0.15.0/src/penguwatch/drivers/tvmaze/options.py +12 -0
  92. penguwatch-0.15.0/src/penguwatch/drivers/tvmaze/service.py +151 -0
  93. penguwatch-0.15.0/src/penguwatch/envfile.py +88 -0
  94. penguwatch-0.15.0/src/penguwatch/events.py +38 -0
  95. penguwatch-0.15.0/src/penguwatch/grafana.py +50 -0
  96. penguwatch-0.15.0/src/penguwatch/insights.py +75 -0
  97. penguwatch-0.15.0/src/penguwatch/interest.py +162 -0
  98. penguwatch-0.15.0/src/penguwatch/library.py +184 -0
  99. penguwatch-0.15.0/src/penguwatch/main.py +181 -0
  100. penguwatch-0.15.0/src/penguwatch/models.py +394 -0
  101. penguwatch-0.15.0/src/penguwatch/notify.py +158 -0
  102. penguwatch-0.15.0/src/penguwatch/notify_component.py +21 -0
  103. penguwatch-0.15.0/src/penguwatch/observability.py +370 -0
  104. penguwatch-0.15.0/src/penguwatch/optimizer/__init__.py +1 -0
  105. penguwatch-0.15.0/src/penguwatch/optimizer/bundles.py +118 -0
  106. penguwatch-0.15.0/src/penguwatch/optimizer/component.py +9 -0
  107. penguwatch-0.15.0/src/penguwatch/optimizer/engine.py +425 -0
  108. penguwatch-0.15.0/src/penguwatch/optimizer/plannable.py +51 -0
  109. penguwatch-0.15.0/src/penguwatch/optimizer/planner.py +326 -0
  110. penguwatch-0.15.0/src/penguwatch/optimizer/renewal.py +56 -0
  111. penguwatch-0.15.0/src/penguwatch/optimizer/status.py +136 -0
  112. penguwatch-0.15.0/src/penguwatch/paths.py +141 -0
  113. penguwatch-0.15.0/src/penguwatch/pwa.py +51 -0
  114. penguwatch-0.15.0/src/penguwatch/ratings.py +318 -0
  115. penguwatch-0.15.0/src/penguwatch/security.py +105 -0
  116. penguwatch-0.15.0/src/penguwatch/service.py +920 -0
  117. penguwatch-0.15.0/src/penguwatch/servicematch.py +68 -0
  118. penguwatch-0.15.0/src/penguwatch/sync/__init__.py +8 -0
  119. penguwatch-0.15.0/src/penguwatch/sync/actions.py +203 -0
  120. penguwatch-0.15.0/src/penguwatch/sync/active.py +54 -0
  121. penguwatch-0.15.0/src/penguwatch/sync/backoff.py +63 -0
  122. penguwatch-0.15.0/src/penguwatch/sync/component.py +16 -0
  123. penguwatch-0.15.0/src/penguwatch/sync/engine.py +169 -0
  124. penguwatch-0.15.0/src/penguwatch/sync/enrich.py +339 -0
  125. penguwatch-0.15.0/src/penguwatch/sync/ops.py +78 -0
  126. penguwatch-0.15.0/src/penguwatch/sync/pull.py +448 -0
  127. penguwatch-0.15.0/src/penguwatch/sync/push.py +326 -0
  128. penguwatch-0.15.0/src/penguwatch/sync/titles.py +150 -0
  129. penguwatch-0.15.0/src/penguwatch/sync/worker.py +126 -0
  130. penguwatch-0.15.0/src/penguwatch/telemetry.py +573 -0
  131. penguwatch-0.15.0/src/penguwatch/telemetry_component.py +18 -0
  132. penguwatch-0.15.0/src/penguwatch/textsearch.py +93 -0
  133. penguwatch-0.15.0/src/penguwatch/ui.py +137 -0
  134. penguwatch-0.15.0/src/penguwatch/web/static/components.css +387 -0
  135. penguwatch-0.15.0/src/penguwatch/web/static/dialogs/add-title/dialog.css +17 -0
  136. penguwatch-0.15.0/src/penguwatch/web/static/dialogs/add-title/dialog.html +86 -0
  137. penguwatch-0.15.0/src/penguwatch/web/static/dialogs/add-title/dialog.js +114 -0
  138. penguwatch-0.15.0/src/penguwatch/web/static/dialogs/details/dialog.css +69 -0
  139. penguwatch-0.15.0/src/penguwatch/web/static/dialogs/details/dialog.html +43 -0
  140. penguwatch-0.15.0/src/penguwatch/web/static/dialogs/details/dialog.js +349 -0
  141. penguwatch-0.15.0/src/penguwatch/web/static/icons/app-180.png +0 -0
  142. penguwatch-0.15.0/src/penguwatch/web/static/icons/app-192.png +0 -0
  143. penguwatch-0.15.0/src/penguwatch/web/static/icons/app-512.png +0 -0
  144. penguwatch-0.15.0/src/penguwatch/web/static/icons/favicon.ico +0 -0
  145. penguwatch-0.15.0/src/penguwatch/web/static/icons/favicon.svg +43 -0
  146. penguwatch-0.15.0/src/penguwatch/web/static/js/api.js +162 -0
  147. penguwatch-0.15.0/src/penguwatch/web/static/js/bus.js +20 -0
  148. penguwatch-0.15.0/src/penguwatch/web/static/js/chrome.js +96 -0
  149. penguwatch-0.15.0/src/penguwatch/web/static/js/data.js +91 -0
  150. penguwatch-0.15.0/src/penguwatch/web/static/js/dispatch.js +71 -0
  151. penguwatch-0.15.0/src/penguwatch/web/static/js/drivers.js +196 -0
  152. penguwatch-0.15.0/src/penguwatch/web/static/js/header.js +120 -0
  153. penguwatch-0.15.0/src/penguwatch/web/static/js/library-status.js +19 -0
  154. penguwatch-0.15.0/src/penguwatch/web/static/js/main.js +51 -0
  155. penguwatch-0.15.0/src/penguwatch/web/static/js/mobile.js +92 -0
  156. penguwatch-0.15.0/src/penguwatch/web/static/js/modal.js +79 -0
  157. penguwatch-0.15.0/src/penguwatch/web/static/js/showchanges.js +84 -0
  158. penguwatch-0.15.0/src/penguwatch/web/static/js/state.js +11 -0
  159. penguwatch-0.15.0/src/penguwatch/web/static/js/tabs.js +92 -0
  160. penguwatch-0.15.0/src/penguwatch/web/static/js/util.js +143 -0
  161. penguwatch-0.15.0/src/penguwatch/web/static/login.css +26 -0
  162. penguwatch-0.15.0/src/penguwatch/web/static/manifest.webmanifest +14 -0
  163. penguwatch-0.15.0/src/penguwatch/web/static/offline.css +6 -0
  164. penguwatch-0.15.0/src/penguwatch/web/static/offline.html +7 -0
  165. penguwatch-0.15.0/src/penguwatch/web/static/page.css +170 -0
  166. penguwatch-0.15.0/src/penguwatch/web/static/panels/calendar/panel.css +22 -0
  167. penguwatch-0.15.0/src/penguwatch/web/static/panels/calendar/panel.html +17 -0
  168. penguwatch-0.15.0/src/penguwatch/web/static/panels/calendar/panel.js +82 -0
  169. penguwatch-0.15.0/src/penguwatch/web/static/panels/calendar/panel.json +1 -0
  170. penguwatch-0.15.0/src/penguwatch/web/static/panels/decide/panel.css +23 -0
  171. penguwatch-0.15.0/src/penguwatch/web/static/panels/decide/panel.html +87 -0
  172. penguwatch-0.15.0/src/penguwatch/web/static/panels/decide/panel.js +62 -0
  173. penguwatch-0.15.0/src/penguwatch/web/static/panels/decide/panel.json +1 -0
  174. penguwatch-0.15.0/src/penguwatch/web/static/panels/discover/panel.css +21 -0
  175. penguwatch-0.15.0/src/penguwatch/web/static/panels/discover/panel.html +34 -0
  176. penguwatch-0.15.0/src/penguwatch/web/static/panels/discover/panel.js +135 -0
  177. penguwatch-0.15.0/src/penguwatch/web/static/panels/discover/panel.json +1 -0
  178. penguwatch-0.15.0/src/penguwatch/web/static/panels/insights/panel.css +17 -0
  179. penguwatch-0.15.0/src/penguwatch/web/static/panels/insights/panel.html +16 -0
  180. penguwatch-0.15.0/src/penguwatch/web/static/panels/insights/panel.js +118 -0
  181. penguwatch-0.15.0/src/penguwatch/web/static/panels/insights/panel.json +1 -0
  182. penguwatch-0.15.0/src/penguwatch/web/static/panels/optimizer/panel.css +118 -0
  183. penguwatch-0.15.0/src/penguwatch/web/static/panels/optimizer/panel.html +76 -0
  184. penguwatch-0.15.0/src/penguwatch/web/static/panels/optimizer/panel.js +302 -0
  185. penguwatch-0.15.0/src/penguwatch/web/static/panels/optimizer/panel.json +1 -0
  186. penguwatch-0.15.0/src/penguwatch/web/static/panels/optimizer/planner.js +224 -0
  187. penguwatch-0.15.0/src/penguwatch/web/static/panels/ratings/csv.js +78 -0
  188. penguwatch-0.15.0/src/penguwatch/web/static/panels/ratings/panel.css +14 -0
  189. penguwatch-0.15.0/src/penguwatch/web/static/panels/ratings/panel.html +42 -0
  190. penguwatch-0.15.0/src/penguwatch/web/static/panels/ratings/panel.js +291 -0
  191. penguwatch-0.15.0/src/penguwatch/web/static/panels/ratings/panel.json +1 -0
  192. penguwatch-0.15.0/src/penguwatch/web/static/panels/ratings/source.html +50 -0
  193. penguwatch-0.15.0/src/penguwatch/web/static/panels/services/addservice.js +95 -0
  194. penguwatch-0.15.0/src/penguwatch/web/static/panels/services/library.js +218 -0
  195. penguwatch-0.15.0/src/penguwatch/web/static/panels/services/panel.css +75 -0
  196. penguwatch-0.15.0/src/penguwatch/web/static/panels/services/panel.html +167 -0
  197. penguwatch-0.15.0/src/penguwatch/web/static/panels/services/panel.js +186 -0
  198. penguwatch-0.15.0/src/penguwatch/web/static/panels/services/panel.json +1 -0
  199. penguwatch-0.15.0/src/penguwatch/web/static/panels/services/pin.html +20 -0
  200. penguwatch-0.15.0/src/penguwatch/web/static/panels/services/review.html +41 -0
  201. penguwatch-0.15.0/src/penguwatch/web/static/panels/services/review.js +172 -0
  202. penguwatch-0.15.0/src/penguwatch/web/static/panels/services/session.js +29 -0
  203. penguwatch-0.15.0/src/penguwatch/web/static/panels/system/panel.css +20 -0
  204. penguwatch-0.15.0/src/penguwatch/web/static/panels/system/panel.html +15 -0
  205. penguwatch-0.15.0/src/penguwatch/web/static/panels/system/panel.js +195 -0
  206. penguwatch-0.15.0/src/penguwatch/web/static/panels/system/panel.json +1 -0
  207. penguwatch-0.15.0/src/penguwatch/web/static/panels/upnext/panel.css +41 -0
  208. penguwatch-0.15.0/src/penguwatch/web/static/panels/upnext/panel.html +14 -0
  209. penguwatch-0.15.0/src/penguwatch/web/static/panels/upnext/panel.js +155 -0
  210. penguwatch-0.15.0/src/penguwatch/web/static/panels/upnext/panel.json +1 -0
  211. penguwatch-0.15.0/src/penguwatch/web/static/panels/watchlist/cards.js +213 -0
  212. penguwatch-0.15.0/src/penguwatch/web/static/panels/watchlist/panel.css +121 -0
  213. penguwatch-0.15.0/src/penguwatch/web/static/panels/watchlist/panel.html +81 -0
  214. penguwatch-0.15.0/src/penguwatch/web/static/panels/watchlist/panel.js +341 -0
  215. penguwatch-0.15.0/src/penguwatch/web/static/panels/watchlist/panel.json +1 -0
  216. penguwatch-0.15.0/src/penguwatch/web/static/panels/watchlist/tidy.html +61 -0
  217. penguwatch-0.15.0/src/penguwatch/web/static/panels/watchlist/tidy.js +172 -0
  218. penguwatch-0.15.0/src/penguwatch/web/static/panels/watchlist/tidyassign.js +53 -0
  219. penguwatch-0.15.0/src/penguwatch/web/static/style.css +160 -0
  220. penguwatch-0.15.0/src/penguwatch/web/static/sw.js +37 -0
  221. penguwatch-0.15.0/src/penguwatch/web/static/theme.js +8 -0
  222. penguwatch-0.15.0/src/penguwatch/web/static/tokens.css +148 -0
  223. penguwatch-0.15.0/src/penguwatch/web/templates/icons.svg +109 -0
  224. penguwatch-0.15.0/src/penguwatch/web/templates/index.html +100 -0
  225. penguwatch-0.15.0/src/penguwatch/webhooks.py +246 -0
  226. penguwatch-0.15.0/src/penguwatch/webhooks_component.py +130 -0
  227. penguwatch-0.15.0/src/penguwatch.egg-info/PKG-INFO +259 -0
  228. penguwatch-0.15.0/src/penguwatch.egg-info/SOURCES.txt +230 -0
  229. penguwatch-0.15.0/src/penguwatch.egg-info/dependency_links.txt +1 -0
  230. penguwatch-0.15.0/src/penguwatch.egg-info/entry_points.txt +5 -0
  231. penguwatch-0.15.0/src/penguwatch.egg-info/requires.txt +18 -0
  232. penguwatch-0.15.0/src/penguwatch.egg-info/top_level.txt +2 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PenguPowered
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,259 @@
1
+ Metadata-Version: 2.4
2
+ Name: penguwatch
3
+ Version: 0.15.0
4
+ Summary: TV & movie tracker, streaming subscription rotation optimizer, and two-way SIMKL sync
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: fastapi>=0.110.0
10
+ Requires-Dist: uvicorn[standard]>=0.29.0
11
+ Requires-Dist: aiosqlite>=0.20.0
12
+ Requires-Dist: pydantic>=2.7.0
13
+ Requires-Dist: pydantic-settings>=2.2.1
14
+ Requires-Dist: pyyaml>=6.0.1
15
+ Requires-Dist: jinja2>=3.1.3
16
+ Requires-Dist: httpx>=0.27.0
17
+ Requires-Dist: opentelemetry-sdk<2,>=1.45.1
18
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.45.1
19
+ Requires-Dist: tzdata>=2024.1; sys_platform == "win32"
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=8.1.0; extra == "dev"
22
+ Requires-Dist: pytest-asyncio>=0.23.6; extra == "dev"
23
+ Requires-Dist: respx>=0.21.0; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ <p align="center">
27
+ <img src="docs/images/logo.png" width="112" height="112" alt="">
28
+ </p>
29
+
30
+ <h1 align="center">PenguWatch</h1>
31
+
32
+ <p align="center">
33
+ <b>Know what to watch next, and pay only for the streaming services you actually use.</b><br>
34
+ A self-hosted TV, anime and movie tracker with a streaming-subscription rotation planner, and two-way sync with your tracking account (SIMKL by default) or no account at all.
35
+ </p>
36
+
37
+ <p align="center">
38
+ <img src="docs/images/up-next-dark.png" width="860" alt="Up Next: the next episode of every show in progress, with posters, services and progress">
39
+ </p>
40
+
41
+ <p align="center">
42
+ <img src="docs/images/library-light.png" width="420" alt="The library in the light theme, with a custom rating badge">
43
+ <img src="docs/images/optimizer-dark.png" width="420" alt="The subscription optimizer advising which service to cancel">
44
+ <img src="docs/images/phone-up-next.png" width="150" alt="Up Next on a phone">
45
+ </p>
46
+
47
+ <p align="center"><sub>Screenshots of the made-up demo library (<code>scripts/demo_library.py</code>). More in <a href="docs/screenshots.md">the gallery</a>.</sub></p>
48
+
49
+ PenguWatch is for households that follow shows, anime and movies across several streaming services and want one place
50
+ that says what to watch next and which subscriptions are worth keeping. You run it yourself, on a home server, a NAS or
51
+ just your own computer: one small program with one SQLite database, used from any browser on your network (and as an
52
+ installable phone app). It keeps your library in step with a tracking account, the **library service**
53
+ ([SIMKL](https://simkl.com) out of the box), or keeps it only in PenguWatch with no account at all. Each service is a
54
+ [driver](docs/drivers.md); this README says "the library service" for whichever one holds your library.
55
+
56
+ ## Why
57
+
58
+ Most households pay for more streaming services than they watch in a given month, and lose track of where each show
59
+ is. PenguWatch keeps your library in sync with your library service, shows the exact next episode of everything
60
+ you're watching, and plans which services to keep, pause or cancel month by month, based on what you actually have
61
+ left to watch on each.
62
+
63
+ It runs as one small container on a home server or NAS (or as a Python program installed with uv or pipx), keeps everything in a local SQLite database, and works from
64
+ any browser on your network, including as an installable phone app.
65
+
66
+ ## Features
67
+
68
+ - **Up Next:** every show you're watching with its exact next episode (`S02E05 – Title`), progress and episodes left.
69
+ One tap marks it watched and sends that episode to the library service.
70
+ - **Episode-level tracking** for shows, anime and movies: whole seasons, single episodes or "first N" at once; only
71
+ the episodes that changed are sent to the library service.
72
+ - **Two-way sync** with the library service, fetching only what changed since the last sync, with an outbox that survives restarts and a
73
+ **review mode** (on by default) where nothing is sent or applied until you approve it.
74
+ - **Drivers:** each service is a driver ([docs/drivers.md](docs/drivers.md)): SIMKL (the default), a `local` driver that
75
+ keeps your library only in PenguWatch, and a TVmaze driver that finds TV shows without any account. The guide shows how
76
+ to write another one.
77
+ - **Subscription optimizer and rotation planner:** ranks services by backlog hours, ready-to-binge seasons and
78
+ ratings; plans the next 3, 6 or 12 months within a limit of active services; understands bundles (for example
79
+ Disney+ & Hulu); tracks renewal dates, price history and cost per hour watched.
80
+ - **Discover:** trending titles and "because you liked X & Y" picks from the discovery service, marked with what you already have and
81
+ which of your services carries them.
82
+ - **English titles and forgiving search:** anime stored under romaji titles show their English name first; search
83
+ ignores accents, case, punctuation and long vowels ("shogun" finds *Shōgun*).
84
+ - **Household:** optionally two people, each title tagged with who wants to watch it; "both want it" titles count
85
+ more in the plan. Names come from your configuration.
86
+ - **Tonight's Pick, air schedule and an iCal feed** for your calendar app, including renewal and "cancel by"
87
+ reminders.
88
+ - **Alerts and insights:** a daily ntfy or webhook digest, monthly hours, hours by service, top genres.
89
+ - **Your own ratings:** add rating sources (a site you follow, a friend, a spreadsheet), import them from CSV or the
90
+ API, then show them as badges, sort and filter by them. The ratings the catalogue drivers provide are built in (SIMKL's and IMDb's, or MyAnimeList's for anime, from SIMKL;
91
+ TVmaze's from TVmaze).
92
+ - **Extensible from outside:** a documented REST API ([docs/api.md](docs/api.md)) and signed event webhooks
93
+ (title added, status or rating changed, episode watched or airing today, sync failed); examples in
94
+ [docs/extending.md](docs/extending.md).
95
+ - **Light and dark themes**, following your system until you pick one.
96
+ - **Built for a home network:** optional access token, DNS-rebinding and cross-site protection, a strict CSP, a
97
+ non-root read-only container, Prometheus metrics and optional OpenTelemetry export.
98
+
99
+ ## Quick start
100
+
101
+ Out of the box PenguWatch syncs with a free SIMKL account, but you can [run without one](#without-an-account). PenguWatch runs on your own computer, starting by itself in the background, or in a
102
+ container: pick one.
103
+
104
+ **Windows** (PowerShell, no administrator rights):
105
+
106
+ ```powershell
107
+ irm https://github.com/pengupowered/penguwatch/releases/latest/download/install.ps1 | iex
108
+ ```
109
+
110
+ **macOS and Linux** (asks for sudo once; end the line with `sh -s -- --user` instead to avoid it):
111
+
112
+ ```sh
113
+ curl -fsSL https://github.com/pengupowered/penguwatch/releases/latest/download/install.sh | sh
114
+ ```
115
+
116
+ The scripts check every download (pinned uv, hash-locked dependencies), install a private Python 3.12, and start
117
+ PenguWatch on `http://127.0.0.1:8080`, this computer only. Per-system details, upgrading and uninstalling:
118
+ [Install](docs/install.md).
119
+
120
+ **With uv or pipx** (Python 3.11 or newer):
121
+
122
+ ```bash
123
+ uv tool install penguwatch # or: pipx install penguwatch
124
+ penguwatch init-config # writes an example config.yaml to edit (your services, prices, household names)
125
+ penguwatch # http://127.0.0.1:8080, this computer only
126
+ penguwatch service install --user # optional: start it by itself (systemd, launchd or a logon task)
127
+ ```
128
+
129
+ **With a container** (Docker, or Podman with podman-compose; Intel/AMD and ARM):
130
+
131
+ ```bash
132
+ mkdir penguwatch && cd penguwatch
133
+ curl -fsSL https://raw.githubusercontent.com/pengupowered/penguwatch/main/compose.release.yaml -o compose.yaml
134
+ curl -fsSL https://raw.githubusercontent.com/pengupowered/penguwatch/main/config.example.yaml -o config.yaml
135
+ docker compose up -d # or: podman compose up -d
136
+ ```
137
+
138
+ Or build it yourself from a clone: `cp config.example.yaml config.yaml`, then `docker compose up -d --build`. Open
139
+ `http://<server-ip>:8080`. [Install](docs/install.md) has the details: where config and data live, upgrading, backups.
140
+ Then connect the default library service, SIMKL (the first run, with local and TVmaze instead, is in [Getting started](docs/getting-started.md)):
141
+
142
+ 1. Create a SIMKL app once at [simkl.com/settings/developer](https://simkl.com/settings/developer/app/new) and copy
143
+ its client ID.
144
+ 2. In PenguWatch, open **Services & Sync**, enter the client ID under *Enter Client ID / Token Manually*, then choose
145
+ **Connect SIMKL with Device PIN** and approve the code at simkl.com/pin. The first sync imports your library.
146
+ 3. Mark the services you subscribe to this month and check their prices.
147
+
148
+ > [!IMPORTANT]
149
+ > Without an access token, anyone who can reach port 8080 can read and change your library, **connect PenguWatch to
150
+ > their own library-service account** (your later changes are then sent to them), approve queued changes, download a backup and
151
+ > trigger the webhook and notification tests. Both compose files publish `0.0.0.0:8080`, so a container is reachable on
152
+ > your whole network from its first start: set `PENGUWATCH_ACCESS_TOKEN` first (in `.env` for the container, in
153
+ > `penguwatch.env` next to `config.yaml` for a native install; see [Install](docs/install.md#settings-and-secrets) and
154
+ > [Security](docs/security.md)). Over plain HTTP the token can be read by others on the network: use HTTPS through a
155
+ > reverse proxy if that matters. The `penguwatch` command listens on this computer only unless you pass `--host 0.0.0.0`.
156
+
157
+ Your data survives upgrades: it lives in the `penguwatch_data` volume (container) or your data folder
158
+ (`penguwatch paths`). To update, run the install script again (or `uv tool upgrade penguwatch`), or raise the image tag
159
+ and `docker compose up -d`;
160
+ database migrations run on start.
161
+
162
+ ## Without an account
163
+
164
+ SIMKL is the default library service, but PenguWatch works with no account. In `config.yaml`:
165
+
166
+ ```yaml
167
+ drivers:
168
+ library: local # your library, watch history and ratings live only in PenguWatch
169
+ catalog: [tvmaze] # find and add TV shows at TVmaze (no account or key)
170
+ schedule: [tvmaze] # upcoming episodes, from TVmaze
171
+ discovery: [] # no Discover tab
172
+ ```
173
+
174
+ You can still add shows, track episodes, rate them, get the air schedule and calendar feed, and use the subscription
175
+ optimizer and planner. What you lose compared with a library service:
176
+
177
+ - **Sync:** nothing is sent to or read from an account, so an existing account library is not imported and no other device
178
+ or app sees your changes. Back up the database ([Operations](docs/operations.md)).
179
+ - **Discovery:** TVmaze has no trending lists or recommendations, so Discover is off.
180
+ - **Movies:** TVmaze has no movies, so a movie cannot be searched for and added (the catalogue list can name more than
181
+ one driver: `[tvmaze, simkl]` adds SIMKL's catalogue, which then needs a SIMKL app id).
182
+ - **Anime:** TVmaze lists an anime as a TV show with the Anime genre, which PenguWatch types as anime; it has no
183
+ sequels and no anime-specific data beyond that genre.
184
+ - **Ratings:** TVmaze's average only, with none of the scores a library service's catalogue brings (SIMKL's and IMDb's, with the default).
185
+
186
+ TVmaze's data is CC BY-SA 4.0, so PenguWatch credits TVmaze (in the page footer, on Services & Sync and in a title's
187
+ details) as its licence asks. [Drivers](docs/drivers.md) has the details, and `library: simkl` with
188
+ `catalog: [tvmaze, simkl]` mixes both.
189
+
190
+ ## Configuration in brief
191
+
192
+ `config.yaml` holds the server settings, the sync schedule, the optimizer's weights, your household and the streaming
193
+ services with their network aliases (used to work out which service carries a title). For example:
194
+
195
+ ```yaml
196
+ household:
197
+ members: ["Alex", "Sam"] # one name hides the "who wants to watch" field
198
+
199
+ optimizer:
200
+ max_active_services: 3 # how many paid services the planner may keep at once
201
+ ```
202
+
203
+ `.env` holds secrets and deployment settings (`PENGUWATCH_*`). Everything is described in
204
+ [Configuration](docs/configuration.md).
205
+
206
+ ## Documentation
207
+
208
+ New here? [Getting started](docs/getting-started.md) walks through the first run. [docs/README.md](docs/README.md) is the
209
+ index of everything.
210
+
211
+ | Topic | What it covers |
212
+ |---|---|
213
+ | [Getting started](docs/getting-started.md) | The first run end to end: install, services, the library service or local, the first sync |
214
+ | [FAQ and troubleshooting](docs/faq.md) | Port in use, expired login, nothing syncs, wrong service, time zone, reverse proxy, backup, upgrade, rollback |
215
+ | [Install](docs/install.md) | Windows, macOS, Linux, Raspberry Pi, uv or pipx, container, the background service, upgrading, backups |
216
+ | [Configuration](docs/configuration.md) | `config.yaml`, environment variables, household, services and bundles, time zone |
217
+ | [Using PenguWatch](docs/usage.md) | The tabs, the optimizer and planner, the calendar feed, shortcuts, the phone app |
218
+ | [Sync](docs/sync.md) | How the sync engine works (outbox, review mode, removals), then the SIMKL driver's rules: API usage, login renewal |
219
+ | [Drivers](docs/drivers.md) | The drivers that ship (SIMKL, local, TVmaze), switching, and a guide to writing one |
220
+ | [API](docs/api.md) | Authentication, the stable endpoints and their shapes, errors, limits |
221
+ | [Extending](docs/extending.md) | Your own rating sources, event webhooks, drivers, adding a component |
222
+ | [Security](docs/security.md) | Access token, request guards, the container, what is exposed |
223
+ | [Operations](docs/operations.md) | Monitoring, Prometheus, OpenTelemetry, notifications, backups and exports |
224
+ | [Architecture](docs/architecture.md) | The ordered list of backend components, start-up failures, shutdown |
225
+ | [Development](docs/development.md) | Project layout, running locally, tests |
226
+ | [The web UI](docs/ui.md) | How the page is built, theming, adding a tab, the rules the tests enforce |
227
+ | [Releasing](docs/releasing.md) | CI, the release workflow, PyPI and the container registry |
228
+ | [Supply chain](docs/supply-chain.md) | Dependency policy, hash-locked requirements, vendored code |
229
+
230
+ **Security in one line:** one shared access token protects the app, which is meant for a home network and not the open
231
+ internet; set it, and read [docs/security.md](docs/security.md). Found a vulnerability? Report it privately
232
+ ([SECURITY.md](SECURITY.md)).
233
+
234
+ ## Contributing
235
+
236
+ Bug reports, ideas and code are welcome: see [CONTRIBUTING.md](CONTRIBUTING.md) and the
237
+ [code of conduct](CODE_OF_CONDUCT.md).
238
+
239
+ ## Development
240
+
241
+ ```bash
242
+ pip install --require-hashes --no-deps -r requirements-dev.txt
243
+ PYTHONPATH=src pytest -q
244
+ PYTHONPATH=src python -m penguwatch.main # http://localhost:8080, using ./config.yaml or the example
245
+ scripts/test.sh # the test suite in a container, nothing installed locally
246
+ ```
247
+
248
+ Dependencies are hash-locked (`requirements*.txt`, generated from the `.in` files) and installed with
249
+ `--require-hashes`. `scripts/deploy.sh` is the maintainer's deploy for rootless Podman: it adds a dependency audit (the
250
+ separate `depguard` tool), a rollback image and a database backup before rebuilding. `docker compose` is all you need
251
+ to run PenguWatch.
252
+
253
+ ## License
254
+
255
+ [MIT](LICENSE). PenguWatch is not affiliated with SIMKL or TVmaze; its default driver uses SIMKL's public API with your own client ID.
256
+
257
+ **TVmaze credit:** when the TVmaze driver is used, TV data comes from [TVmaze](https://www.tvmaze.com) under
258
+ [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), and PenguWatch says so in the page footer, on Services &
259
+ Sync and in a title's details ("TV data from TVmaze", linked to tvmaze.com).
@@ -0,0 +1,234 @@
1
+ <p align="center">
2
+ <img src="docs/images/logo.png" width="112" height="112" alt="">
3
+ </p>
4
+
5
+ <h1 align="center">PenguWatch</h1>
6
+
7
+ <p align="center">
8
+ <b>Know what to watch next, and pay only for the streaming services you actually use.</b><br>
9
+ A self-hosted TV, anime and movie tracker with a streaming-subscription rotation planner, and two-way sync with your tracking account (SIMKL by default) or no account at all.
10
+ </p>
11
+
12
+ <p align="center">
13
+ <img src="docs/images/up-next-dark.png" width="860" alt="Up Next: the next episode of every show in progress, with posters, services and progress">
14
+ </p>
15
+
16
+ <p align="center">
17
+ <img src="docs/images/library-light.png" width="420" alt="The library in the light theme, with a custom rating badge">
18
+ <img src="docs/images/optimizer-dark.png" width="420" alt="The subscription optimizer advising which service to cancel">
19
+ <img src="docs/images/phone-up-next.png" width="150" alt="Up Next on a phone">
20
+ </p>
21
+
22
+ <p align="center"><sub>Screenshots of the made-up demo library (<code>scripts/demo_library.py</code>). More in <a href="docs/screenshots.md">the gallery</a>.</sub></p>
23
+
24
+ PenguWatch is for households that follow shows, anime and movies across several streaming services and want one place
25
+ that says what to watch next and which subscriptions are worth keeping. You run it yourself, on a home server, a NAS or
26
+ just your own computer: one small program with one SQLite database, used from any browser on your network (and as an
27
+ installable phone app). It keeps your library in step with a tracking account, the **library service**
28
+ ([SIMKL](https://simkl.com) out of the box), or keeps it only in PenguWatch with no account at all. Each service is a
29
+ [driver](docs/drivers.md); this README says "the library service" for whichever one holds your library.
30
+
31
+ ## Why
32
+
33
+ Most households pay for more streaming services than they watch in a given month, and lose track of where each show
34
+ is. PenguWatch keeps your library in sync with your library service, shows the exact next episode of everything
35
+ you're watching, and plans which services to keep, pause or cancel month by month, based on what you actually have
36
+ left to watch on each.
37
+
38
+ It runs as one small container on a home server or NAS (or as a Python program installed with uv or pipx), keeps everything in a local SQLite database, and works from
39
+ any browser on your network, including as an installable phone app.
40
+
41
+ ## Features
42
+
43
+ - **Up Next:** every show you're watching with its exact next episode (`S02E05 – Title`), progress and episodes left.
44
+ One tap marks it watched and sends that episode to the library service.
45
+ - **Episode-level tracking** for shows, anime and movies: whole seasons, single episodes or "first N" at once; only
46
+ the episodes that changed are sent to the library service.
47
+ - **Two-way sync** with the library service, fetching only what changed since the last sync, with an outbox that survives restarts and a
48
+ **review mode** (on by default) where nothing is sent or applied until you approve it.
49
+ - **Drivers:** each service is a driver ([docs/drivers.md](docs/drivers.md)): SIMKL (the default), a `local` driver that
50
+ keeps your library only in PenguWatch, and a TVmaze driver that finds TV shows without any account. The guide shows how
51
+ to write another one.
52
+ - **Subscription optimizer and rotation planner:** ranks services by backlog hours, ready-to-binge seasons and
53
+ ratings; plans the next 3, 6 or 12 months within a limit of active services; understands bundles (for example
54
+ Disney+ & Hulu); tracks renewal dates, price history and cost per hour watched.
55
+ - **Discover:** trending titles and "because you liked X & Y" picks from the discovery service, marked with what you already have and
56
+ which of your services carries them.
57
+ - **English titles and forgiving search:** anime stored under romaji titles show their English name first; search
58
+ ignores accents, case, punctuation and long vowels ("shogun" finds *Shōgun*).
59
+ - **Household:** optionally two people, each title tagged with who wants to watch it; "both want it" titles count
60
+ more in the plan. Names come from your configuration.
61
+ - **Tonight's Pick, air schedule and an iCal feed** for your calendar app, including renewal and "cancel by"
62
+ reminders.
63
+ - **Alerts and insights:** a daily ntfy or webhook digest, monthly hours, hours by service, top genres.
64
+ - **Your own ratings:** add rating sources (a site you follow, a friend, a spreadsheet), import them from CSV or the
65
+ API, then show them as badges, sort and filter by them. The ratings the catalogue drivers provide are built in (SIMKL's and IMDb's, or MyAnimeList's for anime, from SIMKL;
66
+ TVmaze's from TVmaze).
67
+ - **Extensible from outside:** a documented REST API ([docs/api.md](docs/api.md)) and signed event webhooks
68
+ (title added, status or rating changed, episode watched or airing today, sync failed); examples in
69
+ [docs/extending.md](docs/extending.md).
70
+ - **Light and dark themes**, following your system until you pick one.
71
+ - **Built for a home network:** optional access token, DNS-rebinding and cross-site protection, a strict CSP, a
72
+ non-root read-only container, Prometheus metrics and optional OpenTelemetry export.
73
+
74
+ ## Quick start
75
+
76
+ Out of the box PenguWatch syncs with a free SIMKL account, but you can [run without one](#without-an-account). PenguWatch runs on your own computer, starting by itself in the background, or in a
77
+ container: pick one.
78
+
79
+ **Windows** (PowerShell, no administrator rights):
80
+
81
+ ```powershell
82
+ irm https://github.com/pengupowered/penguwatch/releases/latest/download/install.ps1 | iex
83
+ ```
84
+
85
+ **macOS and Linux** (asks for sudo once; end the line with `sh -s -- --user` instead to avoid it):
86
+
87
+ ```sh
88
+ curl -fsSL https://github.com/pengupowered/penguwatch/releases/latest/download/install.sh | sh
89
+ ```
90
+
91
+ The scripts check every download (pinned uv, hash-locked dependencies), install a private Python 3.12, and start
92
+ PenguWatch on `http://127.0.0.1:8080`, this computer only. Per-system details, upgrading and uninstalling:
93
+ [Install](docs/install.md).
94
+
95
+ **With uv or pipx** (Python 3.11 or newer):
96
+
97
+ ```bash
98
+ uv tool install penguwatch # or: pipx install penguwatch
99
+ penguwatch init-config # writes an example config.yaml to edit (your services, prices, household names)
100
+ penguwatch # http://127.0.0.1:8080, this computer only
101
+ penguwatch service install --user # optional: start it by itself (systemd, launchd or a logon task)
102
+ ```
103
+
104
+ **With a container** (Docker, or Podman with podman-compose; Intel/AMD and ARM):
105
+
106
+ ```bash
107
+ mkdir penguwatch && cd penguwatch
108
+ curl -fsSL https://raw.githubusercontent.com/pengupowered/penguwatch/main/compose.release.yaml -o compose.yaml
109
+ curl -fsSL https://raw.githubusercontent.com/pengupowered/penguwatch/main/config.example.yaml -o config.yaml
110
+ docker compose up -d # or: podman compose up -d
111
+ ```
112
+
113
+ Or build it yourself from a clone: `cp config.example.yaml config.yaml`, then `docker compose up -d --build`. Open
114
+ `http://<server-ip>:8080`. [Install](docs/install.md) has the details: where config and data live, upgrading, backups.
115
+ Then connect the default library service, SIMKL (the first run, with local and TVmaze instead, is in [Getting started](docs/getting-started.md)):
116
+
117
+ 1. Create a SIMKL app once at [simkl.com/settings/developer](https://simkl.com/settings/developer/app/new) and copy
118
+ its client ID.
119
+ 2. In PenguWatch, open **Services & Sync**, enter the client ID under *Enter Client ID / Token Manually*, then choose
120
+ **Connect SIMKL with Device PIN** and approve the code at simkl.com/pin. The first sync imports your library.
121
+ 3. Mark the services you subscribe to this month and check their prices.
122
+
123
+ > [!IMPORTANT]
124
+ > Without an access token, anyone who can reach port 8080 can read and change your library, **connect PenguWatch to
125
+ > their own library-service account** (your later changes are then sent to them), approve queued changes, download a backup and
126
+ > trigger the webhook and notification tests. Both compose files publish `0.0.0.0:8080`, so a container is reachable on
127
+ > your whole network from its first start: set `PENGUWATCH_ACCESS_TOKEN` first (in `.env` for the container, in
128
+ > `penguwatch.env` next to `config.yaml` for a native install; see [Install](docs/install.md#settings-and-secrets) and
129
+ > [Security](docs/security.md)). Over plain HTTP the token can be read by others on the network: use HTTPS through a
130
+ > reverse proxy if that matters. The `penguwatch` command listens on this computer only unless you pass `--host 0.0.0.0`.
131
+
132
+ Your data survives upgrades: it lives in the `penguwatch_data` volume (container) or your data folder
133
+ (`penguwatch paths`). To update, run the install script again (or `uv tool upgrade penguwatch`), or raise the image tag
134
+ and `docker compose up -d`;
135
+ database migrations run on start.
136
+
137
+ ## Without an account
138
+
139
+ SIMKL is the default library service, but PenguWatch works with no account. In `config.yaml`:
140
+
141
+ ```yaml
142
+ drivers:
143
+ library: local # your library, watch history and ratings live only in PenguWatch
144
+ catalog: [tvmaze] # find and add TV shows at TVmaze (no account or key)
145
+ schedule: [tvmaze] # upcoming episodes, from TVmaze
146
+ discovery: [] # no Discover tab
147
+ ```
148
+
149
+ You can still add shows, track episodes, rate them, get the air schedule and calendar feed, and use the subscription
150
+ optimizer and planner. What you lose compared with a library service:
151
+
152
+ - **Sync:** nothing is sent to or read from an account, so an existing account library is not imported and no other device
153
+ or app sees your changes. Back up the database ([Operations](docs/operations.md)).
154
+ - **Discovery:** TVmaze has no trending lists or recommendations, so Discover is off.
155
+ - **Movies:** TVmaze has no movies, so a movie cannot be searched for and added (the catalogue list can name more than
156
+ one driver: `[tvmaze, simkl]` adds SIMKL's catalogue, which then needs a SIMKL app id).
157
+ - **Anime:** TVmaze lists an anime as a TV show with the Anime genre, which PenguWatch types as anime; it has no
158
+ sequels and no anime-specific data beyond that genre.
159
+ - **Ratings:** TVmaze's average only, with none of the scores a library service's catalogue brings (SIMKL's and IMDb's, with the default).
160
+
161
+ TVmaze's data is CC BY-SA 4.0, so PenguWatch credits TVmaze (in the page footer, on Services & Sync and in a title's
162
+ details) as its licence asks. [Drivers](docs/drivers.md) has the details, and `library: simkl` with
163
+ `catalog: [tvmaze, simkl]` mixes both.
164
+
165
+ ## Configuration in brief
166
+
167
+ `config.yaml` holds the server settings, the sync schedule, the optimizer's weights, your household and the streaming
168
+ services with their network aliases (used to work out which service carries a title). For example:
169
+
170
+ ```yaml
171
+ household:
172
+ members: ["Alex", "Sam"] # one name hides the "who wants to watch" field
173
+
174
+ optimizer:
175
+ max_active_services: 3 # how many paid services the planner may keep at once
176
+ ```
177
+
178
+ `.env` holds secrets and deployment settings (`PENGUWATCH_*`). Everything is described in
179
+ [Configuration](docs/configuration.md).
180
+
181
+ ## Documentation
182
+
183
+ New here? [Getting started](docs/getting-started.md) walks through the first run. [docs/README.md](docs/README.md) is the
184
+ index of everything.
185
+
186
+ | Topic | What it covers |
187
+ |---|---|
188
+ | [Getting started](docs/getting-started.md) | The first run end to end: install, services, the library service or local, the first sync |
189
+ | [FAQ and troubleshooting](docs/faq.md) | Port in use, expired login, nothing syncs, wrong service, time zone, reverse proxy, backup, upgrade, rollback |
190
+ | [Install](docs/install.md) | Windows, macOS, Linux, Raspberry Pi, uv or pipx, container, the background service, upgrading, backups |
191
+ | [Configuration](docs/configuration.md) | `config.yaml`, environment variables, household, services and bundles, time zone |
192
+ | [Using PenguWatch](docs/usage.md) | The tabs, the optimizer and planner, the calendar feed, shortcuts, the phone app |
193
+ | [Sync](docs/sync.md) | How the sync engine works (outbox, review mode, removals), then the SIMKL driver's rules: API usage, login renewal |
194
+ | [Drivers](docs/drivers.md) | The drivers that ship (SIMKL, local, TVmaze), switching, and a guide to writing one |
195
+ | [API](docs/api.md) | Authentication, the stable endpoints and their shapes, errors, limits |
196
+ | [Extending](docs/extending.md) | Your own rating sources, event webhooks, drivers, adding a component |
197
+ | [Security](docs/security.md) | Access token, request guards, the container, what is exposed |
198
+ | [Operations](docs/operations.md) | Monitoring, Prometheus, OpenTelemetry, notifications, backups and exports |
199
+ | [Architecture](docs/architecture.md) | The ordered list of backend components, start-up failures, shutdown |
200
+ | [Development](docs/development.md) | Project layout, running locally, tests |
201
+ | [The web UI](docs/ui.md) | How the page is built, theming, adding a tab, the rules the tests enforce |
202
+ | [Releasing](docs/releasing.md) | CI, the release workflow, PyPI and the container registry |
203
+ | [Supply chain](docs/supply-chain.md) | Dependency policy, hash-locked requirements, vendored code |
204
+
205
+ **Security in one line:** one shared access token protects the app, which is meant for a home network and not the open
206
+ internet; set it, and read [docs/security.md](docs/security.md). Found a vulnerability? Report it privately
207
+ ([SECURITY.md](SECURITY.md)).
208
+
209
+ ## Contributing
210
+
211
+ Bug reports, ideas and code are welcome: see [CONTRIBUTING.md](CONTRIBUTING.md) and the
212
+ [code of conduct](CODE_OF_CONDUCT.md).
213
+
214
+ ## Development
215
+
216
+ ```bash
217
+ pip install --require-hashes --no-deps -r requirements-dev.txt
218
+ PYTHONPATH=src pytest -q
219
+ PYTHONPATH=src python -m penguwatch.main # http://localhost:8080, using ./config.yaml or the example
220
+ scripts/test.sh # the test suite in a container, nothing installed locally
221
+ ```
222
+
223
+ Dependencies are hash-locked (`requirements*.txt`, generated from the `.in` files) and installed with
224
+ `--require-hashes`. `scripts/deploy.sh` is the maintainer's deploy for rootless Podman: it adds a dependency audit (the
225
+ separate `depguard` tool), a rollback image and a database backup before rebuilding. `docker compose` is all you need
226
+ to run PenguWatch.
227
+
228
+ ## License
229
+
230
+ [MIT](LICENSE). PenguWatch is not affiliated with SIMKL or TVmaze; its default driver uses SIMKL's public API with your own client ID.
231
+
232
+ **TVmaze credit:** when the TVmaze driver is used, TV data comes from [TVmaze](https://www.tvmaze.com) under
233
+ [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), and PenguWatch says so in the page footer, on Services &
234
+ Sync and in a title's details ("TV data from TVmaze", linked to tvmaze.com).
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "penguwatch"
7
+ dynamic = ["version"]
8
+ description = "TV & movie tracker, streaming subscription rotation optimizer, and two-way SIMKL sync"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.11"
13
+ dependencies = [
14
+ "fastapi>=0.110.0",
15
+ "uvicorn[standard]>=0.29.0",
16
+ "aiosqlite>=0.20.0",
17
+ "pydantic>=2.7.0",
18
+ "pydantic-settings>=2.2.1",
19
+ "pyyaml>=6.0.1",
20
+ "jinja2>=3.1.3",
21
+ "httpx>=0.27.0",
22
+ "opentelemetry-sdk>=1.45.1,<2",
23
+ "opentelemetry-exporter-otlp-proto-http>=1.45.1,<2",
24
+ # Windows has no system time zone database; zoneinfo (the household time zone) reads this one there
25
+ 'tzdata>=2024.1; sys_platform == "win32"'
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ "pytest>=8.1.0",
31
+ "pytest-asyncio>=0.23.6",
32
+ "respx>=0.21.0"
33
+ ]
34
+
35
+ [project.scripts]
36
+ penguwatch = "penguwatch.cli:main"
37
+
38
+ # Same command without a console window (Windows; elsewhere an ordinary script): what the logon task starts.
39
+ [project.gui-scripts]
40
+ penguwatchw = "penguwatch.cli:main"
41
+
42
+ [tool.setuptools.dynamic]
43
+ # The single source of the version (the tests run from src/ without installing, so metadata cannot be it).
44
+ version = {attr = "penguwatch.__version__"}
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["src"]
48
+
49
+ [tool.pytest.ini_options]
50
+ pythonpath = ["src"]
51
+ asyncio_mode = "auto"
52
+
53
+ [tool.setuptools.package-data]
54
+ # Everything read at run time that is not a .py file: the dashboard (HTML, CSS, JS, icons, panels) and the example
55
+ # config. tests/test_wheel.py builds the wheel and checks it is all there.
56
+ penguwatch = ["web/static/**/*", "web/templates/**/*", "config.example.yaml"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,10 @@
1
+ """homeguard: request guards shared by the home apps that are reachable on the local network.
2
+
3
+ This package is vendored: scripts/vendor.py copies it verbatim into each app's src/ directory. Never edit
4
+ the copy inside an app; change it here, commit, and re-vendor.
5
+
6
+ The version constant is deliberately not called __version__: `depguard sbom` takes an app's version from
7
+ the first src/*/__init__.py that defines __version__, and "homeguard" sorts before the app packages.
8
+ """
9
+
10
+ HOMEGUARD_VERSION = "0.1.0"
@@ -0,0 +1,13 @@
1
+ """Generated by homeguard/scripts/vendor.py - do not edit.
2
+
3
+ Records which homeguard commit this copy came from and the SHA-256 of each vendored file (LF bytes).
4
+ FILES are the Python files in this package, SCRIPTS the PowerShell modules copied to <app>/scripts/.
5
+ """
6
+
7
+ COMMIT = "82f1127"
8
+ FILES = {
9
+ "__init__.py": "035b4c21661fbffe3d455b70580a68bed3fb54f460d837d090ba2da6f45dcdc7",
10
+ "db.py": "7df9c8dc66acb331a330cedf568d4dc059c3e99dd34870d8eb4f9d8b8ec0e19a",
11
+ "security.py": "d859214340681b4743f83544fad594135db5e00dbb1b92f36c3c19ccdf45f1c6",
12
+ }
13
+ SCRIPTS = {}