chore(frontend): introduce prettier + eslint and prune unused code
Backend CI / Backend (push) Has been cancelled
Frontend CI / Frontend (push) Failing after 1m39s

- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports)
- Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier
- Add root scripts: format, format:check, lint, lint:fix
- Reformat 257 files across admin-web, ops-web, desktop-client, packages
- Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters
- Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex
- Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 20:39:09 +08:00
parent 21c7892ee4
commit 162abdc97c
258 changed files with 42261 additions and 37556 deletions
+187 -180
View File
@@ -1,130 +1,136 @@
import { randomBytes } from "node:crypto";
import { randomBytes } from 'node:crypto'
import type { JsonValue } from "@geo/shared-types";
import type { JsonValue } from '@geo/shared-types'
import type { PublishAdapter, PublishAdapterContext } from './base'
import {
extractImageSources,
normalizeArticleHtml,
sessionCookieValue,
sessionFetchJson,
uploadHtmlImages,
} from "./common";
import { fetchImageAssetBlob } from "./media-image";
import type { PublishAdapter, PublishAdapterContext } from "./base";
} from './common'
import { fetchImageAssetBlob } from './media-image'
type SohuRegisterInfoResponse = {
data?: {
account?: {
id?: string | number;
nickName?: string;
avatar?: string;
};
};
};
id?: string | number
nickName?: string
avatar?: string
}
}
}
type SohuAccountListResponse = {
code?: number;
code?: number
data?: {
data?: Array<{
accounts?: SohuRawAccount[];
}>;
};
};
accounts?: SohuRawAccount[]
}>
}
}
type SohuRawAccount = {
id?: string | number;
nickName?: string;
avatar?: string;
};
id?: string | number
nickName?: string
avatar?: string
}
type SohuSubmitResponse = {
code?: number;
success?: boolean;
data?: string | number;
msg?: string;
message?: string;
};
code?: number
success?: boolean
data?: string | number
msg?: string
message?: string
}
type SohuUploadResponse = {
url?: string;
msg?: string;
message?: string;
};
url?: string
msg?: string
message?: string
}
type SohuAccount = {
id: string;
name: string;
};
id: string
name: string
}
type SohuPublishType = "publish" | "draft";
type SohuPublishType = 'publish' | 'draft'
const SOHU_ORIGIN = "https://mp.sohu.com";
const SOHU_MANAGE_URL = `${SOHU_ORIGIN}/mpfe/v4/contentManagement/first/page?newsType=1`;
const SOHU_PUBLISH_URL = `${SOHU_ORIGIN}/mpbp/bp/news/v4/news/publish/v2`;
const SOHU_DRAFT_URL = `${SOHU_ORIGIN}/mpbp/bp/news/v4/news/draft/v2`;
const SOHU_ORIGIN = 'https://mp.sohu.com'
const SOHU_MANAGE_URL = `${SOHU_ORIGIN}/mpfe/v4/contentManagement/first/page?newsType=1`
const SOHU_PUBLISH_URL = `${SOHU_ORIGIN}/mpbp/bp/news/v4/news/publish/v2`
const SOHU_DRAFT_URL = `${SOHU_ORIGIN}/mpbp/bp/news/v4/news/draft/v2`
function stringId(value: unknown): string {
if (typeof value === "number") {
return Number.isFinite(value) ? String(value) : "";
if (typeof value === 'number') {
return Number.isFinite(value) ? String(value) : ''
}
if (typeof value !== "string") {
return "";
if (typeof value !== 'string') {
return ''
}
return value.trim();
return value.trim()
}
function resolvePublishType(payload: Record<string, unknown>): SohuPublishType {
return payload.publish_type === "draft" || payload.publishType === "draft" ? "draft" : "publish";
return payload.publish_type === 'draft' || payload.publishType === 'draft' ? 'draft' : 'publish'
}
function isSohuSuccess(response: SohuSubmitResponse | null | undefined): boolean {
return Boolean(response?.data) && (response?.success === true || response?.code === 2_000_000);
return Boolean(response?.data) && (response?.success === true || response?.code === 2_000_000)
}
function sohuResponseMessage(response: { msg?: string; message?: string; code?: number } | null | undefined): string {
return response?.msg || response?.message || `sohuhao_error_${response?.code ?? "unknown"}`;
function sohuResponseMessage(
response: { msg?: string; message?: string; code?: number } | null | undefined,
): string {
return response?.msg || response?.message || `sohuhao_error_${response?.code ?? 'unknown'}`
}
function isSohuChallengeMessage(message: string): boolean {
return /(验证码|滑块|人机验证|安全验证|请完成验证|短信验证|扫码验证|captcha|challenge|verification required|风险|风控)/i
.test(message);
return /(验证码|滑块|人机验证|安全验证|请完成验证|短信验证|扫码验证|captcha|challenge|verification required|风险|风控)/i.test(
message,
)
}
function generateDeviceId(): string {
return randomBytes(16).toString("hex");
return randomBytes(16).toString('hex')
}
async function cookieHeaderForUrl(context: PublishAdapterContext, url = SOHU_ORIGIN): Promise<string> {
const cookies = await context.session.cookies.get({ url }).catch(() => []);
return cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
async function cookieHeaderForUrl(
context: PublishAdapterContext,
url = SOHU_ORIGIN,
): Promise<string> {
const cookies = await context.session.cookies.get({ url }).catch(() => [])
return cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ')
}
async function sohuSpCm(context: PublishAdapterContext): Promise<string> {
const cookieValue =
(await sessionCookieValue(context.session, "sohu.com", "mp-cv").catch(() => "")) ||
(await sessionCookieValue(context.session, "mp.sohu.com", "mp-cv").catch(() => ""));
return cookieValue || `100-${Date.now()}-${generateDeviceId()}`;
(await sessionCookieValue(context.session, 'sohu.com', 'mp-cv').catch(() => '')) ||
(await sessionCookieValue(context.session, 'mp.sohu.com', 'mp-cv').catch(() => ''))
return cookieValue || `100-${Date.now()}-${generateDeviceId()}`
}
async function sohuHeaders(
context: PublishAdapterContext,
extra: Record<string, string> = {},
): Promise<Record<string, string>> {
const cookie = await cookieHeaderForUrl(context);
const cookie = await cookieHeaderForUrl(context)
return {
accept: "application/json, text/plain, */*",
accept: 'application/json, text/plain, */*',
origin: SOHU_ORIGIN,
referer: `${SOHU_ORIGIN}/`,
"x-requested-with": "XMLHttpRequest",
'x-requested-with': 'XMLHttpRequest',
...(cookie ? { cookie } : {}),
...extra,
};
}
}
function accountFromRaw(account: SohuRawAccount | null | undefined): SohuAccount | null {
const id = stringId(account?.id);
const name = account?.nickName?.trim() || "";
return id && name ? { id, name } : null;
const id = stringId(account?.id)
const name = account?.nickName?.trim() || ''
return id && name ? { id, name } : null
}
async function fetchAccount(context: PublishAdapterContext): Promise<SohuAccount | null> {
@@ -132,66 +138,66 @@ async function fetchAccount(context: PublishAdapterContext): Promise<SohuAccount
context.session,
`${SOHU_ORIGIN}/mpbp/bp/account/register-info`,
{
credentials: "include",
credentials: 'include',
headers: await sohuHeaders(context),
},
).catch(() => null);
).catch(() => null)
const account = accountFromRaw(registerInfo?.data?.account);
const account = accountFromRaw(registerInfo?.data?.account)
if (account) {
return account;
return account
}
const accountListURL = new URL(`${SOHU_ORIGIN}/mpbp/bp/account/list`);
accountListURL.searchParams.set("_", String(Date.now()));
const accountListURL = new URL(`${SOHU_ORIGIN}/mpbp/bp/account/list`)
accountListURL.searchParams.set('_', String(Date.now()))
const list = await sessionFetchJson<SohuAccountListResponse>(
context.session,
accountListURL.toString(),
{
credentials: "include",
credentials: 'include',
headers: await sohuHeaders(context),
},
).catch(() => null);
).catch(() => null)
for (const group of list?.data?.data ?? []) {
for (const raw of group.accounts ?? []) {
const listed = accountFromRaw(raw);
const listed = accountFromRaw(raw)
if (listed) {
return listed;
return listed
}
}
}
return null;
return null
}
async function uploadImage(context: PublishAdapterContext, accountId: string, sourceUrl: string): Promise<string | null> {
const image = await fetchImageAssetBlob(sourceUrl);
async function uploadImage(
context: PublishAdapterContext,
accountId: string,
sourceUrl: string,
): Promise<string | null> {
const image = await fetchImageAssetBlob(sourceUrl)
if (!image) {
return null;
return null
}
const form = new FormData();
form.append("file", image.blob, image.fileName);
form.append("accountId", accountId);
const form = new FormData()
form.append('file', image.blob, image.fileName)
form.append('accountId', accountId)
const url = new URL(`${SOHU_ORIGIN}/commons/front/outerUpload/image/file`);
url.searchParams.set("accountId", accountId);
const url = new URL(`${SOHU_ORIGIN}/commons/front/outerUpload/image/file`)
url.searchParams.set('accountId', accountId)
const response = await sessionFetchJson<SohuUploadResponse>(
context.session,
url.toString(),
{
method: "POST",
credentials: "include",
headers: await sohuHeaders(context, {
"sp-cm": await sohuSpCm(context),
}),
body: form,
},
).catch(() => null);
const response = await sessionFetchJson<SohuUploadResponse>(context.session, url.toString(), {
method: 'POST',
credentials: 'include',
headers: await sohuHeaders(context, {
'sp-cm': await sohuSpCm(context),
}),
body: form,
}).catch(() => null)
return response?.url?.trim() || null;
return response?.url?.trim() || null
}
async function submitArticle(
@@ -202,49 +208,47 @@ async function submitArticle(
): Promise<string> {
const coverUrl = context.article.cover_asset_url
? await uploadImage(context, account.id, context.article.cover_asset_url).catch(() => null)
: "";
: ''
const url = new URL(publishType === "draft" ? SOHU_DRAFT_URL : SOHU_PUBLISH_URL);
url.searchParams.set("accountId", account.id);
const url = new URL(publishType === 'draft' ? SOHU_DRAFT_URL : SOHU_PUBLISH_URL)
url.searchParams.set('accountId', account.id)
const body = {
title: context.article.title?.trim() || "未命名文章",
title: context.article.title?.trim() || '未命名文章',
content: html,
brief: "",
brief: '',
channelId: 30,
categoryId: -1,
cover: coverUrl || "",
cover: coverUrl || '',
accountId: Number(account.id),
infoResource: 0,
};
const response = await sessionFetchJson<SohuSubmitResponse>(
context.session,
url.toString(),
{
method: "POST",
credentials: "include",
headers: await sohuHeaders(context, {
"content-type": "application/json",
"dv-id": generateDeviceId(),
"sp-cm": await sohuSpCm(context),
}),
body: JSON.stringify(body),
},
).catch((error): SohuSubmitResponse => ({
code: -1,
msg: error instanceof Error ? error.message : "sohuhao_submit_failed",
}));
if (!isSohuSuccess(response)) {
const message = sohuResponseMessage(response);
if (isSohuChallengeMessage(message)) {
throw new Error(`sohuhao_challenge_required:${message}`);
}
throw new Error(`sohuhao_submit_failed:${message}`);
}
return stringId(response.data);
const response = await sessionFetchJson<SohuSubmitResponse>(context.session, url.toString(), {
method: 'POST',
credentials: 'include',
headers: await sohuHeaders(context, {
'content-type': 'application/json',
'dv-id': generateDeviceId(),
'sp-cm': await sohuSpCm(context),
}),
body: JSON.stringify(body),
}).catch(
(error): SohuSubmitResponse => ({
code: -1,
msg: error instanceof Error ? error.message : 'sohuhao_submit_failed',
}),
)
if (!isSohuSuccess(response)) {
const message = sohuResponseMessage(response)
if (isSohuChallengeMessage(message)) {
throw new Error(`sohuhao_challenge_required:${message}`)
}
throw new Error(`sohuhao_submit_failed:${message}`)
}
return stringId(response.data)
}
function buildResultPayload(
@@ -253,115 +257,118 @@ function buildResultPayload(
mediaName: string,
publishType: SohuPublishType,
): Record<string, JsonValue> {
const editUrl =
`${SOHU_ORIGIN}/mpfe/v4/contentManagement/news/addarticle?spm=smmp.articlelist.0.0&contentStatus=2&id=${encodeURIComponent(articleId)}`;
const editUrl = `${SOHU_ORIGIN}/mpfe/v4/contentManagement/news/addarticle?spm=smmp.articlelist.0.0&contentStatus=2&id=${encodeURIComponent(articleId)}`
const articleUrl =
publishType === "publish"
publishType === 'publish'
? `https://www.sohu.com/a/${encodeURIComponent(articleId)}_${encodeURIComponent(accountId)}`
: null;
: null
return {
platform: "sohuhao",
platform: 'sohuhao',
media_name: mediaName,
external_article_id: articleId,
external_manage_url: editUrl || SOHU_MANAGE_URL,
external_article_url: articleUrl,
publish_type: publishType,
};
}
}
function failureResult(error: unknown): ReturnType<PublishAdapter["publish"]> extends Promise<infer T> ? T : never {
const message = error instanceof Error ? error.message : String(error);
function failureResult(
error: unknown,
): ReturnType<PublishAdapter['publish']> extends Promise<infer T> ? T : never {
const message = error instanceof Error ? error.message : String(error)
if (message === "sohuhao_not_logged_in") {
if (message === 'sohuhao_not_logged_in') {
return {
status: "failed",
summary: "搜狐号登录态失效,无法执行发布。",
status: 'failed',
summary: '搜狐号登录态失效,无法执行发布。',
error: {
code: "sohuhao_not_logged_in",
code: 'sohuhao_not_logged_in',
message,
},
};
}
}
if (message === "article_content_empty") {
if (message === 'article_content_empty') {
return {
status: "failed",
summary: "文章内容为空,无法推送到搜狐号。",
status: 'failed',
summary: '文章内容为空,无法推送到搜狐号。',
error: {
code: "article_content_empty",
code: 'article_content_empty',
message,
},
};
}
}
if (message.startsWith("sohuhao_challenge_required") || isSohuChallengeMessage(message)) {
if (message.startsWith('sohuhao_challenge_required') || isSohuChallengeMessage(message)) {
return {
status: "failed",
summary: "搜狐号触发平台验证,请在搜狐号后台完成验证后重试。",
status: 'failed',
summary: '搜狐号触发平台验证,请在搜狐号后台完成验证后重试。',
error: {
code: "sohuhao_challenge_required",
code: 'sohuhao_challenge_required',
message,
},
};
}
}
if (message.startsWith("sohuhao_submit_failed")) {
if (message.startsWith('sohuhao_submit_failed')) {
return {
status: "failed",
summary: "搜狐号提交失败,请稍后重试或打开搜狐号后台确认内容状态。",
status: 'failed',
summary: '搜狐号提交失败,请稍后重试或打开搜狐号后台确认内容状态。',
error: {
code: "sohuhao_submit_failed",
code: 'sohuhao_submit_failed',
message,
},
};
}
}
return {
status: "failed",
summary: "搜狐号发布失败。",
status: 'failed',
summary: '搜狐号发布失败。',
error: {
code: "sohuhao_publish_failed",
code: 'sohuhao_publish_failed',
message,
},
};
}
}
export const sohuhaoAdapter: PublishAdapter = {
platform: "sohuhao",
executionMode: "session",
platform: 'sohuhao',
executionMode: 'session',
async publish(context, payload) {
try {
context.reportProgress("sohuhao.detect_login");
const account = await fetchAccount(context);
context.reportProgress('sohuhao.detect_login')
const account = await fetchAccount(context)
if (!account) {
throw new Error("sohuhao_not_logged_in");
throw new Error('sohuhao_not_logged_in')
}
context.reportProgress("sohuhao.normalize_html");
let html = normalizeArticleHtml(context.article);
context.reportProgress('sohuhao.normalize_html')
let html = normalizeArticleHtml(context.article)
if (!html) {
throw new Error("article_content_empty");
throw new Error('article_content_empty')
}
const coverAssetUrl = context.article.cover_asset_url?.trim() || "";
const coverAssetUrl = context.article.cover_asset_url?.trim() || ''
if (coverAssetUrl && extractImageSources(html).length === 0) {
html = `<img src="${coverAssetUrl}" />${html}`;
html = `<img src="${coverAssetUrl}" />${html}`
}
context.reportProgress("sohuhao.upload_content_images");
const processed = await uploadHtmlImages(html, async (sourceUrl) => uploadImage(context, account.id, sourceUrl));
const publishType = resolvePublishType(payload);
context.reportProgress('sohuhao.upload_content_images')
const processed = await uploadHtmlImages(html, async (sourceUrl) =>
uploadImage(context, account.id, sourceUrl),
)
const publishType = resolvePublishType(payload)
context.reportProgress("sohuhao.submit");
const articleId = await submitArticle(context, account, processed.html, publishType);
context.reportProgress('sohuhao.submit')
const articleId = await submitArticle(context, account, processed.html, publishType)
return {
status: "succeeded",
summary: publishType === "draft" ? "搜狐号草稿保存成功。" : "搜狐号发布成功。",
status: 'succeeded',
summary: publishType === 'draft' ? '搜狐号草稿保存成功。' : '搜狐号发布成功。',
payload: buildResultPayload(articleId, account.id, account.name, publishType),
};
}
} catch (error) {
return failureResult(error);
return failureResult(error)
}
},
};
}