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.
- refrain-0.1.3/.editorconfig +29 -0
- refrain-0.1.3/.gitattributes +24 -0
- refrain-0.1.3/.github/FUNDING.yml +9 -0
- refrain-0.1.3/.github/ISSUE_TEMPLATE/bug_report.yml +82 -0
- refrain-0.1.3/.github/ISSUE_TEMPLATE/config.yml +10 -0
- refrain-0.1.3/.github/ISSUE_TEMPLATE/feature_request.yml +58 -0
- refrain-0.1.3/.github/codeql/codeql-config.yml +30 -0
- refrain-0.1.3/.github/dependabot.yml +44 -0
- refrain-0.1.3/.github/pull_request_template.md +45 -0
- refrain-0.1.3/.github/workflows/codeql.yml +41 -0
- refrain-0.1.3/.github/workflows/release.yml +125 -0
- refrain-0.1.3/.github/workflows/security.yml +86 -0
- refrain-0.1.3/.github/workflows/tests.yml +59 -0
- refrain-0.1.3/.gitignore +53 -0
- refrain-0.1.3/CHANGELOG.md +223 -0
- refrain-0.1.3/CODE_OF_CONDUCT.md +16 -0
- refrain-0.1.3/CONTRIBUTING.md +121 -0
- refrain-0.1.3/LICENSE +71 -0
- refrain-0.1.3/Makefile +46 -0
- refrain-0.1.3/PKG-INFO +332 -0
- refrain-0.1.3/README.md +298 -0
- refrain-0.1.3/ROADMAP.md +133 -0
- refrain-0.1.3/SECURITY.md +59 -0
- refrain-0.1.3/docs/architecture.md +89 -0
- refrain-0.1.3/docs/faq.md +79 -0
- refrain-0.1.3/docs/screenshots/README.md +36 -0
- refrain-0.1.3/docs/screenshots/discord-rpc.png +0 -0
- refrain-0.1.3/docs/screenshots/live-log.png +0 -0
- refrain-0.1.3/docs/screenshots/notification.png +0 -0
- refrain-0.1.3/docs/screenshots/settings-advanced.png +0 -0
- refrain-0.1.3/docs/screenshots/settings-general.png +0 -0
- refrain-0.1.3/docs/screenshots/settings-sources.png +0 -0
- refrain-0.1.3/docs/screenshots/settings-updates.png +0 -0
- refrain-0.1.3/docs/screenshots/tray-menu.png +0 -0
- refrain-0.1.3/docs/screenshots/update-dialog.png +0 -0
- refrain-0.1.3/packaging/README.md +93 -0
- refrain-0.1.3/packaging/appimage/AppImageBuilder.yml +76 -0
- refrain-0.1.3/packaging/aur/refrain/PKGBUILD +42 -0
- refrain-0.1.3/packaging/aur/refrain-git/PKGBUILD +54 -0
- refrain-0.1.3/packaging/flatpak/io.github.Rockykln.Refrain.desktop +14 -0
- refrain-0.1.3/packaging/flatpak/io.github.Rockykln.Refrain.metainfo.xml +109 -0
- refrain-0.1.3/packaging/flatpak/io.github.Rockykln.Refrain.yaml +94 -0
- refrain-0.1.3/packaging/flatpak/python-deps.json +83 -0
- refrain-0.1.3/pyproject.toml +130 -0
- refrain-0.1.3/requirements-dev.txt +7 -0
- refrain-0.1.3/requirements.txt +3 -0
- refrain-0.1.3/src/refrain/__init__.py +1 -0
- refrain-0.1.3/src/refrain/__main__.py +4 -0
- refrain-0.1.3/src/refrain/app.py +409 -0
- refrain-0.1.3/src/refrain/assets/icons/github-mark.svg +3 -0
- refrain-0.1.3/src/refrain/assets/icons/refrain.svg +14 -0
- refrain-0.1.3/src/refrain/assets/icons/tray-paused.svg +4 -0
- refrain-0.1.3/src/refrain/assets/icons/tray-playing.svg +3 -0
- refrain-0.1.3/src/refrain/assets/icons/tray-stopped.svg +3 -0
- refrain-0.1.3/src/refrain/assets/refrain.desktop +14 -0
- refrain-0.1.3/src/refrain/autostart.py +87 -0
- refrain-0.1.3/src/refrain/config.py +154 -0
- refrain-0.1.3/src/refrain/cover_art.py +153 -0
- refrain-0.1.3/src/refrain/cover_fetcher.py +162 -0
- refrain-0.1.3/src/refrain/daemon.py +387 -0
- refrain-0.1.3/src/refrain/discord_rpc.py +94 -0
- refrain-0.1.3/src/refrain/logging_setup.py +73 -0
- refrain-0.1.3/src/refrain/paths.py +46 -0
- refrain-0.1.3/src/refrain/single_instance.py +38 -0
- refrain-0.1.3/src/refrain/sources/__init__.py +0 -0
- refrain-0.1.3/src/refrain/sources/base.py +35 -0
- refrain-0.1.3/src/refrain/sources/bluetooth.py +175 -0
- refrain-0.1.3/src/refrain/sources/mpris.py +219 -0
- refrain-0.1.3/src/refrain/timing.py +42 -0
- refrain-0.1.3/src/refrain/ui/__init__.py +0 -0
- refrain-0.1.3/src/refrain/ui/log_window.py +109 -0
- refrain-0.1.3/src/refrain/ui/settings_window.py +337 -0
- refrain-0.1.3/src/refrain/ui/tray.py +139 -0
- refrain-0.1.3/src/refrain/ui/update_dialog.py +154 -0
- refrain-0.1.3/src/refrain/updater.py +313 -0
- refrain-0.1.3/tests/__init__.py +0 -0
- refrain-0.1.3/tests/conftest.py +37 -0
- refrain-0.1.3/tests/test_autostart.py +67 -0
- refrain-0.1.3/tests/test_config.py +126 -0
- refrain-0.1.3/tests/test_cover_art.py +148 -0
- refrain-0.1.3/tests/test_cover_fetcher.py +175 -0
- refrain-0.1.3/tests/test_imports.py +22 -0
- refrain-0.1.3/tests/test_paths.py +49 -0
- refrain-0.1.3/tests/test_sources_base.py +68 -0
- refrain-0.1.3/tests/test_timing.py +236 -0
- refrain-0.1.3/tests/test_update_dialog_body.py +51 -0
- 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
|