@follenfang/fupload 0.0.11 → 0.0.15

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.
@@ -17,6 +17,7 @@ JSON_TYPES = {
17
17
  "boolean": (bool,),
18
18
  "array": (list,),
19
19
  "object": (dict,),
20
+ "identifier": (int, str),
20
21
  }
21
22
 
22
23
 
@@ -98,6 +99,52 @@ class Schema:
98
99
  return checked
99
100
 
100
101
  def _validate_conditionals(self, value: Dict[str, Any]) -> None:
102
+ if self.name.startswith("fupload.v1.modus.config"):
103
+ exclude = value.get("exclude_wtf")
104
+ if exclude == 1 and (value.get("account_name") or value.get("role_name")):
105
+ raise ValidationError("account_name and role_name must be empty when exclude_wtf=1", path="$.account_name")
106
+ if exclude == 0 and not str(value.get("account_name") or "").strip():
107
+ raise ValidationError("account_name is required when exclude_wtf=0", path="$.account_name")
108
+ if self.name.startswith(("fupload.v1.modus.config", "fupload.v1.modus.wa")):
109
+ if "title" in value and len(str(value["title"]).strip()) < 6:
110
+ raise ValidationError("must contain at least 6 characters", path="$.title")
111
+ if "content_text" in value and len(str(value["content_text"]).strip()) <= 20:
112
+ raise ValidationError("must contain more than 20 characters", path="$.content_text")
113
+ if "tags" in value:
114
+ tags = [item.strip() for item in str(value["tags"]).split(",")]
115
+ if any(not item for item in tags) or not 1 <= len(tags) <= 3:
116
+ raise ValidationError("must contain 1 to 3 comma-delimited tag IDs", path="$.tags")
117
+ if len(set(tags)) != len(tags):
118
+ raise ValidationError("must not contain duplicate tag IDs", path="$.tags")
119
+ if "image_url" in value:
120
+ images = [item.strip() for item in str(value["image_url"]).split(",")]
121
+ if any(not item for item in images) or not 1 <= len(images) <= 10:
122
+ raise ValidationError("must contain 1 to 10 comma-delimited image references", path="$.image_url")
123
+ if value.get("required_tier_id") is not None and (
124
+ value.get("platform") == 3 or value.get("synchronization_type") == 3
125
+ ):
126
+ raise ValidationError(
127
+ "required_tier_id cannot be combined with BigFoot synchronization",
128
+ path="$.required_tier_id",
129
+ )
130
+ if value.get("is_paid", 0) != 0:
131
+ raise ValidationError("current ModUs form requires is_paid=0", path="$.is_paid")
132
+ if value.get("price", 0) != 0:
133
+ raise ValidationError("current ModUs form requires price=0", path="$.price")
134
+ if value.get("share_type", 0) != 0:
135
+ raise ValidationError("current ModUs form requires share_type=0", path="$.share_type")
136
+ platform = value.get("platform")
137
+ synchronization = value.get("synchronization_type")
138
+ if (platform is None) != (synchronization is None):
139
+ raise ValidationError(
140
+ "platform and synchronization_type must be provided together",
141
+ path="$.synchronization_type" if synchronization is None else "$.platform",
142
+ )
143
+ if platform is not None and synchronization is not None and platform != synchronization:
144
+ raise ValidationError(
145
+ "platform and synchronization_type must select the same ModUs/BigFoot targets",
146
+ path="$.synchronization_type",
147
+ )
101
148
  if self.name in ("fupload.v1.modus.project.create", "fupload.v1.modus.project.edit"):
102
149
  snapshot = value.get("project_state")
103
150
  if snapshot is None:
@@ -424,14 +471,28 @@ class Schema:
424
471
  if "categories" in value and len(value["categories"]) > 5:
425
472
  raise ValidationError("must contain at most 5 items", path="$.categories")
426
473
  if "image_ops" in value:
427
- object_array("image_ops", {"op", "name", "base64"}, ("op", "name"))
474
+ if not isinstance(value["image_ops"], list) or not value["image_ops"]:
475
+ raise ValidationError("expected nonempty array", path="$.image_ops")
428
476
  if "images" not in value:
429
477
  raise ValidationError("images is required when image_ops is supplied", path="$.images")
430
478
  for index, operation in enumerate(value["image_ops"]):
431
- if operation["op"] not in ("upload", "delete"):
432
- raise ValidationError("must be upload or delete", path="$.image_ops[%d].op" % index)
433
- if not isinstance(operation["name"], str) or not operation["name"].strip():
434
- raise ValidationError("must not be empty", path="$.image_ops[%d].name" % index)
479
+ path = "$.image_ops[%d]" % index
480
+ if not isinstance(operation, dict):
481
+ raise ValidationError("expected object", path=path)
482
+ op = operation.get("op")
483
+ if op not in ("upload", "delete", "rename"):
484
+ raise ValidationError("must be upload, delete, or rename", path=path + ".op")
485
+ allowed = {"op", "from", "to"} if op == "rename" else {"op", "name", "base64"}
486
+ unknown = sorted(set(operation) - allowed)
487
+ if unknown:
488
+ raise ValidationError("unknown field", path=path + "." + unknown[0])
489
+ if op == "rename":
490
+ for name in ("from", "to"):
491
+ if not isinstance(operation.get(name), str) or not operation[name].strip():
492
+ raise ValidationError("must not be empty", path=path + "." + name)
493
+ continue
494
+ if not isinstance(operation.get("name"), str) or not operation["name"].strip():
495
+ raise ValidationError("must not be empty", path=path + ".name")
435
496
  if operation["op"] == "upload" and (not isinstance(operation.get("base64"), str) or not operation["base64"].strip()):
436
497
  raise ValidationError("base64 is required for upload", path="$.image_ops[%d].base64" % index)
437
498
  if operation["op"] == "delete" and "base64" in operation:
@@ -439,7 +500,7 @@ class Schema:
439
500
  for name in ("version", "type"):
440
501
  if name in value and isinstance(value[name], str) and not value[name].strip():
441
502
  raise ValidationError("must not be empty", path="$.%s" % name)
442
- if "file" in value and not zipfile.is_zipfile(value["file"]):
503
+ if self.name.startswith("fupload.v1.modus.plugin") and "file" in value and not zipfile.is_zipfile(value["file"]):
443
504
  raise ValidationError("file must be a valid ZIP archive", path="$.file")
444
505
 
445
506
 
@@ -693,6 +754,31 @@ MODUS_RELEASE = {
693
754
  "file": f("string", local_file=True),
694
755
  "transaction_log": f("string", max_length=1000),
695
756
  }
757
+ MODUS_BUILD_ID = f("integer", minimum=0, maximum=4)
758
+ MODUS_SHARE = {
759
+ "share_id": f("identifier", nonempty=True), "addons_id": f("string", max_length=10000),
760
+ "account_name": f("string", nullable=True, max_length=120), "backup_id": f("integer", minimum=1),
761
+ "content": f("string", nonempty=True), "content_text": f("string", nonempty=True),
762
+ "image_url": f("string", nonempty=True, max_length=1000), "is_paid": f("integer", choices=(0, 1)),
763
+ "is_public": f("integer", choices=(0, 1)), "price": f("number", minimum=0),
764
+ "share_type": f("integer", minimum=0), "tags": f("string", nonempty=True, max_length=1000),
765
+ "title": f("string", nonempty=True, max_length=120), "exclude_wtf": f("integer", choices=(0, 1)),
766
+ "role_name": f("string", nullable=True, max_length=120), "required_tier_id": f("integer", nullable=True, minimum=1),
767
+ "sub_type": f("integer", choices=(0, 1)), "platform": f("integer", choices=(1, 3)),
768
+ "synchronization_type": f("integer", choices=(1, 3)), "server_type": MODUS_BUILD_ID,
769
+ }
770
+ MODUS_IMPORT = {
771
+ "import_id": f("identifier", nonempty=True), "code_text": f("string", nonempty=True),
772
+ "content": f("string", nonempty=True), "addons_id": f("string", nonempty=True, max_length=10000),
773
+ "content_text": f("string", nonempty=True), "file_path": f("string", choices=("",)),
774
+ "image_url": f("string", nonempty=True, max_length=1000), "is_paid": f("integer", choices=(0, 1)),
775
+ "is_public": f("integer", choices=(0, 1)), "price": f("number", minimum=0),
776
+ "share_type": f("integer", minimum=0), "support_addon": f("string", nonempty=True, max_length=120),
777
+ "tags": f("string", nonempty=True, max_length=1000), "title": f("string", nonempty=True, max_length=120),
778
+ "version": f("string", nonempty=True, max_length=120), "required_tier_id": f("integer", nullable=True, minimum=1),
779
+ "sub_type": f("integer", choices=(0, 1)), "platform": f("integer", choices=(1, 3)),
780
+ "synchronization_type": f("integer", choices=(1, 3)), "server_type": MODUS_BUILD_ID,
781
+ }
696
782
  register("modus", "project", "create", required(MODUS_PROJECT_CREATE, ("project_state",)))
697
783
  register("modus", "project", "edit", required(with_id(MODUS_PROJECT_EDIT, "project_id"), ("project_id", "project_state")))
698
784
  register("modus", "project", "delete", required({"project_id": f("integer", minimum=1), "confirm": f("string", choices=("DELETE",))}, ("project_id", "confirm")))
@@ -701,7 +787,19 @@ register("modus", "plugin", "upload", required(MODUS_RELEASE, ("project_id", "fi
701
787
  register("modus", "plugin", "update", required(MODUS_RELEASE, ("project_id", "file_id")))
702
788
  register("modus", "plugin", "edit", required(MODUS_RELEASE, ("project_id", "file_id")))
703
789
  register("modus", "plugin", "delete", required({"project_id": f("integer", minimum=1), "file_id": f("integer", minimum=1), "confirm": f("string", choices=("DELETE",))}, ("project_id", "file_id", "confirm")))
704
-
790
+ register("modus", "media", "upload", required({"file": f("string", local_file=True)}, ("file",)))
791
+ register("modus", "config", "create", required(MODUS_SHARE, ("addons_id", "backup_id", "content", "content_text", "image_url", "tags", "title", "exclude_wtf")))
792
+ register("modus", "config", "update", required(MODUS_SHARE, ("share_id",)))
793
+ register("modus", "config", "edit", required(MODUS_SHARE, ("share_id",)))
794
+ register("modus", "config", "delete", required({"share_id": f("identifier", nonempty=True), "confirm": f("string", choices=("DELETE",)), "server_type": MODUS_BUILD_ID}, ("share_id", "confirm")))
795
+ register("modus", "config", "backup-edit", required({"backup_id": f("integer", minimum=1), "backup_name": f("string", nonempty=True, max_length=200), "server_type": MODUS_BUILD_ID}, ("backup_id", "backup_name")))
796
+ register("modus", "config", "backup-delete", required({"backup_id": f("integer", minimum=1), "confirm": f("string", choices=("DELETE",)), "server_type": MODUS_BUILD_ID}, ("backup_id", "confirm")))
797
+ register("modus", "wa", "create", required(MODUS_IMPORT, ("code_text", "content", "addons_id", "content_text", "image_url", "support_addon", "tags", "title", "version")))
798
+ register("modus", "wa", "update", required(MODUS_IMPORT, ("import_id",)))
799
+ register("modus", "wa", "edit", required(MODUS_IMPORT, ("import_id",)))
800
+ register("modus", "wa", "delete", required({"import_id": f("identifier", nonempty=True), "confirm": f("string", choices=("DELETE",)), "server_type": MODUS_BUILD_ID}, ("import_id", "confirm")))
801
+ register("modus", "wa", "version-publish", required({"import_id": f("identifier", nonempty=True), "version": f("string", nonempty=True, max_length=120), "code_text": f("string", nonempty=True), "changelog": f("string", max_length=10000), "server_type": MODUS_BUILD_ID}, ("import_id", "version", "code_text")))
802
+ register("modus", "wa", "version-delete", required({"version_id": f("identifier", nonempty=True), "confirm": f("string", choices=("DELETE",)), "server_type": MODUS_BUILD_ID}, ("version_id", "confirm")))
705
803
  register("blackbox", "plugin", "edit", required({
706
804
  "id": f("integer"), "name": f("string"), "logo_url": f("string"), "category_ids": f("array"),
707
805
  "type": f("integer", choices=(1, 9)), "desc": f("string"), "official": f("string"),
@@ -14,6 +14,17 @@ from .errors import FuploadError
14
14
  from .trust import official_opener, require_official_url
15
15
 
16
16
 
17
+ _IMAGE_CONTENT_TYPES = {
18
+ ".bmp": "image/bmp",
19
+ ".gif": "image/gif",
20
+ ".jpeg": "image/jpeg",
21
+ ".jpg": "image/jpeg",
22
+ ".png": "image/png",
23
+ ".svg": "image/svg+xml",
24
+ ".webp": "image/webp",
25
+ }
26
+
27
+
17
28
  def _http_error_details(raw: bytes, fallback: str) -> tuple[str, Any]:
18
29
  """Extract a server error only from a conforming UTF-8 JSON object."""
19
30
  try:
@@ -88,7 +99,8 @@ def multipart_request(
88
99
  str(value).encode("utf-8"), b"\r\n",
89
100
  ])
90
101
  filename = os.path.basename(file_path)
91
- content_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
102
+ suffix = os.path.splitext(filename)[1].lower()
103
+ content_type = _IMAGE_CONTENT_TYPES.get(suffix) or mimetypes.guess_type(filename)[0] or "application/octet-stream"
92
104
  chunks.extend([
93
105
  ("--%s\r\n" % boundary).encode(),
94
106
  ('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (file_field, filename)).encode("utf-8"),
@@ -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.11",
5
- "skill_version": "0.0.11",
6
- "tree_sha256": "a0d589cb5d67c7bfbd34862ee4c3109e88068662ae3b7430c0635e71ace9a8d9",
4
+ "package_version": "0.0.15",
5
+ "skill_version": "0.0.15",
6
+ "tree_sha256": "cfccc3347d2013188a37b120b94282c839ad81eb4fa92f8617e0cfa12dfbbaef",
7
7
  "files": [
8
8
  {
9
9
  "path": "agents/openai.yaml",
@@ -92,8 +92,8 @@
92
92
  },
93
93
  {
94
94
  "path": "references/modus.md",
95
- "bytes": 8705,
96
- "sha256": "10c2db10ade101c6738d9007865db68da54892da59fc1cc0fc1a422296a4bf07"
95
+ "bytes": 37142,
96
+ "sha256": "84e1524bc3404de3c3bc0582d85651b884c616ff009b1d01839fb84e348917b3"
97
97
  },
98
98
  {
99
99
  "path": "references/newbee-official-cli.md",
@@ -113,7 +113,7 @@
113
113
  {
114
114
  "path": "scripts/fupload_cli/__init__.py",
115
115
  "bytes": 50,
116
- "sha256": "c7d5308bddd21b0e8042689cdeaba91ba45170a998f42844d42f88b7248fc057"
116
+ "sha256": "9159fa55aa8ef07864abf206b64aabd44ea78c2e987ead8b51ec9384b5d1cebf"
117
117
  },
118
118
  {
119
119
  "path": "scripts/fupload_cli/blackbox_web.py",
@@ -127,8 +127,8 @@
127
127
  },
128
128
  {
129
129
  "path": "scripts/fupload_cli/cli.py",
130
- "bytes": 31958,
131
- "sha256": "897c2e7a7fea00125023d7fc977aaa6e3b7729b50a468e7deadfacc04d89f9b8"
130
+ "bytes": 37420,
131
+ "sha256": "3a052ec53c29ca95eafc9ba845d448ef45e4bd0231bf41ec1906e55281aa1650"
132
132
  },
133
133
  {
134
134
  "path": "scripts/fupload_cli/curseforge.py",
@@ -137,8 +137,8 @@
137
137
  },
138
138
  {
139
139
  "path": "scripts/fupload_cli/dd_broker.py",
140
- "bytes": 25750,
141
- "sha256": "48ace5b13687c0d8364c62ee9407ed6ead16946df62ce34d04de1aa83ca44026"
140
+ "bytes": 26638,
141
+ "sha256": "a23fb486dd1ca9d0a1d2f2ca642690535a28e05051ce828ce066cdbc67b37b37"
142
142
  },
143
143
  {
144
144
  "path": "scripts/fupload_cli/dd_sidecar.py",
@@ -157,8 +157,8 @@
157
157
  },
158
158
  {
159
159
  "path": "scripts/fupload_cli/io.py",
160
- "bytes": 4515,
161
- "sha256": "dafcfcaa493def7bc42719f357416208a3cabbb163cf651fb18307b501b46e4b"
160
+ "bytes": 5319,
161
+ "sha256": "57af3921d016815ed5747788881f86c8a6ce04a939e9f6f6571bd6a3f7383a4d"
162
162
  },
163
163
  {
164
164
  "path": "scripts/fupload_cli/modus_zip.py",
@@ -167,8 +167,8 @@
167
167
  },
168
168
  {
169
169
  "path": "scripts/fupload_cli/modus.py",
170
- "bytes": 42241,
171
- "sha256": "ee298b064f4b3c4a25081f76e93d48b18f1b810f647437e78d78a4a20ab5bd5c"
170
+ "bytes": 74134,
171
+ "sha256": "9841ce7188083b62c1c217d89436b3c3ddc3ecf726719d3c44c0b83027172fac"
172
172
  },
173
173
  {
174
174
  "path": "scripts/fupload_cli/newbee_auth.py",
@@ -182,8 +182,8 @@
182
182
  },
183
183
  {
184
184
  "path": "scripts/fupload_cli/schema.py",
185
- "bytes": 46291,
186
- "sha256": "025d6f4af1e8adda87aed14a075028c370e78bf0def6738ab303e2f1798aefd8"
185
+ "bytes": 54561,
186
+ "sha256": "17d57a9c9c53e556faf9e415020d47f84965283680c66f3f1c33beef6b83d104"
187
187
  },
188
188
  {
189
189
  "path": "scripts/fupload_cli/state_machine.py",
@@ -192,8 +192,8 @@
192
192
  },
193
193
  {
194
194
  "path": "scripts/fupload_cli/transport.py",
195
- "bytes": 4895,
196
- "sha256": "0489493c1250c50d460c408394824eba7fc0f6efea9a3c1ec06f3ad67e6b89a7"
195
+ "bytes": 5195,
196
+ "sha256": "37eb742c1cf75184d14f4f8ae85117222cad3d0748ab4c211452e78b5da0c279"
197
197
  },
198
198
  {
199
199
  "path": "scripts/fupload_cli/trust.py",
@@ -208,7 +208,7 @@
208
208
  {
209
209
  "path": "SKILL.md",
210
210
  "bytes": 25720,
211
- "sha256": "cfc3ce770c3594c5a787b25da03cb2e92e0f6ea6fbd162f26f4b6b3c44295a89"
211
+ "sha256": "fae17edc0a59ea74dc70a5744b5aef46a057c23a9056e6d6b1e60990fa54782f"
212
212
  }
213
213
  ]
214
214
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@follenfang/fupload",
3
- "version": "0.0.11",
3
+ "version": "0.0.15",
4
4
  "description": "Install and run the Fuploader Agent Skill and Python CLI.",
5
5
  "type": "module",
6
6
  "bin": {