@miravia_test/miravia-publish-skill 0.1.6 → 0.1.8
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/dist/SKILL.md +16 -6
- package/dist/VERSION +1 -1
- package/dist/scripts/extraction_engine.py +43 -1
- package/dist/scripts/i18n.py +52 -4
- package/dist/scripts/miravia_client.py +6 -0
- package/dist/scripts/product_client.py +23 -4
- package/dist/scripts/product_skill.py +57 -0
- package/dist/scripts/publish_module.py +748 -117
- package/package.json +1 -1
package/dist/SKILL.md
CHANGED
|
@@ -71,6 +71,11 @@ description: Miravia 商品发布 AI Skill。仅负责商品创建(手动一
|
|
|
71
71
|
|
|
72
72
|
### 4.2 AI 抽取模式
|
|
73
73
|
|
|
74
|
+
> **⚠️ Agent 行为准则(必须遵守,禁止绕过)**
|
|
75
|
+
> 1. 卖家上传文件/文件夹/URL 后,Agent **必须**带 `--source` 调用本 Skill 完成读取与抽取,**禁止**自己读取/解析文件内容后改走手动模式(手动模式仅用于卖家直接以 CLI 参数逐项提供的场景)。
|
|
76
|
+
> 2. **Excel/CSV 源按表头规则抽取,完全不需要 LLM key**;只有 PDF/Word/图片/URL 等非表格源才需要 LLM。Agent **不得**以「未配置 LLM key、正则抽取靠不住」为由绕过 Skill——该前提对 Excel/CSV 不成立。
|
|
77
|
+
> 3. 上传文件后的**第一步固定为 CTB 确认**:先向卖家展示抽取到的**标题、品牌、类目预测**并等待确认(`need_ctb_confirm` / 必要时 `need_category` / `need_brand`)。**在 CTB 确认通过之前,禁止进入后续字段抽取与缺字段(EAN / GPSR / 合规等)校验提示**。CTB 确认之后才依次做关键字段与字段摘要检查。
|
|
78
|
+
|
|
74
79
|
0. **Stage 0 - 授权确认**:
|
|
75
80
|
- 首次操作时,Skill 返回 `status: need_authorization`,列出三项授权请求(读取卖家资料、访问商品数据、更新商品数据)。
|
|
76
81
|
- 卖家确认后,加 `--authorized` 重新运行命令,进入后续流程。
|
|
@@ -84,7 +89,8 @@ description: Miravia 商品发布 AI Skill。仅负责商品创建(手动一
|
|
|
84
89
|
- `/seller/shipping_permission/get` 决定默认物流方式(DBS/DBM)。
|
|
85
90
|
- `/manufacturer/get`、`/eu/responsible/person/get` 匹配制造商/EU 负责人。
|
|
86
91
|
5. **Stage 3 - 关键字段检查**:
|
|
87
|
-
-
|
|
92
|
+
- **主图/SKU 图全空 → 硬阻断且不可转 Draft**(`status: need_mandatory_fields`,`draft_mode_available: false`):Miravia `/product/create` 要求至少一张图片,缺失会以 `4224 MAIN_IMAGE_OR_SKU_IMAGE_EMPTY` 被平台硬拒,草稿也绕不过。
|
|
93
|
+
- 缺价格/重量/长宽高等(接口判定必填时)→ 阻断,但允许 `--draft` 降级发布。
|
|
88
94
|
6. **Stage 4 - 字段摘要/草稿确认**:
|
|
89
95
|
- 汇总已抽取字段与缺失字段。
|
|
90
96
|
- 关键字段缺失时询问是否以 `--draft` 模式发布为 `inactive`。
|
|
@@ -103,9 +109,10 @@ description: Miravia 商品发布 AI Skill。仅负责商品创建(手动一
|
|
|
103
109
|
| `need_category` | 类目未找到、类目 ID 无效、推荐多候选或预测失败 | 让用户提供正确的类目 ID(`--category-id`)或从候选中选择一个(`--confirmed-category-id`),**禁止重复搜索同一关键词**;所有 ID 均校验类目树;关键词搜索失败后调 `/product/category/recommend/get` 推荐,Top1 自动选中,TopN 展示候选,空结果展示全量类目树 |
|
|
104
110
|
| `need_brand` | 品牌未找到或存在多个候选 | 让用户提供正确的品牌名(`--brand-name`)或从候选中选择一个(`--confirmed-brand-id`),**禁止重复搜索同一关键词** |
|
|
105
111
|
| `need_ctb_confirm` | 需要确认类目/标题/品牌 | 回传 `ctb_confirm_token` |
|
|
106
|
-
| `need_brand_create` | 品牌未找到,需去 Seller Center
|
|
107
|
-
| `need_mandatory_fields` |
|
|
112
|
+
| `need_brand_create` | 品牌未找到,需去 Seller Center 处理 | 先引导查看已有品牌及审核状态(https://sellercenter.miravia.es/apps/setting/seller/brandlist,可能已在审核中),确无再创建(https://sellercenter.miravia.es/apps/product/publish/brand);创建后重新运行命令 |
|
|
113
|
+
| `need_mandatory_fields` | 缺少必填字段;**图片缺失为硬阻断(不可 Draft)**,价格/重量/尺寸缺失可 `--draft` | 补充字段后重跑 |
|
|
108
114
|
| `need_category_attributes` | 类目必填属性缺失(AI抽取模式) | 通过 `--attribute-data` 补充或使用 `--draft` |
|
|
115
|
+
| `need_attribute_mapping` | Excel/CSV 列未能自动匹配到类目属性(不静默丢弃),返回该列及其候选属性 | 向用户展示候选属性,确认后用 `--attribute-data '{"<属性名>":"<值>"}'` 重跑;非商品属性则删除该列 |
|
|
109
116
|
| `need_field_summary` | 需要确认字段摘要或选择草稿模式 | 回传 `field_summary_token`,可加 `--draft` |
|
|
110
117
|
| `need_compliance` | 制造商/EU 负责人匹配失败 | 按返回候选选择或补充信息后重跑 |
|
|
111
118
|
| `compliance_missing_suggest_draft` | 合规字段缺失(制造商/欧代/Safety Warning内容/合规文档) | 补充合规字段或加 `--draft` 发布为草稿 |
|
|
@@ -124,7 +131,9 @@ description: Miravia 商品发布 AI Skill。仅负责商品创建(手动一
|
|
|
124
131
|
| `title_invalid` | 商品标题不符合规范(过短/过长/词数不足) | 提示具体错误 + 标题格式建议(Brand+Category+Model+Features+Scenario),阻断后续流程,禁止转 Draft |
|
|
125
132
|
| `need_category_permission` | 卖家无该类目发布权限 | 询问是否以 Draft 模式发布(待审批通过后自动激活),提供类目权限申请链接 https://sellercenter.miravia.es/apps/setting/category;或更换类目后重跑 |
|
|
126
133
|
| `preview` | 最终发布预览 | 回传 `publish_preview_token` |
|
|
127
|
-
| `success` |
|
|
134
|
+
| `success` | 创建成功(必须返回 `item_id`,无 `item_id` 一律视为失败) | 返回 `item_id`、`pdp_url`、审核提示 |
|
|
135
|
+
| `draft_created_with_issues` | 草稿创建成功(接口返回了 `item_id`)但必填字段/权限待补全 | 在 Seller Center 草稿中补全字段后激活,禁止当作完全成功 |
|
|
136
|
+
| `create_failed` | `/product/create` 失败或未返回 `item_id`(含 4234 无 `item_id` 的场景,草稿并未创建) | 展示错误码/message/trace_id 定位根因,修复后重跑;重跑前先在 Seller Center 确认是否已有重复草稿 |
|
|
128
137
|
| `brand_permission_failed` | 品牌权限不足/待审批 (BRAND_PENDING / BRAND_PERMISSION_DENIED) | 加 `--draft` 发布为草稿,或在 Seller Center 申请品牌权限后重跑 |
|
|
129
138
|
|
|
130
139
|
---
|
|
@@ -134,11 +143,11 @@ description: Miravia 商品发布 AI Skill。仅负责商品创建(手动一
|
|
|
134
143
|
| ID | 规则 | 说明 |
|
|
135
144
|
|----|------|------|
|
|
136
145
|
| P1 | **sellerId 自动注入** | 从开放平台鉴权上下文读取,禁止用户传入。 |
|
|
137
|
-
| P2 |
|
|
146
|
+
| P2 | **必填判定统一入口** | 所有必填/条件必填检查都走同一规则(`_field_is_required`):**我们的规则优先命中→否则以 `/category/attributes/get` 的 `is_mandatory` 为准**;接口未返回该属性等同选填。创建前必须先确定叶子类目并拉取类目属性。 |
|
|
138
147
|
| P3 | **图片先迁移** | 外部图片 URL 必须经 `/image/migrate` 迁移到 Miravia CDN。 |
|
|
139
148
|
| P4 | **三阶段确认** | CTB 确认 → 字段摘要确认 → 最终发布预览确认。 |
|
|
140
149
|
| P5 | **pre-check 集中** | 品牌/类目权限等校验统一在 `/product/batchValidate` 提交前完成。 |
|
|
141
|
-
| P6 | **必填项阻断** |
|
|
150
|
+
| P6 | **必填项阻断** | 仅结构核心字段(图片、常规价格、库存、标题、类目、品牌、物流)恒定阻断;重量/长宽高为 SKU 级类目属性,仅当接口标 `is_mandatory=1` 时阻断;Seller SKU 非必填(空时接口自动生成)。batchValidate 为最终权威兜底。 |
|
|
142
151
|
| P7 | **草稿模式** | 关键字段缺失时,商家可选择 `--draft` 发布为 `inactive`。 |
|
|
143
152
|
| P8 | **品牌创建后重跑** | 品牌未找到时不自动轮询,给出 Seller Center 创建链接,创建完成后商家重跑命令。 |
|
|
144
153
|
| P9 | **默认物流** | 通过 `/seller/shipping_permission/get` 的 `DBS_permission` 决定默认 `DBS` 或 `DBM`。 |
|
|
@@ -146,6 +155,7 @@ description: Miravia 商品发布 AI Skill。仅负责商品创建(手动一
|
|
|
146
155
|
| P11 | **API 重试** | 每个 API 调用最多重试 3 次。 |
|
|
147
156
|
| P12 | **埋点与审计** | 记录 Skill 调用与商品操作审计日志。 |
|
|
148
157
|
| P13 | **创建前必须确认** | AI Agent **禁止**在收集完信息后直接调用 `/product/create`。必须先向卖家展示完整字段摘要(标题、类目、品牌、价格、SKU、包装、图片、GPSR、Safety warning 等所有已填字段),明确标注缺失字段,等卖家回复「确认」后才能执行创建。无论手动模式还是 AI 抽取模式均适用。 |
|
|
158
|
+
| P14 | **文件上传必走 Skill + CTB 优先** | 卖家上传文件/URL 后,Agent 必须带 `--source` 调用 Skill,禁止自己读文件转手动模式;Excel/CSV 无需 LLM key。抽取后第一步固定为 CTB(标题/品牌/类目)确认,CTB 确认前不得提示 EAN/GPSR 等缺字段。 |
|
|
149
159
|
|
|
150
160
|
---
|
|
151
161
|
|
package/dist/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.8
|
|
@@ -87,6 +87,12 @@ class MiraviaListingSchema:
|
|
|
87
87
|
|
|
88
88
|
raw_skus: List[Dict[str, Any]] = field(default_factory=list)
|
|
89
89
|
|
|
90
|
+
# Excel/CSV columns whose header did not match any known field in
|
|
91
|
+
# _TABLE_FIELD_SYNONYMS. Kept verbatim (header -> value) so the publish
|
|
92
|
+
# flow can bind them against the category attribute definitions returned
|
|
93
|
+
# by /category/attributes/get instead of silently dropping them.
|
|
94
|
+
unmatched_columns: Dict[str, str] = field(default_factory=dict)
|
|
95
|
+
|
|
90
96
|
def to_dict(self) -> Dict[str, Any]:
|
|
91
97
|
return asdict(self)
|
|
92
98
|
|
|
@@ -365,6 +371,7 @@ _TABLE_FIELD_SYNONYMS: Dict[str, tuple] = {
|
|
|
365
371
|
"length_cm": ("长度", "长cm", "长", "length", "largura"),
|
|
366
372
|
"width_cm": ("宽度", "宽cm", "宽", "width", "ancho"),
|
|
367
373
|
"height_cm": ("高度", "高cm", "高", "height", "altura"),
|
|
374
|
+
"delivery_method": ("deliverymethod", "delivery", "shippingmethod", "shipping", "物流方式", "物流", "发货方式", "配送方式", "metododeenvío", "metododeenvio", "envío", "envio", "spedizione", "metododiconsegna"),
|
|
368
375
|
"manufacturer_name": ("制造商", "厂家", "manufacturer", "fabricante"),
|
|
369
376
|
"eu_responsible_person_name": ("欧盟负责人", "欧代", "euresponsibleperson", "eurp"),
|
|
370
377
|
"variation_1": ("颜色", "color", "colour", "variation1", "variation", "款式"),
|
|
@@ -383,6 +390,7 @@ _PRODUCT_LEVEL_FIELDS = {
|
|
|
383
390
|
"length_cm",
|
|
384
391
|
"width_cm",
|
|
385
392
|
"height_cm",
|
|
393
|
+
"delivery_method",
|
|
386
394
|
"manufacturer_name",
|
|
387
395
|
"eu_responsible_person_name",
|
|
388
396
|
}
|
|
@@ -395,6 +403,18 @@ def _norm_header(header: Any) -> str:
|
|
|
395
403
|
return re.sub(r"[\s_\-—·()()【】\[\].,,、::]+", "", str(header)).lower()
|
|
396
404
|
|
|
397
405
|
|
|
406
|
+
# Overly generic single-word synonyms. These are allowed to match a header by
|
|
407
|
+
# equality or as a prefix (e.g. "Image", "Product name") but must NOT match by
|
|
408
|
+
# substring, otherwise a compound header such as "Package/Label Photo(EU)"
|
|
409
|
+
# (normalized "packagelabelphotoeu") is greedily absorbed by "photo" and
|
|
410
|
+
# reported as a main product image instead of binding to its real category
|
|
411
|
+
# attribute Package_Label_Photo_EU.
|
|
412
|
+
_GENERIC_SUBSTRING_SYNONYMS = frozenset({
|
|
413
|
+
"image", "images", "photo", "photos", "picture", "pictures",
|
|
414
|
+
"name", "names", "size", "color", "colour", "talla", "nombre",
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
|
|
398
418
|
def _match_table_field(header_norm: str) -> Optional[str]:
|
|
399
419
|
"""Resolve a normalized header to a schema field via 3-pass matching
|
|
400
420
|
(exact -> prefix -> substring) to keep specific fields from being
|
|
@@ -406,7 +426,11 @@ def _match_table_field(header_norm: str) -> Optional[str]:
|
|
|
406
426
|
return field_name
|
|
407
427
|
if pass_kind == "prefix" and header_norm.startswith(syn):
|
|
408
428
|
return field_name
|
|
409
|
-
if
|
|
429
|
+
if (
|
|
430
|
+
pass_kind == "contains"
|
|
431
|
+
and syn not in _GENERIC_SUBSTRING_SYNONYMS
|
|
432
|
+
and syn in header_norm
|
|
433
|
+
):
|
|
410
434
|
return field_name
|
|
411
435
|
return None
|
|
412
436
|
|
|
@@ -464,12 +488,20 @@ def extract_schema_from_tables(content_items: List[Dict[str, Any]]) -> Optional[
|
|
|
464
488
|
schema = MiraviaListingSchema()
|
|
465
489
|
# column plans: field -> (table_index, header) pairs, preserving order
|
|
466
490
|
column_plan: Dict[str, List[tuple]] = {}
|
|
491
|
+
# Headers that matched no known field, kept in order for later binding.
|
|
492
|
+
unmatched_headers: List[str] = []
|
|
493
|
+
seen_unmatched: set = set()
|
|
467
494
|
|
|
468
495
|
for t_idx, table in enumerate(tables):
|
|
469
496
|
for header in table.get("headers", []):
|
|
470
497
|
field_name = _match_table_field(_norm_header(header))
|
|
471
498
|
if field_name:
|
|
472
499
|
column_plan.setdefault(field_name, []).append((t_idx, header))
|
|
500
|
+
else:
|
|
501
|
+
hkey = _clean_cell(header)
|
|
502
|
+
if hkey and hkey not in seen_unmatched:
|
|
503
|
+
seen_unmatched.add(hkey)
|
|
504
|
+
unmatched_headers.append(hkey)
|
|
473
505
|
|
|
474
506
|
if not column_plan:
|
|
475
507
|
return None
|
|
@@ -571,6 +603,16 @@ def extract_schema_from_tables(content_items: List[Dict[str, Any]]) -> Optional[
|
|
|
571
603
|
if sku:
|
|
572
604
|
schema.raw_skus.append(sku)
|
|
573
605
|
|
|
606
|
+
# Keep every non-whitelisted column (first non-empty value) so the publish
|
|
607
|
+
# flow can bind it against the category attribute definitions instead of
|
|
608
|
+
# dropping it. Keyed by the original header text.
|
|
609
|
+
for header in unmatched_headers:
|
|
610
|
+
for t_idx, row in all_rows:
|
|
611
|
+
value = _clean_cell(row.get(header))
|
|
612
|
+
if value:
|
|
613
|
+
schema.unmatched_columns[header] = value
|
|
614
|
+
break
|
|
615
|
+
|
|
574
616
|
# Only claim success when something meaningful was extracted; otherwise
|
|
575
617
|
# the caller falls back to LLM extraction.
|
|
576
618
|
if schema.product_title or schema.general_price or schema.raw_skus:
|
package/dist/scripts/i18n.py
CHANGED
|
@@ -172,14 +172,21 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
172
172
|
"pub_unsupported_format": "Unsupported file format: {source}. Only PDF, Word, Excel, Image, and URL are supported.",
|
|
173
173
|
"pub_invalid_url": "Failed to fetch URL: {source}. Please check the URL and try again.",
|
|
174
174
|
"pub_url_fetch_failed": "Failed to fetch content from {source}: {error}. Please check the URL and try again.",
|
|
175
|
-
"pub_llm_not_configured": "
|
|
176
|
-
"pub_llm_not_configured_next": "
|
|
175
|
+
"pub_llm_not_configured": "LLM extraction is unavailable: no LLM API key is configured (OPENAI_API_KEY or DASHSCOPE_API_KEY). This only applies to non-table sources (PDF/Word/image/URL); Excel/CSV sources are extracted by header rules and need no LLM key.",
|
|
176
|
+
"pub_llm_not_configured_next": "If the source is Excel/CSV, just rerun with --source (no key needed). For PDF/Word/image/URL, configure the LLM API key and rerun with --source. Do NOT read the file yourself or switch to manual mode; the first step must be CTB (title/brand/category) confirmation.",
|
|
177
177
|
"pub_create_requires_category": "create_product requires --category-id (leaf category)",
|
|
178
178
|
"pub_create_requires_name": "create_product requires --product-name",
|
|
179
179
|
"pub_create_preview_message": "Please review the product creation details below and confirm before I create the product.",
|
|
180
180
|
"pub_create_preview_next": "If the details are correct, rerun with --publish-preview-token {token} to execute.",
|
|
181
181
|
"pub_create_success": "Product created successfully.",
|
|
182
182
|
"pub_create_success_next": "Ask the seller if they need to view the created product or make further edits.",
|
|
183
|
+
"pub_draft_created_with_issues": "Draft created, but some required fields or authorizations are missing. Complete them in Seller Center to activate the product.",
|
|
184
|
+
"pub_draft_update_hint": "Fill in the missing fields in Seller Center draft list, then activate the product.",
|
|
185
|
+
"pub_create_failed_title": "Product creation failed",
|
|
186
|
+
"pub_create_failed_message": "Product was NOT created (including as draft): error code {code}, reason: {reason}.",
|
|
187
|
+
"pub_create_failed_no_item_id": "The create API returned success but no item_id, so the product was not actually created.",
|
|
188
|
+
"pub_create_failed_check_and_rerun": "Check whether a duplicate draft already exists in Seller Center, fix the reported issue, then rerun.",
|
|
189
|
+
"pub_create_failed_trace_hint": "When contacting platform support, provide trace_id {trace_id} / request_id {request_id} to locate the root cause.",
|
|
183
190
|
"pub_title_too_short": "Product title is too short (minimum {min} characters, got {actual}).",
|
|
184
191
|
"pub_title_too_long": "Product title is too long (maximum {max} characters, got {actual}).",
|
|
185
192
|
"pub_title_too_few_words": "Product title must have at least {min} words (got {actual}).",
|
|
@@ -234,6 +241,7 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
234
241
|
"pub_summary_conditional_section": "[Conditional Fields]",
|
|
235
242
|
"pub_field_status_filled": "Filled",
|
|
236
243
|
"pub_field_status_missing": "MISSING",
|
|
244
|
+
"pub_field_status_optional": "Optional",
|
|
237
245
|
"pub_mandatory_missing_block": "Cannot publish: missing mandatory fields [{fields}]. Please fill them and rerun.",
|
|
238
246
|
"pub_conditional_missing_draft": "Missing conditional fields [{fields}]. The product will be saved as Inactive (Draft) and auto-activated once completed. Confirm to proceed?",
|
|
239
247
|
# DBM/DBS auto-switch
|
|
@@ -241,6 +249,7 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
241
249
|
"pub_dbm_dim_sum_exceeded": "Dimension sum {dim_sum} exceeds DBM limit {limit}cm.",
|
|
242
250
|
"pub_dbm_volume_exceeded": "Volumetric weight {volume} exceeds DBM limit {limit}.",
|
|
243
251
|
"pub_dbm_auto_switch_to_dbs": "Package exceeds DBM limits. Delivery method auto-switched to DBS.",
|
|
252
|
+
"pub_dbs_no_permission_downgrade": "Seller requested DBS but this account has no DBS permission; the delivery method was set to DBM.",
|
|
244
253
|
"pub_dbm_no_dbs_permission": "Package exceeds DBM limits but seller has no DBS permission. Cannot auto-switch to DBS.",
|
|
245
254
|
"pub_dbm_fix_or_apply_dbs": "Please reduce package size/weight to meet DBM limits, or apply for DBS permission in Seller Center.",
|
|
246
255
|
# SKU already exists (concurrency conflict)
|
|
@@ -302,6 +311,7 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
302
311
|
"pub_ctb_confirm_next": "If correct, rerun with --ctb-confirm-token {token} to continue.",
|
|
303
312
|
"pub_brand_not_found_title": "Brand not found",
|
|
304
313
|
"pub_brand_not_found_message": "No brand matching \"{brand_name}\" was found in Miravia. Would you like to create a new brand?",
|
|
314
|
+
"pub_brand_list_link": "Check your existing brands and their approval status here: https://sellercenter.miravia.es/apps/setting/seller/brandlist",
|
|
305
315
|
"pub_brand_create_link": "Create a new brand here: https://sellercenter.miravia.es/apps/product/publish/brand",
|
|
306
316
|
"pub_brand_create_next": "After creating the brand in Seller Center, rerun this command with --brand-name <brand_name> or --confirmed-brand-id <brand_id>.",
|
|
307
317
|
"pub_brand_reconfirm_or_choose": "If you do not want to create a brand, please re-enter the brand name or choose one from the matched list below.",
|
|
@@ -310,7 +320,10 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
310
320
|
"pub_field_summary_description": "Review which fields have been filled and which are still missing.",
|
|
311
321
|
"pub_field_status_filled": "filled",
|
|
312
322
|
"pub_field_status_missing": "missing",
|
|
323
|
+
"pub_field_status_optional": "optional",
|
|
313
324
|
"pub_mandatory_fields_missing": "The following required fields are missing and must be provided before continuing: {fields}",
|
|
325
|
+
"pub_image_required_no_draft": "At least one image (main image or a SKU image) is required. This is a Miravia platform requirement that cannot be bypassed even by publishing as Draft; missing: {fields}.",
|
|
326
|
+
"pub_add_images_and_rerun": "Add at least one product image (URL or local file) and rerun the command.",
|
|
314
327
|
"pub_key_fields_missing_draft_question": "Some key fields are missing. You can publish as a draft and complete them later. To publish as draft, rerun with --draft.",
|
|
315
328
|
"pub_manufacturer_eurp_mismatch": "The manufacturer / EU responsible person does not match an approved record. Please choose a valid one or publish as draft.",
|
|
316
329
|
"pub_seller_sku_recommended": "Seller SKU is strongly recommended as the internal identifier for this SKU. Please provide one if available.",
|
|
@@ -326,6 +339,9 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
326
339
|
"pub_need_category_attributes_description": "The category {category_id} requires the following attributes that were not found in the extracted data. Please provide them via --attribute-data or rerun with --draft to publish as an inactive draft.",
|
|
327
340
|
"pub_need_category_attributes_item": "{idx}. {label} ({en_name}) - {input_type}",
|
|
328
341
|
"pub_need_category_attributes_next": "Provide the missing attributes and rerun with --attribute-data '<json>', or use --draft to publish as inactive draft and complete them later.",
|
|
342
|
+
"pub_need_attribute_mapping_title": "Some columns could not be matched automatically",
|
|
343
|
+
"pub_need_attribute_mapping_description": "These Excel/CSV columns did not clearly match a category attribute, so they were not bound. Please confirm which attribute each column should map to, then rerun with --attribute-data using the canonical attribute names shown below (or drop the column if it is not a product attribute).",
|
|
344
|
+
"pub_need_attribute_mapping_next": "Rerun with --attribute-data '{\"<attribute_name>\": \"<value>\"}' using one of the suggested attribute names, or remove the column if it is not needed.",
|
|
329
345
|
"pub_html_cleaned_notice": "The product description has been cleaned: unsafe HTML tags (script, iframe, style attributes, etc.) were removed. Only safe formatting tags (p, br, b, ul, li, etc.) are kept.",
|
|
330
346
|
"pub_create_pending_review": "Product submitted successfully. It is currently under Miravia review and temporarily unavailable for sale; approval may take up to 48 hours, especially if package photos or documents are required. Once approved, it will be activated automatically. If rejected, please check the Suspended tab.",
|
|
331
347
|
"pub_pdp_link": "Product detail page: {url}",
|
|
@@ -473,6 +489,13 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
473
489
|
"pub_create_preview_next": "Si los detalles son correctos, vuelve a ejecutar con --publish-preview-token {token}.",
|
|
474
490
|
"pub_create_success": "Producto creado correctamente.",
|
|
475
491
|
"pub_create_success_next": "Pregunta al vendedor si necesita ver el producto creado o hacer más ediciones.",
|
|
492
|
+
"pub_draft_created_with_issues": "Borrador creado, pero faltan campos obligatorios o autorizaciones. Complételos en Seller Center para activar el producto.",
|
|
493
|
+
"pub_draft_update_hint": "Complete los campos faltantes en la lista de borradores de Seller Center y active el producto.",
|
|
494
|
+
"pub_create_failed_title": "Error al crear el producto",
|
|
495
|
+
"pub_create_failed_message": "El producto NO se creó (tampoco como borrador): código {code}, motivo: {reason}.",
|
|
496
|
+
"pub_create_failed_no_item_id": "La API de creación devolvió éxito pero no incluyó item_id, por lo que el producto no se creó.",
|
|
497
|
+
"pub_create_failed_check_and_rerun": "Compruebe en Seller Center si ya existe un borrador duplicado, corrija el problema y vuelva a ejecutar.",
|
|
498
|
+
"pub_create_failed_trace_hint": "Al contactar con el soporte, indique trace_id {trace_id} / request_id {request_id}.",
|
|
476
499
|
"pub_title_too_short": "El título del producto es demasiado corto (mínimo {min} caracteres, tiene {actual}).",
|
|
477
500
|
"pub_title_too_long": "El título del producto es demasiado largo (máximo {max} caracteres, tiene {actual}).",
|
|
478
501
|
"pub_title_too_few_words": "El título del producto debe tener al menos {min} palabras (tiene {actual}).",
|
|
@@ -496,6 +519,7 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
496
519
|
"pub_need_attributes_field_item": "{idx}. {label} ({en_name}) - {input_type}, {mandatory}",
|
|
497
520
|
"pub_attribute_data_next": "Vuelve a ejecutar con --attribute-data '<json>' para proporcionar los valores de atributos.",
|
|
498
521
|
"pub_image_migration_partial": "Algunas imágenes no se migraron: {failed}. Reemplázalas o vuelve a ejecutar sin esas URLs.",
|
|
522
|
+
"pub_field_status_optional": "Opcional",
|
|
499
523
|
},
|
|
500
524
|
"it": {
|
|
501
525
|
"greeting": "Cosa vorresti fare con la skill di inventario? Rispondi con la lettera dell'opzione o descrivi la tua richiesta:",
|
|
@@ -640,6 +664,13 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
640
664
|
"pub_create_preview_next": "Se i dettagli sono corretti, riesegui con --publish-preview-token {token}.",
|
|
641
665
|
"pub_create_success": "Prodotto creato con successo.",
|
|
642
666
|
"pub_create_success_next": "Chiedi al venditore se ha bisogno di visualizzare il prodotto creato o apportare modifiche.",
|
|
667
|
+
"pub_draft_created_with_issues": "Bozza creata, ma mancano campi obbligatori o autorizzazioni. Completali in Seller Center per attivare il prodotto.",
|
|
668
|
+
"pub_draft_update_hint": "Compila i campi mancanti nell'elenco bozze di Seller Center e attiva il prodotto.",
|
|
669
|
+
"pub_create_failed_title": "Creazione del prodotto non riuscita",
|
|
670
|
+
"pub_create_failed_message": "Il prodotto NON è stato creato (nemmeno come bozza): codice {code}, motivo: {reason}.",
|
|
671
|
+
"pub_create_failed_no_item_id": "L'API di creazione ha restituito successo ma senza item_id; il prodotto non è stato creato.",
|
|
672
|
+
"pub_create_failed_check_and_rerun": "Verifica in Seller Center se esiste già una bozza duplicata, correggi il problema e riesegui.",
|
|
673
|
+
"pub_create_failed_trace_hint": "Contattando il supporto, indica trace_id {trace_id} / request_id {request_id}.",
|
|
643
674
|
"pub_title_too_short": "Il titolo del prodotto è troppo corto (minimo {min} caratteri, ha {actual}).",
|
|
644
675
|
"pub_title_too_long": "Il titolo del prodotto è troppo lungo (massimo {max} caratteri, ha {actual}).",
|
|
645
676
|
"pub_title_too_few_words": "Il titolo del prodotto deve avere almeno {min} parole (ha {actual}).",
|
|
@@ -663,6 +694,7 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
663
694
|
"pub_need_attributes_field_item": "{idx}. {label} ({en_name}) - {input_type}, {mandatory}",
|
|
664
695
|
"pub_attribute_data_next": "Riesegui con --attribute-data '<json>' per fornire i valori degli attributi.",
|
|
665
696
|
"pub_image_migration_partial": "Alcune immagini non sono state migrate: {failed}. Sostituiscile o riesegui senza quegli URL.",
|
|
697
|
+
"pub_field_status_optional": "Opzionale",
|
|
666
698
|
},
|
|
667
699
|
"zh": {
|
|
668
700
|
"greeting": "您想使用库存分析 Skill 做什么?请直接回复选项字母,或输入你的需求:",
|
|
@@ -809,14 +841,21 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
809
841
|
"pub_unsupported_format": "不支持的文件格式:{source}。仅支持 PDF、Word、Excel、图片和 URL。",
|
|
810
842
|
"pub_invalid_url": "无法获取 URL:{source}。请检查 URL 后重试。",
|
|
811
843
|
"pub_url_fetch_failed": "无法从 {source} 获取内容:{error}。请检查 URL 后重试。",
|
|
812
|
-
"pub_llm_not_configured": "
|
|
813
|
-
"pub_llm_not_configured_next": "
|
|
844
|
+
"pub_llm_not_configured": "LLM 抽取不可用:未配置 LLM API key(OPENAI_API_KEY 或 DASHSCOPE_API_KEY)。此状态仅针对非表格源(PDF/Word/图片/URL);Excel/CSV 源按表头规则抽取,无需 LLM key。",
|
|
845
|
+
"pub_llm_not_configured_next": "若源为 Excel/CSV,直接带 --source 重跑即可(无需 key);若为 PDF/Word/图片/URL,请配置 LLM API key 后带 --source 重跑。不要自己读文件改走手动模式;第一步必须是 CTB(标题/品牌/类目)确认。",
|
|
814
846
|
"pub_create_requires_category": "创建商品必须提供 --category-id(叶子类目 ID)",
|
|
815
847
|
"pub_create_requires_name": "创建商品必须提供 --product-name(商品标题)",
|
|
816
848
|
"pub_create_preview_message": "请检查以下商品信息,确认无误后我将创建商品。",
|
|
817
849
|
"pub_create_preview_next": "如果信息正确,请加上 --publish-preview-token {token} 重新执行。",
|
|
818
850
|
"pub_create_success": "商品创建成功。",
|
|
819
851
|
"pub_create_success_next": "询问商家是否需要查看已创建的商品或进一步编辑。",
|
|
852
|
+
"pub_draft_created_with_issues": "草稿已创建,但部分必填字段或权限缺失。请在 Seller Center 补全后激活商品。",
|
|
853
|
+
"pub_draft_update_hint": "在 Seller Center 草稿列表中补全缺失字段后激活商品。",
|
|
854
|
+
"pub_create_failed_title": "商品创建失败",
|
|
855
|
+
"pub_create_failed_message": "商品未创建成功(含草稿):错误码 {code},原因:{reason}。",
|
|
856
|
+
"pub_create_failed_no_item_id": "创建接口返回成功但未返回 item_id,商品实际未创建。",
|
|
857
|
+
"pub_create_failed_check_and_rerun": "请先在 Seller Center 确认是否已存在重复草稿,修复报错问题后重新运行。",
|
|
858
|
+
"pub_create_failed_trace_hint": "如需平台排查,请提供 trace_id {trace_id} / request_id {request_id} 定位根因。",
|
|
820
859
|
"pub_title_too_short": "商品标题过短(最少 {min} 个字符,当前 {actual} 个)。",
|
|
821
860
|
"pub_title_too_long": "商品标题过长(最多 {max} 个字符,当前 {actual} 个)。",
|
|
822
861
|
"pub_title_too_few_words": "商品标题至少需要 {min} 个单词(当前 {actual} 个)。",
|
|
@@ -871,6 +910,7 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
871
910
|
"pub_summary_conditional_section": "【条件必填字段】",
|
|
872
911
|
"pub_field_status_filled": "已填写",
|
|
873
912
|
"pub_field_status_missing": "缺失",
|
|
913
|
+
"pub_field_status_optional": "选填",
|
|
874
914
|
"pub_mandatory_missing_block": "无法发布:缺少必填字段 [{fields}]。请补充后重新运行。",
|
|
875
915
|
"pub_conditional_missing_draft": "缺少条件必填字段 [{fields}]。商品将以未激活(Draft)状态保存,待补全后自动激活。是否确认?",
|
|
876
916
|
# DBM/DBS auto-switch
|
|
@@ -878,6 +918,7 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
878
918
|
"pub_dbm_dim_sum_exceeded": "三边之和 {dim_sum} 超过 DBM 限制 {limit}cm。",
|
|
879
919
|
"pub_dbm_volume_exceeded": "体积重 {volume} 超过 DBM 限制 {limit}。",
|
|
880
920
|
"pub_dbm_auto_switch_to_dbs": "包裹超出 DBM 限制,物流方式自动转为 DBS。",
|
|
921
|
+
"pub_dbs_no_permission_downgrade": "商家请求使用 DBS,但当前账号没有 DBS 权限,物流方式已设为 DBM。",
|
|
881
922
|
"pub_dbm_no_dbs_permission": "包裹超出 DBM 限制,但卖家无 DBS 权限,无法自动转为 DBS。",
|
|
882
923
|
"pub_dbm_fix_or_apply_dbs": "请减小包裹尺寸/重量以满足 DBM 限制,或在 Seller Center 申请 DBS 权限。",
|
|
883
924
|
# SKU already exists (concurrency conflict)
|
|
@@ -904,6 +945,7 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
904
945
|
"pub_ctb_confirm_next": "如果无误,请使用 --ctb-confirm-token {token} 继续。",
|
|
905
946
|
"pub_brand_not_found_title": "未找到品牌",
|
|
906
947
|
"pub_brand_not_found_message": "在 Miravia 中未找到与 \"{brand_name}\" 匹配的品牌。您是否想创建新品牌?",
|
|
948
|
+
"pub_brand_list_link": "点此查看已有品牌及审核状态:https://sellercenter.miravia.es/apps/setting/seller/brandlist",
|
|
907
949
|
"pub_brand_create_link": "点此创建新品牌:https://sellercenter.miravia.es/apps/product/publish/brand",
|
|
908
950
|
"pub_brand_create_next": "在 Seller Center 创建品牌后,请使用 --brand-name <brand_name> 或 --confirmed-brand-id <brand_id> 重新运行命令。",
|
|
909
951
|
"pub_brand_reconfirm_or_choose": "如果您不想创建品牌,请重新输入品牌名称,或从下方匹配列表中选择一个。",
|
|
@@ -912,7 +954,10 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
912
954
|
"pub_field_summary_description": "查看哪些字段已填写,哪些字段仍缺失。",
|
|
913
955
|
"pub_field_status_filled": "已填写",
|
|
914
956
|
"pub_field_status_missing": "未填写",
|
|
957
|
+
"pub_field_status_optional": "选填",
|
|
915
958
|
"pub_mandatory_fields_missing": "以下必填字段缺失,必须补充后才能继续:{fields}",
|
|
959
|
+
"pub_image_required_no_draft": "至少需要一张图片(主图或 SKU 图)。这是 Miravia 平台硬性要求,即使发草稿也无法绕过;缺失:{fields}。",
|
|
960
|
+
"pub_add_images_and_rerun": "请至少补充一张商品图片(URL 或本地文件)后重新运行命令。",
|
|
916
961
|
"pub_key_fields_missing_draft_question": "部分关键字段缺失。您可以选择以草稿模式发布,稍后再补充。如需发布为草稿,请加上 --draft 重新运行。",
|
|
917
962
|
"pub_manufacturer_eurp_mismatch": "制造商 / 欧盟负责人与已审核记录不匹配。请选择有效的记录,或以草稿模式发布。",
|
|
918
963
|
"pub_seller_sku_recommended": "强烈建议填写商家 SKU,作为该 SKU 的内部标识符,方便后续查找。如有请补充。",
|
|
@@ -928,6 +973,9 @@ TRANSLATIONS: Dict[str, Dict[str, str]] = {
|
|
|
928
973
|
"pub_need_category_attributes_description": "类目 {category_id} 要求以下属性,但 AI 抽取的数据中未包含。请通过 --attribute-data 补充,或使用 --draft 发布为未激活草稿。",
|
|
929
974
|
"pub_need_category_attributes_item": "{idx}. {label}({en_name})- {input_type}",
|
|
930
975
|
"pub_need_category_attributes_next": "补充缺失属性后使用 --attribute-data '<json>' 重新运行,或使用 --draft 发布为未激活草稿稍后补充。",
|
|
976
|
+
"pub_need_attribute_mapping_title": "部分列未能自动匹配",
|
|
977
|
+
"pub_need_attribute_mapping_description": "以下 Excel/CSV 列未能明确匹配到类目属性,因此未绑定。请确认每列应对应哪个属性,然后用下方展示的规范属性名通过 --attribute-data 重新运行(若该列不是商品属性则可删除)。",
|
|
978
|
+
"pub_need_attribute_mapping_next": "使用下方建议的属性名之一,通过 --attribute-data '{\"<属性名>\": \"<值>\"}' 重新运行;若不需要该列则删除。",
|
|
931
979
|
"pub_html_cleaned_notice": "商品描述已清理:不安全的 HTML 标签(script、iframe、style 属性等)已被移除。仅保留安全格式标签(p、br、b、ul、li 等)。",
|
|
932
980
|
"pub_create_pending_review": "商品提交成功,目前正在接受 Miravia 审核,暂不可售;审核最长可能需要 48 小时,特别是需要包装照片或文件时。审核通过后会自动激活,如被拒绝请在“已暂停(Suspended)”选项卡中查看。",
|
|
933
981
|
"pub_pdp_link": "商品详情页:{url}",
|
|
@@ -80,6 +80,12 @@ _BUSINESS_ERROR_HINTS = {
|
|
|
80
80
|
"BRAND_PERMISSION_DENIED": "Your seller account does not have permission for this brand.",
|
|
81
81
|
"BRAND_PENDING": "Brand is pending approval. Use --draft to publish as inactive draft until the brand permission is granted.",
|
|
82
82
|
"PARAM_ILLEGAL": "Request parameter validation failed. Please check the input fields.",
|
|
83
|
+
"4234": (
|
|
84
|
+
"Draft-mode creation reported missing required fields or authorizations. "
|
|
85
|
+
"If no product ID is returned, the draft was NOT created: complete the "
|
|
86
|
+
"missing fields/permissions (e.g. brand or category authorization) in "
|
|
87
|
+
"Seller Center and resubmit."
|
|
88
|
+
),
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
_RETRYABLE_ERROR_CODES = {
|
|
@@ -360,6 +360,16 @@ class ProductClient:
|
|
|
360
360
|
|
|
361
361
|
# GPSR compliance fields are injected at SKU level (see below).
|
|
362
362
|
|
|
363
|
+
# Delivery method -> Miravia create field ``Attributes.deliverySof``
|
|
364
|
+
# ("DBM" or "DBS"). Only written when the seller explicitly chose one;
|
|
365
|
+
# when absent, publish_module._check_dbm_limits applies the default
|
|
366
|
+
# (DBM generally, DBS if the seller has DBS permission).
|
|
367
|
+
_dm = str(data.get("delivery_method") or "").strip().upper()
|
|
368
|
+
if "DBS" in _dm:
|
|
369
|
+
attributes["deliverySof"] = "DBS"
|
|
370
|
+
elif "DBM" in _dm:
|
|
371
|
+
attributes["deliverySof"] = "DBM"
|
|
372
|
+
|
|
363
373
|
product_payload: Dict[str, Any] = {
|
|
364
374
|
"PrimaryCategory": category_id,
|
|
365
375
|
"Attributes": attributes,
|
|
@@ -379,10 +389,19 @@ class ProductClient:
|
|
|
379
389
|
sku["EU_Responsible"] = eurp_name
|
|
380
390
|
product_payload["Skus"] = {"Sku": skus}
|
|
381
391
|
|
|
382
|
-
# Sales-property variation definition
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
392
|
+
# Sales-property variation definition — NOT sent.
|
|
393
|
+
# Verified against ``/product/create``: including a product-level
|
|
394
|
+
# ``variation`` node ({"variation1": {...}}) makes Miravia reject the
|
|
395
|
+
# whole request with E005 Invalid Request Format, while the identical
|
|
396
|
+
# payload WITHOUT it creates successfully (draft item_id
|
|
397
|
+
# 1374866769737415). Miravia derives sale properties from the flat
|
|
398
|
+
# per-SKU fields (e.g. ``color: "Blue"``), so the separate node is both
|
|
399
|
+
# redundant and unsupported by this endpoint. ``_build_variation_definition``
|
|
400
|
+
# is retained in case a correctly-shaped multi-variation contract is
|
|
401
|
+
# confirmed later, but we must not inject it here.
|
|
402
|
+
# variation = self._build_variation_definition(data)
|
|
403
|
+
# if variation:
|
|
404
|
+
# product_payload["variation"] = variation
|
|
386
405
|
|
|
387
406
|
# Draft mode
|
|
388
407
|
if draft:
|
|
@@ -373,6 +373,25 @@ def _print_conversational_result(result: Dict[str, Any]) -> None:
|
|
|
373
373
|
print(f"• {action}")
|
|
374
374
|
return
|
|
375
375
|
|
|
376
|
+
# Unmapped Excel/CSV columns needing a confirmed attribute mapping
|
|
377
|
+
if status == "need_attribute_mapping":
|
|
378
|
+
print(result.get("title", ""))
|
|
379
|
+
print(result.get("message", ""))
|
|
380
|
+
print()
|
|
381
|
+
for item in result.get("unmapped_columns", []):
|
|
382
|
+
print(f" 列 \"{item.get('header')}\" = {item.get('value')} 可能对应:")
|
|
383
|
+
for c in item.get("candidates", []):
|
|
384
|
+
mand = "(必填)" if str(c.get("is_mandatory")) == "1" else ""
|
|
385
|
+
print(
|
|
386
|
+
f" - {c.get('label')} "
|
|
387
|
+
f"[name={c.get('name')}, en_name={c.get('en_name')}, "
|
|
388
|
+
f"type={c.get('attribute_type')}]{mand}"
|
|
389
|
+
)
|
|
390
|
+
print()
|
|
391
|
+
for action in result.get("next_actions", []):
|
|
392
|
+
print(f"• {action}")
|
|
393
|
+
return
|
|
394
|
+
|
|
376
395
|
if status == "need_images":
|
|
377
396
|
print(result.get("message", ""))
|
|
378
397
|
if result.get("failed_images"):
|
|
@@ -474,6 +493,7 @@ def _print_conversational_result(result: Dict[str, Any]) -> None:
|
|
|
474
493
|
print(result.get("title", ""))
|
|
475
494
|
print(result.get("message", ""))
|
|
476
495
|
print()
|
|
496
|
+
print(result.get("brand_list_link", ""))
|
|
477
497
|
print(result.get("create_brand_link", ""))
|
|
478
498
|
print()
|
|
479
499
|
for action in result.get("next_actions", []):
|
|
@@ -653,6 +673,43 @@ def _print_conversational_result(result: Dict[str, Any]) -> None:
|
|
|
653
673
|
print(f"• {action}")
|
|
654
674
|
return
|
|
655
675
|
|
|
676
|
+
# Draft created successfully but with missing fields (item_id present)
|
|
677
|
+
if status == "draft_created_with_issues":
|
|
678
|
+
print(result.get("message", ""))
|
|
679
|
+
item_id = result.get("item_id")
|
|
680
|
+
if item_id:
|
|
681
|
+
print(f"• Product ID: {item_id}")
|
|
682
|
+
pdp_url = result.get("pdp_url")
|
|
683
|
+
if pdp_url:
|
|
684
|
+
print(f"• {pdp_url}")
|
|
685
|
+
api_message = result.get("api_message")
|
|
686
|
+
if api_message:
|
|
687
|
+
print(f"• {api_message}")
|
|
688
|
+
print()
|
|
689
|
+
for action in result.get("next_actions", []):
|
|
690
|
+
print(f"• {action}")
|
|
691
|
+
return
|
|
692
|
+
|
|
693
|
+
# Creation failed (no item_id returned) — surface the root cause
|
|
694
|
+
if status == "create_failed":
|
|
695
|
+
print(result.get("title", "Product creation failed"))
|
|
696
|
+
print(result.get("message", ""))
|
|
697
|
+
hint = result.get("hint")
|
|
698
|
+
if hint:
|
|
699
|
+
print(f"提示:{hint}")
|
|
700
|
+
for key, label in (
|
|
701
|
+
("error_type", "错误类型"),
|
|
702
|
+
("request_id", "request_id"),
|
|
703
|
+
("trace_id", "trace_id"),
|
|
704
|
+
):
|
|
705
|
+
value = result.get(key)
|
|
706
|
+
if value:
|
|
707
|
+
print(f"• {label}: {value}")
|
|
708
|
+
print()
|
|
709
|
+
for action in result.get("next_actions", []):
|
|
710
|
+
print(f"• {action}")
|
|
711
|
+
return
|
|
712
|
+
|
|
656
713
|
# Brand permission failed (BRAND_PENDING / BRAND_PERMISSION_DENIED)
|
|
657
714
|
if status == "brand_permission_failed":
|
|
658
715
|
print(result.get("message", ""))
|