publishport-opencli 1.8.5-pp.27 → 1.8.5-pp.28

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 CHANGED
@@ -11126,6 +11126,43 @@
11126
11126
  "modulePath": "devto/read.js",
11127
11127
  "sourceFile": "devto/read.js"
11128
11128
  },
11129
+ {
11130
+ "site": "devto",
11131
+ "name": "stats",
11132
+ "description": "dev.to 文章数据(每篇浏览/反应/评论等运营指标,含草稿)。首次调用要拉起浏览器会话、偏慢,客户端超时属正常,重试一次即可",
11133
+ "access": "read",
11134
+ "domain": "dev.to",
11135
+ "strategy": "cookie",
11136
+ "browser": true,
11137
+ "args": [
11138
+ {
11139
+ "name": "limit",
11140
+ "type": "number",
11141
+ "default": 20,
11142
+ "required": false,
11143
+ "help": "返回条数"
11144
+ }
11145
+ ],
11146
+ "columns": [
11147
+ "id",
11148
+ "type",
11149
+ "title",
11150
+ "url",
11151
+ "published_at",
11152
+ "views",
11153
+ "likes",
11154
+ "comments",
11155
+ "collects",
11156
+ "shares",
11157
+ "tags",
11158
+ "read_time",
11159
+ "extra"
11160
+ ],
11161
+ "type": "js",
11162
+ "modulePath": "devto/stats.js",
11163
+ "sourceFile": "devto/stats.js",
11164
+ "navigateBefore": false
11165
+ },
11129
11166
  {
11130
11167
  "site": "devto",
11131
11168
  "name": "tag",
@@ -25578,6 +25615,46 @@
25578
25615
  "sourceFile": "medium/search.js",
25579
25616
  "navigateBefore": "https://medium.com"
25580
25617
  },
25618
+ {
25619
+ "site": "medium",
25620
+ "name": "stats",
25621
+ "description": "Medium 文章数据(每篇曝光/浏览/读完/读完率/收益等运营指标)。首次调用要拉起浏览器会话、偏慢,客户端超时属正常,重试一次即可",
25622
+ "access": "read",
25623
+ "domain": "medium.com",
25624
+ "strategy": "cookie",
25625
+ "browser": true,
25626
+ "args": [
25627
+ {
25628
+ "name": "limit",
25629
+ "type": "number",
25630
+ "default": 20,
25631
+ "required": false,
25632
+ "help": "返回条数"
25633
+ }
25634
+ ],
25635
+ "columns": [
25636
+ "id",
25637
+ "type",
25638
+ "title",
25639
+ "url",
25640
+ "published_at",
25641
+ "views",
25642
+ "likes",
25643
+ "comments",
25644
+ "collects",
25645
+ "shares",
25646
+ "displays",
25647
+ "reads",
25648
+ "read_ratio",
25649
+ "earnings",
25650
+ "read_time",
25651
+ "extra"
25652
+ ],
25653
+ "type": "js",
25654
+ "modulePath": "medium/stats.js",
25655
+ "sourceFile": "medium/stats.js",
25656
+ "navigateBefore": false
25657
+ },
25581
25658
  {
25582
25659
  "site": "medium",
25583
25660
  "name": "tag",
@@ -30490,6 +30567,42 @@
30490
30567
  "sourceFile": "qiita/publish.js",
30491
30568
  "navigateBefore": "https://qiita.com"
30492
30569
  },
30570
+ {
30571
+ "site": "qiita",
30572
+ "name": "stats",
30573
+ "description": "Qiita 文章数据(每篇浏览/点赞/收藏/评论等运营指标)。逐篇补浏览数、篇数多时偏慢,客户端超时属正常,重试一次即可",
30574
+ "access": "read",
30575
+ "domain": "qiita.com",
30576
+ "strategy": "cookie",
30577
+ "browser": true,
30578
+ "args": [
30579
+ {
30580
+ "name": "limit",
30581
+ "type": "number",
30582
+ "default": 20,
30583
+ "required": false,
30584
+ "help": "返回条数"
30585
+ }
30586
+ ],
30587
+ "columns": [
30588
+ "id",
30589
+ "type",
30590
+ "title",
30591
+ "url",
30592
+ "published_at",
30593
+ "views",
30594
+ "likes",
30595
+ "comments",
30596
+ "collects",
30597
+ "shares",
30598
+ "tags",
30599
+ "extra"
30600
+ ],
30601
+ "type": "js",
30602
+ "modulePath": "qiita/stats.js",
30603
+ "sourceFile": "qiita/stats.js",
30604
+ "navigateBefore": false
30605
+ },
30493
30606
  {
30494
30607
  "site": "qiita",
30495
30608
  "name": "whoami",
@@ -0,0 +1,27 @@
1
+ import{cli as C,Strategy as F}from"@jackwener/opencli/registry";import{EmptyResultError as G}from"@jackwener/opencli/errors";const I="https://dev.to/dashboard";C({site:"devto",name:"stats",access:"read",description:"dev.to 文章数据(每篇浏览/反应/评论等运营指标,含草稿)。首次调用要拉起浏览器会话、偏慢,客户端超时属正常,重试一次即可",domain:"dev.to",strategy:F.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"limit",type:"number",default:20,help:"返回条数"}],columns:["id","type","title","url","published_at","views","likes","comments","collects","shares","tags","read_time","extra"],func:async(h,x)=>{const z=Math.max(1,Number(x.limit)||20);await h.goto(I);const f=await h.evaluate(`(async () => {
2
+ try {
3
+ if (!location.href.includes('dev.to')) {
4
+ return { error: '未登录 dev.to(页面被重定向到 ' + location.href + '),请先在 Chrome 里登录 dev.to' };
5
+ }
6
+ const limit = ${JSON.stringify(z)};
7
+ const items = [];
8
+ for (let pageNo = 1; items.length < limit && pageNo <= 50; pageNo++) {
9
+ const per = Math.min(100, limit - items.length + 1);
10
+ const resp = await fetch('/api/articles/me/all?per_page=' + per + '&page=' + pageNo, {
11
+ credentials: 'include',
12
+ headers: { accept: 'application/json' },
13
+ });
14
+ if (resp.status === 401) return { error: '未登录 dev.to:请先在 Chrome 里登录 dev.to 再运行 devto stats' };
15
+ if (!resp.ok) return { error: 'dev.to 接口 HTTP ' + resp.status + '(/api/articles/me/all 第 ' + pageNo + ' 页)' };
16
+ const batch = await resp.json();
17
+ if (!Array.isArray(batch) || batch.length === 0) break;
18
+ items.push(...batch);
19
+ if (batch.length < per) break;
20
+ }
21
+ // body_markdown 是全文、体积巨大,出页面前先剔掉
22
+ for (const it of items) { delete it.body_markdown; delete it.user; }
23
+ return { items: items.slice(0, limit) };
24
+ } catch (e) {
25
+ return { error: String((e && e.message) || e) };
26
+ }
27
+ })()`);if(!f||f.error)throw Error(f?.error||"devto stats:evaluate 无返回(浏览器会话异常)");const q=Array.isArray(f.items)?f.items:[];if(q.length===0)throw new G("devto stats","该 dev.to 账号没有任何文章(含草稿)。");return q.map((b)=>({id:String(b.id??""),type:"article",title:b.title||"",url:b.url||"",published_at:b.published_at||"",views:b.page_views_count??0,likes:b.public_reactions_count??0,comments:b.comments_count??0,collects:"",shares:"",tags:Array.isArray(b.tag_list)?b.tag_list.join("|"):String(b.tag_list||""),read_time:b.reading_time_minutes?`${b.reading_time_minutes}min`:"",extra:JSON.stringify({published:b.published??null,positive_reactions:b.positive_reactions_count??null,description:b.description||"",cover_image:b.cover_image||"",canonical_url:b.canonical_url||""})}))}});
@@ -0,0 +1,56 @@
1
+ import{cli as I,Strategy as J}from"@jackwener/opencli/registry";import{EmptyResultError as K}from"@jackwener/opencli/errors";const M="https://medium.com/me/stats?publishedAt=DESC";I({site:"medium",name:"stats",access:"read",description:"Medium 文章数据(每篇曝光/浏览/读完/读完率/收益等运营指标)。首次调用要拉起浏览器会话、偏慢,客户端超时属正常,重试一次即可",domain:"medium.com",strategy:J.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"limit",type:"number",default:20,help:"返回条数"}],columns:["id","type","title","url","published_at","views","likes","comments","collects","shares","displays","reads","read_ratio","earnings","read_time","extra"],func:async(D,G)=>{const H=Math.max(1,Number(G.limit)||20);await D.goto(M);const q=await D.evaluate(`(async () => {
2
+ try {
3
+ if (!location.href.includes('medium.com')) {
4
+ return { error: '未登录 Medium(页面被重定向到 ' + location.href + '),请先在 Chrome 里登录 medium.com' };
5
+ }
6
+ if (location.pathname.includes('/m/signin') || location.pathname === '/') {
7
+ return { error: '未登录 Medium:请先在 Chrome 里登录 medium.com 再运行 medium stats' };
8
+ }
9
+ const limit = ${JSON.stringify(H)};
10
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
11
+ const postsInCache = () => {
12
+ const c = window.__APOLLO_CLIENT__ && window.__APOLLO_CLIENT__.cache;
13
+ if (!c) return null;
14
+ const state = c.extract();
15
+ const posts = [];
16
+ for (const k of Object.keys(state)) {
17
+ if (k.startsWith('Post:') && state[k] && state[k].totalStats) posts.push(state[k]);
18
+ }
19
+ return posts;
20
+ };
21
+ // 等首屏统计数据进缓存(Apollo 客户端 + 至少一篇带 totalStats 的 Post)
22
+ let posts = null;
23
+ for (let i = 0; i < 40; i++) {
24
+ posts = postsInCache();
25
+ if (posts && posts.length > 0) break;
26
+ await sleep(500);
27
+ }
28
+ if (!window.__APOLLO_CLIENT__) return { error: 'Medium 页面没有 Apollo 客户端(页面结构可能已改版),请重试或反馈' };
29
+ if (!posts || posts.length === 0) {
30
+ // 可能确实一篇都没有——再区分:页面文案有 "You haven\\u2019t published" 之类时按空处理
31
+ return { items: [] };
32
+ }
33
+ // 滚动加载更多(stats 列表按滚动分页),直到数量不再增长或已够 limit
34
+ let stall = 0;
35
+ while (posts.length < limit && stall < 3) {
36
+ const before = posts.length;
37
+ window.scrollTo(0, document.body.scrollHeight);
38
+ await sleep(1200);
39
+ posts = postsInCache() || posts;
40
+ stall = posts.length > before ? 0 : stall + 1;
41
+ }
42
+ return { items: posts.slice(0, limit).map((p) => ({
43
+ id: p.id,
44
+ title: p.title || '',
45
+ url: p.mediumUrl || '',
46
+ publishedAt: p.firstPublishedAt || null,
47
+ stats: p.totalStats || {},
48
+ earnings: p.earnings && p.earnings.total ? p.earnings.total : null,
49
+ readingTime: p.readingTime || null,
50
+ visibility: p.visibility || '',
51
+ isLocked: p.isLocked ?? null,
52
+ })) };
53
+ } catch (e) {
54
+ return { error: String((e && e.message) || e) };
55
+ }
56
+ })()`);if(!q||q.error)throw Error(q?.error||"medium stats:evaluate 无返回(浏览器会话异常)");const z=Array.isArray(q.items)?q.items:[];if(z.length===0)throw new K("medium stats","该 Medium 账号没有任何已发布文章。");z.sort((h,x)=>(x.publishedAt||0)-(h.publishedAt||0));return z.map((h)=>{const x=h.stats||{},B=x.views??0,F=x.reads??0,C=h.earnings?Number(h.earnings.units||0)+Number(h.earnings.nanos||0)/1e9:null;return{id:h.id||"",type:"article",title:h.title||"",url:h.url||"",published_at:h.publishedAt?new Date(h.publishedAt).toISOString():"",views:B,likes:"",comments:"",collects:"",shares:"",displays:x.presentations??0,reads:F,read_ratio:B>0?`${(F/B*100).toFixed(1)}%`:"",earnings:C!=null&&C>0?`$${C.toFixed(2)}`:"",read_time:h.readingTime?`${Math.round(h.readingTime)}min`:"",extra:JSON.stringify({visibility:h.visibility||"",is_locked:h.isLocked})}})}});
@@ -0,0 +1,37 @@
1
+ import{cli as F,Strategy as G}from"@jackwener/opencli/registry";import{EmptyResultError as I,AuthRequiredError as J}from"@jackwener/opencli/errors";import{qiitaViewer as K}from"./gql.js";const L="https://qiita.com/";F({site:"qiita",name:"stats",access:"read",description:"Qiita 文章数据(每篇浏览/点赞/收藏/评论等运营指标)。逐篇补浏览数、篇数多时偏慢,客户端超时属正常,重试一次即可",domain:"qiita.com",strategy:G.COOKIE,browser:!0,navigateBefore:!1,args:[{name:"limit",type:"number",default:20,help:"返回条数"}],columns:["id","type","title","url","published_at","views","likes","comments","collects","shares","tags","extra"],func:async(v,B)=>{const C=Math.max(1,Number(B.limit)||20);await v.goto(L);const x=(await K(v))?.urlName;if(!x)throw new J("qiita.com","未登录 Qiita:请先在 Chrome 里登录 qiita.com 再运行 qiita stats");const f=await v.evaluate(`(async () => {
2
+ try {
3
+ const limit = ${JSON.stringify(C)};
4
+ const urlName = ${JSON.stringify(x)};
5
+ const items = [];
6
+ for (let pageNo = 1; items.length < limit && pageNo <= 100; pageNo++) {
7
+ const resp = await fetch('/api/v2/users/' + encodeURIComponent(urlName) + '/items?per_page=100&page=' + pageNo, {
8
+ headers: { accept: 'application/json' },
9
+ });
10
+ if (!resp.ok) return { error: 'Qiita 接口 HTTP ' + resp.status + '(/api/v2/users/' + urlName + '/items 第 ' + pageNo + ' 页)' };
11
+ const batch = await resp.json();
12
+ if (!Array.isArray(batch) || batch.length === 0) break;
13
+ for (const it of batch) { delete it.body; delete it.rendered_body; delete it.user; }
14
+ items.push(...batch);
15
+ if (batch.length < 100) break;
16
+ }
17
+ const picked = items.slice(0, limit);
18
+ // 逐篇带 cookie 拉文章页 HTML,抠作者可见的「N views」(SSR 渲染,含千分位/k 缩写)
19
+ for (const it of picked) {
20
+ try {
21
+ const r = await fetch(it.url, { credentials: 'include' });
22
+ if (!r.ok) { it.__views = null; continue; }
23
+ const html = await r.text();
24
+ const m = html.match(/aria-hidden="true">([0-9.,]+[kKmM]?)\\s*views</);
25
+ if (!m) { it.__views = null; continue; }
26
+ let v = m[1].replace(/,/g, '');
27
+ let mul = 1;
28
+ if (/k$/i.test(v)) { mul = 1000; v = v.slice(0, -1); }
29
+ else if (/m$/i.test(v)) { mul = 1000000; v = v.slice(0, -1); }
30
+ it.__views = Math.round(parseFloat(v) * mul);
31
+ } catch (e) { it.__views = null; }
32
+ }
33
+ return { items: picked };
34
+ } catch (e) {
35
+ return { error: String((e && e.message) || e) };
36
+ }
37
+ })()`);if(!f||f.error)throw Error(f?.error||"qiita stats:evaluate 无返回(浏览器会话异常)");const z=Array.isArray(f.items)?f.items:[];if(z.length===0)throw new I("qiita stats","该 Qiita 账号没有任何公开文章。");return z.map((b)=>({id:b.id||"",type:"article",title:b.title||"",url:b.url||"",published_at:b.created_at||"",views:b.__views??"",likes:b.likes_count??0,comments:b.comments_count??0,collects:b.stocks_count??0,shares:"",tags:Array.isArray(b.tags)?b.tags.map((D)=>D.name||"").filter(Boolean).join("|"):"",extra:JSON.stringify({reactions:b.reactions_count??null,private:b.private??null,updated_at:b.updated_at||"",organization:b.organization_url_name||""})}))}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "publishport-opencli",
3
- "version": "1.8.5-pp.27",
3
+ "version": "1.8.5-pp.28",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": false