Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: CI

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

# Cancel an in-flight run on the same branch when a new commit arrives.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
# --------------------------------------------------------------------
# Lint: ruff format + check. Fast (~5 s).
# --------------------------------------------------------------------
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install ruff
- name: ruff check
# Same rule set as .github/workflows/style.yml so the two
# jobs don't disagree. Real-bug rules only (E/F/W/B/C4/UP);
# cosmetic rules (E501 line length, E701/E702 single-line
# try/except, etc.) are intentionally off.
working-directory: SerienJunkie
run: |
ruff check . \
--select=E,F,W,B,C4,UP \
--ignore=E501,E701,E731,B008,B904,UP015 \
--exclude='user.BingeWatcher,tor,extensions,_fresh_profiles,__pycache__' \
--no-fix \
--output-format=github
- name: ruff format --check
# Informational only — format drift across the legacy tree
# would be a churn-bomb to enforce strictly.
working-directory: SerienJunkie
continue-on-error: true
run: |
ruff format --check \
--exclude='user.BingeWatcher,tor,extensions,_fresh_profiles,__pycache__' \
. || echo "::warning::format drift — run 'ruff format' locally to fix."

# --------------------------------------------------------------------
# Type check: mypy on the newly-annotated modules. Currently scoped
# to the new modules; expand as the type-hint sweep progresses.
# --------------------------------------------------------------------
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install mypy
- name: mypy
# --follow-imports=silent so strict checks only apply to the
# listed modules. Without it, mypy walks into legacy
# bingewatcher.py / player_loop.py and surfaces hundreds of
# pre-existing errors that aren't this PR's scope.
run: |
mypy --strict --ignore-missing-imports --follow-imports=silent \
SerienJunkie/bw/constants.py \
SerienJunkie/bw/settings_types.py \
SerienJunkie/bw/shutdown.py \
SerienJunkie/bw/session.py \
SerienJunkie/bw/episode_logic.py \
SerienJunkie/bw/i18n.py

# --------------------------------------------------------------------
# Unit tests on Python 3.11 + 3.12. Skips slow (network/browser) tests.
# --------------------------------------------------------------------
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python: ["3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- run: pip install -r SerienJunkie/requirements.txt pytest pytest-xdist
- name: pytest (fast tier)
working-directory: SerienJunkie
run: pytest tests/ -m "not slow" -n auto --tb=short

# --------------------------------------------------------------------
# Dedicated i18n parity check. Runs as a separate job so a missing
# DE translation shows up immediately in the PR status bar with a
# specific failure name (instead of being buried in the test job).
# --------------------------------------------------------------------
i18n-parity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: assert EN/DE parity
working-directory: SerienJunkie
run: |
python -c "
import sys
sys.path.insert(0, '.')
from bw import i18n
diff = i18n.diff_keys()
missing = {k: v for k, v in diff.items() if v}
if missing:
for lang, keys in missing.items():
print(f'::error::{lang} missing {len(keys)} key(s): {keys[:5]}')
sys.exit(1)
print(f'i18n OK — EN={len(i18n.STRINGS[\"en\"])}, DE={len(i18n.STRINGS[\"de\"])}')
"
1 change: 1 addition & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ jobs:
hits=$(grep -rnE '"useTorProxy"|'"'"'useTorProxy'"'" \
--include='*.py' --include='*.json' \
--exclude='selftest_phase0.py' \
--exclude-dir='tests' \
. | grep -vE '^[^:]+:[0-9]+:\s*#' || true)
if [ -n "$hits" ]; then
echo "ERROR: 'useTorProxy' key resurfaced -- Tor must stay mandatory (see Phase 0)."
Expand Down
12 changes: 6 additions & 6 deletions SerienJunkie/assets/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@
const bar = document.createElement('div');
bar.className = 'bw-tb';
bar.innerHTML = `
<button class="bw-tb-back10" type="button" title="10s zurück" aria-label="10s zurück">
<button class="bw-tb-back10" type="button" title="Back 10s" aria-label="Back 10s">
<svg viewBox="0 0 24 24"><path d="M12.5 8V4l-5 5 5 5V10c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6h-2c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/><text x="11.5" y="19" text-anchor="middle" font-size="7" font-weight="700" fill="currentColor" stroke="none" font-family="system-ui">10</text></svg>
</button>
<button class="bw-tb-play" type="button" title="Play/Pause" aria-label="Play/Pause">
Expand Down Expand Up @@ -543,7 +543,7 @@
<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
</div>
<div class="bw-ec-text">
<span class="bw-ec-title">Nächste Folge in <span class="bw-ec-time">--s</span></span>
<span class="bw-ec-title">Next episode in <span class="bw-ec-time">--s</span></span>
<span class="bw-ec-next-title"></span>
</div>
<button class="bw-ec-cancel" type="button">Abbrechen</button>
Expand Down Expand Up @@ -610,7 +610,7 @@
document.exitPictureInPicture().catch(()=>{});
} else {
v.requestPictureInPicture().catch((err) => {
try { window.top.postMessage({type:'bw-toast', msg:'PiP nicht möglich: ' + (err.message || err.name), level:'warn'}, '*'); } catch(_) {}
try { window.top.postMessage({type:'bw-toast', msg:'PiP not possible: ' + (err.message || err.name), level:'warn'}, '*'); } catch(_) {}
});
}
} catch(_) {}
Expand Down Expand Up @@ -1308,8 +1308,8 @@
ecIcon.style.cursor = 'pointer';
ecIcon.setAttribute('role', 'button');
ecIcon.setAttribute('tabindex', '0');
ecIcon.setAttribute('aria-label', 'Jetzt zur nächsten Folge');
ecIcon.setAttribute('title', 'Jetzt zur nächsten Folge');
ecIcon.setAttribute('aria-label', 'Go to next episode now');
ecIcon.setAttribute('title', 'Go to next episode now');
const skipNow = (e) => {
try { e.preventDefault(); e.stopPropagation(); } catch(_){}
try {
Expand Down Expand Up @@ -1757,7 +1757,7 @@
if (!inWindow) { pill.removeAttribute('data-open'); return; }
pillState = 'predict';
pill.setAttribute('data-mode', 'predict');
if (pillText) pillText.textContent = 'Intro überspringen';
if (pillText) pillText.textContent = 'Skip intro';
if (pillShortcut) pillShortcut.textContent = 'I';
pill.setAttribute('data-open', '1');
} else {
Expand Down
Loading
Loading