publishport-opencli 1.8.5-pp.28 → 1.8.5-pp.29
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 +1 -0
- package/clis/baijiahao/article.js +1 -1
- package/clis/bilibili/article.js +1 -1
- package/clis/bilibili/search.js +1 -1
- package/clis/cnblogs/article.js +1 -1
- package/clis/csdn/article.js +1 -1
- package/clis/douban/article.js +1 -1
- package/clis/eastmoney/article.js +1 -1
- package/clis/imooc/article.js +1 -1
- package/clis/jianshu/article.js +1 -1
- package/clis/jike/comment.js +6 -6
- package/clis/jike/create.js +6 -6
- package/clis/jike/like.js +12 -4
- package/clis/jike/repost.js +10 -10
- package/clis/juejin/article.js +1 -1
- package/clis/medium/article.js +1 -1
- package/clis/oschina/article.js +1 -1
- package/clis/segmentfault/article.js +1 -1
- package/clis/sohu/article.js +1 -1
- package/clis/toutiao/article.js +1 -1
- package/clis/weibo/article.js +1 -1
- package/clis/weixin/article.js +1 -1
- package/clis/woshipm/article.js +1 -1
- package/clis/xueqiu/article.js +1 -1
- package/clis/zhihu/write-shared.js +1 -1
- package/package.json +1 -1
package/cli-manifest.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as F,CommandExecutionError as R}from"@jackwener/opencli/errors";import{cli as h,Strategy as P}from"@jackwener/opencli/registry";import{publishArticle as m}from"../_shared/article/publish.js";import{readFile as v,stat as n}from"node:fs/promises";function b(G){if(!G.execute)throw new F("INVALID_INPUT","此百家号写操作需要 --execute
|
|
1
|
+
import{CliError as F,CommandExecutionError as R}from"@jackwener/opencli/errors";import{cli as h,Strategy as P}from"@jackwener/opencli/registry";import{publishArticle as m}from"../_shared/article/publish.js";import{readFile as v,stat as n}from"node:fs/promises";function b(G){if(!G.execute)throw new F("INVALID_INPUT","此百家号写操作需要 --execute 参数才能真正发布。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function w(G){const D=typeof G.text==="string"?G.text:void 0,z=typeof G.file==="string"?G.file:void 0;if(D&&z)throw new F("INVALID_INPUT","正文和 --file 只能选一个");let K=D??"";if(z){let Q;try{Q=await n(z)}catch{throw new F("INVALID_INPUT",`找不到文件: ${z}`)}if(!Q.isFile())throw new F("INVALID_INPUT",`--file 必须是可读文本文件: ${z}`);let Z;try{Z=await v(z)}catch{throw new F("INVALID_INPUT",`文件无法读取: ${z}`)}try{K=new TextDecoder("utf-8",{fatal:!0}).decode(Z)}catch{throw new F("INVALID_INPUT",`文件必须是 UTF-8 编码: ${z}`)}}if(!K.trim())throw new F("INVALID_INPUT","正文不能为空");return K}function f(G,D,z,K,Q={}){return[{status:"success",outcome:K,message:G,target_type:D,target:z,...Q}]}export const baijiahaoProfile={home:"https://baijiahao.baidu.com",outputFormat:"html",preprocessConfig:{},image:{skip:["baijiahao.baidu.com","bdstatic.com","bcebos.com"],uploadFn:async(G,D)=>{const z=await fetch(G,{credentials:"omit"});if(!z.ok)throw Error("图片下载失败: "+G+" HTTP "+z.status);let K=await z.blob();K=await D.webpToJpeg(K);const Q=new FormData;Q.append("media",K,"image.jpg");Q.append("type","image");Q.append("app_id","1589639493090963");Q.append("is_waterlog","1");Q.append("save_material","1");Q.append("no_compress","0");Q.append("is_events","");Q.append("article_type","news");const X=await(await fetch("https://baijiahao.baidu.com/pcui/picture/uploadproxy",{method:"POST",credentials:"include",body:Q})).json();if(X.errmsg!=="success"||!X.ret||!X.ret.https_url)throw Error(X.errmsg||"图片上传失败");return{url:X.ret.https_url}}},publish:async(G,D)=>{const z=G.params||{},K=await fetch("https://baijiahao.baidu.com/builder/rc/edit",{credentials:"include"}),Z=(await K.text()).match(/window\.__BJH__INIT__AUTH__\s*=\s*['"]([^'"]+)['"]/);if(!Z)return{ok:!1,stage:"auth",status:K.status,message:"登录失效,请重新登录百家号"};const X=Z[1],Y=G.content;if(G.draftOnly){const $=new URLSearchParams({title:G.title,content:Y,feed_cat:"1",len:String(Y.length),activity_list:JSON.stringify([{id:408,is_checked:0}]),source_reprinted_allow:"0",original_status:"0",original_handler_status:"1",isBeautify:"false",subtitle:"",bjhtopic_id:"",bjhtopic_info:"",type:"news"}),H=await fetch("https://baijiahao.baidu.com/pcui/article/save?callback=bjhdraft",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",token:X},body:$}),V=(await H.text()).replace(/^bjhdraft\(/,"").replace(/\)$/,"");let A=null;try{A=JSON.parse(V)}catch(d){}if(!A||A.errmsg!=="success"||!A.ret||!A.ret.article_id)return{ok:!1,stage:"save",status:H.status,message:A&&A.errmsg||"保存草稿失败"};const _=String(A.ret.article_id),I="https://baijiahao.baidu.com/builder/rc/edit?type=news&article_id="+_;return{ok:!0,id:_,url:I,draft:!0}}if(!z.cover||!String(z.cover).trim())return{ok:!1,stage:"cover",message:"正式发布百家号文章必须提供封面图(--cover)"};const B=String(z.cover);let q="";try{q=new URL(B,location.href).hostname}catch($){}const O=/(^|\.)baijiahao\.baidu\.com$|(^|\.)bdstatic\.com$|(^|\.)bcebos\.com$/.test(q);let W="";if(O)W=B;else try{const $=q===location.hostname,H=await fetch(B,{credentials:$?"include":"omit"});if(!H.ok)return{ok:!1,stage:"cover",status:H.status,message:"封面图下载失败:"+z.cover};let M=await H.blob();if(/text\/html/i.test(M.type))return{ok:!1,stage:"cover",message:"封面图下载到的是 HTML 页面而非图片(防盗链/登录墙?):"+z.cover};M=await D.webpToJpeg(M);const V=new FormData;V.append("media",M,"cover.jpg");V.append("type","image");V.append("app_id","1589639493090963");V.append("is_waterlog","1");V.append("save_material","1");V.append("no_compress","0");V.append("is_events","");V.append("article_type","news");const _=await(await fetch("https://baijiahao.baidu.com/pcui/picture/uploadproxy",{method:"POST",credentials:"include",body:V})).json();if(_.errmsg!=="success"||!_.ret||!_.ret.https_url)return{ok:!1,stage:"cover",message:"封面图上传失败:"+(_.errmsg||"未知错误")};W=_.ret.https_url}catch($){return{ok:!1,stage:"cover",message:"封面图处理异常:"+String($&&$.message||$)}}const T=Y+'<img src="'+W+'"><br>',J=[{src:W,cropData:{x:0,y:0,width:2048,height:1365},machine_chooseimg:0,isLegal:1}],j=[{src:W}],S={type:"news",title:G.title,author:z.author?String(z.author):"",abstract:z.abstract?String(z.abstract):"",content:T,auto_mount_goods:"1",len:String(T.length),vertical_cover:W,cover_images:JSON.stringify(J),_cover_images_map:JSON.stringify(j)},U="&activity_list%5B0%5D%5Bid%5D=408&activity_list%5B0%5D%5Bis_checked%5D=0&activity_list%5B1%5D%5Bid%5D=ttv&activity_list%5B1%5D%5Bis_checked%5D=1&activity_list%5B2%5D%5Bid%5D=reward&activity_list%5B2%5D%5Bis_checked%5D=1&activity_list%5B3%5D%5Bid%5D=aigc_bjh_status&activity_list%5B3%5D%5Bis_checked%5D=0&source_reprinted_allow=0&abstract_from=2&isBeautify=false&usingImgFilter=false&cover_layout=one",y="&source=upload&cover_source=upload&subtitle=&bjhtopic_id=&bjhtopic_info=&clue=1&bjhmt=&order_id=&aigc_rebuild=&image_edit_point=%5B%7B%22img_type%22%3A%22cover%22%2C%22img_num%22%3A%7B%22template%22%3A0%2C%22font%22%3A0%2C%22filter%22%3A0%2C%22paster%22%3A0%2C%22cut%22%3A0%2C%22any%22%3A0%7D%7D%2C%7B%22img_type%22%3A%22body%22%2C%22img_num%22%3A%7B%22template%22%3A0%2C%22font%22%3A0%2C%22filter%22%3A0%2C%22paster%22%3A0%2C%22cut%22%3A0%2C%22any%22%3A0%7D%7D%5D",C=new URLSearchParams(S).toString()+U+y,N=await fetch("https://baijiahao.baidu.com/pcui/article/publish?callback=bjhpublish",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",token:X},body:C}),E=(await N.text()).replace(/^bjhpublish\(/,"").replace(/\)$/,"");let L=null;try{L=JSON.parse(E)}catch($){}if(!L||L.errno!==0||!L.ret)return{ok:!1,stage:"publish",status:N.status,message:L&&(L.errmsg||L.errno)||"百家号发布失败"};const x=L.ret.url||"";return{ok:!0,id:x,url:x,draft:!1}},checkAuth:async(G)=>{try{const z=await(await fetch("https://baijiahao.baidu.com/builder/app/appinfo?_="+Date.now(),{credentials:"include"})).json();if(z.errmsg==="success"&&z.data&&z.data.user){const K=z.data.user;return{isAuthenticated:!0,userId:String(K.userid||""),username:K.name||"",avatar:K.avatar||""}}return{isAuthenticated:!1}}catch(D){return{isAuthenticated:!1,error:String(D&&D.message||D)}}}};h({site:"baijiahao",name:"article",access:"write",description:"发布百家号文章。默认正式发布(需 --cover 封面),加 --draft 仅存草稿(草稿不需要封面)。正文默认 Markdown,图片自动转存到百家号图床。",domain:"baijiahao.baidu.com",strategy:P.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(默认 Markdown;传 --html 则视为原始 HTML)"},{name:"file",help:"正文文件路径(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"将正文视为原始 HTML 而非 Markdown"},{name:"cover",help:"封面图 URL(正式发布必填;会先转存到百家号图床。草稿模式可不传)"},{name:"abstract",help:"文章摘要(可空,正式发布时写入;草稿模式忽略)"},{name:"author",help:"作者署名(可空,正式发布时写入;草稿模式忽略)"},{name:"draft",type:"boolean",help:"仅保存草稿,不正式发布(草稿不需要封面)"},{name:"execute",type:"boolean",help:"真正执行写操作。不加此参数时命令拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(G,D)=>{if(!G)throw new R("百家号文章发布需要浏览器会话");b(D);const z=String(D.title??"").trim();if(!z)throw new F("INVALID_INPUT","文章标题不能为空");const K=await w(D),Q=Boolean(D.draft),Z=typeof D.cover==="string"?D.cover.trim():"";if(!Q&&!Z)throw new F("INVALID_INPUT","正式发布百家号文章必须提供封面图:请传 --cover <图片URL>(或加 --draft 只存草稿)");const X={cover:Z,abstract:typeof D.abstract==="string"?D.abstract:"",author:typeof D.author==="string"?D.author:""},Y=await m(G,{title:z,body:K,format:D.html?"html":"markdown",draftOnly:Q,profile:baijiahaoProfile,publishParams:X}),B=Y.images.uploaded.length|0,q=Y.images.failed.length|0;let O=Y.draft?"已保存百家号文章草稿":"已正式发布百家号文章";if(B||q)O+=`(图片:${B} 张已转存${q?`,${q} 张失败`:""})`;return f(O,"article","",Y.draft?"draft":"created",{created_target:"article:"+Y.id,created_url:Y.url})}});
|
package/clis/bilibili/article.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as j,CommandExecutionError as P}from"@jackwener/opencli/errors";import{cli as m,Strategy as o}from"@jackwener/opencli/registry";import{publishArticle as c}from"../_shared/article/publish.js";import{readFile as g,stat as l}from"node:fs/promises";function d(K){if(!K.execute)throw new j("INVALID_INPUT","此写操作需要 --execute
|
|
1
|
+
import{CliError as j,CommandExecutionError as P}from"@jackwener/opencli/errors";import{cli as m,Strategy as o}from"@jackwener/opencli/registry";import{publishArticle as c}from"../_shared/article/publish.js";import{readFile as g,stat as l}from"node:fs/promises";function d(K){if(!K.execute)throw new j("INVALID_INPUT","此写操作需要 --execute 才会真正提交。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function p(K){const G=typeof K.text==="string"?K.text:void 0,M=typeof K.file==="string"?K.file:void 0;if(G&&M)throw new j("INVALID_INPUT","正文和 --file 只能选一个");let z=G??"";if(M){let $;try{$=await l(M)}catch{throw new j("INVALID_INPUT",`文件不存在:${M}`)}if(!$.isFile())throw new j("INVALID_INPUT",`--file 必须是可读的文本文件:${M}`);let Z;try{Z=await g(M)}catch{throw new j("INVALID_INPUT",`文件无法读取:${M}`)}try{z=new TextDecoder("utf-8",{fatal:!0}).decode(Z)}catch{throw new j("INVALID_INPUT",`文件无法以 UTF-8 解码:${M}`)}}if(!z.trim())throw new j("INVALID_INPUT","正文不能为空");return z}function u(K,G,M,z,$={}){return[{status:"success",outcome:z,message:K,target_type:G,target:M,...$}]}export const bilibiliArticleProfile={home:"https://member.bilibili.com/platform/upload/text",originRe:"^https?://([^/]*\\.)?bilibili\\.com(/|$)",outputFormat:"html",preprocessConfig:{removeLinks:!0},image:{uploadFn:async(K,G)=>{var M=G.cookie("bili_jct");if(!M)throw Error("未找到 bili_jct CSRF token,请确认已登录 B站");var z=await fetch(K,{credentials:"omit"});if(!z.ok)throw Error("图片下载失败(HTTP "+z.status+"):"+K.slice(0,120));var $=await z.blob(),Z=new FormData;Z.append("binary",$,"image.jpg");Z.append("csrf",M);var _=await fetch("https://api.bilibili.com/x/article/creative/article/upcover",{method:"POST",credentials:"include",body:Z}),Y=await _.text(),V=null;try{V=JSON.parse(Y)}catch(h){}if(!_.ok||!V||V.code!==0||!V.data||!V.data.url)throw Error("B站图片上传失败:"+(V&&V.message?V.message:Y.slice(0,150)));return{url:V.data.url}},skip:["hdslb.com","bilibili.com","biliimg.com"]},publish:async(K,G)=>{var M=G.cookie("bili_jct");if(!M)return{ok:!1,stage:"csrf",status:0,message:"未找到 bili_jct CSRF token,请确认已登录 B站"};var z=K.params||{},$=z.tid?String(z.tid):"4",Z=z.listId!=null?String(z.listId):"0",_=z.bannerUrl?String(z.bannerUrl):"",Y=z.summary?String(z.summary):"";if(!_&&z.coverSrc)try{var V=await fetch(String(z.coverSrc),{credentials:"omit"});if(!V.ok)return{ok:!1,stage:"cover",status:V.status,message:"封面图下载失败(HTTP "+V.status+"):"+String(z.coverSrc).slice(0,120)};var h=await V.blob(),H=new FormData;H.append("binary",h,"cover.jpg");H.append("csrf",M);var T=await fetch("https://api.bilibili.com/x/article/creative/article/upcover",{method:"POST",credentials:"include",body:H}),v=await T.text(),B=null;try{B=JSON.parse(v)}catch(Q){}if(!T.ok||!B||B.code!==0||!B.data||!B.data.url)return{ok:!1,stage:"cover",status:T.status,message:"封面图上传失败:"+(B&&B.message?B.message:v.slice(0,150))};_=B.data.url}catch(Q){return{ok:!1,stage:"cover",status:0,message:"封面图上传异常:"+String(Q&&Q.message||Q)}}var F=z.tags?String(z.tags):"",U=z.original===!1?!1:!0,N=0;try{var y=document.createElement("div");y.innerHTML=K.content||"";N=(y.textContent||y.innerText||"").replace(/\s+/g,"").length}catch(Q){N=0}var W=new URLSearchParams;W.append("title",K.title);W.append("content",K.content);W.append("csrf",M);W.append("save","0");W.append("tid",$);if(z.category)W.append("category",String(z.category));if(Z)W.append("list_id",Z);if(_)W.append("banner_url",_);if(Y)W.append("summary",Y);if(F)W.append("tags",F);W.append("reprint",U?"0":"1");W.append("original",U?"1":"0");W.append("media_id","0");W.append("spoiler","0");if(N)W.append("words",String(N));var C=await fetch("https://api.bilibili.com/x/article/creative/draft/addupdate",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",Origin:"https://member.bilibili.com",Referer:"https://member.bilibili.com/"},body:W.toString()}),R=await C.text(),L=null;try{L=JSON.parse(R)}catch(Q){}if(!C.ok||!L)return{ok:!1,stage:"draft",status:C.status,message:R.slice(0,300)};if(L.code!==0||!L.data||!L.data.aid)return{ok:!1,stage:"draft",status:C.status,message:(L.message||"保存草稿失败")+"(code="+L.code+")"};var q=String(L.data.aid);if(K.draftOnly){var k="https://member.bilibili.com/platform/upload/text/edit?aid="+q;return{ok:!0,draft:!0,id:q,url:k}}if(!z.category)return{ok:!1,stage:"category",status:0,message:"正式发布缺少必填的专栏分类 category(草稿 aid="+q+" 已存)。请用 `bilibili categories` 取合法分类 id 后传 --category。"};var J=[];try{var D=await fetch("https://api.bilibili.com/x/article/categories",{credentials:"include"}),S=await D.json();if(!S||S.code!==0||!Array.isArray(S.data))return{ok:!1,stage:"category",status:D.status,message:"获取 B站专栏分类失败,无法校验 category(草稿 aid="+q+" 已存):"+(S&&S.message||"code="+(S&&S.code))};(function Q(w){w.forEach(function(E){var I=Array.isArray(E.children)?E.children:[];J.push({id:String(E.id),name:E.name||"",leaf:I.length===0});if(I.length)Q(I)})})(S.data)}catch(Q){return{ok:!1,stage:"category",status:0,message:"获取 B站专栏分类异常,无法校验 category(草稿 aid="+q+" 已存):"+String(Q&&Q.message||Q)}}var x=J.find(function(Q){return Q.id===String(z.category)});if(!x){var f=J.filter(function(Q){return Q.leaf}).map(function(Q){return Q.id+"="+Q.name}).join(" / ");return{ok:!1,stage:"category",status:0,message:"专栏分类 id「"+z.category+"」不存在(草稿 aid="+q+" 已存)。合法叶子分类:"+(f||"(无)")}}if(!x.leaf)return{ok:!1,stage:"category",status:0,message:"专栏分类 id「"+z.category+"」("+x.name+")是父分类,请改用其下的叶子分类 id(草稿 aid="+q+" 已存)。"};var X=new URLSearchParams;X.append("aid",q);X.append("title",K.title);X.append("content",K.content);X.append("category",String(z.category));X.append("list_id",Z);X.append("tid",$);if(_)X.append("banner_url",_);if(Y)X.append("summary",Y);if(F)X.append("tags",F);X.append("reprint",U?"0":"1");X.append("original",U?"1":"0");X.append("media_id","0");X.append("spoiler","0");if(N)X.append("words",String(N));X.append("csrf",M);var O=await fetch("https://api.bilibili.com/x/article/creative/article/submit",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",Origin:"https://member.bilibili.com",Referer:"https://member.bilibili.com/"},body:X.toString()}),b=await O.text(),A=null;try{A=JSON.parse(b)}catch(Q){}if(O.status===412)return{ok:!1,stage:"submit",status:412,message:"B站风控拦截了发表(HTTP 412),草稿已保存(aid="+q+"),可在创作中心改后重试。"};if(!O.ok||!A)return{ok:!1,stage:"submit",status:O.status,message:"正式发布失败:"+b.slice(0,300)+"(草稿 aid="+q+" 已存)"};if(A.code!==0)return{ok:!1,stage:"submit",status:O.status,message:(A.message||"正式发布失败")+"(code="+A.code+",草稿 aid="+q+" 已存)"};var n=A.data&&A.data.aid!=null?String(A.data.aid):q;return{ok:!0,draft:!1,id:n,url:"https://www.bilibili.com/read/cv"+n}}};m({site:"bilibili",name:"article",access:"write",description:"发布 B站专栏长文(opus/article)。默认正式发布,加 --draft 仅存草稿。正文默认 Markdown,图片自动转存至 B站图床。正式发布必填 --category(合法值用 `bilibili categories` 取)。",domain:"member.bilibili.com",strategy:o.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"正文(默认 Markdown;加 --html 则作 HTML 处理)"},{name:"file",help:"正文文件路径(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"将正文视为原始 HTML 而非 Markdown"},{name:"category",help:"专栏分类 id(正式发布必填,叶子分类)。合法值用 `bilibili categories` 列举,禁止臆造。"},{name:"tid",type:"int",default:4,help:"封面版式:4=单图 / 3=三图(按封面图数量定,默认 4)"},{name:"cover",help:"封面图源 URL(可空);会先经 upcover 上传到 B站图床再作 banner_url"},{name:"banner-url",help:"封面图的 B站 CDN url(可空,已有则直接用,跳过 upcover 上传)"},{name:"summary",help:"文章摘要/digest(可空)"},{name:"tags",help:"标签,逗号拼接的自由文本字符串(可空,非闭合词表)"},{name:"reprint",type:"boolean",help:"标记为转载(默认原创);加此参数则 original=0、reprint=1"},{name:"list-id",type:"int",default:0,help:"文集编号(默认 0=不加入文集)"},{name:"draft",type:"boolean",help:"仅保存草稿,不正式发布"},{name:"execute",type:"boolean",help:"实际提交。不加此参数时命令拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(K,G)=>{if(!K)throw new P("B站专栏发布需要浏览器会话");d(G);const M=String(G.title??"").trim();if(!M)throw new j("INVALID_INPUT","文章标题不能为空");const z=await p(G),$=Boolean(G.draft),Z=typeof G.category==="string"?G.category.trim():"";if(!$&&!Z)throw new j("INVALID_INPUT","正式发布 B站专栏必须指定 --category(专栏分类 id)。请先用 `bilibili categories` 列举合法分类 id;如只想存草稿请加 --draft。");const _={category:Z||void 0,tid:G.tid!=null?String(G.tid):"4",coverSrc:typeof G.cover==="string"?G.cover.trim():"",bannerUrl:typeof G["banner-url"]==="string"?G["banner-url"].trim():"",summary:typeof G.summary==="string"?G.summary:"",tags:typeof G.tags==="string"?G.tags.trim():"",original:!G.reprint,listId:G["list-id"]!=null?String(G["list-id"]):"0"},Y=await c(K,{title:M,body:z,format:G.html?"html":"markdown",draftOnly:$,profile:bilibiliArticleProfile,publishParams:_}),V=Y.images.uploaded.length|0,h=Y.images.failed.length|0;let H=Y.draft?"已保存 B站专栏草稿(需在创作中心手动发布)":"已正式提交 B站专栏发表(可能进入审核态,请用 `bilibili drafts` 或访问返回 URL 回查)";if(V||h)H+=`·图片:${V} 张已转存${h?`,${h} 张失败`:""}`;return u(H,"article","",Y.draft?"draft":"created",{created_target:(Y.draft?"draft:":"article:")+Y.id,created_url:Y.url})}});
|
package/clis/bilibili/search.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as n,Strategy as
|
|
1
|
+
import{cli as n,Strategy as d}from"@jackwener/opencli/registry";import{apiGet as c,stripHtml as a}from"./utils.js";n({site:"bilibili",name:"search",access:"read",description:"Search Bilibili videos or users",domain:"www.bilibili.com",strategy:d.COOKIE,args:[{name:"query",required:!0,positional:!0,help:"Search keyword"},{name:"type",default:"video",help:"video or user"},{name:"page",type:"int",default:1,help:"Result page"},{name:"limit",type:"int",default:20,help:"Number of results"}],columns:["rank","title","author","score","bvid","url"],func:async(t,s)=>{const{query:l,type:o="video",page:u=1,limit:p=20}=s,r=o==="user"?"bili_user":"video";return((await c(t,"/x/web-interface/wbi/search/type",{params:{search_type:r,keyword:l,page:u},signed:!0}))?.data?.result??[]).slice(0,Number(p)).map((e,i)=>{if(r==="bili_user")return{rank:i+1,title:a(e.uname??""),author:(e.usign??"").trim(),score:e.fans??0,bvid:"",url:e.mid?`https://space.bilibili.com/${e.mid}`:""};return{rank:i+1,title:a(e.title??""),author:e.author??"",score:e.play??0,bvid:e.bvid??"",url:e.bvid?`https://www.bilibili.com/video/${e.bvid}`:""}})}});
|
package/clis/cnblogs/article.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as Z,CommandExecutionError as M}from"@jackwener/opencli/errors";import{cli as N,Strategy as v}from"@jackwener/opencli/registry";import{publishArticle as U}from"../_shared/article/publish.js";export const cnblogsProfile={home:"https://i.cnblogs.com/posts/edit",outputFormat:"markdown",image:{uploadFn:async(K,z)=>{const G=z.cookie("XSRF-TOKEN");if(!G)throw Error("未找到 XSRF-TOKEN cookie,请确认已登录博客园");const Q=await fetch(K,{credentials:"omit"});if(!Q.ok)throw Error("图片下载失败: "+K+"(HTTP "+Q.status+")");const Y=await Q.blob(),H=new FormData;H.append("image",Y,"image.png");H.append("app","blog");H.append("uploadType","Select");const X=await fetch("https://upload.cnblogs.com/v2/images/cors-upload",{method:"POST",credentials:"include",headers:{"x-xsrf-token":G},body:H}),W=await X.text();if(!X.ok)throw Error("图片上传失败: HTTP "+X.status+" "+W.slice(0,150));let V=null;try{V=JSON.parse(W)}catch(q){}if(!V)throw Error("图片上传失败: 响应不是 JSON - "+W.slice(0,100));const $=V.data||V.url||V.imageUrl||V.src;if(!$||typeof $!=="string")throw Error("图片上传失败: 无法解析图片 URL - "+JSON.stringify(V).slice(0,150));return{url:$}},skip:["cnblogs.com"]},publish:async(K,z)=>{const G=z.cookie("XSRF-TOKEN");if(!G)return{ok:!1,stage:"auth",status:401,message:"未找到 XSRF-TOKEN cookie,请确认已登录博客园并在 i.cnblogs.com 下访问过"};const Q={"Content-Type":"application/json","x-xsrf-token":G},Y={id:null,postType:2,accessPermission:0,title:K.title,url:null,postBody:K.content,categoryIds:null,categories:null,collectionIds:[],inSiteCandidate:!1,inSiteHome:!1,siteCategoryId:null,blogTeamIds:null,isPublished:!1,displayOnHomePage:!1,isAllowComments:!0,includeInMainSyndication:!1,isPinned:!1,showBodyWhenPinned:!1,isOnlyForRegisterUser:!1,isUpdateDateAdded:!1,entryName:null,description:null,featuredImage:null,tags:null,password:null,publishAt:null,datePublished:new Date().toISOString(),dateUpdated:null,isMarkdown:!0,isDraft:!0,autoDesc:null,changePostType:!1,blogId:0,author:null,removeScript:!1,clientInfo:null,changeCreatedTime:!1,canChangeCreatedTime:!1,isContributeToImpressiveBugActivity:!1,usingEditorId:5,sourceUrl:null},H=await fetch("https://i.cnblogs.com/api/posts",{method:"POST",credentials:"include",headers:Q,body:JSON.stringify(Y)}),X=await H.text();if(!H.ok)return{ok:!1,stage:"create",status:H.status,message:X.slice(0,300)};let W=null;try{W=JSON.parse(X)}catch(j){}if(!W||!W.id)return{ok:!1,stage:"create",status:H.status,message:"创建草稿失败: "+X.slice(0,300)};const V=String(W.id),$="https://i.cnblogs.com/articles/edit;postId="+V;if(K.draftOnly)return{ok:!0,draft:!0,id:V,url:$};const q=await fetch("https://i.cnblogs.com/api/articles/"+V,{credentials:"include",headers:Q}),O=await q.text();let B=null;try{B=JSON.parse(O)}catch(j){}const A=B&&B.blogPost;if(!q.ok||!A)return{ok:!1,stage:"publish",status:q.status,message:"读取草稿完整对象失败: "+O.slice(0,300),id:V};A.isPublished=!0;A.isDraft=!1;const F=await fetch("https://i.cnblogs.com/api/posts",{method:"POST",credentials:"include",headers:Q,body:JSON.stringify(A)}),_=await F.text();if(!F.ok)return{ok:!1,stage:"publish",status:F.status,message:_.slice(0,300),id:V};let L=null;try{L=JSON.parse(_)}catch(j){}const S=L&&L.url||$;return{ok:!0,draft:!1,id:V,url:S}}},cnblogsAuthProfile={home:"https://home.cnblogs.com",checkAuth:async(K)=>{try{const z=await fetch("https://home.cnblogs.com/user/CurrentUserInfo",{method:"GET",credentials:"include"});if(!z.ok)return{isAuthenticated:!1,error:"HTTP "+z.status};const G=await z.text(),Q=G.match(/href="\/u\/([^/]+)\/"/),Y=G.match(/<img[^>]+class="pfs"[^>]+src="([^"]+)"/);if(!Q)return{isAuthenticated:!1};const H=Q[1];return{isAuthenticated:!0,userId:H,username:H,avatar:Y?Y[1]:""}}catch(z){return{isAuthenticated:!1,error:String(z&&z.message||z)}}}};function J(K){if(!K.execute)throw new Z("INVALID_INPUT","此命令需要 --execute
|
|
1
|
+
import{CliError as Z,CommandExecutionError as M}from"@jackwener/opencli/errors";import{cli as N,Strategy as v}from"@jackwener/opencli/registry";import{publishArticle as U}from"../_shared/article/publish.js";export const cnblogsProfile={home:"https://i.cnblogs.com/posts/edit",outputFormat:"markdown",image:{uploadFn:async(K,z)=>{const G=z.cookie("XSRF-TOKEN");if(!G)throw Error("未找到 XSRF-TOKEN cookie,请确认已登录博客园");const Q=await fetch(K,{credentials:"omit"});if(!Q.ok)throw Error("图片下载失败: "+K+"(HTTP "+Q.status+")");const Y=await Q.blob(),H=new FormData;H.append("image",Y,"image.png");H.append("app","blog");H.append("uploadType","Select");const X=await fetch("https://upload.cnblogs.com/v2/images/cors-upload",{method:"POST",credentials:"include",headers:{"x-xsrf-token":G},body:H}),W=await X.text();if(!X.ok)throw Error("图片上传失败: HTTP "+X.status+" "+W.slice(0,150));let V=null;try{V=JSON.parse(W)}catch(q){}if(!V)throw Error("图片上传失败: 响应不是 JSON - "+W.slice(0,100));const $=V.data||V.url||V.imageUrl||V.src;if(!$||typeof $!=="string")throw Error("图片上传失败: 无法解析图片 URL - "+JSON.stringify(V).slice(0,150));return{url:$}},skip:["cnblogs.com"]},publish:async(K,z)=>{const G=z.cookie("XSRF-TOKEN");if(!G)return{ok:!1,stage:"auth",status:401,message:"未找到 XSRF-TOKEN cookie,请确认已登录博客园并在 i.cnblogs.com 下访问过"};const Q={"Content-Type":"application/json","x-xsrf-token":G},Y={id:null,postType:2,accessPermission:0,title:K.title,url:null,postBody:K.content,categoryIds:null,categories:null,collectionIds:[],inSiteCandidate:!1,inSiteHome:!1,siteCategoryId:null,blogTeamIds:null,isPublished:!1,displayOnHomePage:!1,isAllowComments:!0,includeInMainSyndication:!1,isPinned:!1,showBodyWhenPinned:!1,isOnlyForRegisterUser:!1,isUpdateDateAdded:!1,entryName:null,description:null,featuredImage:null,tags:null,password:null,publishAt:null,datePublished:new Date().toISOString(),dateUpdated:null,isMarkdown:!0,isDraft:!0,autoDesc:null,changePostType:!1,blogId:0,author:null,removeScript:!1,clientInfo:null,changeCreatedTime:!1,canChangeCreatedTime:!1,isContributeToImpressiveBugActivity:!1,usingEditorId:5,sourceUrl:null},H=await fetch("https://i.cnblogs.com/api/posts",{method:"POST",credentials:"include",headers:Q,body:JSON.stringify(Y)}),X=await H.text();if(!H.ok)return{ok:!1,stage:"create",status:H.status,message:X.slice(0,300)};let W=null;try{W=JSON.parse(X)}catch(j){}if(!W||!W.id)return{ok:!1,stage:"create",status:H.status,message:"创建草稿失败: "+X.slice(0,300)};const V=String(W.id),$="https://i.cnblogs.com/articles/edit;postId="+V;if(K.draftOnly)return{ok:!0,draft:!0,id:V,url:$};const q=await fetch("https://i.cnblogs.com/api/articles/"+V,{credentials:"include",headers:Q}),O=await q.text();let B=null;try{B=JSON.parse(O)}catch(j){}const A=B&&B.blogPost;if(!q.ok||!A)return{ok:!1,stage:"publish",status:q.status,message:"读取草稿完整对象失败: "+O.slice(0,300),id:V};A.isPublished=!0;A.isDraft=!1;const F=await fetch("https://i.cnblogs.com/api/posts",{method:"POST",credentials:"include",headers:Q,body:JSON.stringify(A)}),_=await F.text();if(!F.ok)return{ok:!1,stage:"publish",status:F.status,message:_.slice(0,300),id:V};let L=null;try{L=JSON.parse(_)}catch(j){}const S=L&&L.url||$;return{ok:!0,draft:!1,id:V,url:S}}},cnblogsAuthProfile={home:"https://home.cnblogs.com",checkAuth:async(K)=>{try{const z=await fetch("https://home.cnblogs.com/user/CurrentUserInfo",{method:"GET",credentials:"include"});if(!z.ok)return{isAuthenticated:!1,error:"HTTP "+z.status};const G=await z.text(),Q=G.match(/href="\/u\/([^/]+)\/"/),Y=G.match(/<img[^>]+class="pfs"[^>]+src="([^"]+)"/);if(!Q)return{isAuthenticated:!1};const H=Q[1];return{isAuthenticated:!0,userId:H,username:H,avatar:Y?Y[1]:""}}catch(z){return{isAuthenticated:!1,error:String(z&&z.message||z)}}}};function J(K){if(!K.execute)throw new Z("INVALID_INPUT","此命令需要 --execute 参数才会实际写入博客园。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function y(K){const z=typeof K.text==="string"?K.text:void 0,G=typeof K.file==="string"?K.file:void 0;if(z&&G)throw new Z("INVALID_INPUT","不能同时使用 <text> 和 --file,选其一即可");let Q=z??"";if(G){const{readFile:Y,stat:H}=await import("node:fs/promises");let X;try{X=await H(G)}catch{throw new Z("INVALID_INPUT","文件不存在: "+G)}if(!X.isFile())throw new Z("INVALID_INPUT","必须是可读文本文件: "+G);let W;try{W=await Y(G)}catch{throw new Z("INVALID_INPUT","文件读取失败: "+G)}try{Q=new TextDecoder("utf-8",{fatal:!0}).decode(W)}catch{throw new Z("INVALID_INPUT","文件不是有效 UTF-8 编码: "+G)}}if(!Q.trim())throw new Z("INVALID_INPUT","正文不能为空");return Q}function C(K,z,G,Q,Y={}){return[{status:"success",outcome:Q,message:K,target_type:z,target:G,...Y}]}N({site:"cnblogs",name:"article",access:"write",description:"发布博客园文章(Markdown)。正文默认 Markdown;图片自动转存到博客园图床。",domain:"cnblogs.com",strategy:v.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(Markdown,默认;--html 时按 HTML 处理)"},{name:"file",help:"从文件读取正文(UTF-8,Markdown 默认)"},{name:"html",type:"boolean",help:"将正文当作原始 HTML 而非 Markdown"},{name:"draft",type:"boolean",help:"仅保存草稿,不发布"},{name:"execute",type:"boolean",help:"实际执行写入。不加此参数命令拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(K,z)=>{if(!K)throw new M("博客园文章发布需要浏览器会话");J(z);const G=String(z.title??"").trim();if(!G)throw new Z("INVALID_INPUT","文章标题不能为空");const Q=await y(z),Y=Boolean(z.draft),H=await U(K,{title:G,body:Q,format:z.html?"html":"markdown",draftOnly:Y,profile:cnblogsProfile}),X=H.images.uploaded.length|0,W=H.images.failed.length|0;let V=H.draft?"已保存博客园草稿":"已发布博客园文章";if(X||W)V+="·图片: "+X+" 张已转存"+(W?","+W+" 张失败":"");return C(V,"article","",H.draft?"draft":"created",{created_target:"article:"+H.id,created_url:H.url})}});
|
package/clis/csdn/article.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{CliError as S,CommandExecutionError as k}from"@jackwener/opencli/errors";import{cli as w,Strategy as b}from"@jackwener/opencli/registry";import{publishArticle as f}from"../_shared/article/publish.js";import{checkLogin as o}from"../_shared/article/auth.js";import{readFile as D,stat as p}from"node:fs/promises";function m(z){if(!z.execute)throw new S("INVALID_INPUT","此 CSDN 写操作需要加 --execute
|
|
1
|
+
import{CliError as S,CommandExecutionError as k}from"@jackwener/opencli/errors";import{cli as w,Strategy as b}from"@jackwener/opencli/registry";import{publishArticle as f}from"../_shared/article/publish.js";import{checkLogin as o}from"../_shared/article/auth.js";import{readFile as D,stat as p}from"node:fs/promises";function m(z){if(!z.execute)throw new S("INVALID_INPUT","此 CSDN 写操作需要加 --execute 参数才能真正执行。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function u(z){const J=typeof z.text==="string"?z.text:void 0,G=typeof z.file==="string"?z.file:void 0;if(J&&G)throw new S("INVALID_INPUT","只能用 <text> 或 --file 之一,不能同时传");let X=J??"";if(G){let $;try{$=await p(G)}catch{throw new S("INVALID_INPUT",`文件不存在:${G}`)}if(!$.isFile())throw new S("INVALID_INPUT",`路径必须是可读文本文件:${G}`);let F;try{F=await D(G)}catch{throw new S("INVALID_INPUT",`文件读取失败:${G}`)}try{X=new TextDecoder("utf-8",{fatal:!0}).decode(F)}catch{throw new S("INVALID_INPUT",`文件无法解码为 UTF-8 文本:${G}`)}}if(!X.trim())throw new S("INVALID_INPUT","正文不能为空");return X}function n(z,J,G,X,$={},F="success"){return[{status:F,outcome:X,message:z,target_type:J,target:G,...$}]}const csdnProfile={home:"https://editor.csdn.net/md/",outputFormat:"markdown",needsHtml:!0,image:{skip:["csdnimg.cn","csdn.net"],uploadFn:async(z,J)=>{const G="203803574",X="9znpamsyl2c7cdrr9sas0le9vbc3r6ba";function $(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(Y){var j=Math.random()*16|0,C=Y==="x"?j:j&3|8;return C.toString(16)})}async function F(Y,j){var C=new TextEncoder,I=C.encode(j),v=C.encode(Y),x=await crypto.subtle.importKey("raw",I,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),P=await crypto.subtle.sign("HMAC",x,v),E=new Uint8Array(P),h="";for(var y=0;y<E.byteLength;y++)h+=String.fromCharCode(E[y]);return btoa(h)}async function O(Y,j){var C=$(),I=j==="GET"?`GET
|
|
2
2
|
*/*
|
|
3
3
|
|
|
4
4
|
|
package/clis/douban/article.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{CliError as Y,CommandExecutionError as G}from"@jackwener/opencli/errors";import{cli as I,Strategy as J}from"@jackwener/opencli/registry";import{readFile as U,stat as h}from"node:fs/promises";import{gotoWritePage as z}from"../_shared/article/publish.js";import{normalizeContent as T}from"../_shared/article/format.js";import{markdownToDraftState as v}from"../_shared/article/douban-md2draft.js";function P(L){if(!L.execute)throw new Y("INVALID_INPUT","此命令需要 --execute
|
|
1
|
+
import{CliError as Y,CommandExecutionError as G}from"@jackwener/opencli/errors";import{cli as I,Strategy as J}from"@jackwener/opencli/registry";import{readFile as U,stat as h}from"node:fs/promises";import{gotoWritePage as z}from"../_shared/article/publish.js";import{normalizeContent as T}from"../_shared/article/format.js";import{markdownToDraftState as v}from"../_shared/article/douban-md2draft.js";function P(L){if(!L.execute)throw new Y("INVALID_INPUT","此命令需要 --execute 才会真正发布。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function R(L){const V=typeof L.text==="string"?L.text:void 0,K=typeof L.file==="string"?L.file:void 0;if(V&&K)throw new Y("INVALID_INPUT","text 和 --file 不能同时使用");let Z=V??"";if(K){let X;try{X=await h(K)}catch{throw new Y("INVALID_INPUT",`文件未找到:${K}`)}if(!X.isFile())throw new Y("INVALID_INPUT",`必须是可读文本文件:${K}`);let $;try{$=await U(K)}catch{throw new Y("INVALID_INPUT",`文件无法读取:${K}`)}try{Z=new TextDecoder("utf-8",{fatal:!0}).decode($)}catch{throw new Y("INVALID_INPUT",`文件不是合法 UTF-8 文本:${K}`)}}if(!Z.trim())throw new Y("INVALID_INPUT","正文不能为空");return Z}function _(L,V,K,Z,X={}){return[{status:"success",outcome:Z,message:L,target_type:V,target:K,...X}]}function H(L){let V=0;for(const K of L.blocks||[]){if(K.key==null)K.key="b"+(V++).toString(36);if(K.depth==null)K.depth=0;if(!K.inlineStyleRanges)K.inlineStyleRanges=[];if(!K.entityRanges)K.entityRanges=[];if(!K.data)K.data={}}if(!L.entityMap)L.entityMap={};return L}function M(L){return`(async () => {
|
|
2
2
|
const I = `+JSON.stringify(L)+`;
|
|
3
3
|
const ck = (document.cookie.match(/\\bck="?([^";]+)"?/) || [])[1] || "";
|
|
4
4
|
const uploadAuthToken = (window.__INIT_STATE__ && window.__INIT_STATE__.upload_auth_token) || "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as X,CommandExecutionError as v}from"@jackwener/opencli/errors";import{cli as N,Strategy as h}from"@jackwener/opencli/registry";import{publishArticle as S}from"../_shared/article/publish.js";import{checkLogin as P}from"../_shared/article/auth.js";export const authProfile={home:"https://mp.eastmoney.com",checkAuth:async(B)=>{const H=B.cookie("ct"),z=B.cookie("ut");if(!H||!z)return{isAuthenticated:!1,error:"未检测到登录 cookie(ct/ut),请先登录东方财富"};try{const G=await fetch(`https://caifuhaoapi.eastmoney.com/api/v2/getauthorinfo?platform=&ctoken=${encodeURIComponent(H)}&utoken=${encodeURIComponent(z)}`,{method:"GET",credentials:"include",headers:{"x-requested-with":"fetch"}});if(!G.ok)return{isAuthenticated:!1,error:`账号接口 HTTP ${G.status}`};const J=await G.json();if(J.Success===1&&J.Result&&J.Result.accountId)return{isAuthenticated:!0,userId:String(J.Result.accountId),username:J.Result.accountName||"",avatar:J.Result.portrait||""};return{isAuthenticated:!1}}catch(G){return{isAuthenticated:!1,error:String(G&&G.message||G)}}}};const x={home:"https://mp.eastmoney.com",outputFormat:"html",preprocessConfig:{removeComments:!0,removeSpecialTags:!0,processCodeBlocks:!0,convertSectionToDiv:!0,removeEmptyLines:!0,removeEmptyDivs:!0,removeNestedEmptyContainers:!0,unwrapSingleChildContainers:!0,unwrapNestedFigures:!0,removeTrailingBr:!0,removeDataAttributes:!0,removeSrcset:!0,removeSizes:!0,compactHtml:!0},image:{skip:["gbres.dfcfw.com"],uploadFn:async(B,H)=>{const z=H.cookie("ct"),G=H.cookie("ut");if(!z||!G)throw Error("东方财富图片上传失败:未检测到登录 cookie(ct/ut),请先登录");if(B.indexOf("data:")===0){const Q=B.split(","),Y=(Q[0].match(/:(.*?);/)||[])[1]||"image/png",Z=Y.split("/")[1]||"png",V=atob(Q[1]),T=new Uint8Array(V.length);for(let $=0;$<V.length;$++)T[$]=V.charCodeAt($);const U=new Blob([T],{type:Y}),_=Date.now()+"."+Z,K=new FormData;K.append("file",U,_);K.append("noinlist","1");K.append("utoken",G);K.append("ctoken",z);const W=await(await fetch("https://gbapi.eastmoney.com/iimage/image?platform=",{method:"POST",credentials:"include",body:K})).json();if(W.code===200&&W.data&&W.data.url)return{url:W.data.url};throw Error("东方财富图片二进制上传失败:"+(W.message||"未知错误")+" (code: "+W.code+")")}const J=new URLSearchParams({noinlist:"1",linkUrl:B,ctoken:z,utoken:G}),L=await(await fetch("https://gbapi.eastmoney.com/iimage/image/byLink?platform=",{method:"PUT",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:J.toString()})).json();if(L.code===200&&L.data&&L.data.url)return{url:L.data.url};throw Error("东方财富图片链接上传失败:"+(L.message||"未知错误")+" (code: "+L.code+")")}},publish:async(B,H)=>{const z=H.cookie("ct"),G=H.cookie("ut");if(!z||!G)return{ok:!1,stage:"auth",status:401,message:"未检测到登录 cookie(ct/ut),请先登录东方财富"};const J=new Uint8Array(16);crypto.getRandomValues(J);const M=Array.from(J).map(function(K){return K.toString(16).padStart(2,"0").toUpperCase()}).join("");function L(K){return[{ip:"$IP$"},{deviceid:M},{version:"100"},{plat:"web"},{product:"CFH"},{ctoken:z},{utoken:G},{draftid:K.draftid||""},{drafttype:"0"},{type:"0"},{title:encodeURIComponent(K.title)},{text:encodeURIComponent(K.text)},{columns:"2"},{cover:""},{issimplevideo:"0"},{videos:""},{vods:""},{isoriginal:"0"},{tgProduct:""},{spcolumns:""},{textsource:"0"},{replyauthority:""},{modules:encodeURIComponent("[]")}]}async function Q(K,A){const W=A?"https://mp.eastmoney.com/collect/pc_article/index.html#/?id="+A:"https://mp.eastmoney.com/collect/pc_article/index.html#/",$=JSON.stringify({pageUrl:W,path:"draft/api/Article/SaveDraft",parm:JSON.stringify(K)}),F=await fetch("https://emfront.eastmoney.com/apifront/Tran/GetData?platform=",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:$});if(!F.ok)return{ok:!1,stage:"draft_api",status:F.status,message:"草稿 API 请求失败: "+F.status};let q;try{q=await F.json()}catch(j){return{ok:!1,stage:"draft_api",status:F.status,message:"草稿 API 响应不是有效 JSON"}}if(!q.RRquestSuccess||q.RCode!==200)return{ok:!1,stage:"draft_api",status:q.RCode,message:"草稿 API 错误: "+(q.RMsg||"未知错误")};let O;try{O=JSON.parse(q.RData)}catch(j){return{ok:!1,stage:"draft_parse",status:200,message:"无法解析草稿响应数据"}}if(O.error_code!==0)return{ok:!1,stage:"draft_business",status:O.error_code,message:"草稿业务错误: "+(O.me||"未知错误")};return{ok:!0,data:O}}const Y=L({title:B.title,text:'<div class="xeditor_content cfh_web"></div>'}),Z=await Q(Y);if(!Z.ok)return Z;const V=Z.data&&Z.data.draft_id;if(!V)return{ok:!1,stage:"create",status:200,message:"创建草稿失败:响应缺少 draft_id"};const T=L({draftid:V,title:B.title,text:'<div class="xeditor_content cfh_web">'+B.content+"</div>"}),U=await Q(T,V);if(!U.ok)return U;const _="https://mp.eastmoney.com/collect/pc_article/index.html#/?id="+V;return{ok:!0,draft:!0,id:V,url:_}}};import{readFile as R,stat as C}from"node:fs/promises";function E(B){if(!B.execute)throw new X("INVALID_INPUT","此命令需要 --execute
|
|
1
|
+
import{CliError as X,CommandExecutionError as v}from"@jackwener/opencli/errors";import{cli as N,Strategy as h}from"@jackwener/opencli/registry";import{publishArticle as S}from"../_shared/article/publish.js";import{checkLogin as P}from"../_shared/article/auth.js";export const authProfile={home:"https://mp.eastmoney.com",checkAuth:async(B)=>{const H=B.cookie("ct"),z=B.cookie("ut");if(!H||!z)return{isAuthenticated:!1,error:"未检测到登录 cookie(ct/ut),请先登录东方财富"};try{const G=await fetch(`https://caifuhaoapi.eastmoney.com/api/v2/getauthorinfo?platform=&ctoken=${encodeURIComponent(H)}&utoken=${encodeURIComponent(z)}`,{method:"GET",credentials:"include",headers:{"x-requested-with":"fetch"}});if(!G.ok)return{isAuthenticated:!1,error:`账号接口 HTTP ${G.status}`};const J=await G.json();if(J.Success===1&&J.Result&&J.Result.accountId)return{isAuthenticated:!0,userId:String(J.Result.accountId),username:J.Result.accountName||"",avatar:J.Result.portrait||""};return{isAuthenticated:!1}}catch(G){return{isAuthenticated:!1,error:String(G&&G.message||G)}}}};const x={home:"https://mp.eastmoney.com",outputFormat:"html",preprocessConfig:{removeComments:!0,removeSpecialTags:!0,processCodeBlocks:!0,convertSectionToDiv:!0,removeEmptyLines:!0,removeEmptyDivs:!0,removeNestedEmptyContainers:!0,unwrapSingleChildContainers:!0,unwrapNestedFigures:!0,removeTrailingBr:!0,removeDataAttributes:!0,removeSrcset:!0,removeSizes:!0,compactHtml:!0},image:{skip:["gbres.dfcfw.com"],uploadFn:async(B,H)=>{const z=H.cookie("ct"),G=H.cookie("ut");if(!z||!G)throw Error("东方财富图片上传失败:未检测到登录 cookie(ct/ut),请先登录");if(B.indexOf("data:")===0){const Q=B.split(","),Y=(Q[0].match(/:(.*?);/)||[])[1]||"image/png",Z=Y.split("/")[1]||"png",V=atob(Q[1]),T=new Uint8Array(V.length);for(let $=0;$<V.length;$++)T[$]=V.charCodeAt($);const U=new Blob([T],{type:Y}),_=Date.now()+"."+Z,K=new FormData;K.append("file",U,_);K.append("noinlist","1");K.append("utoken",G);K.append("ctoken",z);const W=await(await fetch("https://gbapi.eastmoney.com/iimage/image?platform=",{method:"POST",credentials:"include",body:K})).json();if(W.code===200&&W.data&&W.data.url)return{url:W.data.url};throw Error("东方财富图片二进制上传失败:"+(W.message||"未知错误")+" (code: "+W.code+")")}const J=new URLSearchParams({noinlist:"1",linkUrl:B,ctoken:z,utoken:G}),L=await(await fetch("https://gbapi.eastmoney.com/iimage/image/byLink?platform=",{method:"PUT",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:J.toString()})).json();if(L.code===200&&L.data&&L.data.url)return{url:L.data.url};throw Error("东方财富图片链接上传失败:"+(L.message||"未知错误")+" (code: "+L.code+")")}},publish:async(B,H)=>{const z=H.cookie("ct"),G=H.cookie("ut");if(!z||!G)return{ok:!1,stage:"auth",status:401,message:"未检测到登录 cookie(ct/ut),请先登录东方财富"};const J=new Uint8Array(16);crypto.getRandomValues(J);const M=Array.from(J).map(function(K){return K.toString(16).padStart(2,"0").toUpperCase()}).join("");function L(K){return[{ip:"$IP$"},{deviceid:M},{version:"100"},{plat:"web"},{product:"CFH"},{ctoken:z},{utoken:G},{draftid:K.draftid||""},{drafttype:"0"},{type:"0"},{title:encodeURIComponent(K.title)},{text:encodeURIComponent(K.text)},{columns:"2"},{cover:""},{issimplevideo:"0"},{videos:""},{vods:""},{isoriginal:"0"},{tgProduct:""},{spcolumns:""},{textsource:"0"},{replyauthority:""},{modules:encodeURIComponent("[]")}]}async function Q(K,A){const W=A?"https://mp.eastmoney.com/collect/pc_article/index.html#/?id="+A:"https://mp.eastmoney.com/collect/pc_article/index.html#/",$=JSON.stringify({pageUrl:W,path:"draft/api/Article/SaveDraft",parm:JSON.stringify(K)}),F=await fetch("https://emfront.eastmoney.com/apifront/Tran/GetData?platform=",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:$});if(!F.ok)return{ok:!1,stage:"draft_api",status:F.status,message:"草稿 API 请求失败: "+F.status};let q;try{q=await F.json()}catch(j){return{ok:!1,stage:"draft_api",status:F.status,message:"草稿 API 响应不是有效 JSON"}}if(!q.RRquestSuccess||q.RCode!==200)return{ok:!1,stage:"draft_api",status:q.RCode,message:"草稿 API 错误: "+(q.RMsg||"未知错误")};let O;try{O=JSON.parse(q.RData)}catch(j){return{ok:!1,stage:"draft_parse",status:200,message:"无法解析草稿响应数据"}}if(O.error_code!==0)return{ok:!1,stage:"draft_business",status:O.error_code,message:"草稿业务错误: "+(O.me||"未知错误")};return{ok:!0,data:O}}const Y=L({title:B.title,text:'<div class="xeditor_content cfh_web"></div>'}),Z=await Q(Y);if(!Z.ok)return Z;const V=Z.data&&Z.data.draft_id;if(!V)return{ok:!1,stage:"create",status:200,message:"创建草稿失败:响应缺少 draft_id"};const T=L({draftid:V,title:B.title,text:'<div class="xeditor_content cfh_web">'+B.content+"</div>"}),U=await Q(T,V);if(!U.ok)return U;const _="https://mp.eastmoney.com/collect/pc_article/index.html#/?id="+V;return{ok:!0,draft:!0,id:V,url:_}}};import{readFile as R,stat as C}from"node:fs/promises";function E(B){if(!B.execute)throw new X("INVALID_INPUT","此命令需要 --execute 参数才能实际写入。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function D(B){const H=typeof B.text==="string"?B.text:void 0,z=typeof B.file==="string"?B.file:void 0;if(H&&z)throw new X("INVALID_INPUT","正文和 --file 只能二选一");let G=H??"";if(z){let J;try{J=await C(z)}catch{throw new X("INVALID_INPUT",`文件不存在:${z}`)}if(!J.isFile())throw new X("INVALID_INPUT",`必须是可读文本文件:${z}`);let M;try{M=await R(z)}catch{throw new X("INVALID_INPUT",`文件无法读取:${z}`)}try{G=new TextDecoder("utf-8",{fatal:!0}).decode(M)}catch{throw new X("INVALID_INPUT",`文件无法以 UTF-8 解码:${z}`)}}if(!G.trim())throw new X("INVALID_INPUT","正文不能为空");return G}function y(B,H,z,G,J={}){return[{status:"success",outcome:G,message:B,target_type:H,target:z,...J}]}N({site:"eastmoney",name:"article",access:"write",description:"发布东方财富财富号文章(草稿)。正文默认 Markdown;图片自动转存到东方财富图床。",domain:"mp.eastmoney.com",strategy:h.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(默认 Markdown;--html 表示 HTML)"},{name:"file",help:"正文文件路径(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"正文格式为 HTML 而非 Markdown"},{name:"draft",type:"boolean",help:"仅保存草稿(东方财富 mp 端目前仅支持草稿,默认即草稿)"},{name:"execute",type:"boolean",help:"确认实际写入,不带此参数时拒绝写操作"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(B,H)=>{if(!B)throw new v("东方财富文章发布需要浏览器会话(browser session required)");E(H);const z=String(H.title??"").trim();if(!z)throw new X("INVALID_INPUT","文章标题不能为空");const G=await D(H),J=!0,M=await S(B,{title:z,body:G,format:H.html?"html":"markdown",draftOnly:J,profile:x}),L=M.images.uploaded.length|0,Q=M.images.failed.length|0;let Y="已保存东方财富草稿";if(L||Q)Y+=`(图片:${L} 张已转存${Q?`,${Q} 张失败`:""})`;return y(Y,"article","","draft",{created_target:"article:"+M.id,created_url:M.url})}});
|
package/clis/imooc/article.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as M,CommandExecutionError as W}from"@jackwener/opencli/errors";import{cli as X,Strategy as Y}from"@jackwener/opencli/registry";import{publishArticle as Z}from"../_shared/article/publish.js";import{checkLogin as I}from"../_shared/article/auth.js";import{readFile as $,stat as q}from"node:fs/promises";export const imoocProfile={home:"https://www.imooc.com/article",outputFormat:"markdown",checkAuth:async(H)=>{let z=await(await fetch("https://www.imooc.com/u/card",{credentials:"include"})).text();z=z.replace(/^jsonpcallback\(/,"").replace(/\}\)$/,"}");let B;try{B=JSON.parse(z)}catch(G){return{isAuthenticated:!1,error:"JSONP 解析失败:"+String(G&&G.message||G)}}if(!B||B.result!==0)return{isAuthenticated:!1,error:B&&B.msg||"未登录"};return{isAuthenticated:!0,userId:String(B.data.uid),username:B.data.nickname,avatar:B.data.img}},image:{uploadFn:async(H,K)=>{const z=await fetch(H,{credentials:"omit"});if(!z.ok)throw Error("图片下载失败:HTTP "+z.status);const B=await z.blob(),G=Date.now()+".jpg",J=new File([B],G,{type:B.type||"image/jpeg"}),L=new FormData;L.append("photo",J,G);L.append("type",J.type);L.append("id","WU_FILE_0");L.append("name",G);L.append("lastModifiedDate",new Date().toString());L.append("size",String(J.size));const O=await(await fetch("https://www.imooc.com/article/ajaxuploadimg",{method:"POST",credentials:"include",body:L})).json();if(!O||O.result!==0)throw Error(O&&O.msg||"图片上传失败");let Q=O.data.imgpath;if(Q&&Q.indexOf("//")===0)Q="https:"+Q;return{url:Q}},skip:["img.imooc.com","imooc.com"]},publish:async(H,K)=>{const z=await fetch("https://www.imooc.com/article/savedraft",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({editor:"0",draft_id:"0",title:H.title,content:H.content})});if(!z.ok){const J=await z.text();return{ok:!1,stage:"savedraft",status:z.status,message:J.slice(0,300)}}const B=await z.json();if(!B||!B.data){const J=JSON.stringify(B).slice(0,300);return{ok:!1,stage:"savedraft",status:z.status,message:"保存草稿失败:"+J}}const G=String(B.data);return{ok:!0,id:G,url:"https://www.imooc.com/article/draft/id/"+G,draft:!0}}};function A(H){if(!H.execute)throw new M("INVALID_INPUT","此命令需要 --execute
|
|
1
|
+
import{CliError as M,CommandExecutionError as W}from"@jackwener/opencli/errors";import{cli as X,Strategy as Y}from"@jackwener/opencli/registry";import{publishArticle as Z}from"../_shared/article/publish.js";import{checkLogin as I}from"../_shared/article/auth.js";import{readFile as $,stat as q}from"node:fs/promises";export const imoocProfile={home:"https://www.imooc.com/article",outputFormat:"markdown",checkAuth:async(H)=>{let z=await(await fetch("https://www.imooc.com/u/card",{credentials:"include"})).text();z=z.replace(/^jsonpcallback\(/,"").replace(/\}\)$/,"}");let B;try{B=JSON.parse(z)}catch(G){return{isAuthenticated:!1,error:"JSONP 解析失败:"+String(G&&G.message||G)}}if(!B||B.result!==0)return{isAuthenticated:!1,error:B&&B.msg||"未登录"};return{isAuthenticated:!0,userId:String(B.data.uid),username:B.data.nickname,avatar:B.data.img}},image:{uploadFn:async(H,K)=>{const z=await fetch(H,{credentials:"omit"});if(!z.ok)throw Error("图片下载失败:HTTP "+z.status);const B=await z.blob(),G=Date.now()+".jpg",J=new File([B],G,{type:B.type||"image/jpeg"}),L=new FormData;L.append("photo",J,G);L.append("type",J.type);L.append("id","WU_FILE_0");L.append("name",G);L.append("lastModifiedDate",new Date().toString());L.append("size",String(J.size));const O=await(await fetch("https://www.imooc.com/article/ajaxuploadimg",{method:"POST",credentials:"include",body:L})).json();if(!O||O.result!==0)throw Error(O&&O.msg||"图片上传失败");let Q=O.data.imgpath;if(Q&&Q.indexOf("//")===0)Q="https:"+Q;return{url:Q}},skip:["img.imooc.com","imooc.com"]},publish:async(H,K)=>{const z=await fetch("https://www.imooc.com/article/savedraft",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({editor:"0",draft_id:"0",title:H.title,content:H.content})});if(!z.ok){const J=await z.text();return{ok:!1,stage:"savedraft",status:z.status,message:J.slice(0,300)}}const B=await z.json();if(!B||!B.data){const J=JSON.stringify(B).slice(0,300);return{ok:!1,stage:"savedraft",status:z.status,message:"保存草稿失败:"+J}}const G=String(B.data);return{ok:!0,id:G,url:"https://www.imooc.com/article/draft/id/"+G,draft:!0}}};function A(H){if(!H.execute)throw new M("INVALID_INPUT","此命令需要 --execute 才会真正写入。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function F(H){const K=typeof H.text==="string"?H.text:void 0,z=typeof H.file==="string"?H.file:void 0;if(K&&z)throw new M("INVALID_INPUT","<text> 和 --file 不能同时使用");let B=K??"";if(z){let G;try{G=await q(z)}catch{throw new M("INVALID_INPUT","文件不存在:"+z)}if(!G.isFile())throw new M("INVALID_INPUT","路径必须是可读文本文件:"+z);let J;try{J=await $(z)}catch{throw new M("INVALID_INPUT","文件读取失败:"+z)}try{B=new TextDecoder("utf-8",{fatal:!0}).decode(J)}catch{throw new M("INVALID_INPUT","文件不是有效 UTF-8:"+z)}}if(!B.trim())throw new M("INVALID_INPUT","正文不能为空");return B}function T(H,K,z,B,G={}){return[{status:"success",outcome:B,message:H,target_type:K,target:z,...G}]}X({site:"imooc",name:"article",access:"write",description:"发布慕课手记文章(Markdown,始终存为草稿)。正文默认为 Markdown;外链图自动转存到慕课图床。",domain:"www.imooc.com",strategy:Y.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(Markdown,默认)"},{name:"file",help:"正文文件路径(UTF-8,Markdown)"},{name:"html",type:"boolean",help:"将正文视为原始 HTML 而非 Markdown"},{name:"draft",type:"boolean",help:"(慕课始终草稿,此标志无额外效果)"},{name:"execute",type:"boolean",help:"真正执行写入,不加此标志时拒绝写操作"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(H,K)=>{if(!H)throw new W("慕课手记发布需要浏览器会话");A(K);const z=String(K.title??"").trim();if(!z)throw new M("INVALID_INPUT","文章标题不能为空");const B=await F(K),G=await Z(H,{title:z,body:B,format:K.html?"html":"markdown",draftOnly:!0,profile:imoocProfile}),J=G.images.uploaded.length|0,L=G.images.failed.length|0;let V="已保存慕课手记草稿";if(J||L)V+=`・图片:${J} 张已转存${L?`,${L} 张失败`:""}`;return T(V,"article","","draft",{created_target:"article:"+G.id,created_url:G.url})}});
|
package/clis/jianshu/article.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as A,CommandExecutionError as v}from"@jackwener/opencli/errors";import{cli as N,Strategy as W}from"@jackwener/opencli/registry";import{publishArticle as j}from"../_shared/article/publish.js";export const jianshuProfile={home:"https://www.jianshu.com",outputFormat:"markdown",checkAuth:async(B)=>{try{const G=await fetch("https://www.jianshu.com/settings/basic.json",{method:"GET",credentials:"include",headers:{accept:"application/json"}});if(!G.ok)return{isAuthenticated:!1};const z=await G.text();let K=null;try{K=JSON.parse(z)}catch(V){return{isAuthenticated:!1}}const J=K&&K.data;if(!J||!J.nickname)return{isAuthenticated:!1};const Q=/\/upload_avatars\/(\d+)\//.exec(J.avatar||"");return{isAuthenticated:!0,userId:Q?Q[1]:"",username:J.nickname,avatar:J.avatar||""}}catch(G){return{isAuthenticated:!1,error:String(G&&G.message||G)}}},image:{skip:["jianshu.io"],uploadFn:async(B,G)=>{const z=await fetch(B,{method:"GET"});if(!z.ok)throw Error("下载图片失败: "+z.status);const K=await z.blob(),Q={"image/png":"png","image/jpeg":"jpg","image/gif":"gif","image/webp":"webp"}[K.type]||"png",V=Date.now()+"-"+Math.random().toString(16).slice(2,8)+"."+Q,Z=await fetch("https://www.jianshu.com/upload_images/token.json?filename="+encodeURIComponent(V),{credentials:"include",headers:{accept:"application/json"}}).then((L)=>L.json());if(!Z||!Z.token||!Z.key)throw Error("获取简书图片上传凭证失败");const Y=new FormData;Y.append("token",Z.token);Y.append("key",Z.key);Y.append("x:protocol","https");Y.append("file",K,V);const q=await fetch("https://upload.qiniup.com/",{method:"POST",body:Y}),$=await q.text();let X=null;try{X=JSON.parse($)}catch(L){}if(!q.ok||!X||!X.url)throw Error("七牛上传失败: "+q.status+" "+$.slice(0,120));return{url:X.url}}},publish:async(B,G)=>{const z=B.params||{},K={accept:"application/json","content-type":"application/json"},J=String(z.notebook||"").trim();if(!J)return{ok:!1,stage:"validate",message:"发布简书文章必须指定文集:--notebook <文集名>(用 `jianshu notebooks` 查看可选文集)"};let Q=[];try{Q=await(await fetch("https://www.jianshu.com/author/notebooks",{credentials:"include",headers:{accept:"application/json"}})).json()}catch(M){return{ok:!1,stage:"notebook",message:"获取简书文集列表失败:"+String(M&&M.message||M)}}if(!Array.isArray(Q)||!Q.length)return{ok:!1,stage:"notebook",message:"简书账号下没有文集,请先在简书创作中心创建一个文集"};let V=Q.find((M)=>M&&M.name===J);if(!V&&/^\d+$/.test(J))V=Q.find((M)=>M&&String(M.id)===J);if(!V)return{ok:!1,stage:"notebook",message:"未找到文集「"+J+"」,可选:"+Q.map((M)=>M.name).join(" / ")};const Z=V.id,Y=await fetch("https://www.jianshu.com/author/notes",{method:"POST",credentials:"include",headers:K,body:JSON.stringify({at_bottom:!1,notebook_id:Z,title:B.title})}),q=await Y.text();let $=null;try{$=JSON.parse(q)}catch(M){}if(!Y.ok||!$||!$.id)return{ok:!1,stage:"create",status:Y.status,message:q.slice(0,300)};const X=$.id,L=$.slug||"",F="https://www.jianshu.com/writer#/notebooks/"+Z+"/notes/"+X;if($.note_type!==2)return{ok:!1,stage:"create",message:"简书账号默认编辑器不是 Markdown(note_type="+$.note_type+")。请到 https://www.jianshu.com/settings/basic 将默认编辑器改为 Markdown 后重试。"};const O=await fetch("https://www.jianshu.com/author/notes/"+X,{method:"PUT",credentials:"include",headers:K,body:JSON.stringify({id:X,autosave_control:1,title:B.title,content:B.content})}),P=await O.text();if(!O.ok)return{ok:!1,stage:"content",status:O.status,message:P.slice(0,300),id:String(X),url:F};if(B.draftOnly)return{ok:!0,draft:!0,id:String(X),url:F};const _=await fetch("https://www.jianshu.com/author/notes/"+X+"/publicize",{method:"POST",credentials:"include",headers:K,body:JSON.stringify({})}),S=await _.text();if(!_.ok)return{ok:!1,stage:"publish",status:_.status,message:S.slice(0,300),id:String(X),url:F};const H="https://www.jianshu.com/p/"+L;try{const M=await fetch(H,{credentials:"include"});if(!M.ok)return{ok:!1,stage:"verify",status:M.status,message:"发布请求已受理但文章页不可访问,请到写作页确认状态:"+F,id:String(X),url:F}}catch(M){}return{ok:!0,draft:!1,id:String(X),url:H}}};import{readFile as y,stat as U}from"node:fs/promises";function T(B){if(!B.execute)throw new A("INVALID_INPUT","此命令需要 --execute
|
|
1
|
+
import{CliError as A,CommandExecutionError as v}from"@jackwener/opencli/errors";import{cli as N,Strategy as W}from"@jackwener/opencli/registry";import{publishArticle as j}from"../_shared/article/publish.js";export const jianshuProfile={home:"https://www.jianshu.com",outputFormat:"markdown",checkAuth:async(B)=>{try{const G=await fetch("https://www.jianshu.com/settings/basic.json",{method:"GET",credentials:"include",headers:{accept:"application/json"}});if(!G.ok)return{isAuthenticated:!1};const z=await G.text();let K=null;try{K=JSON.parse(z)}catch(V){return{isAuthenticated:!1}}const J=K&&K.data;if(!J||!J.nickname)return{isAuthenticated:!1};const Q=/\/upload_avatars\/(\d+)\//.exec(J.avatar||"");return{isAuthenticated:!0,userId:Q?Q[1]:"",username:J.nickname,avatar:J.avatar||""}}catch(G){return{isAuthenticated:!1,error:String(G&&G.message||G)}}},image:{skip:["jianshu.io"],uploadFn:async(B,G)=>{const z=await fetch(B,{method:"GET"});if(!z.ok)throw Error("下载图片失败: "+z.status);const K=await z.blob(),Q={"image/png":"png","image/jpeg":"jpg","image/gif":"gif","image/webp":"webp"}[K.type]||"png",V=Date.now()+"-"+Math.random().toString(16).slice(2,8)+"."+Q,Z=await fetch("https://www.jianshu.com/upload_images/token.json?filename="+encodeURIComponent(V),{credentials:"include",headers:{accept:"application/json"}}).then((L)=>L.json());if(!Z||!Z.token||!Z.key)throw Error("获取简书图片上传凭证失败");const Y=new FormData;Y.append("token",Z.token);Y.append("key",Z.key);Y.append("x:protocol","https");Y.append("file",K,V);const q=await fetch("https://upload.qiniup.com/",{method:"POST",body:Y}),$=await q.text();let X=null;try{X=JSON.parse($)}catch(L){}if(!q.ok||!X||!X.url)throw Error("七牛上传失败: "+q.status+" "+$.slice(0,120));return{url:X.url}}},publish:async(B,G)=>{const z=B.params||{},K={accept:"application/json","content-type":"application/json"},J=String(z.notebook||"").trim();if(!J)return{ok:!1,stage:"validate",message:"发布简书文章必须指定文集:--notebook <文集名>(用 `jianshu notebooks` 查看可选文集)"};let Q=[];try{Q=await(await fetch("https://www.jianshu.com/author/notebooks",{credentials:"include",headers:{accept:"application/json"}})).json()}catch(M){return{ok:!1,stage:"notebook",message:"获取简书文集列表失败:"+String(M&&M.message||M)}}if(!Array.isArray(Q)||!Q.length)return{ok:!1,stage:"notebook",message:"简书账号下没有文集,请先在简书创作中心创建一个文集"};let V=Q.find((M)=>M&&M.name===J);if(!V&&/^\d+$/.test(J))V=Q.find((M)=>M&&String(M.id)===J);if(!V)return{ok:!1,stage:"notebook",message:"未找到文集「"+J+"」,可选:"+Q.map((M)=>M.name).join(" / ")};const Z=V.id,Y=await fetch("https://www.jianshu.com/author/notes",{method:"POST",credentials:"include",headers:K,body:JSON.stringify({at_bottom:!1,notebook_id:Z,title:B.title})}),q=await Y.text();let $=null;try{$=JSON.parse(q)}catch(M){}if(!Y.ok||!$||!$.id)return{ok:!1,stage:"create",status:Y.status,message:q.slice(0,300)};const X=$.id,L=$.slug||"",F="https://www.jianshu.com/writer#/notebooks/"+Z+"/notes/"+X;if($.note_type!==2)return{ok:!1,stage:"create",message:"简书账号默认编辑器不是 Markdown(note_type="+$.note_type+")。请到 https://www.jianshu.com/settings/basic 将默认编辑器改为 Markdown 后重试。"};const O=await fetch("https://www.jianshu.com/author/notes/"+X,{method:"PUT",credentials:"include",headers:K,body:JSON.stringify({id:X,autosave_control:1,title:B.title,content:B.content})}),P=await O.text();if(!O.ok)return{ok:!1,stage:"content",status:O.status,message:P.slice(0,300),id:String(X),url:F};if(B.draftOnly)return{ok:!0,draft:!0,id:String(X),url:F};const _=await fetch("https://www.jianshu.com/author/notes/"+X+"/publicize",{method:"POST",credentials:"include",headers:K,body:JSON.stringify({})}),S=await _.text();if(!_.ok)return{ok:!1,stage:"publish",status:_.status,message:S.slice(0,300),id:String(X),url:F};const H="https://www.jianshu.com/p/"+L;try{const M=await fetch(H,{credentials:"include"});if(!M.ok)return{ok:!1,stage:"verify",status:M.status,message:"发布请求已受理但文章页不可访问,请到写作页确认状态:"+F,id:String(X),url:F}}catch(M){}return{ok:!0,draft:!1,id:String(X),url:H}}};import{readFile as y,stat as U}from"node:fs/promises";function T(B){if(!B.execute)throw new A("INVALID_INPUT","此命令需要 --execute 参数才能执行写操作。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function C(B){const G=typeof B.text==="string"?B.text:void 0,z=typeof B.file==="string"?B.file:void 0;if(G&&z)throw new A("INVALID_INPUT","不能同时指定正文参数和 --file,请二选一");let K=G??"";if(z){let J;try{J=await U(z)}catch{throw new A("INVALID_INPUT","文件不存在: "+z)}if(!J.isFile())throw new A("INVALID_INPUT","必须是可读的文本文件: "+z);let Q;try{Q=await y(z)}catch{throw new A("INVALID_INPUT","无法读取文件: "+z)}try{K=new TextDecoder("utf-8",{fatal:!0}).decode(Q)}catch{throw new A("INVALID_INPUT","文件必须是 UTF-8 编码: "+z)}}if(!K.trim())throw new A("INVALID_INPUT","正文不能为空");return K}function h(B,G,z,K,J={}){return[{status:"success",outcome:K,message:B,target_type:G,target:z,...J}]}N({site:"jianshu",name:"article",access:"write",description:"发布简书文章。默认正式发布;加 --draft 仅存草稿。必须指定文集 --notebook(用 `jianshu notebooks` 查看)。正文为 Markdown;图片自动转存至简书图床(七牛)。",domain:"jianshu.com",strategy:W.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(Markdown)"},{name:"file",help:"正文文件路径(UTF-8 编码,Markdown)"},{name:"notebook",required:!0,help:"【必填】文集名(精确匹配;也接受文集数字 ID)。可用 `jianshu notebooks` 查看全部文集。"},{name:"draft",type:"boolean",help:"仅保存草稿,不发布"},{name:"execute",type:"boolean",help:"确认执行写操作。不加此参数则拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(B,G)=>{if(!B)throw new v("简书文章发布需要浏览器会话");T(G);const z=String(G.title??"").trim();if(!z)throw new A("INVALID_INPUT","文章标题不能为空");const K=await C(G),J=Boolean(G.draft),Q={notebook:typeof G.notebook==="string"?G.notebook.trim():""},V=await j(B,{title:z,body:K,format:"markdown",draftOnly:J,profile:jianshuProfile,publishParams:Q}),Z=V.images.uploaded.length|0,Y=V.images.failed.length|0;let q=V.draft?"草稿已保存至简书(可在写作页手动发布)":"文章已正式发布至简书";if(Z||Y)q+=";图片:"+Z+" 张已转存"+(Y?","+Y+" 张失败":"");return h(q,"article","",V.draft?"draft":"publish",{created_target:"article:"+V.id,created_url:V.url})}});
|
package/clis/jike/comment.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import{cli as
|
|
1
|
+
import{CommandExecutionError as A}from"@jackwener/opencli/errors";import{cli as D,Strategy as F}from"@jackwener/opencli/registry";D({site:"jike",name:"comment",access:"write",description:"评论即刻帖子",domain:"web.okjike.com",strategy:F.UI,browser:!0,args:[{name:"id",type:"string",required:!0,positional:!0,help:"帖子 ID"},{name:"text",type:"string",required:!0,positional:!0,help:"评论内容"}],columns:["status","message"],func:async(f,v)=>{await f.goto(`https://web.okjike.com/originalPost/${v.id}`);for(let h=0;h<30;h++){if(await f.evaluate(`(() => {
|
|
2
2
|
const editor =
|
|
3
3
|
document.querySelector('[class*="_comment_"] [contenteditable="true"]') ||
|
|
4
4
|
document.querySelector('[contenteditable="true"]');
|
|
5
5
|
const textarea = document.querySelector('textarea');
|
|
6
6
|
return !!(editor || textarea);
|
|
7
|
-
})()`))break;await
|
|
7
|
+
})()`))break;await f.wait(0.5)}const z=await f.evaluate(`(async () => {
|
|
8
8
|
try {
|
|
9
|
-
const textToInsert = ${JSON.stringify(
|
|
9
|
+
const textToInsert = ${JSON.stringify(v.text)};
|
|
10
10
|
|
|
11
11
|
// 优先在评论区容器内找 contenteditable,避免误选页面其他编辑器;
|
|
12
12
|
// 若评论区 class 名变更则回退到全页查找
|
|
@@ -59,12 +59,12 @@ import{cli as z,Strategy as A}from"@jackwener/opencli/registry";z({site:"jike",n
|
|
|
59
59
|
} catch (e) {
|
|
60
60
|
return { ok: false, message: e.toString() };
|
|
61
61
|
}
|
|
62
|
-
})()`);if(!
|
|
62
|
+
})()`);if(!z.ok)throw new A(`评论失败:${z.message}`);for(let h=0;h<30;h++){if(await f.evaluate(`(() => {
|
|
63
63
|
return Array.from(document.querySelectorAll('button')).some(btn => {
|
|
64
64
|
const text = btn.textContent?.trim() || '';
|
|
65
65
|
return (text === '回复' || text === '发布' || text === '发送' || text === '评论') && !btn.disabled;
|
|
66
66
|
});
|
|
67
|
-
})()`))break;await
|
|
67
|
+
})()`))break;await f.wait(0.5)}const q=await f.evaluate(`(async () => {
|
|
68
68
|
try {
|
|
69
69
|
await new Promise(r => setTimeout(r, 500));
|
|
70
70
|
const btns = Array.from(document.querySelectorAll('button')).filter(btn => {
|
|
@@ -79,4 +79,4 @@ import{cli as z,Strategy as A}from"@jackwener/opencli/registry";z({site:"jike",n
|
|
|
79
79
|
} catch (e) {
|
|
80
80
|
return { ok: false, message: e.toString() };
|
|
81
81
|
}
|
|
82
|
-
})()`);if(
|
|
82
|
+
})()`);if(!q.ok)throw new A(`评论失败:${q.message}`);await f.wait(3);return[{status:"success",message:q.message}]}});
|
package/clis/jike/create.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{cli as
|
|
1
|
+
import{CommandExecutionError as z}from"@jackwener/opencli/errors";import{cli as D,Strategy as F}from"@jackwener/opencli/registry";D({site:"jike",name:"create",access:"write",description:"发布即刻动态",domain:"web.okjike.com",strategy:F.UI,browser:!0,args:[{name:"text",type:"string",required:!0,positional:!0,help:"动态正文内容"}],columns:["status","message"],func:async(f,A)=>{await f.goto("https://web.okjike.com");for(let h=0;h<30;h++){if(await f.evaluate(`(() => {
|
|
2
2
|
const form = document.querySelector('[class*="_postForm_"]');
|
|
3
3
|
const editor = form
|
|
4
4
|
? form.querySelector('[contenteditable="true"]')
|
|
@@ -7,9 +7,9 @@ import{cli as A,Strategy as B}from"@jackwener/opencli/registry";A({site:"jike",n
|
|
|
7
7
|
? form.querySelector('textarea')
|
|
8
8
|
: document.querySelector('textarea');
|
|
9
9
|
return !!(editor || textarea);
|
|
10
|
-
})()`))break;await
|
|
10
|
+
})()`))break;await f.wait(0.5)}const v=await f.evaluate(`(async () => {
|
|
11
11
|
try {
|
|
12
|
-
const textToInsert = ${JSON.stringify(
|
|
12
|
+
const textToInsert = ${JSON.stringify(A.text)};
|
|
13
13
|
|
|
14
14
|
// 首页发帖框在 _postForm_ 容器内,查找其中的 contenteditable
|
|
15
15
|
const form = document.querySelector('[class*="_postForm_"]');
|
|
@@ -54,12 +54,12 @@ import{cli as A,Strategy as B}from"@jackwener/opencli/registry";A({site:"jike",n
|
|
|
54
54
|
} catch (e) {
|
|
55
55
|
return { ok: false, message: e.toString() };
|
|
56
56
|
}
|
|
57
|
-
})()`);if(!
|
|
57
|
+
})()`);if(!v.ok)throw new z(`发布动态失败:${v.message}`);for(let h=0;h<30;h++){if(await f.evaluate(`(() => {
|
|
58
58
|
return Array.from(document.querySelectorAll('button')).some(btn => {
|
|
59
59
|
const text = btn.textContent?.trim() || '';
|
|
60
60
|
return (text === '发送' || text === '发布') && !btn.disabled;
|
|
61
61
|
});
|
|
62
|
-
})()`))break;await
|
|
62
|
+
})()`))break;await f.wait(0.5)}const q=await f.evaluate(`(async () => {
|
|
63
63
|
try {
|
|
64
64
|
await new Promise(r => setTimeout(r, 500));
|
|
65
65
|
|
|
@@ -80,4 +80,4 @@ import{cli as A,Strategy as B}from"@jackwener/opencli/registry";A({site:"jike",n
|
|
|
80
80
|
} catch (e) {
|
|
81
81
|
return { ok: false, message: e.toString() };
|
|
82
82
|
}
|
|
83
|
-
})()`);if(
|
|
83
|
+
})()`);if(!q.ok)throw new z(`发布动态失败:${q.message}`);await f.wait(3);return[{status:"success",message:q.message}]}});
|
package/clis/jike/like.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import{cli as
|
|
1
|
+
import{CommandExecutionError as v}from"@jackwener/opencli/errors";import{cli as z,Strategy as A}from"@jackwener/opencli/registry";z({site:"jike",name:"like",access:"write",description:"点赞即刻帖子",domain:"web.okjike.com",strategy:A.UI,browser:!0,args:[{name:"id",type:"string",required:!0,positional:!0,help:"帖子 ID"}],columns:["status","message"],func:async(b,q)=>{await b.goto(`https://web.okjike.com/originalPost/${q.id}`);for(let h=0;h<30;h++){if(await b.evaluate(`(() => {
|
|
2
2
|
return !!document.querySelector('[class*="_likeButton_"]');
|
|
3
|
-
})()`))break;await b.wait(0.5)}const
|
|
3
|
+
})()`))break;await b.wait(0.5)}const f=await b.evaluate(`(async () => {
|
|
4
4
|
try {
|
|
5
5
|
// 点赞按钮:class 包含 _likeButton_,在 _actions_ 容器内
|
|
6
6
|
const likeBtn = document.querySelector('[class*="_likeButton_"]');
|
|
7
7
|
if (!likeBtn) {
|
|
8
|
-
|
|
8
|
+
// 按钮缺失原因很多(帖子被删 / 未登录 / 选择器失效),把页面现场一并带回,
|
|
9
|
+
// 让调用方(云端 AI)能分诊,而不是只看到一句「未找到点赞按钮」。
|
|
10
|
+
const bodyText = (document.body && document.body.innerText || '').slice(0, 400);
|
|
11
|
+
const deleted = /不存在|已删除|已被删除/.test(bodyText);
|
|
12
|
+
const loginWall = /登录|下载即刻/.test(bodyText) && !document.querySelector('[class*="_avatar_"]');
|
|
13
|
+
let hint = '页面已加载但没有点赞按钮,可能是选择器失效';
|
|
14
|
+
if (deleted) hint = '帖子疑似不存在或已被删除';
|
|
15
|
+
else if (loginWall) hint = '页面疑似处于未登录态(出现登录/下载引导)';
|
|
16
|
+
return { ok: false, message: '未找到点赞按钮:' + hint + '(页面标题「' + document.title + '」)' };
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
// 已赞状态挂在内层 SVG 图标的类上(_likeIconLiked_),外层容器 div 的
|
|
@@ -27,4 +35,4 @@ import{cli as m,Strategy as q}from"@jackwener/opencli/registry";m({site:"jike",n
|
|
|
27
35
|
} catch (e) {
|
|
28
36
|
return { ok: false, message: e.toString() };
|
|
29
37
|
}
|
|
30
|
-
})()`);return[{status:
|
|
38
|
+
})()`);if(!f.ok)throw new v(`点赞失败:${f.message}`);return[{status:"success",message:f.message}]}});
|
package/clis/jike/repost.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{cli as
|
|
1
|
+
import{CommandExecutionError as v}from"@jackwener/opencli/errors";import{cli as G,Strategy as H}from"@jackwener/opencli/registry";G({site:"jike",name:"repost",access:"write",description:"转发即刻帖子",domain:"web.okjike.com",strategy:H.UI,browser:!0,args:[{name:"id",type:"string",required:!0,positional:!0,help:"帖子 ID"},{name:"text",positional:!0,type:"string",required:!1,help:"转发附言(可选)"}],columns:["status","message"],func:async(b,z)=>{await b.goto(`https://web.okjike.com/originalPost/${z.id}`);for(let h=0;h<30;h++){if(await b.evaluate(`(() => {
|
|
2
2
|
const actions = document.querySelector('[class*="_actions_"]');
|
|
3
3
|
if (!actions) return false;
|
|
4
4
|
const children = Array.from(actions.children).filter(c => c.offsetHeight > 0);
|
|
5
5
|
return !!children[2];
|
|
6
|
-
})()`))break;await b.wait(0.5)}const
|
|
6
|
+
})()`))break;await b.wait(0.5)}const B=await b.evaluate(`(async () => {
|
|
7
7
|
try {
|
|
8
8
|
const actions = document.querySelector('[class*="_actions_"]');
|
|
9
9
|
if (!actions) return { ok: false, message: '未找到操作栏' };
|
|
@@ -15,11 +15,11 @@ import{cli as E,Strategy as F}from"@jackwener/opencli/registry";E({site:"jike",n
|
|
|
15
15
|
} catch (e) {
|
|
16
16
|
return { ok: false, message: e.toString() };
|
|
17
17
|
}
|
|
18
|
-
})()`);if(!
|
|
18
|
+
})()`);if(!B.ok)throw new v(`转发失败:${B.message}`);await b.wait(1);for(let h=0;h<30;h++){if(await b.evaluate(`(() => {
|
|
19
19
|
return Array.from(document.querySelectorAll('button')).some(
|
|
20
20
|
b => b.textContent?.trim() === '转发动态'
|
|
21
21
|
);
|
|
22
|
-
})()`))break;await b.wait(0.5)}const
|
|
22
|
+
})()`))break;await b.wait(0.5)}const D=await b.evaluate(`(async () => {
|
|
23
23
|
try {
|
|
24
24
|
const btn = Array.from(document.querySelectorAll('button')).find(
|
|
25
25
|
b => b.textContent?.trim() === '转发动态'
|
|
@@ -30,11 +30,11 @@ import{cli as E,Strategy as F}from"@jackwener/opencli/registry";E({site:"jike",n
|
|
|
30
30
|
} catch (e) {
|
|
31
31
|
return { ok: false, message: e.toString() };
|
|
32
32
|
}
|
|
33
|
-
})()`);if(!
|
|
33
|
+
})()`);if(!D.ok)throw new v(`转发失败:${D.message}`);await b.wait(2);if(z.text){for(let q=0;q<30;q++){if(await b.evaluate(`(() => {
|
|
34
34
|
return !!document.querySelector('[class*="Modal"] [contenteditable="true"], [class*="modal"] [contenteditable="true"]');
|
|
35
35
|
})()`))break;await b.wait(0.5)}const h=await b.evaluate(`(async () => {
|
|
36
36
|
try {
|
|
37
|
-
const textToInsert = ${JSON.stringify(
|
|
37
|
+
const textToInsert = ${JSON.stringify(z.text)};
|
|
38
38
|
const editor = document.querySelector('[class*="Modal"] [contenteditable="true"], [class*="modal"] [contenteditable="true"]');
|
|
39
39
|
if (!editor) return { ok: false, message: '未找到转发弹窗内的附言输入框' };
|
|
40
40
|
editor.focus();
|
|
@@ -46,7 +46,7 @@ import{cli as E,Strategy as F}from"@jackwener/opencli/registry";E({site:"jike",n
|
|
|
46
46
|
await new Promise(r => setTimeout(r, 500));
|
|
47
47
|
return { ok: true };
|
|
48
48
|
} catch(e) { return { ok: false, message: '附言写入失败: ' + e.toString() }; }
|
|
49
|
-
})()`);if(!h.ok)
|
|
49
|
+
})()`);if(!h.ok)throw new v(`转发失败:${h.message}`)}const F=`(() => {
|
|
50
50
|
return Array.from(document.querySelectorAll('button')).find(b => {
|
|
51
51
|
const text = b.textContent?.trim() || '';
|
|
52
52
|
if (text !== '转发动态' && text !== '发送' && text !== '发布') return false;
|
|
@@ -54,14 +54,14 @@ import{cli as E,Strategy as F}from"@jackwener/opencli/registry";E({site:"jike",n
|
|
|
54
54
|
if (b.closest('[class*="Popover"]')) return false;
|
|
55
55
|
return !!(b.closest('[class*="Modal"], [class*="modal"], [class*="_submitButton_"]'));
|
|
56
56
|
});
|
|
57
|
-
})`;for(let h=0;h<30;h++){if(await b.evaluate(`(() => !!${
|
|
57
|
+
})`;for(let h=0;h<30;h++){if(await b.evaluate(`(() => !!${F}())()`))break;await b.wait(0.5)}const A=await b.evaluate(`(async () => {
|
|
58
58
|
try {
|
|
59
59
|
await new Promise(r => setTimeout(r, 500));
|
|
60
|
-
const btn = ${
|
|
60
|
+
const btn = ${F}();
|
|
61
61
|
if (!btn) return { ok: false, message: '未找到转发弹窗内的确认按钮' };
|
|
62
62
|
btn.click();
|
|
63
63
|
return { ok: true, message: '转发成功' };
|
|
64
64
|
} catch (e) {
|
|
65
65
|
return { ok: false, message: e.toString() };
|
|
66
66
|
}
|
|
67
|
-
})()`);if(
|
|
67
|
+
})()`);if(!A.ok)throw new v(`转发失败:${A.message}`);await b.wait(3);return[{status:"success",message:A.message}]}});
|
package/clis/juejin/article.js
CHANGED
|
@@ -2,4 +2,4 @@ import{CliError as x,CommandExecutionError as MJ}from"@jackwener/opencli/errors"
|
|
|
2
2
|
`)+`
|
|
3
3
|
`,FJ=await P(""),QJ=[$.toUpperCase(),YJ||"/",ZJ,$J,p,FJ].join(`
|
|
4
4
|
`),d="cn-north-1",l="imagex",i="AWS4-HMAC-SHA256",o=g+"/"+d+"/"+l+"/aws4_request",BJ=await P(QJ),WJ=[i,D,o,BJ].join(`
|
|
5
|
-
`),qJ=await J(new TextEncoder().encode("AWS4"+X),g),zJ=await J(qJ,d),PJ=await J(zJ,l),VJ=await J(PJ,"aws4_request"),GJ=G(await J(VJ,WJ)),c={authorization:i+" Credential="+j+"/"+o+", SignedHeaders="+p+", Signature="+GJ,"x-amz-date":D};if(_)c["x-amz-security-token"]=_;return c}function E($){let O=4294967295;const j=new Uint32Array(256);for(let X=0;X<256;X++){let _=X;for(let h=0;h<8;h++)_=_&1?3988292384^_>>>1:_>>>1;j[X]=_}for(let X=0;X<$.length;X++)O=O>>>8^j[(O^$[X])&255];return((O^4294967295)>>>0).toString(16).padStart(8,"0")}const W="2608",K="73owjymdk6",F=Date.now().toString(16)+Math.random().toString(16).slice(2);let q;if(Y.indexOf("data:")===0)q=await fetch(Y).then(function($){return $.blob()});else{const $=await fetch(Y,{method:"GET"});if(!$.ok)throw Error("下载图片失败: "+$.status);q=await $.blob()}const C="https://api.juejin.cn/imagex/v2/gen_token?aid="+W+"&uuid="+F+"&client=web",N=await(await fetch(C,{method:"GET",credentials:"include",headers:{"Content-Type":"application/json"}})).json();if(N.err_no&&N.err_no!==0)throw Error("获取 ImageX 凭证失败: "+(N.err_msg||N.err_no));const Q=N.data&&N.data.token;if(!Q||!Q.AccessKeyId||!Q.SecretAccessKey)throw Error("ImageX 凭证响应无效");const{AccessKeyId:U,SecretAccessKey:z,SessionToken:M}=Q,B="https://imagex.bytedanceapi.com/?Action=ApplyImageUpload&Version=2018-08-01&ServiceId="+K,V=await L("GET",B,U,z,M),v=await(await fetch(B,{method:"GET",headers:V})).json(),R=v&&v.Result&&v.Result.UploadAddress;if(!R)throw Error("申请上传地址失败");const w=R.StoreInfos&&R.StoreInfos[0],b=R.UploadHosts&&R.UploadHosts[0];if(!w||!b)throw Error("上传地址无效");const s=R.SessionKey,t=await q.arrayBuffer(),r=new Uint8Array(t),a=E(r),e="https://"+b+"/"+w.StoreUri,I=await fetch(e,{method:"PUT",headers:{Authorization:w.Auth,"Content-Type":q.type||"application/octet-stream","Content-CRC32":a},body:q});if(!I.ok){const $=await I.text();throw Error("TOS 上传失败: "+I.status+" "+$.slice(0,100))}const m="https://imagex.bytedanceapi.com/?Action=CommitImageUpload&Version=2018-08-01&SessionKey="+encodeURIComponent(s)+"&ServiceId="+K,u=await L("POST",m,U,z,M);u["Content-Length"]="0";if(!(await(await fetch(m,{method:"POST",headers:u})).json()).Result)throw Error("提交图片上传失败");const JJ=w.StoreUri,A=await(await fetch("https://api.juejin.cn/imagex/v2/get_img_url?aid="+W+"&uuid="+F+"&uri="+encodeURIComponent(JJ)+"&img_type=private",{method:"GET",credentials:"include",headers:{"Content-Type":"application/json"}})).json();if(A.err_no&&A.err_no!==0)throw Error("获取图片 URL 失败: "+(A.err_msg||A.err_no));const n=A.data&&(A.data.main_url||A.data.backup_url);if(!n)throw Error("图片 URL 响应无效");return{url:n}}},publish:async(Y,Z)=>{const J=Y.params||{};let P="";try{const M=(await fetch("https://api.juejin.cn/user_api/v1/sys/token",{method:"HEAD",credentials:"include",headers:{"x-secsdk-csrf-request":"1","x-secsdk-csrf-version":"1.2.10"}})).headers.get("x-ware-csrf-token");if(M){const B=M.split(",");if(B.length>=2)P=B[1]}}catch(z){}const G={"Content-Type":"application/json"};if(P)G["x-secsdk-csrf-token"]=P;let L="0";if(J.category){let z=[];try{const V=await(await fetch("https://api.juejin.cn/tag_api/v1/query_category_briefs",{method:"GET",credentials:"include"})).json();z=V&&V.data||[]}catch(B){return{ok:!1,stage:"category",message:"获取掘金分类列表失败:"+String(B&&B.message||B)}}const M=z.find((B)=>B.category_name===J.category);if(!M)return{ok:!1,stage:"category",message:"未找到分类「"+J.category+"」,可选:"+z.map((B)=>B.category_name).join(" / ")};L=String(M.category_id)}const E=[];if(J.tags&&J.tags.length)for(const z of J.tags){let M=[];try{const T=await(await fetch("https://api.juejin.cn/tag_api/v1/query_tag_list",{method:"POST",credentials:"include",headers:G,body:JSON.stringify({cursor:"0",key_word:z,limit:20,sort_type:1})})).json();M=(T&&T.data||[]).map((v)=>v&&v.tag?v.tag:v)}catch(V){return{ok:!1,stage:"tag",message:"搜索标签「"+z+"」失败:"+String(V&&V.message||V)}}const B=M.find((V)=>String(V.tag_name||"").toLowerCase()===String(z).toLowerCase());if(!B){const V=M.slice(0,8).map((T)=>T.tag_name).filter(Boolean).join(" / ");return{ok:!1,stage:"tag",message:"未找到标签「"+z+"」(精确名匹配)"+(V?";相近:"+V:"")}}E.push(String(B.tag_id))}if(!Y.draftOnly){if(L==="0")return{ok:!1,stage:"validate",message:"发布掘金文章必须指定分类:--category <后端|前端|Android|iOS|人工智能|开发工具|代码人生|阅读>"};if(!E.length)return{ok:!1,stage:"validate",message:"发布掘金文章必须指定标签:--tags <标签名,标签名>(至少一个,按名精确匹配)"};if(!String(J.brief||"").trim())return{ok:!1,stage:"validate",message:"发布掘金文章必须填写摘要:--brief <摘要>(掘金要求约 50-100 字,不可留空)"}}const W=await fetch("https://api.juejin.cn/content_api/v1/article_draft/create",{method:"POST",credentials:"include",headers:G,body:JSON.stringify({brief_content:J.brief||"",category_id:L,cover_image:"",edit_type:10,html_content:"",link_url:"",mark_content:Y.content,tag_ids:E,theme_ids:[],title:Y.title})}),K=await W.text();let F=null;try{F=JSON.parse(K)}catch(z){}if(!W.ok)return{ok:!1,stage:"create",status:W.status,message:K.slice(0,300)};if(F&&F.err_no&&F.err_no!==0)return{ok:!1,stage:"create",status:W.status,message:F.err_msg||"创建草稿失败: err_no="+F.err_no};if(!F||!F.data||!F.data.id)return{ok:!1,stage:"create",status:W.status,message:K.slice(0,300)};const q=String(F.data.id),C="https://juejin.cn/editor/drafts/"+q;if(Y.draftOnly)return{ok:!0,draft:!0,id:q,url:C};const S=await fetch("https://api.juejin.cn/content_api/v1/article/publish",{method:"POST",credentials:"include",headers:G,body:JSON.stringify({draft_id:q,sync_to_org:!1,column_ids:[]})}),N=await S.text();let Q=null;try{Q=JSON.parse(N)}catch(z){}if(!S.ok)return{ok:!1,stage:"publish",status:S.status,message:N.slice(0,300),id:q,url:C};if(Q&&Q.err_no&&Q.err_no!==0)return{ok:!1,stage:"publish",status:S.status,message:Q.err_msg||"发布失败: err_no="+Q.err_no,id:q,url:C};const U=Q&&Q.data&&String(Q.data.article_id||Q.data.id||"");if(!U)return{ok:!1,stage:"publish",status:S.status,message:N.slice(0,300),id:q,url:C};return{ok:!0,draft:!1,id:U,url:"https://juejin.cn/post/"+U}}};import{readFile as NJ,stat as _J}from"node:fs/promises";function KJ(Y){if(!Y.execute)throw new x("INVALID_INPUT","此命令需要 --execute
|
|
5
|
+
`),qJ=await J(new TextEncoder().encode("AWS4"+X),g),zJ=await J(qJ,d),PJ=await J(zJ,l),VJ=await J(PJ,"aws4_request"),GJ=G(await J(VJ,WJ)),c={authorization:i+" Credential="+j+"/"+o+", SignedHeaders="+p+", Signature="+GJ,"x-amz-date":D};if(_)c["x-amz-security-token"]=_;return c}function E($){let O=4294967295;const j=new Uint32Array(256);for(let X=0;X<256;X++){let _=X;for(let h=0;h<8;h++)_=_&1?3988292384^_>>>1:_>>>1;j[X]=_}for(let X=0;X<$.length;X++)O=O>>>8^j[(O^$[X])&255];return((O^4294967295)>>>0).toString(16).padStart(8,"0")}const W="2608",K="73owjymdk6",F=Date.now().toString(16)+Math.random().toString(16).slice(2);let q;if(Y.indexOf("data:")===0)q=await fetch(Y).then(function($){return $.blob()});else{const $=await fetch(Y,{method:"GET"});if(!$.ok)throw Error("下载图片失败: "+$.status);q=await $.blob()}const C="https://api.juejin.cn/imagex/v2/gen_token?aid="+W+"&uuid="+F+"&client=web",N=await(await fetch(C,{method:"GET",credentials:"include",headers:{"Content-Type":"application/json"}})).json();if(N.err_no&&N.err_no!==0)throw Error("获取 ImageX 凭证失败: "+(N.err_msg||N.err_no));const Q=N.data&&N.data.token;if(!Q||!Q.AccessKeyId||!Q.SecretAccessKey)throw Error("ImageX 凭证响应无效");const{AccessKeyId:U,SecretAccessKey:z,SessionToken:M}=Q,B="https://imagex.bytedanceapi.com/?Action=ApplyImageUpload&Version=2018-08-01&ServiceId="+K,V=await L("GET",B,U,z,M),v=await(await fetch(B,{method:"GET",headers:V})).json(),R=v&&v.Result&&v.Result.UploadAddress;if(!R)throw Error("申请上传地址失败");const w=R.StoreInfos&&R.StoreInfos[0],b=R.UploadHosts&&R.UploadHosts[0];if(!w||!b)throw Error("上传地址无效");const s=R.SessionKey,t=await q.arrayBuffer(),r=new Uint8Array(t),a=E(r),e="https://"+b+"/"+w.StoreUri,I=await fetch(e,{method:"PUT",headers:{Authorization:w.Auth,"Content-Type":q.type||"application/octet-stream","Content-CRC32":a},body:q});if(!I.ok){const $=await I.text();throw Error("TOS 上传失败: "+I.status+" "+$.slice(0,100))}const m="https://imagex.bytedanceapi.com/?Action=CommitImageUpload&Version=2018-08-01&SessionKey="+encodeURIComponent(s)+"&ServiceId="+K,u=await L("POST",m,U,z,M);u["Content-Length"]="0";if(!(await(await fetch(m,{method:"POST",headers:u})).json()).Result)throw Error("提交图片上传失败");const JJ=w.StoreUri,A=await(await fetch("https://api.juejin.cn/imagex/v2/get_img_url?aid="+W+"&uuid="+F+"&uri="+encodeURIComponent(JJ)+"&img_type=private",{method:"GET",credentials:"include",headers:{"Content-Type":"application/json"}})).json();if(A.err_no&&A.err_no!==0)throw Error("获取图片 URL 失败: "+(A.err_msg||A.err_no));const n=A.data&&(A.data.main_url||A.data.backup_url);if(!n)throw Error("图片 URL 响应无效");return{url:n}}},publish:async(Y,Z)=>{const J=Y.params||{};let P="";try{const M=(await fetch("https://api.juejin.cn/user_api/v1/sys/token",{method:"HEAD",credentials:"include",headers:{"x-secsdk-csrf-request":"1","x-secsdk-csrf-version":"1.2.10"}})).headers.get("x-ware-csrf-token");if(M){const B=M.split(",");if(B.length>=2)P=B[1]}}catch(z){}const G={"Content-Type":"application/json"};if(P)G["x-secsdk-csrf-token"]=P;let L="0";if(J.category){let z=[];try{const V=await(await fetch("https://api.juejin.cn/tag_api/v1/query_category_briefs",{method:"GET",credentials:"include"})).json();z=V&&V.data||[]}catch(B){return{ok:!1,stage:"category",message:"获取掘金分类列表失败:"+String(B&&B.message||B)}}const M=z.find((B)=>B.category_name===J.category);if(!M)return{ok:!1,stage:"category",message:"未找到分类「"+J.category+"」,可选:"+z.map((B)=>B.category_name).join(" / ")};L=String(M.category_id)}const E=[];if(J.tags&&J.tags.length)for(const z of J.tags){let M=[];try{const T=await(await fetch("https://api.juejin.cn/tag_api/v1/query_tag_list",{method:"POST",credentials:"include",headers:G,body:JSON.stringify({cursor:"0",key_word:z,limit:20,sort_type:1})})).json();M=(T&&T.data||[]).map((v)=>v&&v.tag?v.tag:v)}catch(V){return{ok:!1,stage:"tag",message:"搜索标签「"+z+"」失败:"+String(V&&V.message||V)}}const B=M.find((V)=>String(V.tag_name||"").toLowerCase()===String(z).toLowerCase());if(!B){const V=M.slice(0,8).map((T)=>T.tag_name).filter(Boolean).join(" / ");return{ok:!1,stage:"tag",message:"未找到标签「"+z+"」(精确名匹配)"+(V?";相近:"+V:"")}}E.push(String(B.tag_id))}if(!Y.draftOnly){if(L==="0")return{ok:!1,stage:"validate",message:"发布掘金文章必须指定分类:--category <后端|前端|Android|iOS|人工智能|开发工具|代码人生|阅读>"};if(!E.length)return{ok:!1,stage:"validate",message:"发布掘金文章必须指定标签:--tags <标签名,标签名>(至少一个,按名精确匹配)"};if(!String(J.brief||"").trim())return{ok:!1,stage:"validate",message:"发布掘金文章必须填写摘要:--brief <摘要>(掘金要求约 50-100 字,不可留空)"}}const W=await fetch("https://api.juejin.cn/content_api/v1/article_draft/create",{method:"POST",credentials:"include",headers:G,body:JSON.stringify({brief_content:J.brief||"",category_id:L,cover_image:"",edit_type:10,html_content:"",link_url:"",mark_content:Y.content,tag_ids:E,theme_ids:[],title:Y.title})}),K=await W.text();let F=null;try{F=JSON.parse(K)}catch(z){}if(!W.ok)return{ok:!1,stage:"create",status:W.status,message:K.slice(0,300)};if(F&&F.err_no&&F.err_no!==0)return{ok:!1,stage:"create",status:W.status,message:F.err_msg||"创建草稿失败: err_no="+F.err_no};if(!F||!F.data||!F.data.id)return{ok:!1,stage:"create",status:W.status,message:K.slice(0,300)};const q=String(F.data.id),C="https://juejin.cn/editor/drafts/"+q;if(Y.draftOnly)return{ok:!0,draft:!0,id:q,url:C};const S=await fetch("https://api.juejin.cn/content_api/v1/article/publish",{method:"POST",credentials:"include",headers:G,body:JSON.stringify({draft_id:q,sync_to_org:!1,column_ids:[]})}),N=await S.text();let Q=null;try{Q=JSON.parse(N)}catch(z){}if(!S.ok)return{ok:!1,stage:"publish",status:S.status,message:N.slice(0,300),id:q,url:C};if(Q&&Q.err_no&&Q.err_no!==0)return{ok:!1,stage:"publish",status:S.status,message:Q.err_msg||"发布失败: err_no="+Q.err_no,id:q,url:C};const U=Q&&Q.data&&String(Q.data.article_id||Q.data.id||"");if(!U)return{ok:!1,stage:"publish",status:S.status,message:N.slice(0,300),id:q,url:C};return{ok:!0,draft:!1,id:U,url:"https://juejin.cn/post/"+U}}};import{readFile as NJ,stat as _J}from"node:fs/promises";function KJ(Y){if(!Y.execute)throw new x("INVALID_INPUT","此命令需要 --execute 参数才能执行写操作。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function CJ(Y){const Z=typeof Y.text==="string"?Y.text:void 0,J=typeof Y.file==="string"?Y.file:void 0;if(Z&&J)throw new x("INVALID_INPUT","不能同时指定正文参数和 --file,请二选一");let P=Z??"";if(J){let G;try{G=await _J(J)}catch{throw new x("INVALID_INPUT","文件不存在: "+J)}if(!G.isFile())throw new x("INVALID_INPUT","必须是可读的文本文件: "+J);let L;try{L=await NJ(J)}catch{throw new x("INVALID_INPUT","无法读取文件: "+J)}try{P=new TextDecoder("utf-8",{fatal:!0}).decode(L)}catch{throw new x("INVALID_INPUT","文件必须是 UTF-8 编码: "+J)}}if(!P.trim())throw new x("INVALID_INPUT","正文不能为空");return P}function vJ(Y,Z,J,P,G={}){return[{status:"success",outcome:P,message:Y,target_type:Z,target:J,...G}]}OJ({site:"juejin",name:"article",access:"write",description:"发布掘金文章。默认正式发布(需 --category 和 --tags);加 --draft 仅存草稿。正文默认 Markdown;图片自动转存至掘金图床(字节 ImageX)。",domain:"juejin.cn",strategy:XJ.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(默认 Markdown;传 --html 则视为 HTML)"},{name:"file",help:"正文文件路径(UTF-8 编码,默认 Markdown)"},{name:"html",type:"boolean",help:"将正文视为原始 HTML 而非 Markdown"},{name:"category",help:"【正式发布必填】分类名(精确匹配):后端 / 前端 / Android / iOS / 人工智能 / 开发工具 / 代码人生 / 阅读"},{name:"tags",help:'【正式发布必填】标签名,逗号分隔,至少一个(按名精确匹配掘金标签库,如 "Linux,后端")'},{name:"brief",help:"【正式发布必填】文章摘要,掘金要求约 50-100 字(不做自动截取,必须显式提供)"},{name:"draft",type:"boolean",help:"仅保存草稿,不发布(草稿无需分类/标签)"},{name:"execute",type:"boolean",help:"确认执行写操作。不加此参数则拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(Y,Z)=>{if(!Y)throw new MJ("掘金文章发布需要浏览器会话");KJ(Z);const J=String(Z.title??"").trim();if(!J)throw new x("INVALID_INPUT","文章标题不能为空");const P=await CJ(Z),G=Boolean(Z.draft),L=String(Z.tags??"").split(",").map((C)=>C.trim()).filter(Boolean),E={category:typeof Z.category==="string"?Z.category.trim():"",tags:L,brief:typeof Z.brief==="string"?Z.brief:""},W=await LJ(Y,{title:J,body:P,format:Z.html?"html":"markdown",draftOnly:G,profile:juejinProfile,publishParams:E}),K=W.images.uploaded.length|0,F=W.images.failed.length|0;let q=W.draft?"草稿已保存至掘金(需在编辑器内手动发布)":"文章已正式发布至掘金";if(K||F)q+=";图片:"+K+" 张已转存"+(F?","+F+" 张失败":"");return vJ(q,"article","",W.draft?"draft":"publish",{created_target:"article:"+W.id,created_url:W.url})}});
|
package/clis/medium/article.js
CHANGED
|
@@ -9,4 +9,4 @@ import{cli as a,Strategy as zz}from"@jackwener/opencli/registry";import{CliError
|
|
|
9
9
|
updateCanonicalUrl(input: $input) {
|
|
10
10
|
__typename
|
|
11
11
|
}
|
|
12
|
-
}`,variables:{input:{postId:_,url:String(R.canonicalUrl).trim()}}});const s="https://medium.com/p/"+_+"/edit";if(K.draftOnly)return{ok:!0,id:_,url:s,draft:!0,uploaded:j,failed:M};const k=await D("/p/"+_+"/publish","POST",{});if(k.status>=400)return{ok:!1,stage:"publish",status:k.status,message:"发布失败:"+k.text.slice(0,200),uploaded:j,failed:M};const g=await D("/_/api/posts/"+_),i=g.json&&g.json.payload&&g.json.payload.value||{},e=i.mediumUrl||(i.uniqueSlug?"https://medium.com/p/"+_:"https://medium.com/p/"+_);return{ok:!0,id:_,url:e,draft:!1,uploaded:j,failed:M}}},mediumAuthProfile={home:"https://medium.com/",checkAuth:mediumProfile.checkAuth};function Vz(K){if(!K.execute)throw new L("INVALID_INPUT","此命令需要 --execute
|
|
12
|
+
}`,variables:{input:{postId:_,url:String(R.canonicalUrl).trim()}}});const s="https://medium.com/p/"+_+"/edit";if(K.draftOnly)return{ok:!0,id:_,url:s,draft:!0,uploaded:j,failed:M};const k=await D("/p/"+_+"/publish","POST",{});if(k.status>=400)return{ok:!1,stage:"publish",status:k.status,message:"发布失败:"+k.text.slice(0,200),uploaded:j,failed:M};const g=await D("/_/api/posts/"+_),i=g.json&&g.json.payload&&g.json.payload.value||{},e=i.mediumUrl||(i.uniqueSlug?"https://medium.com/p/"+_:"https://medium.com/p/"+_);return{ok:!0,id:_,url:e,draft:!1,uploaded:j,failed:M}}},mediumAuthProfile={home:"https://medium.com/",checkAuth:mediumProfile.checkAuth};function Vz(K){if(!K.execute)throw new L("INVALID_INPUT","此命令需要 --execute 参数才能执行写操作。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function Wz(K){const Q=typeof K.text==="string"?K.text:void 0,W=typeof K.file==="string"?K.file:void 0;if(Q&&W)throw new L("INVALID_INPUT","不能同时指定正文参数和 --file,请二选一");let $=Q??"";if(W){let q;try{q=await Kz(W)}catch{throw new L("INVALID_INPUT","文件不存在: "+W)}if(!q.isFile())throw new L("INVALID_INPUT","必须是可读的文本文件: "+W);let P;try{P=await Jz(W)}catch{throw new L("INVALID_INPUT","无法读取文件: "+W)}try{$=new TextDecoder("utf-8",{fatal:!0}).decode(P)}catch{throw new L("INVALID_INPUT","文件必须是 UTF-8 编码: "+W)}}if(!$.trim())throw new L("INVALID_INPUT","正文不能为空");return $}function Xz(K,Q,W,$,q={}){return[{status:"success",outcome:$,message:K,target_type:"article",target:Q,...q}]}a({site:"medium",name:"article",access:"write",description:"发布 Medium 文章(走 medium.com 内部编辑器端点,非官方 API)。默认正式发布;加 --draft 仅存草稿。正文 Markdown,图片自动转存 Medium 图床。可选 --tags/--subtitle/--canonical-url。",domain:"medium.com",strategy:zz.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题(作为 Medium 文章标题)"},{name:"text",positional:!0,help:"文章正文(Markdown)"},{name:"file",help:"正文文件路径(UTF-8,Markdown)"},{name:"tags",help:'Medium 话题标签,逗号分隔,最多 5 个(如 "AI,Programming,Open Source")'},{name:"subtitle",help:"副标题(可选,显示在标题下方)"},{name:"canonical-url",help:"原文规范链接(可选,内容首发别处时填,避免 SEO 重复)"},{name:"draft",type:"boolean",help:"仅保存草稿,不正式发布"},{name:"execute",type:"boolean",help:"确认执行写操作。不加此参数则拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(K,Q)=>{if(!K)throw new Gz("Medium 文章发布需要浏览器会话");Vz(Q);const W=String(Q.title??"").trim();if(!W)throw new L("INVALID_INPUT","文章标题不能为空");const $=await Wz(Q),q=Boolean(Q.draft),D={tags:String(Q.tags??"").split(",").map((y)=>y.trim()).filter(Boolean),subtitle:typeof Q.subtitle==="string"?Q.subtitle:"",canonicalUrl:typeof Q["canonical-url"]==="string"?Q["canonical-url"]:""},H=await Qz(K,{title:W,body:$,format:"markdown",draftOnly:q,profile:mediumProfile,publishParams:D}),x=H.images.uploaded.length|0,C=H.images.failed.length|0;let j=H.draft?"Medium 草稿已保存(可在编辑器内手动发布)":"Medium 文章已正式发布";if(x||C)j+=";图片:"+x+" 张已转存"+(C?","+C+" 张失败(失败明细见 images_failed)":"");const M=Xz(j,"",H.url,H.draft?"draft":"publish",{created_target:"article:"+H.id,created_url:H.url});if(C){M[0].status="partial";M[0].images_failed=JSON.stringify(H.images.failed)}return M}});
|
package/clis/oschina/article.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as V,CommandExecutionError as A}from"@jackwener/opencli/errors";import{cli as F,Strategy as O}from"@jackwener/opencli/registry";import{readFile as _,stat as q}from"node:fs/promises";import{publishArticle as T}from"../_shared/article/publish.js";export const oschinaProfile={home:"https://my.oschina.net",outputFormat:"markdown",image:{spec:{url:"https://apiv1.oschina.net/oschinapi/ai/creation/project/uploadDetail",method:"POST",bodyType:"binary-multipart",fileField:"file",fileName:"image",body:{},responsePath:"result"},skip:["apiv1.oschina.net","oscimg.oschina.net","static.oschina.net"]},checkAuth:async()=>{const G=await fetch("https://apiv1.oschina.net/oschinapi/user/myDetails",{credentials:"include"});if(!G.ok)return{isAuthenticated:!1,error:"HTTP "+G.status};let z=null;try{z=await G.json()}catch(U){return{isAuthenticated:!1,error:"解析响应失败"}}if(!z||!z.success||!z.result||!z.result.userId)return{isAuthenticated:!1,error:z&&z.message||"未登录"};const B=String(z.result.userId),M=z.result.userVo&&z.result.userVo.name||B,Q=z.result.userVo&&z.result.userVo.portraitUrl||"";return{isAuthenticated:!0,userId:B,username:M,avatar:Q}},publish:async(G)=>{const z=G.params||{};let B="";try{const L=await(await fetch("https://apiv1.oschina.net/oschinapi/user/myDetails",{credentials:"include"})).json();if(L&&L.success&&L.result&&L.result.userId)B=String(L.result.userId)}catch(K){}if(!B)return{ok:!1,stage:"auth",status:401,message:"未登录开源中国,无法获取用户 ID"};let M=0;if(z.category){let K=[];try{const X=await(await fetch("https://apiv1.oschina.net/oschinapi/blog_catalog/list_by_user",{credentials:"include"})).json();K=X&&X.result||[]}catch(H){return{ok:!1,stage:"category",message:"获取开源中国博客分类失败:"+String(H&&H.message||H)}}const L=K.find((H)=>String(H.name)===String(z.category));if(!L)return{ok:!1,stage:"category",message:"未找到博客分类「"+z.category+"」,可选:"+(K.map((H)=>H.name).join(" / ")||"(你还没有任何分类)")};M=L.id}if(G.draftOnly){const K=await fetch("https://apiv1.oschina.net/oschinapi/api/draft/save_draft",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:G.title,user:Number(B),content:G.content,contentType:1,catalog:M,originUrl:"",privacy:!0,disableComment:!1})}),L=await K.text();let H=null;try{H=JSON.parse(L)}catch(S){}if(!K.ok||!H||!H.success||!H.result||!H.result.id)return{ok:!1,stage:"save_draft",status:K.status,message:H&&H.message||L.slice(0,300)};const X=String(H.result.id);return{ok:!0,draft:!0,id:X,url:"https://my.oschina.net/u/"+B+"/blog/write/draft/"+X}}const Q=await fetch("https://apiv1.oschina.net/oschinapi/blog/web/add",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:G.title,content:G.content,contentType:1,type:"1",originUrl:"",catalog:M,privacy:!1,disableComment:!1,isAiRelated:!1,user:Number(B)})}),U=await Q.text();let J=null;try{J=JSON.parse(U)}catch(K){}if(!Q.ok||!J||J.code!==200||J.result==null)return{ok:!1,stage:"publish",status:Q.status,message:J&&(J.message||J.msg)||U.slice(0,300)};const W=String(J.result);return{ok:!0,draft:!1,id:W,url:"https://my.oschina.net/u/"+B+"/blog/"+W}}};function Y(G){if(!G.execute)throw new V("INVALID_INPUT","发布开源中国文章需要 --execute
|
|
1
|
+
import{CliError as V,CommandExecutionError as A}from"@jackwener/opencli/errors";import{cli as F,Strategy as O}from"@jackwener/opencli/registry";import{readFile as _,stat as q}from"node:fs/promises";import{publishArticle as T}from"../_shared/article/publish.js";export const oschinaProfile={home:"https://my.oschina.net",outputFormat:"markdown",image:{spec:{url:"https://apiv1.oschina.net/oschinapi/ai/creation/project/uploadDetail",method:"POST",bodyType:"binary-multipart",fileField:"file",fileName:"image",body:{},responsePath:"result"},skip:["apiv1.oschina.net","oscimg.oschina.net","static.oschina.net"]},checkAuth:async()=>{const G=await fetch("https://apiv1.oschina.net/oschinapi/user/myDetails",{credentials:"include"});if(!G.ok)return{isAuthenticated:!1,error:"HTTP "+G.status};let z=null;try{z=await G.json()}catch(U){return{isAuthenticated:!1,error:"解析响应失败"}}if(!z||!z.success||!z.result||!z.result.userId)return{isAuthenticated:!1,error:z&&z.message||"未登录"};const B=String(z.result.userId),M=z.result.userVo&&z.result.userVo.name||B,Q=z.result.userVo&&z.result.userVo.portraitUrl||"";return{isAuthenticated:!0,userId:B,username:M,avatar:Q}},publish:async(G)=>{const z=G.params||{};let B="";try{const L=await(await fetch("https://apiv1.oschina.net/oschinapi/user/myDetails",{credentials:"include"})).json();if(L&&L.success&&L.result&&L.result.userId)B=String(L.result.userId)}catch(K){}if(!B)return{ok:!1,stage:"auth",status:401,message:"未登录开源中国,无法获取用户 ID"};let M=0;if(z.category){let K=[];try{const X=await(await fetch("https://apiv1.oschina.net/oschinapi/blog_catalog/list_by_user",{credentials:"include"})).json();K=X&&X.result||[]}catch(H){return{ok:!1,stage:"category",message:"获取开源中国博客分类失败:"+String(H&&H.message||H)}}const L=K.find((H)=>String(H.name)===String(z.category));if(!L)return{ok:!1,stage:"category",message:"未找到博客分类「"+z.category+"」,可选:"+(K.map((H)=>H.name).join(" / ")||"(你还没有任何分类)")};M=L.id}if(G.draftOnly){const K=await fetch("https://apiv1.oschina.net/oschinapi/api/draft/save_draft",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:G.title,user:Number(B),content:G.content,contentType:1,catalog:M,originUrl:"",privacy:!0,disableComment:!1})}),L=await K.text();let H=null;try{H=JSON.parse(L)}catch(S){}if(!K.ok||!H||!H.success||!H.result||!H.result.id)return{ok:!1,stage:"save_draft",status:K.status,message:H&&H.message||L.slice(0,300)};const X=String(H.result.id);return{ok:!0,draft:!0,id:X,url:"https://my.oschina.net/u/"+B+"/blog/write/draft/"+X}}const Q=await fetch("https://apiv1.oschina.net/oschinapi/blog/web/add",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:G.title,content:G.content,contentType:1,type:"1",originUrl:"",catalog:M,privacy:!1,disableComment:!1,isAiRelated:!1,user:Number(B)})}),U=await Q.text();let J=null;try{J=JSON.parse(U)}catch(K){}if(!Q.ok||!J||J.code!==200||J.result==null)return{ok:!1,stage:"publish",status:Q.status,message:J&&(J.message||J.msg)||U.slice(0,300)};const W=String(J.result);return{ok:!0,draft:!1,id:W,url:"https://my.oschina.net/u/"+B+"/blog/"+W}}};function Y(G){if(!G.execute)throw new V("INVALID_INPUT","发布开源中国文章需要 --execute 参数。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}function Z(G,z,B,M,Q={}){return[{status:"success",outcome:M,message:G,target_type:z,target:B,...Q}]}async function $(G){const z=typeof G.text==="string"?G.text:void 0,B=typeof G.file==="string"?G.file:void 0;if(z&&B)throw new V("INVALID_INPUT","正文和文件路径只能二选一,不能同时指定");let M=z??"";if(B){let Q;try{Q=await q(B)}catch{throw new V("INVALID_INPUT","文件不存在:"+B)}if(!Q.isFile())throw new V("INVALID_INPUT","必须指定可读的文本文件:"+B);let U;try{U=await _(B)}catch{throw new V("INVALID_INPUT","文件读取失败:"+B)}try{M=new TextDecoder("utf-8",{fatal:!0}).decode(U)}catch{throw new V("INVALID_INPUT","文件编码不是 UTF-8:"+B)}}if(!M.trim())throw new V("INVALID_INPUT","文章正文不能为空");return M}F({site:"oschina",name:"article",access:"write",description:"发布文章到开源中国博客。默认正式发布,加 --draft 仅存草稿。正文默认 Markdown,外链图片自动转存到开源中国图床。",domain:"my.oschina.net",strategy:O.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(默认 Markdown;--html 表示原始 HTML)"},{name:"file",help:"从文件读取正文(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"将正文视为原始 HTML 而非 Markdown"},{name:"category",help:"个人博客分类名(精确匹配),合法值用 `oschina catalogs` 列举;可空=不分类"},{name:"draft",type:"boolean",help:"仅存草稿,不发布"},{name:"execute",type:"boolean",help:"真正执行写操作;不加此参数命令拒绝写入"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(G,z)=>{if(!G)throw new A("开源中国文章发布需要浏览器会话");Y(z);const B=String(z.title??"").trim();if(!B)throw new V("INVALID_INPUT","文章标题不能为空");const M=await $(z),Q=Boolean(z.draft),U={category:typeof z.category==="string"?z.category.trim():""},J=await T(G,{title:B,body:M,format:z.html?"html":"markdown",draftOnly:Q,profile:oschinaProfile,publishParams:U}),W=J.images.uploaded.length|0,K=J.images.failed.length|0;let L=J.draft?"已保存到开源中国草稿箱":"已正式发布到开源中国博客";if(W||K)L+=`·图片:${W} 张已转存${K?`,${K} 张失败`:""}`;return Z(L,"article","",J.draft?"draft":"created",{created_target:(J.draft?"draft:":"article:")+J.id,created_url:J.url})}});export const __test__={oschinaProfile,requireExecute:Y,buildResultRow:Z,resolvePayload:$};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{CliError as O,CommandExecutionError as v}from"@jackwener/opencli/errors";import{cli as y,Strategy as C}from"@jackwener/opencli/registry";import{publishArticle as p}from"../_shared/article/publish.js";import{readFile as f,stat as u}from"node:fs/promises";function I(X){if(!X.execute)throw new O("INVALID_INPUT","本命令需要 --execute 才会真正发布,请确认后加上 --execute")}async function h(X){const A=typeof X.text==="string"?X.text:void 0,W=typeof X.file==="string"?X.file:void 0;if(A&&W)throw new O("INVALID_INPUT","请只传 <text> 或 --file 之一,不能同时使用");let G=A??"";if(W){let Z;try{Z=await u(W)}catch{throw new O("INVALID_INPUT",`文件不存在:${W}`)}if(!Z.isFile())throw new O("INVALID_INPUT",`路径必须是可读文本文件:${W}`);let M;try{M=await f(W)}catch{throw new O("INVALID_INPUT",`无法读取文件:${W}`)}try{G=new TextDecoder("utf-8",{fatal:!0}).decode(M)}catch{throw new O("INVALID_INPUT",`文件编码不是 UTF-8:${W}`)}}if(!G.trim())throw new O("INVALID_INPUT","正文不能为空");return G}function w(X,A,W,G,Z={}){return[{status:"success",outcome:G,message:X,target_type:A,target:W,...Z}]}export const segmentfaultProfile={home:"https://segmentfault.com/write",outputFormat:"markdown",image:{skip:["image-static.segmentfault.com","avatar-static.segmentfault.com"],uploadFn:async(X,A)=>{const G=await(await fetch("https://segmentfault.com/write",{credentials:"include"})).text();let Z=null;const M=G.match(/serverData"\s*:\s*\{\s*"Token"\s*:\s*"([^"]+)"/);if(M)Z=M[1];else{const _="window.g_initialProps = ",U=G.indexOf(_);if(U!==-1){const T=G.indexOf(`;
|
|
1
|
+
import{CliError as O,CommandExecutionError as v}from"@jackwener/opencli/errors";import{cli as y,Strategy as C}from"@jackwener/opencli/registry";import{publishArticle as p}from"../_shared/article/publish.js";import{readFile as f,stat as u}from"node:fs/promises";function I(X){if(!X.execute)throw new O("INVALID_INPUT","本命令需要 --execute 才会真正发布,请确认后加上 --execute。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function h(X){const A=typeof X.text==="string"?X.text:void 0,W=typeof X.file==="string"?X.file:void 0;if(A&&W)throw new O("INVALID_INPUT","请只传 <text> 或 --file 之一,不能同时使用");let G=A??"";if(W){let Z;try{Z=await u(W)}catch{throw new O("INVALID_INPUT",`文件不存在:${W}`)}if(!Z.isFile())throw new O("INVALID_INPUT",`路径必须是可读文本文件:${W}`);let M;try{M=await f(W)}catch{throw new O("INVALID_INPUT",`无法读取文件:${W}`)}try{G=new TextDecoder("utf-8",{fatal:!0}).decode(M)}catch{throw new O("INVALID_INPUT",`文件编码不是 UTF-8:${W}`)}}if(!G.trim())throw new O("INVALID_INPUT","正文不能为空");return G}function w(X,A,W,G,Z={}){return[{status:"success",outcome:G,message:X,target_type:A,target:W,...Z}]}export const segmentfaultProfile={home:"https://segmentfault.com/write",outputFormat:"markdown",image:{skip:["image-static.segmentfault.com","avatar-static.segmentfault.com"],uploadFn:async(X,A)=>{const G=await(await fetch("https://segmentfault.com/write",{credentials:"include"})).text();let Z=null;const M=G.match(/serverData"\s*:\s*\{\s*"Token"\s*:\s*"([^"]+)"/);if(M)Z=M[1];else{const _="window.g_initialProps = ",U=G.indexOf(_);if(U!==-1){const T=G.indexOf(`;
|
|
2
2
|
</script>`,U);if(T!==-1)try{const V=JSON.parse(G.substring(U+_.length,T));Z=V&&V.global&&V.global.sessionInfo&&V.global.sessionInfo.key}catch(V){}}}if(!Z)throw Error("获取思否 session token 失败");const b=await fetch(X,{credentials:"omit"});if(!b.ok)throw Error("下载图片失败:"+b.status);const R=await b.blob(),N=new FormData;N.append("image",R);const L=await(await fetch("https://segmentfault.com/gateway/image",{method:"POST",credentials:"include",headers:{token:Z},body:N})).text();if(L==="Unauthorized"||L.includes("禁言")||L.includes("锁定"))throw Error(L==="Unauthorized"?"未授权,请检查登录态":L);let $;try{$=JSON.parse(L)}catch(_){throw Error("图片上传失败:"+L)}let B=null;if($&&$.result)B=$.result;else if(Array.isArray($)){if($[0]===1)throw Error($[1]||"图片上传失败");B=$[1]||($[2]?"https://image-static.segmentfault.com/"+$[2]:null)}if(!B)throw Error("图片上传失败,服务端未返回 URL");return{url:B}}},publish:async(X,A)=>{const W=X.params||{},Z=await(await fetch("https://segmentfault.com/write",{credentials:"include"})).text();let M=null;const b=Z.match(/serverData"\s*:\s*\{\s*"Token"\s*:\s*"([^"]+)"/);if(b)M=b[1];else{const Q="window.g_initialProps = ",z=Z.indexOf(Q);if(z!==-1){const J=Z.indexOf(`;
|
|
3
3
|
</script>`,z);if(J!==-1)try{const K=JSON.parse(Z.substring(z+Q.length,J));M=K&&K.global&&K.global.sessionInfo&&K.global.sessionInfo.key}catch(K){}}}if(!M)return{ok:!1,stage:"token",status:0,message:"获取思否 session token 失败,请确认已登录"};async function R(Q){let z=null;try{const P=(await fetch("/",{method:"HEAD"})).headers.get("date");if(P){const q=new Date(P).getTime();if(!isNaN(q))z=Math.floor(q/1000)}}catch(Y){}const J=Object.keys(window).find((Y)=>Y.startsWith("webpackChunk"));if(!J)return{__sfErr:"页面未就绪:未找到 webpack 运行时,无法复用思否请求签名"};let K;try{window[J].push([[Symbol("pp_sf_"+Math.random())],{},(Y)=>{K=Y}])}catch(Y){return{__sfErr:"注入 webpack 探针失败:"+String(Y)}}if(!K||!K.m)return{__sfErr:"无法取得 webpack require(思否前端结构可能已变)"};let H=null;for(const Y of Object.keys(K.m)){let P="";try{P=String(K.m[Y])}catch(q){continue}if(/function getUrl/.test(P)&&P.indexOf("/gateway")!==-1){H=Y;break}}if(!H)return{__sfErr:"未定位到思否请求签名模块(getUrl/gateway),站点前端结构可能已变,需更新 adapter"};let F;try{F=K(H).default}catch(Y){return{__sfErr:"加载思否请求模块失败:"+String(Y)}}if(typeof F!=="function")return{__sfErr:"思否请求模块导出异常(default 非构造器)"};let D=null;const S=XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open=function(Y,P){try{if(String(P).indexOf(Q)!==-1)D=this}catch(q){}return S.apply(this,arguments)};try{const Y=new F({Token:M,userAgent:navigator.userAgent});if(z)Y.setServerTime(z);await Y.send(Q,{method:"GET"}).catch(function(){})}catch(Y){XMLHttpRequest.prototype.open=S;return{__sfErr:"签名请求执行失败:"+String(Y)}}XMLHttpRequest.prototype.open=S;if(!D)return{__sfErr:"签名请求未发出(Request.send 未触发 XHR)"};return{status:D.status,raw:String(D.responseText||"")}}if(X.draftOnly){const Q={title:X.title,tags:[],text:X.content,object_id:"",type:"article"},z=await fetch("https://segmentfault.com/gateway/draft",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",token:M,accept:"*/*"},body:JSON.stringify(Q)}),J=await z.text();if(J==="Unauthorized"||J.includes("禁言")||J.includes("锁定"))return{ok:!1,stage:"publish",status:z.status,message:J==="Unauthorized"?"未授权":J};let K;try{K=JSON.parse(J)}catch(H){return{ok:!1,stage:"publish",status:z.status,message:"解析响应失败:"+J.slice(0,200)}}if(Array.isArray(K)){if(K[0]===1)return{ok:!1,stage:"publish",status:z.status,message:K[1]||"发布失败"};const H=K[1];if(H&&H.id)return{ok:!0,draft:!0,id:String(H.id),url:"https://segmentfault.com/write?draftId="+H.id}}if(!K||!K.id){const H=K&&(K.message||K.msg||K.error||K.errMsg)||J.slice(0,200);return{ok:!1,stage:"publish",status:z.status,message:H}}return{ok:!0,draft:!0,id:String(K.id),url:"https://segmentfault.com/write?draftId="+K.id}}const N={"Content-Type":"application/json",token:M,accept:"*/*"},j=Array.isArray(W.tags)?W.tags.map((Q)=>String(Q).trim()).filter(Boolean):[];if(j.length===0)return{ok:!1,stage:"tags",message:"思否发文必须至少指定 1 个标签(--tags),合法标签可用 `segmentfault tags <名称>` 校验"};const L=[];for(const Q of j){const z=await R("/tag/"+encodeURIComponent(Q));if(z&&z.__sfErr)return{ok:!1,stage:"tags",message:"标签「"+Q+"」校验失败:"+z.__sfErr};let J;try{J=JSON.parse(z.raw)}catch(H){return{ok:!1,stage:"tags",status:z.status,message:"解析标签「"+Q+"」响应失败(HTTP "+z.status+"):"+String(z.raw).slice(0,200)}}if(z.status!==200||typeof J==="string")return{ok:!1,stage:"tags",status:z.status,message:"思否标签接口请求被拒(HTTP "+z.status+"):"+(typeof J==="string"?J:String(z.raw).slice(0,120))};const K=J&&J.tag;if(!K||K.id==null)return{ok:!1,stage:"tags",message:"思否上找不到标签「"+Q+"」,请用 `segmentfault tags <名称>` 确认存在再发布"};L.push(K.id)}const $=Array.isArray(W.channels)?W.channels.map((Q)=>String(Q).trim()).filter(Boolean):[],B=[];if($.length>0){let Q=[];const z=await R("/channels");if(z&&z.__sfErr)return{ok:!1,stage:"channels",message:"获取思否频道列表失败:"+z.__sfErr};let J;try{J=JSON.parse(z.raw)}catch(K){return{ok:!1,stage:"channels",status:z.status,message:"解析频道列表失败(HTTP "+z.status+"):"+String(z.raw).slice(0,200)}}if(z.status!==200||typeof J==="string")return{ok:!1,stage:"channels",status:z.status,message:"思否频道接口请求被拒(HTTP "+z.status+"):"+(typeof J==="string"?J:String(z.raw).slice(0,120))};if(Array.isArray(J))Q=J;else if(J&&Array.isArray(J.data))Q=J.data;else if(J&&Array.isArray(J.rows))Q=J.rows;else if(J&&Array.isArray(J.channels))Q=J.channels;else Q=[];for(const K of $){const H=Q.find((F)=>F&&(String(F.name||F.title||F.text)===K||String(F.slug)===K));if(!H||!H.slug){const F=Q.map((D)=>D&&(D.name||D.title||D.text)||"").filter(Boolean).join(" / ");return{ok:!1,stage:"channels",message:"思否上找不到频道「"+K+"」,可选:"+(F||"(频道列表为空)")}}B.push(H.slug)}}const _={title:X.title,text:X.content,tags:L};if(B.length>0)_.channel=B;if(W.cover)_.cover=String(W.cover);const U=await fetch("https://segmentfault.com/gateway/article",{method:"POST",credentials:"include",headers:N,body:JSON.stringify(_)}),T=await U.text();if(T==="Unauthorized"||T.includes("禁言")||T.includes("锁定"))return{ok:!1,stage:"publish",status:U.status,message:T==="Unauthorized"?"未授权,请检查登录态":T};let V;try{V=JSON.parse(T)}catch(Q){return{ok:!1,stage:"publish",status:U.status,message:"解析发布响应失败:"+T.slice(0,200)}}if(V&&(V.isError||V.error||V.errno||V.scene_id)){let Q=V.message||V.msg||V.error||"";if(V.scene_id)Q=(Q?Q+";":"")+"思否要求人机验证(geetest),无法在自动化环境完成";return{ok:!1,stage:"publish",status:U.status,message:Q||"发布失败:"+T.slice(0,200)}}const E=V&&V.data&&V.data.id;if(E==null)return{ok:!1,stage:"publish",status:U.status,message:V&&(V.message||V.msg)||"发布响应缺少文章 id:"+T.slice(0,200)};return{ok:!0,draft:!1,id:String(E),url:"https://segmentfault.com/a/"+E}}};y({site:"segmentfault",name:"article",access:"write",description:"发布文章到思否(SegmentFault)。默认正式发布,加 --draft 仅存草稿。正文默认 Markdown;外链图片自动转存到思否图床。正式发布必须指定标签(--tags),合法标签用 `segmentfault tags <名称>` 校验、频道用 `segmentfault channels` 列举。",domain:"segmentfault.com",strategy:C.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(默认 Markdown;传 --html 则视为 HTML)"},{name:"file",help:"正文文件路径(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"将正文视为原始 HTML 而非 Markdown"},{name:"tags",help:"标签名,逗号分隔(正式发布必填,至少 1 个)。合法标签用 `segmentfault tags <名称>` 校验;草稿可省略。"},{name:"cover",help:"封面图思否图床 url(选填)。需先把图片上传到思否图床得到 url。"},{name:"channels",help:"频道名,逗号分隔(选填)。合法值用 `segmentfault channels` 列举。"},{name:"draft",type:"boolean",help:"仅保存草稿,不正式发布"},{name:"execute",type:"boolean",help:"实际执行发布。不加此参数时命令拒绝写操作。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(X,A)=>{if(!X)throw new v("思否发布需要浏览器会话");I(A);const W=String(A.title??"").trim();if(!W)throw new O("INVALID_INPUT","文章标题不能为空");const G=await h(A),Z=Boolean(A.draft),M=(_)=>typeof _==="string"?_.split(",").map((U)=>U.trim()).filter(Boolean):[],b=M(A.tags),R=M(A.channels),N=typeof A.cover==="string"?A.cover.trim():"";if(!Z&&b.length===0)throw new O("INVALID_INPUT","思否正式发布必须指定 --tags(至少 1 个标签);合法标签用 `segmentfault tags <名称>` 校验,或加 --draft 只存草稿");const j=await p(X,{title:W,body:G,format:A.html?"html":"markdown",draftOnly:Z,profile:segmentfaultProfile,publishParams:{tags:b,channels:R,cover:N}}),L=j.images.uploaded.length|0,$=j.images.failed.length|0;let B=j.draft?"已保存到思否草稿箱(需在网页上手动发布)":"已正式发布到思否";if(L||$)B+=`;图片:${L} 张转存成功${$?`,${$} 张失败`:""}`;return w(B,"article","",j.draft?"draft":"created",{created_target:(j.draft?"draft:":"article:")+j.id,created_url:j.url})}});
|
package/clis/sohu/article.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as $,CommandExecutionError as v}from"@jackwener/opencli/errors";import{cli as N,Strategy as b}from"@jackwener/opencli/registry";import{publishArticle as f,gotoWritePage as P}from"../_shared/article/publish.js";import{SOHU_MPBP_HEADERS_JS as R}from"./mpbp.js";function k(){const Q="0123456789abcdef";let K="";for(let z=0;z<32;z++)K+=Q[Math.floor(Math.random()*Q.length)];return K}export const sohuProfile={home:"https://mp.sohu.com/mpfe/v3/main/first/page?newsType=1",outputFormat:"html",preprocessConfig:{},checkAuth:async(Q)=>{try{const z=await(await fetch("https://mp.sohu.com/mpbp/bp/account/list?_="+Date.now(),{method:"GET",credentials:"include"})).json();if(z.code!==2000000||!z.data?.data?.length)return{isAuthenticated:!1};const L=[];for(const V of z.data.data)if(V.accounts)L.push(...V.accounts);if(!L.length)return{isAuthenticated:!1};const G=L[0],X=L.length>1?G.nickName+"(共"+L.length+"个子账号)":G.nickName;return{isAuthenticated:!0,userId:String(G.id),username:X,avatar:G.avatar||""}}catch(K){return{isAuthenticated:!1,error:String(K&&K.message||K)}}},image:{skip:["sohu.com"],uploadFn:async(Q,K)=>{const L=await(await fetch("https://mp.sohu.com/mpbp/bp/account/list?_="+Date.now(),{method:"GET",credentials:"include"})).json();if(L.code!==2000000||!L.data?.data?.length)throw Error("获取搜狐账号信息失败,请确认已登录搜狐号");const G=[];for(const B of L.data.data)if(B.accounts)G.push(...B.accounts);if(!G.length)throw Error("搜狐号子账号列表为空");const X=String(G[0].id),V=await fetch(Q,{credentials:"omit"});if(!V.ok)throw Error("图片下载失败("+V.status+"):"+Q);const Y=await V.blob(),F=new FormData;F.append("file",Y,"image.jpg");F.append("accountId",X);const M=await(await fetch("https://mp.sohu.com/commons/front/outerUpload/image/file?accountId="+X,{method:"POST",credentials:"include",body:F})).json();if(!M.url)throw Error("搜狐图片上传失败:"+(M.msg||JSON.stringify(M).slice(0,100)));return{url:M.url}}},publish:async(Q,K)=>{const z=Q.params||{},L=await fetch("https://mp.sohu.com/mpbp/bp/account/list?_="+Date.now(),{method:"GET",credentials:"include"}),G=await L.json();if(G.code!==2000000||!G.data?.data?.length)return{ok:!1,stage:"auth",status:L.status,message:"获取搜狐账号信息失败,请确认已登录搜狐号"};const X=[];for(const W of G.data.data)if(W.accounts)X.push(...W.accounts);if(!X.length)return{ok:!1,stage:"auth",status:0,message:"搜狐号子账号列表为空"};const V=Number(X[0].id),Y="0123456789abcdef";let F="";for(let W=0;W<32;W++)F+=Y[Math.floor(Math.random()*Y.length)];const q="100-"+Date.now()+"-"+F,M=Number(z.channelId);if(!Number.isFinite(M)||M<=0)return{ok:!1,stage:"params",status:0,message:"缺少合法 channelId(频道 id),请先用 `sohu channels` 取值"};const B=z.categoryId==null||z.categoryId===""?-1:Number(z.categoryId),U={title:Q.title,brief:typeof z.brief==="string"?z.brief:"",content:Q.content,channelId:M,categoryId:B,id:0,userColumnId:z.userColumnId==null||z.userColumnId==-1?0:Number(z.userColumnId),columnNewsIds:Array.isArray(z.columnNewsIds)?z.columnNewsIds:[],businessCode:0,declareOriginal:Boolean(z.declareOriginal),cover:typeof z.cover==="string"?z.cover:"",topicIds:Array.isArray(z.topicIds)?z.topicIds:[],isAd:0,userLabels:"[]",reprint:!1,customTags:typeof z.customTags==="string"?z.customTags:"",infoResource:0,sourceUrl:"",visibleToLoginedUsers:0,attrIds:Array.isArray(z.attrIds)?z.attrIds:[],accountId:V};if(Q.draftOnly){const W=Object.assign({},U,{auto:!0}),S=await fetch("https://mp.sohu.com/mpbp/bp/news/v4/news/draft/v2?accountId="+V,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest","dv-id":F,"sp-cm":q},body:JSON.stringify(W)}),x=await S.text();let O=null;try{O=JSON.parse(x)}catch(i){}if(!S.ok||!O||!O.success)return{ok:!1,stage:"draft",status:S.status,message:O&&O.msg||x.slice(0,300)};const A=String(O.data),E="https://mp.sohu.com/mpfe/v4/contentManagement/news/addarticle?spm=smmp.articlelist.0.0&contentStatus=2&id="+A;return{ok:!0,id:A,url:E,draft:!0}}const H=await fetch("https://mp.sohu.com/mpbp/bp/news/v4/news/publish/v2?accountId="+V,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest","dv-id":F,"sp-cm":q},body:JSON.stringify(U)}),T=await H.text();let Z=null;try{Z=JSON.parse(T)}catch(W){}if(!H.ok||!Z||Z.code!==2000000)return{ok:!1,stage:"publish",status:H.status,message:Z&&(Z.msg||Z.message)||T.slice(0,300)};if(Z.data!=null&&Object.prototype.toString.call(Z.data)==="[object Object]")return{ok:!1,stage:"origin_check",status:H.status,message:"声明原创触发了原创校验(需在搜狐后台完成原创确认):"+JSON.stringify(Z.data).slice(0,200)};const D=String(Z.data),_="https://mp.sohu.com/mpfe/v4/contentManagement/news/addarticle?spm=smmp.articlelist.0.0&contentStatus=1&id="+D;return{ok:!0,id:D,url:_,draft:!1}}},sohuAuthProfile={home:sohuProfile.home,checkAuth:sohuProfile.checkAuth};function y(Q){if(!Q.execute)throw new $("INVALID_INPUT","此命令需要 --execute 才会真正写入,干跑请去掉该参数。")}async function j(Q){const K=typeof Q.text==="string"?Q.text:void 0,z=typeof Q.file==="string"?Q.file:void 0;if(K&&z)throw new $("INVALID_INPUT","text 和 --file 只能二选一");let L=K??"";if(z){const{readFile:G,stat:X}=await import("node:fs/promises");let V;try{V=await X(z)}catch{throw new $("INVALID_INPUT","文件不存在:"+z)}if(!V.isFile())throw new $("INVALID_INPUT","路径不是可读文本文件:"+z);let Y;try{Y=await G(z)}catch{throw new $("INVALID_INPUT","文件读取失败:"+z)}try{L=new TextDecoder("utf-8",{fatal:!0}).decode(Y)}catch{throw new $("INVALID_INPUT","文件不是合法 UTF-8 编码:"+z)}}if(!L.trim())throw new $("INVALID_INPUT","正文不能为空");return L}function C(Q,K,z,L,G={}){return[{status:"success",outcome:L,message:Q,target_type:K,target:z,...G}]}async function J(Q,K,z){const L="(async () => {try {"+R+"const r = await fetch("+JSON.stringify(K)+', { method: "GET", credentials: "include", headers: HDRS });const t = await r.text();let j = null; try { j = JSON.parse(t); } catch (e) {}return { ok: r.ok, status: r.status, json: j, text: t.slice(0, 300) };} catch (e) { return { ok: false, status: 0, json: null, text: String((e && e.message) || e) }; }})()',G=await Q.evaluate(L);if(!G||!G.ok||!G.json)throw new $("INVALID_INPUT","搜狐"+z+"接口请求失败(HTTP "+(G?G.status:"?")+"):"+(G?G.text:""));if(G.json.success===!1||G.json.code!=null&&G.json.code!==2000000&&G.json.code!==2000)throw new $("INVALID_INPUT","搜狐"+z+"接口返回错误:"+(G.json.msg||G.json.message||"未登录或无权限"));return G.json}async function m(Q){const K=await J(Q,"https://mp.sohu.com/mpbp/bp/account/list?_="+Date.now(),"账号列表"),z=K.data&&K.data.data||[],L=[];for(const G of z)if(G.accounts)L.push(...G.accounts);if(!L.length)throw new $("INVALID_INPUT","搜狐号子账号列表为空,请确认已登录搜狐号");return String(L[0].id)}async function I(Q,K,z){const L=await J(Q,"https://mp.sohu.com/mpbp/bp/account/common/channels-data-api?status=1&accountId="+encodeURIComponent(K),"频道列举"),G=Array.isArray(L)?L:L.data||[],X=G.find((V)=>String(V.name)===String(z));if(!X)throw new $("INVALID_INPUT","未找到频道「"+z+"」,可选:"+(G.map((V)=>V.name).join(" / ")||"(空)")+"。用 `sohu channels` 查看完整列表。");return Number(X.id!=null?X.id:X.channelId)}async function h(Q,K,z,L){const G=await J(Q,"https://mp.sohu.com/mpbp/bp/account/common/channels/"+z+"/categories?accountId="+encodeURIComponent(K),"分类列举"),X=Array.isArray(G)?G:G.data||[],V=X.find((Y)=>String(Y.name)===String(L));if(!V)throw new $("INVALID_INPUT","频道(id="+z+")下未找到分类「"+L+"」,可选:"+(X.map((Y)=>Y.name).join(" / ")||"(该频道无分类)")+"。用 `sohu categories --channel <频道名>` 查看。");return Number(V.id)}async function w(Q,K,z){const L=await J(Q,"https://mp.sohu.com/mpbp/bp/account/column/v2/list?accountId="+encodeURIComponent(K)+"&page=1&size=100","专栏列举"),G=Array.isArray(L)?L:L.data&&(L.data.data||L.data)||L.data||[],X=Array.isArray(G)?G:[],V=X.find((Y)=>String(Y.name)===String(z));if(!V)throw new $("INVALID_INPUT","未找到专栏「"+z+"」,可选:"+(X.map((Y)=>Y.name).join(" / ")||"(你还没有任何专栏)")+"。用 `sohu columns` 查看。");return Number(V.id)}async function d(Q,K,z){const L=z.split(",").map((X)=>X.trim()).filter(Boolean);if(!L.length)return[];const G=[];for(const X of L){const V=await J(Q,"https://mp.sohu.com/mpbp/bp/news/v4/label/topic/search?accountId="+K+"&keyword="+encodeURIComponent(X),"话题搜索"),F=(Array.isArray(V)?V:V.data||[]).find((q)=>String(q.name)===String(X));if(!F)throw new $("INVALID_INPUT","未找到话题「"+X+"」,请用 `sohu topics --keyword "+X+"` 搜索确认确切名称。");G.push(F.id)}return G}N({site:"sohu",name:"article",access:"write",description:"发布文章到搜狐号(HTML 格式,外链图片自动转存到搜狐图床)。默认正式发布,加 --draft 仅存草稿。频道必填,合法值用 `sohu channels` 取。",domain:"mp.sohu.com",strategy:b.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(默认 Markdown,传 --html 则视为 HTML)"},{name:"file",help:"正文文件路径(UTF-8,Markdown 或 HTML)"},{name:"html",type:"boolean",help:"将正文视为原始 HTML 而非 Markdown"},{name:"channel",help:"频道名(精确匹配,必填),合法值用 `sohu channels` 列举"},{name:"category",help:"分类名(精确匹配,依赖所选频道),合法值用 `sohu categories --channel <频道名>` 列举;不传=不限"},{name:"column",help:"专栏名(精确匹配),合法值用 `sohu columns` 列举;不传=不归属专栏"},{name:"topics",help:"话题名,逗号分隔(每个精确匹配),用 `sohu topics` 搜索取确切名"},{name:"cover",help:"封面图 URL(须为搜狐图床地址);可空"},{name:"brief",help:"文章摘要;可空"},{name:"declare-original",type:"boolean",help:"声明原创(会触发搜狐原创校验,发布将被拦到原创确认流程)"},{name:"draft",type:"boolean",help:"仅保存草稿,不正式发布"},{name:"execute",type:"boolean",help:"实际执行写入操作;不加此参数则拒绝写入(干跑保护)"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(Q,K)=>{if(!Q)throw new v("搜狐号文章发布需要浏览器会话");y(K);const z=String(K.title??"").trim();if(!z)throw new $("INVALID_INPUT","文章标题不能为空");const L=await j(K),G=Boolean(K.draft),X=typeof K.channel==="string"?K.channel.trim():"";if(!X)throw new $("INVALID_INPUT","缺少 --channel(频道名)。请先运行 `sohu channels` 取合法频道名再传入,不提供默认值。");await P(Q,sohuProfile.home);const V=await m(Q),Y=await I(Q,V,X);let F=-1;const q=typeof K.category==="string"?K.category.trim():"";if(q)F=await h(Q,V,Y,q);let M=0;const B=typeof K.column==="string"?K.column.trim():"";if(B)M=await w(Q,V,B);let U=[];const H=typeof K.topics==="string"?K.topics.trim():"";if(H)U=await d(Q,V,H);const T={channelId:Y,categoryId:F,userColumnId:M,topicIds:U,cover:typeof K.cover==="string"?K.cover.trim():"",brief:typeof K.brief==="string"?K.brief:"",declareOriginal:Boolean(K["declare-original"])},Z=await f(Q,{title:z,body:L,format:K.html?"html":"markdown",draftOnly:G,profile:sohuProfile,publishParams:T}),D=Z.images.uploaded.length|0,_=Z.images.failed.length|0;let W=Z.draft?"已保存搜狐号草稿":"已正式发布到搜狐号";if(D||_)W+=`;图片:${D} 张已转存${_?`,${_} 张失败`:""}`;return C(W,"article","",Z.draft?"draft":"created",{created_target:(Z.draft?"draft:":"article:")+Z.id,created_url:Z.url})}});export const __test__={sohuProfile,requireExecute:y,buildResultRow:C,resolvePayload:j,generateDeviceId:k};
|
|
1
|
+
import{CliError as $,CommandExecutionError as v}from"@jackwener/opencli/errors";import{cli as N,Strategy as b}from"@jackwener/opencli/registry";import{publishArticle as f,gotoWritePage as P}from"../_shared/article/publish.js";import{SOHU_MPBP_HEADERS_JS as R}from"./mpbp.js";function k(){const Q="0123456789abcdef";let K="";for(let z=0;z<32;z++)K+=Q[Math.floor(Math.random()*Q.length)];return K}export const sohuProfile={home:"https://mp.sohu.com/mpfe/v3/main/first/page?newsType=1",outputFormat:"html",preprocessConfig:{},checkAuth:async(Q)=>{try{const z=await(await fetch("https://mp.sohu.com/mpbp/bp/account/list?_="+Date.now(),{method:"GET",credentials:"include"})).json();if(z.code!==2000000||!z.data?.data?.length)return{isAuthenticated:!1};const L=[];for(const V of z.data.data)if(V.accounts)L.push(...V.accounts);if(!L.length)return{isAuthenticated:!1};const G=L[0],X=L.length>1?G.nickName+"(共"+L.length+"个子账号)":G.nickName;return{isAuthenticated:!0,userId:String(G.id),username:X,avatar:G.avatar||""}}catch(K){return{isAuthenticated:!1,error:String(K&&K.message||K)}}},image:{skip:["sohu.com"],uploadFn:async(Q,K)=>{const L=await(await fetch("https://mp.sohu.com/mpbp/bp/account/list?_="+Date.now(),{method:"GET",credentials:"include"})).json();if(L.code!==2000000||!L.data?.data?.length)throw Error("获取搜狐账号信息失败,请确认已登录搜狐号");const G=[];for(const B of L.data.data)if(B.accounts)G.push(...B.accounts);if(!G.length)throw Error("搜狐号子账号列表为空");const X=String(G[0].id),V=await fetch(Q,{credentials:"omit"});if(!V.ok)throw Error("图片下载失败("+V.status+"):"+Q);const Y=await V.blob(),F=new FormData;F.append("file",Y,"image.jpg");F.append("accountId",X);const M=await(await fetch("https://mp.sohu.com/commons/front/outerUpload/image/file?accountId="+X,{method:"POST",credentials:"include",body:F})).json();if(!M.url)throw Error("搜狐图片上传失败:"+(M.msg||JSON.stringify(M).slice(0,100)));return{url:M.url}}},publish:async(Q,K)=>{const z=Q.params||{},L=await fetch("https://mp.sohu.com/mpbp/bp/account/list?_="+Date.now(),{method:"GET",credentials:"include"}),G=await L.json();if(G.code!==2000000||!G.data?.data?.length)return{ok:!1,stage:"auth",status:L.status,message:"获取搜狐账号信息失败,请确认已登录搜狐号"};const X=[];for(const W of G.data.data)if(W.accounts)X.push(...W.accounts);if(!X.length)return{ok:!1,stage:"auth",status:0,message:"搜狐号子账号列表为空"};const V=Number(X[0].id),Y="0123456789abcdef";let F="";for(let W=0;W<32;W++)F+=Y[Math.floor(Math.random()*Y.length)];const q="100-"+Date.now()+"-"+F,M=Number(z.channelId);if(!Number.isFinite(M)||M<=0)return{ok:!1,stage:"params",status:0,message:"缺少合法 channelId(频道 id),请先用 `sohu channels` 取值"};const B=z.categoryId==null||z.categoryId===""?-1:Number(z.categoryId),U={title:Q.title,brief:typeof z.brief==="string"?z.brief:"",content:Q.content,channelId:M,categoryId:B,id:0,userColumnId:z.userColumnId==null||z.userColumnId==-1?0:Number(z.userColumnId),columnNewsIds:Array.isArray(z.columnNewsIds)?z.columnNewsIds:[],businessCode:0,declareOriginal:Boolean(z.declareOriginal),cover:typeof z.cover==="string"?z.cover:"",topicIds:Array.isArray(z.topicIds)?z.topicIds:[],isAd:0,userLabels:"[]",reprint:!1,customTags:typeof z.customTags==="string"?z.customTags:"",infoResource:0,sourceUrl:"",visibleToLoginedUsers:0,attrIds:Array.isArray(z.attrIds)?z.attrIds:[],accountId:V};if(Q.draftOnly){const W=Object.assign({},U,{auto:!0}),S=await fetch("https://mp.sohu.com/mpbp/bp/news/v4/news/draft/v2?accountId="+V,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest","dv-id":F,"sp-cm":q},body:JSON.stringify(W)}),x=await S.text();let O=null;try{O=JSON.parse(x)}catch(i){}if(!S.ok||!O||!O.success)return{ok:!1,stage:"draft",status:S.status,message:O&&O.msg||x.slice(0,300)};const A=String(O.data),E="https://mp.sohu.com/mpfe/v4/contentManagement/news/addarticle?spm=smmp.articlelist.0.0&contentStatus=2&id="+A;return{ok:!0,id:A,url:E,draft:!0}}const H=await fetch("https://mp.sohu.com/mpbp/bp/news/v4/news/publish/v2?accountId="+V,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest","dv-id":F,"sp-cm":q},body:JSON.stringify(U)}),T=await H.text();let Z=null;try{Z=JSON.parse(T)}catch(W){}if(!H.ok||!Z||Z.code!==2000000)return{ok:!1,stage:"publish",status:H.status,message:Z&&(Z.msg||Z.message)||T.slice(0,300)};if(Z.data!=null&&Object.prototype.toString.call(Z.data)==="[object Object]")return{ok:!1,stage:"origin_check",status:H.status,message:"声明原创触发了原创校验(需在搜狐后台完成原创确认):"+JSON.stringify(Z.data).slice(0,200)};const D=String(Z.data),_="https://mp.sohu.com/mpfe/v4/contentManagement/news/addarticle?spm=smmp.articlelist.0.0&contentStatus=1&id="+D;return{ok:!0,id:D,url:_,draft:!1}}},sohuAuthProfile={home:sohuProfile.home,checkAuth:sohuProfile.checkAuth};function y(Q){if(!Q.execute)throw new $("INVALID_INPUT","此命令需要 --execute 才会真正写入。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function j(Q){const K=typeof Q.text==="string"?Q.text:void 0,z=typeof Q.file==="string"?Q.file:void 0;if(K&&z)throw new $("INVALID_INPUT","text 和 --file 只能二选一");let L=K??"";if(z){const{readFile:G,stat:X}=await import("node:fs/promises");let V;try{V=await X(z)}catch{throw new $("INVALID_INPUT","文件不存在:"+z)}if(!V.isFile())throw new $("INVALID_INPUT","路径不是可读文本文件:"+z);let Y;try{Y=await G(z)}catch{throw new $("INVALID_INPUT","文件读取失败:"+z)}try{L=new TextDecoder("utf-8",{fatal:!0}).decode(Y)}catch{throw new $("INVALID_INPUT","文件不是合法 UTF-8 编码:"+z)}}if(!L.trim())throw new $("INVALID_INPUT","正文不能为空");return L}function C(Q,K,z,L,G={}){return[{status:"success",outcome:L,message:Q,target_type:K,target:z,...G}]}async function J(Q,K,z){const L="(async () => {try {"+R+"const r = await fetch("+JSON.stringify(K)+', { method: "GET", credentials: "include", headers: HDRS });const t = await r.text();let j = null; try { j = JSON.parse(t); } catch (e) {}return { ok: r.ok, status: r.status, json: j, text: t.slice(0, 300) };} catch (e) { return { ok: false, status: 0, json: null, text: String((e && e.message) || e) }; }})()',G=await Q.evaluate(L);if(!G||!G.ok||!G.json)throw new $("INVALID_INPUT","搜狐"+z+"接口请求失败(HTTP "+(G?G.status:"?")+"):"+(G?G.text:""));if(G.json.success===!1||G.json.code!=null&&G.json.code!==2000000&&G.json.code!==2000)throw new $("INVALID_INPUT","搜狐"+z+"接口返回错误:"+(G.json.msg||G.json.message||"未登录或无权限"));return G.json}async function m(Q){const K=await J(Q,"https://mp.sohu.com/mpbp/bp/account/list?_="+Date.now(),"账号列表"),z=K.data&&K.data.data||[],L=[];for(const G of z)if(G.accounts)L.push(...G.accounts);if(!L.length)throw new $("INVALID_INPUT","搜狐号子账号列表为空,请确认已登录搜狐号");return String(L[0].id)}async function I(Q,K,z){const L=await J(Q,"https://mp.sohu.com/mpbp/bp/account/common/channels-data-api?status=1&accountId="+encodeURIComponent(K),"频道列举"),G=Array.isArray(L)?L:L.data||[],X=G.find((V)=>String(V.name)===String(z));if(!X)throw new $("INVALID_INPUT","未找到频道「"+z+"」,可选:"+(G.map((V)=>V.name).join(" / ")||"(空)")+"。用 `sohu channels` 查看完整列表。");return Number(X.id!=null?X.id:X.channelId)}async function h(Q,K,z,L){const G=await J(Q,"https://mp.sohu.com/mpbp/bp/account/common/channels/"+z+"/categories?accountId="+encodeURIComponent(K),"分类列举"),X=Array.isArray(G)?G:G.data||[],V=X.find((Y)=>String(Y.name)===String(L));if(!V)throw new $("INVALID_INPUT","频道(id="+z+")下未找到分类「"+L+"」,可选:"+(X.map((Y)=>Y.name).join(" / ")||"(该频道无分类)")+"。用 `sohu categories --channel <频道名>` 查看。");return Number(V.id)}async function w(Q,K,z){const L=await J(Q,"https://mp.sohu.com/mpbp/bp/account/column/v2/list?accountId="+encodeURIComponent(K)+"&page=1&size=100","专栏列举"),G=Array.isArray(L)?L:L.data&&(L.data.data||L.data)||L.data||[],X=Array.isArray(G)?G:[],V=X.find((Y)=>String(Y.name)===String(z));if(!V)throw new $("INVALID_INPUT","未找到专栏「"+z+"」,可选:"+(X.map((Y)=>Y.name).join(" / ")||"(你还没有任何专栏)")+"。用 `sohu columns` 查看。");return Number(V.id)}async function d(Q,K,z){const L=z.split(",").map((X)=>X.trim()).filter(Boolean);if(!L.length)return[];const G=[];for(const X of L){const V=await J(Q,"https://mp.sohu.com/mpbp/bp/news/v4/label/topic/search?accountId="+K+"&keyword="+encodeURIComponent(X),"话题搜索"),F=(Array.isArray(V)?V:V.data||[]).find((q)=>String(q.name)===String(X));if(!F)throw new $("INVALID_INPUT","未找到话题「"+X+"」,请用 `sohu topics --keyword "+X+"` 搜索确认确切名称。");G.push(F.id)}return G}N({site:"sohu",name:"article",access:"write",description:"发布文章到搜狐号(HTML 格式,外链图片自动转存到搜狐图床)。默认正式发布,加 --draft 仅存草稿。频道必填,合法值用 `sohu channels` 取。",domain:"mp.sohu.com",strategy:b.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(默认 Markdown,传 --html 则视为 HTML)"},{name:"file",help:"正文文件路径(UTF-8,Markdown 或 HTML)"},{name:"html",type:"boolean",help:"将正文视为原始 HTML 而非 Markdown"},{name:"channel",help:"频道名(精确匹配,必填),合法值用 `sohu channels` 列举"},{name:"category",help:"分类名(精确匹配,依赖所选频道),合法值用 `sohu categories --channel <频道名>` 列举;不传=不限"},{name:"column",help:"专栏名(精确匹配),合法值用 `sohu columns` 列举;不传=不归属专栏"},{name:"topics",help:"话题名,逗号分隔(每个精确匹配),用 `sohu topics` 搜索取确切名"},{name:"cover",help:"封面图 URL(须为搜狐图床地址);可空"},{name:"brief",help:"文章摘要;可空"},{name:"declare-original",type:"boolean",help:"声明原创(会触发搜狐原创校验,发布将被拦到原创确认流程)"},{name:"draft",type:"boolean",help:"仅保存草稿,不正式发布"},{name:"execute",type:"boolean",help:"实际执行写入操作;不加此参数则拒绝写入(干跑保护)"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(Q,K)=>{if(!Q)throw new v("搜狐号文章发布需要浏览器会话");y(K);const z=String(K.title??"").trim();if(!z)throw new $("INVALID_INPUT","文章标题不能为空");const L=await j(K),G=Boolean(K.draft),X=typeof K.channel==="string"?K.channel.trim():"";if(!X)throw new $("INVALID_INPUT","缺少 --channel(频道名)。请先运行 `sohu channels` 取合法频道名再传入,不提供默认值。");await P(Q,sohuProfile.home);const V=await m(Q),Y=await I(Q,V,X);let F=-1;const q=typeof K.category==="string"?K.category.trim():"";if(q)F=await h(Q,V,Y,q);let M=0;const B=typeof K.column==="string"?K.column.trim():"";if(B)M=await w(Q,V,B);let U=[];const H=typeof K.topics==="string"?K.topics.trim():"";if(H)U=await d(Q,V,H);const T={channelId:Y,categoryId:F,userColumnId:M,topicIds:U,cover:typeof K.cover==="string"?K.cover.trim():"",brief:typeof K.brief==="string"?K.brief:"",declareOriginal:Boolean(K["declare-original"])},Z=await f(Q,{title:z,body:L,format:K.html?"html":"markdown",draftOnly:G,profile:sohuProfile,publishParams:T}),D=Z.images.uploaded.length|0,_=Z.images.failed.length|0;let W=Z.draft?"已保存搜狐号草稿":"已正式发布到搜狐号";if(D||_)W+=`;图片:${D} 张已转存${_?`,${_} 张失败`:""}`;return C(W,"article","",Z.draft?"draft":"created",{created_target:(Z.draft?"draft:":"article:")+Z.id,created_url:Z.url})}});export const __test__={sohuProfile,requireExecute:y,buildResultRow:C,resolvePayload:j,generateDeviceId:k};
|
package/clis/toutiao/article.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as V,CommandExecutionError as T}from"@jackwener/opencli/errors";import{cli as h,Strategy as P}from"@jackwener/opencli/registry";import{publishArticle as _}from"../_shared/article/publish.js";import{readFile as x,stat as y}from"node:fs/promises";function b(J){if(!J.execute)throw new V("INVALID_INPUT","此头条号写操作需要 --execute
|
|
1
|
+
import{CliError as V,CommandExecutionError as T}from"@jackwener/opencli/errors";import{cli as h,Strategy as P}from"@jackwener/opencli/registry";import{publishArticle as _}from"../_shared/article/publish.js";import{readFile as x,stat as y}from"node:fs/promises";function b(J){if(!J.execute)throw new V("INVALID_INPUT","此头条号写操作需要 --execute 参数才能真正发布。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function C(J){const G=typeof J.text==="string"?J.text:void 0,z=typeof J.file==="string"?J.file:void 0;if(G&&z)throw new V("INVALID_INPUT","正文和 --file 只能选一个");let B=G??"";if(z){let Q;try{Q=await y(z)}catch{throw new V("INVALID_INPUT",`找不到文件: ${z}`)}if(!Q.isFile())throw new V("INVALID_INPUT",`--file 必须是可读文本文件: ${z}`);let X;try{X=await x(z)}catch{throw new V("INVALID_INPUT",`文件无法读取: ${z}`)}try{B=new TextDecoder("utf-8",{fatal:!0}).decode(X)}catch{throw new V("INVALID_INPUT",`文件必须是 UTF-8 编码: ${z}`)}}if(!B.trim())throw new V("INVALID_INPUT","正文不能为空");return B}function E(J,G,z,B,Q={}){return[{status:"success",outcome:B,message:J,target_type:G,target:z,...Q}]}export const toutiaoProfile={home:"https://mp.toutiao.com/profile_v3/graphic/publish",outputFormat:"html",preprocessConfig:{},publish:async(J,G)=>{const z=J.params||{},B=[],Q=[],X=["toutiao.com","pstatp.com","byteimg.com","bytecdn","ibytedtos","toutiaoimg","tos-cn"];async function W(D){const L=await fetch(D,{credentials:"omit"});if(!L.ok)throw Error("图片下载失败 HTTP "+L.status+":"+D.slice(0,80));const M=await L.blob(),S=((M.type||"").split("/")[1]||"jpg").split("+")[0].split(";")[0]||"jpg",H=new FormData;H.append("upfile",M,"image."+S);const U=await(await fetch("https://mp.toutiao.com/mp/agw/article_material/photo/upload_picture?type=ueditor&pgc_watermark=1&action=uploadimage&encode=utf-8",{method:"POST",credentials:"include",body:H})).json();if(U.code!==0||U.state!=="SUCCESS"||!U.url)throw Error(U.message||"图片上传失败");return{url:U.url,web_uri:U.origin_web_uri||U.web_uri,width:U.width,height:U.height}}const Y=document.createElement("div");Y.innerHTML=J.content;const Z=Array.prototype.slice.call(Y.querySelectorAll("img"));for(const D of Z){const L=D.getAttribute("src")||"";if(!L){D.remove();continue}if(X.some((M)=>L.indexOf(M)!==-1))continue;try{const M=await W(L);D.setAttribute("src",M.url);D.setAttribute("web_uri",M.web_uri);B.push({src:L.slice(0,120),url:M.url})}catch(M){Q.push({src:L.slice(0,120),error:String(M&&M.message||M)})}}const $=Y.innerHTML,A=[];if(z.cover&&String(z.cover).trim()){let D;try{D=await W(String(z.cover))}catch(L){return{ok:!1,stage:"cover",message:"封面图转存失败:"+String(L&&L.message||L),uploaded:B,failed:Q}}A.push({id:0,url:D.url,uri:D.web_uri,origin_uri:D.web_uri,ic_uri:"",thumb_width:D.width,thumb_height:D.height})}await fetch("https://mp.toutiao.com/profile_v3/graphic/publish",{credentials:"include"});const O=new URLSearchParams({title:J.title,article_ad_type:"2",article_type:"0",from_diagnosis:"0",origin_debut_check_pgc_normal:"0",tree_plan_article:"0",save:"0",pgc_id:"0",content:$,pgc_feed_covers:JSON.stringify(A)}),F=await fetch("https://mp.toutiao.com/mp/agw/article/publish?source=mp&type=article",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:O});let K=null;try{K=await F.json()}catch(D){}const R=K&&(K.code===0||K.err_no===0),q=K&&K.data&&K.data.pgc_id;if(!R||!q||String(q)==="0"){let D=K&&(K.message||K.reason)||"头条号发布失败";if(K&&(K.code===7050||K.err_no===7050))D+="(code 7050:官方编辑器页发布同样被拒,通常为账号被平台限制发文或内容风控拦截,"+"请登录 mp.toutiao.com 查看站内通知并手动发一篇确认;读接口不受影响)";return{ok:!1,stage:"publish",status:F.status,message:D,detail:K,uploaded:B,failed:Q}}const N="https://mp.toutiao.com/profile_v3/graphic/publish?pgc_id="+String(q);return{ok:!0,id:String(q),url:N,draft:!1,uploaded:B,failed:Q}},checkAuth:async(J)=>{try{const z=await(await fetch("https://mp.toutiao.com/mp/agw/media/get_media_info",{credentials:"include"})).json(),B=z&&z.data&&z.data.user;if(z&&z.code===0&&B&&B.id)return{isAuthenticated:!0,userId:String(B.id),username:B.screen_name||B.name||"",avatar:B.https_avatar_url||B.avatar_url||""};return{isAuthenticated:!1}}catch(G){return{isAuthenticated:!1,error:String(G&&G.message||G)}}}};h({site:"toutiao",name:"article",access:"write",description:"正式发布头条号图文文章(会立即公开,头条无可靠的存草稿接口,故不支持草稿)。正文默认 Markdown,图片自动转存到头条图床。标题需 2~30 字;封面可选(--cover,不传则头条自动从正文图选封面)。",domain:"mp.toutiao.com",strategy:P.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题(2~30 字)"},{name:"text",positional:!0,help:"文章正文(默认 Markdown;传 --html 则视为原始 HTML)"},{name:"file",help:"正文文件路径(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"将正文视为原始 HTML 而非 Markdown"},{name:"cover",help:"封面图 URL 或本机路径(可选;会先转存到头条图床)"},{name:"execute",type:"boolean",help:"真正执行写操作。不加此参数时命令拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(J,G)=>{if(!J)throw new T("头条号文章发布需要浏览器会话");b(G);const z=String(G.title??"").trim();if(!z)throw new V("INVALID_INPUT","文章标题不能为空");if(z.length<2||z.length>30)throw new V("INVALID_INPUT",`头条号标题需 2~30 字,当前 ${z.length} 字`);const B=await C(G),X={cover:typeof G.cover==="string"?G.cover.trim():""},W=await _(J,{title:z,body:B,format:G.html?"html":"markdown",profile:toutiaoProfile,publishParams:X}),Y=W.images.uploaded.length|0,Z=W.images.failed.length|0;let $="已正式发布头条号文章";if(Y||Z)$+=`(图片:${Y} 张已转存${Z?`,${Z} 张失败`:""})`;return E($,"article","","created",{created_target:"article:"+W.id,created_url:W.url})}});
|
package/clis/weibo/article.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as A,CommandExecutionError as N}from"@jackwener/opencli/errors";import{cli as P,Strategy as T}from"@jackwener/opencli/registry";import{publishArticle as y}from"../_shared/article/publish.js";import{readFile as C,stat as E}from"node:fs/promises";function b(B){if(!B.execute)throw new A("INVALID_INPUT","微博头条文章写操作需要 --execute
|
|
1
|
+
import{CliError as A,CommandExecutionError as N}from"@jackwener/opencli/errors";import{cli as P,Strategy as T}from"@jackwener/opencli/registry";import{publishArticle as y}from"../_shared/article/publish.js";import{readFile as C,stat as E}from"node:fs/promises";function b(B){if(!B.execute)throw new A("INVALID_INPUT","微博头条文章写操作需要 --execute 确认,避免误发。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function q(B){const K=typeof B.text==="string"?B.text:void 0,G=typeof B.file==="string"?B.file:void 0;if(K&&G)throw new A("INVALID_INPUT","请只提供 <text> 或 --file 中的一种,不能同时使用。");let H=K??"";if(G){let z;try{z=await E(G)}catch{throw new A("INVALID_INPUT",`文件不存在:${G}`)}if(!z.isFile())throw new A("INVALID_INPUT",`路径不是可读文本文件:${G}`);let Y;try{Y=await C(G)}catch{throw new A("INVALID_INPUT",`文件无法读取:${G}`)}try{H=new TextDecoder("utf-8",{fatal:!0}).decode(Y)}catch{throw new A("INVALID_INPUT",`文件无法解码为 UTF-8:${G}`)}}if(!H.trim())throw new A("INVALID_INPUT","正文不能为空。");return H}function h(B,K,G,H,z={}){return[{status:"success",outcome:H,message:B,target_type:K,target:G,...z}]}export const weiboProfile={home:"https://card.weibo.com/article/v5/editor",outputFormat:"html",preprocessConfig:{compactHtml:!0},image:{skip:["sinaimg.cn","weibo.com"],uploadFn:async(B,K)=>{async function G(){let D=document.documentElement.innerHTML||"",L=D.match(/config:\s*JSON\.parse\('(.+?)'\)/);if(!L){D=await(await fetch("https://card.weibo.com/article/v5/editor",{credentials:"include"})).text();L=D.match(/config:\s*JSON\.parse\('(.+?)'\)/)}if(!L)return"";try{const J=JSON.parse(L[1].replace(/\\'/g,"'").replace(/\\\\/g,"\\"));return J.uid?String(J.uid):""}catch(J){return""}}function H(D){const L=btoa(D+"&"+Date.now()).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,""),J="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";let Q=L;while(Q.length<43)Q+=J.charAt(Math.floor(Math.random()*J.length));return Q.slice(0,43)}const z=await G();if(!z)throw Error("未取到微博 uid,请先登录微博");if(B.indexOf("data:")===0){const D=K.dataUriToBlob(B),J="https://picupload.weibo.com/interface/pic_upload.php?app=miniblog&s=json&p=1&data=1&url=&markpos=1&logo=0&nick=&file_source=4&_rid="+H(z),V=await(await fetch(J,{method:"POST",credentials:"include",body:D})).json(),W=V&&V.data&&V.data.pics&&V.data.pics.pic_1&&V.data.pics.pic_1.pid;if(!W)throw Error("图片直传失败:"+JSON.stringify(V).slice(0,200));return{url:"https://wx3.sinaimg.cn/large/"+W+".jpg"}}const Y=H(z);try{await fetch("https://card.weibo.com/article/v5/aj/editor/plugins/asyncuploadimg?uid="+z+"&_rid="+Y,{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",accept:"application/json, text/plain, */*","SN-REQID":Y},body:new URLSearchParams({"urls[0]":B})})}catch(D){}for(let D=0;D<30;D++){const L=H(z),Q=await(await fetch("https://card.weibo.com/article/v5/aj/editor/plugins/asyncimginfo?uid="+z+"&_rid="+L,{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",accept:"application/json, text/plain, */*","SN-REQID":L},body:new URLSearchParams({"urls[0]":B})})).json(),V=Q&&Q.data&&Q.data[0],W=V&&V.task_status_code;if(W===1&&V.pid)return{url:"https://wx3.sinaimg.cn/large/"+V.pid+".jpg"};if(W===2)throw Error("微博图片转存失败:"+B);await new Promise((O)=>setTimeout(O,1000))}throw Error("微博图片转存超时:"+B)}},checkAuth:async(B)=>{const H=(await(await fetch("https://card.weibo.com/article/v5/editor",{credentials:"include"})).text()).match(/config:\s*JSON\.parse\('(.+?)'\)/);if(!H)return{isAuthenticated:!1};let z=null;try{z=JSON.parse(H[1].replace(/\\'/g,"'").replace(/\\\\/g,"\\"))}catch(Y){return{isAuthenticated:!1,error:"编辑器 config 解析失败"}}if(!z||!z.uid)return{isAuthenticated:!1};return{isAuthenticated:!0,userId:String(z.uid),username:z.nick||"",avatar:z.avatar_large||""}},publish:async(B,K)=>{async function G(){let Z=document.documentElement.innerHTML||"",X=Z.match(/config:\s*JSON\.parse\('(.+?)'\)/);if(!X){Z=await(await fetch("https://card.weibo.com/article/v5/editor",{credentials:"include"})).text();X=Z.match(/config:\s*JSON\.parse\('(.+?)'\)/)}if(!X)return"";try{const $=JSON.parse(X[1].replace(/\\'/g,"'").replace(/\\\\/g,"\\"));return $.uid?String($.uid):""}catch($){return""}}function H(Z){const X=btoa(Z+"&"+Date.now()).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,""),$="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";let F=X;while(F.length<43)F+=$.charAt(Math.floor(Math.random()*$.length));return F.slice(0,43)}const z=await G();if(!z)return{ok:!1,stage:"auth",message:"未取到微博 uid,请先登录微博"};let Y=(B.content||"").replace(/>\s+</g,"><");const D=document.createElement("div");D.innerHTML=Y;const L=D.querySelectorAll("img");for(let Z=0;Z<L.length;Z++){const X=L[Z],$=(X.getAttribute("src")||"").match(/sinaimg\.cn\/large\/([^/."]+)\.jpg/);if(!$)continue;X.setAttribute("data-pid",$[1]);if(!X.closest("figure")){const F=document.createElement("figure");F.className="image";X.parentNode.insertBefore(F,X);F.appendChild(X)}}Y=D.innerHTML;const J=H(z),Q=await fetch("https://card.weibo.com/article/v5/aj/editor/draft/create?uid="+z+"&_rid="+J,{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",accept:"application/json, text/plain, */*","SN-REQID":J},body:new URLSearchParams({})}),V=await Q.text();let W=null;try{W=JSON.parse(V)}catch(Z){}if(!Q.ok||!W||W.code!==1e5||!W.data||!W.data.id)return{ok:!1,stage:"create",status:Q.status,message:W&&W.msg||"创建草稿失败:"+V.slice(0,200)};const O=String(W.data.id),U=B.params&&typeof B.params.cover==="string"?B.params.cover:"",j=H(z),M=await fetch("https://card.weibo.com/article/v5/aj/editor/draft/save?uid="+z+"&id="+O+"&_rid="+j,{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded",accept:"application/json, text/plain, */*","SN-REQID":j},body:new URLSearchParams({id:O,title:B.title,subtitle:"",type:"",status:"0",publish_at:"",error_msg:"",error_code:"0",collection:"[]",free_content:"",content:Y,cover:U,summary:"",writer:"",extra:"null",is_word:"0",article_recommend:"[]",follow_to_read:"1",isreward:"1",pay_setting:'{"ispay":0,"isvclub":0}',source:"0",action:"1",content_type:"0",save:"1"})}),S=await M.text();let _=null;try{_=JSON.parse(S)}catch(Z){}if(!M.ok||!_||String(_.code)!=="100000")return{ok:!1,stage:"save",status:M.status,message:_&&_.msg||"保存草稿失败:"+S.slice(0,200)};return{ok:!0,id:O,url:"https://card.weibo.com/article/v5/editor#/draft/"+O,draft:!0}}};P({site:"weibo",name:"article",access:"write",description:"把长文以微博头条文章形式保存到草稿箱(card.weibo.com)。正文默认 Markdown,外链/本机图片自动转存到微博图床,支持 --cover 题图。注意:平台接口只到草稿态,正式发布需在微博头条文章编辑器里手动操作。",domain:"weibo.com",strategy:T.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"文章正文(默认 Markdown;加 --html 则当 HTML 处理)"},{name:"file",help:"正文文件路径(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"把正文当 HTML 处理而不是 Markdown"},{name:"cover",help:"题图(本机图片路径或图片 URL,自动转存到微博图床)"},{name:"draft",type:"boolean",help:"仅保存草稿(微博头条文章目前只支持草稿,此参数保留兼容性)"},{name:"execute",type:"boolean",help:"实际执行写入。不加此参数时命令拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(B,K)=>{if(!B)throw new N("微博头条文章命令需要浏览器会话。");b(K);const G=String(K.title??"").trim();if(!G)throw new A("INVALID_INPUT","文章标题不能为空。");const H=await q(K),z=typeof K.cover==="string"&&K.cover.trim()?K.cover.trim():"",Y=!0,D=await y(B,{title:G,body:H,format:K.html?"html":"markdown",draftOnly:Y,profile:weiboProfile,publishParams:z?{cover:z}:null}),L=D.images.uploaded.length|0,J=D.images.failed.length|0;let Q="已保存微博头条文章草稿(正式发布请在编辑器里手动操作)";if(L||J)Q+="·图片:"+L+" 张已转存"+(J?","+J+" 张失败":"");return h(Q,"article","","draft",{created_target:"draft:"+D.id,created_url:D.url})}});
|
package/clis/weixin/article.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as W,CommandExecutionError as S}from"@jackwener/opencli/errors";import{cli as J,Strategy as P}from"@jackwener/opencli/registry";import{publishArticle as T}from"../_shared/article/publish.js";import{readFile as I,stat as y}from"node:fs/promises";function R(B){if(!B.execute)throw new W("INVALID_INPUT","微信公众号写操作需要 --execute
|
|
1
|
+
import{CliError as W,CommandExecutionError as S}from"@jackwener/opencli/errors";import{cli as J,Strategy as P}from"@jackwener/opencli/registry";import{publishArticle as T}from"../_shared/article/publish.js";import{readFile as I,stat as y}from"node:fs/promises";function R(B){if(!B.execute)throw new W("INVALID_INPUT","微信公众号写操作需要 --execute 确认,避免误发。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function C(B){const L=typeof B.text==="string"?B.text:void 0,z=typeof B.file==="string"?B.file:void 0;if(L&&z)throw new W("INVALID_INPUT","请只提供 <text> 或 --file 中的一种,不能同时使用。");let D=L??"";if(z){let G;try{G=await y(z)}catch{throw new W("INVALID_INPUT",`文件不存在:${z}`)}if(!G.isFile())throw new W("INVALID_INPUT",`路径不是可读文本文件:${z}`);let H;try{H=await I(z)}catch{throw new W("INVALID_INPUT",`文件无法读取:${z}`)}try{D=new TextDecoder("utf-8",{fatal:!0}).decode(H)}catch{throw new W("INVALID_INPUT",`文件无法解码为 UTF-8:${z}`)}}if(!D.trim())throw new W("INVALID_INPUT","正文不能为空。");return D}function b(B,L,z,D,G={}){return[{status:"success",outcome:D,message:B,target_type:L,target:z,...G}]}export const weixinProfile={home:"https://mp.weixin.qq.com",outputFormat:"html",preprocessConfig:{removeLinks:!0,keepLinkDomains:["mp.weixin.qq.com","weixin.qq.com"],compactHtml:!0},image:{skip:["mmbiz.qpic.cn","mmbiz.qlogo.cn"],uploadFn:async(B,L)=>{const z=document.documentElement.innerHTML,D=z.match(/data:\s*\{[\s\S]*?t:\s*["']([^"']+)["']/),G=z.match(/ticket:\s*["']([^"']+)["']/),H=z.match(/user_name:\s*["']([^"']+)["']/),V=z.match(/time:\s*["'](\d+)["']/);if(!D)throw Error("微信图片上传:页面中未找到 token,请确认已登录 mp.weixin.qq.com。");const K=D[1],X=G?G[1]:"",Q=H?H[1]:"",Z=V?Number(V[1]):Math.floor(Date.now()/1000),$=await fetch(B,{credentials:"omit"});if(!$.ok)throw Error("图片下载失败:"+B+"(HTTP "+$.status+")");const A=await $.blob(),F=Date.now(),q=F+".jpg",Y=new FormData;Y.append("type",A.type||"image/jpeg");Y.append("id",String(F));Y.append("name",q);Y.append("lastModifiedDate",new Date().toString());Y.append("size",String(A.size));Y.append("file",A,q);const _=Date.now(),j="https://mp.weixin.qq.com/cgi-bin/filetransfer?action=upload_material&f=json&scene=8&writetype=doublewrite&groupid=1&ticket_id="+encodeURIComponent(Q)+"&ticket="+encodeURIComponent(X)+"&svr_time="+Z+"&token="+encodeURIComponent(K)+"&lang=zh_CN&seq="+_+"&t="+Math.random(),U=await fetch(j,{method:"POST",credentials:"include",body:Y}),O=await U.json().catch(()=>({}));if(!U.ok||O.base_resp?.err_msg!=="ok"||!O.cdn_url)throw Error("微信图片上传失败:"+B+"("+(O.base_resp?.err_msg||"HTTP "+U.status)+")");return{url:O.cdn_url}}},checkAuth:async(B)=>{const z=await(await fetch("https://mp.weixin.qq.com/",{method:"GET",credentials:"include"})).text(),D=z.match(/user_name:\s*["']([^"']+)["']/),G=z.match(/nick_name:\s*["']([^"']+)["']/);if(!D&&!G)return{isAuthenticated:!1};const H=z.match(/class="weui-desktop-account__thumb"[^>]*src="([^"]+)"/),V=z.match(/head_img:\s*['"]([^'"]+)['"]/);let K=H?H[1]:V?V[1]:"";if(K.startsWith("http://"))K=K.replace("http://","https://");return{isAuthenticated:!0,userId:D?D[1]:"",username:G?G[1]:"",avatar:K}},publish:async(B,L)=>{const D=document.documentElement.innerHTML.match(/data:\s*\{[\s\S]*?t:\s*["']([^"']+)["']/);if(!D)return{ok:!1,stage:"auth",message:"页面中未找到 token,请确认已登录 mp.weixin.qq.com。"};const G=D[1],H=B.content,V=new URLSearchParams({token:G,lang:"zh_CN",f:"json",ajax:"1",random:String(Math.random()),AppMsgId:"",count:"1",data_seq:"0",operate_from:"Chrome",isnew:"0",ad_video_transition0:"",can_reward0:"0",related_video0:"",is_video_recommend0:"-1",title0:B.title,author0:"",writerid0:"0",fileid0:"",digest0:"",auto_gen_digest0:"1",content0:H,sourceurl0:"",need_open_comment0:"1",only_fans_can_comment0:"0",cdn_url0:"",cdn_235_1_url0:"",cdn_1_1_url0:"",cdn_url_back0:"",crop_list0:"",music_id0:"",video_id0:"",voteid0:"",voteismlt0:"",supervoteid0:"",cardid0:"",cardquantity0:"",cardlimit0:"",vid_type0:"",show_cover_pic0:"0",shortvideofileid0:"",copyright_type0:"0",releasefirst0:"",platform0:"",reprint_permit_type0:"",allow_reprint0:"",allow_reprint_modify0:"",original_article_type0:"",ori_white_list0:"",free_content0:"",fee0:"0",ad_id0:"",guide_words0:"",is_share_copyright0:"0",share_copyright_url0:"",source_article_type0:"",reprint_recommend_title0:"",reprint_recommend_content0:"",share_page_type0:"0",share_imageinfo0:'{"list":[]}',share_video_id0:"",dot0:"{}",share_voice_id0:"",insert_ad_mode0:"",categories_list0:"[]"}),K=await fetch("https://mp.weixin.qq.com/cgi-bin/operate_appmsg?t=ajax-response&sub=create&type=77&token="+encodeURIComponent(G)+"&lang=zh_CN",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:V});var X=await K.text(),Q=null;try{Q=JSON.parse(X)}catch(q){}if(!K.ok||!Q||!Q.appMsgId){var Z=Q&&(Q.ret!=null?Q.ret:Q.base_resp&&Q.base_resp.ret),$={"-6":"请输入验证码","-8":"请输入验证码","-1":"系统错误,请注意备份内容后重试","-2":"参数错误,请注意备份内容后重试","-99":"内容超出字数,请调整","200003":"登录态超时,请重新登录","64507":"内容不能包含外部链接","64702":"标题超出 64 字长度限制","64705":"内容超出字数,请调整"},A=Z!=null&&$[String(Z)]||"发布失败(错误码:"+Z+"):"+X.slice(0,200);return{ok:!1,stage:"publish",status:K.status,message:A}}const F="https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit&action=edit&type=77&appmsgid="+Q.appMsgId+"&token="+encodeURIComponent(G)+"&lang=zh_CN";return{ok:!0,id:String(Q.appMsgId),url:F,draft:!0}}},weixinAuthProfile={home:weixinProfile.home,checkAuth:weixinProfile.checkAuth};J({site:"weixin",name:"article",access:"write",description:"通过 API 把文章发布到微信公众号草稿箱。正文默认 Markdown,外链图片自动转存到微信 CDN。",domain:"mp.weixin.qq.com",strategy:P.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题(最长 64 字)"},{name:"text",positional:!0,help:"文章正文(默认 Markdown;加 --html 则当 HTML 处理)"},{name:"file",help:"正文文件路径(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"把正文当 HTML 处理而不是 Markdown"},{name:"draft",type:"boolean",help:"仅保存草稿(微信公众号目前只支持草稿,此参数保留兼容性)"},{name:"execute",type:"boolean",help:"实际执行发布。不加此参数时命令拒绝写入。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(B,L)=>{if(!B)throw new S("微信公众号 article 命令需要浏览器会话。");R(L);const z=String(L.title??"").trim();if(!z)throw new W("INVALID_INPUT","文章标题不能为空。");const D=await C(L),G=!0,H=await T(B,{title:z,body:D,format:L.html?"html":"markdown",draftOnly:G,profile:weixinProfile}),V=H.images.uploaded.length|0,K=H.images.failed.length|0;let X="已保存微信公众号草稿";if(V||K)X+="·图片:"+V+" 张已转存"+(K?","+K+" 张失败":"");return b(X,"article","","draft",{created_target:"article:"+H.id,created_url:H.url})}});
|
package/clis/woshipm/article.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CliError as F,CommandExecutionError as U}from"@jackwener/opencli/errors";import{cli as v,Strategy as H}from"@jackwener/opencli/registry";import{publishArticle as O}from"../_shared/article/publish.js";import{checkLogin as x}from"../_shared/article/auth.js";import{readFile as S,stat as _}from"node:fs/promises";function A(K){if(!K.execute)throw new F("INVALID_INPUT","此写操作需要 --execute
|
|
1
|
+
import{CliError as F,CommandExecutionError as U}from"@jackwener/opencli/errors";import{cli as v,Strategy as H}from"@jackwener/opencli/registry";import{publishArticle as O}from"../_shared/article/publish.js";import{checkLogin as x}from"../_shared/article/auth.js";import{readFile as S,stat as _}from"node:fs/promises";function A(K){if(!K.execute)throw new F("INVALID_INPUT","此写操作需要 --execute 参数确认,未传则拒绝写入。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function N(K){const W=typeof K.text==="string"?K.text:void 0,G=typeof K.file==="string"?K.file:void 0;if(W&&G)throw new F("INVALID_INPUT","正文来源只能二选一:<text> 或 --file,不能同时指定");let V=W??"";if(G){let Y;try{Y=await _(G)}catch{throw new F("INVALID_INPUT",`文件不存在:${G}`)}if(!Y.isFile())throw new F("INVALID_INPUT",`必须是普通文本文件:${G}`);let Q;try{Q=await S(G)}catch{throw new F("INVALID_INPUT",`文件无法读取:${G}`)}try{V=new TextDecoder("utf-8",{fatal:!0}).decode(Q)}catch{throw new F("INVALID_INPUT",`文件无法解码为 UTF-8:${G}`)}}if(!V.trim())throw new F("INVALID_INPUT","正文不能为空或仅有空白字符");return V}function y(K,W,G,V,Y={}){return[{status:"success",outcome:V,message:K,target_type:W,target:G,...Y}]}export const woshipmProfile={home:"https://www.woshipm.com/writing",outputFormat:"html",preprocessConfig:{removeEmptyLines:!0},image:{skip:["woshipm.com","image.woshipm.com"],uploadFn:async(K,W)=>{const V=(document.documentElement.innerHTML||"").match(/"jltoken"\s*:\s*"([^"]+)"/),Y=V?V[1]:"";let Q="image.png";try{const X=new URL(K).pathname.split("/"),J=X[X.length-1];if(J)Q=J}catch(B){}const Z=await fetch(K,{credentials:"omit"});if(!Z.ok)throw Error("下载图片失败:"+Z.status+" "+K);const $=await Z.blob(),z=new FormData;z.append("action","wpuf_insert_image");z.append("name",Q);z.append("files",$,Q);const L={};if(Y)L.jlstar="Bearer "+Y;const q=await(await fetch("https://www.woshipm.com/tensorflow/upyun/upload",{method:"POST",credentials:"include",headers:L,body:z})).json();if(q&&q.data&&q.data.length>0&&q.data[0].url)return{url:q.data[0].url};throw Error(q.error||"图片上传失败:接口返回无 URL")}},checkAuth:async(K)=>{const G=await(await fetch("https://www.woshipm.com/writing",{method:"GET",credentials:"include"})).text(),V=G.match(/"jltoken"\s*:\s*"([^"]+)"/),Y=V?V[1]:"",Q=G.match(/var\s+userSettings\s*=\s*\{[^}]*"uid"\s*:\s*"(\d+)"/);if(!Q)return{isAuthenticated:!1};const Z=Q[1],$=await fetch("https://www.woshipm.com/api2/user/profile?uid="+Z,{method:"GET",credentials:"include",headers:{"X-Requested-With":"XMLHttpRequest"}});let z=null;try{z=await $.json()}catch(L){}if(z&&z.CODE===200&&z.RESULT&&z.RESULT.userInfoVo&&z.RESULT.userInfoVo.uid){const L=z.RESULT.userInfoVo;return{isAuthenticated:!0,userId:String(L.uid),username:L.nickName||"",avatar:L.avartar||""}}return{isAuthenticated:!1,error:"接口返回未登录(CODE: "+(z&&z.CODE)+")"}},publish:async(K,W)=>{const G=K.content,V="https://www.woshipm.com/wp-admin/admin-ajax.php",Y={"Content-Type":"application/x-www-form-urlencoded","X-Requested-With":"XMLHttpRequest"};if(K.draftOnly){const P=await fetch(V,{method:"POST",credentials:"include",headers:Y,body:new URLSearchParams({action:"add_draft",post_title:K.title,post_content:G})}),q=await P.text();let B=null;try{B=JSON.parse(q)}catch(T){}if(!P.ok||!B||!B.post_id)return{ok:!1,stage:"create",status:P.status,message:B&&B.error||q.slice(0,300)};const X=String(B.post_id),J=B.url||"https://www.woshipm.com/writing?pid="+X;return{ok:!0,draft:!0,id:X,url:J}}const Q=new URLSearchParams({action:"add_pending",post_title:K.title,post_content:G,copyright:"1",copyright_pm:"1",copyright_other:"1"}),Z=await fetch(V,{method:"POST",credentials:"include",headers:Y,body:Q}),$=await Z.text();let z=null;try{z=JSON.parse($)}catch(P){}if(!Z.ok||!z||String(z.success)!=="1")return{ok:!1,stage:"publish",status:Z.status,message:z&&z.message||$.slice(0,300)};return{ok:!0,draft:!1,pending:!0,id:z&&(z.post_id||z.id)?String(z.post_id||z.id):"",url:"https://www.woshipm.com/me/posts/"}}};v({site:"woshipm",name:"article",access:"write",description:"发布文章到人人都是产品经理(woshipm.com)。默认提交审核(正式发布),加 --draft 仅存草稿。正文默认 Markdown,图片自动转存到站内图床。注意:平台为审核制,提交后稿件进入待审核(pending),运营 24h 内人工审核通过才公开。",domain:"www.woshipm.com",strategy:H.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题(非空,长度<=200)"},{name:"text",positional:!0,help:"文章正文(默认 Markdown;传 --html 则视为 HTML)"},{name:"file",help:"正文文件路径(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"把正文当 HTML 而非 Markdown 处理"},{name:"draft",type:"boolean",help:"仅保存草稿,不提交审核"},{name:"execute",type:"boolean",help:"确认执行写操作。未传则拒绝写入。提交审核不可轻易撤回,请确认后再加此参数。"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(K,W)=>{if(!K)throw new U("woshipm article 需要浏览器会话");A(W);const G=String(W.title??"").trim();if(!G)throw new F("INVALID_INPUT","文章标题不能为空");if(G.length>200)throw new F("INVALID_INPUT","文章标题过长(最多 200 字),当前 "+G.length+" 字");const V=await N(W),Y=Boolean(W.draft),Q=await O(K,{title:G,body:V,format:W.html?"html":"markdown",draftOnly:Y,profile:woshipmProfile}),Z=Q.images.uploaded.length|0,$=Q.images.failed.length|0;let z=Q.draft?"已保存到草稿箱":"已提交审核(稿件进入待审核 pending,运营 24h 内人工审核通过后才公开)";if(Z||$)z+=`(图片:${Z} 张已转存${$?`,${$} 张失败`:""})`;return y(z,"article","",Q.draft?"draft":"pending",{created_target:(Q.draft?"draft:":"article:")+(Q.id||""),created_url:Q.url})}});
|
package/clis/xueqiu/article.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import{CliError as Y,CommandExecutionError as E}from"@jackwener/opencli/errors";import{cli as b,Strategy as m}from"@jackwener/opencli/registry";import{readFile as R,stat as k}from"node:fs/promises";import{publishArticle as v}from"../_shared/article/publish.js";export const xueqiuProfile={home:"https://mp.xueqiu.com/writeV2",outputFormat:"markdown",image:{spec:{url:"https://mp.xueqiu.com/xq/photo/upload.json",method:"POST",bodyType:"binary-multipart",fileField:"file",fileName:"image.jpg",responsePath:"url"},skip:["xueqiu.com","imedao.com"],uploadFn:async(J,D)=>{const z=await fetch(J,{credentials:"omit"});if(!z.ok)throw Error("图片下载失败: "+J);const U=await z.blob(),K=new FormData;K.append("file",U,"image.jpg");const H=await fetch("https://mp.xueqiu.com/xq/photo/upload.json",{method:"POST",credentials:"include",body:K}),B=await H.text();let Q=null;try{Q=JSON.parse(B)}catch(Z){}if(!H.ok||!Q||!Q.url||!Q.filename)throw Error("图片上传失败: "+B.slice(0,200));return{url:(Q.url.startsWith("//")?"https:"+Q.url:Q.url)+"/"+Q.filename}}},checkAuth:async(J)=>{try{const U=(await(await fetch("https://mp.xueqiu.com/writeV2",{method:"GET",credentials:"include"})).text()).match(/window\.UOM_CURRENTUSER\s*=\s*(\{[\s\S]*?\})\s*<\/script>/);if(!U)return{isAuthenticated:!1};let K=null;try{K=JSON.parse(U[1])}catch(Q){}const H=K&&K.currentUser;if(!H||!H.id)return{isAuthenticated:!1};const B=H.photo_domain&&H.profile_image_url?"https:"+H.photo_domain+(H.profile_image_url.split(",")[0]||""):"";return{isAuthenticated:!0,userId:String(H.id),username:H.screen_name||"",avatar:B}}catch(D){return{isAuthenticated:!1,error:String(D&&D.message||D)}}},publish:async(J,D)=>{const z=J.params||{};let U=J.content;const K=U.split(`
|
|
2
2
|
`),H=[];let B=0;while(B<K.length){const G=K[B];if(G.startsWith("```")){let L="";B++;while(B<K.length&&!K[B].startsWith("```")){L+=(L?`
|
|
3
3
|
`:"")+K[B];B++}H.push("<pre><code>"+L.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")+"</code></pre>");B++;continue}const V=G.match(/^#{1,6}\s+(.*)/);if(V){H.push("<h4>"+V[1]+"</h4>");B++;continue}const X=G.match(/^!\[([^\]]*)\]\(([^)]+)\)/);if(X){H.push('<img src="'+X[2]+'" alt="'+X[1]+'" class="ke_img">');B++;continue}if(/^[-*_]{3,}\s*$/.test(G)){B++;continue}if(!G.trim()){H.push("");B++;continue}let f=G.replace(/\*\*([^*]+)\*\*/g,"<b>$1</b>").replace(/\*([^*]+)\*/g,"<i>$1</i>").replace(/`([^`]+)`/g,"<code>$1</code>");H.push("<p>"+f+"</p>");B++}U=H.filter((G,V,X)=>{return!(G===""&&V>0&&X[V-1]==="")}).join(`
|
|
4
|
-
`).trim();const Q=typeof z.coverPic==="string"?z.coverPic.trim():"";if(Q&&!/(imedao\.com|xueqiu\.com)/.test(Q))return{ok:!1,stage:"cover",message:"封面图必须是雪球图床地址(含 imedao.com / xueqiu.com);外链需先上传。当前:"+Q.slice(0,120)};const $=new URLSearchParams({text:U,title:J.title,cover_pic:Q,flags:"false",original_event:"",status_id:"",legal_user_visible:"false",is_private:"false"}),Z=await fetch("https://mp.xueqiu.com/xq/statuses/draft/save.json",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:$}),y=await Z.text();let F=null;try{F=JSON.parse(y)}catch(G){}if(!Z.ok||!F||!F.id)return{ok:!1,stage:"save",status:Z.status,message:F&&F.error_description||y.slice(0,300)};const M=String(F.id);if(J.draftOnly)return{ok:!0,id:M,url:"https://mp.xueqiu.com/write/draft/"+M,draft:!0};let _=typeof z.originalEventId==="string"?z.originalEventId.trim():"",j="",q=!0;if(_)try{const V=await(await fetch("/xq/statuses/original/original_event.json?id="+encodeURIComponent(_),{credentials:"include"})).json(),X=V&&V.originalEvent;if(!X||X.id==null)return{ok:!1,stage:"original_event",message:"原创活动事件不存在:"+_+"(用 `xueqiu events` 列举热门事件)"};if(X.disabled)return{ok:!1,stage:"original_event",message:"原创活动事件已停用,不可参与:"+_};j=String(X.tag||"")}catch(G){return{ok:!1,stage:"original_event",message:"校验原创活动事件失败:"+String(G&&G.message||G)}}else q=!1;let S="";try{const V=await(await fetch("/xq/provider/session/token.json?api_path="+encodeURIComponent("/statuses/update.json"),{credentials:"include"})).json();S=V&&V.session_token?String(V.session_token):""}catch(G){return{ok:!1,stage:"csrf",message:"获取发布 CSRF token 失败:"+String(G&&G.message||G)}}if(!S)return{ok:!1,stage:"csrf",message:"未取得发布 CSRF token(session_token 为空)"};const T=z.showCoverPic===!1?"false":"true",h=z.original===!0?"true":"false",x=z.allowReward===!0?"true":"false",P=new URLSearchParams({title:J.title,status:U,draft_id:M,session_token:S,cover_pic:Q,show_cover_pic:T,original:h,industry_category_name:"",original_event_id:q?_:"",original_event_tag:q?j:"",original_event_active:q?"true":"false",legal_user_visible:"false",is_private:"false",legal_user_state:"open",allow_reward:x}),A=await fetch("https://mp.xueqiu.com/xq/statuses/update.json",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:P}),C=await A.text();let W=null;try{W=JSON.parse(C)}catch(G){}if(!A.ok||!W||!W.id)return{ok:!1,stage:"publish",status:A.status,message:W&&(W.error_description||W.error_code)||C.slice(0,300)};const N=String(W.id);let O="";if(W.user_id!=null)O=String(W.user_id);else if(W.user&&W.user.id!=null)O=String(W.user.id);if(!O)try{const G=(document.documentElement.outerHTML||"").match(/UOM_CURRENTUSER\s*=\s*(\{[\s\S]*?\})\s*<\/script>/);if(G){const V=JSON.parse(G[1]).currentUser;if(V&&V.id!=null)O=String(V.id)}}catch(G){}const I=O?"https://xueqiu.com/"+O+"/"+N:"https://xueqiu.com/"+N;return{ok:!0,id:N,url:I,draft:!1}}};function w(J){if(!J.execute)throw new Y("INVALID_INPUT","此命令需要 --execute
|
|
4
|
+
`).trim();const Q=typeof z.coverPic==="string"?z.coverPic.trim():"";if(Q&&!/(imedao\.com|xueqiu\.com)/.test(Q))return{ok:!1,stage:"cover",message:"封面图必须是雪球图床地址(含 imedao.com / xueqiu.com);外链需先上传。当前:"+Q.slice(0,120)};const $=new URLSearchParams({text:U,title:J.title,cover_pic:Q,flags:"false",original_event:"",status_id:"",legal_user_visible:"false",is_private:"false"}),Z=await fetch("https://mp.xueqiu.com/xq/statuses/draft/save.json",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:$}),y=await Z.text();let F=null;try{F=JSON.parse(y)}catch(G){}if(!Z.ok||!F||!F.id)return{ok:!1,stage:"save",status:Z.status,message:F&&F.error_description||y.slice(0,300)};const M=String(F.id);if(J.draftOnly)return{ok:!0,id:M,url:"https://mp.xueqiu.com/write/draft/"+M,draft:!0};let _=typeof z.originalEventId==="string"?z.originalEventId.trim():"",j="",q=!0;if(_)try{const V=await(await fetch("/xq/statuses/original/original_event.json?id="+encodeURIComponent(_),{credentials:"include"})).json(),X=V&&V.originalEvent;if(!X||X.id==null)return{ok:!1,stage:"original_event",message:"原创活动事件不存在:"+_+"(用 `xueqiu events` 列举热门事件)"};if(X.disabled)return{ok:!1,stage:"original_event",message:"原创活动事件已停用,不可参与:"+_};j=String(X.tag||"")}catch(G){return{ok:!1,stage:"original_event",message:"校验原创活动事件失败:"+String(G&&G.message||G)}}else q=!1;let S="";try{const V=await(await fetch("/xq/provider/session/token.json?api_path="+encodeURIComponent("/statuses/update.json"),{credentials:"include"})).json();S=V&&V.session_token?String(V.session_token):""}catch(G){return{ok:!1,stage:"csrf",message:"获取发布 CSRF token 失败:"+String(G&&G.message||G)}}if(!S)return{ok:!1,stage:"csrf",message:"未取得发布 CSRF token(session_token 为空)"};const T=z.showCoverPic===!1?"false":"true",h=z.original===!0?"true":"false",x=z.allowReward===!0?"true":"false",P=new URLSearchParams({title:J.title,status:U,draft_id:M,session_token:S,cover_pic:Q,show_cover_pic:T,original:h,industry_category_name:"",original_event_id:q?_:"",original_event_tag:q?j:"",original_event_active:q?"true":"false",legal_user_visible:"false",is_private:"false",legal_user_state:"open",allow_reward:x}),A=await fetch("https://mp.xueqiu.com/xq/statuses/update.json",{method:"POST",credentials:"include",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:P}),C=await A.text();let W=null;try{W=JSON.parse(C)}catch(G){}if(!A.ok||!W||!W.id)return{ok:!1,stage:"publish",status:A.status,message:W&&(W.error_description||W.error_code)||C.slice(0,300)};const N=String(W.id);let O="";if(W.user_id!=null)O=String(W.user_id);else if(W.user&&W.user.id!=null)O=String(W.user.id);if(!O)try{const G=(document.documentElement.outerHTML||"").match(/UOM_CURRENTUSER\s*=\s*(\{[\s\S]*?\})\s*<\/script>/);if(G){const V=JSON.parse(G[1]).currentUser;if(V&&V.id!=null)O=String(V.id)}}catch(G){}const I=O?"https://xueqiu.com/"+O+"/"+N:"https://xueqiu.com/"+N;return{ok:!0,id:N,url:I,draft:!1}}};function w(J){if(!J.execute)throw new Y("INVALID_INPUT","此命令需要 --execute 参数才能实际写入,防止误操作。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}async function c(J){const D=typeof J.text==="string"?J.text:void 0,z=typeof J.file==="string"?J.file:void 0;if(D&&z)throw new Y("INVALID_INPUT","不能同时使用 <text> 和 --file,请选一种");let U=D??"";if(z){let K;try{K=await k(z)}catch{throw new Y("INVALID_INPUT","文件不存在: "+z)}if(!K.isFile())throw new Y("INVALID_INPUT","路径必须是可读文本文件: "+z);let H;try{H=await R(z)}catch{throw new Y("INVALID_INPUT","文件读取失败: "+z)}try{U=new TextDecoder("utf-8",{fatal:!0}).decode(H)}catch{throw new Y("INVALID_INPUT","文件不是合法 UTF-8 文本: "+z)}}if(!U.trim())throw new Y("INVALID_INPUT","正文不能为空");return U}function d(J,D,z,U,K={}){return[{status:"success",outcome:U,message:J,target_type:D,target:z,...K}]}b({site:"xueqiu",name:"article",access:"write",description:"发布雪球文章(长文)。默认正式发布,加 --draft 仅存草稿。正文默认 Markdown,图片自动转存到雪球图床。",domain:"mp.xueqiu.com",strategy:m.COOKIE,browser:!0,args:[{name:"title",positional:!0,required:!0,help:"文章标题"},{name:"text",positional:!0,help:"正文(默认 Markdown;传 --html 则视为原始 HTML)"},{name:"file",help:"正文文件路径(UTF-8,默认 Markdown)"},{name:"html",type:"boolean",help:"把正文视为原始 HTML 而非 Markdown"},{name:"cover-image",help:"封面图地址(必须是雪球图床 imedao.com/xueqiu.com 的 url;外链请先上传)。可空=无封面"},{name:"no-cover",type:"boolean",help:"有封面图时不在文章头部展示封面(show_cover_pic=false)"},{name:"original",type:"boolean",help:"声明原创(需账号有原创权限,否则发布会被拒)。默认非原创"},{name:"original-event-id",help:"参与的原创活动/事件 id(精确校验,找不到/已停用报错);合法值用 `xueqiu events` 列举。可空=不参与"},{name:"allow-reward",type:"boolean",help:"允许打赏。默认不允许"},{name:"draft",type:"boolean",help:"仅保存为草稿,不正式发布"},{name:"execute",type:"boolean",help:"实际执行写入;不加此参数命令拒绝写操作"}],columns:["status","outcome","message","target_type","target","created_target","created_url"],func:async(J,D)=>{if(!J)throw new E("雪球文章发布需要浏览器会话(browser session)");w(D);const z=String(D.title??"").trim();if(!z)throw new Y("INVALID_INPUT","文章标题不能为空");const U=await c(D),K=Boolean(D.draft),H={coverPic:typeof D["cover-image"]==="string"?D["cover-image"].trim():"",showCoverPic:!D["no-cover"],original:Boolean(D.original),originalEventId:typeof D["original-event-id"]==="string"?D["original-event-id"].trim():"",allowReward:Boolean(D["allow-reward"])},B=await v(J,{title:z,body:U,format:D.html?"html":"markdown",draftOnly:K,profile:xueqiuProfile,publishParams:H}),Q=B.images.uploaded.length|0,$=B.images.failed.length|0;let Z=B.draft?"已保存雪球文章草稿(需在写作页手动发布)":"已正式发布雪球文章";if(Q||$)Z+=`;图片: ${Q} 张已转存${$?`,${$} 张失败`:""}`;return d(Z,"article","",B.draft?"draft":"created",{created_target:"article:"+B.id,created_url:B.url})}});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{readFile as h,stat as A}from"node:fs/promises";import{CliError as Q}from"@jackwener/opencli/errors";const T=new Set(["status","outcome","message","target_type","target"]),$='header, nav, [role="banner"], [role="navigation"]',J='a[href^="/people/"]',W='img, [class*="Avatar"], [data-testid*="avatar" i], [aria-label*="头像"]',Y=["我","我的","个人主页"],z=[["self"],["current","user"],["account","profile"],["my","profile"],["my","account"]],C=JSON.stringify(z);function F(){return{readFile:h,stat:(j)=>A(j),decodeUtf8:(j)=>new TextDecoder("utf-8",{fatal:!0}).decode(j)}}function f(j){const q=j.toLowerCase();return Y.some((G)=>j.includes(G))||q.includes("my profile")||q.includes("my account")}function I(j){return j.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)}function _(j){const q=new Set(I(j));return z.some((G)=>G.every((B)=>q.has(B)))}function O(j){return typeof j==="object"&&j!==null&&"querySelectorAll"in j&&typeof j.querySelectorAll==="function"}function c(j){return typeof j==="object"&&j!==null&&"getAttribute"in j&&"querySelector"in j&&typeof j.getAttribute==="function"&&typeof j.querySelector==="function"}function L(j){const q=j?.topstory?.me?.slug||j?.me?.slug||j?.initialState?.me?.slug;return typeof q==="string"&&q?q:null}function x(j,q){const B=(j.getAttribute("href")||"").match(/^\/people\/([A-Za-z0-9_-]+)/);if(!B)return null;const H=j.getAttribute("aria-label")||"",X=j.getAttribute("title")||"",Z=j.getAttribute("data-testid")||"",M=j.getAttribute("class")||"",K=j.getAttribute("rel")||"",U=`${H} ${X} ${j.textContent||""}`,V=`${Z} ${M} ${K}`,b=Boolean(j.querySelector(W)),P=f(U),N=_(V);if(P||N)return B[1];if(q&&b)return B[1];return null}function D(j,q){for(const G of j)for(const B of Array.from(G.querySelectorAll(J)).filter(c)){const H=x(B,q);if(H)return H}return null}export function resolveCurrentUserSlugFromDom(j,q){const G=L(j);if(G)return G;const B=Array.from(q.querySelectorAll($)).filter(O);return D(B,!0)||D([q],!1)}export function requireExecute(j){if(!j.execute)throw new Q("INVALID_INPUT","
|
|
1
|
+
import{readFile as h,stat as A}from"node:fs/promises";import{CliError as Q}from"@jackwener/opencli/errors";const T=new Set(["status","outcome","message","target_type","target"]),$='header, nav, [role="banner"], [role="navigation"]',J='a[href^="/people/"]',W='img, [class*="Avatar"], [data-testid*="avatar" i], [aria-label*="头像"]',Y=["我","我的","个人主页"],z=[["self"],["current","user"],["account","profile"],["my","profile"],["my","account"]],C=JSON.stringify(z);function F(){return{readFile:h,stat:(j)=>A(j),decodeUtf8:(j)=>new TextDecoder("utf-8",{fatal:!0}).decode(j)}}function f(j){const q=j.toLowerCase();return Y.some((G)=>j.includes(G))||q.includes("my profile")||q.includes("my account")}function I(j){return j.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)}function _(j){const q=new Set(I(j));return z.some((G)=>G.every((B)=>q.has(B)))}function O(j){return typeof j==="object"&&j!==null&&"querySelectorAll"in j&&typeof j.querySelectorAll==="function"}function c(j){return typeof j==="object"&&j!==null&&"getAttribute"in j&&"querySelector"in j&&typeof j.getAttribute==="function"&&typeof j.querySelector==="function"}function L(j){const q=j?.topstory?.me?.slug||j?.me?.slug||j?.initialState?.me?.slug;return typeof q==="string"&&q?q:null}function x(j,q){const B=(j.getAttribute("href")||"").match(/^\/people\/([A-Za-z0-9_-]+)/);if(!B)return null;const H=j.getAttribute("aria-label")||"",X=j.getAttribute("title")||"",Z=j.getAttribute("data-testid")||"",M=j.getAttribute("class")||"",K=j.getAttribute("rel")||"",U=`${H} ${X} ${j.textContent||""}`,V=`${Z} ${M} ${K}`,b=Boolean(j.querySelector(W)),P=f(U),N=_(V);if(P||N)return B[1];if(q&&b)return B[1];return null}function D(j,q){for(const G of j)for(const B of Array.from(G.querySelectorAll(J)).filter(c)){const H=x(B,q);if(H)return H}return null}export function resolveCurrentUserSlugFromDom(j,q){const G=L(j);if(G)return G;const B=Array.from(q.querySelectorAll($)).filter(O);return D(B,!0)||D([q],!1)}export function requireExecute(j){if(!j.execute)throw new Q("INVALID_INPUT","此知乎写命令需要 --execute 才会真正执行。注意:本报错仅表示缺少 --execute,并未校验其余参数,不能当 dry-run 预览用")}export async function resolvePayload(j,q=F()){const G=typeof j.text==="string"?j.text:void 0,B=typeof j.file==="string"?j.file:void 0;if(G&&B)throw new Q("INVALID_INPUT","Use either <text> or --file, not both");let H=G??"";if(B){let X;try{X=await q.stat(B)}catch{throw new Q("INVALID_INPUT",`File not found: ${B}`)}if(!X.isFile())throw new Q("INVALID_INPUT",`File must be a readable text file: ${B}`);let Z;try{Z=await q.readFile(B)}catch{throw new Q("INVALID_INPUT",`File could not be read: ${B}`)}try{H=q.decodeUtf8(Z)}catch{throw new Q("INVALID_INPUT",`File could not be decoded as UTF-8 text: ${B}`)}}if(!H.trim())throw new Q("INVALID_INPUT","Payload cannot be empty or whitespace only");return H}function R(){return`(() => {
|
|
2
2
|
const selfLabelTokens = ${JSON.stringify(Y)};
|
|
3
3
|
const explicitIdentityMetaTokenGroups = ${C};
|
|
4
4
|
const navScopeSelector = ${JSON.stringify($)};
|