vinted-bot 2.1.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 (211) hide show
  1. vinted_bot-2.1.0/.dockerignore +14 -0
  2. vinted_bot-2.1.0/.env.example +126 -0
  3. vinted_bot-2.1.0/.gitignore +23 -0
  4. vinted_bot-2.1.0/.python-version +1 -0
  5. vinted_bot-2.1.0/CHANGELOG.md +182 -0
  6. vinted_bot-2.1.0/CONTRIBUTING.md +148 -0
  7. vinted_bot-2.1.0/Dockerfile +76 -0
  8. vinted_bot-2.1.0/LICENSE +21 -0
  9. vinted_bot-2.1.0/PKG-INFO +282 -0
  10. vinted_bot-2.1.0/README.fr.md +238 -0
  11. vinted_bot-2.1.0/README.md +234 -0
  12. vinted_bot-2.1.0/SECURITY.md +56 -0
  13. vinted_bot-2.1.0/docker/entrypoint.sh +51 -0
  14. vinted_bot-2.1.0/docker-compose.yml +69 -0
  15. vinted_bot-2.1.0/docs/REVIEW.md +101 -0
  16. vinted_bot-2.1.0/docs/architecture.md +198 -0
  17. vinted_bot-2.1.0/docs/configuration.md +319 -0
  18. vinted_bot-2.1.0/docs/legal.md +82 -0
  19. vinted_bot-2.1.0/docs/self-hosting.md +263 -0
  20. vinted_bot-2.1.0/docs/troubleshooting.md +254 -0
  21. vinted_bot-2.1.0/install.ps1 +380 -0
  22. vinted_bot-2.1.0/install.sh +515 -0
  23. vinted_bot-2.1.0/llms.txt +168 -0
  24. vinted_bot-2.1.0/pyproject.toml +173 -0
  25. vinted_bot-2.1.0/src/vinted_bot/__init__.py +3 -0
  26. vinted_bot-2.1.0/src/vinted_bot/__main__.py +3 -0
  27. vinted_bot-2.1.0/src/vinted_bot/app.py +400 -0
  28. vinted_bot-2.1.0/src/vinted_bot/botctl/__init__.py +1 -0
  29. vinted_bot-2.1.0/src/vinted_bot/botctl/telegram_bot.py +582 -0
  30. vinted_bot-2.1.0/src/vinted_bot/cli.py +490 -0
  31. vinted_bot-2.1.0/src/vinted_bot/config.py +288 -0
  32. vinted_bot-2.1.0/src/vinted_bot/db/__init__.py +6 -0
  33. vinted_bot-2.1.0/src/vinted_bot/db/connection.py +126 -0
  34. vinted_bot-2.1.0/src/vinted_bot/db/export.py +98 -0
  35. vinted_bot-2.1.0/src/vinted_bot/db/migrations/0001_init.sql +124 -0
  36. vinted_bot-2.1.0/src/vinted_bot/db/migrations/0002_seller_details.sql +6 -0
  37. vinted_bot-2.1.0/src/vinted_bot/db/migrations/0003_listing_gallery.sql +7 -0
  38. vinted_bot-2.1.0/src/vinted_bot/db/migrations/0004_richer_searches.sql +277 -0
  39. vinted_bot-2.1.0/src/vinted_bot/db/migrations/0005_resale_and_mute.sql +17 -0
  40. vinted_bot-2.1.0/src/vinted_bot/db/migrations/0006_dedup_that_survives.sql +66 -0
  41. vinted_bot-2.1.0/src/vinted_bot/db/migrations.py +100 -0
  42. vinted_bot-2.1.0/src/vinted_bot/db/repo.py +2145 -0
  43. vinted_bot-2.1.0/src/vinted_bot/deals.py +85 -0
  44. vinted_bot-2.1.0/src/vinted_bot/deliver/__init__.py +15 -0
  45. vinted_bot-2.1.0/src/vinted_bot/deliver/apprise.py +136 -0
  46. vinted_bot-2.1.0/src/vinted_bot/deliver/base.py +88 -0
  47. vinted_bot-2.1.0/src/vinted_bot/deliver/discord.py +819 -0
  48. vinted_bot-2.1.0/src/vinted_bot/deliver/dispatcher.py +359 -0
  49. vinted_bot-2.1.0/src/vinted_bot/deliver/labels.py +452 -0
  50. vinted_bot-2.1.0/src/vinted_bot/deliver/ntfy.py +208 -0
  51. vinted_bot-2.1.0/src/vinted_bot/deliver/preview.py +218 -0
  52. vinted_bot-2.1.0/src/vinted_bot/deliver/ratelimit.py +80 -0
  53. vinted_bot-2.1.0/src/vinted_bot/deliver/render.py +390 -0
  54. vinted_bot-2.1.0/src/vinted_bot/deliver/telegram.py +551 -0
  55. vinted_bot-2.1.0/src/vinted_bot/deliver/webhook.py +186 -0
  56. vinted_bot-2.1.0/src/vinted_bot/engine/__init__.py +6 -0
  57. vinted_bot-2.1.0/src/vinted_bot/engine/dedup.py +135 -0
  58. vinted_bot-2.1.0/src/vinted_bot/engine/filters.py +249 -0
  59. vinted_bot-2.1.0/src/vinted_bot/engine/health.py +204 -0
  60. vinted_bot-2.1.0/src/vinted_bot/engine/poller.py +484 -0
  61. vinted_bot-2.1.0/src/vinted_bot/engine/quiet.py +37 -0
  62. vinted_bot-2.1.0/src/vinted_bot/engine/self_update.py +621 -0
  63. vinted_bot-2.1.0/src/vinted_bot/engine/updates.py +81 -0
  64. vinted_bot-2.1.0/src/vinted_bot/engine/validation.py +293 -0
  65. vinted_bot-2.1.0/src/vinted_bot/engine/watchdog.py +142 -0
  66. vinted_bot-2.1.0/src/vinted_bot/engine/watcher.py +215 -0
  67. vinted_bot-2.1.0/src/vinted_bot/log.py +52 -0
  68. vinted_bot-2.1.0/src/vinted_bot/vinted/__init__.py +34 -0
  69. vinted_bot-2.1.0/src/vinted_bot/vinted/agents.json +78 -0
  70. vinted_bot-2.1.0/src/vinted_bot/vinted/canary.py +100 -0
  71. vinted_bot-2.1.0/src/vinted_bot/vinted/client.py +420 -0
  72. vinted_bot-2.1.0/src/vinted_bot/vinted/errors.py +73 -0
  73. vinted_bot-2.1.0/src/vinted_bot/vinted/headers.py +239 -0
  74. vinted_bot-2.1.0/src/vinted_bot/vinted/item_page.py +228 -0
  75. vinted_bot-2.1.0/src/vinted_bot/vinted/models.py +373 -0
  76. vinted_bot-2.1.0/src/vinted_bot/vinted/proxies.py +94 -0
  77. vinted_bot-2.1.0/src/vinted_bot/vinted/session.py +289 -0
  78. vinted_bot-2.1.0/src/vinted_bot/vinted/taxonomy.py +343 -0
  79. vinted_bot-2.1.0/src/vinted_bot/vinted/transport.py +382 -0
  80. vinted_bot-2.1.0/src/vinted_bot/vinted/urls.py +329 -0
  81. vinted_bot-2.1.0/src/vinted_bot/web/__init__.py +1 -0
  82. vinted_bot-2.1.0/src/vinted_bot/web/core.py +245 -0
  83. vinted_bot-2.1.0/src/vinted_bot/web/data.py +156 -0
  84. vinted_bot-2.1.0/src/vinted_bot/web/destinations.py +207 -0
  85. vinted_bot-2.1.0/src/vinted_bot/web/forms.py +366 -0
  86. vinted_bot-2.1.0/src/vinted_bot/web/preview.py +97 -0
  87. vinted_bot-2.1.0/src/vinted_bot/web/routes/__init__.py +1 -0
  88. vinted_bot-2.1.0/src/vinted_bot/web/routes/alerts.py +208 -0
  89. vinted_bot-2.1.0/src/vinted_bot/web/routes/api.py +293 -0
  90. vinted_bot-2.1.0/src/vinted_bot/web/routes/auth.py +85 -0
  91. vinted_bot-2.1.0/src/vinted_bot/web/routes/finds.py +308 -0
  92. vinted_bot-2.1.0/src/vinted_bot/web/routes/searches.py +471 -0
  93. vinted_bot-2.1.0/src/vinted_bot/web/routes/settings_page.py +185 -0
  94. vinted_bot-2.1.0/src/vinted_bot/web/routes/stats.py +157 -0
  95. vinted_bot-2.1.0/src/vinted_bot/web/routes/updating.py +37 -0
  96. vinted_bot-2.1.0/src/vinted_bot/web/routes/watchlist.py +147 -0
  97. vinted_bot-2.1.0/src/vinted_bot/web/routes/welcome.py +394 -0
  98. vinted_bot-2.1.0/src/vinted_bot/web/security.py +236 -0
  99. vinted_bot-2.1.0/src/vinted_bot/web/server.py +308 -0
  100. vinted_bot-2.1.0/src/vinted_bot/web/static/LICENSE-lucide.txt +15 -0
  101. vinted_bot-2.1.0/src/vinted_bot/web/static/css/app.css +1211 -0
  102. vinted_bot-2.1.0/src/vinted_bot/web/static/css/tokens.css +276 -0
  103. vinted_bot-2.1.0/src/vinted_bot/web/static/fonts/Figtree-latin-ext.woff2 +0 -0
  104. vinted_bot-2.1.0/src/vinted_bot/web/static/fonts/Figtree-latin.woff2 +0 -0
  105. vinted_bot-2.1.0/src/vinted_bot/web/static/fonts/Gabarito-latin-ext.woff2 +0 -0
  106. vinted_bot-2.1.0/src/vinted_bot/web/static/fonts/Gabarito-latin.woff2 +0 -0
  107. vinted_bot-2.1.0/src/vinted_bot/web/static/fonts/OFL-Figtree.txt +93 -0
  108. vinted_bot-2.1.0/src/vinted_bot/web/static/fonts/OFL-Gabarito.txt +93 -0
  109. vinted_bot-2.1.0/src/vinted_bot/web/static/img/demo/corduroy-trousers.webp +0 -0
  110. vinted_bot-2.1.0/src/vinted_bot/web/static/img/demo/denim-jacket.webp +0 -0
  111. vinted_bot-2.1.0/src/vinted_bot/web/static/img/demo/straight-leg-jeans.webp +0 -0
  112. vinted_bot-2.1.0/src/vinted_bot/web/static/img/demo/wool-coat.webp +0 -0
  113. vinted_bot-2.1.0/src/vinted_bot/web/static/img/icons/apple-touch-icon.png +0 -0
  114. vinted_bot-2.1.0/src/vinted_bot/web/static/img/icons/favicon.ico +0 -0
  115. vinted_bot-2.1.0/src/vinted_bot/web/static/img/icons/favicon.svg +13 -0
  116. vinted_bot-2.1.0/src/vinted_bot/web/static/img/icons/icon-192.png +0 -0
  117. vinted_bot-2.1.0/src/vinted_bot/web/static/img/icons/icon-512.png +0 -0
  118. vinted_bot-2.1.0/src/vinted_bot/web/static/img/icons/icon-maskable-512.png +0 -0
  119. vinted_bot-2.1.0/src/vinted_bot/web/static/img/mags/mags-alert.webp +0 -0
  120. vinted_bot-2.1.0/src/vinted_bot/web/static/img/mags/mags-cheer.webp +0 -0
  121. vinted_bot-2.1.0/src/vinted_bot/web/static/img/mags/mags-find.webp +0 -0
  122. vinted_bot-2.1.0/src/vinted_bot/web/static/img/mags/mags-head.webp +0 -0
  123. vinted_bot-2.1.0/src/vinted_bot/web/static/img/mags/mags-parcel.webp +0 -0
  124. vinted_bot-2.1.0/src/vinted_bot/web/static/img/mags/mags-point.webp +0 -0
  125. vinted_bot-2.1.0/src/vinted_bot/web/static/img/mags/mags-sleep.webp +0 -0
  126. vinted_bot-2.1.0/src/vinted_bot/web/static/img/mags/mags-think.webp +0 -0
  127. vinted_bot-2.1.0/src/vinted_bot/web/static/img/mags/mags-watch.webp +0 -0
  128. vinted_bot-2.1.0/src/vinted_bot/web/static/img/mags/mags-wave.webp +0 -0
  129. vinted_bot-2.1.0/src/vinted_bot/web/static/img/mags/mags-worried.webp +0 -0
  130. vinted_bot-2.1.0/src/vinted_bot/web/static/js/app.js +976 -0
  131. vinted_bot-2.1.0/src/vinted_bot/web/static/js/qrcode.js +2297 -0
  132. vinted_bot-2.1.0/src/vinted_bot/web/support.py +73 -0
  133. vinted_bot-2.1.0/src/vinted_bot/web/templates/alerts.html +131 -0
  134. vinted_bot-2.1.0/src/vinted_bot/web/templates/bare.html +14 -0
  135. vinted_bot-2.1.0/src/vinted_bot/web/templates/base.html +110 -0
  136. vinted_bot-2.1.0/src/vinted_bot/web/templates/error.html +12 -0
  137. vinted_bot-2.1.0/src/vinted_bot/web/templates/index.html +184 -0
  138. vinted_bot-2.1.0/src/vinted_bot/web/templates/login.html +32 -0
  139. vinted_bot-2.1.0/src/vinted_bot/web/templates/ob_base.html +54 -0
  140. vinted_bot-2.1.0/src/vinted_bot/web/templates/partials/banners.html +57 -0
  141. vinted_bot-2.1.0/src/vinted_bot/web/templates/partials/card.html +1 -0
  142. vinted_bot-2.1.0/src/vinted_bot/web/templates/partials/dest_forms.html +109 -0
  143. vinted_bot-2.1.0/src/vinted_bot/web/templates/partials/head.html +14 -0
  144. vinted_bot-2.1.0/src/vinted_bot/web/templates/partials/icons.svg +109 -0
  145. vinted_bot-2.1.0/src/vinted_bot/web/templates/partials/macros.html +87 -0
  146. vinted_bot-2.1.0/src/vinted_bot/web/templates/partials/ob_back.html +1 -0
  147. vinted_bot-2.1.0/src/vinted_bot/web/templates/partials/support.html +94 -0
  148. vinted_bot-2.1.0/src/vinted_bot/web/templates/search-detail.html +103 -0
  149. vinted_bot-2.1.0/src/vinted_bot/web/templates/search-edit.html +319 -0
  150. vinted_bot-2.1.0/src/vinted_bot/web/templates/searches.html +158 -0
  151. vinted_bot-2.1.0/src/vinted_bot/web/templates/settings.html +183 -0
  152. vinted_bot-2.1.0/src/vinted_bot/web/templates/stats.html +118 -0
  153. vinted_bot-2.1.0/src/vinted_bot/web/templates/watchlist.html +80 -0
  154. vinted_bot-2.1.0/src/vinted_bot/web/templates/welcome-1.html +36 -0
  155. vinted_bot-2.1.0/src/vinted_bot/web/templates/welcome-2.html +48 -0
  156. vinted_bot-2.1.0/src/vinted_bot/web/templates/welcome-3.html +85 -0
  157. vinted_bot-2.1.0/src/vinted_bot/web/templates/welcome-4.html +51 -0
  158. vinted_bot-2.1.0/src/vinted_bot/web/templates/welcome-5.html +48 -0
  159. vinted_bot-2.1.0/src/vinted_bot/web/templates/welcome-6.html +37 -0
  160. vinted_bot-2.1.0/src/vinted_bot/web/views.py +615 -0
  161. vinted_bot-2.1.0/tests/__init__.py +0 -0
  162. vinted_bot-2.1.0/tests/conftest.py +149 -0
  163. vinted_bot-2.1.0/tests/fixtures/vinted/catalogue_fr.json +982 -0
  164. vinted_bot-2.1.0/tests/fixtures/vinted/item_available_fr.html +4 -0
  165. vinted_bot-2.1.0/tests/fixtures/vinted/item_sold_nl.html +22 -0
  166. vinted_bot-2.1.0/tests/fixtures/vinted/user_fr.json +23 -0
  167. vinted_bot-2.1.0/tests/fixtures/vinted/wardrobe_fr.json +754 -0
  168. vinted_bot-2.1.0/tests/integration/__init__.py +0 -0
  169. vinted_bot-2.1.0/tests/integration/test_alert_tools.py +386 -0
  170. vinted_bot-2.1.0/tests/integration/test_app_end_to_end.py +245 -0
  171. vinted_bot-2.1.0/tests/integration/test_cli.py +116 -0
  172. vinted_bot-2.1.0/tests/integration/test_dashboard_usability.py +399 -0
  173. vinted_bot-2.1.0/tests/integration/test_delivery.py +315 -0
  174. vinted_bot-2.1.0/tests/integration/test_heartbeat_and_updates.py +118 -0
  175. vinted_bot-2.1.0/tests/integration/test_poller.py +402 -0
  176. vinted_bot-2.1.0/tests/integration/test_poller_rules.py +471 -0
  177. vinted_bot-2.1.0/tests/integration/test_repo.py +658 -0
  178. vinted_bot-2.1.0/tests/integration/test_self_update.py +535 -0
  179. vinted_bot-2.1.0/tests/integration/test_telegram_bot.py +553 -0
  180. vinted_bot-2.1.0/tests/integration/test_watchdog_and_storage.py +266 -0
  181. vinted_bot-2.1.0/tests/integration/test_watcher.py +228 -0
  182. vinted_bot-2.1.0/tests/integration/test_web.py +1350 -0
  183. vinted_bot-2.1.0/tests/integration/test_web_updates.py +198 -0
  184. vinted_bot-2.1.0/tests/integration/test_what_counts_as_new.py +343 -0
  185. vinted_bot-2.1.0/tests/unit/__init__.py +0 -0
  186. vinted_bot-2.1.0/tests/unit/sending.py +258 -0
  187. vinted_bot-2.1.0/tests/unit/test_canary.py +63 -0
  188. vinted_bot-2.1.0/tests/unit/test_client.py +325 -0
  189. vinted_bot-2.1.0/tests/unit/test_config.py +109 -0
  190. vinted_bot-2.1.0/tests/unit/test_deals.py +60 -0
  191. vinted_bot-2.1.0/tests/unit/test_dedup_and_filters.py +208 -0
  192. vinted_bot-2.1.0/tests/unit/test_discord.py +667 -0
  193. vinted_bot-2.1.0/tests/unit/test_docs_match_code.py +68 -0
  194. vinted_bot-2.1.0/tests/unit/test_filters.py +232 -0
  195. vinted_bot-2.1.0/tests/unit/test_install_detection.py +195 -0
  196. vinted_bot-2.1.0/tests/unit/test_item_page.py +116 -0
  197. vinted_bot-2.1.0/tests/unit/test_migrations.py +202 -0
  198. vinted_bot-2.1.0/tests/unit/test_models.py +277 -0
  199. vinted_bot-2.1.0/tests/unit/test_other_senders.py +436 -0
  200. vinted_bot-2.1.0/tests/unit/test_proxies.py +125 -0
  201. vinted_bot-2.1.0/tests/unit/test_quiet.py +36 -0
  202. vinted_bot-2.1.0/tests/unit/test_ratelimit.py +39 -0
  203. vinted_bot-2.1.0/tests/unit/test_render.py +301 -0
  204. vinted_bot-2.1.0/tests/unit/test_session.py +74 -0
  205. vinted_bot-2.1.0/tests/unit/test_support.py +55 -0
  206. vinted_bot-2.1.0/tests/unit/test_taxonomy.py +298 -0
  207. vinted_bot-2.1.0/tests/unit/test_telegram.py +468 -0
  208. vinted_bot-2.1.0/tests/unit/test_transport.py +108 -0
  209. vinted_bot-2.1.0/tests/unit/test_urls.py +228 -0
  210. vinted_bot-2.1.0/tests/unit/test_validation.py +118 -0
  211. vinted_bot-2.1.0/uv.lock +2050 -0
@@ -0,0 +1,14 @@
1
+ .venv/
2
+ .git/
3
+ .github/
4
+ data/
5
+ docs/
6
+ tests/
7
+ *.md
8
+ !README.md
9
+ .env
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ .pytest_cache/
13
+ __pycache__/
14
+ *.db
@@ -0,0 +1,126 @@
1
+ # You don't have to change anything in this file to get started.
2
+ # Searches and Discord webhooks are set up in the dashboard at http://localhost:8000
3
+ # (or with the CLI) — not here. Every line below is optional and shows its default.
4
+ # Full reference: docs/configuration.md
5
+
6
+ # --- Telegram (only if you want alerts there) ----------------------------------------
7
+ # Talk to @BotFather on Telegram to create a bot; it gives you a token like
8
+ # 123456:AAxxxx. Setting this turns on Telegram delivery and the pairing bot.
9
+ # VINTED_BOT_TELEGRAM_BOT_TOKEN=
10
+
11
+ # --- Web dashboard -------------------------------------------------------------------
12
+ # On by default at http://localhost:8000, no password. That is safe because only your
13
+ # own machine can reach it. Set a token before exposing it any further: anyone who can
14
+ # open the dashboard can add, remove and reroute your alerts.
15
+ # Generate one with: openssl rand -hex 32
16
+ # VINTED_BOT_WEB_AUTH_TOKEN=
17
+
18
+ # Set to false if you only use the CLI.
19
+ # VINTED_BOT_WEB_ENABLED=true
20
+
21
+ # Localhost only. The container sets 0.0.0.0 internally; the compose file is what limits
22
+ # who can reach the published port.
23
+ # VINTED_BOT_WEB_HOST=127.0.0.1
24
+ # VINTED_BOT_WEB_PORT=8000
25
+
26
+ # The address the dashboard is reachable at from wherever you read your alerts — set it
27
+ # when the dashboard sits behind a reverse proxy or a tunnel. It becomes the Dashboard
28
+ # link in Discord messages; unset, that link points at http://<host>:<port> above.
29
+ # VINTED_BOT_WEB_PUBLIC_URL=
30
+
31
+ # Extra host names the dashboard answers to, comma-separated (e.g. nas.local). It always
32
+ # answers to localhost, any IP address and the host of WEB_PUBLIC_URL; other names get a
33
+ # 400, which stops other websites using it through your browser (DNS rebinding).
34
+ # VINTED_BOT_WEB_ALLOWED_HOSTS=
35
+
36
+
37
+ # ======================================================================================
38
+ # Everything below is tuning. The defaults are deliberate; skip this on a first read.
39
+ # ======================================================================================
40
+
41
+ # --- How often to check --------------------------------------------------------------
42
+ # Seconds between checks for a new search. Per-search overrides live in the app itself.
43
+ # Below 10 is ignored: Vinted's own catalog lags further behind than that, so checking
44
+ # faster finds nothing sooner and does get you blocked.
45
+ # VINTED_BOT_POLL_DEFAULT_INTERVAL_S=60
46
+
47
+ # How old a listing may be and still count as new, in minutes. Listings the app can tell
48
+ # were already on Vinted longer ago are skipped, and after a gap in checking longer than
49
+ # this (the app off, the computer asleep) the first check records what it missed without
50
+ # announcing it, so a restart never replays yesterday's catalog.
51
+ # VINTED_BOT_FRESHNESS_WINDOW_MIN=20
52
+
53
+ # What a brand-new search does on its first check:
54
+ # silent notify nothing, just remember what is already there (default)
55
+ # newest send exactly one listing, so you can confirm delivery works
56
+ # VINTED_BOT_FIRST_RUN_MODE=silent
57
+
58
+
59
+ # --- Staying unblocked ---------------------------------------------------------------
60
+ # Start a fresh session after this many minutes. Blocks track how long a session has been
61
+ # alive more than how fast it is used, so replacing them on a timer helps.
62
+ # VINTED_BOT_SESSION_ROTATE_MINUTES=60
63
+
64
+ # Present a real browser's TLS fingerprint instead of Python's. Only worth turning on if
65
+ # you are getting blocked while the same search loads fine in your browser. Needs the
66
+ # image built with the 'impersonate' extra.
67
+ # VINTED_BOT_HTTP_IMPERSONATE=false
68
+
69
+ # A text file of proxy URLs, one per line. Off by default and rarely needed.
70
+ # VINTED_BOT_PROXY_FILE=
71
+
72
+ # How long to wait for Vinted before giving up on a single request.
73
+ # VINTED_BOT_REQUEST_TIMEOUT_S=15
74
+
75
+
76
+ # --- Noticing when something is wrong -------------------------------------------------
77
+ # How many checks with no new listing before a search is treated as stuck. It is only
78
+ # treated as stuck if other searches on the same site are still finding things.
79
+ # VINTED_BOT_WATCHDOG_STALE_CYCLES=10
80
+
81
+ # What to do about it: warn, or also start a fresh session (default).
82
+ # VINTED_BOT_WATCHDOG_ACTION=rotate
83
+
84
+ # Give up on an undelivered notification after this long. An alert about a listing from
85
+ # two hours ago is not worth sending.
86
+ # VINTED_BOT_OUTBOX_EXPIRY_MINUTES=60
87
+
88
+
89
+ # --- Where things are stored ----------------------------------------------------------
90
+ # Inside Docker this is already /data/app.db; leave it alone unless you know why.
91
+ # Unset outside Docker: ./data/app.db if that file already exists (so upgrades keep your
92
+ # searches), otherwise your user data folder, e.g. ~/.local/share/vinted-bot/app.db.
93
+ # VINTED_BOT_DB_PATH=./data/app.db
94
+
95
+ # Delete stored listings after this many days. Does not cause anything to be re-sent.
96
+ # VINTED_BOT_ITEM_RETENTION_DAYS=30
97
+
98
+ # Keep each listing's raw API payload. Useful when debugging, but it stores more seller
99
+ # data than notifications need, so it is off by default.
100
+ # VINTED_BOT_KEEP_RAW_JSON=false
101
+
102
+
103
+ # --- Time ------------------------------------------------------------------------------
104
+ # The time zone quiet hours and the stats page use. TZ is used if this is unset; the
105
+ # dashboard's settings page can change it later.
106
+ # VINTED_BOT_TIMEZONE=UTC
107
+
108
+
109
+ # --- Updating --------------------------------------------------------------------------
110
+ # Set by the installers' start-at-login entries (launchd, systemd or windows-launcher) to
111
+ # say something restarts the app after it exits, so updating from the dashboard can
112
+ # restart it too. Only set it for your own service if that service restarts after any exit.
113
+ # VINTED_BOT_SUPERVISED=
114
+
115
+
116
+ # --- Logging ---------------------------------------------------------------------------
117
+ # VINTED_BOT_LOG_LEVEL=INFO
118
+ # console while you are watching, json when something else is collecting it.
119
+ # VINTED_BOT_LOG_FORMAT=console
120
+
121
+
122
+ # --- Working on the code ----------------------------------------------------------------
123
+ # 'mock' replays recorded responses from disk instead of calling Vinted, so you can
124
+ # develop and demo offline. Requires a scenario directory.
125
+ # VINTED_BOT_FETCH_MODE=live
126
+ # VINTED_BOT_MOCK_SCENARIO_DIR=
@@ -0,0 +1,23 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+ .coverage
8
+ htmlcov/
9
+ dist/
10
+ build/
11
+ *.egg-info/
12
+
13
+ # Runtime data and secrets — never commit these.
14
+ .env
15
+ data/
16
+ *.db
17
+ *.db-wal
18
+ *.db-shm
19
+
20
+ .DS_Store
21
+
22
+ # Landing site build output (python3 site/build.py)
23
+ site/dist/
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,182 @@
1
+ # Changelog
2
+
3
+ Every release, newest first. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
4
+ and versions follow [Semantic Versioning](https://semver.org/). Each version's section here
5
+ becomes the text of its GitHub release, so write it for the people running the app.
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [2.1.0] - 2026-10-30
10
+
11
+ ### Changed
12
+
13
+ - **Renamed to vinted-bot** (from vinted-sniper), the name people search for. Everything
14
+ uses it now: the `vinted-bot` command, the PyPI package `vinted-bot`, the Docker image
15
+ `ghcr.io/jasp-nerd/vinted-bot`, the repository github.com/jasp-nerd/vinted-bot (old links
16
+ redirect), the website vinted-bot.jaspnerd.dev, settings (`VINTED_BOT_...`), the data folder
17
+ (`vinted-bot`), the installers' start-at-login entries, the Docker volume
18
+ (`vinted-bot-data`), the default Discord name and the webhook headers
19
+ (`X-Vinted-Bot-Signature`, `X-Vinted-Bot-Timestamp`). The old names are not read any more,
20
+ so this is a fresh install: install vinted-bot with the commands in the README, uninstall
21
+ the old copy, and rename any settings in your `.env`. To keep your searches, copy the old
22
+ data folder's `app.db` into the new one before the first start.
23
+ - The installers' start-at-login entries now tell the app they restart it
24
+ (`VINTED_BOT_SUPERVISED`), and the Windows launcher stays running in the background so it
25
+ can install an update while the app is closed.
26
+
27
+ ### Added
28
+
29
+ - **Update from the dashboard.** Where the installer set vinted-bot up to start by itself,
30
+ "Update now" installs the new version and restarts into it, and "Install updates by
31
+ itself" (off unless you turn it on) does that between 4 and 5 at night, never while an
32
+ alert is on its way out, then says so in your status alerts. Installed with uv or pipx but
33
+ started by hand, the button installs it and tells you how to restart. Docker and source
34
+ installs are shown the command to run. Updates only ever come from PyPI through uv or
35
+ pipx, and only when GitHub has a newer release; a failed update leaves the running version
36
+ alone and says what went wrong.
37
+
38
+ ### Removed
39
+
40
+ - The empty `web` extra, kept in 2.0 only so 1.x install commands still worked. The
41
+ dashboard is part of every install.
42
+
43
+ ## [2.0.0] - 2026-10-30
44
+
45
+ Vinted moved its search to a new address in September 2026, and every earlier version stopped
46
+ finding listings. This release works again, and comes with a rebuilt dashboard, many more
47
+ filters, new-look alerts and one-line installers for Windows, macOS and Linux. Your searches
48
+ and destinations carry over: the database upgrades itself on the first start.
49
+
50
+ ### Fixed
51
+
52
+ - Searches work again after Vinted's September 2026 move to a new search address
53
+ ([#9](https://github.com/jasp-nerd/vinted-bot/issues/9),
54
+ [#12](https://github.com/jasp-nerd/vinted-bot/issues/12),
55
+ [#14](https://github.com/jasp-nerd/vinted-bot/issues/14)). Filters saved before the move
56
+ keep working, and brand, size and condition show up in alerts again.
57
+ - The search builder's filter options load again.
58
+ - A listing that turns up in the results after a newer one was announced is no longer
59
+ skipped forever. New listings are now recognised by their id, since Vinted's search stopped
60
+ saying when a photo was taken.
61
+ - Excluding a word only matches whole words, ignoring accents and capitals: excluding "art"
62
+ no longer drops "smartphone".
63
+ - ntfy alerts with accents or a euro sign in the title no longer fail to send.
64
+ - A listing title containing brackets or HTML can no longer turn into a link in ntfy, or load
65
+ an image in an Apprise email.
66
+ - Prices read back as "15.00" instead of "15.0", and "12,50" means the same thing in the
67
+ dashboard, the command line and the Telegram bot.
68
+ - A database upgrade that fails part-way no longer leaves the database half changed.
69
+ - A locked database no longer stops the whole app from its heartbeat.
70
+ - A search that has failed since it was added shows as failing, not as starting.
71
+ - Restarting no longer fires every search in the same second.
72
+ - Starting again after the app was off, the computer asleep or Vinted refusing for a while no
73
+ longer announces a whole page of old listings as new. The first check back records what it
74
+ missed quietly. The same goes for the first check after upgrading from 1.x, which now
75
+ carries on from the newest listing 1.x had seen.
76
+ - "Ignore listings older than" (and `watch --max-age`, and `FRESHNESS_WINDOW_MIN`) works
77
+ again: it skips listings the app can tell were already on Vinted that long ago, such as an
78
+ old listing whose price was just cut into range.
79
+ - Changing a search's link or sites starts it over quietly, instead of announcing everything
80
+ above where the old search had got to, and its typical price starts again too.
81
+ - A listing still for sale after the retention period (30 days) is no longer announced again,
82
+ by a search or by a seller watch.
83
+ - A listing two searches match is judged by each search's own typical price, so a narrow
84
+ search's steal is no longer reported as overpriced because a broader search found it
85
+ first.
86
+ - When Vinted refuses the homepage itself, a search backs off like for any other refusal
87
+ instead of retrying every 15 seconds. A search that crashes is restarted with growing
88
+ pauses, and tokens that keep being rejected are no longer retried every five seconds.
89
+ - When Vinted refuses or rate-limits seller lookups, the app stops asking for a while
90
+ (honouring `Retry-After`), and the listings waiting on one are listed in "Not sent" as
91
+ "seller unknown".
92
+ - Alerts a destination did not want (for example a normal listing for a steals-only
93
+ channel) no longer count as failed deliveries on the alerts and stats pages.
94
+ - An open dashboard tab no longer misses finds after a quiet month emptied the stored
95
+ listings.
96
+ - Watch alerts sent to a webhook are posted one watch at a time, so `search` names the right
97
+ watch.
98
+ - **Security:** the dashboard only answers to localhost, IP addresses, the `WEB_PUBLIC_URL`
99
+ host and the new `WEB_ALLOWED_HOSTS`, however it is bound. Before, the Docker image's
100
+ `0.0.0.0` bind switched that check off, which let a web page use a password-less
101
+ dashboard through your browser (DNS rebinding). If you reach the dashboard by a name, add
102
+ it to `VINTED_SNIPER_WEB_ALLOWED_HOSTS`; the error page says so too.
103
+ - **Security:** a search name can no longer run script in the stats page's chart tooltips.
104
+ - **Security:** in a CSV export, a cell that would start a spreadsheet formula (a title such
105
+ as `=HYPERLINK(...)`) is prefixed with an apostrophe so it shows as text.
106
+ - **Security:** signing in to the dashboard gives each browser its own random session, which
107
+ signing out revokes, and changing the password signs everyone out. After five wrong
108
+ passwords from one address, sign-in waits 15 minutes.
109
+
110
+ ### Added
111
+
112
+ - **A rebuilt dashboard**: a guided setup on first run that ends with a test alert, a live
113
+ feed of finds laid out like Vinted's own grid (save, hide, block the seller), a plain-words
114
+ status for every search, a "Not sent" list showing why each skipped listing was skipped, a
115
+ watchlist, stats, settings, dark mode and a phone layout.
116
+ - **More filters**: words the title must contain (with alternatives such as `501|505`), a title
117
+ pattern, a minimum price, seller rating, number of reviews and country, and a seller
118
+ blocklist.
119
+ - **Deals**: each search learns its typical price, alerts show how far under it a listing is,
120
+ and a search or a destination can be set to deals only.
121
+ - **Quiet hours** per search, in your time zone: hold alerts until the end, drop them, or send
122
+ them without a sound.
123
+ - One search can cover several country sites.
124
+ - **Watches**: hear when a listing's price drops or it is reserved, sold or removed, and when
125
+ a seller uploads something new.
126
+ - **Discord alerts** as cards with a photo gallery, the total price, a deal badge and Buy,
127
+ Message and Open buttons, in the search's colour. Up to three share a message, and bigger bursts become a
128
+ compact digest. Mentions are opt-in.
129
+ - **Telegram alerts** as photo cards with buttons, an optional gallery style, and a digest in
130
+ busy groups. A paired chat can `/list`, `/pause`, `/resume`, `/add` and `/remove` searches
131
+ and ask for `/status`.
132
+ - Alerts are written in the language of the listing's site (English, French, German, Dutch,
133
+ Spanish, Italian, Polish, Lithuanian and Czech) with prices formatted to match.
134
+ - ntfy alerts gain the photo, action buttons and a higher priority for deals.
135
+ - **Apprise**, an optional extra, for email, Slack, Pushover, Gotify, Matrix and many more.
136
+ - Webhook payloads can be signed with a shared secret (HMAC-SHA256).
137
+ - A test button for every destination, and a preview of what its alerts will look like.
138
+ - Export found listings as CSV or JSON, from the dashboard or `vinted-sniper export`.
139
+ - A notice in the dashboard when a newer version is out (one request to GitHub a day, can be
140
+ turned off), and an optional uptime ping to a service such as healthchecks.io.
141
+ - `vinted-sniper run --open` opens the dashboard once it is ready.
142
+ - One-line installers for Windows (`install.ps1`) and macOS and Linux (`install.sh`), with
143
+ optional start at login, no Docker and no admin rights.
144
+ - Docker: `PUID` and `PGID` to choose who owns the data folder, so NAS shares and cloud
145
+ volumes owned by root work without manual fixes, a `TZ` setting, and an opt-in automatic
146
+ updater.
147
+ - Templates for Unraid, Umbrel, CasaOS, Render and Coolify in `deploy/`.
148
+ - `llms.txt`, a setup guide an AI assistant can follow to walk someone through installing,
149
+ and a ready-made prompt in the README.
150
+ - Issue forms that ask for the version, the status output and the log.
151
+
152
+ ### Changed
153
+
154
+ - The `latest` image tag now means the newest release. Builds of the main branch are tagged
155
+ `edge`.
156
+ - The dashboard is part of every install. `vinted-sniper[web]` still works but is no longer
157
+ needed.
158
+ - Outside a container, the database lives in your user data folder (unless `./data/app.db`
159
+ already exists, which keeps being used).
160
+ - A search you haven't routed anywhere goes to every active destination, including ones added
161
+ later.
162
+ - The same Vinted link can be added twice with different filters, for example to send a
163
+ stricter copy somewhere else.
164
+ - The webhook payload is version 2: every version 1 field is still there, with the alert
165
+ kind, deal, photos and seller details added beside them. Each POST holds one search's
166
+ listings, or one watch's. The fields and the signature headers are documented in
167
+ docs/configuration.md.
168
+ - **RSS feeds of a password-protected dashboard need the new feed key** instead of the
169
+ password: `?key=<your password>` no longer works, because feed addresses end up in reader
170
+ settings and proxy logs. Copy the new address from the "RSS feed of this search" link on
171
+ the search's page.
172
+
173
+ ## [0.1.0] - 2026-08-16
174
+
175
+ First version, never tagged as a release: polling with a stale-catalog watchdog, filters on
176
+ the total price including buyer protection, Discord, Telegram, ntfy, RSS and webhook
177
+ delivery through a reliable outbox, and a web dashboard.
178
+
179
+ [Unreleased]: https://github.com/jasp-nerd/vinted-bot/compare/v2.1.0...HEAD
180
+ [2.1.0]: https://github.com/jasp-nerd/vinted-bot/compare/v2.0.0...v2.1.0
181
+ [2.0.0]: https://github.com/jasp-nerd/vinted-bot/compare/v0.1.0...v2.0.0
182
+ [0.1.0]: https://github.com/jasp-nerd/vinted-bot/commits/v0.1.0
@@ -0,0 +1,148 @@
1
+ # Contributing
2
+
3
+ Bug reports with logs attached are the most useful thing anyone can send. For code, read on.
4
+
5
+ ## Setting up
6
+
7
+ You need [uv](https://docs.astral.sh/uv/). It installs the right Python by itself.
8
+
9
+ ```bash
10
+ git clone https://github.com/jasp-nerd/vinted-bot && cd vinted-bot
11
+ uv sync --all-extras
12
+ uv run vinted-bot run --open
13
+ ```
14
+
15
+ The test suite never contacts Vinted, so it runs anywhere.
16
+ [docs/architecture.md](docs/architecture.md) explains how the pieces fit together.
17
+
18
+ ### A dashboard with something in it
19
+
20
+ For screenshots, the public demo or working on the dashboard, `scripts/seed_demo.py` fills
21
+ a fresh database with invented searches, a month of finds, destinations and delivery
22
+ history. Its photos are the AI-generated ones hosted with the project site
23
+ (`scripts/demo/catalog.json` lists them), so nothing points at a real listing.
24
+
25
+ ```bash
26
+ uv run python scripts/seed_demo.py /tmp/demo.db
27
+ VINTED_BOT_DB_PATH=/tmp/demo.db VINTED_BOT_FETCH_MODE=mock \
28
+ VINTED_BOT_MOCK_SCENARIO_DIR=scripts/demo/mock uv run vinted-bot run --open
29
+ ```
30
+
31
+ The mock scenario answers every search with an empty page, so this never talks to Vinted
32
+ and never sends an alert. Leave out the two mock variables and the demo searches check
33
+ Vinted for real, adding real finds next to the invented ones. Even then nothing already
34
+ on Vinted is announced, and the demo's places for alerts reach nobody (a random ntfy
35
+ name, a switched-off Discord webhook, a Telegram chat that doesn't exist). Run it from a
36
+ folder without your own `.env`, or it picks up your real bot token and settings.
37
+
38
+ The dashboard's templates live in `src/vinted_bot/web/templates` and its CSS and
39
+ script in `web/static`; they need no build step. Static files are served with a content
40
+ hash in the URL, so a reload always picks up a change.
41
+
42
+ ## Before you push
43
+
44
+ CI runs exactly these, and a pull request needs all of them green:
45
+
46
+ ```bash
47
+ uv run pytest
48
+ uv run ruff check .
49
+ uv run ruff format --check .
50
+ uv run mypy
51
+ uv run deptry src
52
+ uv run vulture
53
+ uv run --with zizmor zizmor .github/workflows # only if you touched a workflow
54
+ ```
55
+
56
+ If you changed `install.sh` or `docker/entrypoint.sh`, also run
57
+ `uvx --from shellcheck-py shellcheck -s sh install.sh docker/entrypoint.sh`. If you changed
58
+ the Dockerfile, `docker build .` and start the result once.
59
+
60
+ ## What a change is held to
61
+
62
+ [docs/REVIEW.md](docs/REVIEW.md) is the checklist every change is reviewed against, yours and
63
+ the maintainer's alike. The short version:
64
+
65
+ - Every new behaviour gets a test that would fail without it. Don't loosen a test to make it
66
+ pass.
67
+ - Match the code around you: docstrings that explain *why*, plain asyncio, SQL only in
68
+ `db/repo.py`, structlog events named like `poller.started`.
69
+ - No retry or fallback whose only job is to make a symptom go away.
70
+ - The README and docs never claim something the code doesn't do.
71
+ - Never log in to Vinted, and no features that need a Vinted account (buying, messaging,
72
+ offers). That is a deliberate line, not a missing feature.
73
+
74
+ ## Commit messages
75
+
76
+ One plain sentence in the imperative, saying what the change does for someone using or
77
+ reading the code:
78
+
79
+ ```
80
+ Retry Telegram sends that hit a rate limit after the delay Telegram asks for
81
+ ```
82
+
83
+ No prefixes (`feat:`, `fix(web):`), no ticket numbers in the subject, no trailers crediting
84
+ tools. Add a body when the *why* isn't obvious from the subject.
85
+
86
+ ## Pull requests
87
+
88
+ Keep each one about one thing. Say what changed and how you checked it. A new abstraction
89
+ (a base class, a registry, a new layer) needs one line explaining why the simpler version
90
+ wasn't enough.
91
+
92
+ User-facing changes get a line in `CHANGELOG.md` under `[Unreleased]`, written for the
93
+ people running the app.
94
+
95
+ ## Cutting a release
96
+
97
+ For the maintainer. A release is one button; the workflows do the rest.
98
+
99
+ 1. In a pull request: in `CHANGELOG.md`, rename `## [Unreleased]` to `## [X.Y.Z] - YYYY-MM-DD`,
100
+ add a fresh empty `## [Unreleased]` above it, and update the compare links at the bottom.
101
+ Set `version = "X.Y.Z"` in `pyproject.toml` and `__version__` in
102
+ `src/vinted_bot/__init__.py`, run `uv lock`, and merge it to main.
103
+ 2. On GitHub, open **Actions**, choose **Release** on the left, click **Run workflow**, leave
104
+ the branch on `main`, and click the green **Run workflow** button. There is nothing to
105
+ fill in.
106
+
107
+ The Release workflow then:
108
+
109
+ - reads the version from `pyproject.toml`, and stops without changing anything if
110
+ `CHANGELOG.md` has no `## [X.Y.Z]` section, if the tag `vX.Y.Z` already exists, or if it
111
+ was started on a branch other than main;
112
+ - creates the tag `vX.Y.Z` on that commit;
113
+ - builds the package and publishes it to PyPI;
114
+ - builds the Docker image for amd64 and arm64 and tags it `X.Y.Z`, `X.Y`, `X` and `latest`
115
+ (by calling **Publish image**);
116
+ - last, creates the GitHub release with this version's changelog section as its text and the
117
+ package files attached. The dashboard's update notice reads that release, so it only
118
+ appears once the package and the image are out.
119
+
120
+ If a check fails before the tag is made, fix it and click Run workflow again. If a later job
121
+ fails (PyPI or the registry having a bad moment), open that run and use **Re-run failed
122
+ jobs**: a new run would refuse, because the tag exists by then. If the fix needs a code
123
+ change, release it as the next version instead.
124
+
125
+ Pushing a tag by hand (`git tag -a vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z`) still works
126
+ and does the same, with the image built by **Publish image** on that push. Builds of main
127
+ keep going out as the `edge` image in between.
128
+
129
+ ### One-time PyPI setup
130
+
131
+ The Release workflow publishes with PyPI trusted publishing, so no token is stored anywhere.
132
+ Before the first release:
133
+
134
+ 1. On pypi.org, open Your account, **Publishing**, and under "Add a new pending publisher"
135
+ choose GitHub and fill in:
136
+
137
+ | Field | Value |
138
+ |---|---|
139
+ | PyPI Project Name | `vinted-bot` |
140
+ | Owner | `jasp-nerd` |
141
+ | Repository name | `vinted-bot` |
142
+ | Workflow name | `release.yml` |
143
+ | Environment name | `pypi` |
144
+
145
+ The first release creates the project and turns the pending publisher into a normal one.
146
+ 2. In the GitHub repository's **Settings**, **Environments**, create an environment named
147
+ `pypi`. Adding yourself as a required reviewer there means each PyPI upload waits for a
148
+ click.
@@ -0,0 +1,76 @@
1
+ # Build stage: resolve and install dependencies into a self-contained virtualenv.
2
+ # Both base images are pinned by digest so a rebuild of the same commit gets the same bytes;
3
+ # Dependabot moves the digests forward weekly.
4
+ FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim@sha256:e586c1c65b7071aec78f02c5587c76c5011159cd76b3d6f1255f40e48e850abc AS build
5
+
6
+ ENV UV_COMPILE_BYTECODE=1 \
7
+ UV_LINK_MODE=copy \
8
+ UV_PYTHON_DOWNLOADS=never
9
+
10
+ WORKDIR /app
11
+
12
+ # Dependencies change far less often than the code, so they get their own layer and their
13
+ # own cache mount. Editing a source file then costs a rebuild of the last layer only.
14
+ RUN --mount=type=cache,target=/root/.cache/uv \
15
+ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
16
+ --mount=type=bind,source=uv.lock,target=uv.lock \
17
+ --mount=type=bind,source=README.md,target=README.md \
18
+ uv sync --locked --no-install-project --no-dev
19
+
20
+ COPY . /app
21
+ RUN --mount=type=cache,target=/root/.cache/uv \
22
+ uv sync --locked --no-editable --no-dev
23
+
24
+
25
+ # Runtime stage: the virtualenv and the code, nothing else.
26
+ FROM python:3.13-slim-trixie@sha256:2d44738ebf4ce5f63b9d9ea871e9c7cfdc0426f7b0c97cac915fd88ded1d86bb
27
+
28
+ # Static so a local build carries them too; the publish workflow adds version and revision.
29
+ LABEL org.opencontainers.image.title="vinted-bot" \
30
+ org.opencontainers.image.description="Watch Vinted searches and get a message when a new listing matches." \
31
+ org.opencontainers.image.source="https://github.com/jasp-nerd/vinted-bot" \
32
+ org.opencontainers.image.url="https://github.com/jasp-nerd/vinted-bot" \
33
+ org.opencontainers.image.documentation="https://github.com/jasp-nerd/vinted-bot/blob/main/docs/self-hosting.md" \
34
+ org.opencontainers.image.licenses="MIT"
35
+
36
+ # curl is here for the health check and for the connection test in the troubleshooting
37
+ # guide, which is the first thing anyone reaches for when listings stop arriving.
38
+ RUN apt-get update \
39
+ && apt-get install -y --no-install-recommends curl \
40
+ && rm -rf /var/lib/apt/lists/* \
41
+ && groupadd --system --gid 10001 vinted \
42
+ && useradd --system --create-home --uid 10001 --gid 10001 vinted
43
+
44
+ WORKDIR /app
45
+ COPY --from=build --chown=vinted:vinted /app/.venv /app/.venv
46
+ COPY --from=build --chown=vinted:vinted /app/src /app/src
47
+
48
+ # The privilege-dropping wrapper from docker/entrypoint.sh. It sits ahead of the real
49
+ # command on PATH, so the health check and `docker compose exec ... vinted-bot status`
50
+ # also run as the app's user rather than as root.
51
+ COPY --chmod=755 docker/entrypoint.sh /app/bin/vinted-bot
52
+
53
+ # The web host is 0.0.0.0 *inside the container* so the published port can reach it;
54
+ # docker-compose.yml maps it to the host's localhost only.
55
+ ENV PATH="/app/bin:/app/.venv/bin:$PATH" \
56
+ PYTHONUNBUFFERED=1 \
57
+ VINTED_BOT_DB_PATH=/data/app.db \
58
+ VINTED_BOT_LOG_FORMAT=json \
59
+ VINTED_BOT_WEB_HOST=0.0.0.0
60
+
61
+ RUN mkdir -p /data && chown vinted:vinted /data
62
+ VOLUME ["/data"]
63
+
64
+ # No USER line on purpose: the container starts as root only long enough for the wrapper
65
+ # to hand /data to PUID:PGID (default 10001, the `vinted` user), then the app runs as that
66
+ # user. Anyone who wants no root at all can set `user: "10001:10001"` instead.
67
+
68
+ EXPOSE 8000
69
+
70
+ # Checks that the app came round the loop recently, not merely that a process exists —
71
+ # a poller can be alive and stuck, which is the failure worth catching.
72
+ HEALTHCHECK --interval=60s --timeout=10s --start-period=45s --retries=3 \
73
+ CMD ["vinted-bot", "heartbeat"]
74
+
75
+ ENTRYPOINT ["vinted-bot"]
76
+ CMD ["run"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vinted-bot contributors
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.