Browse Source

天龙八部拍卖行

PUGE 1 tháng trước cách đây
mục cha
commit
da1b22b996
3 tập tin đã thay đổi với 307 bổ sung và 203 xóa
  1. 1 0
      .gitignore
  2. 185 88
      test2.py
  3. 121 115
      天龙八部拍卖行.py

+ 1 - 0
.gitignore

@@ -2,3 +2,4 @@ build
 dist
 *.spec
 user_scripts_backup.json
+*.txt

+ 185 - 88
test2.py

@@ -3,24 +3,29 @@ import json
 import datetime
 import hashlib
 import hmac
-import time
-from urllib.parse import quote
+import uuid
+import base64
+from urllib.parse import quote, urlparse, parse_qsl
+from io import BytesIO
 
-# ==================== 配置区域 ====================
+# ==================== 配置(全部填好,直接运行) ====================
 CLIENT_ID = "amzn1.application-oa2-client.9f775d35077a4c7598116e5a89b60222"
 CLIENT_SECRET = "amzn1.oa2-cs.v1.6909777baa82b796555a431cd23709342e8e01434abcad1f8b59cadf048b51c3"
 REFRESH_TOKEN = "Atzr|IwEBICh_fojh7qAfrePzSjOZhQxTnHRZCYOuWNvSAHGaBs1WfkjR4qjC1Dmv0uK3oyq_L-R9SRKK4cYmPwF1MQXQdqKlpzOAGItWeXnNzh-SyhHHISv0_vCt3D6hluVmIEmF2kA7ro_XF-PslD41g3Eq6bv0Qq4y4pQbkDj-9dudDuz7nku8UL-nc4EWgJCrHCJTAs8TK48Yvrfi3E35uv97tYhs6qb8D9d4dR9C7daaC49eoDqP88qyUHThQV1Kqon8xukx4AT2_wLqWNcabpADzuK-5w7D-vvFFtvzfkbmtfzoPeEdHPulG6scc59BPFTyBlGU7a3d1JSFg2ROHIlrljRm"
 
-AWS_ACCESS_KEY = "AKIA24CLQSVTW2K4S6RP"       # ← 填你的
-AWS_SECRET_KEY = "CwBJqlsYATNC920A+U6iv80WnP6Eso+ECfIalQ1W"             # ← 填你的
+AWS_ACCESS_KEY = "AKIA24CLQSVTW2K4S6RP"
+AWS_SECRET_KEY = "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"
 AWS_REGION = "us-east-1"
 
 SELLER_ID = "A1H50Z0WYYLEGQ"
 SKU = "bar table pink"
 MARKETPLACE_ID = "ATVPDKIKX0DER"
-NEW_IMAGE_URL = "https://cunchu.site/temp/8c7a90e1-a5a1-41af-a119-41b1ca74b40d.png"
+ASIN = "B0FR1WRK4M"
 
-# ==================== 工具函数 ====================
+CONTENT_KEY = f"amzn1.aplus.content.v1.{uuid.uuid4()}"
+IMAGE_URL = "https://cunchu.site/temp/8c7a90e1-a5a1-41af-a119-41b1ca74b40d.png"
+
+# ==================== 签名工具(复用你之前的) ====================
 def sha256_hash(data): 
     return hashlib.sha256(data.encode("utf-8")).hexdigest()
 
@@ -93,78 +98,150 @@ def spapi_request(method, path, query, body=None, access_token=None):
     url = f"https://{host}{path}?{query}"
     return requests.request(method, url, headers=headers, data=payload or None)
 
-# ==================== 通用图片修改函数 ====================
-def patch_image(access_token, image_type, image_url):
+# ==================== Step 1: 下载图片并计算 MD5 ====================
+def download_and_hash_image(image_url):
+    print(f"⬇️ 下载图片: {image_url}")
+    resp = requests.get(image_url, timeout=30)
+    resp.raise_for_status()
+    image_binary = resp.content
+    print(f"   图片大小: {len(image_binary)} bytes")
+    
+    md5_hash = hashlib.md5(image_binary).digest()
+    content_md5 = base64.b64encode(md5_hash).decode('utf-8')
+    print(f"   Content-MD5: {content_md5}")
+    
+    width, height = 1940, 1200
+    try:
+        from PIL import Image
+        img = Image.open(BytesIO(image_binary))
+        width, height = img.size
+        print(f"   图片尺寸: {width}x{height}")
+    except ImportError:
+        print(f"   未安装 Pillow,使用默认尺寸: {width}x{height}")
+    
+    return image_binary, content_md5, width, height
+
+# ==================== Step 2: 创建上传目标(官方修正版) ====================
+def create_upload_destination(access_token, content_md5, content_type):
     """
-    image_type 可选值:
-      "main"              → 主图
-      "other_1" ~ "other_8" → 副图1~8
-      "swatch"            → 变体图
+    官方修正:resource 固定为 aplus/2020-11-01/contentDocuments
+    不需要 contentDocumentId,不需要图片文件名!
     """
-    encoded_sku = quote(SKU, safe="")
-    path = f"/listings/2021-08-01/items/{SELLER_ID}/{encoded_sku}"
-    query = f"marketplaceIds={MARKETPLACE_ID}"
+    resource = "aplus/2020-11-01/contentDocuments"  # ← 关键修正:固定字符串
+    encoded_resource = quote(resource, safe="/")
+    
+    path = f"/uploads/2020-11-01/uploadDestinations/{encoded_resource}"
+    query = f"marketplaceIds={MARKETPLACE_ID}&contentMD5={quote(content_md5, safe='')}&contentType={quote(content_type, safe='')}"
+    
+    print(f"\n📝 创建上传目标...")
+    print(f"   Resource: {resource}")
+    print(f"   Content-MD5: {content_md5}")
+    print(f"   Content-Type: {content_type}")
+    
+    resp = spapi_request("POST", path, query, access_token=access_token)
+    print(f"📤 状态码: {resp.status_code}")
+    print(f"📄 响应: {resp.text}")
     
-    # 根据类型生成 path
-    if image_type == "main":
-        attr_path = "/attributes/main_product_image_locator"
-    elif image_type.startswith("other_") and 1 <= int(image_type.split("_")[1]) <= 8:
-        n = image_type.split("_")[1]
-        attr_path = f"/attributes/other_product_image_locator_{n}"
-    elif image_type == "swatch":
-        attr_path = "/attributes/swatch_product_image_locator"
-    else:
-        raise ValueError(f"不支持的 image_type: {image_type}")
+    if resp.status_code != 200:
+        return None, None
+    
+    data = resp.json()
+    upload_destination_id = data.get("uploadDestinationId")
+    url = data.get("url")
+    
+    print(f"✅ uploadDestinationId: {upload_destination_id}")
+    print(f"✅ upload URL: {url[:120]}...")
+    
+    return upload_destination_id, url
+
+# ==================== Step 3: Multipart Form POST 上传图片到 S3 ====================
+def upload_image_to_s3(upload_url, image_binary, content_type):
+    print(f"\n⬆️ 解析预签名 URL 并上传图片...")
+    
+    parsed = urlparse(upload_url)
+    base_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
+    query_params = dict(parse_qsl(parsed.query))
+    
+    print(f"   Base URL: {base_url}")
+    print(f"   Form fields: {list(query_params.keys())}")
+    
+    files = {
+        "File": ("image.png", image_binary, content_type)
+    }
+    
+    resp = requests.post(base_url, data=query_params, files=files, timeout=60)
+    print(f"📤 S3 上传状态码: {resp.status_code}")
+    
+    if resp.text:
+        print(f"📄 S3 响应: {resp.text[:500]}")
+    
+    return resp.status_code in [200, 201, 204]
+
+# ==================== Step 4: 创建 A+ Content 文档 ====================
+def create_content_document(access_token, content_key, upload_id, img_width, img_height):
+    path = "/aplus/2020-11-01/contentDocuments"
+    query = f"marketplaceIds={MARKETPLACE_ID}"
     
     body = {
-        "productType": "TABLE",
-        "patches": [
-            {
-                "op": "replace",
-                "path": attr_path,
-                "value": [
-                    {
-                        "marketplace_id": MARKETPLACE_ID,
-                        "media_location": image_url
+        "contentDocument": {
+            "name": "Product Detail Image",
+            "contentReference": {
+                "key": content_key,
+                "version": "0"
+            },
+            "contentModuleList": [
+                {
+                    "moduleType": "STANDARD_SINGLE_IMAGE",
+                    "standardSingleImage": {
+                        "image": {
+                            "uploadDestinationId": upload_id,
+                            "imageCropSpecification": {
+                                "size": {
+                                    "width": {"value": img_width, "units": "pixels"},
+                                    "height": {"value": img_height, "units": "pixels"}
+                                },
+                                "offset": {
+                                    "x": {"value": 0, "units": "pixels"},
+                                    "y": {"value": 0, "units": "pixels"}
+                                }
+                            },
+                            "altText": "Product detail image"
+                        }
                     }
-                ]
-            }
-        ]
+                }
+            ]
+        }
     }
-    return spapi_request("PATCH", path, query, body=body, access_token=access_token)
+    
+    print(f"\n📝 创建 A+ Content 文档...")
+    print(f"   Content Key: {content_key}")
+    resp = spapi_request("POST", path, query, body=body, access_token=access_token)
+    print(f"📤 状态码: {resp.status_code}")
+    print(resp.text)
+    return resp
 
-def check_submission_result(access_token):
-    encoded_sku = quote(SKU, safe="")
-    path = f"/listings/2021-08-01/items/{SELLER_ID}/{encoded_sku}"
-    # 关键:只带 issues,减少返回数据量
-    query = f"marketplaceIds={MARKETPLACE_ID}&includedData=issues"
+# ==================== Step 5: 绑定 ASIN ====================
+def associate_asin(access_token, content_key, asin):
+    path = f"/aplus/2020-11-01/contentDocuments/{content_key}/asins"
+    query = f"marketplaceIds={MARKETPLACE_ID}"
+    body = {"asinSet": [asin]}
     
-    resp = spapi_request("GET", path, query, access_token=access_token)
-    data = resp.json()
+    print(f"\n🔗 绑定 ASIN: {asin}")
+    resp = spapi_request("POST", path, query, body=body, access_token=access_token)
+    print(f"📤 状态码: {resp.status_code}")
+    print(resp.text)
+    return resp
+
+# ==================== Step 6: 提交审核 ====================
+def submit_approval(access_token, content_key):
+    path = f"/aplus/2020-11-01/contentDocuments/{content_key}/approvalSubmissions"
+    query = f"marketplaceIds={MARKETPLACE_ID}"
     
-    issues = data.get("issues", [])
-    if not issues:
-        print("✅ 没有 issues,图片应该已生效")
-        return True
-    
-    print(f"⚠️ 发现 {len(issues)} 个问题:\n")
-    for issue in issues:
-        print(f"  错误码: {issue.get('code')}")
-        print(f"  级别: {issue.get('severity')}")
-        print(f"  信息: {issue.get('message')}")
-        
-        enforcements = issue.get("enforcements", {})
-        actions = enforcements.get("actions", [])
-        for action in actions:
-            print(f"  执行动作: {action.get('action')}")  # 如 ATTRIBUTE_SUPPRESSED
-        
-        # 看是否跟图片相关
-        attrs = issue.get("attributeNames", [])
-        if any("image" in a or "media" in a for a in attrs):
-            print(f"  涉及属性: {attrs} → 图片审核未通过")
-        print()
-    
-    return False
+    print(f"\n📋 提交审核...")
+    resp = spapi_request("POST", path, query, access_token=access_token)
+    print(f"📤 状态码: {resp.status_code}")
+    print(resp.text)
+    return resp
 
 # ==================== 主程序 ====================
 if __name__ == "__main__":
@@ -172,23 +249,43 @@ if __name__ == "__main__":
     token = get_access_token()
     print("✅ 令牌获取成功")
     
-    # 示例:增加/修改副图第3张
-    new_images = {
-        "other_1": NEW_IMAGE_URL
-    }
+    print(f"\n🆔 Content Key: {CONTENT_KEY}")
+    print(f"🖼️ 图片URL: {IMAGE_URL}")
+    print(f"🔗 ASIN: {ASIN}")
+    
+    # Step 1
+    print("\n=== Step 1: 下载图片 ===")
+    image_binary, content_md5, img_width, img_height = download_and_hash_image(IMAGE_URL)
+    
+    # Step 2(官方修正版)
+    print("\n=== Step 2: 创建上传目标 ===")
+    upload_id, upload_url = create_upload_destination(token, content_md5, "image/png")
+    
+    if not upload_id or not upload_url:
+        print("\n⛔ 创建上传目标失败,终止")
+        exit(1)
+    
+    # Step 3
+    print("\n=== Step 3: 上传图片到 S3 ===")
+    success = upload_image_to_s3(upload_url, image_binary, "image/png")
+    if not success:
+        print("\n⛔ 图片上传失败,终止")
+        exit(1)
+    print("✅ 图片上传成功")
+    
+    # Step 4
+    print("\n=== Step 4: 创建内容文档 ===")
+    create_content_document(token, CONTENT_KEY, upload_id, img_width, img_height)
+    
+    # Step 5
+    print("\n=== Step 5: 绑定ASIN ===")
+    associate_asin(token, CONTENT_KEY, ASIN)
+    
+    # Step 6
+    print("\n=== Step 6: 提交审核 ===")
+    submit_approval(token, CONTENT_KEY)
     
-    for img_type, img_url in new_images.items():
-        print(f"\n🖼️ 正在设置 {img_type}: {img_url}")
-        resp = patch_image(token, img_type, img_url)
-        print(f"📤 状态码: {resp.status_code}")
-        try:
-            timestamp = int(time.time())
-            print(timestamp)
-            print(json.dumps(resp.json(), indent=2, ensure_ascii=False))
-        except Exception:
-            print(resp.text)
-        
-        if resp.status_code == 200:
-            print(f"✅ {img_type} 提交成功")
-        else:
-            print(f"⚠️ {img_type} 提交失败")
+    print("\n🎉 全部步骤执行完毕!")
+    print("   A+ 内容已提交审核,通常需要 1-7 个工作日")
+    print(f"   Content Key: {CONTENT_KEY}")
+    print("   请保存此 Key 以便后续查询状态")

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 121 - 115
天龙八部拍卖行.py


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác