|
|
|
@@ -2,19 +2,18 @@ import { Buffer } from 'node:buffer'
|
|
|
|
|
import { createCipheriv, createHash } from 'node:crypto'
|
|
|
|
|
|
|
|
|
|
import type { JsonValue } from '@geo/shared-types'
|
|
|
|
|
import { nativeImage } from 'electron'
|
|
|
|
|
|
|
|
|
|
import { PUBLISH_IMAGE_FETCH_FAILED_MESSAGE } from '../../shared/publisher-errors'
|
|
|
|
|
import { resolveDesktopApiURL } from '../transport/api-client'
|
|
|
|
|
import type { PublishAdapter, PublishAdapterContext } from './base'
|
|
|
|
|
import {
|
|
|
|
|
adapterFetch,
|
|
|
|
|
extractImageSources,
|
|
|
|
|
normalizeArticleHtml,
|
|
|
|
|
raceWithAbort,
|
|
|
|
|
sessionCookieHeader,
|
|
|
|
|
sessionCookieValue,
|
|
|
|
|
sessionFetchJson,
|
|
|
|
|
} from './common'
|
|
|
|
|
import { fetchImageAssetBlob } from './media-image'
|
|
|
|
|
|
|
|
|
|
type SmzdmPublishType = 'publish'
|
|
|
|
|
|
|
|
|
@@ -64,6 +63,14 @@ type SmzdmCropResponse = {
|
|
|
|
|
}>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type SmzdmEditorTokenResponse = {
|
|
|
|
|
error_code?: number | string
|
|
|
|
|
error_msg?: string
|
|
|
|
|
data?: {
|
|
|
|
|
token?: string
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type SmzdmSubmitResponse = {
|
|
|
|
|
error_code?: number | string
|
|
|
|
|
error_msg?: string
|
|
|
|
@@ -75,12 +82,6 @@ type SmzdmSubmitResponse = {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type SmzdmImageBlob = {
|
|
|
|
|
blob: Blob
|
|
|
|
|
width: number
|
|
|
|
|
height: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type SmzdmUploadedImage = {
|
|
|
|
|
id: string
|
|
|
|
|
origin: string
|
|
|
|
@@ -151,6 +152,29 @@ function isSmzdmChallengeMessage(message: string): boolean {
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isSmzdmCsrfMissingMessage(message: string): boolean {
|
|
|
|
|
return /csrf/i.test(message) && /(缺失|missing|required|invalid|校验|验证|403)/i.test(message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function smzdmCsrfHeaders(token: string): Record<string, string> {
|
|
|
|
|
return {
|
|
|
|
|
_csrf_token: token,
|
|
|
|
|
'x-csrf-token': token,
|
|
|
|
|
'x-xsrf-token': token,
|
|
|
|
|
'csrf-token': token,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function appendSmzdmCsrfFields(target: FormData | URLSearchParams, token: string): void {
|
|
|
|
|
if (!token) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
target.append('_csrf', token)
|
|
|
|
|
target.append('csrf_token', token)
|
|
|
|
|
target.append('csrfToken', token)
|
|
|
|
|
target.append('csrf', token)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function waitForHumanPace(signal?: AbortSignal): Promise<void> {
|
|
|
|
|
if (signal?.aborted) {
|
|
|
|
|
throw new Error('adapter_aborted')
|
|
|
|
@@ -178,16 +202,152 @@ async function smzdmCookieHeader(context: PublishAdapterContext): Promise<string
|
|
|
|
|
return await sessionCookieHeader(context.session, 'smzdm.com').catch(() => '')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function smzdmCookieCsrfToken(context: PublishAdapterContext): Promise<string> {
|
|
|
|
|
const names = [
|
|
|
|
|
'csrf_token',
|
|
|
|
|
'csrfToken',
|
|
|
|
|
'_csrf',
|
|
|
|
|
'csrf',
|
|
|
|
|
'XSRF-TOKEN',
|
|
|
|
|
'xsrf-token',
|
|
|
|
|
'csrftoken',
|
|
|
|
|
'CSRF-TOKEN',
|
|
|
|
|
'SMZDM_CSRF_TOKEN',
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for (const name of names) {
|
|
|
|
|
const value = await sessionCookieValue(context.session, 'smzdm.com', name).catch(() => '')
|
|
|
|
|
const decoded = decodeCookieText(value).trim()
|
|
|
|
|
if (decoded) {
|
|
|
|
|
return decoded
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cookies = await context.session.cookies.get({ domain: 'smzdm.com' }).catch(() => [])
|
|
|
|
|
const matched = cookies.find((cookie) => /(csrf|xsrf)/i.test(cookie.name) && cookie.value.trim())
|
|
|
|
|
return matched ? decodeCookieText(matched.value).trim() : ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function smzdmPageCsrfToken(context: PublishAdapterContext): Promise<string> {
|
|
|
|
|
const page = context.playwright?.page
|
|
|
|
|
if (!page) {
|
|
|
|
|
return ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await page
|
|
|
|
|
.evaluate(() => {
|
|
|
|
|
const candidates: string[] = []
|
|
|
|
|
const push = (value: unknown) => {
|
|
|
|
|
if (typeof value !== 'string') {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const trimmed = value.trim()
|
|
|
|
|
if (trimmed && !candidates.includes(trimmed)) {
|
|
|
|
|
candidates.push(trimmed)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const selectors = [
|
|
|
|
|
'meta[name="csrf-token"]',
|
|
|
|
|
'meta[name="csrf_token"]',
|
|
|
|
|
'meta[name="_csrf"]',
|
|
|
|
|
'meta[name="csrf"]',
|
|
|
|
|
'meta[name="x-csrf-token"]',
|
|
|
|
|
'meta[name="xsrf-token"]',
|
|
|
|
|
]
|
|
|
|
|
for (const selector of selectors) {
|
|
|
|
|
push(document.querySelector<HTMLMetaElement>(selector)?.content)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const inputNames = ['_csrf', 'csrf', 'csrf_token', 'csrfToken', 'x-csrf-token']
|
|
|
|
|
for (const name of inputNames) {
|
|
|
|
|
push(document.querySelector<HTMLInputElement>(`input[name="${name}"]`)?.value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const globals = window as unknown as Window &
|
|
|
|
|
Record<string, unknown> & {
|
|
|
|
|
csrfToken?: string
|
|
|
|
|
csrf_token?: string
|
|
|
|
|
_csrf?: string
|
|
|
|
|
CSRF_TOKEN?: string
|
|
|
|
|
SMZDM_CSRF_TOKEN?: string
|
|
|
|
|
PAGE_CONFIG?: Record<string, unknown>
|
|
|
|
|
__INITIAL_STATE__?: Record<string, unknown>
|
|
|
|
|
}
|
|
|
|
|
push(globals.csrfToken)
|
|
|
|
|
push(globals.csrf_token)
|
|
|
|
|
push(globals._csrf)
|
|
|
|
|
push(globals.CSRF_TOKEN)
|
|
|
|
|
push(globals.SMZDM_CSRF_TOKEN)
|
|
|
|
|
push(globals.PAGE_CONFIG?.csrfToken)
|
|
|
|
|
push(globals.PAGE_CONFIG?.csrf_token)
|
|
|
|
|
push(globals.__INITIAL_STATE__?.csrfToken)
|
|
|
|
|
push(globals.__INITIAL_STATE__?.csrf_token)
|
|
|
|
|
|
|
|
|
|
const readStorage = (storage: Storage) => {
|
|
|
|
|
for (let index = 0; index < storage.length; index += 1) {
|
|
|
|
|
const key = storage.key(index) || ''
|
|
|
|
|
if (/(csrf|xsrf)/i.test(key)) {
|
|
|
|
|
push(storage.getItem(key))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
readStorage(window.localStorage)
|
|
|
|
|
} catch {
|
|
|
|
|
// Ignore inaccessible storage in restricted platform pages.
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
readStorage(window.sessionStorage)
|
|
|
|
|
} catch {
|
|
|
|
|
// Ignore inaccessible storage in restricted platform pages.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const item of document.cookie.split(';')) {
|
|
|
|
|
const [rawName, ...rawValue] = item.split('=')
|
|
|
|
|
if (/(csrf|xsrf)/i.test(rawName || '')) {
|
|
|
|
|
push(decodeURIComponent(rawValue.join('=')))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return candidates[0] || ''
|
|
|
|
|
})
|
|
|
|
|
.catch(() => '')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function smzdmCsrfToken(context: PublishAdapterContext): Promise<string> {
|
|
|
|
|
return (await smzdmCookieCsrfToken(context)) || (await smzdmPageCsrfToken(context))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function fetchSmzdmEditorToken(context: PublishAdapterContext): Promise<string> {
|
|
|
|
|
const response = await sessionFetchJson<SmzdmEditorTokenResponse>(
|
|
|
|
|
context.session,
|
|
|
|
|
`${SMZDM_ORIGIN}/api/editor/get_token`,
|
|
|
|
|
{
|
|
|
|
|
method: 'GET',
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
headers: await smzdmHeaders(context, {}, ''),
|
|
|
|
|
},
|
|
|
|
|
{ signal: context.signal },
|
|
|
|
|
).catch(() => null)
|
|
|
|
|
|
|
|
|
|
return stringId(response?.data?.token) || (await smzdmCsrfToken(context))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function smzdmHeaders(
|
|
|
|
|
context: PublishAdapterContext,
|
|
|
|
|
extra: Record<string, string> = {},
|
|
|
|
|
csrfToken?: string,
|
|
|
|
|
): Promise<Record<string, string>> {
|
|
|
|
|
const cookie = await smzdmCookieHeader(context)
|
|
|
|
|
const token = csrfToken ?? (await smzdmCsrfToken(context))
|
|
|
|
|
return {
|
|
|
|
|
accept: 'application/json, text/plain, */*',
|
|
|
|
|
origin: SMZDM_ORIGIN,
|
|
|
|
|
referer: SMZDM_TOU_GAO_URL,
|
|
|
|
|
'x-requested-with': 'XMLHttpRequest',
|
|
|
|
|
...(cookie ? { cookie } : {}),
|
|
|
|
|
...(token ? smzdmCsrfHeaders(token) : {}),
|
|
|
|
|
...extra,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -289,81 +449,6 @@ async function getArticleId(context: PublishAdapterContext): Promise<string> {
|
|
|
|
|
return articleId
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildAssetURLCandidates(sourceUrl: string): string[] {
|
|
|
|
|
const trimmed = sourceUrl.trim()
|
|
|
|
|
if (!trimmed) {
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (/^(data|blob):/i.test(trimmed)) {
|
|
|
|
|
return [trimmed]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const candidates: string[] = []
|
|
|
|
|
const pushCandidate = (value: string) => {
|
|
|
|
|
if (!candidates.includes(value)) {
|
|
|
|
|
candidates.push(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const parsed = new URL(trimmed, resolveDesktopApiURL('/'))
|
|
|
|
|
|
|
|
|
|
if (parsed.pathname.startsWith('/api/')) {
|
|
|
|
|
const apiAssetUrl = new URL(resolveDesktopApiURL(`${parsed.pathname}${parsed.search}`))
|
|
|
|
|
if (apiAssetUrl.pathname.startsWith('/api/public/assets/')) {
|
|
|
|
|
apiAssetUrl.searchParams.set('format', 'png')
|
|
|
|
|
}
|
|
|
|
|
pushCandidate(apiAssetUrl.toString())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pushCandidate(parsed.toString())
|
|
|
|
|
} catch {
|
|
|
|
|
pushCandidate(trimmed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return candidates
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function fetchSmzdmImageBlob(
|
|
|
|
|
sourceUrl: string,
|
|
|
|
|
signal?: AbortSignal,
|
|
|
|
|
): Promise<SmzdmImageBlob | null> {
|
|
|
|
|
for (const candidate of buildAssetURLCandidates(sourceUrl)) {
|
|
|
|
|
const response = await adapterFetch(candidate, {}, { signal, timeoutMs: 10_000 }).catch(
|
|
|
|
|
() => null,
|
|
|
|
|
)
|
|
|
|
|
if (!response?.ok) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sourceBlob = await response.blob().catch(() => null)
|
|
|
|
|
if (!sourceBlob || !sourceBlob.type.startsWith('image/')) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const buffer = Buffer.from(await sourceBlob.arrayBuffer())
|
|
|
|
|
const image = nativeImage.createFromBuffer(buffer)
|
|
|
|
|
if (image.isEmpty()) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const size = image.getSize()
|
|
|
|
|
const normalizedBlob =
|
|
|
|
|
sourceBlob.type.toLowerCase() === 'image/webp'
|
|
|
|
|
? new Blob([new Uint8Array(image.toPNG())], { type: 'image/png' })
|
|
|
|
|
: sourceBlob
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
blob: normalizedBlob,
|
|
|
|
|
width: size.width,
|
|
|
|
|
height: size.height,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getSmzdmCoverCropRect(
|
|
|
|
|
width: number,
|
|
|
|
|
height: number,
|
|
|
|
@@ -396,16 +481,18 @@ async function uploadImage(
|
|
|
|
|
articleId: string,
|
|
|
|
|
cropCover: boolean,
|
|
|
|
|
): Promise<SmzdmUploadedImage | null> {
|
|
|
|
|
const image = await fetchSmzdmImageBlob(sourceUrl, context.signal)
|
|
|
|
|
const image = await fetchImageAssetBlob(sourceUrl, { signal: context.signal })
|
|
|
|
|
if (!image) {
|
|
|
|
|
throw new Error(cropCover ? 'smzdm_cover_fetch_failed' : 'smzdm_image_fetch_failed')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const csrfToken = await smzdmCsrfToken(context)
|
|
|
|
|
const form = new FormData()
|
|
|
|
|
form.append('imgFile', image.blob, 'image.png')
|
|
|
|
|
form.append('imgFile', image.blob, image.fileName)
|
|
|
|
|
form.append('id', 'WU_FILE_0')
|
|
|
|
|
form.append('type', image.blob.type || 'image/png')
|
|
|
|
|
form.append('article_id', articleId)
|
|
|
|
|
appendSmzdmCsrfFields(form, csrfToken)
|
|
|
|
|
|
|
|
|
|
const uploaded = await sessionFetchJson<SmzdmImageUploadResponse>(
|
|
|
|
|
context.session,
|
|
|
|
@@ -413,7 +500,7 @@ async function uploadImage(
|
|
|
|
|
{
|
|
|
|
|
method: 'POST',
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
headers: await smzdmHeaders(context),
|
|
|
|
|
headers: await smzdmHeaders(context, {}, csrfToken),
|
|
|
|
|
body: form,
|
|
|
|
|
},
|
|
|
|
|
{ signal: context.signal },
|
|
|
|
@@ -426,6 +513,9 @@ async function uploadImage(
|
|
|
|
|
|
|
|
|
|
if (!isSuccessfulCode(uploaded.error_code) || !uploaded.data?.url) {
|
|
|
|
|
const message = smzdmResponseMessage(uploaded)
|
|
|
|
|
if (isSmzdmCsrfMissingMessage(message)) {
|
|
|
|
|
throw new Error(`smzdm_csrf_missing:${message}`)
|
|
|
|
|
}
|
|
|
|
|
if (isSmzdmChallengeMessage(message)) {
|
|
|
|
|
throw new Error(`smzdm_challenge_required:${message}`)
|
|
|
|
|
}
|
|
|
|
@@ -436,19 +526,26 @@ async function uploadImage(
|
|
|
|
|
|
|
|
|
|
const uploadedId = stringId(uploaded.data.id)
|
|
|
|
|
if (uploadedId) {
|
|
|
|
|
const recordBody = new URLSearchParams({
|
|
|
|
|
article_id: articleId,
|
|
|
|
|
ids: uploadedId,
|
|
|
|
|
})
|
|
|
|
|
appendSmzdmCsrfFields(recordBody, csrfToken)
|
|
|
|
|
|
|
|
|
|
void sessionFetchJson(
|
|
|
|
|
context.session,
|
|
|
|
|
`${SMZDM_ORIGIN}/api/editor/image_add_time/record`,
|
|
|
|
|
{
|
|
|
|
|
method: 'POST',
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
headers: await smzdmHeaders(context, {
|
|
|
|
|
'content-type': 'application/x-www-form-urlencoded',
|
|
|
|
|
}),
|
|
|
|
|
body: new URLSearchParams({
|
|
|
|
|
article_id: articleId,
|
|
|
|
|
ids: uploadedId,
|
|
|
|
|
}),
|
|
|
|
|
headers: await smzdmHeaders(
|
|
|
|
|
context,
|
|
|
|
|
{
|
|
|
|
|
'content-type': 'application/x-www-form-urlencoded',
|
|
|
|
|
},
|
|
|
|
|
csrfToken,
|
|
|
|
|
),
|
|
|
|
|
body: recordBody,
|
|
|
|
|
},
|
|
|
|
|
{ signal: context.signal },
|
|
|
|
|
).catch(() => null)
|
|
|
|
@@ -468,13 +565,21 @@ async function uploadImage(
|
|
|
|
|
{
|
|
|
|
|
method: 'POST',
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
headers: await smzdmHeaders(context, {
|
|
|
|
|
'content-type': 'application/x-www-form-urlencoded',
|
|
|
|
|
}),
|
|
|
|
|
body: new URLSearchParams({
|
|
|
|
|
article_id: articleId,
|
|
|
|
|
pic_url: uploaded.data.url,
|
|
|
|
|
}),
|
|
|
|
|
headers: await smzdmHeaders(
|
|
|
|
|
context,
|
|
|
|
|
{
|
|
|
|
|
'content-type': 'application/x-www-form-urlencoded',
|
|
|
|
|
},
|
|
|
|
|
csrfToken,
|
|
|
|
|
),
|
|
|
|
|
body: (() => {
|
|
|
|
|
const body = new URLSearchParams({
|
|
|
|
|
article_id: articleId,
|
|
|
|
|
pic_url: uploaded.data.url,
|
|
|
|
|
})
|
|
|
|
|
appendSmzdmCsrfFields(body, csrfToken)
|
|
|
|
|
return body
|
|
|
|
|
})(),
|
|
|
|
|
},
|
|
|
|
|
{ signal: context.signal },
|
|
|
|
|
).catch(() => null)
|
|
|
|
@@ -504,6 +609,7 @@ async function uploadImage(
|
|
|
|
|
cropForm.append('cut_pic_list[0][original_pic_width]', String(image.width))
|
|
|
|
|
cropForm.append('cut_pic_list[0][cutUrl]', original?.data?.original_url || '')
|
|
|
|
|
cropForm.append('cut_pic_list[0][is_head]', '1')
|
|
|
|
|
appendSmzdmCsrfFields(cropForm, csrfToken)
|
|
|
|
|
|
|
|
|
|
const cropped = await sessionFetchJson<SmzdmCropResponse>(
|
|
|
|
|
context.session,
|
|
|
|
@@ -511,7 +617,7 @@ async function uploadImage(
|
|
|
|
|
{
|
|
|
|
|
method: 'POST',
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
headers: await smzdmHeaders(context),
|
|
|
|
|
headers: await smzdmHeaders(context, {}, csrfToken),
|
|
|
|
|
body: cropForm,
|
|
|
|
|
},
|
|
|
|
|
{ signal: context.signal },
|
|
|
|
@@ -796,6 +902,17 @@ function failureResult(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message.startsWith('smzdm_csrf_missing') || isSmzdmCsrfMissingMessage(message)) {
|
|
|
|
|
return {
|
|
|
|
|
status: 'failed',
|
|
|
|
|
summary: '什么值得买 CSRF token 缺失,请重新登录或重新绑定账号后再试。',
|
|
|
|
|
error: {
|
|
|
|
|
code: 'smzdm_csrf_missing',
|
|
|
|
|
message,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message.startsWith('smzdm_cover_fetch_failed')) {
|
|
|
|
|
return {
|
|
|
|
|
status: 'failed',
|
|
|
|
@@ -905,15 +1022,24 @@ export const smzdmAdapter: PublishAdapter = {
|
|
|
|
|
|
|
|
|
|
await context.reportProgress('smzdm.submit')
|
|
|
|
|
await waitForHumanPace(context.signal)
|
|
|
|
|
const csrfToken = await fetchSmzdmEditorToken(context)
|
|
|
|
|
if (!csrfToken) {
|
|
|
|
|
throw new Error('smzdm_csrf_missing:editor_token_missing')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await sessionFetchJson<SmzdmSubmitResponse>(
|
|
|
|
|
context.session,
|
|
|
|
|
`${SMZDM_ORIGIN}/api/editor/article/submit`,
|
|
|
|
|
{
|
|
|
|
|
method: 'POST',
|
|
|
|
|
credentials: 'include',
|
|
|
|
|
headers: await smzdmHeaders(context, {
|
|
|
|
|
'content-type': 'application/x-www-form-urlencoded',
|
|
|
|
|
}),
|
|
|
|
|
headers: await smzdmHeaders(
|
|
|
|
|
context,
|
|
|
|
|
{
|
|
|
|
|
'content-type': 'application/x-www-form-urlencoded',
|
|
|
|
|
},
|
|
|
|
|
csrfToken,
|
|
|
|
|
),
|
|
|
|
|
body: encodeSmzdmForm(
|
|
|
|
|
buildSubmitForm({
|
|
|
|
|
articleId,
|
|
|
|
@@ -937,6 +1063,9 @@ export const smzdmAdapter: PublishAdapter = {
|
|
|
|
|
const topicCode = response.data?.update_topic?.error_code
|
|
|
|
|
if (!isSuccessfulCode(response.error_code) || !isSuccessfulCode(topicCode)) {
|
|
|
|
|
const message = response.data?.update_topic?.error_msg || smzdmResponseMessage(response)
|
|
|
|
|
if (isSmzdmCsrfMissingMessage(message)) {
|
|
|
|
|
throw new Error(`smzdm_csrf_missing:${message}`)
|
|
|
|
|
}
|
|
|
|
|
if (isSmzdmChallengeMessage(message)) {
|
|
|
|
|
throw new Error(`smzdm_challenge_required:${message}`)
|
|
|
|
|
}
|
|
|
|
|