steam-idle-bot 1.0.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 (120) hide show
  1. steam_idle_bot-1.0.0/.coveragerc +10 -0
  2. steam_idle_bot-1.0.0/.env.example +66 -0
  3. steam_idle_bot-1.0.0/.githooks/pre-commit +20 -0
  4. steam_idle_bot-1.0.0/.github/FUNDING.yml +3 -0
  5. steam_idle_bot-1.0.0/.github/dependabot.yml +14 -0
  6. steam_idle_bot-1.0.0/.github/workflows/ci.yml +77 -0
  7. steam_idle_bot-1.0.0/.github/workflows/release.yml +206 -0
  8. steam_idle_bot-1.0.0/.github/workflows/static.yml +86 -0
  9. steam_idle_bot-1.0.0/.gitignore +29 -0
  10. steam_idle_bot-1.0.0/.python-version +1 -0
  11. steam_idle_bot-1.0.0/BACKLOG.md +159 -0
  12. steam_idle_bot-1.0.0/CHANGELOG.md +149 -0
  13. steam_idle_bot-1.0.0/CLAUDE.md +107 -0
  14. steam_idle_bot-1.0.0/COMMIT_NOTES.md +180 -0
  15. steam_idle_bot-1.0.0/LICENSE +21 -0
  16. steam_idle_bot-1.0.0/PKG-INFO +183 -0
  17. steam_idle_bot-1.0.0/README.md +153 -0
  18. steam_idle_bot-1.0.0/RELEASING.md +46 -0
  19. steam_idle_bot-1.0.0/SECURITY.md +41 -0
  20. steam_idle_bot-1.0.0/TODO.md +24 -0
  21. steam_idle_bot-1.0.0/docs/404.md +13 -0
  22. steam_idle_bot-1.0.0/docs/README.md +46 -0
  23. steam_idle_bot-1.0.0/docs/USAGE.md +66 -0
  24. steam_idle_bot-1.0.0/docs/_config.yml +28 -0
  25. steam_idle_bot-1.0.0/docs/_includes/footer.html +27 -0
  26. steam_idle_bot-1.0.0/docs/_includes/head.html +12 -0
  27. steam_idle_bot-1.0.0/docs/_includes/nav.html +14 -0
  28. steam_idle_bot-1.0.0/docs/_layouts/default.html +23 -0
  29. steam_idle_bot-1.0.0/docs/_layouts/home.html +111 -0
  30. steam_idle_bot-1.0.0/docs/assets/architecture.svg +105 -0
  31. steam_idle_bot-1.0.0/docs/assets/banner.svg +65 -0
  32. steam_idle_bot-1.0.0/docs/assets/css/style.css +196 -0
  33. steam_idle_bot-1.0.0/docs/assets/pipeline.svg +73 -0
  34. steam_idle_bot-1.0.0/docs/en/README.md +298 -0
  35. steam_idle_bot-1.0.0/docs/en/SECURITY.md +42 -0
  36. steam_idle_bot-1.0.0/docs/en/USAGE.md +96 -0
  37. steam_idle_bot-1.0.0/docs/idle-test-2026-06-23.md +97 -0
  38. steam_idle_bot-1.0.0/docs/index.md +5 -0
  39. steam_idle_bot-1.0.0/docs/pt-br/README.md +299 -0
  40. steam_idle_bot-1.0.0/docs/pt-br/SECURITY.md +42 -0
  41. steam_idle_bot-1.0.0/docs/pt-br/USAGE.md +96 -0
  42. steam_idle_bot-1.0.0/frontend/index.html +14 -0
  43. steam_idle_bot-1.0.0/frontend/package-lock.json +1402 -0
  44. steam_idle_bot-1.0.0/frontend/package.json +24 -0
  45. steam_idle_bot-1.0.0/frontend/src/App.tsx +107 -0
  46. steam_idle_bot-1.0.0/frontend/src/api.ts +120 -0
  47. steam_idle_bot-1.0.0/frontend/src/components/AuthDialog.tsx +52 -0
  48. steam_idle_bot-1.0.0/frontend/src/components/StatCard.tsx +31 -0
  49. steam_idle_bot-1.0.0/frontend/src/index.css +51 -0
  50. steam_idle_bot-1.0.0/frontend/src/main.tsx +10 -0
  51. steam_idle_bot-1.0.0/frontend/src/types.ts +33 -0
  52. steam_idle_bot-1.0.0/frontend/src/views/Dashboard.tsx +127 -0
  53. steam_idle_bot-1.0.0/frontend/src/views/LogsView.tsx +92 -0
  54. steam_idle_bot-1.0.0/frontend/src/views/ReportView.tsx +45 -0
  55. steam_idle_bot-1.0.0/frontend/src/views/SettingsView.tsx +249 -0
  56. steam_idle_bot-1.0.0/frontend/src/vite-env.d.ts +1 -0
  57. steam_idle_bot-1.0.0/frontend/tsconfig.json +20 -0
  58. steam_idle_bot-1.0.0/frontend/tsconfig.tsbuildinfo +1 -0
  59. steam_idle_bot-1.0.0/frontend/vite.config.ts +16 -0
  60. steam_idle_bot-1.0.0/pyproject.toml +79 -0
  61. steam_idle_bot-1.0.0/pytest.ini +8 -0
  62. steam_idle_bot-1.0.0/ruff.toml +6 -0
  63. steam_idle_bot-1.0.0/run-web.sh +34 -0
  64. steam_idle_bot-1.0.0/run.sh +155 -0
  65. steam_idle_bot-1.0.0/scripts/list_games.py +77 -0
  66. steam_idle_bot-1.0.0/src/steam_idle_bot/__init__.py +11 -0
  67. steam_idle_bot-1.0.0/src/steam_idle_bot/__main__.py +4 -0
  68. steam_idle_bot-1.0.0/src/steam_idle_bot/config/__init__.py +5 -0
  69. steam_idle_bot-1.0.0/src/steam_idle_bot/config/settings.py +482 -0
  70. steam_idle_bot-1.0.0/src/steam_idle_bot/main.py +958 -0
  71. steam_idle_bot-1.0.0/src/steam_idle_bot/steam/__init__.py +8 -0
  72. steam_idle_bot-1.0.0/src/steam_idle_bot/steam/badges.py +214 -0
  73. steam_idle_bot-1.0.0/src/steam_idle_bot/steam/browser_cookies.py +124 -0
  74. steam_idle_bot-1.0.0/src/steam_idle_bot/steam/card_drops.py +607 -0
  75. steam_idle_bot-1.0.0/src/steam_idle_bot/steam/client.py +480 -0
  76. steam_idle_bot-1.0.0/src/steam_idle_bot/steam/games.py +456 -0
  77. steam_idle_bot-1.0.0/src/steam_idle_bot/steam/inventory.py +157 -0
  78. steam_idle_bot-1.0.0/src/steam_idle_bot/steam/protocol.py +77 -0
  79. steam_idle_bot-1.0.0/src/steam_idle_bot/steam/steam_utility.py +430 -0
  80. steam_idle_bot-1.0.0/src/steam_idle_bot/steam/trading_cards.py +274 -0
  81. steam_idle_bot-1.0.0/src/steam_idle_bot/utils/__init__.py +28 -0
  82. steam_idle_bot-1.0.0/src/steam_idle_bot/utils/detailed_logger.py +154 -0
  83. steam_idle_bot-1.0.0/src/steam_idle_bot/utils/exceptions.py +77 -0
  84. steam_idle_bot-1.0.0/src/steam_idle_bot/utils/idle_tracker.py +368 -0
  85. steam_idle_bot-1.0.0/src/steam_idle_bot/utils/logger.py +86 -0
  86. steam_idle_bot-1.0.0/src/steam_idle_bot/utils/preflight.py +77 -0
  87. steam_idle_bot-1.0.0/src/steam_idle_bot/utils/redaction.py +15 -0
  88. steam_idle_bot-1.0.0/src/steam_idle_bot/webapi/__init__.py +6 -0
  89. steam_idle_bot-1.0.0/src/steam_idle_bot/webapi/controller.py +243 -0
  90. steam_idle_bot-1.0.0/src/steam_idle_bot/webapi/server.py +221 -0
  91. steam_idle_bot-1.0.0/tests/conftest.py +49 -0
  92. steam_idle_bot-1.0.0/tests/unit/test_badges.py +110 -0
  93. steam_idle_bot-1.0.0/tests/unit/test_badges_extra.py +145 -0
  94. steam_idle_bot-1.0.0/tests/unit/test_browser_cookies.py +74 -0
  95. steam_idle_bot-1.0.0/tests/unit/test_browser_cookies_extra.py +216 -0
  96. steam_idle_bot-1.0.0/tests/unit/test_card_drops.py +76 -0
  97. steam_idle_bot-1.0.0/tests/unit/test_card_drops_cache.py +206 -0
  98. steam_idle_bot-1.0.0/tests/unit/test_card_drops_extra.py +149 -0
  99. steam_idle_bot-1.0.0/tests/unit/test_client.py +484 -0
  100. steam_idle_bot-1.0.0/tests/unit/test_detailed_logger.py +113 -0
  101. steam_idle_bot-1.0.0/tests/unit/test_efficiency_caches.py +294 -0
  102. steam_idle_bot-1.0.0/tests/unit/test_games.py +301 -0
  103. steam_idle_bot-1.0.0/tests/unit/test_games_extra.py +284 -0
  104. steam_idle_bot-1.0.0/tests/unit/test_idle_tracker.py +194 -0
  105. steam_idle_bot-1.0.0/tests/unit/test_inventory.py +91 -0
  106. steam_idle_bot-1.0.0/tests/unit/test_main_cli.py +638 -0
  107. steam_idle_bot-1.0.0/tests/unit/test_main_extra.py +1028 -0
  108. steam_idle_bot-1.0.0/tests/unit/test_preflight.py +72 -0
  109. steam_idle_bot-1.0.0/tests/unit/test_redaction.py +29 -0
  110. steam_idle_bot-1.0.0/tests/unit/test_settings.py +237 -0
  111. steam_idle_bot-1.0.0/tests/unit/test_settings_extra.py +43 -0
  112. steam_idle_bot-1.0.0/tests/unit/test_signal_integration.py +81 -0
  113. steam_idle_bot-1.0.0/tests/unit/test_steam_idle_bot.py +252 -0
  114. steam_idle_bot-1.0.0/tests/unit/test_steam_utility.py +247 -0
  115. steam_idle_bot-1.0.0/tests/unit/test_steam_utility_extra.py +469 -0
  116. steam_idle_bot-1.0.0/tests/unit/test_trading_cards.py +166 -0
  117. steam_idle_bot-1.0.0/tests/unit/test_trading_cards_cache.py +41 -0
  118. steam_idle_bot-1.0.0/tests/unit/test_trading_cards_extra.py +135 -0
  119. steam_idle_bot-1.0.0/tests/unit/test_webapi.py +218 -0
  120. steam_idle_bot-1.0.0/uv.lock +1477 -0
@@ -0,0 +1,10 @@
1
+ [run]
2
+ branch = True
3
+ source = .
4
+ omit =
5
+ venv/*
6
+ tests/*
7
+
8
+ [report]
9
+ skip_covered = True
10
+ show_missing = True
@@ -0,0 +1,66 @@
1
+ # Steam Idle Bot configuration via environment variables
2
+ # Copy to .env and fill in your values. Do NOT commit the filled .env.
3
+
4
+ # Required credentials
5
+ USERNAME=your_steam_username
6
+ PASSWORD=your_steam_password
7
+
8
+ # Optional: Steam Web API key
9
+ STEAM_API_KEY=
10
+
11
+ # Optional: authenticated Steam browser cookies for community page scraping
12
+ # Use JSON, for example:
13
+ # STEAM_WEB_COOKIES={"steamLoginSecure":"...","sessionid":"...","steamCountry":"..."}
14
+ STEAM_WEB_COOKIES={}
15
+
16
+ # Game settings
17
+ # Accepts JSON list (recommended) or CSV list
18
+ GAME_APP_IDS=[570,730]
19
+ FILTER_TRADING_CARDS=true
20
+ FILTER_COMPLETED_CARD_DROPS=true
21
+ EXCLUDE_APP_IDS=[]
22
+ USE_OWNED_GAMES=true
23
+ MAX_GAMES_TO_IDLE=30
24
+ # Seconds between re-running the game-selection pipeline while idling (min 10)
25
+ REFRESH_INTERVAL_SECONDS=600
26
+ # Write a JSON/Markdown checkpoint every N minutes while idling (0 disables)
27
+ CHECKPOINT_MINUTES=0
28
+ # Stop idling after N minutes (0 runs until interrupted)
29
+ DURATION_MINUTES=0
30
+ # Re-scrape card counts this many seconds after stopping, to catch lagging badge updates (0 disables)
31
+ POST_RUN_VERIFY_SECONDS=0
32
+
33
+ # Idling backend
34
+ # python: current steam.client backend
35
+ # steam_utility: delegates idling to ../steam-utility-multiplataform (or STEAM_UTILITY_PATH)
36
+ IDLING_BACKEND=python
37
+ STEAM_UTILITY_PATH=
38
+
39
+ # Logging
40
+ LOG_LEVEL=INFO
41
+
42
+ # Performance tuning
43
+ API_TIMEOUT=10
44
+ # Base delay (seconds) between Steam API calls. Auto-increases on HTTP 429.
45
+ # Raise it (e.g. 2.0) for very large libraries to avoid throttling.
46
+ RATE_LIMIT_DELAY=1.0
47
+
48
+ # Trading-card cache
49
+ ENABLE_CARD_CACHE=true
50
+ CARD_CACHE_PATH=.cache/trading_cards.json
51
+ CARD_CACHE_TTL_DAYS=30
52
+ # No-drop cache: games confirmed without remaining card drops are skipped on
53
+ # future runs (a finished game never regains drops). Gated by ENABLE_CARD_CACHE.
54
+ DROP_CACHE_PATH=.cache/no_drop_cards.json
55
+ DROP_CACHE_TTL_DAYS=90
56
+
57
+ # Browser cookie recovery: if the configured STEAM_WEB_COOKIES are not a valid
58
+ # community session (e.g. a store-only/expired token), pull fresh cookies from a
59
+ # browser you're logged into Steam with. Set the browser or leave "auto".
60
+ AUTO_BROWSER_COOKIES=true
61
+ BROWSER_COOKIES_BROWSER=auto
62
+
63
+ # Filtering controls
64
+ # Optional: cap number of card checks; keep commented to disable
65
+ # MAX_CHECKS=100
66
+ SKIP_FAILURES=false
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # Prevent committing local secrets or environment-specific files
3
+
4
+ set -euo pipefail
5
+
6
+ # Block committing config.py explicitly
7
+ if git diff --cached --name-only | grep -qE '^config\.py$'; then
8
+ echo "Error: Refusing to commit config.py (contains secrets)." >&2
9
+ echo "Tip: Keep credentials in config.py (not tracked) or use env vars." >&2
10
+ exit 1
11
+ fi
12
+
13
+ # Basic sanity: prevent large accidentally added venv or cache
14
+ if git diff --cached --name-only | grep -qE '(^|/)__pycache__/|(^|/)venv/|(^|/)\.vscode/'; then
15
+ echo "Warning: You are staging cache/venv/.vscode files. Please unstage them." >&2
16
+ echo "Run: git restore --staged <paths>" >&2
17
+ exit 1
18
+ fi
19
+
20
+ exit 0
@@ -0,0 +1,3 @@
1
+ github: bernardopg
2
+ ko_fi: bernardopg
3
+ buy_me_a_coffee: wctwom9emu
@@ -0,0 +1,14 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ open-pull-requests-limit: 5
8
+ - package-ecosystem: "pip"
9
+ directory: "/"
10
+ schedule:
11
+ interval: "weekly"
12
+ open-pull-requests-limit: 5
13
+ allow:
14
+ - dependency-type: "all"
@@ -0,0 +1,77 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ # Principle of least privilege for GITHUB_TOKEN
10
+ permissions:
11
+ contents: read
12
+
13
+ # Cancel previous runs of the same ref to save minutes
14
+ concurrency:
15
+ group: ci-${{ github.workflow }}-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ jobs:
19
+ lint-and-test:
20
+ runs-on: ubuntu-latest
21
+ timeout-minutes: 30
22
+ strategy:
23
+ fail-fast: false
24
+ matrix:
25
+ python-version: ["3.12", "3.13", "3.14"]
26
+ steps:
27
+ - name: Checkout repository
28
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
29
+
30
+ - name: Set up UV
31
+ uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
32
+
33
+ - name: Set up Python
34
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
35
+ with:
36
+ python-version: ${{ matrix.python-version }}
37
+
38
+ - name: Cache UV
39
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
40
+ with:
41
+ path: ~/.cache/uv
42
+ key: ${{ runner.os }}-uv-${{ hashFiles('**/pyproject.toml', '**/uv.lock') }}
43
+ restore-keys: |
44
+ ${{ runner.os }}-uv-
45
+
46
+ - name: Install dependencies
47
+ run: uv sync --dev
48
+
49
+ - name: Lint (ruff)
50
+ run: |
51
+ uv run ruff check .
52
+ uv run ruff format --check .
53
+
54
+ - name: Type-check (mypy)
55
+ run: uv run mypy src
56
+
57
+ - name: Install xvfb
58
+ run: sudo apt-get update && sudo apt-get install -y xvfb
59
+
60
+ - name: Run tests (pytest + coverage)
61
+ env:
62
+ # Minimal credentials for Settings model in tests when needed
63
+ USERNAME: test_user
64
+ PASSWORD: test_pass
65
+ DISPLAY: ":99"
66
+ run: |
67
+ Xvfb :99 -screen 0 1024x768x24 &
68
+ sleep 1
69
+ uv run pytest -q --cov=src/steam_idle_bot --cov-report=xml --cov-fail-under=85
70
+
71
+ - name: Upload coverage report
72
+ if: always()
73
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
74
+ with:
75
+ name: coverage-xml-${{ matrix.python-version }}
76
+ path: coverage.xml
77
+ retention-days: 7
@@ -0,0 +1,206 @@
1
+ # Tag-triggered release: re-verifies the tagged commit, builds distributable
2
+ # artifacts, and publishes a GitHub Release with notes pulled straight from
3
+ # CHANGELOG.md. The maintainer prepares the release by hand (bump
4
+ # pyproject.toml, move [Unreleased] to a dated version section — see
5
+ # RELEASING.md) and pushes a `vX.Y.Z` tag; this workflow never mutates the
6
+ # repo, it only validates and ships what was already committed.
7
+ name: Release
8
+
9
+ on:
10
+ push:
11
+ tags:
12
+ - "v*.*.*"
13
+ workflow_dispatch:
14
+ inputs:
15
+ tag:
16
+ description: "Existing tag to (re-)release, e.g. v1.0.0"
17
+ required: true
18
+
19
+ # Least privilege by default; only the publish job elevates to contents:write.
20
+ permissions:
21
+ contents: read
22
+
23
+ concurrency:
24
+ group: release-${{ github.workflow }}-${{ github.ref }}
25
+ cancel-in-progress: false
26
+
27
+ jobs:
28
+ # ---- Re-verify the tagged commit before shipping anything from it ----
29
+ verify:
30
+ name: Verify version & test suite
31
+ runs-on: ubuntu-latest
32
+ timeout-minutes: 20
33
+ outputs:
34
+ version: ${{ steps.version.outputs.version }}
35
+ steps:
36
+ - name: Checkout repository
37
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
38
+ with:
39
+ ref: ${{ inputs.tag || github.ref_name }}
40
+
41
+ - name: Verify tag matches pyproject.toml version
42
+ id: version
43
+ run: |
44
+ set -euo pipefail
45
+ ref="${{ inputs.tag || github.ref_name }}"
46
+ tag_version="${ref#v}"
47
+ pyproject_version=$(grep -m1 '^version = ' pyproject.toml | sed -E 's/version = "(.*)"/\1/')
48
+ if [ "$tag_version" != "$pyproject_version" ]; then
49
+ echo "::error::Tag $ref (version $tag_version) does not match pyproject.toml version $pyproject_version"
50
+ exit 1
51
+ fi
52
+ echo "version=$pyproject_version" >> "$GITHUB_OUTPUT"
53
+
54
+ - name: Set up UV
55
+ uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
56
+
57
+ - name: Set up Python
58
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
59
+ with:
60
+ python-version: "3.12"
61
+
62
+ - name: Install dependencies
63
+ run: uv sync --dev
64
+
65
+ - name: Lint (ruff)
66
+ run: |
67
+ uv run ruff check .
68
+ uv run ruff format --check .
69
+
70
+ - name: Type-check (mypy)
71
+ run: uv run mypy src
72
+
73
+ - name: Install xvfb
74
+ run: sudo apt-get update && sudo apt-get install -y xvfb
75
+
76
+ - name: Run tests
77
+ env:
78
+ USERNAME: test_user
79
+ PASSWORD: test_pass
80
+ DISPLAY: ":99"
81
+ run: |
82
+ Xvfb :99 -screen 0 1024x768x24 &
83
+ sleep 1
84
+ uv run pytest -q --cov=src/steam_idle_bot --cov-fail-under=85
85
+
86
+ # ---- Build the artifacts attached to the release ----
87
+ build:
88
+ name: Build distribution artifacts
89
+ needs: verify
90
+ runs-on: ubuntu-latest
91
+ timeout-minutes: 15
92
+ steps:
93
+ - name: Checkout repository
94
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
95
+ with:
96
+ ref: ${{ inputs.tag || github.ref_name }}
97
+
98
+ - name: Set up UV
99
+ uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
100
+
101
+ - name: Build Python package (sdist + wheel)
102
+ run: uv build
103
+
104
+ - name: Set up Node
105
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
106
+ with:
107
+ node-version: "22"
108
+ cache: "npm"
109
+ cache-dependency-path: frontend/package-lock.json
110
+
111
+ - name: Build frontend
112
+ working-directory: frontend
113
+ run: |
114
+ npm ci
115
+ npm run build
116
+
117
+ - name: Package frontend build
118
+ run: |
119
+ set -euo pipefail
120
+ version="${{ needs.verify.outputs.version }}"
121
+ mkdir -p web-dist
122
+ (cd frontend/dist && zip -rq "../../web-dist/steam-idle-bot-web-${version}.zip" .)
123
+
124
+ - name: Upload build artifacts
125
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
126
+ with:
127
+ name: release-dist
128
+ # Kept in separate dirs so the PyPI publish job can point twine at
129
+ # dist/ alone — it chokes on non-package files like the zip.
130
+ path: |
131
+ dist/
132
+ web-dist/
133
+ retention-days: 7
134
+
135
+ # ---- Publish the GitHub Release with changelog-derived notes ----
136
+ publish:
137
+ name: Publish GitHub Release
138
+ needs: [verify, build]
139
+ runs-on: ubuntu-latest
140
+ timeout-minutes: 10
141
+ permissions:
142
+ contents: write
143
+ steps:
144
+ - name: Checkout repository
145
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
146
+ with:
147
+ ref: ${{ inputs.tag || github.ref_name }}
148
+
149
+ - name: Download build artifacts
150
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
151
+ with:
152
+ name: release-dist
153
+
154
+ - name: Extract changelog section
155
+ run: |
156
+ set -euo pipefail
157
+ version="${{ needs.verify.outputs.version }}"
158
+ awk -v ver="$version" '
159
+ /^## \[/ {
160
+ if (found) exit
161
+ if ($0 ~ "^## \\[" ver "\\]") { found = 1; next }
162
+ next
163
+ }
164
+ found { print }
165
+ ' CHANGELOG.md > /tmp/release_notes.md
166
+ if [ ! -s /tmp/release_notes.md ]; then
167
+ echo "::error::No CHANGELOG.md section found for [$version]. Move [Unreleased] to [$version] - <date> before tagging (see RELEASING.md)."
168
+ exit 1
169
+ fi
170
+
171
+ - name: Create GitHub Release
172
+ uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
173
+ env:
174
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
175
+ with:
176
+ tag_name: ${{ inputs.tag || github.ref_name }}
177
+ name: "v${{ needs.verify.outputs.version }}"
178
+ body_path: /tmp/release_notes.md
179
+ prerelease: ${{ contains(needs.verify.outputs.version, '-') }}
180
+ files: |
181
+ dist/*
182
+ web-dist/*
183
+
184
+ # ---- Publish the wheel/sdist to PyPI via Trusted Publishing (OIDC, no ----
185
+ # ---- stored secrets). Independent of the GitHub Release job so a not- ----
186
+ # ---- yet-configured PyPI trusted publisher never blocks the release. ----
187
+ publish-pypi:
188
+ name: Publish to PyPI
189
+ needs: [verify, build]
190
+ runs-on: ubuntu-latest
191
+ timeout-minutes: 10
192
+ environment:
193
+ name: pypi
194
+ url: https://pypi.org/project/steam-idle-bot/${{ needs.verify.outputs.version }}/
195
+ permissions:
196
+ id-token: write # required for OIDC trusted publishing
197
+ steps:
198
+ - name: Download build artifacts
199
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
200
+ with:
201
+ name: release-dist
202
+
203
+ - name: Publish to PyPI
204
+ uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
205
+ with:
206
+ packages-dir: dist/
@@ -0,0 +1,86 @@
1
+ # Build and deploy the documentation site to GitHub Pages.
2
+ name: Deploy docs to Pages
3
+
4
+ on:
5
+ # On pushes to main, build AND deploy. On pull requests, only build (validation):
6
+ # the protected github-pages environment permits `main` only, so the deploy job
7
+ # is skipped on PRs while the build job still catches broken layouts/assets.
8
+ push:
9
+ branches: ["main"]
10
+ paths:
11
+ - "docs/**"
12
+ - ".github/workflows/static.yml"
13
+ pull_request:
14
+ branches: ["main"]
15
+ paths:
16
+ - "docs/**"
17
+ - ".github/workflows/static.yml"
18
+ workflow_dispatch:
19
+
20
+ # Least privilege: only the deploy job needs pages/id-token.
21
+ permissions:
22
+ contents: read
23
+
24
+ # One deployment at a time; let in-flight production deploys finish.
25
+ concurrency:
26
+ group: "pages"
27
+ cancel-in-progress: false
28
+
29
+ jobs:
30
+ # ---- Build the static site and validate the output ----
31
+ build:
32
+ name: Build site
33
+ runs-on: ubuntu-latest
34
+ timeout-minutes: 10
35
+ steps:
36
+ - name: Checkout
37
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
38
+
39
+ - name: Setup Pages
40
+ uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
41
+
42
+ - name: Build site with Jekyll
43
+ uses: actions/jekyll-build-pages@44a6e6beabd48582f863aeeb6cb2151cc1716697 # v1.0.13
44
+ with:
45
+ source: ./docs
46
+ destination: ./_site
47
+
48
+ - name: Validate build output
49
+ run: |
50
+ set -euo pipefail
51
+ test -f ./_site/index.html || { echo "::error::index.html missing from build"; exit 1; }
52
+ for asset in banner.svg architecture.svg pipeline.svg css/style.css; do
53
+ test -f "./_site/assets/$asset" || { echo "::error::missing asset: $asset"; exit 1; }
54
+ done
55
+ pages=$(find ./_site -name '*.html' | wc -l)
56
+ size=$(du -sh ./_site | cut -f1)
57
+ {
58
+ echo "### 📄 Docs site built"
59
+ echo ""
60
+ echo "- **HTML pages:** $pages"
61
+ echo "- **Total size:** $size"
62
+ echo "- **Assets verified:** banner · architecture · pipeline · stylesheet"
63
+ } >> "$GITHUB_STEP_SUMMARY"
64
+
65
+ - name: Upload artifact
66
+ uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
67
+ with:
68
+ path: "./_site"
69
+
70
+ # ---- Deploy the built artifact to GitHub Pages (main only) ----
71
+ deploy:
72
+ name: Deploy to Pages
73
+ needs: build
74
+ if: github.event_name != 'pull_request'
75
+ runs-on: ubuntu-latest
76
+ timeout-minutes: 10
77
+ permissions:
78
+ pages: write
79
+ id-token: write
80
+ environment:
81
+ name: github-pages
82
+ url: ${{ steps.deployment.outputs.page_url }}
83
+ steps:
84
+ - name: Deploy to GitHub Pages
85
+ id: deployment
86
+ uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
@@ -0,0 +1,29 @@
1
+ config.py
2
+ .env
3
+ .cache/
4
+ __pycache__/
5
+ venv/
6
+ .vscode/
7
+ .claude/
8
+ .venv/
9
+ .mypy_cache/
10
+ .serena/
11
+
12
+ # Python build/cache
13
+ *.pyc
14
+ .ruff_cache/
15
+ .pytest_cache/
16
+ .coverage
17
+ coverage.xml
18
+ logs/
19
+ *.log
20
+ *.log.*
21
+ *.egg-info/
22
+
23
+ # Frontend
24
+ frontend/node_modules/
25
+ frontend/dist/
26
+
27
+ # Local AI tooling caches
28
+ .headroom/
29
+ .tokensave/
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,159 @@
1
+ # 📋 Product Backlog & Roadmap
2
+
3
+ Living, scrum-style backlog for **Steam Idle Bot**. Tracks shipped work, the prioritized
4
+ queue, known defects, and risks. Source of truth for "what's next"; narrative history lives
5
+ in [`CHANGELOG.md`](CHANGELOG.md).
6
+
7
+ ---
8
+
9
+ ## Legend
10
+
11
+ | Field | Values |
12
+ |-------|--------|
13
+ | **Status** | ✅ Done · 🟡 In progress · ⬜ Todo · 🧊 Icebox |
14
+ | **Type** | ✨ Feature · 🐛 Bug · 🧱 Tech-debt · 📚 Docs · 🔐 Security · 🧪 Test |
15
+ | **Impact** | 🟥 High · 🟧 Medium · 🟩 Low (user/operational value) |
16
+ | **Effort** | `S` ≤½ day · `M` 1–3 days · `L` > 3 days |
17
+ | **Priority** | `P0` now · `P1` next · `P2` soon · `P3` later — derived from impact ÷ effort |
18
+
19
+ **Definition of Done:** code + tests green (`ruff` · `mypy` · `pytest` across 3.12–3.14),
20
+ docs/CHANGELOG updated, no committed secrets/artifacts, reviewed & merged to `main`.
21
+
22
+ **Cadence:** ~1-week sprints. Each closed sprint ships as a PR; backlog re-groomed at close.
23
+
24
+ ---
25
+
26
+ ## ✅ Shipped (sprints 1–5)
27
+
28
+ > Delivered in PRs [#30](https://github.com/bernardopg/steam-idler-python/pull/30)–[#34](https://github.com/bernardopg/steam-idler-python/pull/34). Test suite grew 204 → 242.
29
+
30
+ ### Sprint 1 — Audit & hardening · PR #30
31
+ - [x] 🐛 🟥 Rotate the log file (`RotatingFileHandler`, 10 MB × 3) — fixes unbounded 1 GB log growth
32
+ - [x] 🐛 🟥 Live idle/session duration in the panel (was frozen at `0 min`)
33
+ - [x] ✨ 🟧 Configurable `--refresh-interval-seconds` / `REFRESH_INTERVAL_SECONDS`
34
+ - [x] 🐛 🟧 Tolerate malformed Steam `appdetails` payloads (e.g. app `2321720`)
35
+ - [x] 📚 🟥 Correct inverted `eventemitter` dependency note in `CLAUDE.md`
36
+
37
+ ### Sprint 2 — Graceful shutdown · PR #31
38
+ - [x] 🐛 🟥 `SIGINT`/`SIGTERM` graceful stop that still emits the session report
39
+
40
+ ### Sprint 3 — Privacy · PR #32
41
+ - [x] 🔐 🟧 Consistent account-name redaction across both backends (`utils.redaction`)
42
+
43
+ ### Sprint 4 — Follow-ups · PR #33
44
+ - [x] ✨ 🟧 Preflight warnings (Steam not running / no graphical session) for `steam_utility`
45
+ - [x] 🐛 🟧 Backfill drained final card counts to `0` in the session report
46
+ - [x] ✨ 🟧 steam-utility idle reconciliation (reuse / dedup / report via `/proc`)
47
+
48
+ ### Sprint 5 — Backlog close-out · PR #34
49
+ - [x] ✨ 🟧 Checkpoint report mode (`--checkpoint-minutes` → JSON + Markdown) + `--duration-minutes`
50
+ - [x] ✨ 🟩 Delayed post-run verification (`--post-run-verify-seconds`)
51
+ - [x] ✨ 🟩 `--stop-app-ids` maintenance mode
52
+ - [x] 🧪 🟧 run.sh signal-forwarding integration tests
53
+ - [x] 📚 🟩 Document expected empty Badge-API card-drop responses
54
+
55
+ ### Sprint 6 — Full audit (2026-07-03)
56
+ - [x] 🐛 🟥 Terminal color was silently disabled under `run.sh` (FIFO ≠ TTY → rich dropped ANSI). Fixed via `FORCE_COLOR=1` + ANSI-stripped run log
57
+ - [x] ✨ 🟧 A2: exponential backoff + jitter on reconnect storms (10s → 5 min cap, reset on success)
58
+ - [x] ✨ 🟩 Status panel reprinted every refresh so idle-time columns stay live
59
+ - [x] 🧪 🟧 G1: CI coverage floor (`--cov-fail-under=85`; suite currently at 90%)
60
+ - [x] 🧪 🟧 G2: `pytest-timeout` (120s, thread method — signal timeouts don't fire in gevent)
61
+ - [x] 🧱 🟩 G3: `ruff check` + `ruff format --check` + `mypy` as CI gates (CI previously ran *only* pytest)
62
+ - [x] ✨ 🟥 F1 groundwork: hatchling build system, `steam-idle-bot` entry point, full PyPI metadata
63
+ - [x] 🧱 🟧 Bounded `DetailedLogger` outputs (last 500 append entries; 50 filtering snapshots)
64
+
65
+ ### Sprint 7 — Web UI (2026-07-03)
66
+ - [x] ✨ 🟥 FastAPI + React web UI (`--web` / `run-web.sh`): live WebSocket dashboard, full settings
67
+ editor (.env persist, masked password), streamed logs, session report, Steam Guard dialog.
68
+ Emerald/dark-green minimalist theme. React 19 + Vite 8 + TS 6 + Tailwind 4; deps at latest.
69
+ - [x] 🧪 🟧 20 backend tests (`test_webapi.py`) incl. React-form ⇄ `Settings` parity check
70
+
71
+ ---
72
+
73
+ ## 🎯 Product backlog (prioritized)
74
+
75
+ ### EPIC A — Reliability & resilience
76
+ | ID | Type | Item | Impact | Effort | Priority | Status |
77
+ |----|------|------|:------:|:------:|:--------:|:------:|
78
+ | A1 | 🧪 | Raise coverage on `client.py` (79%), `inventory.py` (80%), `games.py` (83%) | 🟧 | M | P2 | ⬜ |
79
+ | A2 | ✨ | Exponential backoff + jitter on reconnect storms | 🟧 | S | — | ✅ |
80
+ | A3 | 🧱 | Structured health metrics (uptime, reconnects, drops/hr) exposed to the panel | 🟩 | M | P3 | ⬜ |
81
+ | A4 | 🐛 | Detect & recover from a silently-killed steam-utility child mid-session | 🟧 | M | P2 | ⬜ |
82
+
83
+ ### EPIC B — Card-drop accuracy
84
+ | ID | Type | Item | Impact | Effort | Priority | Status |
85
+ |----|------|------|:------:|:------:|:--------:|:------:|
86
+ | B1 | ✨ | Honor Steam's weekly 3-drop / playtime gate in the planner | 🟥 | L | P1 | ⬜ |
87
+ | B2 | ✨ | Cross-check badge-API vs scraper counts; warn on divergence | 🟧 | M | P2 | ⬜ |
88
+ | B3 | 🧱 | Cache TTL auto-tuning from observed drop cadence | 🟩 | M | P3 | 🧊 |
89
+
90
+ ### EPIC C — Backends & idling
91
+ | ID | Type | Item | Impact | Effort | Priority | Status |
92
+ |----|------|------|:------:|:------:|:--------:|:------:|
93
+ | C1 | ✨ | Multi-account / account-rotation support | 🟥 | L | P2 | 🧊 |
94
+ | C2 | ✨ | Windows-native steam-utility process detection (no `/proc`) | 🟧 | M | P2 | ⬜ |
95
+ | C3 | 🧱 | Formalize the backend `Protocol` and add a conformance test suite | 🟧 | S | P1 | ⬜ |
96
+
97
+ ### EPIC D — UX (terminal & GUI)
98
+ | ID | Type | Item | Impact | Effort | Priority | Status |
99
+ |----|------|------|:------:|:------:|:--------:|:------:|
100
+ | D1 | 🧱 | Split `gui.py` (1 359 lines, 93% covered) into view/controller modules | 🟧 | M | P2 | ⬜ |
101
+ | D2 | ✨ | Live in-place terminal panel via `rich.live.Live` writing to `/dev/tty` (survives the run.sh FIFO redirect; falls back to periodic reprint when no TTY) | 🟩 | M | P3 | ⬜ |
102
+ | D3 | ✨ | GUI: per-game drop progress bars + ETA | 🟩 | M | P3 | 🧊 |
103
+ | D4 | 🧱 | Default `LOG_FILE` (`steam_card_idler.log`) should live under `logs/`, not the repo root | 🟩 | S | P2 | ⬜ |
104
+
105
+ ### EPIC E — Observability & reporting
106
+ | ID | Type | Item | Impact | Effort | Priority | Status |
107
+ |----|------|------|:------:|:------:|:--------:|:------:|
108
+ | E1 | ✨ | HTML/Markdown session summary with charts | 🟩 | M | P3 | ⬜ |
109
+ | E2 | ✨ | Prometheus/JSON metrics endpoint for long-running hosts | 🟩 | M | P3 | 🧊 |
110
+
111
+ ### EPIC F — Packaging & distribution
112
+ | ID | Type | Item | Impact | Effort | Priority | Status |
113
+ |----|------|------|:------:|:------:|:--------:|:------:|
114
+ | F1 | ✨ | Publish to PyPI (`pipx install steam-idle-bot`) — packaging metadata + entry point shipped in Sprint 6; remaining: release workflow + first upload | 🟥 | S | P1 | 🟡 |
115
+ | F2 | ✨ | Official Docker image + compose for headless 24/7 idling | 🟧 | M | P2 | ⬜ |
116
+ | F3 | 📚 | Release automation (tagged builds, changelog extraction) | 🟧 | S | P2 | ⬜ |
117
+
118
+ ### EPIC G — Quality & testing
119
+ | ID | Type | Item | Impact | Effort | Priority | Status |
120
+ |----|------|------|:------:|:------:|:--------:|:------:|
121
+ | G1 | 🧪 | Enforce a coverage floor in CI (`--cov-fail-under=85`) | 🟧 | S | — | ✅ |
122
+ | G2 | 🧪 | Add `pytest-timeout` to kill hung tests deterministically | 🟧 | S | — | ✅ |
123
+ | G3 | 🧱 | `ruff check` + `ruff format --check` + `mypy` as CI gates | 🟩 | S | — | ✅ |
124
+
125
+ ### EPIC H — Security & privacy
126
+ | ID | Type | Item | Impact | Effort | Priority | Status |
127
+ |----|------|------|:------:|:------:|:--------:|:------:|
128
+ | H1 | 🔐 | Optional OS-keyring credential storage (drop plaintext `.env`) | 🟥 | M | P1 | ⬜ |
129
+ | H2 | 🔐 | Redact steam_id/cookies/tokens in `DetailedLogger` JSON dumps (growth caps shipped in Sprint 6; redaction still open) | 🟧 | S | P1 | ⬜ |
130
+ | H3 | 🔐 | Document & track the accepted `protobuf<4` advisory exceptions | 🟩 | S | P2 | ✅ |
131
+
132
+ ### EPIC I — Documentation
133
+ | ID | Type | Item | Impact | Effort | Priority | Status |
134
+ |----|------|------|:------:|:------:|:--------:|:------:|
135
+ | I1 | 📚 | Architecture deep-dive page linked from the README diagrams | 🟧 | S | P2 | ⬜ |
136
+ | I2 | 📚 | Troubleshooting matrix (auth failures, 429s, no drops) | 🟧 | S | P1 | ⬜ |
137
+ | I3 | 📚 | Animated demo (asciinema/GIF) in the README | 🟩 | S | P3 | ⬜ |
138
+
139
+ ---
140
+
141
+ ## 🐛 Known defects / risks
142
+
143
+ | ID | Severity | Description | Mitigation | Status |
144
+ |----|:--------:|-------------|-----------|:------:|
145
+ | R1 | 🟧 | steam-utility reconciliation is Linux-only (`/proc`) | No-op elsewhere; tracked as C2 | ⬜ |
146
+ | R2 | 🟩 | `protobuf<4` pin blocks GHSA-8qvm-5x2c-j2w7 / GHSA-7gcm-g887-7qv7 | Accepted: only trusted Steam data deserialized | ✅ |
147
+ | R3 | 🟧 | Card-drop scraping depends on a short-lived community cookie | Auto browser-cookie recovery; session verify | ✅ |
148
+ | R4 | 🟩 | ~~GUI is largely untested~~ GUI now at 93% coverage (171 tests); remaining risk is the 1 359-line monolith | Tracked as D1 | ✅ |
149
+ | R5 | 🟩 | `logs/` accumulates per-run artifacts (`runs/`, checkpoints, reports) with no retention policy beyond DetailedLogger caps | Periodic manual cleanup; candidate for a small janitor on startup | ⬜ |
150
+
151
+ ---
152
+
153
+ ## 📌 Next sprint candidate (groomed 2026-07-03)
154
+
155
+ Top of queue by impact ÷ effort: **F1 finish** (release workflow + first PyPI upload),
156
+ **C3** (backend conformance tests — `protocol.py` already exists, only the suite is missing),
157
+ **H2** (redact logger dumps), **I2** (troubleshooting matrix), **D4** (default log into `logs/`).
158
+ Next after that: **B1** (weekly drop-gate planner — highest-impact open feature) and **D1**
159
+ (split `gui.py`).