publishport-opencli 1.8.4-pp.2 → 1.8.4-pp.3
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 +140 -0
- package/clis/bilibili/dynamic-post.js +81 -0
- package/clis/zhihu/article.js +118 -0
- package/clis/zhihu/pin.js +79 -0
- package/package.json +1 -1
package/cli-manifest.json
CHANGED
|
@@ -3658,6 +3658,40 @@
|
|
|
3658
3658
|
"sourceFile": "bilibili/dynamic.js",
|
|
3659
3659
|
"navigateBefore": "https://www.bilibili.com"
|
|
3660
3660
|
},
|
|
3661
|
+
{
|
|
3662
|
+
"site": "bilibili",
|
|
3663
|
+
"name": "dynamic-post",
|
|
3664
|
+
"description": "发布 B站动态(纯文本,官方 API,需登录)",
|
|
3665
|
+
"access": "write",
|
|
3666
|
+
"domain": "www.bilibili.com",
|
|
3667
|
+
"strategy": "cookie",
|
|
3668
|
+
"browser": true,
|
|
3669
|
+
"args": [
|
|
3670
|
+
{
|
|
3671
|
+
"name": "text",
|
|
3672
|
+
"type": "str",
|
|
3673
|
+
"required": true,
|
|
3674
|
+
"positional": true,
|
|
3675
|
+
"help": "Dynamic text content"
|
|
3676
|
+
},
|
|
3677
|
+
{
|
|
3678
|
+
"name": "execute",
|
|
3679
|
+
"type": "boolean",
|
|
3680
|
+
"required": false,
|
|
3681
|
+
"help": "Actually publish. Without it the command refuses to write."
|
|
3682
|
+
}
|
|
3683
|
+
],
|
|
3684
|
+
"columns": [
|
|
3685
|
+
"status",
|
|
3686
|
+
"dynamic_id",
|
|
3687
|
+
"text",
|
|
3688
|
+
"url"
|
|
3689
|
+
],
|
|
3690
|
+
"type": "js",
|
|
3691
|
+
"modulePath": "bilibili/dynamic-post.js",
|
|
3692
|
+
"sourceFile": "bilibili/dynamic-post.js",
|
|
3693
|
+
"navigateBefore": "https://www.bilibili.com"
|
|
3694
|
+
},
|
|
3661
3695
|
{
|
|
3662
3696
|
"site": "bilibili",
|
|
3663
3697
|
"name": "favorite",
|
|
@@ -43196,6 +43230,68 @@
|
|
|
43196
43230
|
"sourceFile": "zhihu/answer-detail.js",
|
|
43197
43231
|
"navigateBefore": "https://www.zhihu.com"
|
|
43198
43232
|
},
|
|
43233
|
+
{
|
|
43234
|
+
"site": "zhihu",
|
|
43235
|
+
"name": "article",
|
|
43236
|
+
"description": "Publish a Zhihu article (文章/专栏)",
|
|
43237
|
+
"access": "write",
|
|
43238
|
+
"domain": "zhuanlan.zhihu.com",
|
|
43239
|
+
"strategy": "cookie",
|
|
43240
|
+
"browser": true,
|
|
43241
|
+
"args": [
|
|
43242
|
+
{
|
|
43243
|
+
"name": "title",
|
|
43244
|
+
"type": "str",
|
|
43245
|
+
"required": true,
|
|
43246
|
+
"positional": true,
|
|
43247
|
+
"help": "Article title"
|
|
43248
|
+
},
|
|
43249
|
+
{
|
|
43250
|
+
"name": "text",
|
|
43251
|
+
"type": "str",
|
|
43252
|
+
"required": false,
|
|
43253
|
+
"positional": true,
|
|
43254
|
+
"help": "Article body (plain text by default; pass --html for raw HTML)"
|
|
43255
|
+
},
|
|
43256
|
+
{
|
|
43257
|
+
"name": "file",
|
|
43258
|
+
"type": "str",
|
|
43259
|
+
"required": false,
|
|
43260
|
+
"help": "Article body file path (UTF-8)"
|
|
43261
|
+
},
|
|
43262
|
+
{
|
|
43263
|
+
"name": "html",
|
|
43264
|
+
"type": "boolean",
|
|
43265
|
+
"required": false,
|
|
43266
|
+
"help": "Treat body as raw HTML (default: plain text — escaped, newlines become paragraphs)"
|
|
43267
|
+
},
|
|
43268
|
+
{
|
|
43269
|
+
"name": "draft",
|
|
43270
|
+
"type": "boolean",
|
|
43271
|
+
"required": false,
|
|
43272
|
+
"help": "Save as draft only; do not publish"
|
|
43273
|
+
},
|
|
43274
|
+
{
|
|
43275
|
+
"name": "execute",
|
|
43276
|
+
"type": "boolean",
|
|
43277
|
+
"required": false,
|
|
43278
|
+
"help": "Actually create/publish. Without it the command refuses to write."
|
|
43279
|
+
}
|
|
43280
|
+
],
|
|
43281
|
+
"columns": [
|
|
43282
|
+
"status",
|
|
43283
|
+
"outcome",
|
|
43284
|
+
"message",
|
|
43285
|
+
"target_type",
|
|
43286
|
+
"target",
|
|
43287
|
+
"created_target",
|
|
43288
|
+
"created_url"
|
|
43289
|
+
],
|
|
43290
|
+
"type": "js",
|
|
43291
|
+
"modulePath": "zhihu/article.js",
|
|
43292
|
+
"sourceFile": "zhihu/article.js",
|
|
43293
|
+
"navigateBefore": "https://zhuanlan.zhihu.com"
|
|
43294
|
+
},
|
|
43199
43295
|
{
|
|
43200
43296
|
"site": "zhihu",
|
|
43201
43297
|
"name": "collection",
|
|
@@ -43615,6 +43711,50 @@
|
|
|
43615
43711
|
"siteSession": "persistent",
|
|
43616
43712
|
"defaultWindowMode": "foreground"
|
|
43617
43713
|
},
|
|
43714
|
+
{
|
|
43715
|
+
"site": "zhihu",
|
|
43716
|
+
"name": "pin",
|
|
43717
|
+
"description": "Publish a Zhihu pin (想法/short post)",
|
|
43718
|
+
"access": "write",
|
|
43719
|
+
"domain": "www.zhihu.com",
|
|
43720
|
+
"strategy": "cookie",
|
|
43721
|
+
"browser": true,
|
|
43722
|
+
"args": [
|
|
43723
|
+
{
|
|
43724
|
+
"name": "text",
|
|
43725
|
+
"type": "str",
|
|
43726
|
+
"required": false,
|
|
43727
|
+
"positional": true,
|
|
43728
|
+
"help": "Pin text content"
|
|
43729
|
+
},
|
|
43730
|
+
{
|
|
43731
|
+
"name": "file",
|
|
43732
|
+
"type": "str",
|
|
43733
|
+
"required": false,
|
|
43734
|
+
"help": "Pin text file path (UTF-8)"
|
|
43735
|
+
},
|
|
43736
|
+
{
|
|
43737
|
+
"name": "execute",
|
|
43738
|
+
"type": "boolean",
|
|
43739
|
+
"required": false,
|
|
43740
|
+
"help": "Actually publish the pin. Without it the command refuses to write."
|
|
43741
|
+
}
|
|
43742
|
+
],
|
|
43743
|
+
"columns": [
|
|
43744
|
+
"status",
|
|
43745
|
+
"outcome",
|
|
43746
|
+
"message",
|
|
43747
|
+
"target_type",
|
|
43748
|
+
"target",
|
|
43749
|
+
"created_target",
|
|
43750
|
+
"created_url",
|
|
43751
|
+
"author_identity"
|
|
43752
|
+
],
|
|
43753
|
+
"type": "js",
|
|
43754
|
+
"modulePath": "zhihu/pin.js",
|
|
43755
|
+
"sourceFile": "zhihu/pin.js",
|
|
43756
|
+
"navigateBefore": "https://www.zhihu.com"
|
|
43757
|
+
},
|
|
43618
43758
|
{
|
|
43619
43759
|
"site": "zhihu",
|
|
43620
43760
|
"name": "pins",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bilibili dynamic-post — publish a text dynamic (动态) via the official web API
|
|
3
|
+
* (/x/dynamic/feed/create/dyn), authenticated by the logged-in cookie + bili_jct CSRF.
|
|
4
|
+
* Text-only for now; image dynamics need a separate bfs upload step (future work).
|
|
5
|
+
*/
|
|
6
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
7
|
+
import { ArgumentError, CommandExecutionError } from '@jackwener/opencli/errors';
|
|
8
|
+
|
|
9
|
+
cli({
|
|
10
|
+
site: 'bilibili',
|
|
11
|
+
name: 'dynamic-post',
|
|
12
|
+
access: 'write',
|
|
13
|
+
description: '发布 B站动态(纯文本,官方 API,需登录)',
|
|
14
|
+
domain: 'www.bilibili.com',
|
|
15
|
+
strategy: Strategy.COOKIE,
|
|
16
|
+
browser: true,
|
|
17
|
+
args: [
|
|
18
|
+
{ name: 'text', required: true, positional: true, help: 'Dynamic text content' },
|
|
19
|
+
{ name: 'execute', type: 'boolean', help: 'Actually publish. Without it the command refuses to write.' },
|
|
20
|
+
],
|
|
21
|
+
columns: ['status', 'dynamic_id', 'text', 'url'],
|
|
22
|
+
func: async (page, kwargs) => {
|
|
23
|
+
if (!page)
|
|
24
|
+
throw new CommandExecutionError('Browser session required for bilibili dynamic-post');
|
|
25
|
+
const text = String(kwargs.text ?? '').trim();
|
|
26
|
+
if (!text)
|
|
27
|
+
throw new ArgumentError('bilibili dynamic-post text cannot be empty');
|
|
28
|
+
if (!kwargs.execute)
|
|
29
|
+
throw new ArgumentError('Refusing to post: pass --execute to actually publish this dynamic');
|
|
30
|
+
|
|
31
|
+
// goto occasionally returns before the navigation lands (page still on a
|
|
32
|
+
// data: URL), which breaks the cookie/CSRF read — retry until we're on bilibili.
|
|
33
|
+
let landed = false;
|
|
34
|
+
for (let i = 0; i < 4; i++) {
|
|
35
|
+
await page.goto('https://t.bilibili.com');
|
|
36
|
+
await page.wait(2);
|
|
37
|
+
const href = await page.evaluate('location.href');
|
|
38
|
+
if (typeof href === 'string' && /^https?:\/\/[^/]*bilibili\.com/.test(href)) {
|
|
39
|
+
landed = true;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!landed)
|
|
44
|
+
throw new CommandExecutionError('Failed to navigate to t.bilibili.com (page did not land on a bilibili origin)');
|
|
45
|
+
|
|
46
|
+
const result = await page.evaluate(`(async () => {
|
|
47
|
+
var text = ${JSON.stringify(text)};
|
|
48
|
+
var csrf = (document.cookie.match(/bili_jct=([^;]+)/) || [])[1] || '';
|
|
49
|
+
var url = 'https://api.bilibili.com/x/dynamic/feed/create/dyn?csrf=' + encodeURIComponent(csrf);
|
|
50
|
+
var body = {
|
|
51
|
+
dyn_req: {
|
|
52
|
+
content: { contents: [{ raw_text: text, type: 1, biz_id: '' }] },
|
|
53
|
+
scene: 1,
|
|
54
|
+
meta: { app_meta: { from: 'create.dynamic.web', mobi_app: 'web' } },
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
var resp = await fetch(url, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
credentials: 'include',
|
|
60
|
+
headers: { 'Content-Type': 'application/json' },
|
|
61
|
+
body: JSON.stringify(body),
|
|
62
|
+
});
|
|
63
|
+
var t = await resp.text();
|
|
64
|
+
var data; try { data = JSON.parse(t); } catch (e) { data = null; }
|
|
65
|
+
if (!data) return { ok: false, message: 'Non-JSON response (HTTP ' + resp.status + '): ' + t.slice(0, 250) };
|
|
66
|
+
if (data.code !== 0) return { ok: false, message: (data.message || 'unknown error') + ' (' + data.code + ')' };
|
|
67
|
+
var dynId = data.data && (data.data.dyn_id_str || data.data.dyn_id || data.data.dynamic_id_str);
|
|
68
|
+
return { ok: true, dynId: String(dynId || ''), raw: t.slice(0, 250) };
|
|
69
|
+
})()`);
|
|
70
|
+
|
|
71
|
+
if (!result?.ok) {
|
|
72
|
+
throw new CommandExecutionError(`Bilibili dynamic-post failed: ${result?.message || 'unknown error'}`);
|
|
73
|
+
}
|
|
74
|
+
return [{
|
|
75
|
+
status: 'success',
|
|
76
|
+
dynamic_id: result.dynId,
|
|
77
|
+
text,
|
|
78
|
+
url: result.dynId ? `https://t.bilibili.com/${result.dynId}` : '',
|
|
79
|
+
}];
|
|
80
|
+
},
|
|
81
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { CliError, CommandExecutionError } from '@jackwener/opencli/errors';
|
|
2
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
3
|
+
import { buildResultRow, requireExecute, resolvePayload } from './write-shared.js';
|
|
4
|
+
|
|
5
|
+
cli({
|
|
6
|
+
site: 'zhihu',
|
|
7
|
+
name: 'article',
|
|
8
|
+
access: 'write',
|
|
9
|
+
description: 'Publish a Zhihu article (文章/专栏)',
|
|
10
|
+
domain: 'zhuanlan.zhihu.com',
|
|
11
|
+
strategy: Strategy.COOKIE,
|
|
12
|
+
browser: true,
|
|
13
|
+
args: [
|
|
14
|
+
{ name: 'title', positional: true, required: true, help: 'Article title' },
|
|
15
|
+
{ name: 'text', positional: true, help: 'Article body (plain text by default; pass --html for raw HTML)' },
|
|
16
|
+
{ name: 'file', help: 'Article body file path (UTF-8)' },
|
|
17
|
+
{ name: 'html', type: 'boolean', help: 'Treat body as raw HTML (default: plain text — escaped, newlines become paragraphs)' },
|
|
18
|
+
{ name: 'draft', type: 'boolean', help: 'Save as draft only; do not publish' },
|
|
19
|
+
{ name: 'execute', type: 'boolean', help: 'Actually create/publish. Without it the command refuses to write.' },
|
|
20
|
+
],
|
|
21
|
+
columns: ['status', 'outcome', 'message', 'target_type', 'target', 'created_target', 'created_url'],
|
|
22
|
+
func: async (page, kwargs) => {
|
|
23
|
+
if (!page)
|
|
24
|
+
throw new CommandExecutionError('Browser session required for zhihu article');
|
|
25
|
+
requireExecute(kwargs);
|
|
26
|
+
const title = String(kwargs.title ?? '').trim();
|
|
27
|
+
if (!title)
|
|
28
|
+
throw new CliError('INVALID_INPUT', 'Article title cannot be empty');
|
|
29
|
+
const body = await resolvePayload(kwargs);
|
|
30
|
+
// Plain text by default (escaped, one <p> per line). Pass --html to send raw HTML.
|
|
31
|
+
const content = kwargs.html
|
|
32
|
+
? body
|
|
33
|
+
: body.split('\n').map(line => `<p>${escapeHtml(line)}</p>`).join('');
|
|
34
|
+
const draftOnly = Boolean(kwargs.draft);
|
|
35
|
+
|
|
36
|
+
// Fetch from the zhuanlan origin so Origin/Referer match the write surface.
|
|
37
|
+
// goto occasionally returns before the navigation lands (page still on a
|
|
38
|
+
// data: URL), which breaks the cookie/xsrf read — retry until we're on zhihu.
|
|
39
|
+
await gotoZhuanlan(page);
|
|
40
|
+
|
|
41
|
+
const result = await page.evaluate(`(async () => {
|
|
42
|
+
var title = ${JSON.stringify(title)};
|
|
43
|
+
var content = ${JSON.stringify(content)};
|
|
44
|
+
var draftOnly = ${JSON.stringify(draftOnly)};
|
|
45
|
+
var xsrf = (document.cookie.match(/_xsrf=([^;]+)/) || [])[1] || '';
|
|
46
|
+
var H = { 'Content-Type': 'application/json', 'x-requested-with': 'fetch', 'x-xsrftoken': xsrf };
|
|
47
|
+
var base = 'https://zhuanlan.zhihu.com/api/articles';
|
|
48
|
+
|
|
49
|
+
// 1) create an empty draft
|
|
50
|
+
var cr = await fetch(base + '/drafts', {
|
|
51
|
+
method: 'POST', credentials: 'include', headers: H,
|
|
52
|
+
body: JSON.stringify({ title: title }),
|
|
53
|
+
});
|
|
54
|
+
var crText = await cr.text();
|
|
55
|
+
var crData; try { crData = JSON.parse(crText); } catch (e) { crData = null; }
|
|
56
|
+
if (!cr.ok || !crData || !crData.id) {
|
|
57
|
+
return { ok: false, stage: 'create', status: cr.status, message: crText.slice(0, 300) };
|
|
58
|
+
}
|
|
59
|
+
var id = String(crData.id);
|
|
60
|
+
|
|
61
|
+
// 2) write title + content into the draft
|
|
62
|
+
var up = await fetch(base + '/' + id + '/draft', {
|
|
63
|
+
method: 'PATCH', credentials: 'include', headers: H,
|
|
64
|
+
body: JSON.stringify({ title: title, content: content, table_of_contents: false, delta_time: 30 }),
|
|
65
|
+
});
|
|
66
|
+
var upText = await up.text();
|
|
67
|
+
if (!up.ok) {
|
|
68
|
+
return { ok: false, stage: 'update', status: up.status, message: upText.slice(0, 300), id: id };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (draftOnly) {
|
|
72
|
+
return { ok: true, draft: true, id: id, url: 'https://zhuanlan.zhihu.com/p/' + id + '/edit' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 3) publish
|
|
76
|
+
var pub = await fetch(base + '/' + id + '/publish', {
|
|
77
|
+
method: 'PUT', credentials: 'include', headers: H,
|
|
78
|
+
body: JSON.stringify({}),
|
|
79
|
+
});
|
|
80
|
+
var pubText = await pub.text();
|
|
81
|
+
var pubData; try { pubData = JSON.parse(pubText); } catch (e) { pubData = null; }
|
|
82
|
+
if (!pub.ok) {
|
|
83
|
+
return { ok: false, stage: 'publish', status: pub.status, message: pubText.slice(0, 300), id: id };
|
|
84
|
+
}
|
|
85
|
+
return { ok: true, draft: false, id: id, url: (pubData && pubData.url) || ('https://zhuanlan.zhihu.com/p/' + id) };
|
|
86
|
+
})()`);
|
|
87
|
+
|
|
88
|
+
if (!result?.ok) {
|
|
89
|
+
throw new CliError('COMMAND_EXEC', `[${result?.stage}] ${result?.message || 'Failed to publish article'} (HTTP ${result?.status})`);
|
|
90
|
+
}
|
|
91
|
+
return buildResultRow(
|
|
92
|
+
result.draft ? 'Saved article draft' : 'Published article',
|
|
93
|
+
'article',
|
|
94
|
+
'',
|
|
95
|
+
result.draft ? 'draft' : 'created',
|
|
96
|
+
{ created_target: 'article:' + result.id, created_url: result.url },
|
|
97
|
+
);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
async function gotoZhuanlan(page) {
|
|
102
|
+
for (let i = 0; i < 4; i++) {
|
|
103
|
+
await page.goto('https://zhuanlan.zhihu.com');
|
|
104
|
+
await page.wait(3);
|
|
105
|
+
const href = await page.evaluate('location.href');
|
|
106
|
+
if (typeof href === 'string' && /^https?:\/\/[^/]*zhihu\.com/.test(href)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
throw new CommandExecutionError('Failed to navigate to zhuanlan.zhihu.com (page did not land on a zhihu origin)');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function escapeHtml(s) {
|
|
114
|
+
return String(s)
|
|
115
|
+
.replace(/&/g, '&')
|
|
116
|
+
.replace(/</g, '<')
|
|
117
|
+
.replace(/>/g, '>');
|
|
118
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CliError, CommandExecutionError } from '@jackwener/opencli/errors';
|
|
2
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
3
|
+
import { buildResultRow, requireExecute, resolveCurrentUserIdentity, resolvePayload } from './write-shared.js';
|
|
4
|
+
|
|
5
|
+
cli({
|
|
6
|
+
site: 'zhihu',
|
|
7
|
+
name: 'pin',
|
|
8
|
+
access: 'write',
|
|
9
|
+
description: 'Publish a Zhihu pin (想法/short post)',
|
|
10
|
+
domain: 'www.zhihu.com',
|
|
11
|
+
strategy: Strategy.COOKIE,
|
|
12
|
+
browser: true,
|
|
13
|
+
args: [
|
|
14
|
+
{ name: 'text', positional: true, help: 'Pin text content' },
|
|
15
|
+
{ name: 'file', help: 'Pin text file path (UTF-8)' },
|
|
16
|
+
{ name: 'execute', type: 'boolean', help: 'Actually publish the pin. Without it the command refuses to write.' },
|
|
17
|
+
],
|
|
18
|
+
columns: ['status', 'outcome', 'message', 'target_type', 'target', 'created_target', 'created_url', 'author_identity'],
|
|
19
|
+
func: async (page, kwargs) => {
|
|
20
|
+
if (!page)
|
|
21
|
+
throw new CommandExecutionError('Browser session required for zhihu pin');
|
|
22
|
+
requireExecute(kwargs);
|
|
23
|
+
const payload = await resolvePayload(kwargs);
|
|
24
|
+
await page.goto('https://www.zhihu.com');
|
|
25
|
+
await page.wait(3);
|
|
26
|
+
// Identity is only used to annotate the result row; the create API itself
|
|
27
|
+
// enforces login, so a flaky DOM/state probe must not block the write.
|
|
28
|
+
let authorIdentity = '';
|
|
29
|
+
try {
|
|
30
|
+
authorIdentity = await resolveCurrentUserIdentity(page);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// best-effort
|
|
34
|
+
}
|
|
35
|
+
// Zhihu pins store text as HTML; wrap each line in <p> so line breaks survive.
|
|
36
|
+
const html = payload
|
|
37
|
+
.split('\n')
|
|
38
|
+
.map(line => `<p>${escapeHtml(line)}</p>`)
|
|
39
|
+
.join('');
|
|
40
|
+
const apiResult = await page.evaluate(`(async () => {
|
|
41
|
+
var html = ${JSON.stringify(html)};
|
|
42
|
+
var xsrf = (document.cookie.match(/_xsrf=([^;]+)/) || [])[1] || '';
|
|
43
|
+
var body = new URLSearchParams();
|
|
44
|
+
body.set('content', JSON.stringify([{ type: 'text', content: html }]));
|
|
45
|
+
body.set('reaction_instruction', JSON.stringify({}));
|
|
46
|
+
var resp = await fetch('https://www.zhihu.com/api/v4/pins', {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
credentials: 'include',
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
51
|
+
'x-requested-with': 'fetch',
|
|
52
|
+
'x-xsrftoken': xsrf,
|
|
53
|
+
},
|
|
54
|
+
body: body.toString(),
|
|
55
|
+
});
|
|
56
|
+
var text = await resp.text();
|
|
57
|
+
var data;
|
|
58
|
+
try { data = JSON.parse(text); } catch (e) { data = null; }
|
|
59
|
+
if (!resp.ok) return { ok: false, status: resp.status, message: (data && data.error && data.error.message) || text.slice(0, 300) };
|
|
60
|
+
if (!data || !data.id) return { ok: false, status: resp.status, message: 'Pin API response did not include a created pin id: ' + text.slice(0, 300) };
|
|
61
|
+
return { ok: true, id: String(data.id), url: data.url || ('https://www.zhihu.com/pin/' + data.id) };
|
|
62
|
+
})()`);
|
|
63
|
+
if (!apiResult?.ok) {
|
|
64
|
+
throw new CliError('COMMAND_EXEC', apiResult?.message || 'Failed to publish pin');
|
|
65
|
+
}
|
|
66
|
+
return buildResultRow('Published pin', 'pin', '', 'created', {
|
|
67
|
+
created_target: 'pin:' + apiResult.id,
|
|
68
|
+
created_url: apiResult.url,
|
|
69
|
+
author_identity: authorIdentity,
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
function escapeHtml(s) {
|
|
75
|
+
return String(s)
|
|
76
|
+
.replace(/&/g, '&')
|
|
77
|
+
.replace(/</g, '<')
|
|
78
|
+
.replace(/>/g, '>');
|
|
79
|
+
}
|