ankiweb-api 1.0.0__py3-none-any.whl
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.
- ankicli.py +214 -0
- ankiweb_api-1.0.0.dist-info/METADATA +574 -0
- ankiweb_api-1.0.0.dist-info/RECORD +14 -0
- ankiweb_api-1.0.0.dist-info/WHEEL +4 -0
- ankiweb_api-1.0.0.dist-info/entry_points.txt +4 -0
- ankiweb_api-1.0.0.dist-info/licenses/LICENSE +21 -0
- ankiweb_auth.py +148 -0
- ankiweb_client.py +405 -0
- ankiweb_engine/index.js +44894 -0
- ankiweb_proto.py +372 -0
- ankiweb_schema.py +49 -0
- make_test_addon.py +66 -0
- terminal_ankiweb.py +342 -0
- upload.py +195 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ankiweb-api
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Full AnkiWeb /svc/* API client plus add-on upload/download tooling for the terminal and GitHub Actions
|
|
5
|
+
Project-URL: Homepage, https://github.com/athulkrishna2015/ankiweb-api
|
|
6
|
+
Project-URL: Issues, https://github.com/athulkrishna2015/ankiweb-api/issues
|
|
7
|
+
Author: Tzu-Hsien Chao
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 CHAO TZU-HSIEN
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: anki,ankiaddon,ankiweb,cli,protobuf
|
|
31
|
+
Classifier: Environment :: Console
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Topic :: Utilities
|
|
35
|
+
Requires-Python: >=3.9
|
|
36
|
+
Requires-Dist: requests>=2.31
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# AnkiWeb API — full `/svc/*` compatibility + add-on tools
|
|
40
|
+
|
|
41
|
+
[](https://github.com/athulkrishna2015/ankiweb-api/actions/workflows/ci.yml)
|
|
42
|
+
|
|
43
|
+
A complete Python client for AnkiWeb's reverse-engineered `/svc/*` API —
|
|
44
|
+
**all 41 public endpoints** as typed `AnkiWebClient` methods — plus tools to
|
|
45
|
+
inspect, download, and upload [Anki](https://apps.ankiweb.net/) add-ons from
|
|
46
|
+
your terminal or GitHub Actions.
|
|
47
|
+
|
|
48
|
+
> Fork of [danny900714/upload-anki-addon](https://github.com/danny900714/upload-anki-addon) (`upstream/main` remote).
|
|
49
|
+
> The Node upload engine (`dist/index.js`) is unmodified upstream code. This repository adds terminal tooling and a full schema-driven API client decoded from AnkiWeb's own frontend bundle.
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
- **Full API coverage**: All 41 public `/svc/*` endpoints as typed methods
|
|
54
|
+
([reference](docs/ENDPOINTS.md)) — account, decks, editor, search, shared,
|
|
55
|
+
study — decoded from AnkiWeb's own frontend bundle, verified live.
|
|
56
|
+
- **Multiple branches**: Upload different add-on files targeting different Anki version ranges.
|
|
57
|
+
- **Description file**: Use a separate file for the add-on description, making it easier to edit and format.
|
|
58
|
+
- **Terminal CLI**: Upload, inspect, download, and call any endpoint (`ankicli.py call ...`) without GitHub Actions.
|
|
59
|
+
- **Metadata-only updates**: Change title/description/tags without re-uploading the `.ankiaddon`.
|
|
60
|
+
|
|
61
|
+
## Table of contents
|
|
62
|
+
|
|
63
|
+
- [Python API client](#python-api-client-full-svc-coverage) — [quick start](#quick-start) · [endpoint reference](docs/ENDPOINTS.md)
|
|
64
|
+
- [AnkiWeb API docs](docs/API.md) · [full endpoint reference](docs/ENDPOINTS.md)
|
|
65
|
+
- [Terminal usage](#terminal-usage) — [setup](#setup) · [upload](#upload) · [info](#info-add-on-metadata) · [download](#download-ankiaddon) · [notes](#terminal-notes)
|
|
66
|
+
- [List your own shared items](#list-your-own-shared-items)
|
|
67
|
+
- [Tests](#tests)
|
|
68
|
+
- [GitHub Actions usage](#github-actions-usage) — [inputs](#inputs) · [outputs](#outputs)
|
|
69
|
+
|
|
70
|
+
## Python API client (full `/svc/*` coverage)
|
|
71
|
+
|
|
72
|
+
All **41 public endpoints** are typed methods on `AnkiWebClient` — account (12),
|
|
73
|
+
decks (8), editor (3), search (1), shared (12), and study (4). The wire schema
|
|
74
|
+
is decoded from AnkiWeb's own frontend bundle (revision `2026-10-10`, all
|
|
75
|
+
request/response/nested messages resolved) and verified live. Full field-level
|
|
76
|
+
reference: [docs/ENDPOINTS.md](docs/ENDPOINTS.md).
|
|
77
|
+
|
|
78
|
+
### Quick start
|
|
79
|
+
|
|
80
|
+
Requires Python 3.9+ with `requests` (same install as the terminal tools):
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from ankiweb_client import AnkiWebClient
|
|
84
|
+
|
|
85
|
+
client = AnkiWebClient() # auto-loads .env (ANKIWEB_USERNAME/PASSWORD)
|
|
86
|
+
client.login()
|
|
87
|
+
|
|
88
|
+
mine = client.shared_list_mine() # your shared add-ons & decks
|
|
89
|
+
for item in mine["items"]:
|
|
90
|
+
print(item["id"], item["title"])
|
|
91
|
+
|
|
92
|
+
# Public, no auth needed:
|
|
93
|
+
item = client.shared_item_info(shared_id=2055492159)
|
|
94
|
+
print(item["available"]["title"])
|
|
95
|
+
|
|
96
|
+
# The rest of the surface:
|
|
97
|
+
client.decks_create_deck("My Deck")
|
|
98
|
+
client.search_search("deck:My Deck")
|
|
99
|
+
info = client.decks_deck_list_info(minutes_west_of_utc=0)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Method names mirror the path (`/svc/shared/list-mine` → `shared_list_mine`).
|
|
103
|
+
Legacy aliases `login`, `list_mine`, and `item_info` still work.
|
|
104
|
+
|
|
105
|
+
### Any endpoint from the terminal
|
|
106
|
+
|
|
107
|
+
`ankicli.py` can call any public endpoint and print decoded JSON:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
./ankicli.py endpoints # list all 41 with request/response types
|
|
111
|
+
./ankicli.py call shared_item_info --query sharedId=2055492159
|
|
112
|
+
./ankicli.py call shared_list_addons # public, no auth
|
|
113
|
+
./ankicli.py call decks_create_deck --request '{"name": "New Deck"}' # uses .env
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Keeping the schema fresh
|
|
117
|
+
|
|
118
|
+
AnkiWeb ships no official API, so the schema can change. Regenerate and review
|
|
119
|
+
the diff whenever something breaks:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
python3 tools/generate_schema.py # re-decode the live frontend bundle
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Details: [docs/API.md](docs/API.md) (client usage, regeneration, codec) and
|
|
126
|
+
[docs/ENDPOINTS.md](docs/ENDPOINTS.md) (every endpoint, field, and enum).
|
|
127
|
+
|
|
128
|
+
## Terminal usage
|
|
129
|
+
|
|
130
|
+
Requires Python 3.9+ with `requests`, plus Node 24 if you upload (the action engine runs on Node).
|
|
131
|
+
`info` and `download` are Python-only and need no Node.
|
|
132
|
+
|
|
133
|
+
### Setup
|
|
134
|
+
|
|
135
|
+
Install as a command:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
uv tool install ankiweb-api # provides: ankiweb, ankiweb-upload, ankiweb-test-addon
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Or run it straight from a checkout:
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
git clone https://github.com/athulkrishna2015/ankiweb-api.git
|
|
145
|
+
cd ankiweb-api
|
|
146
|
+
uv run --with requests ./ankicli.py info 805854302
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Keep credentials in `.env` (gitignored — never commit it). The tools look for `.env` in
|
|
150
|
+
the current directory first, then next to the script:
|
|
151
|
+
|
|
152
|
+
```sh
|
|
153
|
+
cp .env.example .env
|
|
154
|
+
$EDITOR .env # ANKIWEB_USERNAME=... / ANKIWEB_PASSWORD=...
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`upload.py` reads `./.env` automatically. Exported shell variables take precedence, and
|
|
158
|
+
`--username` / `--password` override both. When installed as a tool, point the engine at
|
|
159
|
+
your own Node build with `ANKIWEB_ENGINE=/path/to/dist/index.js`.
|
|
160
|
+
|
|
161
|
+
Every example below uses the in-checkout form; the installed commands are equivalent:
|
|
162
|
+
|
|
163
|
+
| Script | Installed command |
|
|
164
|
+
| ---------------------- | -------------------- |
|
|
165
|
+
| `./ankicli.py …` | `ankiweb …` |
|
|
166
|
+
| `./upload.py …` | `ankiweb-upload …` |
|
|
167
|
+
| `./make_test_addon.py` | `ankiweb-test-addon` |
|
|
168
|
+
|
|
169
|
+
> [!IMPORTANT]
|
|
170
|
+
> If you build the wheel yourself, send the artifacts somewhere other than `dist/` —
|
|
171
|
+
> that directory holds `dist/index.js`, the action's upload engine, and CI diffs it:
|
|
172
|
+
> `uv build --out-dir /tmp/wheel`.
|
|
173
|
+
|
|
174
|
+
#### Releasing to PyPI
|
|
175
|
+
|
|
176
|
+
Publishing uses **trusted publishing (OIDC)** — there is no API token stored anywhere.
|
|
177
|
+
`.github/workflows/publish.yml` builds on a tag and uploads with a short-lived token
|
|
178
|
+
that PyPI exchanges for an upload token.
|
|
179
|
+
|
|
180
|
+
One-time setup, once the project exists:
|
|
181
|
+
|
|
182
|
+
1. Create the project `ankiweb-api` on PyPI (under `athulkrishna2015`).
|
|
183
|
+
2. On that project: **Manage → Publishing → Add a pending publisher**:
|
|
184
|
+
|
|
185
|
+
| Field | Value |
|
|
186
|
+
| ----------------- | ------------------ |
|
|
187
|
+
| PyPI project name | `ankiweb-api` |
|
|
188
|
+
| Owner | `athulkrishna2015` |
|
|
189
|
+
| Repository name | `ankiweb-api` |
|
|
190
|
+
| Workflow name | `publish.yml` |
|
|
191
|
+
|
|
192
|
+
3. Publish a release — `workflow_dispatch` on that workflow, or tag a release, and it
|
|
193
|
+
builds and uploads. The first successful upload activates the pending publisher;
|
|
194
|
+
the name is then permanently claimed.
|
|
195
|
+
|
|
196
|
+
### Upload
|
|
197
|
+
|
|
198
|
+
Same inputs as the action, as CLI flags. `upload.py` maps them to the action's `INPUT_*`
|
|
199
|
+
variables and runs the upstream engine — no separate upload implementation.
|
|
200
|
+
|
|
201
|
+
> [!WARNING]
|
|
202
|
+
> **Never test uploads with a real add-on file.** An upload replaces the file on the target
|
|
203
|
+
> add-on id, and the previous version is **not** recoverable from AnkiWeb. Build the synthetic
|
|
204
|
+
> fixture instead and use a throwaway add-on id:
|
|
205
|
+
>
|
|
206
|
+
> ```sh
|
|
207
|
+
> python3 make_test_addon.py # -> fixtures/test.ankiaddon
|
|
208
|
+
> ./upload.py --addon-id <test-id> --title "CLI Test" \
|
|
209
|
+
> --description "test" --branches "2.1.1-2.1.1+" \
|
|
210
|
+
> --addon-files fixtures/test.ankiaddon
|
|
211
|
+
> ```
|
|
212
|
+
|
|
213
|
+
```sh
|
|
214
|
+
# New add-on (omit --addon-id)
|
|
215
|
+
./upload.py --title "My Anki add-on" \
|
|
216
|
+
--description "Description for my add-on" \
|
|
217
|
+
--branches "2.1.1-25.09.4+" \
|
|
218
|
+
--addon-files my-addon.ankiaddon
|
|
219
|
+
|
|
220
|
+
# Update an existing add-on, with description from a file
|
|
221
|
+
./upload.py --addon-id 805854302 --title "My Anki add-on" \
|
|
222
|
+
--description-file DESCRIPTION.md \
|
|
223
|
+
--branches "2.1.1-25.09.4+" \
|
|
224
|
+
--addon-files my-addon.ankiaddon
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Metadata-only** — update title/description/tags with no file upload. Use `-` for the file,
|
|
228
|
+
the same convention the action uses:
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
./upload.py --addon-id 805854302 --title "My Anki add-on" \
|
|
232
|
+
--description "Updated description" \
|
|
233
|
+
--branches "2.1.1-2.1.1+" \
|
|
234
|
+
--addon-files -
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Useful flags:
|
|
238
|
+
|
|
239
|
+
| Flag | Description |
|
|
240
|
+
| -------------------- | ------------------------------------------------------------ |
|
|
241
|
+
| `--tags "a b"` | Space-separated tags. |
|
|
242
|
+
| `--support-page URL` | Must start with `http`. |
|
|
243
|
+
| `--dry-run` | Print the `INPUT_*` mapping and exit without network access. |
|
|
244
|
+
| `--env-file PATH` | Use a different `.env` (default `./.env`). |
|
|
245
|
+
| `--node PATH` | Node binary to run the engine with (default `node`). |
|
|
246
|
+
|
|
247
|
+
Multi-branch works the same way — one branch and one file per line, in matching order:
|
|
248
|
+
|
|
249
|
+
```sh
|
|
250
|
+
./upload.py --addon-id 805854302 --title "My Anki add-on" --description "Desc" \
|
|
251
|
+
--branches "2.1.1-2.1.66" "23.10-25.09.4+" \
|
|
252
|
+
--addon-files first.ankiaddon second.ankiaddon
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
> [!IMPORTANT]
|
|
256
|
+
> Branch notation is `min-max`. A bare `2.1.1+` is rejected. Trailing `+` means
|
|
257
|
+
> "and all future versions". Reproduce an existing range by reading it first:
|
|
258
|
+
> `minpt`/`maxpt` from `./ankicli.py download <id> -o -` output map to it.
|
|
259
|
+
|
|
260
|
+
### List your own shared items
|
|
261
|
+
|
|
262
|
+
Everything on your account (add-ons and decks), with download counts and votes.
|
|
263
|
+
Uses the credentials from `.env`.
|
|
264
|
+
|
|
265
|
+
```sh
|
|
266
|
+
./ankicli.py mine
|
|
267
|
+
./ankicli.py mine --json
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```text
|
|
271
|
+
22 shared item(s), 46 review(s):
|
|
272
|
+
|
|
273
|
+
ID UPD DL + - TITLE
|
|
274
|
+
------------------------------------------------
|
|
275
|
+
2119980872 2026-10-10 856 3 1 AI-Hints and Options
|
|
276
|
+
226796325 2026-09-25 2080 5 2 Background Auto Sync
|
|
277
|
+
196373966 2026-07-17 4245 7 1 csv import plus
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Pair it with `info` for full metadata on any id from the list:
|
|
281
|
+
|
|
282
|
+
```sh
|
|
283
|
+
for id in $(./ankicli.py mine --json | jq -r '.[].id'); do ./ankicli.py info "$id"; done
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Info (add-on metadata)
|
|
287
|
+
|
|
288
|
+
Public, no auth. Reads title, tags, support page, size, versions, and description
|
|
289
|
+
from AnkiWeb's `svc/shared/item-info` protobuf endpoint.
|
|
290
|
+
|
|
291
|
+
```sh
|
|
292
|
+
./ankicli.py info 805854302
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
```text
|
|
296
|
+
id: 805854302
|
|
297
|
+
title: Integration test for ankiweb client of @athulkrishna2015/ankiweb-api
|
|
298
|
+
tags: integration test tag
|
|
299
|
+
support: https://github.com/danny900714/upload-anki-addon
|
|
300
|
+
size: 172
|
|
301
|
+
last_updated: 1791704155 (2026-10-11T07:35:55+00:00)
|
|
302
|
+
versions: 2.1.1-25.09.4+
|
|
303
|
+
description:
|
|
304
|
+
<p>Throwaway fixture for upload integration tests. Limit probe: 65535 bytes OK, 65536 rejected.</p>
|
|
305
|
+
|
|
306
|
+
verify: https://ankiweb.net/shared/info/805854302?cb=1791710745423
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Download `.ankiaddon`
|
|
310
|
+
|
|
311
|
+
Public, no auth. Follows the `/shared/download/<id>` redirect and writes the file.
|
|
312
|
+
|
|
313
|
+
```sh
|
|
314
|
+
./ankicli.py download 805854302
|
|
315
|
+
./ankicli.py download 805854302 -o /tmp/test.ankiaddon
|
|
316
|
+
./ankicli.py download 805854302 --point-version 250902 # Anki int_version
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
```text
|
|
320
|
+
saved: Integration_test_for_ankiweb_client_of_athulkrishna2015ankiweb-api.ankiaddon
|
|
321
|
+
filename: Integration_test_for_ankiweb_client_of_athulkrishna2015ankiweb-api.ankiaddon
|
|
322
|
+
size: 172
|
|
323
|
+
t=1791704155 minpt=1 maxpt=250904 bidx=0
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Equivalent one-liner, no Python needed:
|
|
327
|
+
|
|
328
|
+
```sh
|
|
329
|
+
curl -sL "https://ankiweb.net/shared/download/805854302?v=2.1&p=250902" -o addon.ankiaddon
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Terminal notes
|
|
333
|
+
|
|
334
|
+
- **Description size limit: 65,535 bytes.** Measured on 2026-10-11 by probing
|
|
335
|
+
the throwaway add-on — 65,535 stores, 65,536 is rejected. An over-limit
|
|
336
|
+
upload is **rejected, not truncated**: the add-on keeps its previous
|
|
337
|
+
description, so verify after every large upload.
|
|
338
|
+
- `info` prints a `verify:` line with a cache-busted link. AnkiWeb serves
|
|
339
|
+
`item-info` with `max-age=600`, so a plain `/shared/info/<id>` URL can show
|
|
340
|
+
up to 10 minutes of stale data; the `cb` query is required to see what was
|
|
341
|
+
just uploaded.
|
|
342
|
+
- `v=2.1` is required on the download URL; without it AnkiWeb replies 404
|
|
343
|
+
("Your version of Anki is too old"). `p` is Anki's `int_version()`.
|
|
344
|
+
- The `.ankiaddon` must be a zip with `__init__.py` at the root — no wrapping
|
|
345
|
+
folder, no `__pycache__` (see [Anki's sharing docs](https://addon-docs.ankiweb.net/sharing.html)).
|
|
346
|
+
`make_test_addon.py` builds a compliant fixture you can inspect.
|
|
347
|
+
- Never commit `.env` or AnkiWeb credentials.
|
|
348
|
+
|
|
349
|
+
## Tests
|
|
350
|
+
|
|
351
|
+
```sh
|
|
352
|
+
python3 test_terminal_ankiweb.py # offline, no network
|
|
353
|
+
python3 test_terminal_ankiweb.py --live # + live AnkiWeb round-trips
|
|
354
|
+
python3 -m unittest -v test_ankiweb_proto.py # API codec and schema regressions
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Offline tests parse real captured responses in `fixtures/` (private add-on and
|
|
358
|
+
AnkiConnect, which carries 133 KB of reviews) and cover CLI exit codes.
|
|
359
|
+
Live tests hit `info` / `download` / error paths. API client and codec
|
|
360
|
+
documentation is in [docs/API.md](docs/API.md); the complete field-level
|
|
361
|
+
reference for all 41 public endpoints is [docs/ENDPOINTS.md](docs/ENDPOINTS.md).
|
|
362
|
+
|
|
363
|
+
### Live upload verification (GitHub Actions engine)
|
|
364
|
+
|
|
365
|
+
Verified live on 2026-10-11 against the private throwaway add-on
|
|
366
|
+
[805854302](https://ankiweb.net/shared/info/805854302) — saved as
|
|
367
|
+
`ANKIWEB_TEST_ADDON_ID` in the gitignored `.env` (never committed), used by
|
|
368
|
+
local runs and the CI `test-action` / integration jobs (`vars.ADDON_ID`,
|
|
369
|
+
`vars.INTEGRATION_TEST_ADDON_ID`):
|
|
370
|
+
|
|
371
|
+
```sh
|
|
372
|
+
python3 make_test_addon.py -o /tmp/gh-action-test.ankiaddon
|
|
373
|
+
./upload.py --addon-id "$ANKIWEB_TEST_ADDON_ID" --title "testingaddon for me [private]" \
|
|
374
|
+
--description "test upload via GitHub-Actions engine - 2026-10-11T07:28:56Z" \
|
|
375
|
+
--branches "2.1.1-25.09.4+" --addon-files /tmp/gh-action-test.ankiaddon
|
|
376
|
+
./ankicli.py info "$ANKIWEB_TEST_ADDON_ID" # new description + timestamp confirmed
|
|
377
|
+
./ankicli.py download "$ANKIWEB_TEST_ADDON_ID" -o /tmp/verify.ankiaddon # sha256 matches upload
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
`upload.py` execs `node dist/index.js` — the exact engine the
|
|
381
|
+
`test-action` CI job runs — so this covers the GitHub Actions upload path.
|
|
382
|
+
Downloaded bytes were sha256-identical to the uploaded fixture.
|
|
383
|
+
|
|
384
|
+
Same upload verified from real runners: CI run `38121891143` (2026-10-11) is
|
|
385
|
+
fully green, with the `test-action` job uploading from `ubuntu-latest`:
|
|
386
|
+
|
|
387
|
+
```text
|
|
388
|
+
Successfully login to AnkiWeb.
|
|
389
|
+
Find addon file in test-addon-1.ankiaddon. Uploading it to 2.1.1-25.09.4+ branch ...
|
|
390
|
+
Successfully uploaded addon file: test-addon-1.ankiaddon.
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Note: AnkiWeb requires site-wide unique add-on titles, so the fork's CI test
|
|
394
|
+
titles are qualified with `@athulkrishna2015/ankiweb-api` — the inherited
|
|
395
|
+
`@danny900714` titles are already taken and fail with `400 title must be unique`.
|
|
396
|
+
|
|
397
|
+
## GitHub Actions usage
|
|
398
|
+
|
|
399
|
+
The action itself is unchanged from upstream. Use this fork's reference if you want the
|
|
400
|
+
terminal additions from CI; otherwise `danny900714/upload-anki-addon@v1` behaves identically.
|
|
401
|
+
|
|
402
|
+
### Upload a new add-on to AnkiWeb
|
|
403
|
+
|
|
404
|
+
If `addon-id` is not provided, a new add-on will be created on AnkiWeb.
|
|
405
|
+
|
|
406
|
+
Specify the Anki versions your add-on supports using the `branches` parameter.
|
|
407
|
+
Use the `+` symbol for the max version to indicate that your add-on supports all future versions as well.
|
|
408
|
+
|
|
409
|
+
```yaml
|
|
410
|
+
name: Release
|
|
411
|
+
|
|
412
|
+
on:
|
|
413
|
+
release:
|
|
414
|
+
types: [published]
|
|
415
|
+
|
|
416
|
+
permissions:
|
|
417
|
+
contents: read
|
|
418
|
+
|
|
419
|
+
jobs:
|
|
420
|
+
upload-addon:
|
|
421
|
+
runs-on: ubuntu-latest
|
|
422
|
+
steps:
|
|
423
|
+
- name: Checkout
|
|
424
|
+
uses: actions/checkout@v6
|
|
425
|
+
- name: Package add-on
|
|
426
|
+
run: cd src/my-addon && zip -r ../../my-addon.ankiaddon *
|
|
427
|
+
- name: Upload add-on to AnkiWeb
|
|
428
|
+
uses: danny900714/upload-anki-addon@v1
|
|
429
|
+
with:
|
|
430
|
+
username: ${{ vars.ANKIWEB_USERNAME }}
|
|
431
|
+
password: ${{ secrets.ANKIWEB_PASSWORD }}
|
|
432
|
+
title: My Anki add-on
|
|
433
|
+
description: Description for my Anki add-on
|
|
434
|
+
branches: 2.1.1-25.09.4+
|
|
435
|
+
addon-files: my-addon.ankiaddon
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
> [!TIP]
|
|
439
|
+
> Package the add-on before uploading. `zip -r ../../my-addon.ankiaddon *` from inside the
|
|
440
|
+
> add-on folder keeps `__init__.py` at the zip root (no wrapping folder, no `__pycache__`),
|
|
441
|
+
> which is what AnkiWeb requires and what `./upload.py --addon-files` expects locally.
|
|
442
|
+
|
|
443
|
+
### Using a description file
|
|
444
|
+
|
|
445
|
+
You can use a separate file for the add-on description, which makes it easier to edit and format.
|
|
446
|
+
Note that `description` takes precedence over `description-file` if both are provided.
|
|
447
|
+
|
|
448
|
+
```yaml
|
|
449
|
+
name: Release
|
|
450
|
+
|
|
451
|
+
on:
|
|
452
|
+
release:
|
|
453
|
+
types: [published]
|
|
454
|
+
|
|
455
|
+
permissions:
|
|
456
|
+
contents: read
|
|
457
|
+
|
|
458
|
+
jobs:
|
|
459
|
+
upload-addon:
|
|
460
|
+
runs-on: ubuntu-latest
|
|
461
|
+
steps:
|
|
462
|
+
- name: Checkout
|
|
463
|
+
uses: actions/checkout@v6
|
|
464
|
+
- name: Package add-on
|
|
465
|
+
run: cd src/my-addon && zip -r ../../my-addon.ankiaddon *
|
|
466
|
+
- name: Upload add-on to AnkiWeb
|
|
467
|
+
uses: danny900714/upload-anki-addon@v1
|
|
468
|
+
with:
|
|
469
|
+
username: ${{ vars.ANKIWEB_USERNAME }}
|
|
470
|
+
password: ${{ secrets.ANKIWEB_PASSWORD }}
|
|
471
|
+
addon-id: 12345678
|
|
472
|
+
title: My Anki add-on
|
|
473
|
+
description-file: README.md
|
|
474
|
+
branches: 2.1.1-25.09.4+
|
|
475
|
+
addon-files: my-addon.ankiaddon
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Multiple branches
|
|
479
|
+
|
|
480
|
+
You can target multiple Anki version ranges by specifying multiple branches, one per line.
|
|
481
|
+
The number of `addon-files` must match the number of `branches`.
|
|
482
|
+
|
|
483
|
+
```yaml
|
|
484
|
+
name: Release
|
|
485
|
+
|
|
486
|
+
on:
|
|
487
|
+
release:
|
|
488
|
+
types: [published]
|
|
489
|
+
|
|
490
|
+
permissions:
|
|
491
|
+
contents: read
|
|
492
|
+
|
|
493
|
+
jobs:
|
|
494
|
+
upload-addon:
|
|
495
|
+
runs-on: ubuntu-latest
|
|
496
|
+
steps:
|
|
497
|
+
- name: Checkout
|
|
498
|
+
uses: actions/checkout@v6
|
|
499
|
+
- name: Package add-on for first branch
|
|
500
|
+
run: cd src/first-branch && zip -r ../../first-branch.ankiaddon *
|
|
501
|
+
- name: Package add-on for second branch
|
|
502
|
+
run: cd src/second-branch && zip -r ../../second-branch.ankiaddon *
|
|
503
|
+
- name: Upload add-on to AnkiWeb
|
|
504
|
+
uses: danny900714/upload-anki-addon@v1
|
|
505
|
+
with:
|
|
506
|
+
username: ${{ vars.ANKIWEB_USERNAME }}
|
|
507
|
+
password: ${{ secrets.ANKIWEB_PASSWORD }}
|
|
508
|
+
addon-id: 12345678
|
|
509
|
+
title: My Anki add-on
|
|
510
|
+
description: Description for my Anki add-on
|
|
511
|
+
branches: |
|
|
512
|
+
2.1.1-2.1.66
|
|
513
|
+
23.10-25.09.4+
|
|
514
|
+
addon-files: |
|
|
515
|
+
first-branch.ankiaddon
|
|
516
|
+
second-branch.ankiaddon
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## Customizing
|
|
520
|
+
|
|
521
|
+
### Inputs
|
|
522
|
+
|
|
523
|
+
The following inputs can be used as `step.with` keys:
|
|
524
|
+
|
|
525
|
+
> `List` type is a newline-delimited string
|
|
526
|
+
>
|
|
527
|
+
> ```yaml
|
|
528
|
+
> branches: |
|
|
529
|
+
> 2.1.1-2.1.66
|
|
530
|
+
> 23.10-25.09.4+
|
|
531
|
+
> ```
|
|
532
|
+
|
|
533
|
+
| Name | Type | Required | Description |
|
|
534
|
+
| ------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
535
|
+
| `username` | String | true | AnkiWeb username. |
|
|
536
|
+
| `password` | String | true | AnkiWeb password. |
|
|
537
|
+
| `addon-id` | String | false | The download code for the add-on. Omit this when uploading a new add-on. |
|
|
538
|
+
| `title` | String | true | The title of the add-on. Must be fewer than 80 characters. |
|
|
539
|
+
| `tags` | String | false | Space-separated tags for this add-on. |
|
|
540
|
+
| `support-page` | String | false | A bug tracker, forum link, or other page where users can reach you. Must start with http. |
|
|
541
|
+
| `branches` | List | true | A list of Anki version branches that your add-on supports. Each branch must be in the format `min-max`, where `min` and `max` are Anki versions. Use `+` for the max version to indicate support for all future versions. |
|
|
542
|
+
| `addon-files` | List | true | A list of add-on files to upload, in the same order as `branches`. Use `-` to skip updating the add-on file for a branch. |
|
|
543
|
+
| `description` | String | false | The description of the add-on. Markdown and basic HTML are supported. AnkiWeb **rejects** descriptions larger than 65,535 bytes — the upload is refused and the add-on keeps its previous description. |
|
|
544
|
+
| `description-file` | String | false | Path to a file containing the add-on description. Markdown and basic HTML are supported. Ignored if `description` is also provided. |
|
|
545
|
+
|
|
546
|
+
> [!NOTE]
|
|
547
|
+
>
|
|
548
|
+
> - `description` takes precedence over `description-file` if both are provided.
|
|
549
|
+
> - The number of `addon-files` must match the number of `branches`.
|
|
550
|
+
|
|
551
|
+
### Terminal flag equivalents
|
|
552
|
+
|
|
553
|
+
`upload.py` takes the same values as CLI flags. Each input maps 1:1:
|
|
554
|
+
|
|
555
|
+
| Input | Terminal flag | Notes |
|
|
556
|
+
| ------------------ | -------------------- | ------------------------------------------- |
|
|
557
|
+
| `username` | `--username` | Default `$ANKIWEB_USERNAME` / `.env`. |
|
|
558
|
+
| `password` | `--password` | Default `$ANKIWEB_PASSWORD` / `.env`. |
|
|
559
|
+
| `addon-id` | `--addon-id` | Omit to create a new add-on. |
|
|
560
|
+
| `title` | `--title` | Required, fewer than 80 characters. |
|
|
561
|
+
| `tags` | `--tags "a b"` | Space-separated. |
|
|
562
|
+
| `support-page` | `--support-page` | Must start with `http`. |
|
|
563
|
+
| `branches` | `--branches a b` | Repeat the flag once per branch. |
|
|
564
|
+
| `addon-files` | `--addon-files a b` | `-` skips a branch's file. |
|
|
565
|
+
| `description` | `--description` | Takes precedence over `--description-file`. |
|
|
566
|
+
| `description-file` | `--description-file` | Read from disk. |
|
|
567
|
+
|
|
568
|
+
### Outputs
|
|
569
|
+
|
|
570
|
+
The following outputs can be accessed via `${{ steps.<step-id>.outputs }}`:
|
|
571
|
+
|
|
572
|
+
| Name | Type | Description |
|
|
573
|
+
| ---------- | ------ | ------------------------------------ |
|
|
574
|
+
| `addon-id` | String | The id for the uploaded Anki add-on. |
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
ankicli.py,sha256=Ej0DSVxZOC-_yl6XkZjzCrNRx88VwvBQamCpIaY29mU,7698
|
|
2
|
+
ankiweb_auth.py,sha256=sMP1swHtKKFrMf_ui1YDrlVBwYu8NC-9ivd7T_ceDLU,4301
|
|
3
|
+
ankiweb_client.py,sha256=rR7dxZe7RV1AH0f9o1STcwP3NFhGJNdGgbI6XqD5lGc,17478
|
|
4
|
+
ankiweb_proto.py,sha256=Ero6lCv3b2ps8tViOFsSCytKR1_P1xAdsoJ8DOn0CBY,12859
|
|
5
|
+
ankiweb_schema.py,sha256=yijZtZsQULV1h1Rm3kfGKRrTa2hcQEPUtVEOjNPEjro,82690
|
|
6
|
+
make_test_addon.py,sha256=fcUhKT8UMpd5ZbRWzz-JaIt9JiOBNfMweeTy5Q7YRGA,1968
|
|
7
|
+
terminal_ankiweb.py,sha256=SIuMKgNVe7x4km_7KUipDFQGZE-nm26Bp6pkM9ukAoM,13525
|
|
8
|
+
upload.py,sha256=KnPPTKq2EZDFrHNkziONHVSkCTgU_qcZFtEIgfhFE_o,7763
|
|
9
|
+
ankiweb_engine/index.js,sha256=gLRj5VCoYm6W5vELzMNv64wKkkXajutn2ONzaNced44,1837307
|
|
10
|
+
ankiweb_api-1.0.0.dist-info/METADATA,sha256=wOjOYAENTlVk57bNiYNjEo1ZF8-Jq876TaGgo8dSaAI,24791
|
|
11
|
+
ankiweb_api-1.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
12
|
+
ankiweb_api-1.0.0.dist-info/entry_points.txt,sha256=9-RjWH3EEMlerQKVOvjAH9yZ1zTRs9eu9OaIW3KvPVg,112
|
|
13
|
+
ankiweb_api-1.0.0.dist-info/licenses/LICENSE,sha256=W4gjKT-xbYVxU6uxy3eIkru3QDMbijo4fBsWlIJ_MYE,1070
|
|
14
|
+
ankiweb_api-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CHAO TZU-HSIEN
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|