baark 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- baark-0.1.0/.github/workflows/ci.yml +121 -0
- baark-0.1.0/.github/workflows/publish.yml +36 -0
- baark-0.1.0/LICENSE +21 -0
- baark-0.1.0/PKG-INFO +240 -0
- baark-0.1.0/README.md +214 -0
- baark-0.1.0/justfile +40 -0
- baark-0.1.0/pyproject.toml +106 -0
- baark-0.1.0/pyproject.toml.orig +97 -0
- baark-0.1.0/scripts/check_dist.py +90 -0
- baark-0.1.0/scripts/check_wheel.py +27 -0
- baark-0.1.0/scripts/e2e.py +280 -0
- baark-0.1.0/src/baark/__init__.py +62 -0
- baark-0.1.0/src/baark/_protocol.py +531 -0
- baark-0.1.0/src/baark/client.py +654 -0
- baark-0.1.0/src/baark/crypto.py +258 -0
- baark-0.1.0/src/baark/errors.py +150 -0
- baark-0.1.0/src/baark/models.py +410 -0
- baark-0.1.0/src/baark/py.typed +0 -0
- baark-0.1.0/tests/__init__.py +2 -0
- baark-0.1.0/tests/test_async.py +319 -0
- baark-0.1.0/tests/test_client.py +632 -0
- baark-0.1.0/tests/test_crypto.py +218 -0
- baark-0.1.0/tests/test_e2e.py +194 -0
- baark-0.1.0/tests/test_errors.py +69 -0
- baark-0.1.0/tests/test_models.py +201 -0
- baark-0.1.0/tests/test_server.py +182 -0
- baark-0.1.0/uv.lock +770 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
workflow_call:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: ci-${{ github.event.pull_request.number || github.run_id }}
|
|
15
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
16
|
+
|
|
17
|
+
env:
|
|
18
|
+
UV_VERSION: "0.12.6"
|
|
19
|
+
JUST_VERSION: "1.58.0"
|
|
20
|
+
|
|
21
|
+
defaults:
|
|
22
|
+
run:
|
|
23
|
+
shell: bash
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
quality:
|
|
27
|
+
name: Quality
|
|
28
|
+
runs-on: ubuntu-24.04
|
|
29
|
+
timeout-minutes: 5
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
32
|
+
with:
|
|
33
|
+
persist-credentials: false
|
|
34
|
+
|
|
35
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
36
|
+
with:
|
|
37
|
+
version: ${{ env.UV_VERSION }}
|
|
38
|
+
python-version: "3.13"
|
|
39
|
+
cache-dependency-glob: uv.lock
|
|
40
|
+
|
|
41
|
+
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
|
|
42
|
+
with:
|
|
43
|
+
just-version: ${{ env.JUST_VERSION }}
|
|
44
|
+
|
|
45
|
+
- name: Install actionlint
|
|
46
|
+
run: |
|
|
47
|
+
set -euo pipefail
|
|
48
|
+
curl --fail --silent --show-error --location \
|
|
49
|
+
https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz \
|
|
50
|
+
--output "$RUNNER_TEMP/actionlint.tar.gz"
|
|
51
|
+
echo "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 $RUNNER_TEMP/actionlint.tar.gz" | sha256sum --check
|
|
52
|
+
tar -xzf "$RUNNER_TEMP/actionlint.tar.gz" -C "$RUNNER_TEMP" actionlint
|
|
53
|
+
echo "$RUNNER_TEMP" >> "$GITHUB_PATH"
|
|
54
|
+
|
|
55
|
+
- run: just lint lint-workflows
|
|
56
|
+
|
|
57
|
+
build:
|
|
58
|
+
name: Distributions
|
|
59
|
+
needs: quality
|
|
60
|
+
runs-on: ubuntu-24.04
|
|
61
|
+
timeout-minutes: 5
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
64
|
+
with:
|
|
65
|
+
persist-credentials: false
|
|
66
|
+
|
|
67
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
68
|
+
with:
|
|
69
|
+
version: ${{ env.UV_VERSION }}
|
|
70
|
+
python-version: "3.13"
|
|
71
|
+
cache-dependency-glob: uv.lock
|
|
72
|
+
|
|
73
|
+
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
|
|
74
|
+
with:
|
|
75
|
+
just-version: ${{ env.JUST_VERSION }}
|
|
76
|
+
|
|
77
|
+
- name: Build and validate release artifacts
|
|
78
|
+
env:
|
|
79
|
+
RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || '' }}
|
|
80
|
+
run: just build
|
|
81
|
+
|
|
82
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
83
|
+
with:
|
|
84
|
+
name: python-distributions
|
|
85
|
+
path: |
|
|
86
|
+
dist/*.whl
|
|
87
|
+
dist/*.tar.gz
|
|
88
|
+
if-no-files-found: error
|
|
89
|
+
retention-days: 14
|
|
90
|
+
|
|
91
|
+
test:
|
|
92
|
+
name: Wheel / ${{ matrix.os }} / Python ${{ matrix.python }}
|
|
93
|
+
needs: build
|
|
94
|
+
runs-on: ${{ matrix.os }}
|
|
95
|
+
timeout-minutes: 5
|
|
96
|
+
strategy:
|
|
97
|
+
fail-fast: false
|
|
98
|
+
matrix:
|
|
99
|
+
os: [ubuntu-24.04, macos-15, windows-2025]
|
|
100
|
+
python: ["3.13", "3.14"]
|
|
101
|
+
steps:
|
|
102
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
103
|
+
with:
|
|
104
|
+
persist-credentials: false
|
|
105
|
+
|
|
106
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
107
|
+
with:
|
|
108
|
+
version: ${{ env.UV_VERSION }}
|
|
109
|
+
python-version: ${{ matrix.python }}
|
|
110
|
+
cache-dependency-glob: uv.lock
|
|
111
|
+
|
|
112
|
+
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
|
|
113
|
+
with:
|
|
114
|
+
just-version: ${{ env.JUST_VERSION }}
|
|
115
|
+
|
|
116
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
117
|
+
with:
|
|
118
|
+
name: python-distributions
|
|
119
|
+
path: dist
|
|
120
|
+
|
|
121
|
+
- run: just test-wheel
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: pypi-${{ github.event.release.tag_name }}
|
|
12
|
+
cancel-in-progress: false
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
checks:
|
|
16
|
+
if: github.repository == 'ngc2000/baark'
|
|
17
|
+
uses: ./.github/workflows/ci.yml
|
|
18
|
+
|
|
19
|
+
publish:
|
|
20
|
+
name: Publish tested distributions
|
|
21
|
+
needs: checks
|
|
22
|
+
runs-on: ubuntu-24.04
|
|
23
|
+
timeout-minutes: 5
|
|
24
|
+
environment:
|
|
25
|
+
name: pypi
|
|
26
|
+
url: https://pypi.org/p/baark
|
|
27
|
+
permissions:
|
|
28
|
+
id-token: write
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
31
|
+
with:
|
|
32
|
+
name: python-distributions
|
|
33
|
+
path: dist
|
|
34
|
+
|
|
35
|
+
- name: Publish with Trusted Publishing
|
|
36
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
baark-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 doabell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
baark-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: baark
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Typed sync and async Python SDK for Bark push notifications
|
|
5
|
+
Keywords: bark,notifications,push,async,encryption
|
|
6
|
+
Author: doabell
|
|
7
|
+
Author-email: doabell <35297086+doabell@users.noreply.github.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Dist: httpx2>=2.13.0
|
|
18
|
+
Requires-Dist: pycryptodome>=3.23.0
|
|
19
|
+
Requires-Python: >=3.13
|
|
20
|
+
Project-URL: Homepage, https://github.com/ngc2000/baark
|
|
21
|
+
Project-URL: Documentation, https://github.com/ngc2000/baark#readme
|
|
22
|
+
Project-URL: Source, https://github.com/ngc2000/baark
|
|
23
|
+
Project-URL: Issues, https://github.com/ngc2000/baark/issues
|
|
24
|
+
Project-URL: Releases, https://github.com/ngc2000/baark/releases
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# baark
|
|
28
|
+
|
|
29
|
+
A typed Python SDK for [Bark](https://bark.day.app/) push notifications. Matching
|
|
30
|
+
sync and async clients, connection pooling, rich notifications, and optional AES
|
|
31
|
+
encryption. Requires Python 3.13+; built on `httpx2` and PyCryptodome.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
From PyPI:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
uv add baark
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
Copy your device key from Bark. A URL ending in the key also works.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from baark import Baark
|
|
47
|
+
|
|
48
|
+
with Baark("YOUR_DEVICE_KEY", group="backups") as client:
|
|
49
|
+
result = client.send(
|
|
50
|
+
"The backup finished.",
|
|
51
|
+
title="Backup",
|
|
52
|
+
logo="https://example.com/backup.png",
|
|
53
|
+
url="https://example.com/backups/latest",
|
|
54
|
+
)
|
|
55
|
+
print(result.ok)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Reuse the client for repeated sends; the context manager closes its connections.
|
|
59
|
+
`result.ok` confirms server acceptance, not delivery to the phone. For a single
|
|
60
|
+
send, use `baark.send("Hello", device_key="YOUR_DEVICE_KEY")`.
|
|
61
|
+
|
|
62
|
+
### Async
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import asyncio
|
|
66
|
+
|
|
67
|
+
from baark import AsyncBaark
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
async def main() -> None:
|
|
71
|
+
async with AsyncBaark("YOUR_DEVICE_KEY") as client:
|
|
72
|
+
await client.send("Job finished", title="Build")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
asyncio.run(main())
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Both clients have the same options and operations; await async network calls.
|
|
79
|
+
The one-shot async helper is `baark.async_send()`.
|
|
80
|
+
|
|
81
|
+
### Notification options
|
|
82
|
+
|
|
83
|
+
Pass options to `send()`, store them in a `Message`, or set client defaults.
|
|
84
|
+
Per-call options override message fields, which override client defaults.
|
|
85
|
+
Pass `None` in a call to clear an inherited option.
|
|
86
|
+
|
|
87
|
+
| Purpose | Options |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| Content | `body`, `title`, `subtitle`, `markdown` |
|
|
90
|
+
| Media | `icon` / `logo` (aliases), `image`, `sound` |
|
|
91
|
+
| Attention | `level`, `volume`, `badge`, `call` |
|
|
92
|
+
| Interaction | `url`, `action`, `copy`, `auto_copy` |
|
|
93
|
+
| History | `group`, `archive`, `ttl` |
|
|
94
|
+
| Lifecycle | `id`, `delete` |
|
|
95
|
+
|
|
96
|
+
Use Python booleans for flags. Media URLs must be reachable by the phone.
|
|
97
|
+
See [message types](https://github.com/ngc2000/baark/blob/main/src/baark/models.py) for all options and
|
|
98
|
+
[Bark's field reference](https://github.com/Finb/Bark/blob/master/docs/en-us/tutorial.md)
|
|
99
|
+
for device behavior.
|
|
100
|
+
|
|
101
|
+
### Update and delete
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
with Baark("YOUR_DEVICE_KEY") as client:
|
|
105
|
+
client.send("Downloading…", id="download-42")
|
|
106
|
+
client.update("download-42", "Complete")
|
|
107
|
+
client.delete("download-42")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Updates replace the complete message using the same ID. They require Bark 1.5.2+
|
|
111
|
+
and bark-server 2.2.5+. Deletion removes the notification and its history entry;
|
|
112
|
+
it requires Background App Refresh and may be delayed by iOS.
|
|
113
|
+
|
|
114
|
+
## Encryption
|
|
115
|
+
|
|
116
|
+
**Use GCM for new configurations.** It encrypts the message and detects ciphertext
|
|
117
|
+
tampering. CBC and ECB are available for compatibility with existing Bark settings.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import os
|
|
121
|
+
|
|
122
|
+
from baark import Baark, Encryption
|
|
123
|
+
|
|
124
|
+
settings = Encryption(os.environ["BAARK_ENCRYPTION_KEY"]) # GCM by default
|
|
125
|
+
with Baark("YOUR_DEVICE_KEY", encryption=settings) as client:
|
|
126
|
+
client.send("Private content", title="Private title")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Use the same key, algorithm, mode, and padding in Bark's **Push Encryption**
|
|
130
|
+
settings. Keys are 16, 24, or 32 printable ASCII characters, selecting AES-128,
|
|
131
|
+
AES-192, or AES-256. Key size and mode are separate choices: a longer key does
|
|
132
|
+
not add tamper detection to CBC or ECB.
|
|
133
|
+
|
|
134
|
+
| Mode | Protection | IV / nonce in baark | Bark padding | Choose it for |
|
|
135
|
+
| --- | --- | --- | --- | --- |
|
|
136
|
+
| **GCM** (default) | Encrypts and detects tampering | Fresh 12-character nonce per message | `noPadding` | New setups |
|
|
137
|
+
| **CBC** | Encrypts; no tamper detection | Fresh 16-character IV by default | `pkcs7` | Existing CBC setups |
|
|
138
|
+
| **ECB** | Encrypts but reveals repeated blocks; no tamper detection | None | `pkcs7` | Legacy compatibility only |
|
|
139
|
+
|
|
140
|
+
An IV or nonce is a public value used alongside the key. Leave `iv` unset so
|
|
141
|
+
baark generates it for each GCM or CBC message; Bark reads it from the request.
|
|
142
|
+
Fixed GCM nonces are rejected because reuse breaks its security. Fixed CBC IVs
|
|
143
|
+
are supported for compatibility, but reuse leaks repeated message prefixes.
|
|
144
|
+
See PyCryptodome's [GCM](https://pycryptodome.readthedocs.io/en/latest/src/cipher/modern.html#gcm-mode)
|
|
145
|
+
and [CBC/ECB](https://pycryptodome.readthedocs.io/en/latest/src/cipher/classic.html)
|
|
146
|
+
documentation for the underlying modes.
|
|
147
|
+
|
|
148
|
+
To create settings, run `settings = Encryption.generate()` once, save the key, and enter
|
|
149
|
+
`settings.key`, `settings.algorithm`, `settings.mode`, and `settings.padding` in
|
|
150
|
+
Bark. It defaults to AES-256-GCM. To import an encoded key, use
|
|
151
|
+
`Encryption.from_key(value, encoding="hex")` or `encoding="base64"`; enter the
|
|
152
|
+
decoded key in Bark. Hex and base64 are encodings, not encryption methods.
|
|
153
|
+
|
|
154
|
+
Encryption hides message fields, including titles and media URLs, from the Bark
|
|
155
|
+
server. Device routing, IVs, and `id`/`delete` controls remain visible. The key is
|
|
156
|
+
never sent. Without this option, HTTPS protects transport but the server can read
|
|
157
|
+
notification content.
|
|
158
|
+
|
|
159
|
+
## Configuration and errors
|
|
160
|
+
|
|
161
|
+
Set `server_url=` for a custom server. `Baark.from_env()` and
|
|
162
|
+
`AsyncBaark.from_env()` read these optional settings from the process environment:
|
|
163
|
+
|
|
164
|
+
| Variable | Value |
|
|
165
|
+
| --- | --- |
|
|
166
|
+
| `BAARK_DEVICE_KEY` | Device key or URL ending in the key |
|
|
167
|
+
| `BAARK_SERVER_URL` | Custom server URL |
|
|
168
|
+
| `BAARK_ENCRYPTION_KEY` | Literal key matching Bark |
|
|
169
|
+
| `BAARK_ENCRYPTION_MODE` | `GCM` (default), `CBC`, or `ECB` |
|
|
170
|
+
| `BAARK_ENCRYPTION_IV` | Optional fixed CBC IV |
|
|
171
|
+
|
|
172
|
+
Ordinary constructors do not load these variables or config files. HTTP requests
|
|
173
|
+
default to a 10-second timeout, with redirects and retries disabled. Set
|
|
174
|
+
`timeout=`, `auth=`, or inject an `httpx2.Client` / `AsyncClient` with `http_client=`
|
|
175
|
+
for custom transport settings. Injected clients remain caller-owned; configure
|
|
176
|
+
authentication on them.
|
|
177
|
+
HTTP query defaults such as authentication tokens are preserved; notification and
|
|
178
|
+
routing fields come from baark's arguments and messages.
|
|
179
|
+
|
|
180
|
+
Send to multiple devices with `client.send("Hello", device_keys=["KEY_A", "KEY_B"])`.
|
|
181
|
+
Encrypted batches require all recipients to share encryption settings.
|
|
182
|
+
Partial failures raise `BatchError`; inspect `error.response.deliveries` for each
|
|
183
|
+
result. All SDK errors inherit from `BaarkError`. `APIError` exposes
|
|
184
|
+
`status_code`, `code`, and `apns_reason`; raw `server_message` is available
|
|
185
|
+
explicitly and may contain sensitive data.
|
|
186
|
+
|
|
187
|
+
For alternate endpoints, server utilities, registration, and URL building, see
|
|
188
|
+
the [client API](https://github.com/ngc2000/baark/blob/main/src/baark/client.py). Also see
|
|
189
|
+
[response and message types](https://github.com/ngc2000/baark/blob/main/src/baark/models.py),
|
|
190
|
+
[errors](https://github.com/ngc2000/baark/blob/main/src/baark/errors.py), and
|
|
191
|
+
[encryption helpers](https://github.com/ngc2000/baark/blob/main/src/baark/crypto.py).
|
|
192
|
+
|
|
193
|
+
## Alternatives
|
|
194
|
+
|
|
195
|
+
| Project | Useful when you want | Documented encryption support |
|
|
196
|
+
| --- | --- | --- |
|
|
197
|
+
| **baark** | Reusable, typed sync and async Bark clients in Python | GCM, CBC, ECB |
|
|
198
|
+
| [iskoldtbark](https://pypi.org/project/iskoldtbark/) | A Bark CLI with saved devices, recipient groups, and per-device keys | AES-256-GCM; AES-128/192/256-CBC |
|
|
199
|
+
| [bark-python](https://pypi.org/project/bark-python/) | A small client with replaceable encryption strategies | Built-in CBC and ECB; custom strategies |
|
|
200
|
+
| [Apprise](https://appriseit.com/) | One library or CLI for Bark and other notification services | [Bark AES-GCM](https://appriseit.com/services/bark/#encryption) |
|
|
201
|
+
|
|
202
|
+
For simple scripts, [Bark's HTTP API](https://github.com/Finb/Bark/blob/master/docs/en-us/tutorial.md)
|
|
203
|
+
also works directly with curl or your existing HTTP client.
|
|
204
|
+
|
|
205
|
+
## Development
|
|
206
|
+
|
|
207
|
+
Install [uv](https://docs.astral.sh/uv/), [just](https://just.systems/), and
|
|
208
|
+
[actionlint](https://github.com/rhysd/actionlint), then run `uv sync --locked`.
|
|
209
|
+
Windows development also needs Bash, as provided by Git for Windows.
|
|
210
|
+
|
|
211
|
+
### Test with your phone
|
|
212
|
+
|
|
213
|
+
Copy `.env.example` with `cp -n .env.example .env`, then edit `.env` with your
|
|
214
|
+
device key and optional encryption settings. Leave unused settings commented out.
|
|
215
|
+
|
|
216
|
+
```sh
|
|
217
|
+
uv run python scripts/e2e.py --list-cases
|
|
218
|
+
uv run --env-file .env python scripts/e2e.py --client sync --case logo image markdown
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
These selected cases send three real notifications. Check the phone, then repeat
|
|
222
|
+
with the same client and cases plus `--action update` or `--action delete`.
|
|
223
|
+
`--case all` covers nine cases; the default client selection, `both`, doubles
|
|
224
|
+
the count. Use `--help` for media URL overrides and other options.
|
|
225
|
+
|
|
226
|
+
### Offline checks
|
|
227
|
+
|
|
228
|
+
```sh
|
|
229
|
+
just check
|
|
230
|
+
just build
|
|
231
|
+
just test-wheel
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The test suite uses mock HTTP transports and encryption vectors. Coverage checks
|
|
235
|
+
enforce the threshold in `pyproject.toml`. CI tests the built wheel on Python 3.13
|
|
236
|
+
and 3.14 across Linux, macOS, and Windows.
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
[MIT](https://github.com/ngc2000/baark/blob/main/LICENSE).
|
baark-0.1.0/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# baark
|
|
2
|
+
|
|
3
|
+
A typed Python SDK for [Bark](https://bark.day.app/) push notifications. Matching
|
|
4
|
+
sync and async clients, connection pooling, rich notifications, and optional AES
|
|
5
|
+
encryption. Requires Python 3.13+; built on `httpx2` and PyCryptodome.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
From PyPI:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
uv add baark
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
Copy your device key from Bark. A URL ending in the key also works.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from baark import Baark
|
|
21
|
+
|
|
22
|
+
with Baark("YOUR_DEVICE_KEY", group="backups") as client:
|
|
23
|
+
result = client.send(
|
|
24
|
+
"The backup finished.",
|
|
25
|
+
title="Backup",
|
|
26
|
+
logo="https://example.com/backup.png",
|
|
27
|
+
url="https://example.com/backups/latest",
|
|
28
|
+
)
|
|
29
|
+
print(result.ok)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Reuse the client for repeated sends; the context manager closes its connections.
|
|
33
|
+
`result.ok` confirms server acceptance, not delivery to the phone. For a single
|
|
34
|
+
send, use `baark.send("Hello", device_key="YOUR_DEVICE_KEY")`.
|
|
35
|
+
|
|
36
|
+
### Async
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import asyncio
|
|
40
|
+
|
|
41
|
+
from baark import AsyncBaark
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def main() -> None:
|
|
45
|
+
async with AsyncBaark("YOUR_DEVICE_KEY") as client:
|
|
46
|
+
await client.send("Job finished", title="Build")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
asyncio.run(main())
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Both clients have the same options and operations; await async network calls.
|
|
53
|
+
The one-shot async helper is `baark.async_send()`.
|
|
54
|
+
|
|
55
|
+
### Notification options
|
|
56
|
+
|
|
57
|
+
Pass options to `send()`, store them in a `Message`, or set client defaults.
|
|
58
|
+
Per-call options override message fields, which override client defaults.
|
|
59
|
+
Pass `None` in a call to clear an inherited option.
|
|
60
|
+
|
|
61
|
+
| Purpose | Options |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| Content | `body`, `title`, `subtitle`, `markdown` |
|
|
64
|
+
| Media | `icon` / `logo` (aliases), `image`, `sound` |
|
|
65
|
+
| Attention | `level`, `volume`, `badge`, `call` |
|
|
66
|
+
| Interaction | `url`, `action`, `copy`, `auto_copy` |
|
|
67
|
+
| History | `group`, `archive`, `ttl` |
|
|
68
|
+
| Lifecycle | `id`, `delete` |
|
|
69
|
+
|
|
70
|
+
Use Python booleans for flags. Media URLs must be reachable by the phone.
|
|
71
|
+
See [message types](https://github.com/ngc2000/baark/blob/main/src/baark/models.py) for all options and
|
|
72
|
+
[Bark's field reference](https://github.com/Finb/Bark/blob/master/docs/en-us/tutorial.md)
|
|
73
|
+
for device behavior.
|
|
74
|
+
|
|
75
|
+
### Update and delete
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
with Baark("YOUR_DEVICE_KEY") as client:
|
|
79
|
+
client.send("Downloading…", id="download-42")
|
|
80
|
+
client.update("download-42", "Complete")
|
|
81
|
+
client.delete("download-42")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Updates replace the complete message using the same ID. They require Bark 1.5.2+
|
|
85
|
+
and bark-server 2.2.5+. Deletion removes the notification and its history entry;
|
|
86
|
+
it requires Background App Refresh and may be delayed by iOS.
|
|
87
|
+
|
|
88
|
+
## Encryption
|
|
89
|
+
|
|
90
|
+
**Use GCM for new configurations.** It encrypts the message and detects ciphertext
|
|
91
|
+
tampering. CBC and ECB are available for compatibility with existing Bark settings.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import os
|
|
95
|
+
|
|
96
|
+
from baark import Baark, Encryption
|
|
97
|
+
|
|
98
|
+
settings = Encryption(os.environ["BAARK_ENCRYPTION_KEY"]) # GCM by default
|
|
99
|
+
with Baark("YOUR_DEVICE_KEY", encryption=settings) as client:
|
|
100
|
+
client.send("Private content", title="Private title")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Use the same key, algorithm, mode, and padding in Bark's **Push Encryption**
|
|
104
|
+
settings. Keys are 16, 24, or 32 printable ASCII characters, selecting AES-128,
|
|
105
|
+
AES-192, or AES-256. Key size and mode are separate choices: a longer key does
|
|
106
|
+
not add tamper detection to CBC or ECB.
|
|
107
|
+
|
|
108
|
+
| Mode | Protection | IV / nonce in baark | Bark padding | Choose it for |
|
|
109
|
+
| --- | --- | --- | --- | --- |
|
|
110
|
+
| **GCM** (default) | Encrypts and detects tampering | Fresh 12-character nonce per message | `noPadding` | New setups |
|
|
111
|
+
| **CBC** | Encrypts; no tamper detection | Fresh 16-character IV by default | `pkcs7` | Existing CBC setups |
|
|
112
|
+
| **ECB** | Encrypts but reveals repeated blocks; no tamper detection | None | `pkcs7` | Legacy compatibility only |
|
|
113
|
+
|
|
114
|
+
An IV or nonce is a public value used alongside the key. Leave `iv` unset so
|
|
115
|
+
baark generates it for each GCM or CBC message; Bark reads it from the request.
|
|
116
|
+
Fixed GCM nonces are rejected because reuse breaks its security. Fixed CBC IVs
|
|
117
|
+
are supported for compatibility, but reuse leaks repeated message prefixes.
|
|
118
|
+
See PyCryptodome's [GCM](https://pycryptodome.readthedocs.io/en/latest/src/cipher/modern.html#gcm-mode)
|
|
119
|
+
and [CBC/ECB](https://pycryptodome.readthedocs.io/en/latest/src/cipher/classic.html)
|
|
120
|
+
documentation for the underlying modes.
|
|
121
|
+
|
|
122
|
+
To create settings, run `settings = Encryption.generate()` once, save the key, and enter
|
|
123
|
+
`settings.key`, `settings.algorithm`, `settings.mode`, and `settings.padding` in
|
|
124
|
+
Bark. It defaults to AES-256-GCM. To import an encoded key, use
|
|
125
|
+
`Encryption.from_key(value, encoding="hex")` or `encoding="base64"`; enter the
|
|
126
|
+
decoded key in Bark. Hex and base64 are encodings, not encryption methods.
|
|
127
|
+
|
|
128
|
+
Encryption hides message fields, including titles and media URLs, from the Bark
|
|
129
|
+
server. Device routing, IVs, and `id`/`delete` controls remain visible. The key is
|
|
130
|
+
never sent. Without this option, HTTPS protects transport but the server can read
|
|
131
|
+
notification content.
|
|
132
|
+
|
|
133
|
+
## Configuration and errors
|
|
134
|
+
|
|
135
|
+
Set `server_url=` for a custom server. `Baark.from_env()` and
|
|
136
|
+
`AsyncBaark.from_env()` read these optional settings from the process environment:
|
|
137
|
+
|
|
138
|
+
| Variable | Value |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `BAARK_DEVICE_KEY` | Device key or URL ending in the key |
|
|
141
|
+
| `BAARK_SERVER_URL` | Custom server URL |
|
|
142
|
+
| `BAARK_ENCRYPTION_KEY` | Literal key matching Bark |
|
|
143
|
+
| `BAARK_ENCRYPTION_MODE` | `GCM` (default), `CBC`, or `ECB` |
|
|
144
|
+
| `BAARK_ENCRYPTION_IV` | Optional fixed CBC IV |
|
|
145
|
+
|
|
146
|
+
Ordinary constructors do not load these variables or config files. HTTP requests
|
|
147
|
+
default to a 10-second timeout, with redirects and retries disabled. Set
|
|
148
|
+
`timeout=`, `auth=`, or inject an `httpx2.Client` / `AsyncClient` with `http_client=`
|
|
149
|
+
for custom transport settings. Injected clients remain caller-owned; configure
|
|
150
|
+
authentication on them.
|
|
151
|
+
HTTP query defaults such as authentication tokens are preserved; notification and
|
|
152
|
+
routing fields come from baark's arguments and messages.
|
|
153
|
+
|
|
154
|
+
Send to multiple devices with `client.send("Hello", device_keys=["KEY_A", "KEY_B"])`.
|
|
155
|
+
Encrypted batches require all recipients to share encryption settings.
|
|
156
|
+
Partial failures raise `BatchError`; inspect `error.response.deliveries` for each
|
|
157
|
+
result. All SDK errors inherit from `BaarkError`. `APIError` exposes
|
|
158
|
+
`status_code`, `code`, and `apns_reason`; raw `server_message` is available
|
|
159
|
+
explicitly and may contain sensitive data.
|
|
160
|
+
|
|
161
|
+
For alternate endpoints, server utilities, registration, and URL building, see
|
|
162
|
+
the [client API](https://github.com/ngc2000/baark/blob/main/src/baark/client.py). Also see
|
|
163
|
+
[response and message types](https://github.com/ngc2000/baark/blob/main/src/baark/models.py),
|
|
164
|
+
[errors](https://github.com/ngc2000/baark/blob/main/src/baark/errors.py), and
|
|
165
|
+
[encryption helpers](https://github.com/ngc2000/baark/blob/main/src/baark/crypto.py).
|
|
166
|
+
|
|
167
|
+
## Alternatives
|
|
168
|
+
|
|
169
|
+
| Project | Useful when you want | Documented encryption support |
|
|
170
|
+
| --- | --- | --- |
|
|
171
|
+
| **baark** | Reusable, typed sync and async Bark clients in Python | GCM, CBC, ECB |
|
|
172
|
+
| [iskoldtbark](https://pypi.org/project/iskoldtbark/) | A Bark CLI with saved devices, recipient groups, and per-device keys | AES-256-GCM; AES-128/192/256-CBC |
|
|
173
|
+
| [bark-python](https://pypi.org/project/bark-python/) | A small client with replaceable encryption strategies | Built-in CBC and ECB; custom strategies |
|
|
174
|
+
| [Apprise](https://appriseit.com/) | One library or CLI for Bark and other notification services | [Bark AES-GCM](https://appriseit.com/services/bark/#encryption) |
|
|
175
|
+
|
|
176
|
+
For simple scripts, [Bark's HTTP API](https://github.com/Finb/Bark/blob/master/docs/en-us/tutorial.md)
|
|
177
|
+
also works directly with curl or your existing HTTP client.
|
|
178
|
+
|
|
179
|
+
## Development
|
|
180
|
+
|
|
181
|
+
Install [uv](https://docs.astral.sh/uv/), [just](https://just.systems/), and
|
|
182
|
+
[actionlint](https://github.com/rhysd/actionlint), then run `uv sync --locked`.
|
|
183
|
+
Windows development also needs Bash, as provided by Git for Windows.
|
|
184
|
+
|
|
185
|
+
### Test with your phone
|
|
186
|
+
|
|
187
|
+
Copy `.env.example` with `cp -n .env.example .env`, then edit `.env` with your
|
|
188
|
+
device key and optional encryption settings. Leave unused settings commented out.
|
|
189
|
+
|
|
190
|
+
```sh
|
|
191
|
+
uv run python scripts/e2e.py --list-cases
|
|
192
|
+
uv run --env-file .env python scripts/e2e.py --client sync --case logo image markdown
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
These selected cases send three real notifications. Check the phone, then repeat
|
|
196
|
+
with the same client and cases plus `--action update` or `--action delete`.
|
|
197
|
+
`--case all` covers nine cases; the default client selection, `both`, doubles
|
|
198
|
+
the count. Use `--help` for media URL overrides and other options.
|
|
199
|
+
|
|
200
|
+
### Offline checks
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
just check
|
|
204
|
+
just build
|
|
205
|
+
just test-wheel
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The test suite uses mock HTTP transports and encryption vectors. Coverage checks
|
|
209
|
+
enforce the threshold in `pyproject.toml`. CI tests the built wheel on Python 3.13
|
|
210
|
+
and 3.14 across Linux, macOS, and Windows.
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
[MIT](https://github.com/ngc2000/baark/blob/main/LICENSE).
|
baark-0.1.0/justfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
set default-list := true
|
|
2
|
+
set dotenv-load := false
|
|
3
|
+
set shell := ["bash", "-euo", "pipefail", "-c"]
|
|
4
|
+
set windows-shell := ["bash", "-euo", "pipefail", "-c"]
|
|
5
|
+
|
|
6
|
+
# Format Python source.
|
|
7
|
+
format:
|
|
8
|
+
uv run --locked python -m ruff format .
|
|
9
|
+
|
|
10
|
+
# Check the lockfile, lint, formatting, and types without changing files.
|
|
11
|
+
lint:
|
|
12
|
+
uv lock --check
|
|
13
|
+
uv run --locked python -m ruff check .
|
|
14
|
+
uv run --locked python -m ruff format --check .
|
|
15
|
+
uv run --locked python -m ty check
|
|
16
|
+
|
|
17
|
+
# Validate GitHub Actions (requires actionlint).
|
|
18
|
+
lint-workflows:
|
|
19
|
+
actionlint .github/workflows/*.yml
|
|
20
|
+
|
|
21
|
+
# Run offline tests and enforce coverage.
|
|
22
|
+
test:
|
|
23
|
+
uv run --locked python -m coverage run
|
|
24
|
+
uv run --locked python -m coverage report
|
|
25
|
+
|
|
26
|
+
# Run all source and workflow checks.
|
|
27
|
+
check: lint lint-workflows test
|
|
28
|
+
|
|
29
|
+
# Build fresh distributions and check their metadata and contents.
|
|
30
|
+
build:
|
|
31
|
+
uv build --clear --no-sources
|
|
32
|
+
uv run --locked python -m twine check --strict dist/*
|
|
33
|
+
uv run --locked python scripts/check_dist.py
|
|
34
|
+
|
|
35
|
+
# Install the built wheel, then run the offline suite against that installation.
|
|
36
|
+
test-wheel:
|
|
37
|
+
uv sync --locked --no-install-project
|
|
38
|
+
uv pip install --no-deps --reinstall dist/*.whl
|
|
39
|
+
uv run --no-sync python -I scripts/check_wheel.py
|
|
40
|
+
UV_NO_SYNC=true just test
|