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",
|
id: "dongchedi",
|
||||||
label: "懂车帝",
|
label: "懂车帝",
|
||||||
loginUrl: "https://mp.dcdapp.com/login",
|
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,
|
detect: detectDongchedi,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createHash, createHmac } from "node:crypto";
|
|||||||
|
|
||||||
import type { JsonValue } from "@geo/shared-types";
|
import type { JsonValue } from "@geo/shared-types";
|
||||||
|
|
||||||
|
import { STANDARD_ACCEPT_LANGUAGES, STANDARD_USER_AGENT } from "../user-agent";
|
||||||
import {
|
import {
|
||||||
extractImageSources,
|
extractImageSources,
|
||||||
normalizeArticleHtml,
|
normalizeArticleHtml,
|
||||||
@@ -91,6 +92,8 @@ const DONGCHEDI_ORIGIN = "https://mp.dcdapp.com";
|
|||||||
const DONGCHEDI_IMAGE_X_ORIGIN = "https://imagex.bytedanceapi.com";
|
const DONGCHEDI_IMAGE_X_ORIGIN = "https://imagex.bytedanceapi.com";
|
||||||
const DONGCHEDI_SERVICE_ID = "f042mdwyw7";
|
const DONGCHEDI_SERVICE_ID = "f042mdwyw7";
|
||||||
const DONGCHEDI_MANAGE_URL = `${DONGCHEDI_ORIGIN}/profile_v2/content/manage/article`;
|
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 {
|
function stringId(value: unknown): string {
|
||||||
if (typeof value === "number") {
|
if (typeof value === "number") {
|
||||||
@@ -125,12 +128,35 @@ async function dongchediHeaders(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mainFetchJson<T>(input: string, init?: RequestInit): Promise<T> {
|
async function imagexFetchText(
|
||||||
const response = await fetch(input, init);
|
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();
|
const text = await response.text();
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(text || `request_failed_${response.status}`);
|
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) {
|
if (!text) {
|
||||||
return {} as T;
|
return {} as T;
|
||||||
}
|
}
|
||||||
@@ -191,7 +217,7 @@ function signAWS4(input: {
|
|||||||
const amzDate = formatAmzDate(now);
|
const amzDate = formatAmzDate(now);
|
||||||
const dateStamp = formatDateStamp(now);
|
const dateStamp = formatDateStamp(now);
|
||||||
const query = [...url.searchParams.entries()]
|
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)}`)
|
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||||
.join("&");
|
.join("&");
|
||||||
const signedHeaders: Record<string, string> = {
|
const signedHeaders: Record<string, string> = {
|
||||||
@@ -325,7 +351,8 @@ async function uploadImage(
|
|||||||
date: new Date(token.CurrentTime),
|
date: new Date(token.CurrentTime),
|
||||||
});
|
});
|
||||||
|
|
||||||
const applyResponse = await mainFetchJson<DongchediImageXApplyResponse>(
|
const applyResponse = await imagexFetchJson<DongchediImageXApplyResponse>(
|
||||||
|
context,
|
||||||
applyUrl.toString(),
|
applyUrl.toString(),
|
||||||
{
|
{
|
||||||
headers: signedApplyHeaders,
|
headers: signedApplyHeaders,
|
||||||
@@ -343,14 +370,17 @@ async function uploadImage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await image.blob.arrayBuffer();
|
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",
|
method: "PUT",
|
||||||
headers: {
|
headers: putHeaders,
|
||||||
"content-disposition": 'attachment; filename="undefined"',
|
|
||||||
"content-type": "application/octet-stream",
|
|
||||||
"content-crc32": crc32(new Uint8Array(buffer)),
|
|
||||||
authorization: storeInfo.Auth,
|
|
||||||
},
|
|
||||||
body: image.blob,
|
body: image.blob,
|
||||||
});
|
});
|
||||||
if (!putResponse.ok) {
|
if (!putResponse.ok) {
|
||||||
@@ -371,7 +401,8 @@ async function uploadImage(
|
|||||||
securityToken: token.SessionToken,
|
securityToken: token.SessionToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
await mainFetchJson<Record<string, unknown>>(
|
await imagexFetchJson<Record<string, unknown>>(
|
||||||
|
context,
|
||||||
commitUrl.toString(),
|
commitUrl.toString(),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
Reference in New Issue
Block a user