fix(publish/dongchedi): route imagex traffic through session.fetch
Use the account session for imagex apply/upload/commit so the requests carry the same cookies/UA the bind console saw, instead of falling out of the session via a bare Node fetch — bytedance imagex was rejecting these as unauthenticated under the bound session. Also align the binding console URL with the v2 publish article path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2910,7 +2910,7 @@ const publishBindingDefinitions: Record<string, PublishPlatformBindingDefinition
|
||||
id: "dongchedi",
|
||||
label: "懂车帝",
|
||||
loginUrl: "https://mp.dcdapp.com/login",
|
||||
consoleUrl: "https://mp.dcdapp.com/content/article/create",
|
||||
consoleUrl: "https://mp.dcdapp.com/profile_v2/publish/article",
|
||||
detect: detectDongchedi,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createHash, createHmac } from "node:crypto";
|
||||
|
||||
import type { JsonValue } from "@geo/shared-types";
|
||||
|
||||
import { STANDARD_ACCEPT_LANGUAGES, STANDARD_USER_AGENT } from "../user-agent";
|
||||
import {
|
||||
extractImageSources,
|
||||
normalizeArticleHtml,
|
||||
@@ -91,6 +92,8 @@ const DONGCHEDI_ORIGIN = "https://mp.dcdapp.com";
|
||||
const DONGCHEDI_IMAGE_X_ORIGIN = "https://imagex.bytedanceapi.com";
|
||||
const DONGCHEDI_SERVICE_ID = "f042mdwyw7";
|
||||
const DONGCHEDI_MANAGE_URL = `${DONGCHEDI_ORIGIN}/profile_v2/content/manage/article`;
|
||||
const IMAGEX_USER_AGENT = STANDARD_USER_AGENT;
|
||||
const IMAGEX_ACCEPT_LANGUAGE = STANDARD_ACCEPT_LANGUAGES;
|
||||
|
||||
function stringId(value: unknown): string {
|
||||
if (typeof value === "number") {
|
||||
@@ -125,12 +128,35 @@ async function dongchediHeaders(
|
||||
};
|
||||
}
|
||||
|
||||
async function mainFetchJson<T>(input: string, init?: RequestInit): Promise<T> {
|
||||
const response = await fetch(input, init);
|
||||
async function imagexFetchText(
|
||||
context: PublishAdapterContext,
|
||||
input: string,
|
||||
init?: RequestInit,
|
||||
): Promise<string> {
|
||||
const headers = new Headers(init?.headers);
|
||||
if (!headers.has("user-agent")) {
|
||||
headers.set("user-agent", IMAGEX_USER_AGENT);
|
||||
}
|
||||
if (!headers.has("accept-language")) {
|
||||
headers.set("accept-language", IMAGEX_ACCEPT_LANGUAGE);
|
||||
}
|
||||
if (!headers.has("accept")) {
|
||||
headers.set("accept", "application/json, text/plain, */*");
|
||||
}
|
||||
const response = await context.session.fetch(input, { ...init, headers });
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
throw new Error(text || `request_failed_${response.status}`);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
async function imagexFetchJson<T>(
|
||||
context: PublishAdapterContext,
|
||||
input: string,
|
||||
init?: RequestInit,
|
||||
): Promise<T> {
|
||||
const text = await imagexFetchText(context, input, init);
|
||||
if (!text) {
|
||||
return {} as T;
|
||||
}
|
||||
@@ -191,7 +217,7 @@ function signAWS4(input: {
|
||||
const amzDate = formatAmzDate(now);
|
||||
const dateStamp = formatDateStamp(now);
|
||||
const query = [...url.searchParams.entries()]
|
||||
.sort(([left], [right]) => left.localeCompare(right))
|
||||
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||
.join("&");
|
||||
const signedHeaders: Record<string, string> = {
|
||||
@@ -325,7 +351,8 @@ async function uploadImage(
|
||||
date: new Date(token.CurrentTime),
|
||||
});
|
||||
|
||||
const applyResponse = await mainFetchJson<DongchediImageXApplyResponse>(
|
||||
const applyResponse = await imagexFetchJson<DongchediImageXApplyResponse>(
|
||||
context,
|
||||
applyUrl.toString(),
|
||||
{
|
||||
headers: signedApplyHeaders,
|
||||
@@ -343,14 +370,17 @@ async function uploadImage(
|
||||
}
|
||||
|
||||
const buffer = await image.blob.arrayBuffer();
|
||||
const putResponse = await fetch(`https://${uploadHost}/${storeUri}`, {
|
||||
const putHeaders = new Headers({
|
||||
"content-disposition": 'attachment; filename="undefined"',
|
||||
"content-type": "application/octet-stream",
|
||||
"content-crc32": crc32(new Uint8Array(buffer)),
|
||||
authorization: storeInfo.Auth,
|
||||
"user-agent": IMAGEX_USER_AGENT,
|
||||
"accept-language": IMAGEX_ACCEPT_LANGUAGE,
|
||||
});
|
||||
const putResponse = await context.session.fetch(`https://${uploadHost}/${storeUri}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"content-disposition": 'attachment; filename="undefined"',
|
||||
"content-type": "application/octet-stream",
|
||||
"content-crc32": crc32(new Uint8Array(buffer)),
|
||||
authorization: storeInfo.Auth,
|
||||
},
|
||||
headers: putHeaders,
|
||||
body: image.blob,
|
||||
});
|
||||
if (!putResponse.ok) {
|
||||
@@ -371,7 +401,8 @@ async function uploadImage(
|
||||
securityToken: token.SessionToken,
|
||||
});
|
||||
|
||||
await mainFetchJson<Record<string, unknown>>(
|
||||
await imagexFetchJson<Record<string, unknown>>(
|
||||
context,
|
||||
commitUrl.toString(),
|
||||
{
|
||||
method: "POST",
|
||||
|
||||
Reference in New Issue
Block a user