npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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 only checkAuth, publish, getStatus, and createExtensionRuntime.
  • 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:

  • douyin uses one package adapter for both image-text and video. The extension UI exposes them as two entries: douyin and douyin-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 queryable statusRef. Public visibility must be checked through getStatus().

Install The Npm Package

npm install @skrillex1224/chrome-article-publish-extension

Or with pnpm:

pnpm add @skrillex1224/chrome-article-publish-extension

Chrome 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. Only http:// and https:// URLs are supported.
  • tags: tag names as string[]. 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()
  • statusRef must 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. postUrl should 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. Show message to the user.
  • not_found: the platform could not find the content by the returned reference.
  • unknown: the adapter could not determine the state. Show message and 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 message directly.
  • If url exists, show it as a QR image or openable verification link.
  • Respect expiresAt and show timeout state if the user does not complete the action.
  • Keep the publish task as publishing while the package is waiting.
  • If publish() returns failed, show error.message and 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 images or video, not both.
  • images[].url, video.url, and cover must be public http(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-app

The zip output is:

chrome-article-publish-extension-1.0.10.zip

For local development:

pnpm --filter chrome-article-publish-extension build
pnpm --filter chrome-article-publish-extension test
pnpm --filter chrome-article-publish-extension typecheck

Install In Chrome

  1. Open chrome://extensions.
  2. Enable Developer Mode.
  3. Choose "Load unpacked" and select article-publish-extension/extension-app/dist.
  4. Or unzip chrome-article-publish-extension-1.0.10.zip and load the extracted directory.
  5. Log in to target platforms in the same Chrome profile.
  6. Click the extension icon to open the side panel.

UI Flow

  1. Create article

    • Click 新建.
    • The editor opens as a full extension tab.
    • Fill title, description, cover, tags, images, video, and markdown.
    • Save the article into local Chrome storage.
  2. Import articles

    • Click 导入.
    • Upload a .csv or .xlsx file.
    • Select sheet, map fields, preview rows, and import.
    • .xls is not supported.
  3. Select articles

    • The side panel lists saved articles.
    • Click article cards or checkboxes to select one or more articles.
    • Click 发布所选.
  4. 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.
  5. 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.
  6. 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.
  7. Export links

    • Click 导出链接.
    • The UI exports an .xlsx workbook with article/platform link and status data.

Extension Storage Model

The UI stores records in Chrome local storage:

  • articlePublishExtension:articles:v2
  • articlePublishExtension:tasks:v2
  • articlePublishExtension:statuses:v2
  • articlePublishExtension:auth:v2
  • articlePublishExtension: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
  • checkAuth
  • publish
  • getStatus
  • 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 build

Adapter-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-group

Live 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, or category inputs.