@xbrowser/wordpress 1.0.0

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.
Files changed (2) hide show
  1. package/index.ts +370 -0
  2. package/package.json +24 -0
package/index.ts ADDED
@@ -0,0 +1,370 @@
1
+ import { z } from 'zod';
2
+ import type { XCLIAPI } from '@dyyz1993/xcli-core';
3
+ import { ok, fail } from '@dyyz1993/xcli-core';
4
+
5
+ export default function (xcli: XCLIAPI): void {
6
+ const site = xcli.createSite({
7
+ name: 'wordpress',
8
+ url: 'https://wordpress.com',
9
+ description: 'WordPress.com SEO 外链 - 博客平台 (DA 93, dofollow)',
10
+ requiresLogin: true,
11
+ });
12
+
13
+ site.command('login', {
14
+ description: '登录 WordPress.com',
15
+ scope: 'browser',
16
+ parameters: z.object({
17
+ email: z.string().optional().describe('WordPress.com 邮箱'),
18
+ }),
19
+ examples: [
20
+ { cmd: 'xbrowser wordpress login --email "user@example.com"', description: '登录 WordPress.com' },
21
+ ],
22
+ result: z.any(),
23
+ handler: async (params, ctx) => {
24
+ const page = (ctx as Record<string, unknown>).page as import('playwright').Page | undefined;
25
+ if (!page) throw new Error('需要浏览器页面上下文');
26
+
27
+ await page.goto('https://wordpress.com/log-in', {
28
+ waitUntil: 'domcontentloaded',
29
+ timeout: 15000,
30
+ });
31
+ await page.waitForTimeout(2000);
32
+
33
+ if (params.email) {
34
+ const emailInput = page.locator(
35
+ 'input[name="username"], input[type="email"], input[id="usernameOrEmail"]'
36
+ ).first();
37
+ if (await emailInput.isVisible().catch(() => false)) {
38
+ await emailInput.fill(params.email);
39
+ await page.keyboard.press('Enter');
40
+ await page.waitForTimeout(2000);
41
+ }
42
+ }
43
+
44
+ await ctx.waitForHuman?.({
45
+ reason: 'Complete WordPress.com login',
46
+ timeout: 300,
47
+ });
48
+
49
+ await page.goto('https://wordpress.com/', {
50
+ waitUntil: 'domcontentloaded',
51
+ timeout: 15000,
52
+ });
53
+ await page.waitForTimeout(2000);
54
+
55
+ const loggedIn = await page
56
+ .locator('.masterbar__item-me, a[href*="/me"], [data-testid="sidebar-me"]')
57
+ .first()
58
+ .isVisible()
59
+ .catch(() => false);
60
+
61
+ await ctx.storage.set('wordpress_login', { loggedIn, at: Date.now() });
62
+
63
+ return ok({ loggedIn, url: page.url() }, [loggedIn ? 'WordPress.com 登录成功' : '登录可能未完成,请检查页面']);
64
+ },
65
+ });
66
+
67
+ site.command('publish', {
68
+ description: '在 WordPress.com 发布文章(dofollow 外链)',
69
+ scope: 'page',
70
+ parameters: z.object({
71
+ title: z.string().describe('文章标题'),
72
+ content: z.string().describe('文章内容(HTML 或纯文本)'),
73
+ tags: z.string().optional().describe('标签,逗号分隔'),
74
+ categories: z.string().optional().describe('分类,逗号分隔'),
75
+ }),
76
+ examples: [
77
+ {
78
+ cmd: 'xbrowser wordpress publish --title "Web Dev Guide" --content "<p>Visit <a href=\'https://example.com\'>my site</a></p>" --tags "web,dev"',
79
+ description: '发布带外链的文章',
80
+ },
81
+ ],
82
+ result: z.any(),
83
+ handler: async (params, ctx) => {
84
+ const page = (ctx as Record<string, unknown>).page as import('playwright').Page | undefined;
85
+ if (!page) throw new Error('需要浏览器页面上下文');
86
+
87
+ await page.goto('https://wordpress.com/post/', {
88
+ waitUntil: 'domcontentloaded',
89
+ timeout: 20000,
90
+ });
91
+ await page.waitForLoadState('networkidle');
92
+ await page.waitForTimeout(4000);
93
+
94
+ const titleInput = page.locator(
95
+ 'h1[aria-label="Post title"], [aria-label="Add title"], h1.wp-block-post-title, textarea[placeholder*="Title"], [data-testid="post-title"]'
96
+ ).first();
97
+ if (await titleInput.isVisible().catch(() => false)) {
98
+ await titleInput.click();
99
+ await titleInput.fill(params.title);
100
+ await page.keyboard.press('Enter');
101
+ }
102
+
103
+ const addBlock = page.locator(
104
+ 'button[aria-label="Add block"], [data-type="core/paragraph"], .block-editor-inserter__toggle'
105
+ ).first();
106
+ if (await addBlock.isVisible().catch(() => false)) {
107
+ await addBlock.click().catch(() => {});
108
+ await page.waitForTimeout(500);
109
+ }
110
+
111
+ const paragraphBlock = page.locator(
112
+ '[data-type="core/paragraph"] [contenteditable], .wp-block-paragraph [contenteditable], p[contenteditable="true"]'
113
+ ).first();
114
+ if (await paragraphBlock.isVisible().catch(() => false)) {
115
+ await paragraphBlock.click();
116
+ await page.keyboard.insertText(params.content);
117
+ }
118
+
119
+ if (params.tags || params.categories) {
120
+ const settingsBtn = page.locator(
121
+ 'button[aria-label="Settings"], button[aria-label="Post"], [data-testid="settings-button"]'
122
+ ).first();
123
+ if (await settingsBtn.isVisible().catch(() => false)) {
124
+ await settingsBtn.click();
125
+ await page.waitForTimeout(1000);
126
+
127
+ if (params.tags) {
128
+ const tagsInput = page.locator(
129
+ 'input[aria-label*="Tag"], input[placeholder*="tag"], input[name*="tags"]'
130
+ ).first();
131
+ if (await tagsInput.isVisible().catch(() => false)) {
132
+ await tagsInput.fill(params.tags);
133
+ await page.keyboard.press('Enter');
134
+ }
135
+ }
136
+
137
+ if (params.categories) {
138
+ const catInput = page.locator(
139
+ 'input[aria-label*="Category"], input[placeholder*="category"]'
140
+ ).first();
141
+ if (await catInput.isVisible().catch(() => false)) {
142
+ await catInput.fill(params.categories);
143
+ await page.keyboard.press('Enter');
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ await ctx.waitForHuman?.({
150
+ reason: 'Review and publish post (resolve CAPTCHA if present)',
151
+ timeout: 120,
152
+ autoDetect: true,
153
+ });
154
+
155
+ const publishBtn = page.locator(
156
+ 'button:has-text("Publish"), button:has-text("发布"), [data-testid="publish-button"]'
157
+ ).first();
158
+ if (await publishBtn.isVisible().catch(() => false)) {
159
+ await publishBtn.click();
160
+ await page.waitForTimeout(2000);
161
+
162
+ const confirmBtn = page.locator(
163
+ 'button:has-text("Publish"), button:has-text("发布")'
164
+ ).last();
165
+ if (await confirmBtn.isVisible().catch(() => false)) {
166
+ await confirmBtn.click();
167
+ await page.waitForTimeout(3000);
168
+ }
169
+ }
170
+
171
+ return ok({
172
+ title: params.title,
173
+ tags: params.tags,
174
+ categories: params.categories,
175
+ url: page.url(),
176
+ }, [`文章 "${params.title}" 已在 WordPress.com 发布`]);
177
+ },
178
+ });
179
+
180
+ site.command('draft', {
181
+ description: '在 WordPress.com 保存草稿',
182
+ scope: 'page',
183
+ parameters: z.object({
184
+ title: z.string().describe('文章标题'),
185
+ content: z.string().describe('文章内容(HTML 或纯文本)'),
186
+ }),
187
+ examples: [
188
+ {
189
+ cmd: 'xbrowser wordpress draft --title "Draft Post" --content "Draft content"',
190
+ description: '保存为草稿',
191
+ },
192
+ ],
193
+ result: z.any(),
194
+ handler: async (params, ctx) => {
195
+ const page = (ctx as Record<string, unknown>).page as import('playwright').Page | undefined;
196
+ if (!page) throw new Error('需要浏览器页面上下文');
197
+
198
+ await page.goto('https://wordpress.com/post/', {
199
+ waitUntil: 'domcontentloaded',
200
+ timeout: 20000,
201
+ });
202
+ await page.waitForLoadState('networkidle');
203
+ await page.waitForTimeout(4000);
204
+
205
+ const titleInput = page.locator(
206
+ 'h1[aria-label="Post title"], [aria-label="Add title"], h1.wp-block-post-title'
207
+ ).first();
208
+ if (await titleInput.isVisible().catch(() => false)) {
209
+ await titleInput.click();
210
+ await titleInput.fill(params.title);
211
+ await page.keyboard.press('Enter');
212
+ }
213
+
214
+ const paragraphBlock = page.locator(
215
+ '[data-type="core/paragraph"] [contenteditable], .wp-block-paragraph [contenteditable], p[contenteditable="true"]'
216
+ ).first();
217
+ if (await paragraphBlock.isVisible().catch(() => false)) {
218
+ await paragraphBlock.click();
219
+ await page.keyboard.insertText(params.content);
220
+ }
221
+
222
+ const saveBtn = page.locator(
223
+ 'button:has-text("Save draft"), button:has-text("保存草稿"), [data-testid="save-draft"]'
224
+ ).first();
225
+ if (await saveBtn.isVisible().catch(() => false)) {
226
+ await saveBtn.click();
227
+ await page.waitForTimeout(3000);
228
+ }
229
+
230
+ return ok({
231
+ title: params.title,
232
+ saved: true,
233
+ url: page.url(),
234
+ }, [`草稿 "${params.title}" 已保存`]);
235
+ },
236
+ });
237
+
238
+ site.command('update-profile', {
239
+ description: '更新 WordPress.com 个人资料(添加外链)',
240
+ scope: 'browser',
241
+ parameters: z.object({
242
+ url: z.string().describe('要添加到 Profile 的 URL'),
243
+ about: z.string().optional().describe('About me 文本'),
244
+ }),
245
+ examples: [
246
+ {
247
+ cmd: 'xbrowser wordpress update-profile --url "https://example.com" --about "Developer"',
248
+ description: '更新 Profile 添加外链',
249
+ },
250
+ ],
251
+ result: z.any(),
252
+ handler: async (params, ctx) => {
253
+ const page = (ctx as Record<string, unknown>).page as import('playwright').Page | undefined;
254
+ if (!page) throw new Error('需要浏览器页面上下文');
255
+
256
+ await page.goto('https://wordpress.com/me', {
257
+ waitUntil: 'domcontentloaded',
258
+ timeout: 15000,
259
+ });
260
+ await page.waitForLoadState('networkidle');
261
+ await page.waitForTimeout(2000);
262
+
263
+ if (params.about) {
264
+ const aboutInput = page.locator(
265
+ 'textarea[aria-label*="About"], textarea[name*="about"], textarea[placeholder*="About"]'
266
+ ).first();
267
+ if (await aboutInput.isVisible().catch(() => false)) {
268
+ await aboutInput.fill(`${params.about}\n\n${params.url}`);
269
+ }
270
+ }
271
+
272
+ const webInput = page.locator(
273
+ 'input[aria-label*="Website"], input[aria-label*="URL"], input[name*="website"], input[placeholder*="website"]'
274
+ ).first();
275
+ if (await webInput.isVisible().catch(() => false)) {
276
+ await webInput.fill(params.url);
277
+ }
278
+
279
+ const saveBtn = page.locator(
280
+ 'button:has-text("Save"), button:has-text("保存"), button[type="submit"]'
281
+ ).first();
282
+ if (await saveBtn.isVisible().catch(() => false)) {
283
+ await saveBtn.click();
284
+ await page.waitForTimeout(2000);
285
+ }
286
+
287
+ return ok({ url: params.url, updated: true }, ['Profile 已更新,包含外链']);
288
+ },
289
+ });
290
+
291
+ site.command('create-page', {
292
+ description: '在 WordPress.com 创建静态页面(dofollow 外链)',
293
+ scope: 'page',
294
+ parameters: z.object({
295
+ title: z.string().describe('页面标题'),
296
+ content: z.string().describe('页面内容(HTML 或纯文本)'),
297
+ }),
298
+ examples: [
299
+ {
300
+ cmd: 'xbrowser wordpress create-page --title "About Us" --content "<p>Visit <a href=\'https://example.com\'>our site</a></p>"',
301
+ description: '创建带外链的静态页面',
302
+ },
303
+ ],
304
+ result: z.any(),
305
+ handler: async (params, ctx) => {
306
+ const page = (ctx as Record<string, unknown>).page as import('playwright').Page | undefined;
307
+ if (!page) throw new Error('需要浏览器页面上下文');
308
+
309
+ await page.goto('https://wordpress.com/page/', {
310
+ waitUntil: 'domcontentloaded',
311
+ timeout: 20000,
312
+ });
313
+ await page.waitForLoadState('networkidle');
314
+ await page.waitForTimeout(4000);
315
+
316
+ const titleInput = page.locator(
317
+ 'h1[aria-label="Page title"], [aria-label="Add title"], h1.wp-block-post-title'
318
+ ).first();
319
+ if (await titleInput.isVisible().catch(() => false)) {
320
+ await titleInput.click();
321
+ await titleInput.fill(params.title);
322
+ await page.keyboard.press('Enter');
323
+ }
324
+
325
+ const paragraphBlock = page.locator(
326
+ '[data-type="core/paragraph"] [contenteditable], .wp-block-paragraph [contenteditable], p[contenteditable="true"]'
327
+ ).first();
328
+ if (await paragraphBlock.isVisible().catch(() => false)) {
329
+ await paragraphBlock.click();
330
+ await page.keyboard.insertText(params.content);
331
+ }
332
+
333
+ await ctx.waitForHuman?.({
334
+ reason: 'Review and publish page',
335
+ timeout: 120,
336
+ autoDetect: true,
337
+ });
338
+
339
+ const publishBtn = page.locator(
340
+ 'button:has-text("Publish"), button:has-text("发布")'
341
+ ).first();
342
+ if (await publishBtn.isVisible().catch(() => false)) {
343
+ await publishBtn.click();
344
+ await page.waitForTimeout(2000);
345
+
346
+ const confirmBtn = page.locator('button:has-text("Publish")').last();
347
+ if (await confirmBtn.isVisible().catch(() => false)) {
348
+ await confirmBtn.click();
349
+ await page.waitForTimeout(3000);
350
+ }
351
+ }
352
+
353
+ return ok({
354
+ title: params.title,
355
+ url: page.url(),
356
+ }, [`页面 "${params.title}" 已在 WordPress.com 发布`]);
357
+ },
358
+ });
359
+
360
+ site.login(async (ctx) => {
361
+ const page = (ctx as Record<string, unknown>).page as import('playwright').Page | undefined;
362
+ if (!page) return;
363
+ await page.goto('https://wordpress.com/log-in');
364
+ await ctx.storage.set('wordpress_login', { at: Date.now() });
365
+ });
366
+
367
+ site.logout(async (ctx) => {
368
+ await ctx.storage.delete('wordpress_login');
369
+ });
370
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@xbrowser/wordpress",
3
+ "version": "1.0.0",
4
+ "description": "WordPress.com SEO 外链 - 博客平台 (DA 93, dofollow)",
5
+ "main": "index.ts",
6
+ "keywords": [
7
+ "xbrowser",
8
+ "xbrowser-plugin",
9
+ "wordpress.com"
10
+ ],
11
+ "author": "dyyz1993",
12
+ "license": "MIT",
13
+ "xbrowser": {
14
+ "site": "https://wordpress.com",
15
+ "requiresLogin": true,
16
+ "commands": [
17
+ "login",
18
+ "publish",
19
+ "draft",
20
+ "update-profile",
21
+ "create-page"
22
+ ]
23
+ }
24
+ }