publishport-opencli 1.0.16 → 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 +568 -37
- package/clis/_shared/article/publish.js +14 -7
- package/clis/bilibili/article.js +1 -1
- package/clis/bilibili/comments.js +1 -1
- package/clis/boss/accept.js +1 -0
- package/clis/boss/applied.js +1 -0
- package/clis/boss/auth.js +1 -13
- package/clis/boss/batchgreet.js +1 -1
- package/clis/boss/bossinfo.js +1 -0
- package/clis/boss/cities.js +1 -0
- package/clis/boss/exchange.js +1 -1
- package/clis/boss/greet.js +1 -1
- package/clis/boss/history.js +1 -0
- package/clis/boss/interviews.js +1 -0
- package/clis/boss/mark.js +1 -1
- package/clis/boss/me.js +26 -0
- package/clis/boss/recommend.js +1 -1
- package/clis/boss/resumedelete.js +1 -0
- package/clis/boss/resumelist.js +1 -0
- package/clis/boss/resumeupload.js +53 -0
- package/clis/boss/search.js +1 -1
- package/clis/boss/send.js +1 -1
- package/clis/boss/sendresume.js +1 -0
- package/clis/boss/unread.js +1 -0
- package/clis/boss/utils.js +11 -11
- 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/clis/boss/utils.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import{ArgumentError as
|
|
1
|
+
import{ArgumentError as P,AuthRequiredError as L,CommandExecutionError as z,EmptyResultError as F}from"@jackwener/opencli/errors";const K="www.zhipin.com",v=`https://${K}/web/chat/index`,G=new Set([7,37]),T="Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。",B="该命令仅支持招聘端(BOSS 端)账号,请使用招聘者账号登录后重试。",N=15000;export function requirePage(Q){if(!Q)throw new z("Browser page required")}export function readPositiveInteger(Q,V,W,J){const $=Q===void 0||Q===null||Q===""?W:Number(Q);if(!Number.isInteger($)||$<1)throw new P(`boss ${V} must be a positive integer`);if(J!==void 0&&$>J)throw new P(`boss ${V} must be <= ${J}`);return $}export function readRequiredString(Q,V){const W=String(Q??"").trim();if(!W)throw new P(`boss ${V} cannot be empty`);return W}export async function navigateToChat(Q,V=2){await Q.goto(v);await Q.wait({time:V})}export async function navigateTo(Q,V,W=1){await Q.goto(V);await Q.wait({time:W})}export function checkAuth(Q){if(G.has(Q.code))throw new L(K,T)}function k(Q){if(Q.code===IDENTITY_MISMATCH_CODE)throw new L(K,B)}export function assertOk(Q,V){if(!Q||typeof Q!=="object")throw new z(`${V?`${V}: `:""}Boss API returned malformed response`);if(Q.code===0)return;checkAuth(Q);k(Q);const W=V?`${V}: `:"";throw new z(`${W}${Q.message||"Unknown error"} (code=${Q.code})`)}export async function bossFetch(Q,V,W={}){const J=W.method??"GET",$=W.timeout??N,j=W.body??null,X=`
|
|
2
2
|
async () => {
|
|
3
3
|
return new Promise((resolve, reject) => {
|
|
4
4
|
const xhr = new XMLHttpRequest();
|
|
5
|
-
xhr.open(${JSON.stringify(
|
|
5
|
+
xhr.open(${JSON.stringify(J)}, ${JSON.stringify(V)}, true);
|
|
6
6
|
xhr.withCredentials = true;
|
|
7
7
|
xhr.timeout = ${$};
|
|
8
8
|
xhr.setRequestHeader('Accept', 'application/json');
|
|
9
|
-
${
|
|
9
|
+
${J==="POST"?"xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');":""}
|
|
10
10
|
xhr.onload = () => {
|
|
11
11
|
try { resolve(JSON.parse(xhr.responseText)); }
|
|
12
12
|
catch(e) { reject(new Error('JSON parse failed: ' + xhr.responseText.substring(0, 200))); }
|
|
@@ -16,9 +16,9 @@ import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,Empt
|
|
|
16
16
|
xhr.send(${j?JSON.stringify(j):"null"});
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
-
`;let
|
|
19
|
+
`;let Z;try{Z=await Q.evaluate(X)}catch(Y){if(Y instanceof L||Y instanceof z)throw Y;const H=Y instanceof Error?Y.message:String(Y);throw new z(`Boss API request failed: ${H}`)}if(!Z||typeof Z!=="object")throw new z("Boss API returned malformed response");if(!W.allowNonZero&&Z.code!==0)assertOk(Z);return Z}export async function fetchFriendList(Q,V={}){const W=V.pageNum??1,J=V.jobId??"0",$=`https://${K}/wapi/zprelation/friend/getBossFriendListV2.json?page=${W}&status=0&jobId=${J}`,j=await bossFetch(Q,$,{allowNonZero:V.allowNonZero});if(V.allowNonZero&&j.code!==0)return j;const X=j.zpData?.friendList;if(!Array.isArray(X))throw new z("Boss friend list response did not include zpData.friendList");return X}export async function fetchRecommendList(Q){const V=`https://${K}/wapi/zprelation/friend/greetRecSortList`,J=(await bossFetch(Q,V)).zpData?.friendList;if(!Array.isArray(J))throw new z("Boss recommend response did not include zpData.friendList");return J}export async function findFriendByUid(Q,V,W={}){const J=W.maxPages??1,$=W.checkGreetList??!1;for(let j=1;j<=J;j++){const X=await fetchFriendList(Q,{pageNum:j,allowNonZero:W.allowNonZero});if(W.allowNonZero&&!Array.isArray(X))return{friend:null,code:X.code};const Z=Array.isArray(X)?X:[],Y=Z.find((H)=>H.encryptUid===V);if(Y)return W.allowNonZero?{friend:Y,code:0}:Y;if(Z.length===0)break}if($){const X=(await fetchRecommendList(Q)).find((Z)=>Z.encryptUid===V);if(X)return W.allowNonZero?{friend:X,code:0}:X}return W.allowNonZero?{friend:null,code:0}:null}export async function clickCandidateInList(Q,V){const W=String(V).replace(/[^0-9]/g,"");return(await Q.evaluate(`
|
|
20
20
|
async () => {
|
|
21
|
-
const uid = ${JSON.stringify(
|
|
21
|
+
const uid = ${JSON.stringify(W)};
|
|
22
22
|
const item = document.querySelector('#_' + uid + '-0') || document.querySelector('[id^="_' + uid + '"]');
|
|
23
23
|
if (item) {
|
|
24
24
|
item.click();
|
|
@@ -33,7 +33,7 @@ import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,Empt
|
|
|
33
33
|
}
|
|
34
34
|
return { clicked: false };
|
|
35
35
|
}
|
|
36
|
-
`)).clicked}export async function typeAndSendMessage(
|
|
36
|
+
`)).clicked}export async function typeAndSendMessage(Q,V){if(!(await Q.evaluate(`
|
|
37
37
|
async () => {
|
|
38
38
|
const selectors = [
|
|
39
39
|
'.chat-editor [contenteditable="true"]',
|
|
@@ -48,12 +48,12 @@ import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,Empt
|
|
|
48
48
|
if (el && el.offsetParent !== null) {
|
|
49
49
|
el.focus();
|
|
50
50
|
if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
|
|
51
|
-
el.value = ${JSON.stringify(
|
|
51
|
+
el.value = ${JSON.stringify(V)};
|
|
52
52
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
53
53
|
} else {
|
|
54
54
|
el.textContent = '';
|
|
55
55
|
el.focus();
|
|
56
|
-
document.execCommand('insertText', false, ${JSON.stringify(
|
|
56
|
+
document.execCommand('insertText', false, ${JSON.stringify(V)});
|
|
57
57
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
58
58
|
}
|
|
59
59
|
return { found: true };
|
|
@@ -61,7 +61,7 @@ import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,Empt
|
|
|
61
61
|
}
|
|
62
62
|
return { found: false };
|
|
63
63
|
}
|
|
64
|
-
`)).found)return!1;await
|
|
64
|
+
`)).found)return!1;await Q.wait({time:0.5});if(!(await Q.evaluate(`
|
|
65
65
|
async () => {
|
|
66
66
|
const btn = document.querySelector('.conversation-editor .submit')
|
|
67
67
|
|| document.querySelector('.submit-content .submit')
|
|
@@ -72,7 +72,7 @@ import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,Empt
|
|
|
72
72
|
}
|
|
73
73
|
return { clicked: false };
|
|
74
74
|
}
|
|
75
|
-
`)).clicked)await
|
|
75
|
+
`)).clicked)await Q.pressKey("Enter");return!0}export function verbose(Q){if(process.env.OPENCLI_VERBOSE)console.error(`[ppcli:boss] ${Q}`)}export const IDENTITY_MISMATCH_CODE=24;const q=`https://${K}/web/geek/chat`;export async function navigateToGeekChat(Q,V=2){await Q.goto(q);await Q.wait({time:V})}export async function readEncryptSystemId(Q){return await Q.evaluate(`
|
|
76
76
|
(() => {
|
|
77
77
|
// 1. Vue app state / Pinia / $route.query
|
|
78
78
|
// The chat component reads encryptSystemId from the app runtime to build
|
|
@@ -158,4 +158,4 @@ import{ArgumentError as H,AuthRequiredError as G,CommandExecutionError as Z,Empt
|
|
|
158
158
|
} catch (_) {}
|
|
159
159
|
return '';
|
|
160
160
|
})()
|
|
161
|
-
`)||""}export async function fetchGeekFriendLabelList(
|
|
161
|
+
`)||""}export async function fetchGeekFriendLabelList(Q,V={}){const W=V.labelId??0,J=V.encryptSystemId??"",$=`https://${K}/wapi/zprelation/friend/geekFilterByLabel?labelId=${W}&encryptSystemId=${encodeURIComponent(J)}`,j=await bossFetch(Q,$,{allowNonZero:V.allowNonZero});if(V.allowNonZero&&j.code!==0)return j;const X=j.zpData?.friendList;if(!Array.isArray(X))throw new z("Boss geek chat list response did not include zpData.friendList");return X}export async function fetchGeekFriendInfoList(Q,V=[]){if(!V.length)return[];const W=50,J=[];for(let $=0;$<V.length;$+=W){const X=`friendIds=${V.slice($,$+W).map(String).join(",")}`,Y=(await bossFetch(Q,`https://${K}/wapi/zprelation/friend/getGeekFriendList.json`,{method:"POST",body:X})).zpData?.result;if(!Array.isArray(Y))throw new z("Boss geek friend enrichment response did not include zpData.result");J.push(...Y)}return J}export async function findGeekFriendByUid(Q,V,W={}){const $=(await fetchGeekFriendLabelList(Q,{encryptSystemId:W.encryptSystemId})).find((X)=>X.encryptFriendId===V||String(X.uid)===String(V)||String(X.friendId)===String(V));if(!$)return null;const j=await fetchGeekFriendInfoList(Q,[$.friendId]);return{...$,...j[0]||{}}}export async function fetchGeekHistoryMsg(Q,V,W={}){const J=W.page??1,$=V.uid,j=encodeURIComponent(V.securityId||""),X=`https://${K}/wapi/zpchat/geek/historyMsg?bossId=${$}&securityId=${j}&page=${J}&c=20&src=0`,Z=await bossFetch(Q,X),Y=Z.zpData?.messages??Z.zpData?.historyMsgList;if(!Array.isArray(Y))throw new z("Boss geek history response did not include a message list");if(Y.length===0)throw new F("boss chatmsg","Boss returned no messages for this chat.");return Y}const M=`https://${K}/web/geek/job`;export async function navigateToGeekJob(Q,V=2){await Q.goto(M);await Q.wait({time:V})}export async function geekGet(Q,V,W={},J={}){const $=new URLSearchParams;for(const[Y,H]of Object.entries(W)){if(H===void 0||H===null||H==="")continue;$.set(Y,String(H))}const j=$.toString(),X=`https://${K}${V}${j?`?${j}`:""}`,Z=await bossFetch(Q,X,J);if(J.allowNonZero&&Z.code!==0)return Z;return Z.zpData??{}}export async function geekPost(Q,V,W={},J={}){const $=new URLSearchParams;for(const[X,Z]of Object.entries(W)){if(Z===void 0||Z===null)continue;$.set(X,String(Z))}const j=await bossFetch(Q,`https://${K}${V}`,{...J,method:"POST",body:$.toString()});if(J.allowNonZero&&j.code!==0)return j;return j.zpData??{}}export async function fetchGeekUserInfo(Q){return geekGet(Q,"/wapi/zpuser/wap/getUserInfo.json")}export async function fetchGeekBossData(Q,V){const W=await geekGet(Q,"/wapi/zpchat/geek/getBossData",{bossId:V});return W?.data??W??{}}export async function fetchGeekLastMsgs(Q,V=[]){if(!V.length)return[];const W=await geekGet(Q,"/wapi/zpchat/geek/userLastMsg",{friendIds:V.map(String).join(",")});return Array.isArray(W)?W:W?.result??[]}export async function fetchGeekAttachmentResumes(Q){return(await geekGet(Q,"/wapi/zpgeek/resume/attachment/checkbox.json"))?.resumeList??[]}export async function resolveGeekResumeId(Q,V){const W=await fetchGeekAttachmentResumes(Q);if(V){if(!W.find((j)=>String(j.resumeId)===String(V))){const j=W.map((X)=>X.resumeId||"?").join(", ")||"(空)";throw new P(`指定的 --resume-id=${V} 不在账号简历列表 [${j}] 中`)}return String(V)}if(W.length===0)throw new F("boss resumelist","账号还没有绑定附件简历,请先用 boss resumeupload 上传,或在 BOSS 直聘 App 里上传。");if(W.length===1){const $=W[0].resumeId;if(!$)throw new z("唯一附件简历缺失 resumeId 字段");return String($)}const J=W.map(($)=>`${$.resumeId}(${$.showName||"未命名"})`).join(", ");throw new P(`账号绑定了 ${W.length} 份附件简历,请用 --resume-id 指定其中一份:${J}`)}export async function resolveGeekBoss(Q,V){const W=String(V??"").trim();if(!W)throw new P("boss id cannot be empty");if(/^\d+$/.test(W))return{id:W,friend:null};const J=await findGeekFriendByUid(Q,W);if(!J)throw new F("boss chatlist",`未在沟通列表中找到 ${W},请先用 boss chatlist --side geek 查 uid。`);const $=J.uid??J.friendId;if(!$)throw new z("沟通列表条目缺失 uid / friendId");return{id:String($),friend:J}}export async function resolveGeekBossId(Q,V){return(await resolveGeekBoss(Q,V)).id}export async function geekAddFriend(Q,V,W=""){return geekGet(Q,"/wapi/zpgeek/friend/add.json",{securityId:V,lid:W})}export async function geekOpenChat(Q,V,W=3){await Q.goto(`${q}?id=${encodeURIComponent(String(V))}`);await Q.wait({time:W})}export const CITY_CODES={"全国":"100010000","北京":"101010100","上海":"101020100","广州":"101280100","深圳":"101280600","杭州":"101210100","成都":"101270100","南京":"101190100","武汉":"101200100","西安":"101110100","苏州":"101190400","长沙":"101250100","天津":"101030100","重庆":"101040100","郑州":"101180100","东莞":"101281600","青岛":"101120200","合肥":"101220100","佛山":"101280800","宁波":"101210400","厦门":"101230200","大连":"101070200","珠海":"101280700","无锡":"101190200","济南":"101120100","福州":"101230100","昆明":"101290100","哈尔滨":"101050100","沈阳":"101070100","石家庄":"101090100","贵阳":"101260100","南宁":"101300100","太原":"101100100","海口":"101310100","兰州":"101160100","乌鲁木齐":"101130100","长春":"101060100","南昌":"101240100","常州":"101191100","温州":"101210700","嘉兴":"101210300","徐州":"101190800","香港":"101320100"};export async function isRecruiterSide(Q){await navigateToChat(Q);const V=await bossFetch(Q,`https://${K}/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0`,{allowNonZero:!0});if(V.code===0)return!0;if(V.code===IDENTITY_MISMATCH_CODE)return!1;assertOk(V,"boss 身份探测");return!1}
|
|
@@ -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:_}});
|