@follenfang/fupload 0.0.17 → 0.0.19
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/fupload/SKILL.md +3 -3
- package/fupload/references/dd.md +2 -2
- package/fupload/scripts/fupload_cli/__init__.py +1 -1
- package/fupload/scripts/fupload_cli/dd.py +28 -9
- package/fupload/scripts/fupload_cli/dd_broker.py +2 -1
- package/fupload/scripts/fupload_cli/dd_sidecar.py +5 -3
- package/fupload/scripts/fupload_cli/schema.py +1 -0
- package/npm/skill-manifest.json +16 -16
- package/package.json +1 -1
package/fupload/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: fupload
|
|
3
3
|
description: Explicit author-publishing workflow for World of Warcraft plugins, configuration shares, and WA/strings on NewBeeBox, NetEase DD, CurseForge, Heybox Workshop, and ModUs.Creator, including local Creator login reuse and plugin ZIP publishing. Use only when the user explicitly invokes `$fupload`, explicitly asks to use the Fupload Skill, or loads this Skill by path. Do not trigger from ordinary mentions of publishing, NewBeeBox, DD, CurseForge, Heybox, ModUs, plugins, configurations, or WA.
|
|
4
4
|
metadata:
|
|
5
|
-
version: "0.0.
|
|
5
|
+
version: "0.0.19"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Fupload
|
|
@@ -22,7 +22,7 @@ Map natural language consistently:
|
|
|
22
22
|
|
|
23
23
|
- `create`: create the main record. If first content is a separate platform action, plan it as a following atomic step.
|
|
24
24
|
- `update`: publish a plugin version, change selected configuration-backup content, or publish a WA/string version.
|
|
25
|
-
- `edit`: change fields allowed by the target platform's action allowlist. For DD plugins, this
|
|
25
|
+
- `edit`: change fields allowed by the target platform's action allowlist. For DD plugins, this includes `description` and existing-record commercial, association, room/channel, and creation-statement settings; other first-publication metadata, categories, media, and version fields are create/update-only. Description edits read both detail and author-list projections; either may confirm the value, but if neither matches, report verification-required.
|
|
26
26
|
- `delete`: delete one main record only. It never deletes an individual version, media object, or associated record.
|
|
27
27
|
|
|
28
28
|
Never offer bulk delete, drafts, guides, messages, or GUI control automation. Heybox supports individual version deletion with asynchronous readback; whole-module deletion is not supported. The DD task-session command may close verified official GUI processes only after the consent flow below.
|
|
@@ -109,7 +109,7 @@ For ModUs, require all four `modus session doctor` booleans to be true before an
|
|
|
109
109
|
|
|
110
110
|
Before any DD live GET or write, run `dd session doctor`. If it reports `gui_running=true`, tell the user that continuing will close the listed official DD GUI instances and ask for explicit consent. Without consent, do not run start, do not close a process, and do not issue a native login. After consent, run `dd session start --confirm-close-gui`; when no GUI is running, run `dd session start` without that flag.
|
|
111
111
|
|
|
112
|
-
`dd session start` automatically selects the native relogin flow from the official `Account.method`, `Cred.type`, and `Cred.modifier` enums. `urs + urs_token + normal` uses `UrsReLoginFlow` for an email session; `mobile + urs_mobile_token + mobile_password|mobile_uplink` uses `MobileReLoginFlow` for a mobile session. Missing, unknown, or contradictory combinations fail before any flow runs. Never infer the account kind from its text, coerce an enum, or try the other flow after a failure.
|
|
112
|
+
`dd session start` automatically selects the native relogin flow from the official `Account.method`, `Cred.type`, and `Cred.modifier` enums. `urs + urs_token + normal` uses `UrsReLoginFlow` for an email session; `mobile + urs_mobile_token + normal|mobile_password|mobile_uplink` uses `MobileReLoginFlow` for a mobile session. Missing, unknown, or contradictory combinations fail before any flow runs. Never infer the account kind from its text, coerce an enum, or try the other flow after a failure.
|
|
113
113
|
|
|
114
114
|
Keep the returned opaque `session_id` only in task memory and pass it as `--session <id>` to every DD GET, write, readback, status, and delete. Reuse this one session for the complete task, serialize all commands, stop on the first failure, and never start one session per item in a batch. In a `finally` path, always run `dd session stop --session <id>` and require `cleanup_complete=true`; the ten-minute idle timeout is only an abnormal-exit fallback.
|
|
115
115
|
|
package/fupload/references/dd.md
CHANGED
|
@@ -7,7 +7,7 @@ Run `dd session doctor` first. Doctor only discovers the installation, verifies
|
|
|
7
7
|
`start` closes only identity- and signature-verified official GUI processes, starts one task broker, and reads the official `AccountCredStorage` auto account and credential. It selects the native relogin flow only from this strict enum matrix:
|
|
8
8
|
|
|
9
9
|
- `Account.method=urs`, `Cred.type=urs_token`, `Cred.modifier=normal`: run `UrsReLoginFlow(controller, sdk, token, username)` for the email login state.
|
|
10
|
-
- `Account.method=mobile`, `Cred.type=urs_mobile_token`, `Cred.modifier=mobile_password|mobile_uplink`: run `MobileReLoginFlow(controller, sdk, cgi, netconfig, token, isPassword, username)` for the mobile login state. `isPassword` is true only for `mobile_password`.
|
|
10
|
+
- `Account.method=mobile`, `Cred.type=urs_mobile_token`, `Cred.modifier=normal|mobile_password|mobile_uplink`: run `MobileReLoginFlow(controller, sdk, cgi, netconfig, token, isPassword, username)` for the mobile login state. `normal` is emitted by the official DD 100130 client for SMS/non-password mobile login. `isPassword` is true only for `mobile_password`.
|
|
11
11
|
|
|
12
12
|
Missing, unknown, or contradictory enum combinations fail before either flow is called. Do not infer account kind from the account string, coerce a value, or fall back to the other flow after a login failure. Do not request or accept manual account names, passwords, tokens, credential values, or account-type overrides.
|
|
13
13
|
|
|
@@ -65,7 +65,7 @@ Omission on edit preserves the remote value. Explicit false follows the field-sp
|
|
|
65
65
|
|
|
66
66
|
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.
|
|
67
67
|
|
|
68
|
-
`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
|
|
68
|
+
`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 accepts `description` plus the official existing-record commercial and association controls (`scope`, payment/lifetime/VIP fields, room/channel linkage, `creation_statement`, and associated content). Other first-publication metadata (`addon_type`, `name`, logo, detail images, categories, and `html_desc`) and version fields are not edit fields. The CLI verifies a description edit through both detail and author-list readback; if DD accepts the request but leaves the remote value unchanged, it reports a verification-required error rather than false success.
|
|
69
69
|
|
|
70
70
|
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.
|
|
71
71
|
|
|
@@ -20,6 +20,8 @@ from .trust import trusted_local_dir, trusted_roaming_dir, verify_dd_executable
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
EXPECTED_DD_VERSION = os.environ.get("FUPLOAD_DD_EXPECTED_VERSION", "any")
|
|
23
|
+
SIDECAR_DEFAULT_RESPONSE_TIMEOUT_SECONDS = 180
|
|
24
|
+
SIDECAR_UPLOAD_RESPONSE_TIMEOUT_SECONDS = 660
|
|
23
25
|
LIFE_TYPES = [
|
|
24
26
|
{"name": "7 days", "value": "seven_day"},
|
|
25
27
|
{"name": "14 days", "value": "fourteen_day"},
|
|
@@ -326,7 +328,7 @@ class Sidecar:
|
|
|
326
328
|
self.responses.put(FuploadError("DD sidecar exited without a result", kind="sidecar_error"))
|
|
327
329
|
|
|
328
330
|
def _next_result(
|
|
329
|
-
self, *, timeout: float =
|
|
331
|
+
self, *, timeout: float = SIDECAR_DEFAULT_RESPONSE_TIMEOUT_SECONDS, endpoint: Optional[str] = None,
|
|
330
332
|
stage: Optional[str] = None,
|
|
331
333
|
verification_required: bool = False,
|
|
332
334
|
) -> Dict[str, Any]:
|
|
@@ -377,6 +379,11 @@ class Sidecar:
|
|
|
377
379
|
else:
|
|
378
380
|
expected_stage, uncertain = "dependency_get", False
|
|
379
381
|
response = self._next_result(
|
|
382
|
+
timeout=(
|
|
383
|
+
SIDECAR_UPLOAD_RESPONSE_TIMEOUT_SECONDS
|
|
384
|
+
if action == "upload"
|
|
385
|
+
else SIDECAR_DEFAULT_RESPONSE_TIMEOUT_SECONDS
|
|
386
|
+
),
|
|
380
387
|
endpoint=endpoint,
|
|
381
388
|
stage=expected_stage,
|
|
382
389
|
verification_required=uncertain,
|
|
@@ -1456,10 +1463,12 @@ PLUGIN_CREATE_DEFAULTS = {
|
|
|
1456
1463
|
"with_associate": False,
|
|
1457
1464
|
}
|
|
1458
1465
|
|
|
1459
|
-
# DD's modify form is not a second create form.
|
|
1460
|
-
# rebuilds the payload from
|
|
1461
|
-
#
|
|
1466
|
+
# DD's modify form is not a second create form. The official edit page
|
|
1467
|
+
# rebuilds the payload from existing commercial/association controls. The
|
|
1468
|
+
# brief description is the supported metadata exception and is verified by
|
|
1469
|
+
# detail and author-list readback after every edit.
|
|
1462
1470
|
PLUGIN_EDIT_FIELDS = (
|
|
1471
|
+
"description",
|
|
1463
1472
|
"scope", "share_code_life_type", "need_buy", "price_fen", "buy_life_type",
|
|
1464
1473
|
"jump_room", "room_id", "channel_id", "channel_type", "sync_room",
|
|
1465
1474
|
"creation_statement", "with_associate", "associated_acts", "need_anchor_vip",
|
|
@@ -1950,7 +1959,10 @@ class DD:
|
|
|
1950
1959
|
if available and any(str(value) not in available for value in values):
|
|
1951
1960
|
raise ValidationError("selection contains an unavailable live option", path=path)
|
|
1952
1961
|
|
|
1953
|
-
def _validate_options(
|
|
1962
|
+
def _validate_options(
|
|
1963
|
+
self, session: Sidecar, resource: str, form: Mapping[str, Any], *,
|
|
1964
|
+
validate_optional_dependencies: bool = True,
|
|
1965
|
+
) -> None:
|
|
1954
1966
|
game_type = form.get("game_type")
|
|
1955
1967
|
if resource == "plugin":
|
|
1956
1968
|
versions = session.get("/game_versions/list", {"game_type": game_type})
|
|
@@ -1983,11 +1995,11 @@ class DD:
|
|
|
1983
1995
|
if form.get("share_code_life_type"):
|
|
1984
1996
|
self._validate_choices(LIFE_TYPES, [form.get("share_code_life_type")], ("value",), "$.share_code_life_type")
|
|
1985
1997
|
|
|
1986
|
-
if form.get("need_anchor_vip"):
|
|
1998
|
+
if validate_optional_dependencies and form.get("need_anchor_vip"):
|
|
1987
1999
|
vip_levels = session.get("/anchor_vip/level/list", {"enrich_acts": "false"})
|
|
1988
2000
|
self._validate_choices(vip_levels, form.get("vip_levels") or [], ("id", "level", "value"), "$.vip_levels")
|
|
1989
2001
|
|
|
1990
|
-
if form.get("jump_room"):
|
|
2002
|
+
if validate_optional_dependencies and form.get("jump_room"):
|
|
1991
2003
|
channels = safe_channels(session.cc_get("https://api.cc.163.com/v1/mixteammsgproxy/channelList?source=pluginPublish"))
|
|
1992
2004
|
if not channels["items"]:
|
|
1993
2005
|
raise FuploadError("live channel response contained no selectable values", kind="platform_data_error")
|
|
@@ -1996,7 +2008,7 @@ class DD:
|
|
|
1996
2008
|
if wanted not in available:
|
|
1997
2009
|
raise ValidationError("room/channel selection is unavailable", path="$.channel_id")
|
|
1998
2010
|
|
|
1999
|
-
if form.get("with_associate"):
|
|
2011
|
+
if validate_optional_dependencies and form.get("with_associate"):
|
|
2000
2012
|
references = self._associated_refs(session, game_type)
|
|
2001
2013
|
if not references:
|
|
2002
2014
|
raise FuploadError("live association response contained no selectable values", kind="platform_data_error")
|
|
@@ -2105,7 +2117,14 @@ class DD:
|
|
|
2105
2117
|
validate_locked_usage_mode(current, form, doc)
|
|
2106
2118
|
normalize_commercial(form, "plugin", create=action == "create")
|
|
2107
2119
|
validate_plugin_submission(form, doc)
|
|
2108
|
-
|
|
2120
|
+
# A version-only update cannot change these optional metadata fields.
|
|
2121
|
+
# Validate their live dependencies on create/edit, but do not block a
|
|
2122
|
+
# release merely because an already-saved room, VIP, or association is
|
|
2123
|
+
# temporarily absent from an auxiliary option endpoint.
|
|
2124
|
+
self._validate_options(
|
|
2125
|
+
session, "plugin", form,
|
|
2126
|
+
validate_optional_dependencies=action != "update",
|
|
2127
|
+
)
|
|
2109
2128
|
validate_no_display_objects(form, "plugin")
|
|
2110
2129
|
if doc.get("logo_file"):
|
|
2111
2130
|
form["logo"] = session.upload(doc["logo_file"], "addon", file_name="", media=True, max_bytes=10 * 1024 * 1024)
|
|
@@ -23,6 +23,7 @@ STATE_NAME = "broker.json"
|
|
|
23
23
|
STARTUP_NAME = "broker.starting.json"
|
|
24
24
|
MAX_REQUEST_BYTES = 64 * 1024 * 1024
|
|
25
25
|
IDLE_SECONDS = 10 * 60
|
|
26
|
+
BROKER_WRITE_TIMEOUT_SECONDS = 15 * 60
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def _dd_module():
|
|
@@ -545,7 +546,7 @@ def execute(session_id: str, kind: str, resource: str, action: str, payload: Dic
|
|
|
545
546
|
"resource": resource,
|
|
546
547
|
"action": action,
|
|
547
548
|
"payload": payload,
|
|
548
|
-
})
|
|
549
|
+
}, timeout=BROKER_WRITE_TIMEOUT_SECONDS if kind == "write" else 300)
|
|
549
550
|
|
|
550
551
|
|
|
551
552
|
def _read_request(connection: socket.socket) -> Dict[str, Any]:
|
|
@@ -42,6 +42,7 @@ _SENSITIVE_KEYS = {
|
|
|
42
42
|
"presigneduri", "presigned_uri", "signature", "client_secret", "api_key", "auth_key", "password", "secret",
|
|
43
43
|
}
|
|
44
44
|
_MAX_LOG_BODY = 1024 * 1024
|
|
45
|
+
OBJECT_PUT_TIMEOUT_SECONDS = 600
|
|
45
46
|
_CONFIG_CONTENT_KEYS = {
|
|
46
47
|
"known_addon", "unknown_addon", "wtf", "material", "font",
|
|
47
48
|
"known_wa", "unknown_wa", "retail_ui_config",
|
|
@@ -624,8 +625,9 @@ def _create_relogin_flow(
|
|
|
624
625
|
if (
|
|
625
626
|
method == "mobile"
|
|
626
627
|
and credential_type == "urs_mobile_token"
|
|
627
|
-
and modifier in ("mobile_password", "mobile_uplink")
|
|
628
|
+
and modifier in ("normal", "mobile_password", "mobile_uplink")
|
|
628
629
|
):
|
|
630
|
+
# Official DD 100130 emits normal for SMS/non-password mobile credentials.
|
|
629
631
|
return mobile_flow_class(
|
|
630
632
|
controller, sdk, cgi, netconfig, credential.value,
|
|
631
633
|
modifier == "mobile_password", account.name,
|
|
@@ -642,7 +644,7 @@ def _credential_kind(account, credential):
|
|
|
642
644
|
if (
|
|
643
645
|
method == "mobile"
|
|
644
646
|
and credential_type == "urs_mobile_token"
|
|
645
|
-
and modifier in ("mobile_password", "mobile_uplink")
|
|
647
|
+
and modifier in ("normal", "mobile_password", "mobile_uplink")
|
|
646
648
|
):
|
|
647
649
|
return "mobile"
|
|
648
650
|
raise RuntimeError("DD persisted login state uses an unsupported credential combination")
|
|
@@ -831,7 +833,7 @@ def run_command(session, command):
|
|
|
831
833
|
request = urllib.request.Request(
|
|
832
834
|
info["url"], data=data, method="PUT", headers=headers,
|
|
833
835
|
)
|
|
834
|
-
with urllib.request.urlopen(request, timeout=
|
|
836
|
+
with urllib.request.urlopen(request, timeout=OBJECT_PUT_TIMEOUT_SECONDS) as response:
|
|
835
837
|
if response.status != 200:
|
|
836
838
|
try:
|
|
837
839
|
response_body = response.read()
|
|
@@ -580,6 +580,7 @@ DD_PLUGIN_EDIT_META = {
|
|
|
580
580
|
name: DD_COMMERCIAL[name]
|
|
581
581
|
for name in DD_COMMERCIAL
|
|
582
582
|
}
|
|
583
|
+
DD_PLUGIN_EDIT_META["description"] = DD_PLUGIN_META["description"]
|
|
583
584
|
DD_PLUGIN_VERSION = {
|
|
584
585
|
"game_versions": f("array", nonempty=True), "detail_url": f("string"),
|
|
585
586
|
"file": f("string", local_file=True), "release_type": f("integer", choices=(1, 2, 3)),
|
package/npm/skill-manifest.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "fupload.npm-skill-manifest.v1",
|
|
3
3
|
"package_name": "@follenfang/fupload",
|
|
4
|
-
"package_version": "0.0.
|
|
5
|
-
"skill_version": "0.0.
|
|
6
|
-
"tree_sha256": "
|
|
4
|
+
"package_version": "0.0.19",
|
|
5
|
+
"skill_version": "0.0.19",
|
|
6
|
+
"tree_sha256": "dd39f508a55b1b4fe50e5bd076d2a2cd85cb03c6864cdedcb4a7de521ea92197",
|
|
7
7
|
"files": [
|
|
8
8
|
{
|
|
9
9
|
"path": "agents/openai.yaml",
|
|
@@ -87,8 +87,8 @@
|
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
"path": "references/dd.md",
|
|
90
|
-
"bytes":
|
|
91
|
-
"sha256": "
|
|
90
|
+
"bytes": 19868,
|
|
91
|
+
"sha256": "79498778d5d9f9c6205b2343b382fc71981d3c3e2cacd7470b63ad18e3afb491"
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
"path": "references/modus.md",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
{
|
|
114
114
|
"path": "scripts/fupload_cli/__init__.py",
|
|
115
115
|
"bytes": 50,
|
|
116
|
-
"sha256": "
|
|
116
|
+
"sha256": "f292736d0644ad8cdfc2c5c693957ec320f889bab0ed5845b574d60e7998f225"
|
|
117
117
|
},
|
|
118
118
|
{
|
|
119
119
|
"path": "scripts/fupload_cli/blackbox_web.py",
|
|
@@ -137,18 +137,18 @@
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
"path": "scripts/fupload_cli/dd_broker.py",
|
|
140
|
-
"bytes":
|
|
141
|
-
"sha256": "
|
|
140
|
+
"bytes": 28353,
|
|
141
|
+
"sha256": "1b05604c27253cd77b40bb2b549a5cd1c99434245ccb60959f989ec690e3c70f"
|
|
142
142
|
},
|
|
143
143
|
{
|
|
144
144
|
"path": "scripts/fupload_cli/dd_sidecar.py",
|
|
145
|
-
"bytes":
|
|
146
|
-
"sha256": "
|
|
145
|
+
"bytes": 39309,
|
|
146
|
+
"sha256": "fa0658f9cd4d5c85f4f1f84dd79bf7b33b5cafb74813107acc09fcf1f17d508c"
|
|
147
147
|
},
|
|
148
148
|
{
|
|
149
149
|
"path": "scripts/fupload_cli/dd.py",
|
|
150
|
-
"bytes":
|
|
151
|
-
"sha256": "
|
|
150
|
+
"bytes": 115390,
|
|
151
|
+
"sha256": "e160eeebb2914016d99f6dad1ad38b61fa201c884bb2314090839408fa70cd58"
|
|
152
152
|
},
|
|
153
153
|
{
|
|
154
154
|
"path": "scripts/fupload_cli/errors.py",
|
|
@@ -182,8 +182,8 @@
|
|
|
182
182
|
},
|
|
183
183
|
{
|
|
184
184
|
"path": "scripts/fupload_cli/schema.py",
|
|
185
|
-
"bytes":
|
|
186
|
-
"sha256": "
|
|
185
|
+
"bytes": 54628,
|
|
186
|
+
"sha256": "6eead57520d6f1d0db62d6da764c4e419cca24a267483dd3708f212a492e396a"
|
|
187
187
|
},
|
|
188
188
|
{
|
|
189
189
|
"path": "scripts/fupload_cli/state_machine.py",
|
|
@@ -207,8 +207,8 @@
|
|
|
207
207
|
},
|
|
208
208
|
{
|
|
209
209
|
"path": "SKILL.md",
|
|
210
|
-
"bytes":
|
|
211
|
-
"sha256": "
|
|
210
|
+
"bytes": 28568,
|
|
211
|
+
"sha256": "e2f60649f6d239c555fac24e85fef6ab0d3354b2736e2022b2b9221eedbf0841"
|
|
212
212
|
}
|
|
213
213
|
]
|
|
214
214
|
}
|