@follenfang/fupload 0.0.0-bootstrap.0 → 0.0.2
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.
- package/README.md +236 -3
- package/fupload/SKILL.md +142 -0
- package/fupload/agents/openai.yaml +4 -0
- package/fupload/examples/curseforge-plugin-upload.json +21 -0
- package/fupload/examples/dd-config-delete.json +5 -0
- package/fupload/examples/dd-config-update.json +25 -0
- package/fupload/examples/dd-plugin-delete.json +5 -0
- package/fupload/examples/dd-plugin-update.json +9 -0
- package/fupload/examples/dd-wa-delete.json +5 -0
- package/fupload/examples/dd-wa-edit.json +9 -0
- package/fupload/examples/newbee-config-delete.json +5 -0
- package/fupload/examples/newbee-config-update.json +10 -0
- package/fupload/examples/newbee-plugin-create.json +14 -0
- package/fupload/examples/newbee-plugin-delete.json +5 -0
- package/fupload/examples/newbee-wa-delete.json +5 -0
- package/fupload/examples/newbee-wa-update.json +8 -0
- package/fupload/references/curseforge.md +233 -0
- package/fupload/references/dd.md +105 -0
- package/fupload/references/newbee-official-cli.md +288 -0
- package/fupload/references/newbee.md +80 -0
- package/fupload/references/workflow.md +67 -0
- package/fupload/scripts/fupload.py +17 -0
- package/fupload/scripts/fupload_cli/__init__.py +3 -0
- package/fupload/scripts/fupload_cli/cli.py +281 -0
- package/fupload/scripts/fupload_cli/curseforge.py +186 -0
- package/fupload/scripts/fupload_cli/dd.py +2406 -0
- package/fupload/scripts/fupload_cli/dd_broker.py +634 -0
- package/fupload/scripts/fupload_cli/dd_sidecar.py +860 -0
- package/fupload/scripts/fupload_cli/errors.py +94 -0
- package/fupload/scripts/fupload_cli/io.py +125 -0
- package/fupload/scripts/fupload_cli/newbee.py +1412 -0
- package/fupload/scripts/fupload_cli/newbee_auth.py +135 -0
- package/fupload/scripts/fupload_cli/schema.py +587 -0
- package/fupload/scripts/fupload_cli/transport.py +125 -0
- package/fupload/scripts/fupload_cli/trust.py +207 -0
- package/npm/bin/fupload.mjs +92 -0
- package/npm/lib/curseforge-config.mjs +36 -0
- package/npm/lib/managed-install.mjs +86 -0
- package/npm/lib/options.mjs +38 -0
- package/npm/lib/python.mjs +45 -0
- package/npm/lib/skill-installer.mjs +228 -0
- package/npm/lib/uninstall.mjs +211 -0
- package/npm/lib/update.mjs +102 -0
- package/npm/lib/versions.mjs +63 -0
- package/npm/postinstall.mjs +21 -0
- package/npm/skill-manifest.json +179 -0
- package/package.json +50 -6
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# CurseForge reference
|
|
2
|
+
|
|
3
|
+
Use this reference only for CurseForge public author-project lookup and WoW plugin ZIP upload. Fuploader uses two independent official APIs and credentials.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Capability boundary](#capability-boundary)
|
|
8
|
+
- [Local configuration](#local-configuration)
|
|
9
|
+
- [Public project lookup](#public-project-lookup)
|
|
10
|
+
- [Game versions](#game-versions)
|
|
11
|
+
- [Plugin upload](#plugin-upload)
|
|
12
|
+
- [Metadata contract](#metadata-contract)
|
|
13
|
+
- [Relations](#relations)
|
|
14
|
+
- [Plan, dry-run, and confirmation](#plan-dry-run-and-confirmation)
|
|
15
|
+
- [Success, visibility, and verification](#success-visibility-and-verification)
|
|
16
|
+
- [Errors and retry rules](#errors-and-retry-rules)
|
|
17
|
+
- [Official documentation](#official-documentation)
|
|
18
|
+
|
|
19
|
+
## Capability boundary
|
|
20
|
+
|
|
21
|
+
Fuploader supports:
|
|
22
|
+
|
|
23
|
+
- querying public World of Warcraft projects for one numeric author ID through the CurseForge Core API;
|
|
24
|
+
- listing Upload API game-version choices;
|
|
25
|
+
- uploading one ZIP to an existing World of Warcraft project.
|
|
26
|
+
|
|
27
|
+
It does not create projects, list an authenticated account's private/draft/pending-review projects, update an existing file, or delete a project/file. Create and inspect non-public projects in the [CurseForge Authors dashboard](https://authors.curseforge.com/). An empty Core API result means only that no matching public project was returned.
|
|
28
|
+
|
|
29
|
+
## Local configuration
|
|
30
|
+
|
|
31
|
+
Use `~/.fupload/curseforge.env`:
|
|
32
|
+
|
|
33
|
+
```dotenv
|
|
34
|
+
CURSEFORGE_AUTHOR_ID=
|
|
35
|
+
CURSEFORGE_API_KEY=
|
|
36
|
+
CURSEFORGE_UPLOAD_TOKEN=
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- `CURSEFORGE_AUTHOR_ID`: non-secret positive integer used by public project lookup. It is an author membership/owner identifier, not an account username or project ID.
|
|
40
|
+
- `CURSEFORGE_API_KEY`: secret CurseForge for Studios/Core API key sent as `x-api-key`.
|
|
41
|
+
- `CURSEFORGE_UPLOAD_TOKEN`: secret Authors Upload API token sent as `X-Api-Token`.
|
|
42
|
+
|
|
43
|
+
The two secrets are not interchangeable. npm install/update creates the directory and template file when missing; an existing file is preserved byte for byte and is never backfilled or overwritten. Process environment values override file values for the current process. Never read, echo, log, copy into `publish/`, or request either secret in chat.
|
|
44
|
+
|
|
45
|
+
Check presence without exposing values:
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
48
|
+
fupload curseforge session doctor
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If the author ID is absent, ask for the numeric ID because it is non-secret, or use `--author-id` for that lookup. If a secret is absent, direct the user to fill the local file and rerun doctor.
|
|
52
|
+
|
|
53
|
+
## Public project lookup
|
|
54
|
+
|
|
55
|
+
Official request:
|
|
56
|
+
|
|
57
|
+
```http
|
|
58
|
+
GET https://api.curseforge.com/v1/mods/search?gameId=1&authorId=AUTHOR_ID&index=0&pageSize=50
|
|
59
|
+
Accept: application/json
|
|
60
|
+
x-api-key: CURSEFORGE_API_KEY
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`gameId=1` selects World of Warcraft. The Core API defines `authorId` as filtering mods for which that author is a member; `primaryAuthorId` is the separate owner-only filter. `index` is zero-based, `pageSize` defaults to and is capped at 50, and `index + pageSize` cannot exceed 10,000.
|
|
64
|
+
|
|
65
|
+
Fuploader commands:
|
|
66
|
+
|
|
67
|
+
```powershell
|
|
68
|
+
fupload curseforge project list
|
|
69
|
+
fupload curseforge project list --author-id 138844367
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Use `pagination.totalCount` as the count of matching public projects. Select the upload target by human-readable project name and returned numeric project ID. Do not interpret this response as a private account inventory.
|
|
73
|
+
|
|
74
|
+
## Game versions
|
|
75
|
+
|
|
76
|
+
Official request against the WoW Authors host:
|
|
77
|
+
|
|
78
|
+
```http
|
|
79
|
+
GET https://wow.curseforge.com/api/game/versions
|
|
80
|
+
X-Api-Token: CURSEFORGE_UPLOAD_TOKEN
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The response entries contain `id`, `gameVersionTypeID`, `name`, and `slug`. Use the returned IDs or official names in upload metadata.
|
|
84
|
+
|
|
85
|
+
Fuploader command:
|
|
86
|
+
|
|
87
|
+
```powershell
|
|
88
|
+
fupload curseforge plugin game-versions
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Fetch the current list immediately before planning an upload. Present selected names and IDs together; never derive IDs from version strings.
|
|
92
|
+
|
|
93
|
+
## Plugin upload
|
|
94
|
+
|
|
95
|
+
Official request:
|
|
96
|
+
|
|
97
|
+
```http
|
|
98
|
+
POST https://wow.curseforge.com/api/projects/{projectId}/upload-file
|
|
99
|
+
X-Api-Token: CURSEFORGE_UPLOAD_TOKEN
|
|
100
|
+
Content-Type: multipart/form-data; boundary=...
|
|
101
|
+
|
|
102
|
+
metadata=<JSON object>
|
|
103
|
+
file=<ZIP bytes>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The multipart request has the text field `metadata` and binary field `file`. The project ID comes from the existing project's overview URL/API record. On acceptance the official API returns JSON containing the new file `id`.
|
|
107
|
+
|
|
108
|
+
Fuploader accepts the strict schema `fupload.v1.curseforge.plugin.upload`:
|
|
109
|
+
|
|
110
|
+
| Field | Required | Contract |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| `schema` | yes | Exactly `fupload.v1.curseforge.plugin.upload`. |
|
|
113
|
+
| `project_id` | yes | Positive integer for an existing project. |
|
|
114
|
+
| `file` | yes | Existing local ZIP path. |
|
|
115
|
+
| `changelog` | yes | Release notes string. |
|
|
116
|
+
| `changelog_type` | no | `text`, `html`, or `markdown`; official default is `text`. |
|
|
117
|
+
| `display_name` | no | Non-empty friendly file name. |
|
|
118
|
+
| `game_versions` | no | Non-empty array of numeric IDs from `plugin game-versions`; not supported with `parent_file_id`. |
|
|
119
|
+
| `game_version_names` | no | Array of non-empty official game-version names. |
|
|
120
|
+
| `release_type` | yes | `alpha`, `beta`, or `release`. |
|
|
121
|
+
| `parent_file_id` | no | Positive parent file ID; mutually exclusive with `game_versions` and `game_version_names`. |
|
|
122
|
+
| `relations` | no | Object described below. |
|
|
123
|
+
| `is_marked_for_manual_release` | no | Boolean; when true, approval does not immediately release the file. |
|
|
124
|
+
|
|
125
|
+
Compatibility fields are optional in the official API and executable schema. When supplied, use current values returned by `plugin game-versions`; do not combine `parent_file_id` with either version field. Example: [curseforge-plugin-upload.json](../examples/curseforge-plugin-upload.json).
|
|
126
|
+
|
|
127
|
+
```powershell
|
|
128
|
+
fupload curseforge plugin upload --input publish\20260807-120000-curseforge-plugin-upload\01-upload.json --dry-run
|
|
129
|
+
fupload curseforge plugin upload --input publish\20260807-120000-curseforge-plugin-upload\01-upload.json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The executable Fuploader JSON uses snake_case. The provider maps it to the official camelCase Upload API metadata keys.
|
|
133
|
+
|
|
134
|
+
## Metadata contract
|
|
135
|
+
|
|
136
|
+
The official Upload API documents these metadata fields:
|
|
137
|
+
|
|
138
|
+
| Official field | Fuploader field | Meaning and constraints |
|
|
139
|
+
| --- | --- | --- |
|
|
140
|
+
| `changelog` | `changelog` | Change description; HTML or Markdown requires matching `changelogType`. |
|
|
141
|
+
| `changelogType` | `changelog_type` | `text`, `html`, or `markdown`; optional, defaults to `text`. |
|
|
142
|
+
| `displayName` | `display_name` | Optional friendly display name. |
|
|
143
|
+
| `parentFileID` | `parent_file_id` | Optional positive parent file ID. Fuploader rejects it together with `gameVersions` or `gameVersionNames`. |
|
|
144
|
+
| `gameVersions` | `game_versions` | Optional array of numeric game-version IDs; not supported with `parentFileID`. |
|
|
145
|
+
| `gameVersionNames` | `game_version_names` | Optional array of game-version names. |
|
|
146
|
+
| `releaseType` | `release_type` | Required: `alpha`, `beta`, or `release`. |
|
|
147
|
+
| `isMarkedForManualRelease` | `is_marked_for_manual_release` | Optional manual-release flag. |
|
|
148
|
+
| `relations` | `relations` | Optional project dependency relations. |
|
|
149
|
+
|
|
150
|
+
Do not put official camelCase keys directly into Fuploader input; unknown fields are rejected. `project_id` and `file` select the endpoint/body file and are not members of the metadata JSON.
|
|
151
|
+
|
|
152
|
+
## Relations
|
|
153
|
+
|
|
154
|
+
Fuploader input mirrors the official relation shape while using snake_case for `projectID`:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"relations": {
|
|
159
|
+
"projects": [
|
|
160
|
+
{
|
|
161
|
+
"slug": "related-project-slug",
|
|
162
|
+
"project_id": 74924,
|
|
163
|
+
"type": "requiredDependency"
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Each item requires non-empty `slug` and `type`. `project_id` is optional and, when supplied, must be a positive integer for an exact project match. Unknown relation keys are rejected. Official relation types are:
|
|
171
|
+
|
|
172
|
+
- `embeddedLibrary`
|
|
173
|
+
- `incompatible`
|
|
174
|
+
- `optionalDependency`
|
|
175
|
+
- `requiredDependency`
|
|
176
|
+
- `tool`
|
|
177
|
+
|
|
178
|
+
The wire mapping is `slug`, optional `projectID`, and `type` inside `relations.projects`. Resolve and show each intended related project during planning; do not guess a slug or ID.
|
|
179
|
+
|
|
180
|
+
## Plan, dry-run, and confirmation
|
|
181
|
+
|
|
182
|
+
Create `publish/<YYYYMMDD-HHmmss>-curseforge-plugin-upload/01-upload.json`. Store only non-secret business fields and local file paths. Never store the API key or upload token.
|
|
183
|
+
|
|
184
|
+
Run `--dry-run` before confirmation. It validates strict JSON, field values, relation structure, and local file existence without authenticating, querying remote permissions, or uploading.
|
|
185
|
+
|
|
186
|
+
Present one complete plan containing:
|
|
187
|
+
|
|
188
|
+
- author ID and public project name plus project ID;
|
|
189
|
+
- ZIP path and inspected filename;
|
|
190
|
+
- selected game-version names plus IDs, or parent file ID;
|
|
191
|
+
- release type, changelog/type, display name, relations, and manual-release value;
|
|
192
|
+
- the exact `fupload curseforge plugin upload --input ...` command;
|
|
193
|
+
- the effects boundary: acceptance is not approval, release, or public visibility.
|
|
194
|
+
|
|
195
|
+
Obtain one explicit confirmation for that exact plan. If any project, file, version, parent, relation, release type, or manual-release choice changes, present and confirm the changed plan. After confirmation, issue only one upload request.
|
|
196
|
+
|
|
197
|
+
## Success, visibility, and verification
|
|
198
|
+
|
|
199
|
+
The Upload API success response is a JSON object containing the new file ID. Record that literal ID and exit status. This verifies that CurseForge accepted the upload request, not that moderation completed or the file is publicly visible.
|
|
200
|
+
|
|
201
|
+
Visibility boundaries:
|
|
202
|
+
|
|
203
|
+
- Core project search lists public projects only and has no authenticated current-account inventory.
|
|
204
|
+
- Upload acceptance can precede processing, moderation, approval, manual release, and public visibility.
|
|
205
|
+
- `is_marked_for_manual_release=true` intentionally adds a later author release decision after approval.
|
|
206
|
+
- The public search/list response cannot verify private, draft, or pending-review state.
|
|
207
|
+
|
|
208
|
+
Fuploader does not expose an authenticated uploaded-file status/readback endpoint. Use the returned file ID as the acceptance record and inspect the Authors dashboard when a later state must be confirmed. Do not describe acceptance as publication.
|
|
209
|
+
|
|
210
|
+
## Errors and retry rules
|
|
211
|
+
|
|
212
|
+
Interpret responses conservatively:
|
|
213
|
+
|
|
214
|
+
| Result | Meaning and action |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| Local schema/file error | No request was sent. Correct the input and rerun dry-run. |
|
|
217
|
+
| `400`/`422` | Metadata, version, relation, or multipart validation was rejected. Do not retry unchanged input. |
|
|
218
|
+
| `401` | Credential missing, invalid, or expired. Regenerate/configure locally; never request it in chat. |
|
|
219
|
+
| `403` | Token lacks permission for the project/action. Recheck project ownership and token scope locally. |
|
|
220
|
+
| `404` | Host, project, or endpoint was not found. Confirm the WoW host and project ID. |
|
|
221
|
+
| `409` | Treat as a conflicting/duplicate state; inspect the project before another write. |
|
|
222
|
+
| `429` | Rate limited. Honor `Retry-After` when supplied; do not loop. |
|
|
223
|
+
| `5xx` after request transmission | Outcome may be uncertain. Inspect the Authors dashboard before retrying. |
|
|
224
|
+
| Network interruption/timeout during upload | Outcome is uncertain. Never automatically resend; check the project/file list first to prevent duplicates. |
|
|
225
|
+
|
|
226
|
+
Expose only sanitized status, error kind, stage, and provider message. Never include request headers, secret values, or raw multipart bodies in output or logs.
|
|
227
|
+
|
|
228
|
+
## Official documentation
|
|
229
|
+
|
|
230
|
+
- [CurseForge for Studios introduction](https://docs.curseforge.com/docs/curseforge-for-studios/intro)
|
|
231
|
+
- [CurseForge Core REST API](https://docs.curseforge.com/rest-api/): base URL, `x-api-key`, pagination, and `GET /v1/mods/search` including `authorId`/`primaryAuthorId`.
|
|
232
|
+
- [CurseForge Upload API](https://support.curseforge.com/en/support/solutions/articles/9000197321-curseforge-upload-api): token generation, `X-Api-Token`, `GET /api/game/versions`, multipart `POST /api/projects/{projectId}/upload-file`, metadata, relations, and success file ID.
|
|
233
|
+
- [CurseForge Authors dashboard](https://authors.curseforge.com/): project creation and non-public author state.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# NetEase DD field and workflow reference
|
|
2
|
+
|
|
3
|
+
## Task session
|
|
4
|
+
|
|
5
|
+
Run `dd session doctor` first. Doctor only discovers the installation, verifies the Authenticode publisher, reports official DD processes and reads local broker state; it does not login. When `gui_running=true`, explain that the official DD GUI must close and obtain explicit user consent. Only then run `dd session start --confirm-close-gui`. When `gui_running=false`, run `dd session start` without the confirmation flag.
|
|
6
|
+
|
|
7
|
+
`start` closes only identity- and signature-verified official GUI processes, starts one task broker and returns an opaque `session_id`. Pass that value through `--session` to every DD read and write. All operations are serialized through one native login. Run `dd session status --session <id>` for a local status check and always run `dd session stop --session <id>` in `finally`; successful stop reports `cleanup_complete=true`. The ten-minute idle timeout is only a crash fallback.
|
|
8
|
+
|
|
9
|
+
Do not request or expose token, Cookie, JWT, credential database, signed URL, `clientNo`, raw WA content, or raw backup objects. The device state remains under Windows Known Folder Roaming AppData at `CCVoiceHub/Fupload/sidecar-device.json`.
|
|
10
|
+
|
|
11
|
+
## GET before one final JSON
|
|
12
|
+
|
|
13
|
+
DD GUI fields are not a flat form. Resolve these dependencies before generating the write JSON:
|
|
14
|
+
|
|
15
|
+
- `game_type` -> `game_versions`, `associated_acts`, WA `category_ids`;
|
|
16
|
+
- `primary_category_id` -> `second_category_ids`;
|
|
17
|
+
- `scope` -> `share_code_life_type`, anchor-VIP availability, `sync_room`;
|
|
18
|
+
- outer free/paid state -> `need_buy`, `need_anchor_vip` -> `price_fen`, `buy_life_type`, `vip_levels`;
|
|
19
|
+
- `jump_room` -> `room_id` -> `channel_id` and `channel_type`;
|
|
20
|
+
- `backup_sn` -> backup detail -> WTF account/server/role -> account-scoped `known_wa_ids` and `unknown_wa_ids`;
|
|
21
|
+
- retail backup -> `retail_ui_config` selectors;
|
|
22
|
+
- `with_file` -> WA `file` and `file_install_path`.
|
|
23
|
+
|
|
24
|
+
Choose a parent, GET and display only that parent's child choices, then continue. Changing a parent invalidates all descendants. Generate one final JSON only after the graph closes. Store parent fields and stable IDs/opaque selectors, never copied backend objects or display names.
|
|
25
|
+
|
|
26
|
+
The Python provider repeats every live GET in the same session before any upload or mutation. A missing, duplicate, or cross-parent selection fails at its exact JSON path with `verification_required=false`. DD detail is authoritative for an existing object's form and ownership; author-list timestamps are not comparable freshness gates.
|
|
27
|
+
|
|
28
|
+
Author-owned plugin/config/WA lists and association candidates traverse the real DD pagination contract instead of assuming the first 100 rows contain every selected SN. Repeated pages or the bounded page limit are platform-data failures and stop the write before upload.
|
|
29
|
+
|
|
30
|
+
The installed author-list response may expose its total as top-level `count`; treat that as the pagination bound. Fetch anchor-VIP levels only when `need_anchor_vip=true`, and fetch CC room/channel data only when `jump_room=true`. Disabled optional features must not fail because an unrelated dependency endpoint is empty or unavailable.
|
|
31
|
+
|
|
32
|
+
## Shared fields
|
|
33
|
+
|
|
34
|
+
All three create/edit form models use `scope`, `share_code_life_type`, `need_buy`, `price_fen`, `buy_life_type`, `jump_room`, `room_id`, `channel_id`, `channel_type`, `sync_room`, `creation_statement`, `with_associate`, `associated_acts`, `need_anchor_vip`, and `vip_levels`.
|
|
35
|
+
|
|
36
|
+
- `scope` is `public` or `private`. Private requires `share_code_life_type`, clears anchor VIP and room sync. Plugin/WA public force `share_code_life_type=forever`; config public omits it.
|
|
37
|
+
- `need_buy=true` requires `buy_life_type`; `price_fen` may be `0` or `10..20000` fen because the official submit validation explicitly accepts zero. A free create resolves to zero, while an existing paid form may retain a hidden historical price when `need_buy` is turned off but the outer paid mode remains active through anchor VIP.
|
|
38
|
+
- The outer free/paid selector is locked after an SN exists. It is derived from `need_buy || need_anchor_vip` and is not a wire field. Existing paid content may still adjust payment methods and their price/lifetime/VIP children while remaining paid.
|
|
39
|
+
- `jump_room=true` requires `room_id`. `channel_id` and `channel_type` are both empty for a room-only link or both present for one live child channel.
|
|
40
|
+
- `with_associate=true` requires nonempty `associated_acts`; each item is exactly `{sn,act_type}` with `act_type` `addon`, `share`, or `wa`.
|
|
41
|
+
- Remote detail/list projections may enrich associated items with display-only fields such as names, covers, or timestamps. Python validates the live reference, then strips every item back to exactly `{sn,act_type}` before any create/modify mutation.
|
|
42
|
+
- `need_anchor_vip=true` requires public scope. `vip_levels` must contain only live values when supplied, but the official submit validation accepts an empty array. Turning only `need_anchor_vip` off preserves the existing level array; switching to private scope or switching the outer mode to free clears it.
|
|
43
|
+
- `creation_statement` is `original`, `chinesize`, `renovate`, or `second`.
|
|
44
|
+
|
|
45
|
+
Omission on edit preserves the remote value. Explicit false follows the field-specific official behavior: room and association children are cleared, while anchor VIP levels remain unless private scope or the outer free mode clears them.
|
|
46
|
+
|
|
47
|
+
## Plugin
|
|
48
|
+
|
|
49
|
+
`plugin create` fields: `game_type`, `scope`, `addon_type`, `name`, `description`, `logo`/`logo_file`, `detail_imgs`/`detail_img_files`, `primary_category_id`, `second_category_ids`, `html_desc`, `game_versions`, `detail_url`/`file`, `release_type`, `version`, `update_desc`, and all shared fields. Name, description, and version are at most 80 characters; update description is at most 1000; detail images are at most 8.
|
|
50
|
+
|
|
51
|
+
Every path in `detail_img_files` must exist as a regular local file. Validation reports the exact indexed path, such as `$.detail_img_files[0]`, before any upload or mutation.
|
|
52
|
+
|
|
53
|
+
`game_type` and the outer free/paid state are create-only. `plugin update` requires `sn`, `game_versions`, `version`, and `update_desc`; optional `file`, `detail_url`, and `release_type` publish a new version. `plugin edit` requires `sn` and only accepts the official existing-record commercial and association controls (`scope`, payment/lifetime/VIP fields, room/channel linkage, `creation_statement`, and associated content). First-publication metadata (`addon_type`, `name`, `description`, logo, detail images, categories, and `html_desc`) and version fields are not edit fields; in particular, sending `description` to `/addon/modify` can be accepted while leaving the remote value unchanged, so the CLI rejects it instead of reporting a false success.
|
|
54
|
+
|
|
55
|
+
When `assign_user_sn` is present, the official UI exposes only the public scope option. Python validates the final rebuilt form and rejects a private scope before upload or mutation.
|
|
56
|
+
|
|
57
|
+
When rebuilding a plugin update/edit form, preserve field presence from the current DD record. A field absent from both official projections must remain absent; do not synthesize JSON `null`, because the official web `pick -> JSON.stringify` path omits absent properties and DD can reject synthetic nulls with HTTP 422. The official detail dialog is opened from the matching author-list item and takes `detail_url`, `release_type`, and `version` from that item's `latest_version`; `detail_v2` supplies the stable detail fields and top-level `game_versions`. Python therefore uses the same-SN author item only to fill null/missing latest-version placeholders, never to overwrite stable metadata or top-level builds.
|
|
58
|
+
|
|
59
|
+
Before `plugin update`, traverse `/addon/addon_versions` pages as an optional duplicate guard. If it returns version rows, reject any candidate version already present anywhere in that history before upload; if it is empty or unavailable, retain the current-version check and continue. After update, confirm the submitted version fields from the matching item in the author plugin list, whose `latest_version` is the official update projection. `detail_v2` is supplementary. An empty history never marks a successful private-plugin update as failed and never triggers replay.
|
|
60
|
+
|
|
61
|
+
Read `plugin categories`, choose `primary_category_id`, then choose only returned `second_category_ids`. Read `plugin game-versions --game-type <id>` and use its stable values. The package accepts `.zip` only. Authorization always uses `file_type=a19-ui-res`, `business_id=addon`, fixed `file_name=addon.zip`, and `mime_type=application/x-zip-compressed`. Plugin image authorization uses `a19-ui-media/img` with an explicit empty wire file name.
|
|
62
|
+
|
|
63
|
+
## Configuration share
|
|
64
|
+
|
|
65
|
+
`config create` fields: `backup_sn`, `scope`, `title`, `brief_desc`, `desc`, `update_desc`, `display_imgs`/`display_img_files`, `known_addon_ids`, `unknown_addon_ids`, `wtf_role_ids`, `material_names`, `font_names`, `known_wa_ids`, `unknown_wa_ids`, optional `retail_ui_config`, all incremental arrays, and shared fields. The incremental arrays are `known_addon_update_ids`, `unknown_addon_update_ids`, `material_update_names`, `font_update_names`, `known_wa_update_ids`, and `unknown_wa_update_ids`.
|
|
66
|
+
|
|
67
|
+
Title is at most 40 characters, brief description 50, update description 1000, and display images 8. `wtf_role_ids` contains at most one opaque selector returned by `config backup-get`.
|
|
68
|
+
|
|
69
|
+
Run `config backups`, select `backup_sn`, then run `config backup-get --sn <backup>`. Choose content references only from that response. After choosing one WTF role, filter known/unknown WA by the selected role's account. Switching backup requires complete reselection. Switching the WTF account clears both WA groups. Unknown WA internal IDs are restored by Python from `extra.wa_account_info[account]`; they never appear in input JSON.
|
|
70
|
+
|
|
71
|
+
Every wire content group is rebuilt from the latest backup. Each `inner_version` map covers every source item; a new entry is 1, an existing value is preserved, and only explicitly listed existing update entries increment.
|
|
72
|
+
|
|
73
|
+
An incremental array may be submitted without repeating its unchanged selection array; Python applies it to the current selected items from the fresh detail. Every incremental marker must reference an item that remains selected. A stale or unselected marker is rejected at its exact `*_update_ids` or `*_update_names` path before upload or mutation.
|
|
74
|
+
|
|
75
|
+
Every path in `display_img_files` must exist as a regular local file and is rejected at its indexed JSON path before upload when absent.
|
|
76
|
+
|
|
77
|
+
For retail, `retail_ui_config` accepts `edit_mode_selectors`, `default_edit_mode_selector`, `cool_down_selectors`, and `enable_dd_setup_wizard`. Up to five edit modes are allowed and one selected mode is default. Only one cooldown per `spec_tag` is allowed. Selectors are bound to one backup; raw `import_string` and raw edit/cooldown objects are read-only and never emitted.
|
|
78
|
+
|
|
79
|
+
`config update` requires `share_sn`, `backup_sn`, and `update_desc`; it changes backup content and increments. `config edit` requires `share_sn` and changes metadata, images, and allowed shared fields only. Config images omit `file_name` from upload authorization.
|
|
80
|
+
|
|
81
|
+
## WA/string
|
|
82
|
+
|
|
83
|
+
`wa create` fields: `game_type`, `scope`, `name`, `game_version`, `brief_desc`, `display_imgs`/`display_img_files`, `category_ids`, `content`, `desc`, `update_desc`, `version`, `with_file`, optional local `file`, `file_install_path`, and all shared fields. Name is at most 40 characters, brief description 50, update description 1000, numeric version length 80, categories 5, and images 8.
|
|
84
|
+
|
|
85
|
+
Read `wa categories --game-type <id>` and use only that game type's category IDs. `game_type` and outer free/paid mode are locked after creation. `wa update` requires `sn`, `content`, `update_desc`, `version`, and `with_file`; new version input contains digits only and must be numerically greater than the current value. Historical current values are compared with the official JavaScript `Number(current)` behavior, so a legacy value such as `1.2` may advance to `2`. `wa edit` requires `sn` and accepts metadata, categories, images, and allowed shared fields. Assigned WA records also validate the final rebuilt scope as public.
|
|
86
|
+
|
|
87
|
+
The official create builder supplies defaults that are required even when the user did not make a business choice: `share_code_life_type="seven_day"`, `need_buy=false`, `buy_life_type="seven_day"`, `category_ids=["ui_original"]`, `file_install_path="Interface/Addons"`, `vip_levels=[]`, and `version="0"`. The CLI applies these only to create, then applies the user's explicit values and normalizes every submitted category ID to its string wire form. Update and edit preserve remote values for omitted fields rather than reapplying create defaults.
|
|
88
|
+
|
|
89
|
+
Every submit whose content begins `!WA:2!` is reparsed by the installed official chain `WowUIInterface.parseWa({"waStr": content}) -> WaParser.parseWa(content)`, including unchanged content on edit. Native `JsResult.toJson()` output may use success code `0` or `200`; IDs come from nested `result.uid` and `result.id`. Internal `parse_wa_uid` and `parse_wa_id` are read-only wire fields and are not accepted in JSON. Non-WA2 clears them.
|
|
90
|
+
|
|
91
|
+
When `with_file=true`, create requires a local `.zip` `file` and nonempty `file_install_path`; update may preserve an existing material when `file` is omitted. Authorization uses `a19-ui-res/wa`, fixed `file_name=wa_materials.zip`, fixed ZIP MIME, and a 50 MiB local limit plus server `maxSize`. `with_file=false` follows the official builder and preserves existing internal `file_path` and install path rather than clearing them. WA images omit upload `file_name`.
|
|
92
|
+
|
|
93
|
+
## Delete
|
|
94
|
+
|
|
95
|
+
`plugin delete`, `config delete`, and `wa delete` each accept one nonempty `sn` and `confirm_delete`=true. Python GETs the target and ownership state before `/addon/delete`, `/share/delete`, or `/wa/delete`, then verifies absence through list/get readback. An uncertain delete is never automatically retried.
|
|
96
|
+
|
|
97
|
+
## Errors and readback
|
|
98
|
+
|
|
99
|
+
Stages are `session`, `dependency_get`, `upload_authorize`, `object_put`, `mutation`, `readback`, and `native_parser`. Explicit HTTP/business failures and all pre-mutation validation failures have `verification_required=false`. In particular, HTTP 4xx responses such as 422 are confirmed server rejections, not uncertain writes; the CLI reports `http_status` and a bounded, secret-free validation summary when the native response exposes one. PUT/mutation connection uncertainty and accepted-write readback uncertainty have `verification_required=true`; GET first and do not replay the write. Native failures retain a bounded exception message and the native `code`/`error_code` when present, with signed-URL query credentials, signatures, and tokens redacted before they leave the sidecar.
|
|
100
|
+
|
|
101
|
+
Each DD native/API failure appends one ASCII JSON line to `<DD version directory>/Fupload/logs/dd-errors-YYYYMMDD.jsonl`. The record includes HTTP status, native business code including falsy code `0`, stage, endpoint, request field names, the sanitized request JSON/body, validation hints, and the sanitized response JSON/body. Upload authorization records `/file/upload`; object PUT records `object-store-put` instead of the signed URL. Token, JWT, Cookie, authorization, credential, client identifier, device proof, signature, and signed upload URL fields are redacted recursively, including truncated non-JSON text. Request and response bodies are independently bounded to 1 MiB by UTF-8 bytes and record their original size and truncation state. A log write failure is reported as `log_write_error` without replacing the original DD error.
|
|
102
|
+
|
|
103
|
+
After an accepted mutation, detail readback uses a short bounded GET-only poll and never resends the mutation. Plugin edit/version confirmation also checks the matching author-list projection because the official UI opens the modify dialog from that item while `detail_v2` can remain stale or contain null latest-version placeholders. Configuration readback compares its official integer `need_buy` wire value with the boolean detail projection through a resource-specific conversion.
|
|
104
|
+
|
|
105
|
+
The parent process and native sidecar exchange ASCII-only JSONL, and Fuploader's final JSON output follows the same rule. Non-ASCII request, response, and output text is represented with JSON Unicode escapes so Chinese titles, descriptions, announcements, and URLs do not depend on either Windows process code page. JSON consumers recover original UTF-8 strings through normal parsing.
|