@skrillex1224/chrome-article-publish-extension 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,13 +11,39 @@ Direct article publishing adapters for Chrome extensions. The package runs in a
11
11
 
12
12
  The host extension owns UI, local task state, retry buttons, storage, and polling cadence.
13
13
 
14
+ ## Included Chrome Extension UI
15
+
16
+ This repository also includes a standalone host extension UI under `extension-app/`.
17
+
18
+ Boundary:
19
+
20
+ - `src/` is the reusable npm kernel.
21
+ - `extension-app/` is only a Chrome extension UI that imports the kernel package at build time.
22
+ - The extension app does not import adapter internals.
23
+ - The extension app never loads executable adapter code from a CDN.
24
+
25
+ Build the side panel extension:
26
+
27
+ ```bash
28
+ pnpm build:extension-app
29
+ pnpm zip:extension-app
30
+ ```
31
+
32
+ The zip is generated as:
33
+
34
+ ```text
35
+ chrome-article-publish-extension-ui-1.0.2.zip
36
+ ```
37
+
38
+ The UI uses Chrome Side Panel as the primary entry. Article creation happens in a full extension tab, while the side panel handles saved article selection, platform auth checks, publish tasks, retries, and status polling.
39
+
14
40
  ## Supported Platforms
15
41
 
16
42
  | id | Platform | Notes |
17
43
  |---|---|---|
18
44
  | `bilibili` | Bilibili Opus | Supports cover and structured Opus payload. Tables degrade to readable text. |
19
45
  | `weixin` | WeChat Official Account | Supports cover, body images, summary, and security QR events. |
20
- | `douyin` | Douyin image-text | Renders Markdown into image cards, then publishes image-text notes. |
46
+ | `douyin` | Douyin | Requires exactly one of `article.images` URL list for image-text notes or `article.video.url` for video posts. |
21
47
  | `cnblogs` | Cnblogs | Supports article publish, tags, cover metadata. |
22
48
  | `juejin` | Juejin | Supports cover and real tag ids when the account is eligible. |
23
49
  | `baijiahao` | Baijiahao | Resolves public URLs from official page data after management status says published. |
@@ -148,10 +174,22 @@ type PublishArticleInput = {
148
174
  cover?: string
149
175
  tags?: string[]
150
176
  category?: string
177
+ images?: Array<{ url: string }>
178
+ video?: {
179
+ url: string
180
+ filename?: string
181
+ mimeType?: string
182
+ }
151
183
  source?: { url: string; platform: string }
152
184
  }
153
185
  ```
154
186
 
187
+ For Douyin image-text, pass `platform: 'douyin'` and `article.images: [{ url }]`. The adapter uploads those image URLs through ImageX and publishes a note. Douyin image-text no longer converts Markdown into image cards.
188
+
189
+ For Douyin video, pass `platform: 'douyin'` and `article.video.url`. The adapter runs inside a logged-in Chrome extension context, uses the official Creator page uploader for the video asset, and then calls the Creator publish API. If `article.cover` is present, the adapter uploads it as the video cover; otherwise it uses the platform-generated poster from the uploaded video.
190
+
191
+ Douyin media inputs are URL-only and mutually exclusive: pass either `article.images[].url` or `article.video.url`, not both. `article.images[].url`, `article.video.url`, and `article.cover` must be `http://` or `https://` URLs.
192
+
155
193
  `publish()` returns only:
156
194
 
157
195
  ```ts
@@ -202,7 +240,7 @@ const runtime = createExtensionRuntime({
202
240
  })
203
241
  ```
204
242
 
205
- `timeout` controls extension `fetch()` timeout in milliseconds. Keep it higher for platforms that upload images or generated Markdown cards.
243
+ `timeout` controls extension `fetch()` timeout in milliseconds. Keep it higher for platforms that upload images or videos.
206
244
 
207
245
  ## Testing In A Host Extension
208
246
 
@@ -1,6 +1,6 @@
1
- import { P as PlatformAdapter, a as PublishOptions, A as AdapterRegistryEntry, b as PreprocessConfig } from '../types-BAnNlLpy.js';
2
- export { C as Category, D as DEFAULT_PREPROCESS_CONFIG, c as Draft, I as ImageProgressCallback, O as OutputFormat } from '../types-BAnNlLpy.js';
3
- import { P as PlatformMeta, R as RuntimeInterface, A as AuthResult, a as Article, S as SyncResult, c as PublishStatusRef, d as PublishStatusResult, H as HeaderRule } from '../interface-BlLeWXzS.js';
1
+ import { P as PlatformAdapter, a as PublishOptions, A as AdapterRegistryEntry, b as PreprocessConfig } from '../types-GigbrW8J.js';
2
+ export { C as Category, D as DEFAULT_PREPROCESS_CONFIG, c as Draft, I as ImageProgressCallback, O as OutputFormat } from '../types-GigbrW8J.js';
3
+ import { P as PlatformMeta, R as RuntimeInterface, A as AuthResult, a as Article, S as SyncResult, c as PublishStatusRef, d as PublishStatusResult, H as HeaderRule } from '../interface-DHk2PdyN.js';
4
4
 
5
5
  /**
6
6
  * 适配器基类
@@ -63,6 +63,11 @@ interface ImageUploadResult {
63
63
  /** 额外的 img 属性 */
64
64
  attrs?: Record<string, string | number>;
65
65
  }
66
+ interface NormalizedImageFile {
67
+ file: File;
68
+ mimeType: string;
69
+ ext: string;
70
+ }
66
71
  /**
67
72
  * 图片处理选项
68
73
  */
@@ -154,6 +159,10 @@ declare abstract class CodeAdapter implements PlatformAdapter {
154
159
  * data URI 转 Blob
155
160
  */
156
161
  protected dataUriToBlob(dataUri: string): Promise<Blob>;
162
+ protected normalizeImageFile(blob: Blob, src: string, filenameBase?: string): Promise<NormalizedImageFile>;
163
+ private detectImageType;
164
+ private imageTypeFromUrl;
165
+ private imageTypeFromMime;
157
166
  /**
158
167
  * 延迟
159
168
  */
@@ -449,9 +458,10 @@ declare class CSDNAdapter extends CodeAdapter {
449
458
  private signRequest;
450
459
  private canonicalizeResource;
451
460
  private resolveTags;
461
+ private preserveTaskListMarkers;
452
462
  private postSaveArticle;
453
463
  private postSaveArticleFromPage;
454
- private getCsdnTabId;
464
+ private getCsdnTab;
455
465
  publish(article: Article, options?: PublishOptions): Promise<SyncResult>;
456
466
  getStatus(statusRef: PublishStatusRef): Promise<PublishStatusResult>;
457
467
  private findManagedArticle;
@@ -506,9 +516,10 @@ declare class BilibiliAdapter extends CodeAdapter {
506
516
  private markdownBlockTokenToInlineNodes;
507
517
  private createCodeBlockParagraph;
508
518
  private createTableParagraphs;
509
- private tableToMarkdown;
510
- private tableCellsToMarkdownRow;
519
+ private tableToTextRows;
520
+ private tableCellsToText;
511
521
  private inlineTokensToPlainText;
522
+ private linkTokenToText;
512
523
  private createBlockquoteParagraphs;
513
524
  private createTextParagraph;
514
525
  private createHeadingParagraph;
@@ -546,12 +557,13 @@ declare class BilibiliAdapter extends CodeAdapter {
546
557
  }
547
558
 
548
559
  /**
549
- * 抖音图文适配器
560
+ * 抖音适配器
550
561
  *
551
562
  * 只复现创作者中心公开前端的 HTTP/API 行为:
552
563
  * - 通过创作者中心接口检查登录态
553
564
  * - 通过 ImageX 上传图片
554
- * - 通过 aweme/create_v2 直接发布图文
565
+ * - 图文:上传 caller 提供的 images URL 后通过 aweme/create_v2 直接发布
566
+ * - 视频:复用创作者中心官方 uploader 上传资产后通过 aweme/create 直接发布
555
567
  *
556
568
  * 不点击平台 UI,不逆向私有算法。若平台返回二次验证/风控,直接暴露给用户处理。
557
569
  */
@@ -572,6 +584,8 @@ declare class DouyinAdapter extends CodeAdapter {
572
584
  checkAuth(): Promise<AuthResult>;
573
585
  publish(article: Article, options?: PublishOptions): Promise<SyncResult>;
574
586
  uploadImage(file: Blob, _filename?: string): Promise<string>;
587
+ private createPublishContext;
588
+ private publishVideo;
575
589
  getStatus(statusRef: PublishStatusRef): Promise<PublishStatusResult>;
576
590
  private resolveStatusId;
577
591
  private getStatusUserId;
@@ -590,21 +604,28 @@ declare class DouyinAdapter extends CodeAdapter {
590
604
  private isRejectedStatus;
591
605
  private normalizeStatusValue;
592
606
  private resolveDouyinPublicUrl;
607
+ private isVideoStatusRef;
608
+ private statusKindLabel;
593
609
  private getStatusFields;
594
610
  private formatStatusError;
595
611
  protected uploadImageByUrl(src: string): Promise<ImageUploadResult>;
596
612
  private postCreatorJson;
597
613
  private postCreatorJsonFromPage;
598
- private getCreatorTabId;
614
+ private getCreatorJsonFromPage;
615
+ private postCreatorForm;
616
+ private postCreatorFormFromPage;
617
+ private getCreatorTab;
618
+ private closeCreatorTab;
599
619
  private extractUserData;
600
620
  private isSuccessResponse;
601
- private renderMarkdownImageUrls;
602
621
  private buildTextPayload;
603
622
  private extractBodyText;
604
- private extractCaptionText;
605
623
  private normalizeText;
606
624
  private limitText;
607
625
  private buildPublishItem;
626
+ private uploadDouyinVideo;
627
+ private waitForVideoTransend;
628
+ private buildVideoPublishPayload;
608
629
  private uploadDouyinImageByUrl;
609
630
  private loadImageBlob;
610
631
  private uploadDouyinImage;
@@ -613,10 +634,12 @@ declare class DouyinAdapter extends CodeAdapter {
613
634
  private uploadToTOS;
614
635
  private commitImageUpload;
615
636
  private assertPublishSuccess;
637
+ private extractPublishPostId;
616
638
  private looksLikeSecondVerify;
617
639
  private detectImageSize;
618
640
  private parseJsonText;
619
641
  private pickString;
642
+ private createCreationId;
620
643
  private stringifyId;
621
644
  private decodeHtmlEntities;
622
645
  }
@@ -644,7 +667,7 @@ declare class BaijiahaoAdapter extends CodeAdapter {
644
667
  private mapArticleStatus;
645
668
  private isPublishedItem;
646
669
  private resolvePublishedPublicUrl;
647
- private getPublishedPageTabId;
670
+ private getPublishedPageTab;
648
671
  private readPublishedPageSnapshot;
649
672
  private urlMatchesArticleId;
650
673
  private sleep;
@@ -653,6 +676,9 @@ declare class BaijiahaoAdapter extends CodeAdapter {
653
676
  private prepareContent;
654
677
  private replaceLinksWithUrls;
655
678
  private replaceTablesWithText;
679
+ private replaceCodeBlocksWithText;
680
+ private replaceHorizontalRules;
681
+ private preserveTaskListMarkers;
656
682
  private textFromHtml;
657
683
  private decodeHtml;
658
684
  private escapeHtml;
@@ -749,9 +775,10 @@ declare class WeixinAdapter extends CodeAdapter {
749
775
  private escapeStyleAttribute;
750
776
  /**
751
777
  * 移除外部链接(微信不允许非 mp.weixin.qq.com 域名的链接)
752
- * 将 <a href="外部链接">文字</a> 转换为 文字
778
+ * 将 <a href="外部链接">文字</a> 转换为 文字(URL),避免公开页丢失链接信息
753
779
  */
754
780
  private stripExternalLinks;
781
+ private stripHtml;
755
782
  private formatError;
756
783
  private formatStageError;
757
784
  private wrapStageError;
@@ -788,7 +815,9 @@ declare class ToutiaoAdapter extends CodeAdapter {
788
815
  private getVisitedUserId;
789
816
  private buildManagedListUrl;
790
817
  private resolvePostUrl;
818
+ private resolvePublicArticleUrl;
791
819
  private prepareContent;
820
+ private textFromHtml;
792
821
  private wrapImages;
793
822
  private toCoverPayload;
794
823
  private textLength;
@@ -818,6 +847,9 @@ declare class TencentAdapter extends CodeAdapter {
818
847
  private prepareArticleContent;
819
848
  private callPublishService;
820
849
  private ensureTencentTab;
850
+ private buildArticleListUrl;
851
+ private hasListData;
852
+ private extractUserInfo;
821
853
  private mapArticleStatus;
822
854
  private isOk;
823
855
  private errorMessage;
@@ -1122,4 +1154,4 @@ declare class NeteaseAdapter extends CodeAdapter {
1122
1154
  private isPublicArticleAvailable;
1123
1155
  }
1124
1156
 
1125
- export { AdapterRegistryEntry, BaijiahaoAdapter, BaseAdapter, BilibiliAdapter, CSDNAdapter, CnblogsAdapter, CodeAdapter, Cto51Adapter, DoubanAdapter, DouyinAdapter, EastmoneyAdapter, type ImageProcessOptions, type ImageUploadResult, ImoocAdapter, JuejinAdapter, NeteaseAdapter, OschinaAdapter, PlatformAdapter, PreprocessConfig, PublishOptions, SegmentfaultAdapter, SohuAdapter, TencentAdapter, ToutiaoAdapter, WeixinAdapter, WoshipmAdapter, XueqiuAdapter, YuqueAdapter, ZipDownloadAdapter, adapterRegistry, getAdapter, getPreprocessConfig, getPreprocessConfigs, registerAdapter };
1157
+ export { AdapterRegistryEntry, BaijiahaoAdapter, BaseAdapter, BilibiliAdapter, CSDNAdapter, CnblogsAdapter, CodeAdapter, Cto51Adapter, DoubanAdapter, DouyinAdapter, EastmoneyAdapter, type ImageProcessOptions, type ImageUploadResult, ImoocAdapter, JuejinAdapter, NeteaseAdapter, type NormalizedImageFile, OschinaAdapter, PlatformAdapter, PreprocessConfig, PublishOptions, SegmentfaultAdapter, SohuAdapter, TencentAdapter, ToutiaoAdapter, WeixinAdapter, WoshipmAdapter, XueqiuAdapter, YuqueAdapter, ZipDownloadAdapter, adapterRegistry, getAdapter, getPreprocessConfig, getPreprocessConfigs, registerAdapter };
@@ -11,16 +11,11 @@ import {
11
11
  SohuAdapter,
12
12
  TencentAdapter,
13
13
  ToutiaoAdapter,
14
- WeixinAdapter
15
- } from "../chunk-NKSXZF7J.js";
16
- import {
17
- markdownToDraft
18
- } from "../chunk-GATX26VC.js";
19
- import {
14
+ WeixinAdapter,
20
15
  createLogger,
21
16
  htmlToMarkdown,
22
17
  parseMarkdownImages
23
- } from "../chunk-JFXS3MPU.js";
18
+ } from "../chunk-TKRBESSS.js";
24
19
 
25
20
  // src/adapters/types.ts
26
21
  var DEFAULT_PREPROCESS_CONFIG = {
@@ -203,6 +198,125 @@ function getPreprocessConfigs(platformIds) {
203
198
  return adapterRegistry.getPreprocessConfigs(platformIds);
204
199
  }
205
200
 
201
+ // src/lib/markdown-to-draft.ts
202
+ import { markdownToDraft as mdToDraft } from "markdown-draft-js";
203
+ var ImageRegexp = /^!\[([^\]]*)]\s*\(([^)"]+)( "([^)"]+)")?\)/;
204
+ var imageBlockPlugin = (remarkable) => {
205
+ remarkable.block.ruler.before("paragraph", "image", (state, startLine, endLine, silent) => {
206
+ const pos = state.bMarks[startLine] + state.tShift[startLine];
207
+ const max = state.eMarks[startLine];
208
+ if (pos >= max) return false;
209
+ if (!state.src) return false;
210
+ if (state.src[pos] !== "!") return false;
211
+ const match = ImageRegexp.exec(state.src.slice(pos));
212
+ if (!match) return false;
213
+ if (!silent) {
214
+ state.tokens.push({
215
+ type: "image_open",
216
+ src: match[2],
217
+ alt: match[1],
218
+ lines: [startLine, state.line],
219
+ level: state.level
220
+ });
221
+ state.tokens.push({
222
+ type: "image_close",
223
+ level: state.level
224
+ });
225
+ }
226
+ state.line = startLine + 1;
227
+ return true;
228
+ });
229
+ };
230
+ function markdownToDraft(markdown, imageDataMap = /* @__PURE__ */ new Map()) {
231
+ const processedMarkdown = markdown.split("\n").map((line) => {
232
+ const imageBlocks = line.split("![]");
233
+ return imageBlocks.length > 1 ? imageBlocks.join("\n![]") : line;
234
+ }).join("\n");
235
+ let keyCounter = 0;
236
+ const generateUniqueKey = () => keyCounter++;
237
+ const draftState = mdToDraft(processedMarkdown, {
238
+ remarkablePlugins: [imageBlockPlugin],
239
+ blockTypes: {
240
+ image_open: function(item) {
241
+ const key = generateUniqueKey();
242
+ const blockEntities = {};
243
+ const sourcePair = item.src ? item.src.split("?#") : ["", ""];
244
+ const rawSrc = sourcePair[0];
245
+ const sourceId = sourcePair[1] || "";
246
+ const imgData = imageDataMap.get(item.src) || imageDataMap.get(rawSrc);
247
+ const imageTemplate = imgData ? {
248
+ id: imgData.id,
249
+ src: imgData.url,
250
+ thumb: imgData.thumb,
251
+ url: imgData.url,
252
+ width: imgData.width,
253
+ height: imgData.height,
254
+ file_name: imgData.file_name,
255
+ file_size: imgData.file_size
256
+ } : {
257
+ id: sourceId,
258
+ src: rawSrc,
259
+ thumb: rawSrc,
260
+ url: rawSrc
261
+ };
262
+ blockEntities[key] = {
263
+ type: "IMAGE",
264
+ mutability: "IMMUTABLE",
265
+ data: imageTemplate
266
+ };
267
+ return {
268
+ type: "atomic",
269
+ blockEntities,
270
+ inlineStyleRanges: [],
271
+ entityRanges: [{ offset: 0, length: 1, key }],
272
+ text: " "
273
+ };
274
+ }
275
+ },
276
+ blockEntities: {
277
+ image: function(item) {
278
+ const sourcePair = item.src ? item.src.split("?#") : ["", ""];
279
+ const rawSrc = sourcePair[0];
280
+ const sourceId = sourcePair[1] || "";
281
+ const imgData = imageDataMap.get(item.src) || imageDataMap.get(rawSrc);
282
+ if (imgData) {
283
+ return {
284
+ type: "IMAGE",
285
+ mutability: "IMMUTABLE",
286
+ data: {
287
+ id: imgData.id,
288
+ src: imgData.url,
289
+ thumb: imgData.thumb,
290
+ url: imgData.url,
291
+ width: imgData.width,
292
+ height: imgData.height
293
+ }
294
+ };
295
+ }
296
+ return {
297
+ type: "IMAGE",
298
+ mutability: "IMMUTABLE",
299
+ data: {
300
+ id: sourceId,
301
+ src: rawSrc,
302
+ thumb: rawSrc,
303
+ url: rawSrc
304
+ }
305
+ };
306
+ }
307
+ }
308
+ });
309
+ if (draftState.blocks) {
310
+ for (const block of draftState.blocks) {
311
+ if (block.blockEntities) {
312
+ Object.assign(draftState.entityMap, block.blockEntities);
313
+ delete block.blockEntities;
314
+ }
315
+ }
316
+ }
317
+ return JSON.stringify(draftState);
318
+ }
319
+
206
320
  // src/adapters/platforms/douban.ts
207
321
  var logger = createLogger("Douban");
208
322
  var DoubanAdapter = class extends CodeAdapter {