publishport-opencli 1.0.6 → 1.0.8
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 +47 -4
- package/clis/chatgpt/auth.js +2 -2
- package/clis/chatgpt/image.js +1 -1
- package/clis/chatgpt/utils.js +1 -1
- package/clis/instagram/_shared/private-publish.js +4 -4
- package/clis/instagram/explore.js +37 -12
- package/clis/instagram/post.js +20 -20
- package/clis/rednote/comments.js +1 -1
- package/clis/toutiao/hot.js +1 -1
- package/clis/toutiao/recommend.js +1 -0
- package/clis/toutiao/utils.js +2 -2
- package/clis/twitter/article.js +53 -12
- package/clis/twitter/profile.js +1 -1
- package/clis/xiaohongshu/comments.js +57 -14
- package/clis/xiaohongshu/human.js +14 -0
- package/clis/xiaohongshu/publish.js +63 -44
- package/clis/zhihu/paginate.js +2 -2
- package/dist/src/browser/managed-chrome.js +1 -1
- package/dist/src/plugin.js +1 -1
- package/dist/src/utils.d.ts +1 -1
- package/dist/src/utils.js +2 -5
- package/package.json +1 -1
package/cli-manifest.json
CHANGED
|
@@ -34440,7 +34440,8 @@
|
|
|
34440
34440
|
"likes",
|
|
34441
34441
|
"time",
|
|
34442
34442
|
"is_reply",
|
|
34443
|
-
"reply_to"
|
|
34443
|
+
"reply_to",
|
|
34444
|
+
"images"
|
|
34444
34445
|
],
|
|
34445
34446
|
"type": "js",
|
|
34446
34447
|
"modulePath": "rednote/comments.js",
|
|
@@ -40283,6 +40284,46 @@
|
|
|
40283
40284
|
"siteSession": "persistent",
|
|
40284
40285
|
"defaultWindowMode": "foreground"
|
|
40285
40286
|
},
|
|
40287
|
+
{
|
|
40288
|
+
"site": "toutiao",
|
|
40289
|
+
"name": "recommend",
|
|
40290
|
+
"description": "今日头条频道推荐流(公开 API,无需登录)",
|
|
40291
|
+
"access": "read",
|
|
40292
|
+
"domain": "www.toutiao.com",
|
|
40293
|
+
"strategy": "public",
|
|
40294
|
+
"browser": false,
|
|
40295
|
+
"args": [
|
|
40296
|
+
{
|
|
40297
|
+
"name": "category",
|
|
40298
|
+
"type": "string",
|
|
40299
|
+
"default": "__all__",
|
|
40300
|
+
"required": false,
|
|
40301
|
+
"help": "频道 (__all__, news_tech, news_finance, news_world, news_sports, news_entertainment, news_military)"
|
|
40302
|
+
},
|
|
40303
|
+
{
|
|
40304
|
+
"name": "limit",
|
|
40305
|
+
"type": "int",
|
|
40306
|
+
"default": 20,
|
|
40307
|
+
"required": false,
|
|
40308
|
+
"help": "返回条数 (1-50)"
|
|
40309
|
+
}
|
|
40310
|
+
],
|
|
40311
|
+
"columns": [
|
|
40312
|
+
"rank",
|
|
40313
|
+
"group_id",
|
|
40314
|
+
"title",
|
|
40315
|
+
"abstract",
|
|
40316
|
+
"source",
|
|
40317
|
+
"tag",
|
|
40318
|
+
"comments",
|
|
40319
|
+
"published_at",
|
|
40320
|
+
"url",
|
|
40321
|
+
"image_url"
|
|
40322
|
+
],
|
|
40323
|
+
"type": "js",
|
|
40324
|
+
"modulePath": "toutiao/recommend.js",
|
|
40325
|
+
"sourceFile": "toutiao/recommend.js"
|
|
40326
|
+
},
|
|
40286
40327
|
{
|
|
40287
40328
|
"site": "toutiao",
|
|
40288
40329
|
"name": "whoami",
|
|
@@ -47794,7 +47835,8 @@
|
|
|
47794
47835
|
"likes",
|
|
47795
47836
|
"time",
|
|
47796
47837
|
"is_reply",
|
|
47797
|
-
"reply_to"
|
|
47838
|
+
"reply_to",
|
|
47839
|
+
"images"
|
|
47798
47840
|
],
|
|
47799
47841
|
"type": "js",
|
|
47800
47842
|
"modulePath": "xiaohongshu/comments.js",
|
|
@@ -48407,7 +48449,7 @@
|
|
|
48407
48449
|
"name": "topics",
|
|
48408
48450
|
"type": "str",
|
|
48409
48451
|
"required": false,
|
|
48410
|
-
"help": "话题标签,逗号分隔,不含 #
|
|
48452
|
+
"help": "话题标签,逗号分隔,不含 # 号。平台话题库匹配不上的会自动跳过并在结果中提示,不影响发布"
|
|
48411
48453
|
},
|
|
48412
48454
|
{
|
|
48413
48455
|
"name": "draft",
|
|
@@ -48419,7 +48461,8 @@
|
|
|
48419
48461
|
],
|
|
48420
48462
|
"columns": [
|
|
48421
48463
|
"status",
|
|
48422
|
-
"detail"
|
|
48464
|
+
"detail",
|
|
48465
|
+
"url"
|
|
48423
48466
|
],
|
|
48424
48467
|
"type": "js",
|
|
48425
48468
|
"modulePath": "xiaohongshu/publish.js",
|
package/clis/chatgpt/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{AuthRequiredError as
|
|
1
|
+
import{AuthRequiredError as G,CommandExecutionError as D}from"@jackwener/opencli/errors";import{registerSiteAuthCommands as K}from"../_shared/site-auth.js";async function H(B){return(await B.getCookies({url:"https://chatgpt.com"})).some((F)=>F.name.startsWith("__Secure-next-auth.session-token")&&F.value)}async function J(B){await B.goto("https://chatgpt.com/");await B.wait(2);const z=await B.evaluate(`(async () => {
|
|
2
2
|
try {
|
|
3
3
|
const res = await fetch('/api/auth/session', { credentials: 'include' });
|
|
4
4
|
if (res.status === 401 || res.status === 403) {
|
|
@@ -14,4 +14,4 @@ import{AuthRequiredError as i,CommandExecutionError as s}from"@jackwener/opencli
|
|
|
14
14
|
} catch (e) {
|
|
15
15
|
return { kind: 'exception', detail: String(e && e.message || e) };
|
|
16
16
|
}
|
|
17
|
-
})()`);if(
|
|
17
|
+
})()`);if(z?.kind==="auth")throw new G("chatgpt.com",z.detail);if(z?.kind==="http")throw new D(`HTTP ${z.httpStatus} from /api/auth/session`);if(z?.kind==="exception")throw new D(`ChatGPT whoami failed: ${z.detail}`);if(!z?.ok)throw new D(`Unexpected ChatGPT probe: ${JSON.stringify(z)}`);return{user_id:z.user_id,name:z.name}}K({site:"chatgpt",domain:"chatgpt.com",loginUrl:"https://auth.openai.com/log-in",columns:["user_id","name"],quickCheck:H,verify:J,poll:async(B)=>{if(!await H(B))throw new G("chatgpt.com","Waiting for ChatGPT session cookie");return J(B)}});
|
package/clis/chatgpt/image.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as H from"node:os";import*as $ from"node:path";import*as I from"node:fs";import{cli as f,Strategy as E}from"@jackwener/opencli/registry";import{saveBase64ToFile as T}from"@jackwener/opencli/utils";import{ArgumentError as y,CommandExecutionError as _,EmptyResultError as v}from"@jackwener/opencli/errors";import{clearChatGPTDraft as C,getChatGPTVisibleImageUrls as x,navigateToProject as k,normalizeBooleanFlag as c,prepareChatGPTImagePaths as w,sendChatGPTMessage as P,unwrapEvaluateResult as h,waitForChatGPTImages as d,getChatGPTImageAssets as u,uploadChatGPTImages as n}from"./utils.js";const N="chatgpt.com";function m(q){if(q.includes("png"))return".png";if(q.includes("webp"))return".webp";if(q.includes("gif"))return".gif";return".jpg"}function l(q){const J=H.homedir();return q.startsWith(J)?`~${q.slice(J.length)}`:q}export function resolveOutputDir(q){const J=String(q||"").trim();if(!J)return $.join(H.homedir(),"Pictures","chatgpt");if(J==="~")return H.homedir();if(J.startsWith("~/"))return $.join(H.homedir(),J.slice(2));return $.resolve(J)}export function nextAvailablePath(q,J,z,W=I.existsSync){let Y=$.join(q,`${J}${z}`);for(let X=1;W(Y);X+=1)Y=$.join(q,`${J}_${X}${z}`);return Y}export function parseImagePaths(q){if(Array.isArray(q))return q.flatMap((J)=>parseImagePaths(J));return String(q??"").split(",").map((J)=>J.trim()).filter(Boolean)}function g(q,J){if(J>0)return`Edit the attached image${J===1?"":"s"}: ${q}`;return`Generate an image of: ${q}`}async function F(q){const J=h(await q.evaluate("window.location.href").catch(()=>""));return typeof J==="string"&&J?J:"https://chatgpt.com"}export const imageCommand=f({site:"chatgpt",name:"image",access:"write",description:"Generate images with ChatGPT web and save them locally",domain:N,strategy:E.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,defaultFormat:"plain",args:[{name:"prompt",positional:!0,required:!0,help:"Image prompt to send to ChatGPT"},{name:"image",help:"Local image path to attach before prompting; comma-separated paths are supported"},{name:"project",valueRequired:!0,help:"Start image generation inside a ChatGPT project ID or /g/g-p-<id> URL"},{name:"op",help:"Output directory (default: ~/Pictures/chatgpt)"},{name:"sd",type:"boolean",default:!1,help:"Skip download shorthand; only show ChatGPT link"},{name:"timeout",type:"int",required:!1,default:240,help:"Max seconds for the overall command (default: 240)"}],columns:["status","file","link"],func:async(q,J)=>{const z=J.prompt,W=parseImagePaths(J.image),Y=resolveOutputDir(J.op),X=J.sd,R=X===""||X===!0||c(X),O=J.timeout;if(!Number.isInteger(O)||O<1)throw new y("--timeout must be a positive integer (seconds)");const S=W.length?await w(W):{ok:!0,paths:[]};if(!S.ok)throw new y(S.reason);if(J.project)await k(q,J.project);else await q.goto(`https://${N}/new`,{settleMs:2000});await C(q);if(W.length){let K;try{K=await n(q,S.paths)}catch(Q){throw new _(`Failed to upload image to ChatGPT: ${Q instanceof Error?Q.message:String(Q)}`)}if(!K?.ok)throw new _(K?.reason||"Failed to upload image to ChatGPT")}const A=await x(q);if(!await P(q,g(z,W.length)))throw new _("Failed to send image prompt to ChatGPT",`Open ${await F(q)} and verify the composer is ready.`);let Z="";for(let K=0;K<10;K++){const Q=await F(q);if(Q.includes("/c/")){Z=Q;break}await q.
|
|
1
|
+
import*as H from"node:os";import*as $ from"node:path";import*as I from"node:fs";import{cli as f,Strategy as E}from"@jackwener/opencli/registry";import{saveBase64ToFile as T}from"@jackwener/opencli/utils";import{ArgumentError as y,CommandExecutionError as _,EmptyResultError as v}from"@jackwener/opencli/errors";import{clearChatGPTDraft as C,getChatGPTVisibleImageUrls as x,navigateToProject as k,normalizeBooleanFlag as c,prepareChatGPTImagePaths as w,sendChatGPTMessage as P,unwrapEvaluateResult as h,waitForChatGPTImages as d,getChatGPTImageAssets as u,uploadChatGPTImages as n}from"./utils.js";const N="chatgpt.com";function m(q){if(q.includes("png"))return".png";if(q.includes("webp"))return".webp";if(q.includes("gif"))return".gif";return".jpg"}function l(q){const J=H.homedir();return q.startsWith(J)?`~${q.slice(J.length)}`:q}export function resolveOutputDir(q){const J=String(q||"").trim();if(!J)return $.join(H.homedir(),"Pictures","chatgpt");if(J==="~")return H.homedir();if(J.startsWith("~/"))return $.join(H.homedir(),J.slice(2));return $.resolve(J)}export function nextAvailablePath(q,J,z,W=I.existsSync){let Y=$.join(q,`${J}${z}`);for(let X=1;W(Y);X+=1)Y=$.join(q,`${J}_${X}${z}`);return Y}export function parseImagePaths(q){if(Array.isArray(q))return q.flatMap((J)=>parseImagePaths(J));return String(q??"").split(",").map((J)=>J.trim()).filter(Boolean)}function g(q,J){if(J>0)return`Edit the attached image${J===1?"":"s"}: ${q}`;return`Generate an image of: ${q}`}async function F(q){const J=h(await q.evaluate("window.location.href").catch(()=>""));return typeof J==="string"&&J?J:"https://chatgpt.com"}export const imageCommand=f({site:"chatgpt",name:"image",access:"write",description:"Generate images with ChatGPT web and save them locally",domain:N,strategy:E.COOKIE,browser:!0,siteSession:"persistent",navigateBefore:!1,defaultFormat:"plain",args:[{name:"prompt",positional:!0,required:!0,help:"Image prompt to send to ChatGPT"},{name:"image",help:"Local image path to attach before prompting; comma-separated paths are supported"},{name:"project",valueRequired:!0,help:"Start image generation inside a ChatGPT project ID or /g/g-p-<id> URL"},{name:"op",help:"Output directory (default: ~/Pictures/chatgpt)"},{name:"sd",type:"boolean",default:!1,help:"Skip download shorthand; only show ChatGPT link"},{name:"timeout",type:"int",required:!1,default:240,help:"Max seconds for the overall command (default: 240)"}],columns:["status","file","link"],func:async(q,J)=>{const z=J.prompt,W=parseImagePaths(J.image),Y=resolveOutputDir(J.op),X=J.sd,R=X===""||X===!0||c(X),O=J.timeout;if(!Number.isInteger(O)||O<1)throw new y("--timeout must be a positive integer (seconds)");const S=W.length?await w(W):{ok:!0,paths:[]};if(!S.ok)throw new y(S.reason);if(J.project)await k(q,J.project);else await q.goto(`https://${N}/new`,{settleMs:2000});await C(q);if(W.length){let K;try{K=await n(q,S.paths)}catch(Q){throw new _(`Failed to upload image to ChatGPT: ${Q instanceof Error?Q.message:String(Q)}`)}if(!K?.ok)throw new _(K?.reason||"Failed to upload image to ChatGPT")}const A=await x(q);if(!await P(q,g(z,W.length)))throw new _("Failed to send image prompt to ChatGPT",`Open ${await F(q)} and verify the composer is ready.`);let Z="";for(let K=0;K<10;K++){const Q=await F(q);if(Q.includes("/c/")){Z=Q;break}await q.sleep(2)}if(!Z)Z=await F(q);const L=await d(q,A,O,Z),V=Z;if(!L.length)throw new v("chatgpt image",`No generated images were detected before timeout. Open ${V} and verify whether ChatGPT finished generating the image.`);if(R)return[{status:"\uD83C\uDFA8 generated",file:"\uD83D\uDCC1 -",link:`\uD83D\uDD17 ${V}`}];const B=await u(q,L);if(!B.length)throw new _("Failed to export generated ChatGPT image assets",`Open ${V} and verify the generated images are visible, then retry.`);const U=Date.now(),M=[];for(let K=0;K<B.length;K+=1){const Q=B[K],D=Q.dataUrl.replace(/^data:[^;]+;base64,/,""),b=B.length>1?`_${K+1}`:"",G=m(Q.mimeType),j=nextAvailablePath(Y,`chatgpt_${U}${b}`,G);await T(D,j);M.push({status:"✅ saved",file:`\uD83D\uDCC1 ${l(j)}`,link:`\uD83D\uDD17 ${V}`})}return M}});
|
package/clis/chatgpt/utils.js
CHANGED
|
@@ -701,7 +701,7 @@ import{htmlToMarkdown as i}from"@jackwener/opencli/utils";import{ArgumentError a
|
|
|
701
701
|
}
|
|
702
702
|
return urls;
|
|
703
703
|
})()
|
|
704
|
-
`)),"chatgpt visible image url extraction")}export async function waitForChatGPTImages(Q,X,Y,Z){const $=new Set(X),W=3,V=Math.max(1,Math.ceil(Y/W));let z=[],q=0;for(let j=0;j<V;j++){await Q.wait(j===0?3:W);let J="";if(Z&&Z.includes("/c/")){J=unwrapEvaluateResult(await Q.evaluate("window.location.href").catch(()=>""));if(J&&!N(J,Z)){await Q.goto(Z);await Q.
|
|
704
|
+
`)),"chatgpt visible image url extraction")}export async function waitForChatGPTImages(Q,X,Y,Z){const $=new Set(X),W=3,V=Math.max(1,Math.ceil(Y/W));let z=[],q=0;for(let j=0;j<V;j++){await Q.wait(j===0?3:W);let J="";if(Z&&Z.includes("/c/")){J=unwrapEvaluateResult(await Q.evaluate("window.location.href").catch(()=>""));if(J&&!N(J,Z)){await Q.goto(Z);await Q.sleep(3)}}if(await isGenerating(Q))continue;if(Z&&Z.includes("/c/")&&j>0&&j%5===0){if(!J||N(J,Z)){await Q.goto(Z);await Q.sleep(3)}}const B=(await getChatGPTVisibleImageUrls(Q)).filter((b)=>!$.has(b));if(B.length===0)continue;const H=B.join(`
|
|
705
705
|
`),D=z.join(`
|
|
706
706
|
`);if(H===D)q+=1;else{z=B;q=1}if(q>=2||j===V-1)return z}return z}export async function getProjectList(Q){await ensureOnChatGPT(Q);if(requireBooleanEvaluateResult(unwrapEvaluateResult(await Q.evaluate(`(() => {
|
|
707
707
|
const button = Array.from(document.querySelectorAll('button'))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as
|
|
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(`
|
|
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=
|
|
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}-${B.randomUUID()}.jpg`)}function qQ(Q){const X=V.parse(Q);return V.join(R.tmpdir(),`opencli-instagram-story-video-${X.name}-${B.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
|
|
@@ -40,7 +40,7 @@ try data.write(to: URL(fileURLWithPath: outputPath))
|
|
|
40
40
|
let payload = ["ok": true]
|
|
41
41
|
let json = try JSONSerialization.data(withJSONObject: payload, options: [])
|
|
42
42
|
FileHandle.standardOutput.write(json)
|
|
43
|
-
`,[Q,X],"generate video cover");return{...readImageAsset(X),cleanupPath:X}}export function readVideoAsset(Q){const X=
|
|
43
|
+
`,[Q,X],"generate video cover");return{...readImageAsset(X),cleanupPath:X}}export function readVideoAsset(Q){const X=U.readFileSync(Q),Z=ZQ(Q),$=FQ(Q);return{filePath:Q,fileName:V.basename(Q),mimeType:"video/mp4",width:Z.width,height:Z.height,durationMs:Z.durationMs,byteLength:X.length,bytes:X,coverImage:$,cleanupPaths:$.cleanupPath?[$.cleanupPath]:[]}}function KQ(Q,X){if(process.platform!=="darwin")throw new K(`Instagram private story publish does not support trimming long videos on ${process.platform}`,"Use macOS for private story video publishing, or trim the video to 15 seconds first");const Z=qQ(Q);A(`
|
|
44
44
|
import AVFoundation
|
|
45
45
|
import Foundation
|
|
46
46
|
|
|
@@ -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<=g)return X;const Z=KQ(Q,g),$=readVideoAsset(Z);return{...$,cleanupPaths:[...$.cleanupPaths||[],Z]}}function b(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=b(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=b(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 m(Q){const X=JSON.stringify(Q),Z=L.createHmac("sha256",o).update(X).digest("hex"),$=new URLSearchParams;$.set("ig_sig_key_version",n);$.set("signed_body",`${Z}.${X}`);return $.toString()}function U(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{...U(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 f(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{...U(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:f(Q)})}}function GQ(Q,X,Z){return{...U(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:f(Q)})}}function HQ(Q,X,Z){return{...U(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 _(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 j(Q,X,Z){let $;for(let q=0;q<w;q+=1)try{return await Q(X,Z)}catch(F){$=F;if(!x(F)||q>=w-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 c(Q){for(const X of Q){if(X.type==="image"){if(X.asset.cleanupPath)Y.rmSync(X.asset.cleanupPath,{force:!0});continue}for(const Z of X.asset.cleanupPaths||[])Y.rmSync(Z,{force:!0});if(X.asset.coverImage.cleanupPath)Y.rmSync(X.asset.coverImage.cleanupPath,{force:!0})}}async function y(Q,X,Z,$,q="feed"){if(X.type==="image"){const V=await j(Q,`https://i.instagram.com/rupload_igphoto/fb_uploader_${Z}`,{method:"POST",headers:OQ(X.asset,Z,$),body:X.asset.bytes}),G=await _(V,"upload");if(String(G?.status||"")!=="ok")throw new K(`Instagram private publish upload failed for ${X.asset.fileName}`);return}const F=await j(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 _(F,"video upload");if(String(N?.status||"")!=="ok")throw new K(`Instagram private publish video upload failed for ${X.asset.fileName}`);const W=await j(Q,`https://i.instagram.com/rupload_igphoto/fb_uploader_${Z}`,{method:"POST",headers:HQ(X.asset,Z,$),body:X.asset.coverImage.bytes}),O=await _(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??i,Z={method:"POST",headers:{...U(Q.apiContext),"Content-Type":"application/json"},body:JSON.stringify(Q.payload)};for(let $=0;$<I;$+=1){const q=await Q.fetcher("https://www.instagram.com/api/v1/media/configure_sidecar/",Z),F=await q.text();let N={};try{N=F?JSON.parse(F):{}}catch{throw new K("Instagram private publish configure_sidecar returned invalid JSON")}if(!q.ok){const O=F?` ${F.slice(0,500)}`:"";throw 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($>=I-1)throw 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}}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 V=N[O],G=$[O];await y(q,V,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");const O=await q("https://www.instagram.com/api/v1/media/configure/",{method:"POST",headers:{...U(Q.apiContext),"Content-Type":"application/x-www-form-urlencoded"},body:buildConfigureBody({uploadId:$[0],caption:Q.caption,jazoest:Q.jazoest})});return{code:(await _(O,"configure"))?.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{c(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,J)=>S(Q.page,G,J)),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:r},O=(G,J)=>m({...buildConfigureToStoryPhotoPayload({uploadId:Z,width:G,height:J,now:X,jazoest:Q.jazoest}),...W}),V=(G,J,B)=>m({...buildConfigureToStoryVideoPayload({uploadId:Z,width:G,height:J,durationMs:B,now:X,jazoest:Q.jazoest}),...W});try{await y($,F,Z,Q.apiContext,"story");if(F.type==="image"){const B=await $("https://i.instagram.com/api/v1/media/configure_to_story/",{method:"POST",headers:{...U(Q.apiContext),"Content-Type":"application/x-www-form-urlencoded"},body:O(F.asset.width,F.asset.height)}),R=await _(B,"configure_to_story");return{mediaPk:String(R?.media?.pk||R?.media?.id||"").split("_")[0]||void 0,uploadId:Z}}await _(await $("https://i.instagram.com/api/v1/media/configure_to_story/",{method:"POST",headers:{...U(Q.apiContext),"Content-Type":"application/x-www-form-urlencoded"},body:O(F.asset.width,F.asset.height)}),"configure_to_story cover");const G=await $("https://i.instagram.com/api/v1/media/configure_to_story/?video=1",{method:"POST",headers:{...U(Q.apiContext),"Content-Type":"application/x-www-form-urlencoded"},body:V(F.asset.width,F.asset.height,F.asset.durationMs)}),J=await _(G,"configure_to_story");return{mediaPk:String(J?.media?.pk||J?.media?.id||"").split("_")[0]||void 0,uploadId:Z}}finally{c([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 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])}}
|
|
@@ -9,19 +9,44 @@ import{cli as e}from"@jackwener/opencli/registry";e({site:"instagram",name:"expl
|
|
|
9
9
|
);
|
|
10
10
|
if (!res.ok) throw new Error('HTTP ' + res.status + ' - make sure you are logged in to Instagram');
|
|
11
11
|
const data = await res.json();
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
|
|
13
|
+
// Instagram no longer populates the flat layout_content.medias[] path. Media
|
|
14
|
+
// objects are now nested across mixed layout shapes (one_by_two_item.clips.
|
|
15
|
+
// items[].media, fill_items[].media, etc.), so recursively walk each sectional
|
|
16
|
+
// item collecting every distinct node.media and dedupe by pk/id/code. See #2091.
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
const medias = [];
|
|
19
|
+
const collect = (node, depth) => {
|
|
20
|
+
if (!node || typeof node !== 'object' || depth > 8) return;
|
|
21
|
+
if (Array.isArray(node)) {
|
|
22
|
+
for (const item of node) collect(item, depth + 1);
|
|
23
|
+
return;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
+
const media = node.media;
|
|
26
|
+
if (media && typeof media === 'object' && !Array.isArray(media)) {
|
|
27
|
+
const key = media.pk ?? media.id ?? media.code;
|
|
28
|
+
if (key != null && !seen.has(key)) {
|
|
29
|
+
seen.add(key);
|
|
30
|
+
medias.push(media);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (const [k, value] of Object.entries(node)) {
|
|
34
|
+
// Don't descend into a collected media object — a carousel's child items
|
|
35
|
+
// carry their own .media and would otherwise be counted as separate posts.
|
|
36
|
+
if (k === 'media') continue;
|
|
37
|
+
if (value && typeof value === 'object') collect(value, depth + 1);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
for (const sec of (data?.sectional_items || [])) collect(sec, 0);
|
|
41
|
+
|
|
42
|
+
const posts = medias.map((media) => ({
|
|
43
|
+
user: media.user?.username || '',
|
|
44
|
+
caption: (media.caption?.text || '').replace(/\\n/g, ' ').substring(0, 100),
|
|
45
|
+
// Clips/reels report engagement via play_count rather than like_count.
|
|
46
|
+
likes: media.like_count ?? media.play_count ?? 0,
|
|
47
|
+
comments: media.comment_count ?? 0,
|
|
48
|
+
type: media.media_type === 1 ? 'photo' : media.media_type === 2 ? 'video' : 'carousel',
|
|
49
|
+
}));
|
|
25
50
|
return posts.slice(0, limit).map((p, i) => ({ rank: i + 1, ...p }));
|
|
26
51
|
})()
|
|
27
52
|
`}]});
|
package/clis/instagram/post.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as b from"node:fs";import*as W from"node:path";import{cli as r,Strategy as p}from"@jackwener/opencli/registry";import{ArgumentError as z,AuthRequiredError as R,CommandExecutionError as Y}from"@jackwener/opencli/errors";import{installInstagramProtocolCapture as a,readInstagramProtocolCapture as t}from"./_shared/protocol-capture.js";import{publishMediaViaPrivateApi as e,publishImagesViaPrivateApi as jj,resolveInstagramPrivatePublishConfig as Gj,isPublishOutcomeUncertain as Nj}from"./_shared/private-publish.js";import{resolveInstagramRuntimeInfo as Oj}from"./_shared/runtime-info.js";import{throwIfFileAccessDenied as Qj}from"../_shared/video-publish.js";const K="https://www.instagram.com/",Xj=new Set([".jpg",".jpeg",".png",".webp"]),Yj=new Set([".mp4"]),C=10,Zj="/tmp/instagram_post_protocol_trace.json";async function k(j,G=!1){if(G){await j.goto(`${K}?__opencli_reset=${Date.now()}`);await j.wait({time:1})}await j.goto(K)}export function buildEnsureComposerOpenJs(){return`
|
|
2
2
|
(() => {
|
|
3
3
|
const path = window.location?.pathname || '';
|
|
4
4
|
const onLoginRoute = /\\/accounts\\/login\\/?/.test(path);
|
|
@@ -61,7 +61,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
61
61
|
const settled = !shared && !composerOpen && !/sharing/.test(visibleText);
|
|
62
62
|
return { ok: shared, failed, settled, url: /\\/p\\//.test(url) ? url : '' };
|
|
63
63
|
})()
|
|
64
|
-
`}function
|
|
64
|
+
`}function _j(j){if(!j)throw new Y("Browser session required for instagram post");return j}function $j(j){if(!j.length)throw new z('Argument "media" is required.',"Provide --media /path/to/file.jpg or --media /path/a.jpg,/path/b.mp4");if(j.length>C)throw new z(`Too many media items: ${j.length}`,`Instagram carousel posts support at most ${C} items`);return j.map((N)=>{const O=W.resolve(String(N||"").trim());if(!O)throw new z("Media path cannot be empty");if(!b.existsSync(O))throw new z(`Media file not found: ${O}`);const Q=W.extname(O).toLowerCase();if(Xj.has(Q))return{type:"image",filePath:O};if(Yj.has(Q))return{type:"video",filePath:O};throw new z(`Unsupported media format: ${Q}`,"Supported formats: images (.jpg, .jpeg, .png, .webp) and videos (.mp4)")})}function qj(j){const G=String(j.media??"").trim();return $j(G.split(",").map((N)=>N.trim()).filter(Boolean))}function Hj(j){if(j.media===void 0)throw new z('Argument "media" is required.',"Provide --media /path/to/file.jpg or --media /path/a.jpg,/path/b.mp4")}function Jj(j){if(!(j instanceof Y))return!1;return j.message.startsWith("Instagram private publish")||j.message.startsWith("Instagram private route")}function x(j,G){return[{status:"✅ Posted",detail:Mj(j),url:G}]}function Tj(j,G){const N=j instanceof Error?j.message:String(j),O=G instanceof Error?G.message:String(G);return`Private route failed first: ${N}. UI fallback then failed: ${O}`}async function Vj(j){const G=await Gj(j.page),N=j.mediaItems.every((Q)=>Q.type==="image")?await jj({page:j.page,imagePaths:j.mediaItems.map((Q)=>Q.filePath),caption:j.content,apiContext:G.apiContext,jazoest:G.jazoest}):await e({page:j.page,mediaItems:j.mediaItems,caption:j.content,apiContext:G.apiContext,jazoest:G.jazoest}),O=N.code?new URL(`/p/${N.code}/`,K).toString():j.canDiffProfile?await w(j.page,j.existingPostPaths):"";return x(j.mediaItems,O)}async function zj(j){let G,N=null;for(let O=0;O<j.commandAttemptBudget;O++){let Q=!1;try{await k(j.page,j.forceFreshStart||O>0);await j.installProtocolCapture();await j.page.wait({time:2});await A(j.page);await y(j.page);const X=await v(j.page,j.mediaItems);if(j.preUploadDelaySeconds>0)await j.page.wait({time:j.preUploadDelaySeconds});let _=!1,Z=null;for(const q of X){let T=q;for(let L=0;L<j.uploadAttemptBudget;L++){await Aj(j.page,j.mediaItems,T);const B=await c(j.page,j.previewProbeWindowSeconds);if(B.state==="preview"){_=!0;break}if(B.state==="failed"){Z=D(B.detail);for(let F=0;F<j.inlineUploadRetryBudget;F++){if(!await Cj(j.page))break;await j.page.wait({time:3});const V=await c(j.page,Math.max(3,Math.floor(j.previewProbeWindowSeconds/2)));if(V.state==="preview"){_=!0;break}if(V.state!=="failed")break}if(_)break;await bj(j.page);await A(j.page);if(L<j.uploadAttemptBudget-1){try{await j.drainProtocolCapture();await k(j.page,!0);await j.installProtocolCapture();await j.page.wait({time:2});await A(j.page);await y(j.page);T=await I(j.page,T,j.mediaItems);if(j.preUploadDelaySeconds>0)await j.page.wait({time:j.preUploadDelaySeconds})}catch{throw Z}await j.page.wait({time:1.5});continue}break}break}if(_)break}if(!_){if(Z)throw Z;await kj(j.page,j.finalPreviewWaitSeconds)}try{await hj(j.page)}catch(q){await Ej(j.page,q)}if(j.content){await Sj(j.page,j.content);await vj(j.page,j.content)}if(j.preShareDelaySeconds>0)await j.page.wait({time:j.preShareDelaySeconds});await d(j.page,["Share","分享"],"caption");Q=!0;let $="";try{$=await g(j.page)}catch(q){if(q instanceof Y&&q.message==="Instagram post share failed"&&await Uj(j.page)){await j.page.wait({time:Math.max(2,j.preShareDelaySeconds)});$=await g(j.page)}else throw q}await j.drainProtocolCapture();if(!$&&j.canDiffProfile)$=await w(j.page,j.existingPostPaths);return x(j.mediaItems,$)}catch(X){G=X;if(X instanceof Y&&X.message!=="Failed to open Instagram post composer")N=X;if(X instanceof R)throw X;if(Q){let Z="";if(j.canDiffProfile)try{Z=await w(j.page,j.existingPostPaths)}catch{}if(Z)return x(j.mediaItems,Z);const $=X instanceof Error?X.message:String(X);throw new Y(`Instagram share was clicked but the outcome could not be confirmed: ${$}`,"The post may already be live. Do NOT rerun this command blindly — open the profile (or rerun after checking) and confirm no new post exists first; retrying on a false failure creates a duplicate post.")}if(!(X instanceof Y)||O===j.commandAttemptBudget-1){if(X instanceof Y&&X.message==="Failed to open Instagram post composer"&&N)throw N;throw X}let _=!1;if(j.mediaItems.length>=10&&j.page.closeWindow)try{await j.drainProtocolCapture();await j.page.closeWindow();_=!0}catch{}if(!_){await A(j.page);await j.page.wait({time:1})}}}throw G instanceof Error?G:new Y("Instagram post failed")}async function y(j){const G=await j.evaluate(buildEnsureComposerOpenJs());if(!G?.ok){if(G?.reason==="auth")throw new R("www.instagram.com","Instagram login required before posting");throw new Y("Failed to open Instagram post composer")}}async function A(j){for(let G=0;G<4;G++){if(!(await j.evaluate(`
|
|
65
65
|
(() => {
|
|
66
66
|
const isVisible = (el) => {
|
|
67
67
|
if (!(el instanceof HTMLElement)) return false;
|
|
@@ -105,7 +105,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
105
105
|
|
|
106
106
|
return { ok: false };
|
|
107
107
|
})()
|
|
108
|
-
`))?.ok)return;await j.wait({time:0.5})}}async function
|
|
108
|
+
`))?.ok)return;await j.wait({time:0.5})}}async function U(j,G){const N=G.some((Q)=>Q.type==="video"),O=await j.evaluate(`
|
|
109
109
|
((includesVideo) => {
|
|
110
110
|
const isVisible = (el) => {
|
|
111
111
|
if (!(el instanceof HTMLElement)) return false;
|
|
@@ -165,12 +165,12 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
165
165
|
});
|
|
166
166
|
return { ok: true, selectors };
|
|
167
167
|
})(${JSON.stringify(N)})
|
|
168
|
-
`);if(!O?.ok||!O.selectors?.length)throw new Y("Instagram upload input not found","Open the new-post composer in a logged-in browser session and retry");return O.selectors}async function
|
|
168
|
+
`);if(!O?.ok||!O.selectors?.length)throw new Y("Instagram upload input not found","Open the new-post composer in a logged-in browser session and retry");return O.selectors}async function v(j,G){try{return await U(j,G)}catch(N){if(!(N instanceof Y)||!N.message.includes("upload input not found"))throw N;await y(j);await j.wait({time:1.5});try{return await U(j,G)}catch(O){if(!(O instanceof Y)||!O.message.includes("upload input not found"))throw O;await k(j,!0);await j.wait({time:2});await A(j);await y(j);await j.wait({time:2});return U(j,G)}}}function Kj(j){const G=j.match(/data-opencli-ig-upload-index="(\d+)"/);if(!G)return null;const N=Number.parseInt(G[1]||"",10);return Number.isNaN(N)?null:N}async function I(j,G,N){const O=await v(j,N),Q=Kj(G);if(Q!==null&&O[Q])return O[Q];return O[0]||G}async function Lj(j,G,N){const O=G.map(($)=>{const q=W.extname($).toLowerCase(),T=q===".png"?"image/png":q===".webp"?"image/webp":"image/jpeg";return{name:W.basename($),type:T,base64:b.readFileSync($).toString("base64")}}),Q=`__opencliInstagramUpload_${Date.now()}_${Math.random().toString(36).slice(2)}`,X=262144;await j.evaluate(`
|
|
169
169
|
(() => {
|
|
170
170
|
window[${JSON.stringify(Q)}] = [];
|
|
171
171
|
return { ok: true };
|
|
172
172
|
})()
|
|
173
|
-
`);const
|
|
173
|
+
`);const _=JSON.stringify(O);for(let $=0;$<_.length;$+=X){const q=_.slice($,$+X);await j.evaluate(`
|
|
174
174
|
(() => {
|
|
175
175
|
const key = ${JSON.stringify(Q)};
|
|
176
176
|
const chunk = ${JSON.stringify(q)};
|
|
@@ -179,7 +179,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
179
179
|
window[key] = parts;
|
|
180
180
|
return { ok: true, count: parts.length };
|
|
181
181
|
})()
|
|
182
|
-
`)}const
|
|
182
|
+
`)}const Z=await j.evaluate(`
|
|
183
183
|
(() => {
|
|
184
184
|
const selector = ${JSON.stringify(N)};
|
|
185
185
|
const key = ${JSON.stringify(Q)};
|
|
@@ -212,7 +212,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
212
212
|
return { ok: false, error: String(error) };
|
|
213
213
|
}
|
|
214
214
|
})()
|
|
215
|
-
`);if(!
|
|
215
|
+
`);if(!Z?.ok)throw new Y(Z?.error||"Instagram fallback file injection failed")}async function Bj(j,G){await j.evaluate(`
|
|
216
216
|
(() => {
|
|
217
217
|
const input = document.querySelector(${JSON.stringify(G)});
|
|
218
218
|
if (!(input instanceof HTMLInputElement)) return { ok: false };
|
|
@@ -255,7 +255,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
255
255
|
if (failed) return { state: 'failed', detail: dialogText || 'Something went wrong' };
|
|
256
256
|
return { state: 'pending', detail: dialogText || '' };
|
|
257
257
|
})()
|
|
258
|
-
`}async function M(j){const G=await j.evaluate(buildInspectUploadStageJs());if(G?.state)return G;if(G?.ok===!0)return{state:"preview",detail:G.detail};return{state:"pending",detail:G?.detail}}function
|
|
258
|
+
`}async function M(j){const G=await j.evaluate(buildInspectUploadStageJs());if(G?.state)return G;if(G?.ok===!0)return{state:"preview",detail:G.detail};return{state:"pending",detail:G?.detail}}function D(j){return new Y("Instagram image upload failed",j?`Instagram rejected the upload: ${j}`:"Instagram rejected the upload before the preview stage")}async function Aj(j,G,N){const O=G.map((X)=>X.filePath);if(!j.setFileInput)throw new Y("Instagram posting requires Browser Bridge file upload support","Use Browser Bridge or another browser mode that supports setFileInput");let Q=N;for(let X=0;X<2;X++)try{await j.setFileInput(O,Q);await Bj(j,Q);return}catch(_){const Z=_ instanceof Error?_.message:String(_);if((Z.includes("No element found matching selector")||Z.includes("Could not find node with given id")||Z.includes("No node with given id found"))&&X===0){Q=await I(j,Q,G);continue}if(!Z.includes("Unknown action")&&!Z.includes("set-file-input")&&!Z.includes("not supported")&&!Z.includes("Not allowed"))throw _;if(G.some((q)=>q.type==="video")){Qj(_);throw new Y("Instagram mixed-media posting requires Browser Bridge file upload support","Use Browser Bridge or another browser mode that supports setFileInput for image/video uploads")}await Lj(j,O,Q);return}}function Mj(j){if(j.every((G)=>G.type==="image"))return j.length===1?"Single image post shared successfully":`${j.length}-image carousel post shared successfully`;return j.length===1?"Single mixed-media post shared successfully":`${j.length}-item mixed-media carousel post shared successfully`}function Dj(j){if(j.length>=10)return 6;if(j.length>=5)return 4;return 3}function Fj(j){if(j.length>=10)return 3;if(j.length>=5)return 1.5;return 0}function Wj(j){if(j.length>=10)return 3;if(j.length>=5)return 3;return 2}function Rj(j){if(j.length>=10)return 6;if(j.length>=5)return 6;return 4}function xj(j){if(j.length>=10)return 12;if(j.length>=5)return 16;return 12}function yj(j){if(j.length>=10)return 4;if(j.length>=5)return 3;return 0}function wj(j){if(j.length>=10)return 3;if(j.length>=5)return 2;return 1}async function bj(j){return!!(await j.evaluate(`
|
|
259
259
|
(() => {
|
|
260
260
|
const isVisible = (el) => {
|
|
261
261
|
if (!(el instanceof HTMLElement)) return false;
|
|
@@ -278,7 +278,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
278
278
|
}
|
|
279
279
|
return { ok: false };
|
|
280
280
|
})()
|
|
281
|
-
`))?.ok}async function
|
|
281
|
+
`))?.ok}async function Cj(j){return!!(await j.evaluate(`
|
|
282
282
|
(() => {
|
|
283
283
|
const isVisible = (el) => {
|
|
284
284
|
if (!(el instanceof HTMLElement)) return false;
|
|
@@ -312,7 +312,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
312
312
|
}
|
|
313
313
|
return { ok: false };
|
|
314
314
|
})()
|
|
315
|
-
`))?.ok}async function
|
|
315
|
+
`))?.ok}async function kj(j,G=12){const N=Math.max(1,Math.ceil(G));for(let O=0;O<N;O++){const Q=await M(j);if(Q.state==="preview")return;if(Q.state==="failed"){await j.screenshot({path:"/tmp/instagram_post_preview_debug.png"});throw D("Inspect /tmp/instagram_post_preview_debug.png. "+(Q.detail||""))}if(O<N-1)await j.wait({time:1})}await j.screenshot({path:"/tmp/instagram_post_preview_debug.png"});throw new Y("Instagram image preview did not appear after upload","The selected file input may not match the active composer; inspect /tmp/instagram_post_preview_debug.png")}async function c(j,G=4){const N=Math.max(1,Math.ceil(G*2));for(let O=0;O<N;O++){const Q=await M(j);if(Q.state!=="pending")return Q;if(O<N-1)await j.wait({time:0.5})}return{state:"pending"}}export function buildClickActionJs(j,G="any"){return`
|
|
316
316
|
((labels, scope) => {
|
|
317
317
|
const isVisible = (el) => {
|
|
318
318
|
if (!(el instanceof HTMLElement)) return false;
|
|
@@ -367,7 +367,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
367
367
|
}
|
|
368
368
|
return { ok: false };
|
|
369
369
|
})(${JSON.stringify(j)}, ${JSON.stringify(G)})
|
|
370
|
-
`}async function
|
|
370
|
+
`}async function d(j,G,N="any"){const O=await j.evaluate(buildClickActionJs(G,N));if(!O?.ok)throw new Y(`Instagram action button not found: ${G.join(" / ")}`);return O.label||G[0]}async function Uj(j){return!!(await j.evaluate(`
|
|
371
371
|
(() => {
|
|
372
372
|
const isVisible = (el) => {
|
|
373
373
|
if (!(el instanceof HTMLElement)) return false;
|
|
@@ -405,12 +405,12 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
405
405
|
|
|
406
406
|
return { ok: false };
|
|
407
407
|
})()
|
|
408
|
-
`))?.ok}async function
|
|
408
|
+
`))?.ok}async function l(j){return!!(await j.evaluate(`
|
|
409
409
|
(() => {
|
|
410
410
|
const editable = document.querySelector('textarea, [contenteditable="true"]');
|
|
411
411
|
return { ok: !!editable };
|
|
412
412
|
})()
|
|
413
|
-
`))?.ok}async function
|
|
413
|
+
`))?.ok}async function m(j){return!!(await j.evaluate(`
|
|
414
414
|
(() => {
|
|
415
415
|
const editable = document.querySelector('textarea, [contenteditable="true"]');
|
|
416
416
|
const dialogText = Array.from(document.querySelectorAll('[role="dialog"]'))
|
|
@@ -424,7 +424,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
424
424
|
|| dialogText.includes('advanced settings'),
|
|
425
425
|
};
|
|
426
426
|
})()
|
|
427
|
-
`))?.ok}async function
|
|
427
|
+
`))?.ok}async function hj(j){for(let G=0;G<3;G++){if(await m(j))return;try{await d(j,["Next","下一步"],"media")}catch(O){if(O instanceof Y){await j.wait({time:1.5});if(await m(j))return;const Q=await M(j);if(Q.state==="failed")throw D(Q.detail);if(G<2)continue}throw O}await j.wait({time:1.5});if(await l(j))return;const N=await M(j);if(N.state==="failed")throw D(N.detail)}await j.screenshot({path:"/tmp/instagram_post_caption_debug.png"});throw new Y("Instagram caption editor did not appear","Instagram may have changed the publish flow; inspect /tmp/instagram_post_caption_debug.png")}async function sj(j){if(!await l(j)){await j.screenshot({path:"/tmp/instagram_post_caption_debug.png"});throw new Y("Instagram caption editor did not appear","Instagram may have changed the publish flow; inspect /tmp/instagram_post_caption_debug.png")}}async function Ej(j,G){const N=await M(j);if(N.state==="failed")throw D(N.detail);throw G}async function fj(j){return!!(await j.evaluate(`
|
|
428
428
|
(() => {
|
|
429
429
|
const textarea = document.querySelector('[aria-label="Write a caption..."], textarea');
|
|
430
430
|
if (textarea instanceof HTMLTextAreaElement) {
|
|
@@ -484,7 +484,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
484
484
|
|
|
485
485
|
return { ok: true, kind: 'contenteditable' };
|
|
486
486
|
})()
|
|
487
|
-
`))?.ok}async function
|
|
487
|
+
`))?.ok}async function Sj(j,G){if(j.insertText&&await fj(j))try{await j.insertText(G);await j.wait({time:0.3});await j.evaluate(`
|
|
488
488
|
(() => {
|
|
489
489
|
const textarea = document.querySelector('[aria-label="Write a caption..."], textarea');
|
|
490
490
|
if (textarea instanceof HTMLTextAreaElement) {
|
|
@@ -596,7 +596,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
596
596
|
|
|
597
597
|
return { ok: false };
|
|
598
598
|
})(${JSON.stringify(G)})
|
|
599
|
-
`))?.ok)throw new Y("Failed to fill Instagram caption")}async function
|
|
599
|
+
`))?.ok)throw new Y("Failed to fill Instagram caption")}async function Pj(j,G){return!!(await j.evaluate(`
|
|
600
600
|
((content) => {
|
|
601
601
|
const normalized = content.trim();
|
|
602
602
|
const readLexicalText = (node) => {
|
|
@@ -650,7 +650,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
650
650
|
|
|
651
651
|
return { ok: false };
|
|
652
652
|
})(${JSON.stringify(G)})
|
|
653
|
-
`))?.ok}async function
|
|
653
|
+
`))?.ok}async function vj(j,G){for(let N=0;N<6;N++){if(await Pj(j,G))return;if(N<5)await j.wait({time:0.5})}await j.screenshot({path:"/tmp/instagram_post_caption_fill_debug.png"});throw new Y("Instagram caption did not stick before sharing","Inspect /tmp/instagram_post_caption_fill_debug.png for the caption editor state")}async function g(j){let G=0;for(let N=0;N<90;N++){const O=await j.evaluate(buildPublishStatusProbeJs());if(O?.failed){await j.screenshot({path:"/tmp/instagram_post_share_debug.png"});throw new Y("Instagram post share failed","Inspect /tmp/instagram_post_share_debug.png for the share failure state")}if(O?.ok)return O.url||"";if(O?.settled){G+=1;if(G>=3)return""}else G=0;if(N<89)await j.wait({time:1})}await j.screenshot({path:"/tmp/instagram_post_share_debug.png"});throw new Y("Instagram post share confirmation did not appear","Inspect /tmp/instagram_post_share_debug.png for the final publish state")}async function o(j){return(await j.getCookies({domain:"instagram.com"})).find((N)=>N.name==="ds_user_id")?.value||""}async function s(j,G=""){if(G){const O=await Oj(j),Q=await j.evaluate(`
|
|
654
654
|
(async () => {
|
|
655
655
|
const userId = ${JSON.stringify(G)};
|
|
656
656
|
const appId = ${JSON.stringify(O.appId||"")};
|
|
@@ -670,7 +670,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
670
670
|
return { ok: false };
|
|
671
671
|
}
|
|
672
672
|
})()
|
|
673
|
-
`);if(Q?.ok&&Q.username)return new URL(`/${Q.username}/`,
|
|
673
|
+
`);if(Q?.ok&&Q.username)return new URL(`/${Q.username}/`,K).toString()}const N=await j.evaluate(`
|
|
674
674
|
(() => {
|
|
675
675
|
const isVisible = (el) => {
|
|
676
676
|
if (!(el instanceof HTMLElement)) return false;
|
|
@@ -695,7 +695,7 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
695
695
|
const path = explicitProfile;
|
|
696
696
|
return { ok: !!path, path };
|
|
697
697
|
})()
|
|
698
|
-
`);if(!N?.ok||!N.path)return"";return new URL(N.path,
|
|
698
|
+
`);if(!N?.ok||!N.path)return"";return new URL(N.path,K).toString()}async function n(j){const G=await j.evaluate(`
|
|
699
699
|
(() => {
|
|
700
700
|
const isVisible = (el) => {
|
|
701
701
|
if (!(el instanceof HTMLElement)) return false;
|
|
@@ -715,4 +715,4 @@ import*as w from"node:fs";import*as W from"node:path";import{cli as s,Strategy a
|
|
|
715
715
|
|
|
716
716
|
return { ok: hrefs.length > 0, hrefs };
|
|
717
717
|
})()
|
|
718
|
-
`);return Array.isArray(G?.hrefs)?G.hrefs.filter(Boolean):[]}async function
|
|
718
|
+
`);return Array.isArray(G?.hrefs)?G.hrefs.filter(Boolean):[]}async function Ij(j){const G=await o(j);if(!G)return null;const N=await s(j,G);if(!N)return null;try{await j.goto(N);await j.wait({time:3});return new Set(await n(j))}catch{return null}}async function w(j,G){const N=await j.getCurrentUrl?.();if(N&&/\/p\//.test(N))return N;const O=await o(j),Q=await s(j,O);if(!Q)return"";await j.goto(Q);await j.wait({time:4});for(let X=0;X<8;X++){const Z=(await n(j)).find(($)=>!G.has($))||"";if(Z)return new URL(Z,K).toString();if(X<7)await j.wait({time:1})}return""}r({site:"instagram",name:"post",access:"write",description:"Post an Instagram feed image or mixed-media carousel",domain:"www.instagram.com",strategy:p.UI,browser:!0,args:[{name:"media",required:!1,valueRequired:!0,help:`Comma-separated media paths (images/videos, up to ${C})`},{name:"content",positional:!0,required:!1,help:"Caption text"},{name:"timeout",type:"int",required:!1,default:300,help:"Max seconds for the overall command (default: 300)"}],columns:["status","detail","url"],validateArgs:Hj,func:async(j,G)=>{const N=_j(j),O=qj(G),Q=String(G.content??"").trim(),X=await Ij(N),_=X!==null,Z=X??new Set,$=Dj(O),q=Fj(O),T=Wj(O),L=Rj(O),B=xj(O),F=yj(O),h=wj(O),V=process.env.OPENCLI_INSTAGRAM_CAPTURE==="1",E=[],f=[],u=async()=>{if(!V)return;await a(N)},S=async()=>{if(!V)return;const H=await t(N);if(H.data.length)E.push(...H.data);if(H.errors.length)f.push(...H.errors)};try{try{return await Vj({page:N,mediaItems:O,content:Q,existingPostPaths:Z,canDiffProfile:_})}catch(H){if(H instanceof R)throw H;if(Nj(H)){let J="",P=!1;if(_)try{J=await w(N,Z);P=!0}catch{}if(J)return x(O,J);if(!P){const i=H instanceof Error?H.message:String(H);throw new Y(`Instagram publish outcome uncertain: ${i}`,"The post may already be live. Do NOT rerun this command blindly — check the profile for a new post first; retrying on a false failure creates a duplicate post.")}}if(!Jj(H))throw H;try{return await zj({page:N,mediaItems:O,content:Q,existingPostPaths:Z,canDiffProfile:_,commandAttemptBudget:$,preUploadDelaySeconds:q,uploadAttemptBudget:T,previewProbeWindowSeconds:L,finalPreviewWaitSeconds:B,preShareDelaySeconds:F,inlineUploadRetryBudget:h,installProtocolCapture:u,drainProtocolCapture:S,forceFreshStart:!0})}catch(J){if(J instanceof R)throw J;if(J instanceof Y)throw new Y(J.message,Tj(H,J));throw J}}}finally{if(V){try{await S()}catch{}b.writeFileSync(Zj,JSON.stringify({data:E,errors:f},null,2))}}}});
|
package/clis/rednote/comments.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as p,Strategy as f}from"@jackwener/opencli/registry";import{ArgumentError as u,AuthRequiredError as h,CommandExecutionError as w,EmptyResultError as y}from"@jackwener/opencli/errors";import{buildCommentsExtractJs as b}from"../xiaohongshu/comments.js";import{buildNoteUrl as
|
|
1
|
+
import{cli as p,Strategy as f}from"@jackwener/opencli/registry";import{ArgumentError as u,AuthRequiredError as h,CommandExecutionError as w,EmptyResultError as y}from"@jackwener/opencli/errors";import{buildCommentsExtractJs as g,normalizeCommentRows as b}from"../xiaohongshu/comments.js";import{buildNoteUrl as R,parseNoteId as x}from"../xiaohongshu/note-helpers.js";const _="Pass a full rednote.com note URL with xsec_token from search results or user/profile context.";function k(o){const e=Number(o??20);if(!Number.isFinite(e)||!Number.isInteger(e))throw new u(`--limit must be an integer between 1 and 50, got ${JSON.stringify(o)}`);if(e<1||e>50)throw new u(`--limit must be between 1 and 50, got ${e}`);return e}p({site:"rednote",name:"comments",access:"read",description:"Read comments from a rednote note (supports nested replies)",domain:"www.rednote.com",strategy:f.COOKIE,navigateBefore:!1,args:[{name:"note-id",required:!0,positional:!0,help:"Full rednote note URL with xsec_token"},{name:"limit",type:"int",default:20,help:"Number of top-level comments (max 50)"},{name:"with-replies",type:"boolean",default:!1,help:"Include nested replies (楼中楼)"}],columns:["rank","author","text","likes","time","is_reply","reply_to","images"],func:async(o,e)=>{const i=k(e.limit),m=Boolean(e["with-replies"]),s=String(e["note-id"]),d=x(s);await o.goto(R(s,{commandName:"rednote comments",cookieRoot:"rednote.com",signedUrlHint:_}));await o.wait({time:2+Math.random()*3});const r=await o.evaluate(g(m,i));if(!r||typeof r!=="object")throw new y("rednote/comments","Unexpected evaluate response");if(r.securityBlock)throw new w("Rednote security block: the note detail page was blocked by risk control.",/^https?:\/\//.test(s)?"The page may be temporarily restricted. Try again later or from a different session.":"Try using a full URL from search results (with xsec_token) instead of a bare note ID.");if(r.loginWall)throw new h("www.rednote.com","Note comments require login");const a=b(r.results,"rednote/comments"),l=(t,n)=>({rank:n+1,author:t.author,text:t.text,likes:t.likes,time:t.time,is_reply:t.is_reply,reply_to:t.reply_to,images:t.images??[]});if(m){const t=[];let n=0;for(const c of a){if(!c.is_reply)n++;if(n>i)break;t.push(c)}return t.map(l)}return a.slice(0,i).map(l)}});
|
package/clis/toutiao/hot.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as M,Strategy as P}from"@jackwener/opencli/registry";import{CommandExecutionError as z,EmptyResultError as Q}from"@jackwener/opencli/errors";import{HOT_BOARD_URL as V,mapHotRow as W,parseHotLimit as X}from"./utils.js";M({site:"toutiao",name:"hot",access:"read",description:"今日头条首页热榜(公开 API,无需登录)",domain:"www.toutiao.com",strategy:P.PUBLIC,browser:!1,args:[{name:"limit",type:"int",default:30,help:"返回条数 (1-50)"}],columns:["rank","group_id","title","query","hot_value","label","url","image_url"],func:async(
|
|
1
|
+
import{cli as M,Strategy as P}from"@jackwener/opencli/registry";import{CommandExecutionError as z,EmptyResultError as Q}from"@jackwener/opencli/errors";import{HOT_BOARD_URL as V,mapHotRow as W,parseHotLimit as X}from"./utils.js";M({site:"toutiao",name:"hot",access:"read",description:"今日头条首页热榜(公开 API,无需登录)",domain:"www.toutiao.com",strategy:P.PUBLIC,browser:!1,args:[{name:"limit",type:"int",default:30,help:"返回条数 (1-50)"}],columns:["rank","group_id","title","query","hot_value","label","url","image_url"],func:async(I)=>{const J=X(I?.limit,30);let F;try{F=await fetch(V,{headers:{"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",Accept:"application/json",Referer:"https://www.toutiao.com/"}})}catch(q){throw new z(`toutiao hot-board request failed: ${q?.message||q}`)}if(!F.ok)throw new z(`toutiao hot-board failed: HTTP ${F.status}`);let b;try{b=await F.json()}catch(q){throw new z(`toutiao hot-board returned malformed JSON: ${q?.message||q}`)}if(b?.status&&b.status!=="success")throw new z(`toutiao hot-board returned status=${b.status}`);if(b?.error||b?.message)throw new z(`toutiao hot-board returned error: ${b.error||b.message}`);const G=(Array.isArray(b?.data)?b.data:[]).map(W).filter(Boolean).slice(0,J);if(G.length===0)throw new Q("toutiao hot","上游 hot-board 返回空列表。");return G.map((q,K)=>({...q,rank:K+1}))}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{cli as V,Strategy as W}from"@jackwener/opencli/registry";import{CommandExecutionError as z,EmptyResultError as X}from"@jackwener/opencli/errors";import{RECOMMEND_CATEGORIES as Y,RECOMMEND_URL as Z,mapRecommendRow as $,parseRecommendCategory as h,parseRecommendLimit as v}from"./utils.js";V({site:"toutiao",name:"recommend",access:"read",description:"今日头条频道推荐流(公开 API,无需登录)",domain:"www.toutiao.com",strategy:W.PUBLIC,browser:!1,args:[{name:"category",type:"string",default:"__all__",help:`频道 (${Y.join(", ")})`},{name:"limit",type:"int",default:20,help:"返回条数 (1-50)"}],columns:["rank","group_id","title","abstract","source","tag","comments","published_at","url","image_url"],func:async(F)=>{const H=h(F?.category,"__all__"),K=v(F?.limit,20),P=`${Z}?category=${encodeURIComponent(H)}`;let B;try{B=await fetch(P,{headers:{"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",Accept:"application/json",Referer:"https://www.toutiao.com/"}})}catch(b){throw new z(`toutiao recommend request failed: ${b?.message||b}`)}if(!B.ok)throw new z(`toutiao recommend failed: HTTP ${B.status}`);let q;try{q=await B.json()}catch(b){throw new z(`toutiao recommend returned malformed JSON: ${b?.message||b}`)}if(q?.message&&q.message!=="success")throw new z(`toutiao recommend returned message=${q.message}`);if(!Array.isArray(q?.data))throw new z("toutiao recommend returned a non-array data field");const J=q.data.map($).filter(Boolean).slice(0,K);if(J.length===0)throw new X("toutiao recommend",`频道 ${H} 返回空列表。`);return J.map((b,Q)=>({...b,rank:Q+1}))}});
|