refrain 0.1.3__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 (87) hide show
  1. refrain-0.1.3/.editorconfig +29 -0
  2. refrain-0.1.3/.gitattributes +24 -0
  3. refrain-0.1.3/.github/FUNDING.yml +9 -0
  4. refrain-0.1.3/.github/ISSUE_TEMPLATE/bug_report.yml +82 -0
  5. refrain-0.1.3/.github/ISSUE_TEMPLATE/config.yml +10 -0
  6. refrain-0.1.3/.github/ISSUE_TEMPLATE/feature_request.yml +58 -0
  7. refrain-0.1.3/.github/codeql/codeql-config.yml +30 -0
  8. refrain-0.1.3/.github/dependabot.yml +44 -0
  9. refrain-0.1.3/.github/pull_request_template.md +45 -0
  10. refrain-0.1.3/.github/workflows/codeql.yml +41 -0
  11. refrain-0.1.3/.github/workflows/release.yml +125 -0
  12. refrain-0.1.3/.github/workflows/security.yml +86 -0
  13. refrain-0.1.3/.github/workflows/tests.yml +59 -0
  14. refrain-0.1.3/.gitignore +53 -0
  15. refrain-0.1.3/CHANGELOG.md +223 -0
  16. refrain-0.1.3/CODE_OF_CONDUCT.md +16 -0
  17. refrain-0.1.3/CONTRIBUTING.md +121 -0
  18. refrain-0.1.3/LICENSE +71 -0
  19. refrain-0.1.3/Makefile +46 -0
  20. refrain-0.1.3/PKG-INFO +332 -0
  21. refrain-0.1.3/README.md +298 -0
  22. refrain-0.1.3/ROADMAP.md +133 -0
  23. refrain-0.1.3/SECURITY.md +59 -0
  24. refrain-0.1.3/docs/architecture.md +89 -0
  25. refrain-0.1.3/docs/faq.md +79 -0
  26. refrain-0.1.3/docs/screenshots/README.md +36 -0
  27. refrain-0.1.3/docs/screenshots/discord-rpc.png +0 -0
  28. refrain-0.1.3/docs/screenshots/live-log.png +0 -0
  29. refrain-0.1.3/docs/screenshots/notification.png +0 -0
  30. refrain-0.1.3/docs/screenshots/settings-advanced.png +0 -0
  31. refrain-0.1.3/docs/screenshots/settings-general.png +0 -0
  32. refrain-0.1.3/docs/screenshots/settings-sources.png +0 -0
  33. refrain-0.1.3/docs/screenshots/settings-updates.png +0 -0
  34. refrain-0.1.3/docs/screenshots/tray-menu.png +0 -0
  35. refrain-0.1.3/docs/screenshots/update-dialog.png +0 -0
  36. refrain-0.1.3/packaging/README.md +93 -0
  37. refrain-0.1.3/packaging/appimage/AppImageBuilder.yml +76 -0
  38. refrain-0.1.3/packaging/aur/refrain/PKGBUILD +42 -0
  39. refrain-0.1.3/packaging/aur/refrain-git/PKGBUILD +54 -0
  40. refrain-0.1.3/packaging/flatpak/io.github.Rockykln.Refrain.desktop +14 -0
  41. refrain-0.1.3/packaging/flatpak/io.github.Rockykln.Refrain.metainfo.xml +109 -0
  42. refrain-0.1.3/packaging/flatpak/io.github.Rockykln.Refrain.yaml +94 -0
  43. refrain-0.1.3/packaging/flatpak/python-deps.json +83 -0
  44. refrain-0.1.3/pyproject.toml +130 -0
  45. refrain-0.1.3/requirements-dev.txt +7 -0
  46. refrain-0.1.3/requirements.txt +3 -0
  47. refrain-0.1.3/src/refrain/__init__.py +1 -0
  48. refrain-0.1.3/src/refrain/__main__.py +4 -0
  49. refrain-0.1.3/src/refrain/app.py +409 -0
  50. refrain-0.1.3/src/refrain/assets/icons/github-mark.svg +3 -0
  51. refrain-0.1.3/src/refrain/assets/icons/refrain.svg +14 -0
  52. refrain-0.1.3/src/refrain/assets/icons/tray-paused.svg +4 -0
  53. refrain-0.1.3/src/refrain/assets/icons/tray-playing.svg +3 -0
  54. refrain-0.1.3/src/refrain/assets/icons/tray-stopped.svg +3 -0
  55. refrain-0.1.3/src/refrain/assets/refrain.desktop +14 -0
  56. refrain-0.1.3/src/refrain/autostart.py +87 -0
  57. refrain-0.1.3/src/refrain/config.py +154 -0
  58. refrain-0.1.3/src/refrain/cover_art.py +153 -0
  59. refrain-0.1.3/src/refrain/cover_fetcher.py +162 -0
  60. refrain-0.1.3/src/refrain/daemon.py +387 -0
  61. refrain-0.1.3/src/refrain/discord_rpc.py +94 -0
  62. refrain-0.1.3/src/refrain/logging_setup.py +73 -0
  63. refrain-0.1.3/src/refrain/paths.py +46 -0
  64. refrain-0.1.3/src/refrain/single_instance.py +38 -0
  65. refrain-0.1.3/src/refrain/sources/__init__.py +0 -0
  66. refrain-0.1.3/src/refrain/sources/base.py +35 -0
  67. refrain-0.1.3/src/refrain/sources/bluetooth.py +175 -0
  68. refrain-0.1.3/src/refrain/sources/mpris.py +219 -0
  69. refrain-0.1.3/src/refrain/timing.py +42 -0
  70. refrain-0.1.3/src/refrain/ui/__init__.py +0 -0
  71. refrain-0.1.3/src/refrain/ui/log_window.py +109 -0
  72. refrain-0.1.3/src/refrain/ui/settings_window.py +337 -0
  73. refrain-0.1.3/src/refrain/ui/tray.py +139 -0
  74. refrain-0.1.3/src/refrain/ui/update_dialog.py +154 -0
  75. refrain-0.1.3/src/refrain/updater.py +313 -0
  76. refrain-0.1.3/tests/__init__.py +0 -0
  77. refrain-0.1.3/tests/conftest.py +37 -0
  78. refrain-0.1.3/tests/test_autostart.py +67 -0
  79. refrain-0.1.3/tests/test_config.py +126 -0
  80. refrain-0.1.3/tests/test_cover_art.py +148 -0
  81. refrain-0.1.3/tests/test_cover_fetcher.py +175 -0
  82. refrain-0.1.3/tests/test_imports.py +22 -0
  83. refrain-0.1.3/tests/test_paths.py +49 -0
  84. refrain-0.1.3/tests/test_sources_base.py +68 -0
  85. refrain-0.1.3/tests/test_timing.py +236 -0
  86. refrain-0.1.3/tests/test_update_dialog_body.py +51 -0
  87. refrain-0.1.3/tests/test_updater.py +169 -0
@@ -0,0 +1,29 @@
1
+ # https://editorconfig.org — picked up automatically by VS Code, JetBrains,
2
+ # Sublime, Vim/Neovim (with plugin), and most other editors. Keeps the
3
+ # project consistent regardless of contributor local settings.
4
+
5
+ root = true
6
+
7
+ [*]
8
+ charset = utf-8
9
+ end_of_line = lf
10
+ indent_style = space
11
+ insert_final_newline = true
12
+ trim_trailing_whitespace = true
13
+
14
+ # Python: 4-space indent, soft 100-col guideline (matches Ruff defaults)
15
+ [*.py]
16
+ indent_size = 4
17
+ max_line_length = 100
18
+
19
+ # Configs, docs, web assets: 2-space indent
20
+ [*.{svg,yml,yaml,toml,json,desktop}]
21
+ indent_size = 2
22
+
23
+ [Makefile]
24
+ indent_style = tab
25
+
26
+ # Markdown allows two trailing spaces as a hard line-break
27
+ [*.md]
28
+ trim_trailing_whitespace = false
29
+ indent_size = 2
@@ -0,0 +1,24 @@
1
+ # Normalize line endings across platforms — every text file gets LF.
2
+ # Without this, Windows contributors would silently inject CRLF.
3
+
4
+ * text=auto eol=lf
5
+
6
+ # Explicit text declarations for common project files
7
+ *.py text eol=lf diff=python
8
+ *.svg text eol=lf
9
+ *.yml text eol=lf
10
+ *.yaml text eol=lf
11
+ *.toml text eol=lf
12
+ *.md text eol=lf
13
+ *.sh text eol=lf
14
+ *.desktop text eol=lf
15
+ .gitignore text eol=lf
16
+ .gitattributes text eol=lf
17
+ .editorconfig text eol=lf
18
+
19
+ # Binary files — never normalize
20
+ *.png binary
21
+ *.jpg binary
22
+ *.jpeg binary
23
+ *.gif binary
24
+ *.ico binary
@@ -0,0 +1,9 @@
1
+ # Optional sponsoring buttons — uncomment any line you actually want to
2
+ # accept money on. Leaving the file in place but empty (with no
3
+ # uncommented entries) is harmless: GitHub simply doesn't render the
4
+ # Sponsor button.
5
+
6
+ # github: Rockykln
7
+ # ko_fi: Rockykln
8
+ # liberapay: Rockykln
9
+ # custom: ['https://www.paypal.me/Rockykln']
@@ -0,0 +1,82 @@
1
+ name: Bug report
2
+ description: Something is broken or behaves unexpectedly
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Thanks for taking the time to file a bug. The more concrete you can be,
9
+ the faster we can fix it.
10
+
11
+ - type: input
12
+ id: version
13
+ attributes:
14
+ label: Refrain version
15
+ description: Run `refrain --version` or check the bottom-right of the settings window.
16
+ placeholder: "0.1.0"
17
+ validations:
18
+ required: true
19
+
20
+ - type: input
21
+ id: distro
22
+ attributes:
23
+ label: Distribution + desktop
24
+ placeholder: "Arch Linux + KDE Plasma 6.2 (Wayland)"
25
+ validations:
26
+ required: true
27
+
28
+ - type: input
29
+ id: python
30
+ attributes:
31
+ label: Python version
32
+ placeholder: "3.12.7"
33
+ validations:
34
+ required: true
35
+
36
+ - type: dropdown
37
+ id: source
38
+ attributes:
39
+ label: Playback source
40
+ options:
41
+ - Apple Music Web (browser)
42
+ - Bluetooth (AVRCP)
43
+ - Both
44
+ - Not sure
45
+ validations:
46
+ required: true
47
+
48
+ - type: input
49
+ id: browser
50
+ attributes:
51
+ label: Browser (if Apple Music Web)
52
+ placeholder: "Firefox 134, Zen 1.7, Chromium …"
53
+ validations:
54
+ required: false
55
+
56
+ - type: textarea
57
+ id: what-happened
58
+ attributes:
59
+ label: What happened
60
+ description: What did you do, what did you expect, what did you get instead?
61
+ validations:
62
+ required: true
63
+
64
+ - type: textarea
65
+ id: logs
66
+ attributes:
67
+ label: Refrain log
68
+ description: |
69
+ Paste the relevant portion of `~/.local/state/refrain/refrain.log`.
70
+ Tracks playing at the time of the bug are useful context but no secrets
71
+ live in the log.
72
+ render: shell
73
+ validations:
74
+ required: false
75
+
76
+ - type: checkboxes
77
+ id: terms
78
+ attributes:
79
+ label: Confirmation
80
+ options:
81
+ - label: I searched existing issues before opening this one.
82
+ required: true
@@ -0,0 +1,10 @@
1
+ # Force users into the templates — no blank "issue" without a category.
2
+ blank_issues_enabled: false
3
+
4
+ contact_links:
5
+ - name: Security report (private)
6
+ url: https://github.com/Rockykln/refrain/security/advisories/new
7
+ about: For vulnerabilities, please use private reporting (or email report@rockykln.com).
8
+ - name: General questions / feedback
9
+ url: mailto:contact@rockykln.com
10
+ about: For non-bug, non-vulnerability questions — feature ideas, packaging help, etc.
@@ -0,0 +1,58 @@
1
+ name: Feature request
2
+ description: Suggest a new feature or enhancement
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Refrain stays focused: it's a Discord Rich Presence app for Apple
9
+ Music on Linux. Features that fit that scope are very welcome.
10
+
11
+ - type: textarea
12
+ id: problem
13
+ attributes:
14
+ label: Problem
15
+ description: What can't you do today, or what's awkward?
16
+ validations:
17
+ required: true
18
+
19
+ - type: textarea
20
+ id: proposal
21
+ attributes:
22
+ label: Proposed solution
23
+ description: How would you like it to work? UI sketches and config shapes welcome.
24
+ validations:
25
+ required: true
26
+
27
+ - type: textarea
28
+ id: alternatives
29
+ attributes:
30
+ label: Alternatives considered
31
+ description: Other approaches you thought about + why you didn't pick them.
32
+ validations:
33
+ required: false
34
+
35
+ - type: dropdown
36
+ id: scope
37
+ attributes:
38
+ label: Scope
39
+ options:
40
+ - Source (MPRIS / Bluetooth / something else)
41
+ - Discord status (cover art, buttons, layout)
42
+ - Settings UI
43
+ - Notifications / tray
44
+ - Distribution (Flatpak / AUR / AppImage / …)
45
+ - Documentation
46
+ - Something else
47
+ validations:
48
+ required: true
49
+
50
+ - type: checkboxes
51
+ id: terms
52
+ attributes:
53
+ label: Confirmation
54
+ options:
55
+ - label: I searched existing issues before opening this one.
56
+ required: true
57
+ - label: I'm willing to discuss design tradeoffs (this is not a "fire and forget").
58
+ required: false
@@ -0,0 +1,30 @@
1
+ name: refrain CodeQL config
2
+
3
+ # We run the security-and-quality query suite. The "security" half is what
4
+ # we care about; the "quality" half ships notes for stylistic patterns that
5
+ # are intentional in this codebase. Suppress those so the alerts page
6
+ # surfaces real findings, not background noise.
7
+
8
+ queries:
9
+ - uses: security-and-quality
10
+
11
+ query-filters:
12
+ # Empty `except` blocks are deliberate in best-effort cleanup paths
13
+ # (DBus disconnects, Discord IPC, tray icon teardown). Each site has
14
+ # context in surrounding code; the CodeQL note here would be repeated
15
+ # ~10× without adding signal.
16
+ - exclude:
17
+ id: py/empty-except
18
+
19
+ # `_qt_bridge` is a module-level memo for `attach_qt_log_bridge()` so
20
+ # repeat calls return the same QObject. CodeQL's per-function analysis
21
+ # mistakes the cross-call read for "unused". The pattern is
22
+ # idiomatic for one-shot lazy initializers.
23
+ - exclude:
24
+ id: py/unused-global-variable
25
+
26
+ paths-ignore:
27
+ - tests/**
28
+ - reference/**
29
+ - assets/**
30
+ - docs/**
@@ -0,0 +1,44 @@
1
+ version: 2
2
+
3
+ updates:
4
+ # Python dependencies (pip — reads requirements*.txt + pyproject.toml)
5
+ - package-ecosystem: "pip"
6
+ directory: "/"
7
+ schedule:
8
+ interval: "weekly"
9
+ day: "monday"
10
+ open-pull-requests-limit: 10
11
+ labels:
12
+ - "dependencies"
13
+ - "python"
14
+ commit-message:
15
+ prefix: "deps"
16
+ groups:
17
+ qt:
18
+ patterns:
19
+ - "PySide6*"
20
+ - "shiboken6*"
21
+ runtime:
22
+ patterns:
23
+ - "pypresence"
24
+ - "dbus-python"
25
+ dev-tools:
26
+ patterns:
27
+ - "pytest*"
28
+ - "coverage"
29
+ - "ruff"
30
+ - "bandit"
31
+ - "pip-audit"
32
+
33
+ # GitHub Actions used in workflows
34
+ - package-ecosystem: "github-actions"
35
+ directory: "/"
36
+ schedule:
37
+ interval: "weekly"
38
+ day: "monday"
39
+ open-pull-requests-limit: 3
40
+ labels:
41
+ - "dependencies"
42
+ - "github-actions"
43
+ commit-message:
44
+ prefix: "ci"
@@ -0,0 +1,45 @@
1
+ <!--
2
+ Thanks for the PR! A few quick checks before merging — fill in
3
+ whatever's relevant and delete the rest.
4
+ -->
5
+
6
+ ## Summary
7
+
8
+ <!-- One or two sentences. What does this change and why? -->
9
+
10
+ ## Type
11
+
12
+ - [ ] Bug fix
13
+ - [ ] New feature
14
+ - [ ] Refactor / cleanup (no behavior change)
15
+ - [ ] Documentation
16
+ - [ ] CI / tooling
17
+ - [ ] Other:
18
+
19
+ ## Linked issues
20
+
21
+ <!-- e.g. Closes #12, Refs #34 -->
22
+
23
+ ## Tested on
24
+
25
+ <!-- Which distro / desktop did you actually run this on? Tick what applies. -->
26
+
27
+ - [ ] KDE Plasma 6 (Wayland)
28
+ - [ ] KDE Plasma 6 (X11)
29
+ - [ ] GNOME (with AppIndicator extension)
30
+ - [ ] Other DE (specify):
31
+ - [ ] Apple Music Web playback verified
32
+ - [ ] Bluetooth (AVRCP) playback verified
33
+ - [ ] Discord RPC visible in profile
34
+
35
+ ## Checklist
36
+
37
+ - [ ] `pytest` passes locally
38
+ - [ ] `ruff check .` and `ruff format --check .` are clean
39
+ - [ ] New / changed behavior is covered by tests (or unreachable from tests due to system integration — explain)
40
+ - [ ] No new private data leaks into the repo (no real Discord client IDs other than the project's, no MAC addresses, no tokens)
41
+ - [ ] If user-visible strings changed, they're English
42
+ - [ ] If the version was bumped, both `pyproject.toml` and `src/refrain/__init__.py` match
43
+ - [ ] README / CHANGELOG updated if behavior changed
44
+
45
+ ## Screenshots (UI changes only)
@@ -0,0 +1,41 @@
1
+ name: codeql
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+ branches: [main, master]
8
+ schedule:
9
+ # Weekly scheduled scan picks up newly disclosed vulnerabilities
10
+ - cron: "27 4 * * 1"
11
+
12
+ jobs:
13
+ analyze:
14
+ name: analyze (${{ matrix.language }})
15
+ runs-on: ubuntu-latest
16
+ # Skip on private repos — Code Scanning needs GitHub Advanced Security
17
+ # to ingest the SARIF results, which isn't free for private repos.
18
+ if: github.event.repository.visibility == 'public'
19
+ permissions:
20
+ actions: read
21
+ contents: read
22
+ security-events: write
23
+
24
+ strategy:
25
+ fail-fast: false
26
+ matrix:
27
+ language: [python]
28
+
29
+ steps:
30
+ - uses: actions/checkout@v6
31
+
32
+ - name: Initialize CodeQL
33
+ uses: github/codeql-action/init@v4
34
+ with:
35
+ languages: ${{ matrix.language }}
36
+ config-file: ./.github/codeql/codeql-config.yml
37
+
38
+ - name: Perform CodeQL analysis
39
+ uses: github/codeql-action/analyze@v4
40
+ with:
41
+ category: "/language:${{ matrix.language }}"
@@ -0,0 +1,125 @@
1
+ name: release
2
+
3
+ # Triggered when a semver tag is pushed: e.g. `git tag v0.2.0 && git push --tags`
4
+ on:
5
+ push:
6
+ tags:
7
+ - "v*.*.*"
8
+
9
+ permissions:
10
+ contents: write # required to create the release
11
+ id-token: write # required by PyPA Trusted Publishers (OIDC) for PyPI upload
12
+
13
+ jobs:
14
+ release:
15
+ runs-on: ubuntu-latest
16
+ environment:
17
+ name: pypi
18
+ url: https://pypi.org/project/refrain/
19
+
20
+ steps:
21
+ - name: Checkout (full history for changelog)
22
+ uses: actions/checkout@v6
23
+ with:
24
+ fetch-depth: 0
25
+
26
+ - uses: actions/setup-python@v6
27
+ with:
28
+ python-version: "3.12"
29
+ cache: pip
30
+
31
+ - name: Install + run tests (gate the release)
32
+ run: |
33
+ python -m pip install --upgrade pip
34
+ pip install pytest pytest-cov
35
+ PYTHONPATH=src pytest -q
36
+
37
+ - name: Verify tag matches package version
38
+ run: |
39
+ TAG="${GITHUB_REF#refs/tags/v}"
40
+ INIT_VERSION=$(python -c "import sys; sys.path.insert(0,'src'); import refrain; print(refrain.__version__)")
41
+ PYPROJECT_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
42
+ if [ "$TAG" != "$INIT_VERSION" ] || [ "$TAG" != "$PYPROJECT_VERSION" ]; then
43
+ echo "::error::Version mismatch — tag=v$TAG, __init__.py=$INIT_VERSION, pyproject.toml=$PYPROJECT_VERSION"
44
+ exit 1
45
+ fi
46
+ echo "Releasing v$TAG"
47
+
48
+ - name: Verify CHANGELOG has a matching entry for this tag
49
+ run: |
50
+ TAG="${GITHUB_REF#refs/tags/v}"
51
+ if ! grep -qE "^## \[${TAG}\]" CHANGELOG.md; then
52
+ echo "::error::CHANGELOG.md is missing a '## [${TAG}]' section"
53
+ exit 1
54
+ fi
55
+
56
+ - name: Build wheel + sdist
57
+ run: |
58
+ pip install build
59
+ python -m build
60
+ # Keep a clean copy of just the Python distributions for PyPI;
61
+ # the AppImage step writes additional files into dist/ later.
62
+ mkdir -p pypi-dist
63
+ cp dist/*.whl dist/*.tar.gz pypi-dist/
64
+ ls -la pypi-dist/
65
+
66
+ # Single-file AppImage so end users can grab one binary from the
67
+ # Releases page and run it without installing anything else.
68
+ # Marked continue-on-error so a recipe glitch never blocks shipping
69
+ # the wheel + sdist; we re-attempt by deleting + re-pushing the tag.
70
+ - name: Install AppImage build deps
71
+ continue-on-error: true
72
+ run: |
73
+ sudo apt-get update
74
+ sudo apt-get install -y \
75
+ libdbus-1-dev libglib2.0-dev pkg-config \
76
+ libfuse2 desktop-file-utils \
77
+ zsync appstream patchelf squashfs-tools file
78
+ pip install appimage-builder
79
+
80
+ - name: Build AppImage
81
+ id: appimage
82
+ continue-on-error: true
83
+ run: |
84
+ TAG="${GITHUB_REF#refs/tags/v}"
85
+ # The recipe pins a literal `version:` value, which determines the
86
+ # output filename (Refrain-<version>-x86_64.AppImage). Rewrite it
87
+ # to match the tag so we don't hand-edit the recipe per release.
88
+ sed -i -E "s/^( version: ).*/\1${TAG}/" packaging/appimage/AppImageBuilder.yml
89
+ grep " version:" packaging/appimage/AppImageBuilder.yml
90
+ cd packaging/appimage
91
+ appimage-builder --recipe AppImageBuilder.yml --skip-test
92
+ mkdir -p ../../dist
93
+ mv *.AppImage ../../dist/ 2>/dev/null || true
94
+ mv *.AppImage.zsync ../../dist/ 2>/dev/null || true
95
+ ls -la ../../dist/
96
+
97
+ - name: Create GitHub release
98
+ uses: softprops/action-gh-release@v3
99
+ with:
100
+ generate_release_notes: true
101
+ draft: false
102
+ prerelease: ${{ contains(github.ref, '-') }} # e.g. v0.2.0-rc1
103
+ name: "Refrain ${{ github.ref_name }}"
104
+ files: |
105
+ dist/*.whl
106
+ dist/*.tar.gz
107
+ dist/*.AppImage
108
+ dist/*.AppImage.zsync
109
+
110
+ # PyPI upload via Trusted Publishers (OIDC). No API token needed; the
111
+ # one-time setup is on pypi.org → Manage → Publishing → Add a new
112
+ # pending publisher (project: refrain, owner: Rockykln,
113
+ # repository: refrain, workflow: release.yml, environment: pypi).
114
+ # Skips silently if the version already exists on PyPI so re-running
115
+ # a release for AppImage-only fixes doesn't fail the workflow.
116
+ - name: Publish to PyPI
117
+ uses: pypa/gh-action-pypi-publish@release/v1
118
+ with:
119
+ packages-dir: pypi-dist/
120
+ skip-existing: true
121
+ verbose: true
122
+ attestations: true
123
+ # Don't let an upload hiccup wipe out the GitHub release that
124
+ # already succeeded above — reruns can fix PyPI later.
125
+ continue-on-error: true
@@ -0,0 +1,86 @@
1
+ name: security
2
+
3
+ # Defense-in-depth scanners on top of CodeQL + Dependabot:
4
+ # - bandit : Python-specific SAST (catches idioms CodeQL sometimes misses)
5
+ # - pip-audit : OSV-backed dependency CVE scan
6
+ # - trufflehog : verified-secret detection across the full git history
7
+
8
+ on:
9
+ push:
10
+ branches: [main, master]
11
+ pull_request:
12
+ branches: [main, master]
13
+ schedule:
14
+ - cron: "31 5 * * 1"
15
+
16
+ permissions:
17
+ contents: read
18
+ security-events: write
19
+
20
+ jobs:
21
+
22
+ bandit:
23
+ name: bandit (python SAST)
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v6
27
+
28
+ - uses: actions/setup-python@v6
29
+ with:
30
+ python-version: "3.12"
31
+ cache: pip
32
+
33
+ - name: Install bandit
34
+ run: pip install bandit
35
+
36
+ - name: Run bandit (medium + high severity only)
37
+ # Config (skips, exclude_dirs) lives in pyproject.toml [tool.bandit].
38
+ # -ll = report only Medium and High severity issues; lows are noise.
39
+ run: bandit -c pyproject.toml -r src/refrain -ll
40
+
41
+ pip-audit:
42
+ name: pip-audit (dependency CVEs)
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v6
46
+
47
+ - uses: actions/setup-python@v6
48
+ with:
49
+ python-version: "3.12"
50
+ cache: pip
51
+
52
+ - name: Install system headers for dbus-python build
53
+ # dbus-python is a C-extension module; pip-audit's resolver tries
54
+ # to build it during dependency resolution, which needs libdbus's
55
+ # development headers + glib. Without these the audit job fails
56
+ # not on a CVE but on a build error.
57
+ run: |
58
+ sudo apt-get update
59
+ sudo apt-get install -y libdbus-1-dev libglib2.0-dev pkg-config
60
+
61
+ - name: Install pip-audit
62
+ run: |
63
+ python -m pip install --upgrade pip
64
+ pip install pip-audit
65
+
66
+ - name: Run pip-audit on requirements.txt
67
+ # --strict makes the job fail on any known vulnerability in the
68
+ # declared dependency set. Refrain has only three runtime deps, so
69
+ # noise is minimal.
70
+ run: pip-audit -r requirements.txt --strict
71
+
72
+ trufflehog:
73
+ name: trufflehog (secret detection)
74
+ runs-on: ubuntu-latest
75
+ steps:
76
+ - uses: actions/checkout@v6
77
+ with:
78
+ fetch-depth: 0
79
+
80
+ - name: Run TruffleHog
81
+ uses: trufflesecurity/trufflehog@17456f8c7d042d8c82c9a8ca9e937231f9f42e26 # v3.95.2
82
+ with:
83
+ # --only-verified: a secret only counts as flagged when TruffleHog
84
+ # successfully validates it against the live service. Drops the
85
+ # false-positive rate dramatically.
86
+ extra_args: --only-verified
@@ -0,0 +1,59 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+ branches: [main, master]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+ fail-fast: false
16
+
17
+ steps:
18
+ - uses: actions/checkout@v6
19
+
20
+ - name: Set up Python ${{ matrix.python-version }}
21
+ uses: actions/setup-python@v6
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+ cache: pip
25
+
26
+ - name: Install test dependencies
27
+ # Tests are designed not to require PySide6 / dbus-python / pypresence,
28
+ # so the heavy runtime deps don't need to be installed in CI. The
29
+ # modules that do depend on them are syntax-checked in the compile step.
30
+ run: |
31
+ python -m pip install --upgrade pip
32
+ pip install pytest pytest-cov
33
+
34
+ - name: Compile-check every module
35
+ # Catches syntax errors in modules tests don't import (daemon, ui/*, sources/mpris, sources/bluetooth)
36
+ run: python -m compileall -q src/
37
+
38
+ - name: Run tests with coverage
39
+ run: PYTHONPATH=src pytest --cov --cov-report=term-missing
40
+
41
+ lint:
42
+ runs-on: ubuntu-latest
43
+ steps:
44
+ - uses: actions/checkout@v6
45
+ - uses: actions/setup-python@v6
46
+ with:
47
+ python-version: "3.12"
48
+ cache: pip
49
+
50
+ - name: Install ruff
51
+ run: pip install ruff
52
+
53
+ - name: Lint with ruff
54
+ run: ruff check . --output-format=github
55
+ continue-on-error: true # warning only — don't fail PRs on style alone
56
+
57
+ - name: Format check with ruff
58
+ run: ruff format --check .
59
+ continue-on-error: true