@skrillex1224/chrome-article-publish-extension
v1.0.10
Published
Chrome-extension runtime article publishing adapters for Chinese content platforms.
Readme
@skrillex1224/chrome-article-publish-extension
Chrome extension runtime package for publishing articles through platform adapters.
This repository contains two separate parts:
src/: the reusable npm package. It exposes onlycheckAuth,publish,getStatus, andcreateExtensionRuntime.extension-app/: a standalone Chrome extension UI that imports the npm package and provides article storage, editing, import/export, publishing, retry, and status pages.
The npm package is the kernel. The extension UI is one host application. Other Chrome extensions can install the package and call the same kernel APIs directly.
Current Scope
The package is designed for low-frequency publishing from a user's own logged-in Chrome profile. It follows the public frontend behavior of each platform and uses platform cookies/session state from the current browser profile.
It is not a Node CLI package and should not be used from a server to copy or reuse user cookies.
Supported Platforms
| Platform ID | UI Name | Publish Type | Main Inputs |
|---|---|---|---|
| baijiahao | 百家号 | Article | title, markdown, description, cover, tags |
| bilibili | 哔哩哔哩 | Opus/article | title, markdown, description, cover, tags |
| cnblogs | 博客园 | Article | title, markdown, tags |
| csdn | CSDN | Article | title, markdown, description, cover, tags |
| 51cto | 51CTO | Article | title, markdown, description, cover, tags |
| douyin | 抖音图文 | Image-text note | title, description, tags, images |
| douyin | 抖音视频 | Video | title, description, tags, video, cover |
| juejin | 掘金 | Article | title, markdown, description, cover, tags |
| netease | 网易号 | Article | title, markdown, description, cover |
| sohu | 搜狐号 | Article | title, markdown, description, cover |
| tencent | 腾讯号 | Article | title, markdown, description, cover |
| toutiao | 今日头条 | Article | title, markdown, description, cover |
| weixin | 微信公众号 | Article | title, markdown, description, cover |
Notes:
douyinuses one package adapter for both image-text and video. The extension UI exposes them as two entries:douyinanddouyin-video.- Platform policy can still put a post into review, reject it, require security verification, or block it because of account limits.
publish()success means the platform accepted the publish/submit request and returned a queryablestatusRef. Public visibility must be checked throughgetStatus().
Install The Npm Package
npm install @skrillex1224/chrome-article-publish-extensionOr with pnpm:
pnpm add @skrillex1224/chrome-article-publish-extensionChrome extension pages and service workers cannot use a bare npm import at runtime by themselves. Bundle this package into your extension with Vite, Rollup, tsup, webpack, or another build tool.
Manifest Requirements
A host extension needs enough Chrome permissions for cookies, cross-origin requests, platform page context, downloads, and managed temporary tabs:
{
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"permissions": [
"storage",
"unlimitedStorage",
"cookies",
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess",
"scripting",
"tabs",
"tabGroups",
"downloads"
],
"host_permissions": [
"http://*/*",
"https://*/*"
]
}If your extension uses a side panel UI, also add sidePanel. The npm package itself does not require a side panel.
Quick Start
import {
checkAuth,
createExtensionRuntime,
getStatus,
publish,
type PublishEvent,
} from '@skrillex1224/chrome-article-publish-extension'
const runtime = createExtensionRuntime({
timeout: 3_600_000,
})
const auth = await checkAuth({
platform: 'bilibili',
runtime,
})
if (!auth.isAuthenticated) {
throw new Error(auth.error || 'Not authenticated')
}
const publishResult = await publish({
platform: 'bilibili',
runtime,
article: {
title: '人为什么会饿',
markdown: '# 人为什么会饿\n\n饥饿是一套身体提醒系统。',
description: '人会饿,不只是因为胃空了。',
cover: 'https://example.com/cover.jpg',
tags: ['健康', '科普'],
},
async onEvent(event: PublishEvent) {
if (event.type === 'security_check') {
console.log(event.method, event.message, event.url, event.expiresAt)
}
},
})
if (publishResult.state === 'failed') {
throw new Error(publishResult.error.message)
}
const status = await getStatus({
platform: 'bilibili',
runtime,
statusRef: publishResult.statusRef,
})
console.log(status.state, status.postUrl, status.message)Public API
createExtensionRuntime(config?)
Creates the Chrome extension runtime used by all adapters.
type RuntimeConfig = {
timeout?: number
userAgent?: string
}Recommended timeout:
- Article-only publishing: at least
120_000. - Image/video upload publishing: use a longer timeout, for example
3_600_000.
The runtime handles:
- authenticated
fetch()from the extension profile - cookie read/write
- local/session storage helpers
- declarative header rules
- downloads
- tab creation/reuse/close
chrome.scripting.executeScript
Temporary tabs created by the runtime are grouped under a Chrome tab group named Article Publish Runtime. Existing user-opened matching platform tabs are reused and not closed by the runtime.
checkAuth(request)
Checks whether the current Chrome profile is logged in to the platform.
type CheckAuthRequest = {
platform: string
runtime: RuntimeInterface
}
type AuthResult = {
isAuthenticated: boolean
username?: string
userId?: string
avatar?: string
error?: string
}Use this before showing a platform as ready to publish. A failed auth check should be displayed as "not logged in" or as the returned error.
publish(request)
Publishes or submits an article to one platform.
type PublishRequest = {
platform: string
runtime: RuntimeInterface
article: PublishArticleInput
onEvent?: (event: PublishEvent) => void | Promise<void>
options?: {
onImageProgress?: (current: number, total: number) => void
}
}
type PublishArticleInput = {
title: string
markdown: string
description?: string
cover?: string
tags?: string[]
images?: Array<{ url: string }>
video?: {
url: string
filename?: string
mimeType?: string
}
}Field contract:
title: platform title.markdown: canonical article body. This is the only public body input.description: summary, digest, excerpt, caption, or platform description field depending on the target platform.cover: cover image URL. Onlyhttp://andhttps://URLs are supported.tags: tag names asstring[]. The caller should pass an array, not a semicolon-joined string.images: image URL list for image-text media flows, currently mainly Douyin image-text.video: video URL for video media flows, currently Douyin video.
Do not pass html, body, content, summary, or category. The new contract intentionally does not support those aliases. Adapters convert markdown to HTML, rich text, or platform-specific payloads internally.
Return type:
type PublishResult =
| {
platform: string
state: 'succeeded'
statusRef: PublishStatusRef
postId?: string
postUrl?: string
timestamp: number
}
| {
platform: string
state: 'failed'
error: { message: string; code?: string }
timestamp: number
}state: 'succeeded' means:
- the platform accepted the publish/submit request
- the adapter returned enough data for
getStatus() statusRefmust be stored by the host extension
It does not guarantee that the article is already publicly visible. Use getStatus() for that.
getStatus(request)
Queries the platform status using the statusRef returned by publish().
type GetStatusRequest = {
platform: string
runtime: RuntimeInterface
statusRef: PublishStatusRef
}
type PublishStatusRef = {
platform: string
kind: string
id?: string
url?: string
data?: Record<string, string | number | boolean | null>
}
type PublishStatusResult = {
platform: string
state: 'published' | 'reviewing' | 'rejected' | 'not_found' | 'unknown'
message?: string
postUrl?: string
checkedAt: number
}Store statusRef exactly as returned. The host UI should not parse or rewrite it.
Status meanings:
published: the platform reports a public or published state.postUrlshould be used when present.reviewing: the platform accepted the content but it is still in review or pending.rejected: the platform rejected or blocked the content. Showmessageto the user.not_found: the platform could not find the content by the returned reference.unknown: the adapter could not determine the state. Showmessageand allow retry/refresh.
Security Check Events
Some platforms may require QR code confirmation, CAPTCHA, or a manual security step. In that case publish() emits an event and waits until the platform flow completes or times out.
type PublishEvent = {
type: 'progress' | 'security_check'
platform: string
message: string
expiresAt: number
url?: string
method?: 'qrcode' | 'captcha' | 'manual'
}Host UI guidance:
- Display
messagedirectly. - If
urlexists, show it as a QR image or openable verification link. - Respect
expiresAtand show timeout state if the user does not complete the action. - Keep the publish task as
publishingwhile the package is waiting. - If
publish()returnsfailed, showerror.messageand allow retry.
Douyin Media Rules
The package uses platform: 'douyin' for both Douyin image-text and Douyin video. The media input decides the mode.
Image-text:
await publish({
platform: 'douyin',
runtime,
article: {
title: '图文标题',
markdown: '',
description: '图文描述',
tags: ['旅行', '生活'],
images: [
{ url: 'https://example.com/1.jpg' },
{ url: 'https://example.com/2.jpg' },
],
},
})Video:
await publish({
platform: 'douyin',
runtime,
article: {
title: '视频标题',
markdown: '',
description: '视频描述',
cover: 'https://example.com/cover.jpg',
tags: ['科技', 'AI'],
video: {
url: 'https://example.com/video.mp4',
filename: 'video.mp4',
mimeType: 'video/mp4',
},
},
})Rules:
- Pass either
imagesorvideo, not both. images[].url,video.url, andcovermust be publichttp(s)URLs.- The package does not convert Markdown into Douyin image cards. The extension UI has an optional Markdown-to-image helper for that UI workflow.
Extension UI
The included extension app lives in extension-app/. It is useful as:
- a ready-to-install manual publishing tool
- a reference implementation for another extension
- an example of how to keep UI state separate from the npm kernel
Build And Package
From article-publish-extension/:
pnpm install
pnpm build:extension-app
pnpm zip:extension-appThe zip output is:
chrome-article-publish-extension-1.0.10.zipFor local development:
pnpm --filter chrome-article-publish-extension build
pnpm --filter chrome-article-publish-extension test
pnpm --filter chrome-article-publish-extension typecheckInstall In Chrome
- Open
chrome://extensions. - Enable Developer Mode.
- Choose "Load unpacked" and select
article-publish-extension/extension-app/dist. - Or unzip
chrome-article-publish-extension-1.0.10.zipand load the extracted directory. - Log in to target platforms in the same Chrome profile.
- Click the extension icon to open the side panel.
UI Flow
Create article
- Click
新建. - The editor opens as a full extension tab.
- Fill
title,description,cover,tags,images,video, andmarkdown. - Save the article into local Chrome storage.
- Click
Import articles
- Click
导入. - Upload a
.csvor.xlsxfile. - Select sheet, map fields, preview rows, and import.
.xlsis not supported.
- Click
Select articles
- The side panel lists saved articles.
- Click article cards or checkboxes to select one or more articles.
- Click
发布所选.
Select platforms
- The side panel checks login state with
checkAuth(). - Logged-in platforms can be selected.
- Not-logged-in platforms show the auth failure reason and should be opened manually for login.
- The side panel checks login state with
Publish
- Click
发布所选. - The background service worker calls package
publish()for each selected article/platform task. - Already succeeded tasks are skipped and not published again.
- Failed tasks can be retried.
- Click
Check status
- After successful submission, the UI stores
statusRef. - Status refresh calls package
getStatus(). - The platform detail page shows per-article publish result, current status, reason, and public link when available.
- After successful submission, the UI stores
Export links
- Click
导出链接. - The UI exports an
.xlsxworkbook with article/platform link and status data.
- Click
Extension Storage Model
The UI stores records in Chrome local storage:
articlePublishExtension:articles:v2articlePublishExtension:tasks:v2articlePublishExtension:statuses:v2articlePublishExtension:auth:v2articlePublishExtension:sidePanel:v2
Article records use the same hard-cut input model as the npm package:
type ArticleRecord = {
id: string
title: string
description?: string
markdown: string
cover?: string
tags: string[]
images?: Array<{ url: string }>
video?: {
url: string
filename?: string
mimeType?: string
}
createdAt: number
updatedAt: number
}Publish tasks are keyed by articleId + platform. A task with state: 'succeeded' is not republished by the UI. A failed task can be retried, reusing the same logical task slot.
CSV/XLSX Import Fields
The import page accepts these canonical fields:
| Field | Chinese Alias | Required | Meaning |
|---|---|---:|---|
| title | 标题 | No | Article title. Empty title becomes 未命名文章. |
| markdown | 正文 | No | Canonical article body. Empty string is allowed for media-only tasks. |
| description | 摘要 | No | Summary/description/caption. |
| cover | 封面 | No | Cover image URL. |
| tags | 标签 | No | JSON string array or text split by ;, ;, ,, ,, or newline. |
| images | 图片 | No | JSON array, URL list, or { "url": "..." } array. |
| video | 视频 | No | One video URL. |
The UI intentionally keeps one English field and one Chinese alias per concept.
OSS Upload In The UI
The extension UI can upload local images/videos or generated Markdown image pages to OSS, then fill URL fields back into the article.
Configuration is stored only in Chrome local storage, not in source code or the npm package:
type OssConfig = {
endpoint: string
accessKeyId: string
accessKeySecret: string
bucketName: string
prefix: string
}Do not ship real access keys in a public extension build. If browser direct upload is used, the OSS bucket must allow the extension origin in CORS settings.
The Markdown-to-image feature is UI-only. It is mainly for generating Douyin image-text images. The npm package still receives only URL-based images.
Package And UI Boundary
The npm package owns:
- platform adapters
checkAuthpublishgetStatus- platform-specific Markdown conversion
- Chrome extension runtime helpers
- security check events
- managed runtime tabs
The extension UI owns:
- article creation and editing
- local article/task/status storage
- CSV/XLSX import
- OSS upload configuration
- Markdown preview and Markdown-to-image generation
- platform selection
- retry buttons
- status polling cadence
- export workbook
The extension UI should call the package. It should not import adapter internals.
Local Verification
Useful commands:
pnpm typecheck
pnpm build:npm
pnpm build:extension-app
pnpm zip:extension-app
pnpm --filter chrome-article-publish-extension typecheck
pnpm --filter chrome-article-publish-extension test
pnpm --filter chrome-article-publish-extension buildAdapter-focused checks live in scripts/, for example:
pnpm check:public-api
pnpm check:publish-contract
pnpm check:douyin-images
pnpm check:douyin-video
pnpm check:tencent-auth
pnpm check:tab-groupLive E2E scripts require a logged-in Chrome profile and the target platform account. They should be run one platform at a time and their result should be verified through publish() and getStatus().
Troubleshooting
Not authenticated
The user is not logged in to the platform in the same Chrome profile as the extension. Open the platform homepage, log in, then run checkAuth() again.
Platform not found
The platform string is not one of the supported package platform IDs. For the extension UI, douyin-video is a UI-only ID and maps to package platform douyin.
Publish succeeded but no public URL
Call getStatus() with the returned statusRef. Some platforms only expose a public URL after review or after management status changes to published.
Security check appears
Listen to onEvent. Show event.message, event.url, and event.expiresAt. The package waits until the platform flow completes or times out.
Images or video fail to upload
Check that media URLs are public http(s) URLs and that the platform account is allowed to upload that media type. For UI OSS uploads, also check OSS CORS and credentials.
Extension opens temporary platform tabs
Some adapters need a platform page context for upload/status data. Runtime-created tabs are grouped under Article Publish Runtime and are closed after use when the adapter requested a temporary tab. Existing matching user tabs are reused and not closed.
Bare import fails in extension page
Bundle the package. Chrome extension pages cannot resolve a bare specifier like @skrillex1224/chrome-article-publish-extension unless your build tool has bundled it.
Not Included
- No server-side publishing.
- No cross-profile cookie copying.
- No old Wechatsync import dependency.
- No public CDN runtime loader.
- No built-in task database in the npm package.
- No publish scheduler in the npm package.
- No compatibility with old
html,body,content,summary, orcategoryinputs.
