publishport-opencli 1.0.17 → 1.0.18
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/cli-manifest.json +33 -15
- package/clis/_shared/article/publish.js +14 -7
- package/clis/bilibili/article.js +1 -1
- package/clis/bilibili/comments.js +1 -1
- package/clis/douyin/_shared/draft-cache.js +13 -0
- package/clis/douyin/drafts.js +1 -1
- package/clis/douyin/publish-image.js +12 -10
- package/clis/instagram/_shared/private-publish.js +3 -3
- package/clis/instagram/detail.js +3 -2
- package/clis/instagram/post.js +24 -24
- package/clis/instagram/user.js +2 -1
- package/clis/jike/api.js +30 -0
- package/clis/jike/comment.js +1 -82
- package/clis/jike/like.js +1 -38
- package/clis/jike/repost.js +20 -11
- package/clis/juejin/article.js +4 -4
- package/clis/sohu/article.js +1 -1
- package/clis/sohu/stats.js +2 -2
- package/clis/toutiao/article.js +1 -1
- package/clis/toutiao/articles.js +12 -3
- package/clis/toutiao/utils.js +2 -2
- package/clis/xiaohongshu/publish.js +14 -14
- package/clis/zhihu/answer.js +1 -15
- package/clis/zhihu/article.js +1 -7
- package/clis/zhihu/image-profile.js +7 -0
- package/dist/src/cli.js +3 -3
- package/dist/src/commands/auth.js +3 -3
- package/dist/src/daemon-utils.d.ts +27 -0
- package/dist/src/daemon-utils.js +1 -1
- package/dist/src/daemon.js +1 -1
- package/dist/src/errors.js +1 -1
- package/dist/src/execution.js +2 -2
- package/package.json +1 -1
package/cli-manifest.json
CHANGED
|
@@ -14164,7 +14164,7 @@
|
|
|
14164
14164
|
{
|
|
14165
14165
|
"site": "douyin",
|
|
14166
14166
|
"name": "drafts",
|
|
14167
|
-
"description": "
|
|
14167
|
+
"description": "列出本机浏览器里暂存的图文草稿(抖音图文草稿只存在本地浏览器,换设备/清缓存即失效)",
|
|
14168
14168
|
"access": "read",
|
|
14169
14169
|
"domain": "creator.douyin.com",
|
|
14170
14170
|
"strategy": "cookie",
|
|
@@ -14179,9 +14179,10 @@
|
|
|
14179
14179
|
}
|
|
14180
14180
|
],
|
|
14181
14181
|
"columns": [
|
|
14182
|
-
"aweme_id",
|
|
14183
14182
|
"title",
|
|
14184
|
-
"
|
|
14183
|
+
"text",
|
|
14184
|
+
"images",
|
|
14185
|
+
"scope"
|
|
14185
14186
|
],
|
|
14186
14187
|
"type": "js",
|
|
14187
14188
|
"modulePath": "douyin/drafts.js",
|
|
@@ -20867,7 +20868,9 @@
|
|
|
20867
20868
|
"columns": [
|
|
20868
20869
|
"status",
|
|
20869
20870
|
"detail",
|
|
20870
|
-
"url"
|
|
20871
|
+
"url",
|
|
20872
|
+
"media_id",
|
|
20873
|
+
"account_id"
|
|
20871
20874
|
],
|
|
20872
20875
|
"type": "js",
|
|
20873
20876
|
"modulePath": "instagram/post.js",
|
|
@@ -21822,10 +21825,10 @@
|
|
|
21822
21825
|
{
|
|
21823
21826
|
"site": "jike",
|
|
21824
21827
|
"name": "comment",
|
|
21825
|
-
"description": "
|
|
21828
|
+
"description": "评论即刻帖子(走即刻内部 API,不依赖页面渲染)",
|
|
21826
21829
|
"access": "write",
|
|
21827
21830
|
"domain": "web.okjike.com",
|
|
21828
|
-
"strategy": "
|
|
21831
|
+
"strategy": "cookie",
|
|
21829
21832
|
"browser": true,
|
|
21830
21833
|
"args": [
|
|
21831
21834
|
{
|
|
@@ -21840,7 +21843,7 @@
|
|
|
21840
21843
|
"type": "string",
|
|
21841
21844
|
"required": false,
|
|
21842
21845
|
"positional": true,
|
|
21843
|
-
"help": "
|
|
21846
|
+
"help": "评论内容(含空格时请用引号整体传入)",
|
|
21844
21847
|
"fromFile": true,
|
|
21845
21848
|
"requiredUnlessFile": true
|
|
21846
21849
|
},
|
|
@@ -21854,12 +21857,13 @@
|
|
|
21854
21857
|
],
|
|
21855
21858
|
"columns": [
|
|
21856
21859
|
"status",
|
|
21857
|
-
"message"
|
|
21860
|
+
"message",
|
|
21861
|
+
"comment_id"
|
|
21858
21862
|
],
|
|
21859
21863
|
"type": "js",
|
|
21860
21864
|
"modulePath": "jike/comment.js",
|
|
21861
21865
|
"sourceFile": "jike/comment.js",
|
|
21862
|
-
"navigateBefore":
|
|
21866
|
+
"navigateBefore": "https://web.okjike.com"
|
|
21863
21867
|
},
|
|
21864
21868
|
{
|
|
21865
21869
|
"site": "jike",
|
|
@@ -21930,10 +21934,10 @@
|
|
|
21930
21934
|
{
|
|
21931
21935
|
"site": "jike",
|
|
21932
21936
|
"name": "like",
|
|
21933
|
-
"description": "
|
|
21937
|
+
"description": "点赞即刻帖子(走即刻内部 API,不依赖页面渲染)",
|
|
21934
21938
|
"access": "write",
|
|
21935
21939
|
"domain": "web.okjike.com",
|
|
21936
|
-
"strategy": "
|
|
21940
|
+
"strategy": "cookie",
|
|
21937
21941
|
"browser": true,
|
|
21938
21942
|
"args": [
|
|
21939
21943
|
{
|
|
@@ -21942,6 +21946,12 @@
|
|
|
21942
21946
|
"required": true,
|
|
21943
21947
|
"positional": true,
|
|
21944
21948
|
"help": "帖子 ID"
|
|
21949
|
+
},
|
|
21950
|
+
{
|
|
21951
|
+
"name": "undo",
|
|
21952
|
+
"type": "boolean",
|
|
21953
|
+
"required": false,
|
|
21954
|
+
"help": "取消点赞(改调 unlike)"
|
|
21945
21955
|
}
|
|
21946
21956
|
],
|
|
21947
21957
|
"columns": [
|
|
@@ -21951,7 +21961,7 @@
|
|
|
21951
21961
|
"type": "js",
|
|
21952
21962
|
"modulePath": "jike/like.js",
|
|
21953
21963
|
"sourceFile": "jike/like.js",
|
|
21954
|
-
"navigateBefore":
|
|
21964
|
+
"navigateBefore": "https://web.okjike.com"
|
|
21955
21965
|
},
|
|
21956
21966
|
{
|
|
21957
21967
|
"site": "jike",
|
|
@@ -22551,7 +22561,7 @@
|
|
|
22551
22561
|
{
|
|
22552
22562
|
"site": "juejin",
|
|
22553
22563
|
"name": "article",
|
|
22554
|
-
"description": "发布掘金文章。默认正式发布(需 --category 和 --tags);加 --draft 仅存草稿。正文默认 Markdown
|
|
22564
|
+
"description": "发布掘金文章。默认正式发布(需 --category 和 --tags);加 --draft 仅存草稿。正文默认 Markdown;正文图片与 --cover 封面自动转存至掘金图床(字节 ImageX)。",
|
|
22555
22565
|
"access": "write",
|
|
22556
22566
|
"domain": "juejin.cn",
|
|
22557
22567
|
"strategy": "cookie",
|
|
@@ -22601,6 +22611,12 @@
|
|
|
22601
22611
|
"required": false,
|
|
22602
22612
|
"help": "【正式发布必填】文章摘要,掘金要求约 50-100 字(不做自动截取,必须显式提供)"
|
|
22603
22613
|
},
|
|
22614
|
+
{
|
|
22615
|
+
"name": "cover",
|
|
22616
|
+
"type": "str",
|
|
22617
|
+
"required": false,
|
|
22618
|
+
"help": "封面图(本机绝对路径或图片 URL),自动转存至掘金图床后设为文章封面"
|
|
22619
|
+
},
|
|
22604
22620
|
{
|
|
22605
22621
|
"name": "draft",
|
|
22606
22622
|
"type": "boolean",
|
|
@@ -38640,7 +38656,8 @@
|
|
|
38640
38656
|
"target_type",
|
|
38641
38657
|
"target",
|
|
38642
38658
|
"created_target",
|
|
38643
|
-
"created_url"
|
|
38659
|
+
"created_url",
|
|
38660
|
+
"public_url"
|
|
38644
38661
|
],
|
|
38645
38662
|
"type": "js",
|
|
38646
38663
|
"modulePath": "sohu/article.js",
|
|
@@ -38778,6 +38795,7 @@
|
|
|
38778
38795
|
"type",
|
|
38779
38796
|
"title",
|
|
38780
38797
|
"url",
|
|
38798
|
+
"public_url",
|
|
38781
38799
|
"published_at",
|
|
38782
38800
|
"status",
|
|
38783
38801
|
"views",
|
|
@@ -53108,7 +53126,7 @@
|
|
|
53108
53126
|
{
|
|
53109
53127
|
"site": "zhihu",
|
|
53110
53128
|
"name": "answer",
|
|
53111
|
-
"description": "Answer a Zhihu question. Body is Markdown by default (paragraphs become <p>); pass --html for raw HTML.",
|
|
53129
|
+
"description": "Answer a Zhihu question. Body is Markdown by default (paragraphs become <p>); pass --html for raw HTML. Images (including local file paths) are auto-rehosted to Zhihu.",
|
|
53112
53130
|
"access": "write",
|
|
53113
53131
|
"domain": "www.zhihu.com",
|
|
53114
53132
|
"strategy": "cookie",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import{CommandExecutionError as
|
|
1
|
+
import{CommandExecutionError as W}from"@jackwener/opencli/errors";import{normalizeContent as L}from"./format.js";import{assertLiteralContent as I}from"../content-guard.js";import{inlineLocalImages as y,isLocalImagePath as E,localImageToDataUri as S}from"./images.js";import{PAGE_RUNTIME as F}from"./page-runtime.js";export function selectContent(K,Q,X="auto"){const Z=L(K,{format:X});return Q.outputFormat==="markdown"?Z.markdown:Z.html}export function originReFromHome(K){let Q="";try{Q=new URL(K).host}catch(Z){Q=""}return"^https?://([^/]*\\.)?"+Q.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"(/|$)"}export async function gotoWritePage(K,Q,X){const Z=X||originReFromHome(Q);let O=null;for(let _=0;_<8;_++){await K.goto(Q);await K.wait({time:1});const $=await K.evaluate(`(() => {
|
|
2
2
|
var href = ''; try { href = location.href; } catch (e) {}
|
|
3
3
|
var onSite = new RegExp(${JSON.stringify(Z)}).test(href);
|
|
4
4
|
var cookieOk = true; try { void document.cookie; } catch (e) { cookieOk = false; }
|
|
5
5
|
return { href: href, onSite: onSite, cookieOk: cookieOk };
|
|
6
|
-
})()`);if($&&$.onSite&&$.cookieOk)return;if($)O=$;await K.wait({time:1})}const G=O&&O.href||"",q=new
|
|
7
|
-
`+
|
|
6
|
+
})()`);if($&&$.onSite&&$.cookieOk)return;if($)O=$;await K.wait({time:1})}const G=O&&O.href||"",q=new W(`无法在浏览器中打开已登录的写作页(未落到 ${Q}${G?`,当前停留在 ${G}`:",页面停留在空白页"})。`+"请确认登录该平台的 Chrome 已连接 ppcli 浏览器桥。");q.lastHref=G;q.landedOffSite=/^https?:/i.test(G);throw q}export function buildPublishJs(K,Q,X){return`(async () => {
|
|
7
|
+
`+F+`
|
|
8
8
|
const I = `+JSON.stringify(K)+`;
|
|
9
9
|
const __publish = (`+Q+`);
|
|
10
10
|
`+(X?"const __upload = ("+X+`);
|
|
@@ -24,11 +24,18 @@ if (I.publishParams && typeof I.publishParams.cover === "string" && I.publishPar
|
|
|
24
24
|
if (__cv.uploaded.length) I.publishParams.cover = __cv.uploaded[0].url;
|
|
25
25
|
__t.uploaded = __t.uploaded.concat(__cv.uploaded);
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
let __pub;
|
|
28
|
+
try {
|
|
29
|
+
__pub = await __publish({ title: I.title, content: content, markdown: I.markdown != null ? I.markdown : (I.outputFormat === "markdown" ? __content0 : null), html: I.html != null ? I.html : (I.outputFormat === "html" ? __content0 : null), draftOnly: I.draftOnly, params: I.publishParams }, PP);
|
|
30
|
+
} catch (e) {
|
|
31
|
+
return { ok: false, stage: "publish", unknownOutcome: true, message: String((e && e.message) || e), uploaded: __t.uploaded || [], failed: __t.failed || [] };
|
|
32
|
+
}
|
|
28
33
|
var __upN = (__t.uploaded || []).concat((__pub && __pub.uploaded) || []);
|
|
29
34
|
var __failN = (__t.failed || []).concat((__pub && __pub.failed) || []);
|
|
30
35
|
if (!__pub || __pub.ok === false) {
|
|
31
|
-
return { ok: false, stage: (__pub && __pub.stage) || "publish", status: __pub && __pub.status, message: (__pub && __pub.message) || "publish failed", detail: __pub && __pub.detail, uploaded: __upN, failed: __failN };
|
|
36
|
+
return { ok: false, stage: (__pub && __pub.stage) || "publish", status: __pub && __pub.status, message: (__pub && __pub.message) || "publish failed", detail: __pub && __pub.detail, unknownOutcome: !!(__pub && __pub.unknownOutcome), uploaded: __upN, failed: __failN };
|
|
32
37
|
}
|
|
33
|
-
return { ok: true, id: String(__pub.id == null ? "" : __pub.id), url: __pub.url || "", draft: !!__pub.draft, uploaded: __upN, failed: __failN };
|
|
34
|
-
})()`}export async function publishArticle(K,Q){const{title:X,body:Z,format:O="auto",draftOnly:G=!1,profile:q,publishParams:
|
|
38
|
+
return { ok: true, id: String(__pub.id == null ? "" : __pub.id), url: __pub.url || "", publicUrl: __pub.publicUrl || "", draft: !!__pub.draft, uploaded: __upN, failed: __failN };
|
|
39
|
+
})()`}export async function publishArticle(K,Q){const{title:X,body:Z,format:O="auto",draftOnly:G=!1,profile:q,publishParams:_=null}=Q;if(!q)throw Error("publishArticle: profile is required");if(!q.home)throw Error("publishArticle: profile.home is required");if(typeof q.publish!=="function")throw Error("publishArticle: profile.publish must be a function");I(Z,{fileFlag:"--file"});const $=L(Z,{format:O}),R=q.outputFormat==="markdown"||q.needsMarkdown===!0,j=q.outputFormat==="html"||q.needsHtml===!0,z=R?await y($.markdown):{content:null,missing:[]},A=j?await y($.html):{content:null,missing:[]},J=z.content,N=A.content,H=q.outputFormat==="markdown"?J:N,T=[],U=new Set;for(const V of[...z.missing,...A.missing]){if(U.has(V.src))continue;U.add(V.src);T.push(V)}let Y=_;if(Y&&typeof Y.cover==="string"&&Y.cover){const V=Y.cover.trim();if(/["<>\s]/.test(V))throw new W("封面图路径/URL 含非法字符(引号/尖括号/空白):"+V.slice(0,120));if(E(V))try{const{dataUri:D}=await S(V);Y={...Y,cover:D}}catch(D){throw new W("封面图读取失败:"+V+"("+String(D&&D.message||D)+")")}else Y={...Y,cover:V}}await gotoWritePage(K,q.home,q.originRe);const M={title:X,content:H,markdown:q.needsMarkdown===!0&&q.outputFormat!=="markdown"?J:null,html:q.needsHtml===!0&&q.outputFormat!=="html"?N:null,draftOnly:!!G,outputFormat:q.outputFormat,preprocessConfig:q.preprocessConfig||null,imageSpec:q.image?.spec||null,imageSkip:q.image?.skip||[],imageAllowRelative:q.image?.allowRelative===!0,publishParams:Y},x=typeof q.image?.uploadFn==="function"?q.image.uploadFn.toString():null,C=buildPublishJs(M,q.publish.toString(),x),B=await K.evaluate(C);if(!B||B.ok===!1){const V=B?.stage||"publish",D=B?.status!=null?` (HTTP ${B.status})`:"",k=B?.detail!=null?`;接口原始响应:${typeof B.detail==="string"?B.detail:JSON.stringify(B.detail)}`:"";if(B?.unknownOutcome){const w=new W(`[${V}] 提交请求已发出但未收到平台响应,本次发布结果未知:${B?.message||"网络异常"}${D}${k}`+`
|
|
40
|
+
⚠️ 禁止直接重试本命令——文章可能已经创建成功,重试会发出重复内容。`+`
|
|
41
|
+
正确做法:先用该平台的列表/统计命令(如 stats / lists / articles)按标题回查最近内容,`+"确认未创建后再重发;已创建则直接以该条为准。");w.unknownOutcome=!0;throw w}throw new W(`[${V}] ${B?.message||"发布失败"}${D}${k}`)}return{id:B.id,url:B.url,publicUrl:B.publicUrl||"",draft:B.draft,images:{uploaded:B.uploaded||[],failed:(B.failed||[]).concat(T)}}}export const __test__={selectContent,originReFromHome,buildPublishJs,publishArticle};
|
package/clis/bilibili/article.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as
|
|
1
|
+
import{CliError as A,CommandExecutionError as g}from"@jackwener/opencli/errors";import{cli as d,Strategy as l}from"@jackwener/opencli/registry";import{publishArticle as c}from"../_shared/article/publish.js";import{readFile as u,stat as o}from"node:fs/promises";function i(K){if(!K.execute)throw new A("INVALID_INPUT","此写操作需要 --execute 才会真正提交。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function p(K){const G=typeof K.text==="string"?K.text:void 0,M=typeof K.file==="string"?K.file:void 0;if(G&&M)throw new A("INVALID_INPUT","正文和 --file 只能选一个");let z=G??"";if(M){let Z;try{Z=await o(M)}catch{throw new A("INVALID_INPUT",`文件不存在:${M}`)}if(!Z.isFile())throw new A("INVALID_INPUT",`--file 必须是可读的文本文件:${M}`);let _;try{_=await u(M)}catch{throw new A("INVALID_INPUT",`文件无法读取:${M}`)}try{z=new TextDecoder("utf-8",{fatal:!0}).decode(_)}catch{throw new A("INVALID_INPUT",`文件无法以 UTF-8 解码:${M}`)}}if(!z.trim())throw new A("INVALID_INPUT","正文不能为空");return z}function e(K,G,M,z,Z={}){return[{status:"success",outcome:z,message:K,target_type:G,target:M,...Z}]}export const bilibiliArticleProfile={home:"https://member.bilibili.com/platform/upload/text",originRe:"^https?://([^/]*\\.)?bilibili\\.com(/|$)",outputFormat:"html",preprocessConfig:{removeLinks:!0},image:{uploadFn:async(K,G)=>{var M=G.cookie("bili_jct");if(!M)throw Error("未找到 bili_jct CSRF token,请确认已登录 B站");var z=await fetch(K,{credentials:"omit"});if(!z.ok)throw Error("图片下载失败(HTTP "+z.status+"):"+K.slice(0,120));var Z=await z.blob();function _(L,h){var B=String(h&&h.type||"").toLowerCase(),U=B==="image/png"?"png":B==="image/webp"?"webp":B==="image/gif"?"gif":B==="image/heif"||B==="image/heic"?"heif":"jpg";return L+"."+U}var $=new FormData;$.append("binary",Z,_("image",Z));$.append("csrf",M);var Y=await fetch("https://api.bilibili.com/x/article/creative/article/upcover",{method:"POST",credentials:"include",body:$}),j=await Y.text(),V=null;try{V=JSON.parse(j)}catch(L){}if(!Y.ok||!V||V.code!==0||!V.data||!V.data.url)throw Error("B站图片上传失败:"+(V&&V.message?V.message:j.slice(0,150)));return{url:V.data.url}},skip:["hdslb.com","bilibili.com","biliimg.com"]},publish:async(K,G)=>{var M=G.cookie("bili_jct");if(!M)return{ok:!1,stage:"csrf",status:0,message:"未找到 bili_jct CSRF token,请确认已登录 B站"};var z=K.params||{},Z=z.tid?String(z.tid):"4",_=z.listId!=null?String(z.listId):"0",$=z.bannerUrl?String(z.bannerUrl):"",Y=z.summary?String(z.summary):"";if(!$&&z.coverSrc)try{var j=await fetch(String(z.coverSrc),{credentials:"omit"});if(!j.ok)return{ok:!1,stage:"cover",status:j.status,message:"封面图下载失败(HTTP "+j.status+"):"+String(z.coverSrc).slice(0,120)};var V=await j.blob(),L=new FormData,h=String(V&&V.type||"").toLowerCase(),B=h==="image/png"?"png":h==="image/webp"?"webp":h==="image/gif"?"gif":h==="image/heif"||h==="image/heic"?"heif":"jpg";L.append("binary",V,"cover."+B);L.append("csrf",M);var U=await fetch("https://api.bilibili.com/x/article/creative/article/upcover",{method:"POST",credentials:"include",body:L}),D=await U.text(),S=null;try{S=JSON.parse(D)}catch(Q){}if(!U.ok||!S||S.code!==0||!S.data||!S.data.url)return{ok:!1,stage:"cover",status:U.status,message:"封面图上传失败:"+(S&&S.message?S.message:D.slice(0,150))};$=S.data.url}catch(Q){return{ok:!1,stage:"cover",status:0,message:"封面图上传异常:"+String(Q&&Q.message||Q)}}var E=z.tags?String(z.tags):"",J=z.original===!1?!1:!0,F=0;try{var T=document.createElement("div");T.innerHTML=K.content||"";F=(T.textContent||T.innerText||"").replace(/\s+/g,"").length}catch(Q){F=0}var W=new URLSearchParams;W.append("title",K.title);W.append("content",K.content);W.append("csrf",M);W.append("save","0");W.append("tid",Z);if(z.category)W.append("category",String(z.category));if(_)W.append("list_id",_);if($)W.append("banner_url",$);if(Y)W.append("summary",Y);if(E)W.append("tags",E);W.append("reprint",J?"0":"1");W.append("original",J?"1":"0");W.append("media_id","0");W.append("spoiler","0");if(F)W.append("words",String(F));var I=await fetch("https://api.bilibili.com/x/article/creative/draft/addupdate",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",Origin:"https://member.bilibili.com",Referer:"https://member.bilibili.com/"},body:W.toString()}),n=await I.text(),H=null;try{H=JSON.parse(n)}catch(Q){}if(!I.ok||!H)return{ok:!1,stage:"draft",status:I.status,message:n.slice(0,300)};if(H.code!==0||!H.data||!H.data.aid)return{ok:!1,stage:"draft",status:I.status,message:(H.message||"保存草稿失败")+"(code="+H.code+")"};var q=String(H.data.aid);if(K.draftOnly){var P="https://member.bilibili.com/platform/upload/text/edit?aid="+q;return{ok:!0,draft:!0,id:q,url:P}}if(!z.category)return{ok:!1,stage:"category",status:0,message:"正式发布缺少必填的专栏分类 category(草稿 aid="+q+" 已存)。请用 `bilibili categories` 取合法分类 id 后传 --category。"};var y=[];try{var k=await fetch("https://api.bilibili.com/x/article/categories",{credentials:"include"}),N=await k.json();if(!N||N.code!==0||!Array.isArray(N.data))return{ok:!1,stage:"category",status:k.status,message:"获取 B站专栏分类失败,无法校验 category(草稿 aid="+q+" 已存):"+(N&&N.message||"code="+(N&&N.code))};(function Q(m){m.forEach(function(x){var v=Array.isArray(x.children)?x.children:[];y.push({id:String(x.id),name:x.name||"",leaf:v.length===0});if(v.length)Q(v)})})(N.data)}catch(Q){return{ok:!1,stage:"category",status:0,message:"获取 B站专栏分类异常,无法校验 category(草稿 aid="+q+" 已存):"+String(Q&&Q.message||Q)}}var R=y.find(function(Q){return Q.id===String(z.category)});if(!R){var b=y.filter(function(Q){return Q.leaf}).map(function(Q){return Q.id+"="+Q.name}).join(" / ");return{ok:!1,stage:"category",status:0,message:"专栏分类 id「"+z.category+"」不存在(草稿 aid="+q+" 已存)。合法叶子分类:"+(b||"(无)")}}if(!R.leaf)return{ok:!1,stage:"category",status:0,message:"专栏分类 id「"+z.category+"」("+R.name+")是父分类,请改用其下的叶子分类 id(草稿 aid="+q+" 已存)。"};var X=new URLSearchParams;X.append("aid",q);X.append("title",K.title);X.append("content",K.content);X.append("category",String(z.category));X.append("list_id",_);X.append("tid",Z);if($)X.append("banner_url",$);if(Y)X.append("summary",Y);if(E)X.append("tags",E);X.append("reprint",J?"0":"1");X.append("original",J?"1":"0");X.append("media_id","0");X.append("spoiler","0");if(F)X.append("words",String(F));X.append("csrf",M);var C=await fetch("https://api.bilibili.com/x/article/creative/article/submit",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",Origin:"https://member.bilibili.com",Referer:"https://member.bilibili.com/"},body:X.toString()}),f=await C.text(),O=null;try{O=JSON.parse(f)}catch(Q){}if(C.status===412)return{ok:!1,stage:"submit",status:412,message:"B站风控拦截了发表(HTTP 412),草稿已保存(aid="+q+"),可在创作中心改后重试。"};if(!C.ok||!O)return{ok:!1,stage:"submit",status:C.status,message:"正式发布失败:"+f.slice(0,300)+"(草稿 aid="+q+" 已存)"};if(O.code!==0)return{ok:!1,stage:"submit",status:C.status,message:(O.message||"正式发布失败")+"(code="+O.code+",草稿 aid="+q+" 已存)"};var w=O.data&&O.data.aid!=null?String(O.data.aid):q;return{ok:!0,draft:!1,id:w,url:"https://www.bilibili.com/read/cv"+w}}};d({site:"bilibili",name:"article",access:"write",description:"发布 B站专栏长文(opus/article)。默认正式发布,加 --draft 仅存草稿。正文默认 Markdown,图片自动转存至 B站图床。正式发布必填 --category(合法值用 `bilibili categories` 取)。",domain:"member.bilibili.com",strategy:l.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"正文(默认 Markdown;加 --html 则作 HTML 处理)"},{name:"file",help:"正文文件路径(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"将正文视为原始 HTML 而非 Markdown"},{name:"category",help:"专栏分类 id(正式发布必填,叶子分类)。合法值用 `bilibili categories` 列举,禁止臆造。"},{name:"tid",type:"int",default:4,help:"封面版式:4=单图 / 3=三图(按封面图数量定,默认 4)"},{name:"cover",help:"封面图源 URL(可空);会先经 upcover 上传到 B站图床再作 banner_url"},{name:"banner-url",help:"封面图的 B站 CDN url(可空,已有则直接用,跳过 upcover 上传)"},{name:"summary",help:"文章摘要/digest(可空)"},{name:"tags",help:"标签,逗号拼接的自由文本字符串(可空,非闭合词表)"},{name:"reprint",type:"boolean",help:"标记为转载(默认原创);加此参数则 original=0、reprint=1"},{name:"list-id",type:"int",default:0,help:"文集编号(默认 0=不加入文集)"},{name:"draft",type:"boolean",help:"仅保存草稿,不正式发布"},{name:"execute",type:"boolean",help:"实际提交。不加此参数时命令拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(K,G)=>{if(!K)throw new g("B站专栏发布需要浏览器会话");i(G);const M=String(G.title??"").trim();if(!M)throw new A("INVALID_INPUT","文章标题不能为空");const z=await p(G),Z=Boolean(G.draft),_=typeof G.category==="string"?G.category.trim():"";if(!Z&&!_)throw new A("INVALID_INPUT","正式发布 B站专栏必须指定 --category(专栏分类 id)。请先用 `bilibili categories` 列举合法分类 id;如只想存草稿请加 --draft。");const $={category:_||void 0,tid:G.tid!=null?String(G.tid):"4",coverSrc:typeof G.cover==="string"?G.cover.trim():"",bannerUrl:typeof G["banner-url"]==="string"?G["banner-url"].trim():"",summary:typeof G.summary==="string"?G.summary:"",tags:typeof G.tags==="string"?G.tags.trim():"",original:!G.reprint,listId:G["list-id"]!=null?String(G["list-id"]):"0"},Y=await c(K,{title:M,body:z,format:G.html?"html":"markdown",draftOnly:Z,profile:bilibiliArticleProfile,publishParams:$}),j=Y.images.uploaded.length|0,V=Y.images.failed.length|0;let L=Y.draft?"已保存 B站专栏草稿(需在创作中心手动发布)":"已正式提交 B站专栏发表(可能进入审核态,请用 `bilibili drafts` 或访问返回 URL 回查)";if(j||V)L+=`·图片:${j} 张已转存${V?`,${V} 张失败`:""}`;return e(L,"article","",Y.draft?"draft":"created",{created_target:(Y.draft?"draft:":"article:")+Y.id,created_url:Y.url})}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as j,Strategy as G}from"@jackwener/opencli/registry";import{ArgumentError as Q,AuthRequiredError as
|
|
1
|
+
import{cli as j,Strategy as G}from"@jackwener/opencli/registry";import{ArgumentError as Q,AuthRequiredError as q,CommandExecutionError as J,EmptyResultError as I}from"@jackwener/opencli/errors";import{apiGet as Z,resolveBvid as P}from"./utils.js";const U=50;function L(z,F){return z===-101||z===-403||/登录|账号|权限|forbidden|permission|login/i.test(String(F??""))}function _(z){const F=z==null?20:z,H=Number(F);if(!Number.isInteger(H)||H<=0||H>U)throw new Q(`bilibili comments limit must be an integer between 1 and ${U}`);return H}const Y={hot:3,time:2};function O(z){const F=z==null?"hot":String(z).trim().toLowerCase(),H=Y[F];if(!H)throw new Q(`bilibili comments sort must be one of: ${Object.keys(Y).join(", ")}`);return H}function T(z){if(z==null)return null;const F=Number(z);if(!Number.isInteger(F)||F<=0)throw new Q("bilibili comments parent must be a positive integer rpid");return F}function h(z,F){if(!z||typeof z!=="object"||Array.isArray(z)||!Object.hasOwn(z,"code"))throw new J(`Bilibili ${F} API returned a malformed payload`);if(z.code!==0){const H=z.message??"unknown error";if(L(z.code,H))throw new q("bilibili.com",`Bilibili ${F} API requires login or permission: ${H} (${z.code})`);throw new J(`Bilibili ${F} API failed: ${H} (${z.code})`)}return z.data}function M(z,F){if(!z||typeof z!=="object"||Array.isArray(z))throw new J(`Bilibili ${F} API returned malformed data`);if(!Object.hasOwn(z,"replies"))throw new J(`Bilibili ${F} API did not return replies`);if(z.replies===null)return[];if(!Array.isArray(z.replies))throw new J(`Bilibili ${F} API returned malformed replies`);return z.replies}function S(z,F){if(!z||typeof z!=="object"||Array.isArray(z))throw new J(`Bilibili comments reply ${F+1} was malformed`);const H=String(z.rpid??"").trim();if(!H)throw new J(`Bilibili comments reply ${F+1} was missing rpid`);const N=Number(z.ctime);if(!Number.isFinite(N))throw new J(`Bilibili comments reply ${F+1} was missing ctime`);return{rank:F+1,rpid:H,author:String(z.member?.uname??""),text:String(z.content?.message??"").replace(/\n/g," ").trim(),likes:z.like??0,replies:z.rcount??0,time:new Date(N*1000).toISOString().slice(0,16).replace("T"," ")}}j({site:"bilibili",name:"comments",access:"read",description:"获取 B站视频评论(官方 API;用 --parent <rpid> 读取某条评论下的「楼中楼」回复;--sort time 按最新排序,发评论后回查用)",domain:"www.bilibili.com",strategy:G.COOKIE,args:[{name:"bvid",required:!0,positional:!0,help:"Video BV ID (e.g. BV1WtAGzYEBm)"},{name:"parent",type:"int",help:"rpid of a comment — fetch the replies under it instead of top-level comments"},{name:"sort",default:"hot",help:"Top-level order: hot (default) or time (newest first; use to verify a just-posted comment)"},{name:"limit",type:"int",default:20,help:"Number of comments (max 50)"}],columns:["rank","rpid","author","text","likes","replies","time"],func:async(z,F)=>{if(!z)throw new J("Browser session required for bilibili comments");let H;try{H=await P(F.bvid)}catch(W){throw new Q(`Cannot resolve Bilibili BV ID from input: ${String(F.bvid??"")}`,W instanceof Error?W.message:String(W))}const N=_(F.limit),K=T(F.parent),B=O(F.sort),D=await Z(z,"/x/web-interface/view",{params:{bvid:H}}),V=h(D,"view")?.aid;if(!V)throw new J(`Cannot resolve aid for bvid: ${H}`);const X=K!=null?await Z(z,"/x/v2/reply/reply",{params:{oid:V,type:1,root:K,pn:1,ps:N}}):await Z(z,"/x/v2/reply/main",{params:{oid:V,type:1,mode:B,ps:N},signed:!0}),$=K!=null?"reply thread":"reply main",f=M(h(X,$),$);if(f.length===0)throw new I(K!=null?`bilibili comment replies: ${K}`:`bilibili comments: ${H}`,K!=null?`该评论(rpid ${K})下当前没有可读到的回复。`+"用于去重时:读不到回复即代表本账号没在该楼回复过,可以安全发布。":`该视频(${H})当前没有可见评论(评论区为空或已关闭)。`+"用于去重时:这就是「本账号没在此视频评论过」的确证,可以安全发布。");return f.slice(0,N).map(S)}});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const IMAGE_DRAFT_KEY_RE=/^publish_cache:\d+:image$/,READ_IMAGE_DRAFT_JS=`
|
|
2
|
+
(() => {
|
|
3
|
+
const out = [];
|
|
4
|
+
try {
|
|
5
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
6
|
+
const k = localStorage.key(i);
|
|
7
|
+
if (!k || !/^publish_cache:\\d+:image$/.test(k)) continue;
|
|
8
|
+
out.push({ key: k, value: localStorage.getItem(k) || '' });
|
|
9
|
+
}
|
|
10
|
+
} catch (e) {}
|
|
11
|
+
return out;
|
|
12
|
+
})()
|
|
13
|
+
`;export function matchImageDraft(L,q){const z=String(q?.title||"").replace(/\s+/g,""),C=String(q?.content||"").split("#")[0].replace(/\s+/g,"").slice(0,20);for(const j of L||[]){if(!j||!IMAGE_DRAFT_KEY_RE.test(j.key||""))continue;let f=null;try{f=JSON.parse(j.value||"")}catch{continue}if(!f)continue;const N=String(f.title||"").replace(/\s+/g,""),O=String(f.text||"").replace(/\s+/g,""),P=!!z&&N===z,Q=!!C&&O.includes(C);if(P||Q)return{hit:!0,key:j.key,title:f.title||""}}return{hit:!1}}
|
package/clis/douyin/drafts.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as
|
|
1
|
+
import{cli as B,Strategy as C}from"@jackwener/opencli/registry";import{READ_IMAGE_DRAFT_JS as H,matchImageDraft as K}from"./_shared/draft-cache.js";B({site:"douyin",name:"drafts",access:"read",description:"列出本机浏览器里暂存的图文草稿(抖音图文草稿只存在本地浏览器,换设备/清缓存即失效)",domain:"creator.douyin.com",strategy:C.COOKIE,browser:!0,args:[{name:"limit",type:"int",default:20}],columns:["title","text","images","scope"],func:async(q,x)=>{await q.goto("https://creator.douyin.com/creator-micro/content/upload?default-tab=3");const z=await q.evaluate(H),u=[];for(const v of z||[]){let b=null;try{b=JSON.parse(v.value||"")}catch{continue}if(!b)continue;u.push({title:b.title||"",text:String(b.text||"").slice(0,120),images:Array.isArray(b.files)?b.files.length:0,scope:v.key})}return u.slice(0,x.limit)}});export const __test__={matchImageDraft:K};
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as v from"node:fs";import*as N from"node:path";import{cli as h,Strategy as d}from"@jackwener/opencli/registry";import{ArgumentError as $,CommandExecutionError as W}from"@jackwener/opencli/errors";import{assertLiteralContent as u}from"../_shared/content-guard.js";import{setInputFiles as T}from"../_shared/file-inject.js";import{browserFetch as m}from"./_shared/browser-fetch.js";import{READ_IMAGE_DRAFT_JS as c,matchImageDraft as E}from"./_shared/draft-cache.js";const l="https://creator.douyin.com/creator-micro/content/upload?default-tab=3",x=35,L=20,w=1000,n=2500,o={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".bmp":"image/bmp",".webp":"image/webp",".tif":"image/tiff",".tiff":"image/tiff"},M='input[type="file"][accept*="image"]',C=['input[placeholder="添加作品标题"]','input[placeholder*="标题"]'],i='input[placeholder="添加作品标题"]',P='.zone-container[contenteditable="true"]';function p(q){return q.map((K)=>{const z=N.resolve(K);if(!v.existsSync(z))throw new $(`图片文件不存在: ${z}`);const Z=N.extname(z).toLowerCase();if(!o[Z])throw new $(`不支持的图片格式 "${Z}",支持: jpg, png, bmp, webp, tif`);return z})}async function s(q,K=15000){const z=500,Z=Math.max(1,Math.ceil(K/z));for(let V=0;V<Z;V++){if(await q.evaluate(`(() => !!document.querySelector(${JSON.stringify(M)}))()`))return!0;if(V<Z-1)await q.wait({time:z/1000})}return!1}async function g(q,K){if(!await s(q))return{ok:!1,error:"上传页未渲染出图片输入框(等待 15s 超时)"};try{await T(q,M,K);return{ok:!0}}catch(Z){return{ok:!1,error:Z instanceof Error?Z.message:String(Z)}}}async function r(q,K=20000){const z=500,Z=Math.ceil(K/z);for(let V=0;V<Z;V++){if(await q.evaluate(`(() => {
|
|
2
2
|
const onEditor = location.href.includes('/content/post/image');
|
|
3
|
-
const titleReady = ${JSON.stringify(
|
|
3
|
+
const titleReady = ${JSON.stringify(C)}.some((s) => !!document.querySelector(s));
|
|
4
4
|
return onEditor && titleReady;
|
|
5
|
-
})()`))return!0;await q.wait({time:z/1000})}return!1}async function
|
|
5
|
+
})()`))return!0;await q.wait({time:z/1000})}return!1}async function t(q,K){await q.typeText(i,K);await q.wait({time:0.3});const z=await q.evaluate(`
|
|
6
6
|
((selectors) => {
|
|
7
7
|
const el = selectors.map((s) => document.querySelector(s)).find(Boolean);
|
|
8
8
|
return el ? (el.value || '') : '';
|
|
9
|
-
})(${JSON.stringify(
|
|
10
|
-
`);if(z.trim()!==
|
|
9
|
+
})(${JSON.stringify(C)})
|
|
10
|
+
`);if(z.trim()!==K){await q.screenshot({path:"/tmp/douyin_image_title_debug.png"});throw new W(`填写标题失败,期望 "${K}",实际 "${z}"。截图: /tmp/douyin_image_title_debug.png`)}}async function e(q,K){await q.typeText(P,K);await q.wait({time:0.5});const z=await q.evaluate(`
|
|
11
11
|
((selector) => {
|
|
12
12
|
const el = Array.from(document.querySelectorAll(selector)).find((n) => n && n.offsetParent !== null);
|
|
13
13
|
return el ? (el.innerText || el.textContent || '').replace(/\\u200b/g, '').trim() : '';
|
|
14
|
-
})(${JSON.stringify(
|
|
15
|
-
`),K
|
|
14
|
+
})(${JSON.stringify(P)})
|
|
15
|
+
`),Z=K.split("#")[0].replace(/\s+/g,"").trim(),V=z.replace(/\s+/g,"");if(Z&&!V.includes(Z.slice(0,Math.min(Z.length,20)))){await q.screenshot({path:"/tmp/douyin_image_body_debug.png"});throw new W(`正文写入校验失败,实际读到 "${z.slice(0,80)}"。截图: /tmp/douyin_image_body_debug.png`)}}async function a(q,K){const z=K?["暂存离开"]:["发布"];return await q.evaluate(`
|
|
16
16
|
((labels) => {
|
|
17
17
|
const isVisible = (el) => {
|
|
18
18
|
if (!el || el.offsetParent === null) return false;
|
|
@@ -28,7 +28,7 @@ import*as y from"node:fs";import*as N from"node:path";import{cli as h,Strategy a
|
|
|
28
28
|
}
|
|
29
29
|
return { ok: false, seen: btns.filter(isVisible).map((b) => norm(b.innerText || b.textContent)).filter(Boolean).slice(0, 12) };
|
|
30
30
|
})(${JSON.stringify(z)})
|
|
31
|
-
`)}h({site:"douyin",name:"publish-image",access:"write",description:"发布图文(图集)到抖音(UI 自动化,最多 35 张图;话题以 #词 写进正文)",domain:"creator.douyin.com",strategy:d.COOKIE,browser:!0,navigateBefore:!1,defaultWindowMode:"foreground",args:[{name:"content",required:!1,positional:!0,help:"正文(字面文本,不展开 @文件 引用;长正文用 --file)"},{name:"file",required:!1,help:"从本机文件读取正文(UTF-8),与位置参数 <content> 二选一"},{name:"title",required:!0,help:"作品标题(≤20字)"},{name:"images",required:!0,help:"图片路径,逗号分隔,最多35张 (jpg/png/bmp/webp/tif)"},{name:"topics",required:!1,help:"话题标签,逗号分隔,不含 # 号"},{name:"draft",type:"bool",default:!1,help:"暂存为草稿,不直接发布"}],columns:["status","detail"],func:async(q,
|
|
31
|
+
`)}h({site:"douyin",name:"publish-image",access:"write",description:"发布图文(图集)到抖音(UI 自动化,最多 35 张图;话题以 #词 写进正文)",domain:"creator.douyin.com",strategy:d.COOKIE,browser:!0,navigateBefore:!1,defaultWindowMode:"foreground",args:[{name:"content",required:!1,positional:!0,help:"正文(字面文本,不展开 @文件 引用;长正文用 --file)"},{name:"file",required:!1,help:"从本机文件读取正文(UTF-8),与位置参数 <content> 二选一"},{name:"title",required:!0,help:"作品标题(≤20字)"},{name:"images",required:!0,help:"图片路径,逗号分隔,最多35张 (jpg/png/bmp/webp/tif)"},{name:"topics",required:!1,help:"话题标签,逗号分隔,不含 # 号"},{name:"draft",type:"bool",default:!1,help:"暂存为草稿,不直接发布"}],columns:["status","detail"],func:async(q,K)=>{if(!q)throw Error("Browser page required");const z=String(K.title??"").trim(),Z=K.file?String(K.file).trim():"";let V=String(K.content??"").trim();if(Z){if(V)throw new $("正文位置参数和 --file 只能二选一");const Q=N.resolve(Z);if(!v.existsSync(Q))throw new $(`--file 指向的文件不存在: ${Q}`);V=v.readFileSync(Q,"utf-8").trim();if(!V)throw new $(`--file 指向的文件内容为空: ${Q}`)}const G=String(K.images??"").split(",").map((Q)=>Q.trim()).filter(Boolean),X=K.topics?String(K.topics).split(",").map((Q)=>Q.trim().replace(/^#/,"")).filter(Boolean):[],B=Boolean(K.draft);if(!z)throw new $("--title 必填");if(z.length>L)throw new $(`标题 ${z.length} 字,需 ≤ ${L} 字`);if(!V)throw new $("缺少正文:给位置参数 <content>,或用 --file <本机文件路径>");u(V,{fileFlag:"--file"});if(V.length>w)throw new $(`正文 ${V.length} 字,需 ≤ ${w} 字`);if(G.length===0)throw new $("至少提供 1 张图片(--images)");if(G.length>x)throw new $(`图片过多: ${G.length}(最多 ${x} 张)`);const O=p(G),b=X.length?`${V} ${X.map((Q)=>`#${Q}`).join(" ")} `:V;await q.goto(l);let j="";for(let Q=0;Q<30;Q++){await q.wait({time:0.5});j=await q.evaluate("() => location.href");if(j.includes("creator.douyin.com/creator-micro/content"))break}if(!j.includes("creator.douyin.com")){await q.screenshot({path:"/tmp/douyin_image_redirect_debug.png"});throw new W(`被重定向离开创作者中心(当前 ${j})——登录态可能已过期。`+"用 ppcli douyin login 重新登录。截图: /tmp/douyin_image_redirect_debug.png")}const D=await g(q,O);if(!D.ok){await q.screenshot({path:"/tmp/douyin_image_upload_debug.png"});throw new W(`图片上传失败: ${D.error??"unknown"}。截图: /tmp/douyin_image_upload_debug.png`)}if(!await r(q)){await q.screenshot({path:"/tmp/douyin_image_form_debug.png"});throw new W("上传后编辑页未就绪(标题输入框未出现),页面结构可能变化。截图: /tmp/douyin_image_form_debug.png")}await q.wait({time:n/1000});await t(q,z);await q.wait({time:0.4});await e(q,b);await q.wait({time:0.6});const U=await a(q,B);if(!U?.ok){await q.screenshot({path:"/tmp/douyin_image_submit_debug.png"});throw new W(`找不到「${B?"暂存离开":"发布"}」按钮。可见按钮: ${(U?.seen||[]).join(" | ")||"无"}。截图: /tmp/douyin_image_submit_debug.png`)}const A=B?["暂存成功","保存成功","存入草稿"]:["发布成功","提交成功"];let Y=j,F=!1,y="";for(let Q=0;Q<24;Q++){await q.wait({time:0.5});const J=await q.evaluate(`
|
|
32
32
|
((markers) => {
|
|
33
33
|
const url = location.href;
|
|
34
34
|
let toast = '';
|
|
@@ -39,5 +39,7 @@ import*as y from"node:fs";import*as N from"node:path";import{cli as h,Strategy a
|
|
|
39
39
|
if (text && text.length <= 40 && markers.some((m) => text.includes(m))) { toast = text; break; }
|
|
40
40
|
}
|
|
41
41
|
return { url, toast };
|
|
42
|
-
})(${JSON.stringify(
|
|
43
|
-
`);Y=
|
|
42
|
+
})(${JSON.stringify(A)})
|
|
43
|
+
`);Y=J?.url??Y;if(J?.toast){y=J.toast;break}if(Y.includes("/content/manage")||B&&Y.includes("/content/draft")){F=!0;break}}let H=null;if(!F&&!y&&Y.includes("creator.douyin.com"))try{if(B){const Q=await q.evaluate(c),J=E(Q,{title:z,content:V});H=J.hit?J:null}else{const J=await m(q,"GET","https://creator.douyin.com/janus/douyin/creator/pc/work_list?page_size=20&page_num=1&status=0"),f=J.aweme_list??J.data?.work_list??[],k=V.replace(/\s+/g,"").slice(0,20),_=Date.now()/1000;H=f.find((R)=>{const I=String(R.desc??R.title??"").replace(/\s+/g,""),S=Number(R.create_time??0);return S>0&&_-S<900&&(I.includes(z.replace(/\s+/g,""))||k&&I.includes(k))})??null}}catch{}if(!F&&!y&&!H){await q.screenshot({path:"/tmp/douyin_image_verify_debug.png"});throw new W(B?"暂存结果无法确认:未见成功提示,本机草稿缓存里也没找到本次内容——大概率没存下,重试是安全的。"+`当前 URL: ${Y}。截图: /tmp/douyin_image_verify_debug.png`:"发布结果无法确认:未跳转作品管理页、未见成功提示,作品列表回查也没找到本次内容。"+`
|
|
44
|
+
⚠️ 禁止直接重试——发布请求可能已经打到抖音,重试会发出重复作品。`+"\n正确做法:先用 `ppcli douyin videos` 按标题回查最近作品,确认没有再重发。"+`
|
|
45
|
+
当前 URL: ${Y}。截图: /tmp/douyin_image_verify_debug.png`)}return[{status:`✅ ${B?"暂存成功":"发布成功"}`,detail:[`"${z}"`,`${O.length}张图片`,X.length?`话题: ${X.map((Q)=>`#${Q}`).join(" ")}`:"",H?H.aweme_id?`aweme_id: ${H.aweme_id}(作品列表回查坐实)`:"本机草稿缓存回查坐实":"",F?Y:y].filter(Boolean).join(" · ")}]}});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as B from"node:crypto";import*as U from"node:fs";import*as R from"node:os";import*as V from"node:path";import{spawnSync as z}from"node:child_process";import{CommandExecutionError as K}from"@jackwener/opencli/errors";import{instagramPrivateApiFetch as j}from"./protocol-capture.js";import{buildReadInstagramRuntimeInfoJs as h}from"./runtime-info.js";export{buildReadInstagramRuntimeInfoJs,extractInstagramRuntimeInfo,resolveInstagramRuntimeInfo}from"./runtime-info.js";const C=0.8,M=1.91,D=0.5625,T=0.75,S="FFFFFF",l="https://www.instagram.com/",u="/api/v1/|/graphql/",k=2,v=2,w=20,d=2000,I=15000,n="19ce5f445dbfd9d29c59dc2a78c616a7fc090a8e018b9267bc4240a30244c53b",o="4",p={manufacturer:"samsung",model:"SM-G930F",android_version:24,android_release:"7.0"};export function markPublishOutcomeUncertain(Q){if(Q&&typeof Q==="object")Q.instagramPublishUncertain=!0;return Q}export function isPublishOutcomeUncertain(Q){return Boolean(Q&&typeof Q==="object"&&Q.instagramPublishUncertain)}export function derivePrivateApiContextFromCapture(Q){for(let X=Q.length-1;X>=0;X-=1){const Z=Q[X]?.requestHeaders??{},$={asbdId:String(Z["X-ASBD-ID"]||""),csrfToken:String(Z["X-CSRFToken"]||""),igAppId:String(Z["X-IG-App-ID"]||""),igWwwClaim:String(Z["X-IG-WWW-Claim"]||""),instagramAjax:String(Z["X-Instagram-AJAX"]||""),webSessionId:String(Z["X-Web-Session-ID"]||"")};if($.asbdId&&$.csrfToken&&$.igAppId&&$.igWwwClaim&&$.instagramAjax&&$.webSessionId)return $}return null}function i(Q){const X={};for(let Z=Q.length-1;Z>=0;Z-=1){const $=Q[Z]?.requestHeaders??{};if(!X.asbdId&&$["X-ASBD-ID"])X.asbdId=String($["X-ASBD-ID"]);if(!X.csrfToken&&$["X-CSRFToken"])X.csrfToken=String($["X-CSRFToken"]);if(!X.igAppId&&$["X-IG-App-ID"])X.igAppId=String($["X-IG-App-ID"]);if(!X.igWwwClaim&&$["X-IG-WWW-Claim"])X.igWwwClaim=String($["X-IG-WWW-Claim"]);if(!X.instagramAjax&&$["X-Instagram-AJAX"])X.instagramAjax=String($["X-Instagram-AJAX"]);if(!X.webSessionId&&$["X-Web-Session-ID"])X.webSessionId=String($["X-Web-Session-ID"])}return X}export function deriveInstagramJazoest(Q){if(!Q)return"";return`2${Array.from(Q).reduce((Z,$)=>Z+$.charCodeAt(0),0)}`}function r(Q){return new Promise((X)=>setTimeout(X,Q))}function g(Q){const X=Q instanceof Error?Q.message:String(Q);return/fetch failed|network|socket hang up|econnreset|etimedout/i.test(X)}function s(Q,X){return Q.find((Z)=>Z.name===X)?.value||""}export async function resolveInstagramPrivatePublishConfig(Q){let X;for(let Z=0;Z<k;Z+=1)try{if(typeof Q.startNetworkCapture==="function")await Q.startNetworkCapture(u);await Q.goto(`${l}?__opencli_private_probe=${Date.now()}`);await Q.wait({time:2});const[$,q,F]=await Promise.all([Q.getCookies({domain:"instagram.com"}),Q.evaluate(h()),typeof Q.readNetworkCapture==="function"?Q.readNetworkCapture():Promise.resolve([])]),O=Array.isArray(F)?F:[],G=derivePrivateApiContextFromCapture(O)??i(O),N=q?.csrfToken||s($,"csrftoken")||G.csrfToken||"",J=q?.appId||G.igAppId||"",W=q?.instagramAjax||G.instagramAjax||"";if(!N)throw new K("Instagram private route could not derive CSRF token from browser session");if(!J)throw new K("Instagram private route could not derive X-IG-App-ID from instagram runtime");if(!W)throw new K("Instagram private route could not derive X-Instagram-AJAX from instagram runtime");const H=G.asbdId||"",Y=G.igWwwClaim||"",y=G.webSessionId||"";return{apiContext:{asbdId:H,csrfToken:N,igAppId:J,igWwwClaim:Y,instagramAjax:W,webSessionId:y},jazoest:deriveInstagramJazoest(N)}}catch($){X=$;if(!g($)||Z>=k-1)throw $}throw X instanceof Error?X:Error(String(X))}export function buildConfigureBody(Q){const X=new URLSearchParams;X.set("archive_only","false");X.set("caption",Q.caption);X.set("clips_share_preview_to_feed","1");X.set("disable_comments","0");X.set("disable_oa_reuse","false");X.set("igtv_share_preview_to_feed","1");X.set("is_meta_only_post","0");X.set("is_unified_video","1");X.set("like_and_view_counts_disabled","0");X.set("media_share_flow","creation_flow");X.set("share_to_facebook","");X.set("share_to_fb_destination_type","USER");X.set("source_type","library");X.set("upload_id",Q.uploadId);X.set("video_subtitles_enabled","0");X.set("jazoest",Q.jazoest);return X.toString()}export function buildConfigureSidecarPayload(Q){return{archive_only:!1,caption:Q.caption,children_metadata:Q.uploadIds.map((X)=>({upload_id:X})),client_sidecar_id:Q.clientSidecarId,disable_comments:"0",is_meta_only_post:!1,is_open_to_public_submission:!1,like_and_view_counts_disabled:0,media_share_flow:"creation_flow",share_to_facebook:"",share_to_fb_destination_type:"USER",source_type:"library",jazoest:Q.jazoest}}function a(Q){switch(V.extname(Q).toLowerCase()){case".png":return"image/png";case".webp":return"image/webp";case".jpg":case".jpeg":default:return"image/jpeg"}}function t(Q){if(Q.length<24)return null;if(Q.subarray(0,8).toString("hex").toUpperCase()!=="89504E470D0A1A0A")return null;if(Q.subarray(12,16).toString("ascii")!=="IHDR")return null;return{width:Q.readUInt32BE(16),height:Q.readUInt32BE(20)}}function e(Q){if(Q.length<4||Q[0]!==255||Q[1]!==216)return null;let X=2;while(X+9<Q.length){if(Q[X]!==255){X+=1;continue}const Z=Q[X+1];X+=2;if(Z===216||Z===217)continue;if(X+2>Q.length)break;const $=Q.readUInt16BE(X);if($<2||X+$>Q.length)break;if(Z>=192&&Z<=207&&![196,200,204].includes(Z)&&$>=7)return{height:Q.readUInt16BE(X+3),width:Q.readUInt16BE(X+5)};X+=$}return null}function QQ(Q){if(Q.length<30)return null;if(Q.subarray(0,4).toString("ascii")!=="RIFF"||Q.subarray(8,12).toString("ascii")!=="WEBP")return null;const X=Q.subarray(12,16).toString("ascii");if(X==="VP8X"&&Q.length>=30)return{width:1+Q.readUIntLE(24,3),height:1+Q.readUIntLE(27,3)};if(X==="VP8 "&&Q.length>=30)return{width:Q.readUInt16LE(26)&16383,height:Q.readUInt16LE(28)&16383};if(X==="VP8L"&&Q.length>=25){const Z=Q.readUInt32LE(21);return{width:(Z&16383)+1,height:(Z>>14&16383)+1}}return null}function XQ(Q,X){const Z=V.extname(Q).toLowerCase(),$=Z===".png"?t(X):Z===".webp"?QQ(X):e(X);if(!$)throw new K(`Failed to read image dimensions for ${Q}`);return $}export function readImageAsset(Q){const X=U.readFileSync(Q),{width:Z,height:$}=XQ(Q,X);return{filePath:Q,fileName:V.basename(Q),mimeType:a(Q),width:Z,height:$,byteLength:X.length,bytes:X}}export function isInstagramFeedAspectRatioAllowed(Q,X){const Z=Q/Math.max(X,1);return Z>=C-0.001&&Z<=M+0.001}export function getInstagramFeedNormalizedDimensions(Q,X){const Z=Q/Math.max(X,1);if(Z<C)return{width:Math.ceil(X*C),height:X};if(Z>M)return{width:Q,height:Math.ceil(Q/M)};return null}export function isInstagramStoryAspectRatioAllowed(Q,X){const Z=Q/Math.max(X,1);return Z>=D-0.001&&Z<=T+0.001}export function getInstagramStoryNormalizedDimensions(Q,X){const Z=Q/Math.max(X,1);if(Z<D)return{width:Math.ceil(X*D),height:X};if(Z>T)return{width:Q,height:Math.ceil(Q/T)};return null}function x(Q){const X=V.parse(Q);return V.join(R.tmpdir(),`opencli-instagram-private-${X.name}-${B.randomUUID()}${X.ext||".png"}`)}export function prepareImageAssetForPrivateUpload(Q){const X=readImageAsset(Q),Z=getInstagramFeedNormalizedDimensions(X.width,X.height);if(!Z)return X;if(process.platform!=="darwin")throw new K(`Instagram private publish does not support auto-normalizing ${X.fileName} on ${process.platform}`,`Use images within ${C.toFixed(2)}-${M.toFixed(2)} aspect ratio, or use the UI route`);const $=x(Q),q=z("sips",["--padToHeightWidth",String(Z.height),String(Z.width),"--padColor",S,Q,"--out",$],{encoding:"utf8"});if(q.error||q.status!==0||!U.existsSync($)){const F=[q.error?.message,q.stderr,q.stdout].map((O)=>String(O||"").trim()).filter(Boolean).join(" ");throw new K(`Instagram private publish failed to normalize ${X.fileName}`,F||"sips padToHeightWidth failed")}return{...readImageAsset($),cleanupPath:$}}export function prepareImageAssetForPrivateStoryUpload(Q){const X=readImageAsset(Q),Z=getInstagramStoryNormalizedDimensions(X.width,X.height);if(!Z)return X;if(process.platform!=="darwin")throw new K(`Instagram private story publish does not support auto-normalizing ${X.fileName} on ${process.platform}`,`Use images within ${D.toFixed(2)}-${T.toFixed(2)} aspect ratio, or use the UI route`);const $=x(Q),q=z("sips",["--padToHeightWidth",String(Z.height),String(Z.width),"--padColor",S,Q,"--out",$],{encoding:"utf8"});if(q.error||q.status!==0||!U.existsSync($)){const F=[q.error?.message,q.stderr,q.stdout].map((O)=>String(O||"").trim()).filter(Boolean).join(" ");throw new K(`Instagram private story publish failed to normalize ${X.fileName}`,F||"sips padToHeightWidth failed")}return{...readImageAsset($),cleanupPath:$}}function A(Q,X,Z){const $=V.join(R.tmpdir(),`opencli-instagram-${B.randomUUID()}.swift`);U.writeFileSync($,Q);try{const q=z("swift",[$,...X],{encoding:"utf8"});if(q.error||q.status!==0){const F=[q.error?.message,q.stderr,q.stdout].map((O)=>String(O||"").trim()).filter(Boolean).join(" ");throw new K(`Instagram private publish failed to ${Z}`,F||"swift helper failed")}return JSON.parse(String(q.stdout||"{}"))}catch(q){if(q instanceof K)throw q;throw new K(`Instagram private publish failed to ${Z}`,q instanceof Error?q.message:String(q))}finally{U.rmSync($,{force:!0})}}function ZQ(Q){if(process.platform!=="darwin")throw new K(`Instagram private mixed-media publish does not support reading video metadata on ${process.platform}`,"Use macOS for private mixed-media publishing, or rely on the UI fallback");const X=A(`
|
|
1
|
+
import*as L from"node:crypto";import*as U from"node:fs";import*as R from"node:os";import*as V from"node:path";import{spawnSync as z}from"node:child_process";import{CommandExecutionError as K}from"@jackwener/opencli/errors";import{instagramPrivateApiFetch as S}from"./protocol-capture.js";import{buildReadInstagramRuntimeInfoJs as h}from"./runtime-info.js";export{buildReadInstagramRuntimeInfoJs,extractInstagramRuntimeInfo,resolveInstagramRuntimeInfo}from"./runtime-info.js";const C=0.8,M=1.91,D=0.5625,T=0.75,j="FFFFFF",l="https://www.instagram.com/",u="/api/v1/|/graphql/",k=2,v=2,w=20,d=2000,I=15000,n="19ce5f445dbfd9d29c59dc2a78c616a7fc090a8e018b9267bc4240a30244c53b",o="4",p={manufacturer:"samsung",model:"SM-G930F",android_version:24,android_release:"7.0"};export function markPublishOutcomeUncertain(Q){if(Q&&typeof Q==="object")Q.instagramPublishUncertain=!0;return Q}export function isPublishOutcomeUncertain(Q){return Boolean(Q&&typeof Q==="object"&&Q.instagramPublishUncertain)}export function derivePrivateApiContextFromCapture(Q){for(let X=Q.length-1;X>=0;X-=1){const Z=Q[X]?.requestHeaders??{},$={asbdId:String(Z["X-ASBD-ID"]||""),csrfToken:String(Z["X-CSRFToken"]||""),igAppId:String(Z["X-IG-App-ID"]||""),igWwwClaim:String(Z["X-IG-WWW-Claim"]||""),instagramAjax:String(Z["X-Instagram-AJAX"]||""),webSessionId:String(Z["X-Web-Session-ID"]||"")};if($.asbdId&&$.csrfToken&&$.igAppId&&$.igWwwClaim&&$.instagramAjax&&$.webSessionId)return $}return null}function i(Q){const X={};for(let Z=Q.length-1;Z>=0;Z-=1){const $=Q[Z]?.requestHeaders??{};if(!X.asbdId&&$["X-ASBD-ID"])X.asbdId=String($["X-ASBD-ID"]);if(!X.csrfToken&&$["X-CSRFToken"])X.csrfToken=String($["X-CSRFToken"]);if(!X.igAppId&&$["X-IG-App-ID"])X.igAppId=String($["X-IG-App-ID"]);if(!X.igWwwClaim&&$["X-IG-WWW-Claim"])X.igWwwClaim=String($["X-IG-WWW-Claim"]);if(!X.instagramAjax&&$["X-Instagram-AJAX"])X.instagramAjax=String($["X-Instagram-AJAX"]);if(!X.webSessionId&&$["X-Web-Session-ID"])X.webSessionId=String($["X-Web-Session-ID"])}return X}export function deriveInstagramJazoest(Q){if(!Q)return"";return`2${Array.from(Q).reduce((Z,$)=>Z+$.charCodeAt(0),0)}`}function r(Q){return new Promise((X)=>setTimeout(X,Q))}function g(Q){const X=Q instanceof Error?Q.message:String(Q);return/fetch failed|network|socket hang up|econnreset|etimedout/i.test(X)}export function explainPrivateNetworkError(Q,X){const Z=Q instanceof Error?Q.message:String(Q);if(!/certificate verification|fetch failed|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|socket hang up/i.test(Z))return null;let q="i.instagram.com";try{q=new URL(X).host}catch{}const F=Boolean(process.env.HTTPS_PROXY||process.env.https_proxy||process.env.HTTP_PROXY||process.env.http_proxy||process.env.ALL_PROXY||process.env.all_proxy);return`Could not reach ${q} from the CLI process (${Z}).`+(F?" A proxy is configured via environment variables — check that it is running and allows this host.":" Note: uploads are sent from the CLI process, which uses only HTTP_PROXY / HTTPS_PROXY and ignores the OS proxy settings, so browser commands can succeed while publishing fails. If this network needs a proxy to reach Instagram, set HTTPS_PROXY (e.g. http://127.0.0.1:1082) and retry.")+" This is a connectivity problem, not a certificate, account, or image problem."}function s(Q,X){return Q.find((Z)=>Z.name===X)?.value||""}export async function resolveInstagramPrivatePublishConfig(Q){let X;for(let Z=0;Z<k;Z+=1)try{if(typeof Q.startNetworkCapture==="function")await Q.startNetworkCapture(u);await Q.goto(`${l}?__opencli_private_probe=${Date.now()}`);await Q.wait({time:2});const[$,q,F]=await Promise.all([Q.getCookies({domain:"instagram.com"}),Q.evaluate(h()),typeof Q.readNetworkCapture==="function"?Q.readNetworkCapture():Promise.resolve([])]),N=Array.isArray(F)?F:[],W=derivePrivateApiContextFromCapture(N)??i(N),O=q?.csrfToken||s($,"csrftoken")||W.csrfToken||"",J=q?.appId||W.igAppId||"",G=q?.instagramAjax||W.instagramAjax||"";if(!O)throw new K("Instagram private route could not derive CSRF token from browser session");if(!J)throw new K("Instagram private route could not derive X-IG-App-ID from instagram runtime");if(!G)throw new K("Instagram private route could not derive X-Instagram-AJAX from instagram runtime");const H=W.asbdId||"",Y=W.igWwwClaim||"",y=W.webSessionId||"";return{apiContext:{asbdId:H,csrfToken:O,igAppId:J,igWwwClaim:Y,instagramAjax:G,webSessionId:y},jazoest:deriveInstagramJazoest(O)}}catch($){X=$;if(!g($)||Z>=k-1)throw $}throw X instanceof Error?X:Error(String(X))}export function buildConfigureBody(Q){const X=new URLSearchParams;X.set("archive_only","false");X.set("caption",Q.caption);X.set("clips_share_preview_to_feed","1");X.set("disable_comments","0");X.set("disable_oa_reuse","false");X.set("igtv_share_preview_to_feed","1");X.set("is_meta_only_post","0");X.set("is_unified_video","1");X.set("like_and_view_counts_disabled","0");X.set("media_share_flow","creation_flow");X.set("share_to_facebook","");X.set("share_to_fb_destination_type","USER");X.set("source_type","library");X.set("upload_id",Q.uploadId);X.set("video_subtitles_enabled","0");X.set("jazoest",Q.jazoest);return X.toString()}export function buildConfigureSidecarPayload(Q){return{archive_only:!1,caption:Q.caption,children_metadata:Q.uploadIds.map((X)=>({upload_id:X})),client_sidecar_id:Q.clientSidecarId,disable_comments:"0",is_meta_only_post:!1,is_open_to_public_submission:!1,like_and_view_counts_disabled:0,media_share_flow:"creation_flow",share_to_facebook:"",share_to_fb_destination_type:"USER",source_type:"library",jazoest:Q.jazoest}}function a(Q){switch(V.extname(Q).toLowerCase()){case".png":return"image/png";case".webp":return"image/webp";case".jpg":case".jpeg":default:return"image/jpeg"}}function t(Q){if(Q.length<24)return null;if(Q.subarray(0,8).toString("hex").toUpperCase()!=="89504E470D0A1A0A")return null;if(Q.subarray(12,16).toString("ascii")!=="IHDR")return null;return{width:Q.readUInt32BE(16),height:Q.readUInt32BE(20)}}function e(Q){if(Q.length<4||Q[0]!==255||Q[1]!==216)return null;let X=2;while(X+9<Q.length){if(Q[X]!==255){X+=1;continue}const Z=Q[X+1];X+=2;if(Z===216||Z===217)continue;if(X+2>Q.length)break;const $=Q.readUInt16BE(X);if($<2||X+$>Q.length)break;if(Z>=192&&Z<=207&&![196,200,204].includes(Z)&&$>=7)return{height:Q.readUInt16BE(X+3),width:Q.readUInt16BE(X+5)};X+=$}return null}function QQ(Q){if(Q.length<30)return null;if(Q.subarray(0,4).toString("ascii")!=="RIFF"||Q.subarray(8,12).toString("ascii")!=="WEBP")return null;const X=Q.subarray(12,16).toString("ascii");if(X==="VP8X"&&Q.length>=30)return{width:1+Q.readUIntLE(24,3),height:1+Q.readUIntLE(27,3)};if(X==="VP8 "&&Q.length>=30)return{width:Q.readUInt16LE(26)&16383,height:Q.readUInt16LE(28)&16383};if(X==="VP8L"&&Q.length>=25){const Z=Q.readUInt32LE(21);return{width:(Z&16383)+1,height:(Z>>14&16383)+1}}return null}function XQ(Q,X){const Z=V.extname(Q).toLowerCase(),$=Z===".png"?t(X):Z===".webp"?QQ(X):e(X);if(!$)throw new K(`Failed to read image dimensions for ${Q}`);return $}export function readImageAsset(Q){const X=U.readFileSync(Q),{width:Z,height:$}=XQ(Q,X);return{filePath:Q,fileName:V.basename(Q),mimeType:a(Q),width:Z,height:$,byteLength:X.length,bytes:X}}export function isInstagramFeedAspectRatioAllowed(Q,X){const Z=Q/Math.max(X,1);return Z>=C-0.001&&Z<=M+0.001}export function getInstagramFeedNormalizedDimensions(Q,X){const Z=Q/Math.max(X,1);if(Z<C)return{width:Math.ceil(X*C),height:X};if(Z>M)return{width:Q,height:Math.ceil(Q/M)};return null}export function isInstagramStoryAspectRatioAllowed(Q,X){const Z=Q/Math.max(X,1);return Z>=D-0.001&&Z<=T+0.001}export function getInstagramStoryNormalizedDimensions(Q,X){const Z=Q/Math.max(X,1);if(Z<D)return{width:Math.ceil(X*D),height:X};if(Z>T)return{width:Q,height:Math.ceil(Q/T)};return null}function x(Q){const X=V.parse(Q);return V.join(R.tmpdir(),`opencli-instagram-private-${X.name}-${L.randomUUID()}${X.ext||".png"}`)}export function prepareImageAssetForPrivateUpload(Q){const X=readImageAsset(Q),Z=getInstagramFeedNormalizedDimensions(X.width,X.height);if(!Z)return X;if(process.platform!=="darwin")throw new K(`Instagram private publish does not support auto-normalizing ${X.fileName} on ${process.platform}`,`Use images within ${C.toFixed(2)}-${M.toFixed(2)} aspect ratio, or use the UI route`);const $=x(Q),q=z("sips",["--padToHeightWidth",String(Z.height),String(Z.width),"--padColor",j,Q,"--out",$],{encoding:"utf8"});if(q.error||q.status!==0||!U.existsSync($)){const F=[q.error?.message,q.stderr,q.stdout].map((N)=>String(N||"").trim()).filter(Boolean).join(" ");throw new K(`Instagram private publish failed to normalize ${X.fileName}`,F||"sips padToHeightWidth failed")}return{...readImageAsset($),cleanupPath:$}}export function prepareImageAssetForPrivateStoryUpload(Q){const X=readImageAsset(Q),Z=getInstagramStoryNormalizedDimensions(X.width,X.height);if(!Z)return X;if(process.platform!=="darwin")throw new K(`Instagram private story publish does not support auto-normalizing ${X.fileName} on ${process.platform}`,`Use images within ${D.toFixed(2)}-${T.toFixed(2)} aspect ratio, or use the UI route`);const $=x(Q),q=z("sips",["--padToHeightWidth",String(Z.height),String(Z.width),"--padColor",j,Q,"--out",$],{encoding:"utf8"});if(q.error||q.status!==0||!U.existsSync($)){const F=[q.error?.message,q.stderr,q.stdout].map((N)=>String(N||"").trim()).filter(Boolean).join(" ");throw new K(`Instagram private story publish failed to normalize ${X.fileName}`,F||"sips padToHeightWidth failed")}return{...readImageAsset($),cleanupPath:$}}function A(Q,X,Z){const $=V.join(R.tmpdir(),`opencli-instagram-${L.randomUUID()}.swift`);U.writeFileSync($,Q);try{const q=z("swift",[$,...X],{encoding:"utf8"});if(q.error||q.status!==0){const F=[q.error?.message,q.stderr,q.stdout].map((N)=>String(N||"").trim()).filter(Boolean).join(" ");throw new K(`Instagram private publish failed to ${Z}`,F||"swift helper failed")}return JSON.parse(String(q.stdout||"{}"))}catch(q){if(q instanceof K)throw q;throw new K(`Instagram private publish failed to ${Z}`,q instanceof Error?q.message:String(q))}finally{U.rmSync($,{force:!0})}}function ZQ(Q){if(process.platform!=="darwin")throw new K(`Instagram private mixed-media publish does not support reading video metadata on ${process.platform}`,"Use macOS for private mixed-media publishing, or rely on the UI fallback");const X=A(`
|
|
2
2
|
import AVFoundation
|
|
3
3
|
import Foundation
|
|
4
4
|
|
|
@@ -20,7 +20,7 @@ let payload: [String: Int] = [
|
|
|
20
20
|
]
|
|
21
21
|
let data = try JSONSerialization.data(withJSONObject: payload, options: [])
|
|
22
22
|
FileHandle.standardOutput.write(data)
|
|
23
|
-
`,[Q],"read video metadata");if(!X.width||!X.height||!X.durationMs)throw new K(`Instagram private publish failed to read video metadata for ${Q}`);return{width:X.width,height:X.height,durationMs:X.durationMs}}function $Q(Q){const X=V.parse(Q);return V.join(R.tmpdir(),`opencli-instagram-private-video-cover-${X.name}-${
|
|
23
|
+
`,[Q],"read video metadata");if(!X.width||!X.height||!X.durationMs)throw new K(`Instagram private publish failed to read video metadata for ${Q}`);return{width:X.width,height:X.height,durationMs:X.durationMs}}function $Q(Q){const X=V.parse(Q);return V.join(R.tmpdir(),`opencli-instagram-private-video-cover-${X.name}-${L.randomUUID()}.jpg`)}function qQ(Q){const X=V.parse(Q);return V.join(R.tmpdir(),`opencli-instagram-story-video-${X.name}-${L.randomUUID()}${X.ext||".mp4"}`)}function FQ(Q){if(process.platform!=="darwin")throw new K(`Instagram private mixed-media publish does not support generating video covers on ${process.platform}`,"Use macOS for private mixed-media publishing, or rely on the UI fallback");const X=$Q(Q);A(`
|
|
24
24
|
import AVFoundation
|
|
25
25
|
import AppKit
|
|
26
26
|
import Foundation
|
|
@@ -72,4 +72,4 @@ if exportSession.status != .completed {
|
|
|
72
72
|
let payload = ["ok": true]
|
|
73
73
|
let json = try JSONSerialization.data(withJSONObject: payload, options: [])
|
|
74
74
|
FileHandle.standardOutput.write(json)
|
|
75
|
-
`,[Q,Z,String(X)],"trim story video");return Z}function NQ(Q){const X=readVideoAsset(Q);if(X.durationMs<=I)return X;const Z=KQ(Q,I),$=readVideoAsset(Z);return{...$,cleanupPaths:[...$.cleanupPaths||[],Z]}}function P(Q){const X=Q();return X>10000000000?Math.floor(X/1000):Math.floor(X)}export function buildConfigureToStoryPhotoPayload(Q){const X=Q.now??(()=>Date.now()),Z=P(X);return{source_type:"4",upload_id:Q.uploadId,story_media_creation_date:String(Z-17),client_shared_at:String(Z-5),client_timestamp:String(Z),configure_mode:1,edits:{crop_original_size:[Q.width,Q.height],crop_center:[0,0],crop_zoom:1.3333334},extra:{source_width:Q.width,source_height:Q.height},jazoest:Q.jazoest}}export function buildConfigureToStoryVideoPayload(Q){const X=Q.now??(()=>Date.now()),Z=P(X),$=Number((Q.durationMs/1000).toFixed(3));return{source_type:"4",upload_id:Q.uploadId,story_media_creation_date:String(Z-17),client_shared_at:String(Z-5),client_timestamp:String(Z),configure_mode:1,poster_frame_index:0,length:$,audio_muted:!1,filter_type:"0",video_result:"deprecated",extra:{source_width:Q.width,source_height:Q.height},jazoest:Q.jazoest}}function BQ(Q){const X=new URLSearchParams;for(const[Z,$]of Object.entries(Q)){if($===void 0||$===null)continue;if(typeof $==="object"){X.set(Z,JSON.stringify($));continue}X.set(Z,String($))}return X.toString()}function b(Q){const X=JSON.stringify(Q),Z=B.createHmac("sha256",n).update(X).digest("hex"),$=new URLSearchParams;$.set("ig_sig_key_version",o);$.set("signed_body",`${Z}.${X}`);return $.toString()}function _(Q){return Object.fromEntries(Object.entries({"X-ASBD-ID":Q.asbdId,"X-CSRFToken":Q.csrfToken,"X-IG-App-ID":Q.igAppId,"X-IG-WWW-Claim":Q.igWwwClaim,"X-Instagram-AJAX":Q.instagramAjax,"X-Web-Session-ID":Q.webSessionId}).filter(([,X])=>!!X))}function OQ(Q,X,Z){return{..._(Z),Accept:"*/*","Content-Type":Q.mimeType,Offset:"0","X-Entity-Length":String(Q.byteLength),"X-Entity-Name":`fb_uploader_${X}`,"X-Entity-Type":Q.mimeType,"X-Instagram-Rupload-Params":JSON.stringify({media_type:1,upload_id:X,upload_media_height:Q.height,upload_media_width:Q.width})}}function m(Q){const X=Math.min(Q.width,Q.height),Z=Number((Q.durationMs/1000).toFixed(3));return{crop_height:X,crop_width:X,crop_x1:Math.max(0,Math.floor((Q.width-X)/2)),crop_y1:Math.max(0,Math.floor((Q.height-X)/2)),mute:!1,trim_end:Z,trim_start:0}}function WQ(Q,X,Z){return{..._(Z),Accept:"*/*",Offset:"0","X-Entity-Length":String(Q.byteLength),"X-Entity-Name":`fb_uploader_${X}`,"X-Instagram-Rupload-Params":JSON.stringify({"client-passthrough":"1",is_unified_video:"0",is_sidecar:"1",media_type:2,for_album:!1,video_format:"",upload_id:X,upload_media_duration_ms:Q.durationMs,upload_media_height:Q.height,upload_media_width:Q.width,video_transform:null,video_edit_params:m(Q)})}}function GQ(Q,X,Z){return{..._(Z),Accept:"*/*",Offset:"0","X-Entity-Length":String(Q.byteLength),"X-Entity-Name":`fb_uploader_${X}`,"X-Instagram-Rupload-Params":JSON.stringify({"client-passthrough":"1",media_type:2,upload_id:X,upload_media_duration_ms:Q.durationMs,upload_media_height:Q.height,upload_media_width:Q.width,video_transform:null,video_edit_params:m(Q)})}}function HQ(Q,X,Z){return{..._(Z),Accept:"*/*","Content-Type":Q.coverImage.mimeType,Offset:"0","X-Entity-Length":String(Q.coverImage.byteLength),"X-Entity-Name":`fb_uploader_${X}`,"X-Entity-Type":Q.coverImage.mimeType,"X-Instagram-Rupload-Params":JSON.stringify({media_type:2,upload_id:X,upload_media_height:Q.height,upload_media_width:Q.width})}}async function L(Q,X){const Z=await Q.text();let $;try{$=Z?JSON.parse(Z):{}}catch{throw new K(`Instagram private publish ${X} returned invalid JSON`)}if(!Q.ok){const q=Z?` ${Z.slice(0,500)}`:"";throw new K(`Instagram private publish ${X} failed: ${Q.status}${q}`)}return $}async function E(Q,X,Z){let $;for(let q=0;q<v;q+=1)try{return await Q(X,Z)}catch(F){$=F;if(!g(F)||q>=v-1)throw F}throw $ instanceof Error?$:Error(String($))}async function JQ(Q){if(Q.type==="video")return{type:"video",asset:readVideoAsset(Q.filePath)};return{type:"image",asset:prepareImageAssetForPrivateUpload(Q.filePath)}}function f(Q){for(const X of Q){if(X.type==="image"){if(X.asset.cleanupPath)U.rmSync(X.asset.cleanupPath,{force:!0});continue}for(const Z of X.asset.cleanupPaths||[])U.rmSync(Z,{force:!0});if(X.asset.coverImage.cleanupPath)U.rmSync(X.asset.coverImage.cleanupPath,{force:!0})}}async function c(Q,X,Z,$,q="feed"){if(X.type==="image"){const J=await E(Q,`https://i.instagram.com/rupload_igphoto/fb_uploader_${Z}`,{method:"POST",headers:OQ(X.asset,Z,$),body:X.asset.bytes}),W=await L(J,"upload");if(String(W?.status||"")!=="ok")throw new K(`Instagram private publish upload failed for ${X.asset.fileName}`);return}const F=await E(Q,`https://i.instagram.com/rupload_igvideo/fb_uploader_${Z}`,{method:"POST",headers:q==="story"?GQ(X.asset,Z,$):WQ(X.asset,Z,$),body:X.asset.bytes}),O=await L(F,"video upload");if(String(O?.status||"")!=="ok")throw new K(`Instagram private publish video upload failed for ${X.asset.fileName}`);const G=await E(Q,`https://i.instagram.com/rupload_igphoto/fb_uploader_${Z}`,{method:"POST",headers:HQ(X.asset,Z,$),body:X.asset.coverImage.bytes}),N=await L(G,"video cover upload");if(String(N?.status||"")!=="ok")throw new K(`Instagram private publish video cover upload failed for ${X.asset.fileName}`)}async function VQ(Q){const X=Q.waitMs??r,Z={method:"POST",headers:{..._(Q.apiContext),"Content-Type":"application/json"},body:JSON.stringify(Q.payload)};for(let $=0;$<w;$+=1){let q,F;try{q=await Q.fetcher("https://www.instagram.com/api/v1/media/configure_sidecar/",Z);F=await q.text()}catch(N){throw markPublishOutcomeUncertain(N instanceof Error?N:new K(`Instagram private publish configure_sidecar request failed: ${String(N)}`))}let O={};try{O=F?JSON.parse(F):{}}catch{throw markPublishOutcomeUncertain(new K("Instagram private publish configure_sidecar returned invalid JSON"))}if(!q.ok){const N=F?` ${F.slice(0,500)}`:"";throw markPublishOutcomeUncertain(new K(`Instagram private publish configure_sidecar failed: ${q.status}${N}`))}const G=String(O?.message||"");if(q.status===202||/transcode not finished yet/i.test(G)){if($>=w-1)throw markPublishOutcomeUncertain(new K("Instagram private publish configure_sidecar timed out waiting for video transcode",F.slice(0,500)));await X(d);continue}if(String(O?.status||"").toLowerCase()==="fail")throw new K("Instagram private publish configure_sidecar failed",G||F.slice(0,500));return{code:O?.media?.code}}throw new K("Instagram private publish configure_sidecar failed")}export async function publishMediaViaPrivateApi(Q){const X=Q.now??(()=>Date.now()),Z=String(X()),$=Q.mediaItems.length>1?Q.mediaItems.map((G,N)=>String(X()+N+1)):[String(X())],q=Q.fetcher??((G,N)=>j(Q.page,G,N)),F=Q.prepareMediaAsset??JQ,O=await Promise.all(Q.mediaItems.map((G)=>F(G)));try{for(let N=0;N<O.length;N+=1){const J=O[N],W=$[N];await c(q,J,W,Q.apiContext)}if($.length===1){if(O[0]?.type!=="image")throw new K("Instagram private publish only supports single-video uploads through instagram reel");let N;try{const J=await q("https://www.instagram.com/api/v1/media/configure/",{method:"POST",headers:{..._(Q.apiContext),"Content-Type":"application/x-www-form-urlencoded"},body:buildConfigureBody({uploadId:$[0],caption:Q.caption,jazoest:Q.jazoest})});N=await L(J,"configure")}catch(J){throw markPublishOutcomeUncertain(J instanceof Error?J:new K(`Instagram private publish configure request failed: ${String(J)}`))}return{code:N?.media?.code,uploadIds:$}}return{code:(await VQ({fetcher:q,payload:buildConfigureSidecarPayload({uploadIds:$,caption:Q.caption,clientSidecarId:Z,jazoest:Q.jazoest}),apiContext:Q.apiContext,waitMs:Q.waitMs})).code,uploadIds:$}}finally{f(O)}}export async function publishImagesViaPrivateApi(Q){return publishMediaViaPrivateApi({page:Q.page,mediaItems:Q.imagePaths.map((X)=>({type:"image",filePath:X})),caption:Q.caption,apiContext:Q.apiContext,jazoest:Q.jazoest,now:Q.now,fetcher:Q.fetcher,waitMs:Q.waitMs,prepareMediaAsset:Q.prepareAsset?async(X)=>({type:"image",asset:await Q.prepareAsset(X.filePath)}):void 0})}export async function publishStoryViaPrivateApi(Q){const X=Q.now??(()=>Date.now()),Z=String(X()),$=Q.fetcher??((W,H)=>j(Q.page,W,H)),F=await(Q.prepareMediaAsset??(async(W)=>W.type==="video"?{type:"video",asset:NQ(W.filePath)}:{type:"image",asset:prepareImageAssetForPrivateStoryUpload(W.filePath)}))(Q.mediaItem),O=Q.currentUserId||("getCookies"in Q.page?String((await(Q.page.getCookies?.({domain:"instagram.com"})??Promise.resolve([]))).find((W)=>W.name==="ds_user_id")?.value||""):"");if(!O)throw new K("Instagram story publish could not derive current user id from browser session");const G={_csrftoken:Q.apiContext.csrfToken,_uid:O,_uuid:B.randomUUID(),device:p},N=(W,H)=>b({...buildConfigureToStoryPhotoPayload({uploadId:Z,width:W,height:H,now:X,jazoest:Q.jazoest}),...G}),J=(W,H,Y)=>b({...buildConfigureToStoryVideoPayload({uploadId:Z,width:W,height:H,durationMs:Y,now:X,jazoest:Q.jazoest}),...G});try{await c($,F,Z,Q.apiContext,"story");if(F.type==="image"){let H;try{const Y=await $("https://i.instagram.com/api/v1/media/configure_to_story/",{method:"POST",headers:{..._(Q.apiContext),"Content-Type":"application/x-www-form-urlencoded"},body:N(F.asset.width,F.asset.height)});H=await L(Y,"configure_to_story")}catch(Y){throw markPublishOutcomeUncertain(Y instanceof Error?Y:new K(`Instagram private publish configure_to_story request failed: ${String(Y)}`))}return{mediaPk:String(H?.media?.pk||H?.media?.id||"").split("_")[0]||void 0,uploadId:Z}}let W;try{await L(await $("https://i.instagram.com/api/v1/media/configure_to_story/",{method:"POST",headers:{..._(Q.apiContext),"Content-Type":"application/x-www-form-urlencoded"},body:N(F.asset.width,F.asset.height)}),"configure_to_story cover");const H=await $("https://i.instagram.com/api/v1/media/configure_to_story/?video=1",{method:"POST",headers:{..._(Q.apiContext),"Content-Type":"application/x-www-form-urlencoded"},body:J(F.asset.width,F.asset.height,F.asset.durationMs)});W=await L(H,"configure_to_story")}catch(H){throw markPublishOutcomeUncertain(H instanceof Error?H:new K(`Instagram private publish configure_to_story request failed: ${String(H)}`))}return{mediaPk:String(W?.media?.pk||W?.media?.id||"").split("_")[0]||void 0,uploadId:Z}}finally{f([F])}}
|
|
75
|
+
`,[Q,Z,String(X)],"trim story video");return Z}function NQ(Q){const X=readVideoAsset(Q);if(X.durationMs<=I)return X;const Z=KQ(Q,I),$=readVideoAsset(Z);return{...$,cleanupPaths:[...$.cleanupPaths||[],Z]}}function P(Q){const X=Q();return X>10000000000?Math.floor(X/1000):Math.floor(X)}export function buildConfigureToStoryPhotoPayload(Q){const X=Q.now??(()=>Date.now()),Z=P(X);return{source_type:"4",upload_id:Q.uploadId,story_media_creation_date:String(Z-17),client_shared_at:String(Z-5),client_timestamp:String(Z),configure_mode:1,edits:{crop_original_size:[Q.width,Q.height],crop_center:[0,0],crop_zoom:1.3333334},extra:{source_width:Q.width,source_height:Q.height},jazoest:Q.jazoest}}export function buildConfigureToStoryVideoPayload(Q){const X=Q.now??(()=>Date.now()),Z=P(X),$=Number((Q.durationMs/1000).toFixed(3));return{source_type:"4",upload_id:Q.uploadId,story_media_creation_date:String(Z-17),client_shared_at:String(Z-5),client_timestamp:String(Z),configure_mode:1,poster_frame_index:0,length:$,audio_muted:!1,filter_type:"0",video_result:"deprecated",extra:{source_width:Q.width,source_height:Q.height},jazoest:Q.jazoest}}function LQ(Q){const X=new URLSearchParams;for(const[Z,$]of Object.entries(Q)){if($===void 0||$===null)continue;if(typeof $==="object"){X.set(Z,JSON.stringify($));continue}X.set(Z,String($))}return X.toString()}function b(Q){const X=JSON.stringify(Q),Z=L.createHmac("sha256",n).update(X).digest("hex"),$=new URLSearchParams;$.set("ig_sig_key_version",o);$.set("signed_body",`${Z}.${X}`);return $.toString()}function _(Q){return Object.fromEntries(Object.entries({"X-ASBD-ID":Q.asbdId,"X-CSRFToken":Q.csrfToken,"X-IG-App-ID":Q.igAppId,"X-IG-WWW-Claim":Q.igWwwClaim,"X-Instagram-AJAX":Q.instagramAjax,"X-Web-Session-ID":Q.webSessionId}).filter(([,X])=>!!X))}function OQ(Q,X,Z){return{..._(Z),Accept:"*/*","Content-Type":Q.mimeType,Offset:"0","X-Entity-Length":String(Q.byteLength),"X-Entity-Name":`fb_uploader_${X}`,"X-Entity-Type":Q.mimeType,"X-Instagram-Rupload-Params":JSON.stringify({media_type:1,upload_id:X,upload_media_height:Q.height,upload_media_width:Q.width})}}function m(Q){const X=Math.min(Q.width,Q.height),Z=Number((Q.durationMs/1000).toFixed(3));return{crop_height:X,crop_width:X,crop_x1:Math.max(0,Math.floor((Q.width-X)/2)),crop_y1:Math.max(0,Math.floor((Q.height-X)/2)),mute:!1,trim_end:Z,trim_start:0}}function WQ(Q,X,Z){return{..._(Z),Accept:"*/*",Offset:"0","X-Entity-Length":String(Q.byteLength),"X-Entity-Name":`fb_uploader_${X}`,"X-Instagram-Rupload-Params":JSON.stringify({"client-passthrough":"1",is_unified_video:"0",is_sidecar:"1",media_type:2,for_album:!1,video_format:"",upload_id:X,upload_media_duration_ms:Q.durationMs,upload_media_height:Q.height,upload_media_width:Q.width,video_transform:null,video_edit_params:m(Q)})}}function GQ(Q,X,Z){return{..._(Z),Accept:"*/*",Offset:"0","X-Entity-Length":String(Q.byteLength),"X-Entity-Name":`fb_uploader_${X}`,"X-Instagram-Rupload-Params":JSON.stringify({"client-passthrough":"1",media_type:2,upload_id:X,upload_media_duration_ms:Q.durationMs,upload_media_height:Q.height,upload_media_width:Q.width,video_transform:null,video_edit_params:m(Q)})}}function HQ(Q,X,Z){return{..._(Z),Accept:"*/*","Content-Type":Q.coverImage.mimeType,Offset:"0","X-Entity-Length":String(Q.coverImage.byteLength),"X-Entity-Name":`fb_uploader_${X}`,"X-Entity-Type":Q.coverImage.mimeType,"X-Instagram-Rupload-Params":JSON.stringify({media_type:2,upload_id:X,upload_media_height:Q.height,upload_media_width:Q.width})}}async function B(Q,X){const Z=await Q.text();let $;try{$=Z?JSON.parse(Z):{}}catch{throw new K(`Instagram private publish ${X} returned invalid JSON`)}if(!Q.ok){const q=Z?` ${Z.slice(0,500)}`:"";throw new K(`Instagram private publish ${X} failed: ${Q.status}${q}`)}return $}async function E(Q,X,Z){let $;for(let F=0;F<v;F+=1)try{return await Q(X,Z)}catch(N){$=N;if(!g(N)||F>=v-1){const W=explainPrivateNetworkError(N,X);if(W)throw new K(`Instagram private publish upload failed: ${W}`);throw N}}const q=explainPrivateNetworkError($,X);if(q)throw new K(`Instagram private publish upload failed: ${q}`);throw $ instanceof Error?$:Error(String($))}async function JQ(Q){if(Q.type==="video")return{type:"video",asset:readVideoAsset(Q.filePath)};return{type:"image",asset:prepareImageAssetForPrivateUpload(Q.filePath)}}function f(Q){for(const X of Q){if(X.type==="image"){if(X.asset.cleanupPath)U.rmSync(X.asset.cleanupPath,{force:!0});continue}for(const Z of X.asset.cleanupPaths||[])U.rmSync(Z,{force:!0});if(X.asset.coverImage.cleanupPath)U.rmSync(X.asset.coverImage.cleanupPath,{force:!0})}}async function c(Q,X,Z,$,q="feed"){if(X.type==="image"){const J=await E(Q,`https://i.instagram.com/rupload_igphoto/fb_uploader_${Z}`,{method:"POST",headers:OQ(X.asset,Z,$),body:X.asset.bytes}),G=await B(J,"upload");if(String(G?.status||"")!=="ok")throw new K(`Instagram private publish upload failed for ${X.asset.fileName}`);return}const F=await E(Q,`https://i.instagram.com/rupload_igvideo/fb_uploader_${Z}`,{method:"POST",headers:q==="story"?GQ(X.asset,Z,$):WQ(X.asset,Z,$),body:X.asset.bytes}),N=await B(F,"video upload");if(String(N?.status||"")!=="ok")throw new K(`Instagram private publish video upload failed for ${X.asset.fileName}`);const W=await E(Q,`https://i.instagram.com/rupload_igphoto/fb_uploader_${Z}`,{method:"POST",headers:HQ(X.asset,Z,$),body:X.asset.coverImage.bytes}),O=await B(W,"video cover upload");if(String(O?.status||"")!=="ok")throw new K(`Instagram private publish video cover upload failed for ${X.asset.fileName}`)}async function VQ(Q){const X=Q.waitMs??r,Z={method:"POST",headers:{..._(Q.apiContext),"Content-Type":"application/json"},body:JSON.stringify(Q.payload)};for(let $=0;$<w;$+=1){let q,F;try{q=await Q.fetcher("https://www.instagram.com/api/v1/media/configure_sidecar/",Z);F=await q.text()}catch(O){throw markPublishOutcomeUncertain(O instanceof Error?O:new K(`Instagram private publish configure_sidecar request failed: ${String(O)}`))}let N={};try{N=F?JSON.parse(F):{}}catch{throw markPublishOutcomeUncertain(new K("Instagram private publish configure_sidecar returned invalid JSON"))}if(!q.ok){const O=F?` ${F.slice(0,500)}`:"";throw markPublishOutcomeUncertain(new K(`Instagram private publish configure_sidecar failed: ${q.status}${O}`))}const W=String(N?.message||"");if(q.status===202||/transcode not finished yet/i.test(W)){if($>=w-1)throw markPublishOutcomeUncertain(new K("Instagram private publish configure_sidecar timed out waiting for video transcode",F.slice(0,500)));await X(d);continue}if(String(N?.status||"").toLowerCase()==="fail")throw new K("Instagram private publish configure_sidecar failed",W||F.slice(0,500));return{code:N?.media?.code,mediaId:String(N?.media?.id||"")}}throw new K("Instagram private publish configure_sidecar failed")}export async function publishMediaViaPrivateApi(Q){const X=Q.now??(()=>Date.now()),Z=String(X()),$=Q.mediaItems.length>1?Q.mediaItems.map((W,O)=>String(X()+O+1)):[String(X())],q=Q.fetcher??((W,O)=>S(Q.page,W,O)),F=Q.prepareMediaAsset??JQ,N=await Promise.all(Q.mediaItems.map((W)=>F(W)));try{for(let O=0;O<N.length;O+=1){const J=N[O],G=$[O];await c(q,J,G,Q.apiContext)}if($.length===1){if(N[0]?.type!=="image")throw new K("Instagram private publish only supports single-video uploads through instagram reel");let O;try{const J=await q("https://www.instagram.com/api/v1/media/configure/",{method:"POST",headers:{..._(Q.apiContext),"Content-Type":"application/x-www-form-urlencoded"},body:buildConfigureBody({uploadId:$[0],caption:Q.caption,jazoest:Q.jazoest})});O=await B(J,"configure")}catch(J){throw markPublishOutcomeUncertain(J instanceof Error?J:new K(`Instagram private publish configure request failed: ${String(J)}`))}return{code:O?.media?.code,mediaId:String(O?.media?.id||""),uploadIds:$}}const W=await VQ({fetcher:q,payload:buildConfigureSidecarPayload({uploadIds:$,caption:Q.caption,clientSidecarId:Z,jazoest:Q.jazoest}),apiContext:Q.apiContext,waitMs:Q.waitMs});return{code:W.code,mediaId:W.mediaId||"",uploadIds:$}}finally{f(N)}}export async function publishImagesViaPrivateApi(Q){return publishMediaViaPrivateApi({page:Q.page,mediaItems:Q.imagePaths.map((X)=>({type:"image",filePath:X})),caption:Q.caption,apiContext:Q.apiContext,jazoest:Q.jazoest,now:Q.now,fetcher:Q.fetcher,waitMs:Q.waitMs,prepareMediaAsset:Q.prepareAsset?async(X)=>({type:"image",asset:await Q.prepareAsset(X.filePath)}):void 0})}export async function publishStoryViaPrivateApi(Q){const X=Q.now??(()=>Date.now()),Z=String(X()),$=Q.fetcher??((G,H)=>S(Q.page,G,H)),F=await(Q.prepareMediaAsset??(async(G)=>G.type==="video"?{type:"video",asset:NQ(G.filePath)}:{type:"image",asset:prepareImageAssetForPrivateStoryUpload(G.filePath)}))(Q.mediaItem),N=Q.currentUserId||("getCookies"in Q.page?String((await(Q.page.getCookies?.({domain:"instagram.com"})??Promise.resolve([]))).find((G)=>G.name==="ds_user_id")?.value||""):"");if(!N)throw new K("Instagram story publish could not derive current user id from browser session");const W={_csrftoken:Q.apiContext.csrfToken,_uid:N,_uuid:L.randomUUID(),device:p},O=(G,H)=>b({...buildConfigureToStoryPhotoPayload({uploadId:Z,width:G,height:H,now:X,jazoest:Q.jazoest}),...W}),J=(G,H,Y)=>b({...buildConfigureToStoryVideoPayload({uploadId:Z,width:G,height:H,durationMs:Y,now:X,jazoest:Q.jazoest}),...W});try{await c($,F,Z,Q.apiContext,"story");if(F.type==="image"){let H;try{const Y=await $("https://i.instagram.com/api/v1/media/configure_to_story/",{method:"POST",headers:{..._(Q.apiContext),"Content-Type":"application/x-www-form-urlencoded"},body:O(F.asset.width,F.asset.height)});H=await B(Y,"configure_to_story")}catch(Y){throw markPublishOutcomeUncertain(Y instanceof Error?Y:new K(`Instagram private publish configure_to_story request failed: ${String(Y)}`))}return{mediaPk:String(H?.media?.pk||H?.media?.id||"").split("_")[0]||void 0,uploadId:Z}}let G;try{await B(await $("https://i.instagram.com/api/v1/media/configure_to_story/",{method:"POST",headers:{..._(Q.apiContext),"Content-Type":"application/x-www-form-urlencoded"},body:O(F.asset.width,F.asset.height)}),"configure_to_story cover");const H=await $("https://i.instagram.com/api/v1/media/configure_to_story/?video=1",{method:"POST",headers:{..._(Q.apiContext),"Content-Type":"application/x-www-form-urlencoded"},body:J(F.asset.width,F.asset.height,F.asset.durationMs)});G=await B(H,"configure_to_story")}catch(H){throw markPublishOutcomeUncertain(H instanceof Error?H:new K(`Instagram private publish configure_to_story request failed: ${String(H)}`))}return{mediaPk:String(G?.media?.pk||G?.media?.id||"").split("_")[0]||void 0,uploadId:Z}}finally{f([F])}}
|
package/clis/instagram/detail.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import{cli as
|
|
2
|
-
`),
|
|
1
|
+
import{cli as v,Strategy as A}from"@jackwener/opencli/registry";import{CommandExecutionError as W}from"@jackwener/opencli/errors";import{buildInstagramFetchScript as D,ensurePage as O,handleFetchFailure as S,normalizeFetchResult as U,parseInstagramMediaTarget as V}from"./download.js";function b(B){return String(B||"").replace(/\s+/g," ").trim()}function j(B){return String(B||"").replace(/\r\n?/g,`
|
|
2
|
+
`).trim()}function Y(B){const q=Number(B||0);return Number.isFinite(q)&&q>0?new Date(q*1000).toISOString():""}function I(B){if(!B||typeof B!=="object")return null;const q=B.is_video===!0,G=String(q?B.video_url||"":B.display_url||"").trim();if(!G)return null;return{kind:q?"video":"image",url:G}}function Z(B){const q=Array.isArray(B?.edge_sidecar_to_children?.edges)?B.edge_sidecar_to_children.edges.map((H)=>H?.node).filter(Boolean):[];return(q.length>0?q:[B]).map(I).filter(Boolean)}function M(B,q){const G=String(q?.product_type||"").toLowerCase();if(G==="clips"||G==="reel"||B==="reel")return"reel";return B||"p"}function f(B){if(B.length>1)return"carousel";return B[0]?.kind||""}function X(B){const q=Number(B?.count??0);return Number.isFinite(q)?q:0}function k(B){const q=B?.edge_media_to_caption?.edges?.[0]?.node?.text||"";return j(q)}function _(B,q){if(!q||typeof q!=="object")throw new W("Instagram returned malformed media detail payload");const G=String(q.shortcode||B.shortcode||"").trim(),H=String(q.id||"").trim(),J=String(q?.owner?.username||"").trim();if(!G||!H||!J)throw new W("Instagram returned malformed media detail payload");const L=Z(q);if(L.length===0)throw new W("Instagram detail payload did not contain any media URLs");const $=M(B.kind,q);return{shortcode:G,media_id:H,kind:$,type:f(L),posted_at:Y(q.taken_at_timestamp),owner:J,owner_name:String(q?.owner?.full_name||"").trim(),owner_verified:q?.owner?.is_verified?"Yes":"No",caption:k(q),likes:X(q?.edge_media_preview_like),comments:X(q?.edge_media_to_comment)||X(q?.edge_media_to_parent_comment),views:Number.isFinite(Number(q?.video_view_count))?Number(q.video_view_count):Number.isFinite(Number(q?.video_play_count))?Number(q.video_play_count):"",media_count:L.length,media_types:L.map((Q)=>Q.kind).join(", "),media_urls:L.map((Q)=>Q.url).join(`
|
|
3
|
+
`),thumbnail_url:String(q.display_url||"").trim(),url:B.canonicalUrl}}v({site:"instagram",name:"detail",access:"read",description:"Get single-post Instagram details, including direct media URLs",domain:"www.instagram.com",strategy:A.COOKIE,navigateBefore:!1,args:[{name:"url",positional:!0,required:!0,help:"Instagram post / reel / tv URL"}],columns:["shortcode","media_id","kind","type","posted_at","owner","owner_name","owner_verified","likes","comments","views","media_count","media_types","media_urls","thumbnail_url","url","caption"],func:async(B,q)=>{const G=O(B),H=V(String(q.url??""));await G.goto(H.canonicalUrl);const J=U(await G.evaluate(D(H.shortcode)));if(!J.ok)S(J);return[_(H,J.media)]},__test__:{normalizeCaptionText:b,formatPostedAt:Y,normalizeMediaNodes:Z,normalizeDetailRow:_}});
|