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.
@@ -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
+ [![CI](https://github.com/athulkrishna2015/ankiweb-api/actions/workflows/ci.yml/badge.svg)](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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ ankiweb = ankicli:main
3
+ ankiweb-test-addon = make_test_addon:main
4
+ ankiweb-upload = upload:main
@@ -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.