publishport-opencli 1.0.26 → 1.0.27
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 +28 -10
- package/clis/nowcoder/comments.js +1 -1
- package/clis/nowcoder/detail.js +4 -2
- package/clis/nowcoder/dm-list.js +1 -1
- package/clis/nowcoder/dm-read.js +1 -1
- package/clis/nowcoder/dm-send.js +1 -1
- package/clis/nowcoder/drafts.js +1 -1
- package/clis/nowcoder/experience.js +4 -2
- package/clis/nowcoder/inbox.js +1 -1
- package/clis/nowcoder/my.js +1 -1
- package/clis/nowcoder/recommend.js +1 -1
- package/clis/nowcoder/search.js +3 -2
- package/clis/nowcoder/utils.js +16 -19
- package/clis/nowcoder/watch.js +1 -1
- package/dist/src/pipeline/template.js +3 -1
- package/package.json +1 -1
package/cli-manifest.json
CHANGED
|
@@ -29530,7 +29530,7 @@
|
|
|
29530
29530
|
{
|
|
29531
29531
|
"site": "nowcoder",
|
|
29532
29532
|
"name": "dm-read",
|
|
29533
|
-
"description": "读某私信会话的消息(target
|
|
29533
|
+
"description": "读某私信会话的消息(target 可填 dm-list 的 conversation_id 或 target_user_id)",
|
|
29534
29534
|
"access": "read",
|
|
29535
29535
|
"domain": "www.nowcoder.com",
|
|
29536
29536
|
"strategy": "cookie",
|
|
@@ -29541,7 +29541,7 @@
|
|
|
29541
29541
|
"type": "str",
|
|
29542
29542
|
"required": true,
|
|
29543
29543
|
"positional": true,
|
|
29544
|
-
"help": "
|
|
29544
|
+
"help": "dm-list 的 conversation_id,或对方 userId"
|
|
29545
29545
|
},
|
|
29546
29546
|
{
|
|
29547
29547
|
"name": "page",
|
|
@@ -29549,11 +29549,22 @@
|
|
|
29549
29549
|
"default": 1,
|
|
29550
29550
|
"required": false,
|
|
29551
29551
|
"help": "页码"
|
|
29552
|
+
},
|
|
29553
|
+
{
|
|
29554
|
+
"name": "page-size",
|
|
29555
|
+
"type": "int",
|
|
29556
|
+
"default": 15,
|
|
29557
|
+
"required": false,
|
|
29558
|
+
"help": "每页条数"
|
|
29552
29559
|
}
|
|
29553
29560
|
],
|
|
29554
29561
|
"columns": [
|
|
29562
|
+
"message_id",
|
|
29563
|
+
"direction",
|
|
29555
29564
|
"from_user_id",
|
|
29556
|
-
"
|
|
29565
|
+
"type",
|
|
29566
|
+
"content",
|
|
29567
|
+
"created_at"
|
|
29557
29568
|
],
|
|
29558
29569
|
"type": "js",
|
|
29559
29570
|
"modulePath": "nowcoder/dm-read.js",
|
|
@@ -29563,18 +29574,18 @@
|
|
|
29563
29574
|
{
|
|
29564
29575
|
"site": "nowcoder",
|
|
29565
29576
|
"name": "dm-send",
|
|
29566
|
-
"description": "发私信(
|
|
29577
|
+
"description": "发私信(target 可填对方 userId 或 dm-list 的 conversation_id)",
|
|
29567
29578
|
"access": "write",
|
|
29568
29579
|
"domain": "www.nowcoder.com",
|
|
29569
29580
|
"strategy": "cookie",
|
|
29570
29581
|
"browser": true,
|
|
29571
29582
|
"args": [
|
|
29572
29583
|
{
|
|
29573
|
-
"name": "
|
|
29584
|
+
"name": "target",
|
|
29574
29585
|
"type": "str",
|
|
29575
29586
|
"required": true,
|
|
29576
29587
|
"positional": true,
|
|
29577
|
-
"help": "
|
|
29588
|
+
"help": "对方 userId(dm-list 的 target_user_id),也接受 conversation_id"
|
|
29578
29589
|
},
|
|
29579
29590
|
{
|
|
29580
29591
|
"name": "content",
|
|
@@ -29594,7 +29605,8 @@
|
|
|
29594
29605
|
}
|
|
29595
29606
|
],
|
|
29596
29607
|
"columns": [
|
|
29597
|
-
"
|
|
29608
|
+
"message_id",
|
|
29609
|
+
"receiver",
|
|
29598
29610
|
"status"
|
|
29599
29611
|
],
|
|
29600
29612
|
"type": "js",
|
|
@@ -29805,7 +29817,7 @@
|
|
|
29805
29817
|
{
|
|
29806
29818
|
"site": "nowcoder",
|
|
29807
29819
|
"name": "my",
|
|
29808
|
-
"description": "
|
|
29820
|
+
"description": "我发布的内容列表(动态 + 长文,含可供 delete 使用的 id)",
|
|
29809
29821
|
"access": "read",
|
|
29810
29822
|
"domain": "www.nowcoder.com",
|
|
29811
29823
|
"strategy": "cookie",
|
|
@@ -29820,9 +29832,15 @@
|
|
|
29820
29832
|
}
|
|
29821
29833
|
],
|
|
29822
29834
|
"columns": [
|
|
29823
|
-
"
|
|
29835
|
+
"content_id",
|
|
29836
|
+
"kind",
|
|
29824
29837
|
"title",
|
|
29825
|
-
"content"
|
|
29838
|
+
"content",
|
|
29839
|
+
"url",
|
|
29840
|
+
"views",
|
|
29841
|
+
"likes",
|
|
29842
|
+
"comments",
|
|
29843
|
+
"created_at"
|
|
29826
29844
|
],
|
|
29827
29845
|
"type": "js",
|
|
29828
29846
|
"modulePath": "nowcoder/my.js",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as i,Strategy as
|
|
1
|
+
import{cli as i,Strategy as d}from"@jackwener/opencli/registry";import{GW_BASE as m,decodeHtmlEntities as a,getJson as c,requireOk as s,resolveMomentId as p}from"./utils.js";i({site:"nowcoder",name:"comments",access:"read",description:"某帖的评论列表(评论前先读,避免重复/不合语境)",domain:"www.nowcoder.com",strategy:d.COOKIE,args:[{name:"id",positional:!0,required:!0,help:"帖子 id(数字 momentId / UUID / 链接均可)"},{name:"page",type:"int",default:1,help:"页码"}],columns:["comment_id","user_id","nickname","content"],func:async(t,n)=>{const o=await p(t,n.id),r=`${m}/comment/list-by-page?entityId=${encodeURIComponent(o)}&entityType=74&order=2&pageNo=${n.page}&toCommentId=0&_=${Date.now()}`;return(s(await c(t,r),"评论列表").data?.records??[]).map((e)=>({comment_id:e.id??"",user_id:e.userBrief?.userId??"",nickname:e.userBrief?.nickname??"",content:a(e.content).trim()}))}});
|
package/clis/nowcoder/detail.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import{cli as t}from"@jackwener/opencli/registry";t({site:"nowcoder",name:"detail",access:"read",description:"Post detail view (supports ID / UUID / URL)",domain:"www.nowcoder.com",args:[{name:"id",positional:!0,required:!0,help:"Post ID, UUID, or URL"}],columns:["title","author","school","content","likes","comments","views","time","location"],pipeline:[{navigate:"https://www.nowcoder.com"},{evaluate:`(async () => {
|
|
2
2
|
const raw = \${{ args.id | json }};
|
|
3
3
|
const base = 'https://gw-c.nowcoder.com';
|
|
4
|
-
|
|
4
|
+
// 牛客把正文里的半角引号按 HTML 实体存库,网页端渲染时才反转义;这里一并解回来,
|
|
5
|
+
// 否则调用方读到的是 "。& 必须放最后,不然 " 会被二次解码成引号。
|
|
6
|
+
const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/"/g, '"').replace(/�?39;/g, "'").replace(/'/g, "'").replace(/ /g, ' ').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&').trim();
|
|
5
7
|
|
|
6
8
|
let id = raw;
|
|
7
9
|
const urlMatch = raw.match(/discuss\\/(\\d+)/);
|
|
@@ -32,7 +34,7 @@ import{cli as t}from"@jackwener/opencli/registry";t({site:"nowcoder",name:"detai
|
|
|
32
34
|
const user = data.userBrief || {};
|
|
33
35
|
const freq = data.frequencyData || {};
|
|
34
36
|
return [{
|
|
35
|
-
title: data.title || '(untitled)',
|
|
37
|
+
title: strip(data.title || '') || '(untitled)',
|
|
36
38
|
author: user.nickname || '',
|
|
37
39
|
school: user.educationInfo || '',
|
|
38
40
|
content: strip(data.content || '').substring(0, 500),
|
package/clis/nowcoder/dm-list.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as o,Strategy as r}from"@jackwener/opencli/registry";import{WWW_BASE as i,
|
|
1
|
+
import{cli as o,Strategy as r}from"@jackwener/opencli/registry";import{WWW_BASE as i,decodeHtmlEntities as c,getJson as d,getSelfUid as l,requireOk as g}from"./utils.js";o({site:"nowcoder",name:"dm-list",access:"read",description:"私信会话列表(conversationId / 最后一条消息 / 未读数)",domain:"www.nowcoder.com",strategy:r.COOKIE,args:[{name:"page",type:"int",default:1,help:"页码"}],columns:["conversation_id","target_user_id","target_name","last_content","unread","allow_send","is_official"],func:async(t,a)=>{const n=await l(t),s=`${i}/sns/message/${n}/get-conversation-list?page=${a.page}`;return(g(await d(t,s),"私信会话列表").data?.conversations??[]).map((e)=>({conversation_id:e.conversationId??"",target_user_id:e.targetUserId??"",target_name:e.targetUserName??"",last_content:c(e.content).replace(/\s+/g," ").slice(0,80),unread:e.unreadMessageCount??0,allow_send:e.allowSendMessage??"",is_official:e.targetIsOfficialAccount??""}))}});
|
package/clis/nowcoder/dm-read.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as
|
|
1
|
+
import{cli as s,Strategy as d}from"@jackwener/opencli/registry";import{WWW_BASE as c,decodeHtmlEntities as g,getJson as m,getSelfUid as p,requireOk as l,resolveConversation as u}from"./utils.js";const _={0:"text",1:"image"};s({site:"nowcoder",name:"dm-read",access:"read",description:"读某私信会话的消息(target 可填 dm-list 的 conversation_id 或 target_user_id)",domain:"www.nowcoder.com",strategy:d.COOKIE,args:[{name:"target",positional:!0,required:!0,help:"dm-list 的 conversation_id,或对方 userId"},{name:"page",type:"int",default:1,help:"页码"},{name:"page-size",type:"int",default:15,help:"每页条数"}],columns:["message_id","direction","from_user_id","type","content","created_at"],func:async(n,a)=>{const t=await p(n),{conversationId:i}=await u(n,a.target,t),r=`${c}/sns/message/${t}/get-msg-list?conversationId=${encodeURIComponent(i)}&ownerId=${t}&page=${a.page}&pageSize=${a["page-size"]}&_=${Date.now()}`;return((l(await m(n,r),"私信读取").data??{}).messages??[]).map((e)=>{const o=String(e.send??"");return{message_id:e.id??"",direction:o===String(t)?"out":"in",from_user_id:o,type:_[e.contentType]??String(e.contentType??""),content:g(e.content).trim(),created_at:e.createDate?new Date(e.createDate).toISOString():""}})}});
|
package/clis/nowcoder/dm-send.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as
|
|
1
|
+
import{cli as o,Strategy as n}from"@jackwener/opencli/registry";import{ArgumentError as a}from"@jackwener/opencli/errors";import{WWW_BASE as m,getSelfUid as d,postForm as c,requireOk as l,resolveConversation as p}from"./utils.js";import{assertLiteralContent as u}from"../_shared/content-guard.js";o({site:"nowcoder",name:"dm-send",access:"write",description:"发私信(target 可填对方 userId 或 dm-list 的 conversation_id)",domain:"www.nowcoder.com",strategy:n.COOKIE,args:[{name:"target",positional:!0,required:!0,help:"对方 userId(dm-list 的 target_user_id),也接受 conversation_id"},{name:"content",fromFile:!0,positional:!0,required:!0,help:"私信内容"}],columns:["message_id","receiver","status"],func:async(e,i)=>{const t=String(i.content??"").trim();u(t,{label:"私信内容"});if(!t)throw new a("私信内容不能为空");const r=String(i.target??"").trim(),s=/^\d+$/.test(r)?r:(await p(e,r,await d(e))).peerUid;return[{message_id:l(await c(e,`${m}/sns/message/send-msg?_=${Date.now()}`,{type:"0",title:"",content:t,images:"",receiver:s},{Accept:"application/json, text/plain, */*"}),"私信发送").data?.messageId??"",receiver:s,status:"sent"}]}});
|
package/clis/nowcoder/drafts.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as
|
|
1
|
+
import{cli as c,Strategy as n}from"@jackwener/opencli/registry";import{GW_BASE as s,decodeHtmlEntities as a,getJson as d,getSelfUid as l,requireOk as p}from"./utils.js";c({site:"nowcoder",name:"drafts",access:"read",description:"我的草稿列表",domain:"www.nowcoder.com",strategy:n.COOKIE,args:[{name:"page",type:"int",default:1,help:"页码"}],columns:["draft_id","title","content"],func:async(e,r)=>{const o=await l(e),i=`${s}/discuss-draft/list?authorId=${o}&pageNo=${r.page}&pageSize=10&entityType=250`;return(p(await d(e,i),"草稿列表").data?.records??[]).map((t)=>({draft_id:t.id??"",title:a(t.title).trim(),content:a(t.content).replace(/\s+/g," ").slice(0,120)}))}});
|
|
@@ -4,12 +4,14 @@ import{cli as e}from"@jackwener/opencli/registry";e({site:"nowcoder",name:"exper
|
|
|
4
4
|
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/home/tab/content?tabId=818&categoryType=1&pageNo=' + page + '&pageSize=' + limit, {credentials: 'include'});
|
|
5
5
|
const d = await r.json();
|
|
6
6
|
if (!d.success) throw new Error(d.msg || 'API failed');
|
|
7
|
+
// 牛客正文里的引号按 HTML 实体存库,网页端渲染才反转义;& 放最后避免二次解码。
|
|
8
|
+
const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/"/g, '"').replace(/�?39;/g, "'").replace(/ /g, ' ').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&').trim();
|
|
7
9
|
return (d.data?.records || []).map((item, i) => ({
|
|
8
10
|
rank: i + 1,
|
|
9
|
-
title: item.contentData?.title || item.momentData?.title || '',
|
|
11
|
+
title: strip(item.contentData?.title || item.momentData?.title || ''),
|
|
10
12
|
author: item.userBrief?.nickname || '',
|
|
11
13
|
school: item.userBrief?.educationInfo || '',
|
|
12
|
-
content: item.momentData?.content || item.contentData?.content || item.momentData?.newContent || item.contentData?.newContent || '',
|
|
14
|
+
content: strip(item.momentData?.content || item.contentData?.content || item.momentData?.newContent || item.contentData?.newContent || ''),
|
|
13
15
|
likes: item.frequencyData?.likeCnt || 0,
|
|
14
16
|
comments: item.frequencyData?.commentCnt || 0,
|
|
15
17
|
views: item.frequencyData?.viewCnt || 0,
|
package/clis/nowcoder/inbox.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as n,Strategy as i}from"@jackwener/opencli/registry";import{GW_BASE as m,
|
|
1
|
+
import{cli as n,Strategy as i}from"@jackwener/opencli/registry";import{GW_BASE as m,decodeHtmlEntities as c,getJson as a,requireOk as s}from"./utils.js";n({site:"nowcoder",name:"inbox",access:"read",description:"评论回复收件箱(谁回复/@了我们,含定向回复所需的评论 id)",domain:"www.nowcoder.com",strategy:i.COOKIE,args:[{name:"page",type:"int",default:1,help:"页码"}],columns:["msg_id","from_user_id","from_nickname","reply_content","reply_comment_id","quoted_ours","url","time"],func:async(t,r)=>{const o=`${m}/message/pc/comment/list?page=${r.page}&pageSize=20`;return(s(await a(t,o),"评论收件箱").data?.records??[]).map((e)=>({msg_id:e.id??"",from_user_id:e.user?.userId??"",from_nickname:e.user?.nickname??"",reply_content:c(e.triggerEntity?.content).trim(),reply_comment_id:e.triggerEntity?.entityId??"",quoted_ours:(e.quote?.content??"").trim(),url:e.pcUrl??"",time:e.createTime??""}))}});
|
package/clis/nowcoder/my.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as
|
|
1
|
+
import{cli as d,Strategy as m}from"@jackwener/opencli/registry";import{GW_BASE as l,decodeHtmlEntities as a,getJson as w,getSelfUid as u,requireOk as p}from"./utils.js";d({site:"nowcoder",name:"my",access:"read",description:"我发布的内容列表(动态 + 长文,含可供 delete 使用的 id)",domain:"www.nowcoder.com",strategy:m.COOKIE,args:[{name:"page",type:"int",default:1,help:"页码"}],columns:["content_id","kind","title","content","url","views","likes","comments","created_at"],func:async(c,i)=>{const r=await u(c),s=`${l}/user/content/my-content?pageNo=${i.page}&userId=${r}&_=${Date.now()}`;return(p(await w(c,s),"我的内容").data?.records??[]).map((n)=>{const t=n.contentData??{},e=n.frequencyData??{},o=String(t.id??n.contentId??"");return{content_id:o,kind:t.typeName??"",title:a(t.title).trim(),content:a(t.content).replace(/\s+/g," ").slice(0,120),url:o?`https://www.nowcoder.com/discuss/${o}`:"",views:e.viewCnt??0,likes:e.likeCnt??0,comments:e.totalCommentCnt??e.commentCnt??0,created_at:t.createTime?new Date(t.createTime).toISOString():""}})}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cli as
|
|
1
|
+
import{cli as t,Strategy as e}from"@jackwener/opencli/registry";t({site:"nowcoder",name:"recommend",access:"read",description:"Recommended feed",domain:"www.nowcoder.com",strategy:e.PUBLIC,browser:!1,args:[{name:"page",type:"int",default:1,help:"Page number"},{name:"limit",type:"int",default:15,help:"Number of items"}],columns:["rank","title","author","likes","comments","views","id"],pipeline:[{fetch:{url:"https://gw-c.nowcoder.com/api/sparta/home/recommend?page=${{ args.page }}&size=${{ args.limit }}"}},{select:"data.records"},{map:{rank:"${{ index + 1 }}",title:"${{ item.momentData?.title || item.longContentData?.title || item.contentData?.title || '' | text }}",author:"${{ item.userBrief?.nickname || '' }}",likes:"${{ item.frequencyData?.likeCnt || 0 }}",comments:"${{ item.frequencyData?.commentCnt || 0 }}",views:"${{ item.frequencyData?.viewCnt || 0 }}",content:"${{ item.momentData?.content || item.longContentData?.content || item.contentData?.content || '' | text }}",id:"${{ item.momentData?.uuid || item.longContentData?.uuid || item.contentData?.uuid || item.contentId || '' }}",momentId:"${{ item.contentId || item.momentData?.id || '' }}"}},{filter:"item.title"},{limit:"${{ args.limit }}"}]});
|
package/clis/nowcoder/search.js
CHANGED
|
@@ -2,7 +2,8 @@ import{cli as t}from"@jackwener/opencli/registry";t({site:"nowcoder",name:"searc
|
|
|
2
2
|
const query = \${{ args.query | json }};
|
|
3
3
|
const type = \${{ args.type | json }};
|
|
4
4
|
const limit = \${{ args.limit }};
|
|
5
|
-
|
|
5
|
+
// " 也要解(牛客把正文引号转义存库);& 挪到最后,否则 &quot; 会被二次解码。
|
|
6
|
+
const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/"/g, '"').replace(/�?39;/g, "'").replace(/'/g, "'").replace(/ /g, ' ').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&').trim();
|
|
6
7
|
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/pc/search', {
|
|
7
8
|
method: 'POST',
|
|
8
9
|
credentials: 'include',
|
|
@@ -20,7 +21,7 @@ import{cli as t}from"@jackwener/opencli/registry";t({site:"nowcoder",name:"searc
|
|
|
20
21
|
const id = data.contentId || '';
|
|
21
22
|
return {
|
|
22
23
|
rank: i + 1,
|
|
23
|
-
title: moment.title || contentData.title || user.nickname || '',
|
|
24
|
+
title: strip(moment.title || contentData.title || user.nickname || ''),
|
|
24
25
|
author: user.nickname || '',
|
|
25
26
|
school: user.educationInfo || '',
|
|
26
27
|
content: strip(moment.content || contentData.content || ''),
|
package/clis/nowcoder/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{AuthRequiredError as
|
|
1
|
+
import{AuthRequiredError as d,CommandExecutionError as i}from"@jackwener/opencli/errors";export const GW_BASE="https://gw-c.nowcoder.com/api/sparta",WWW_BASE="https://www.nowcoder.com";export async function getJson(t,n){const e=JSON.stringify(n);return t.evaluate(`
|
|
2
2
|
async () => {
|
|
3
|
-
const res = await fetch(${
|
|
3
|
+
const res = await fetch(${e}, { credentials: 'include' });
|
|
4
4
|
const text = await res.text();
|
|
5
5
|
try {
|
|
6
6
|
return JSON.parse(text);
|
|
@@ -8,13 +8,13 @@ import{AuthRequiredError as a,CommandExecutionError as i}from"@jackwener/opencli
|
|
|
8
8
|
return { code: -1, msg: '非 JSON 响应 (HTTP ' + res.status + '): ' + text.slice(0, 200) };
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
`)}export async function postJson(t,e,
|
|
11
|
+
`)}export async function postJson(t,n,e,r={}){const s=JSON.stringify(n),c=JSON.stringify(JSON.stringify(e)),o=JSON.stringify({"Content-Type":"application/json",...r});return t.evaluate(`
|
|
12
12
|
async () => {
|
|
13
|
-
const res = await fetch(${
|
|
13
|
+
const res = await fetch(${s}, {
|
|
14
14
|
method: 'POST',
|
|
15
15
|
credentials: 'include',
|
|
16
|
-
headers: ${
|
|
17
|
-
body: ${
|
|
16
|
+
headers: ${o},
|
|
17
|
+
body: ${c},
|
|
18
18
|
});
|
|
19
19
|
const text = await res.text();
|
|
20
20
|
try {
|
|
@@ -23,16 +23,13 @@ import{AuthRequiredError as a,CommandExecutionError as i}from"@jackwener/opencli
|
|
|
23
23
|
return { code: -1, msg: '非 JSON 响应 (HTTP ' + res.status + '): ' + text.slice(0, 200) };
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
`)}export async function postForm(t,e,
|
|
26
|
+
`)}export async function postForm(t,n,e,r={}){const s=JSON.stringify(n),c=JSON.stringify(e),o=JSON.stringify({"Content-Type":"application/x-www-form-urlencoded","x-requested-with":"XMLHttpRequest",...r});return t.evaluate(`
|
|
27
27
|
async () => {
|
|
28
|
-
const body = new URLSearchParams(${
|
|
29
|
-
const res = await fetch(${
|
|
28
|
+
const body = new URLSearchParams(${c});
|
|
29
|
+
const res = await fetch(${s}, {
|
|
30
30
|
method: 'POST',
|
|
31
31
|
credentials: 'include',
|
|
32
|
-
headers: {
|
|
33
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
34
|
-
'x-requested-with': 'XMLHttpRequest',
|
|
35
|
-
},
|
|
32
|
+
headers: ${o},
|
|
36
33
|
body: body.toString(),
|
|
37
34
|
});
|
|
38
35
|
const text = await res.text();
|
|
@@ -42,14 +39,14 @@ import{AuthRequiredError as a,CommandExecutionError as i}from"@jackwener/opencli
|
|
|
42
39
|
return { code: -1, msg: '非 JSON 响应 (HTTP ' + res.status + '): ' + text.slice(0, 200) };
|
|
43
40
|
}
|
|
44
41
|
}
|
|
45
|
-
`)}export async function postMultipart(t,e,
|
|
42
|
+
`)}export async function postMultipart(t,n,e,r={}){const s=JSON.stringify(n),c=JSON.stringify(e),o=JSON.stringify(r);return t.evaluate(`
|
|
46
43
|
async () => {
|
|
47
44
|
const fd = new FormData();
|
|
48
|
-
for (const [k, v] of Object.entries(${
|
|
49
|
-
const res = await fetch(${
|
|
45
|
+
for (const [k, v] of Object.entries(${c})) fd.append(k, v);
|
|
46
|
+
const res = await fetch(${s}, {
|
|
50
47
|
method: 'POST',
|
|
51
48
|
credentials: 'include',
|
|
52
|
-
headers: ${
|
|
49
|
+
headers: ${o},
|
|
53
50
|
body: fd,
|
|
54
51
|
});
|
|
55
52
|
const text = await res.text();
|
|
@@ -59,11 +56,11 @@ import{AuthRequiredError as a,CommandExecutionError as i}from"@jackwener/opencli
|
|
|
59
56
|
return { code: -1, msg: '非 JSON 响应 (HTTP ' + res.status + '): ' + text.slice(0, 200) };
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
|
-
`)}export function requireOk(t,
|
|
59
|
+
`)}export function requireOk(t,n){if(!t||typeof t!=="object"||Array.isArray(t))throw new i(`牛客 ${n} 接口返回了畸形响应`);if(Object.hasOwn(t,"success")?!!t.success:Object.hasOwn(t,"code")?t.code===0:Object.hasOwn(t,"data"))return t;const r=String(t.msg??t.message??"未知错误");if(/登录|登陆|未授权|token|身份/i.test(r))throw new d("nowcoder.com",`牛客 ${n} 需要登录: ${r}`);throw new i(`牛客 ${n} 失败: ${r} (code=${t.code??"?"})`)}export async function resolveMomentId(t,n){let e=String(n??"").trim();for(const c of[/detail\/([0-9a-fA-F]{20,})/,/discuss\/(\d+)/]){const o=e.match(c);if(o){e=o[1];break}}if(/^\d+$/.test(e))return e;if(!/^[0-9a-fA-F]{20,}$/.test(e))throw new i(`无法从输入解析牛客帖子 id: ${n}`);const s=requireOk(await getJson(t,`${GW_BASE}/detail/moment-data/detail/${e}`),"帖子解析").data?.id;if(!s)throw new i(`牛客帖子 ${e} 的详情缺少数字 momentId`);return String(s)}export async function getSelfUid(t){const n=await t.evaluate(`
|
|
63
60
|
async () => {
|
|
64
61
|
const link = document.querySelector('a[href*="/users/"]');
|
|
65
62
|
const href = link ? (link.getAttribute('href') || '') : '';
|
|
66
63
|
const match = href.match(/\\/users\\/(\\d+)/);
|
|
67
64
|
return match ? match[1] : '';
|
|
68
65
|
}
|
|
69
|
-
`);if(!
|
|
66
|
+
`);if(!n)throw new d("nowcoder.com","导航栏无登录用户链接(未登录)");return n}export function peerUidFromConversationId(t,n){const e=String(t??"").split("_");if(e.length!==2||!e.every((r)=>/^\d+$/.test(r)))return"";if(e[0]===e[1])return e[0];return e.find((r)=>r!==String(n))??""}export async function resolveConversation(t,n,e){const r=String(n??"").trim();if(!r)throw new i("缺少会话标识");if(r.includes("_")){const s=peerUidFromConversationId(r,e);if(!s)throw new i(`无法解析牛客会话 id: ${n}(应形如 <uidA>_<uidB>)`);return{conversationId:r,peerUid:s}}if(!/^\d+$/.test(r))throw new i(`无法解析牛客私信标识: ${n}(应为对方 userId 或 dm-list 的 conversation_id)`);for(let s=1;s<=5;s+=1){const o=requireOk(await getJson(t,`${WWW_BASE}/sns/message/${e}/get-conversation-list?page=${s}&_=${Date.now()}`),"私信会话列表").data?.conversations??[];if(!o.length)break;const a=o.find((u)=>String(u?.targetUserId??"")===r);if(a?.conversationId)return{conversationId:String(a.conversationId),peerUid:r}}throw new i(`在前 5 页会话列表里没找到与用户 ${r} 的会话(对方从未与你私信过时会话尚不存在,先用 dm-send 发起)`)}export function decodeHtmlEntities(t){return String(t??"").replace(/"/g,'"').replace(/�?39;/g,"'").replace(/'/g,"'").replace(/ /g," ").replace(/</g,"<").replace(/>/g,">").replace(/&#(\d+);/g,(n,e)=>String.fromCodePoint(Number(e))).replace(/&/g,"&")}
|
package/clis/nowcoder/watch.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{registerWatch as d}from"../_shared/watch.js";import{WWW_BASE as w,
|
|
1
|
+
import{registerWatch as d}from"../_shared/watch.js";import{WWW_BASE as w,decodeHtmlEntities as m,getJson as g,getSelfUid as c,requireOk as l}from"./utils.js";function u(t,e){const r=t?.data?.conversations;if(!Array.isArray(r))return[];const i=[];for(const n of r){const a=Number(n?.unreadMessageCount??0);if(a<=0&&!e?.includeOwn)continue;const s=String(n?.conversationId??""),o=Number(n?.createDate??0);if(!s||!o)continue;i.push({event:"dm",id:`${s}:${o}`,ts:o,conversation_id:s,sender:{id:String(n?.targetUserId??""),name:String(n?.targetUserName??"")},text:m(n?.content).trim(),url:"https://www.nowcoder.com/users/notice/privateChat",raw:{unread_count:a,message_count:n?.messageCount??0}})}return i}d({site:"nowcoder",name:"watch",domain:"www.nowcoder.com",mode:"reconcile",description:"牛客私信监听:按间隔拉取会话列表,有未读私信按 NDJSON 逐条输出",watchUrl:"https://www.nowcoder.com/",pattern:"/sns/message/",parse:(t,e)=>{if(!String(t?.url??"").includes("get-conversation-list"))return null;try{return u(JSON.parse(String(t.responsePreview??"")),e)}catch{return null}},onAttach:async(t,e)=>{e.state.uid=await c(t)},reconcile:async(t,e)=>{if(!e.state.uid)e.state.uid=await c(t);const r=`${w}/sns/message/${e.state.uid}/get-conversation-list?page=1`;return u(l(await g(t,r),"私信会话列表"),e)}});
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
import y from"node:vm";import{isRecord as S}from"../utils.js";import{log as
|
|
1
|
+
import y from"node:vm";import{isRecord as S}from"../utils.js";import{log as b}from"../logger.js";export function render(e,t){if(typeof e!=="string")return e;const i=e.trim(),r=i.match(/^\$\{\{\s*([^}]*(?:\}[^}][^}]*)*)\s*\}\}$/);if(r&&!i.includes("}}-")&&!i.includes("}}${{"))return evalExpr(r[1].trim(),t);const f=i.match(/^\$\{\{\s*([\s\S]*?)\s*\}\}$/);if(f){const s=f[1];if(!s.includes("${{"))return evalExpr(s.trim(),t)}return e.replace(/\$\{\{\s*(.*?)\s*\}\}/g,(s,n)=>String(evalExpr(n.trim(),t)))}export function evalExpr(e,t){const i=t.args??{},r=t.item??{},{data:f,root:s}=t,n=t.index??0,o=e.split(/(?<!\|)\|(?!\|)/).map((a)=>a.trim());if(o.length>1){let a=evalExpr(o[0],t);for(let d=1;d<o.length;d++)a=A(o[d],a);return a}const p=e.match(/^(['"])(.*)\1$/);if(p)return p[2];if(/^\d+(\.\d+)?$/.test(e))return Number(e);const c=resolvePath(e,{args:i,item:r,data:f,root:s,index:n});if(c!==null&&c!==void 0)return c;return O(e,{args:i,item:r,data:f,root:s,index:n})}function A(e,t){const i=e.match(/^(\w+)(?:\((.+)\))?$/);if(!i)return t;const[,r,f]=i,s=f?.replace(/^['"]|['"]$/g,"")??"";switch(r){case"default":{if(t===null||t===void 0||t===""){const n=parseInt(s,10);if(!Number.isNaN(n)&&String(n)===s.trim())return n;return s}return t}case"join":return Array.isArray(t)?t.join(s||", "):t;case"upper":return typeof t==="string"?t.toUpperCase():t;case"lower":return typeof t==="string"?t.toLowerCase():t;case"trim":return typeof t==="string"?t.trim():t;case"truncate":{const n=parseInt(s,10)||50;return typeof t==="string"&&t.length>n?`${t.slice(0,n)}...`:t}case"replace":{if(typeof t!=="string")return t;if(f==null)return t;const n=f.indexOf(",");if(n===-1)return t;const o=(a)=>a.trim().replace(/^['"]|['"]$/g,""),p=o(f.slice(0,n)),c=o(f.slice(n+1));return t.replaceAll(p,c)}case"keys":return t&&typeof t==="object"?Object.keys(t):t;case"length":return Array.isArray(t)?t.length:typeof t==="string"?t.length:t;case"first":return Array.isArray(t)?t[0]:t;case"last":return Array.isArray(t)?t[t.length-1]:t;case"json":return JSON.stringify(t??null);case"text":{if(typeof t!=="string")return t;return t.replace(/<br\s*\/?>/gi,`
|
|
2
|
+
`).replace(/<\/p>/gi,`
|
|
3
|
+
`).replace(/<[^>]+>/g,"").replace(/"/g,'"').replace(/'/g,"'").replace(/ /g," ").replace(/</g,"<").replace(/>/g,">").replace(/&#(\d+);/g,(n,o)=>String.fromCodePoint(Number(o))).replace(/&/g,"&").trim()}case"slugify":return typeof t==="string"?t.toLowerCase().replace(/[^\p{L}\p{N}]+/gu,"-").replace(/^-|-$/g,""):t;case"sanitize":return typeof t==="string"?t.replace(/[<>:"/\\|?*\x00-\x1f]/g,"_"):t;case"ext":{if(typeof t!=="string")return t;const n=t.lastIndexOf("."),o=Math.max(t.lastIndexOf("/"),t.lastIndexOf("\\"));return n>o?t.slice(n):""}case"basename":{if(typeof t!=="string")return t;const n=t.split(/[/\\]/);return n[n.length-1]||t}case"urlencode":return typeof t==="string"?encodeURIComponent(t):t;case"urldecode":return typeof t==="string"?decodeURIComponent(t):t;default:return t}}export function resolvePath(e,t){const i=t.args??{},r=t.item??{},{data:f,root:s}=t,n=t.index??0,o=e.split("."),p=o[0];let c,a;if(p==="args"){c=i;a=o.slice(1)}else if(p==="item"){c=r;a=o.slice(1)}else if(p==="data"){c=f;a=o.slice(1)}else if(p==="root"){c=s;a=o.slice(1)}else if(p==="index")return n;else{c=r;a=o}for(const d of a)if(S(c))c=c[d];else if(Array.isArray(c)&&/^\d+$/.test(d))c=c[parseInt(d,10)];else return null;return c}const C=/\b(constructor|__proto__|prototype|globalThis|process|require|import|eval)\b/,h=new WeakMap;function l(e){if(e===null||e===void 0)return e;if(typeof e!=="object"&&typeof e!=="function")return e;const t=e,i=h.get(t);if(i!==void 0)return JSON.parse(i);try{const r=JSON.stringify(e,(f,s)=>typeof s==="bigint"?s.toString():s);h.set(t,r);return JSON.parse(r)}catch(r){b.warn(`[pipeline/template] sanitizeContext failed: ${r instanceof Error?r.message:String(r)}. Returning {} for this branch. Likely cause: circular reference, Symbol, or other non-serializable value in pipeline context.`);return{}}}const _=256,g=new Map;function N(e){let t=g.get(e);if(t)return t;if(g.size>=_){const i=g.keys().next().value;if(i!==void 0)g.delete(i)}t=new y.Script(`(${e})`);g.set(e,t);return t}let m=null,u=null;function I(){if(m&&u)return{sandbox:m,context:u};m={args:{},item:{},data:null,root:null,index:0,encodeURIComponent,decodeURIComponent,JSON,Math,Number,String,Boolean,Array,Date};u=y.createContext(m,{codeGeneration:{strings:!1,wasm:!1}});return{sandbox:m,context:u}}const x=new Set(["args","item","data","root","index","encodeURIComponent","decodeURIComponent","JSON","Math","Number","String","Boolean","Array","Date"]);function O(e,t){if(e.length>2000)return;if(C.test(e))return;try{const i=N(e),{sandbox:r,context:f}=I();for(const s of Object.keys(r))if(!x.has(s))delete r[s];r.args=l(t.args??{});r.item=l(t.item??{});r.data=l(t.data);r.root=l(t.root);r.index=t.index??0;return i.runInContext(f,{timeout:50})}catch{return}}export function normalizeEvaluateSource(e){const t=e.trim();if(!t)return"() => undefined";if(t.startsWith("(")&&t.endsWith(")()"))return`() => (${t})`;if(/^(async\s+)?\([^)]*\)\s*=>/.test(t))return t;if(/^(async\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=>/.test(t))return t;if(t.startsWith("function ")||t.startsWith("async function "))return t;return`() => (${t})`}
|