mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-12 05:51:44 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 089e0b5ba9 | |||
| 4bc0d05f66 | |||
| 90f3e2ff24 | |||
| 575ed29391 | |||
| 5d4ff2e9c2 |
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||||
"version": "1.4.0"
|
"version": "1.4.2"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
English | [中文](./CHANGELOG.zh.md)
|
English | [中文](./CHANGELOG.zh.md)
|
||||||
|
|
||||||
|
## 1.4.2 - 2026-01-19
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- `baoyu-danger-gemini-web`: adds supported browsers list (Chrome, Chromium, Edge) and proxy configuration guide.
|
||||||
|
|
||||||
|
## 1.4.1 - 2026-01-18
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- `baoyu-post-to-x`: supports multi-language UI selectors for X Articles (contributed by [@ianchenx](https://github.com/ianchenx)).
|
||||||
|
|
||||||
## 1.4.0 - 2026-01-18
|
## 1.4.0 - 2026-01-18
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
[English](./CHANGELOG.md) | 中文
|
[English](./CHANGELOG.md) | 中文
|
||||||
|
|
||||||
|
## 1.4.2 - 2026-01-19
|
||||||
|
|
||||||
|
### 文档
|
||||||
|
- `baoyu-danger-gemini-web`:添加支持的浏览器列表(Chrome、Chromium、Edge)和代理配置指南。
|
||||||
|
|
||||||
|
## 1.4.1 - 2026-01-18
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
- `baoyu-post-to-x`:支持 X Articles 多语言 UI 选择器(感谢 [@ianchenx](https://github.com/ianchenx) 贡献)。
|
||||||
|
|
||||||
## 1.4.0 - 2026-01-18
|
## 1.4.0 - 2026-01-18
|
||||||
|
|
||||||
### 新功能
|
### 新功能
|
||||||
|
|||||||
@@ -421,10 +421,18 @@ This skill uses the Gemini Web API (reverse-engineered).
|
|||||||
|
|
||||||
**Warning:** This project uses unofficial API access via browser cookies. Use at your own risk.
|
**Warning:** This project uses unofficial API access via browser cookies. Use at your own risk.
|
||||||
|
|
||||||
- First run opens Chrome to authenticate with Google
|
- First run opens a browser to authenticate with Google
|
||||||
- Cookies are cached for subsequent runs
|
- Cookies are cached for subsequent runs
|
||||||
- No guarantees on API stability or availability
|
- No guarantees on API stability or availability
|
||||||
|
|
||||||
|
**Supported browsers** (auto-detected): Google Chrome, Chrome Canary/Beta, Chromium, Microsoft Edge
|
||||||
|
|
||||||
|
**Proxy configuration**: If you need a proxy to access Google services (e.g., in China), set environment variables inline:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
HTTP_PROXY=http://127.0.0.1:7890 HTTPS_PROXY=http://127.0.0.1:7890 /baoyu-danger-gemini-web "Hello"
|
||||||
|
```
|
||||||
|
|
||||||
### baoyu-danger-x-to-markdown
|
### baoyu-danger-x-to-markdown
|
||||||
|
|
||||||
This skill uses a reverse-engineered X (Twitter) API.
|
This skill uses a reverse-engineered X (Twitter) API.
|
||||||
|
|||||||
+9
-1
@@ -421,10 +421,18 @@ mkdir -p .baoyu-skills/baoyu-cover-image
|
|||||||
|
|
||||||
**警告:** 本项目通过浏览器 cookies 使用非官方 API。使用风险自负。
|
**警告:** 本项目通过浏览器 cookies 使用非官方 API。使用风险自负。
|
||||||
|
|
||||||
- 首次运行会打开 Chrome 进行 Google 身份验证
|
- 首次运行会打开浏览器进行 Google 身份验证
|
||||||
- Cookies 会被缓存供后续使用
|
- Cookies 会被缓存供后续使用
|
||||||
- 不保证 API 的稳定性或可用性
|
- 不保证 API 的稳定性或可用性
|
||||||
|
|
||||||
|
**支持的浏览器**(自动检测):Google Chrome、Chrome Canary/Beta、Chromium、Microsoft Edge
|
||||||
|
|
||||||
|
**代理配置**:如果需要通过代理访问 Google 服务(如中国大陆用户),请在命令前设置环境变量:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
HTTP_PROXY=http://127.0.0.1:7890 HTTPS_PROXY=http://127.0.0.1:7890 /baoyu-danger-gemini-web "你好"
|
||||||
|
```
|
||||||
|
|
||||||
### baoyu-danger-x-to-markdown
|
### baoyu-danger-x-to-markdown
|
||||||
|
|
||||||
此技能使用逆向工程的 X (Twitter) API。
|
此技能使用逆向工程的 X (Twitter) API。
|
||||||
|
|||||||
@@ -198,7 +198,15 @@ CLI note: `scripts/main.ts` supports text generation, image generation, referenc
|
|||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
First run opens Chrome to authenticate with Google. Cookies are cached for subsequent runs.
|
First run opens a browser to authenticate with Google. Cookies are cached for subsequent runs.
|
||||||
|
|
||||||
|
**Supported browsers** (auto-detected in order):
|
||||||
|
- Google Chrome
|
||||||
|
- Google Chrome Canary / Beta
|
||||||
|
- Chromium
|
||||||
|
- Microsoft Edge
|
||||||
|
|
||||||
|
Override with `GEMINI_WEB_CHROME_PATH` environment variable if needed.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Force cookie refresh
|
# Force cookie refresh
|
||||||
@@ -214,6 +222,23 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --login
|
|||||||
| `GEMINI_WEB_CHROME_PROFILE_DIR` | Chrome profile directory |
|
| `GEMINI_WEB_CHROME_PROFILE_DIR` | Chrome profile directory |
|
||||||
| `GEMINI_WEB_CHROME_PATH` | Chrome executable path |
|
| `GEMINI_WEB_CHROME_PATH` | Chrome executable path |
|
||||||
|
|
||||||
|
## Proxy Configuration
|
||||||
|
|
||||||
|
If you need a proxy to access Google services (e.g., in China), set `HTTP_PROXY` and `HTTPS_PROXY` environment variables before running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example with local proxy
|
||||||
|
HTTP_PROXY=http://127.0.0.1:7890 HTTPS_PROXY=http://127.0.0.1:7890 npx -y bun ${SKILL_DIR}/scripts/main.ts "Hello"
|
||||||
|
|
||||||
|
# Image generation with proxy
|
||||||
|
HTTP_PROXY=http://127.0.0.1:7890 HTTPS_PROXY=http://127.0.0.1:7890 npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image cat.png
|
||||||
|
|
||||||
|
# Cookie refresh with proxy
|
||||||
|
HTTP_PROXY=http://127.0.0.1:7890 HTTPS_PROXY=http://127.0.0.1:7890 npx -y bun ${SKILL_DIR}/scripts/main.ts --login
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Environment variables must be set inline with the command. Shell profile settings (e.g., `.bashrc`) may not be inherited by subprocesses.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Generate text response
|
### Generate text response
|
||||||
|
|||||||
@@ -10,6 +10,37 @@ import { parseMarkdown } from './md-to-html.js';
|
|||||||
|
|
||||||
const X_ARTICLES_URL = 'https://x.com/compose/articles';
|
const X_ARTICLES_URL = 'https://x.com/compose/articles';
|
||||||
|
|
||||||
|
const I18N_SELECTORS = {
|
||||||
|
titleInput: [
|
||||||
|
'textarea[placeholder="Add a title"]',
|
||||||
|
'textarea[placeholder="添加标题"]',
|
||||||
|
'textarea[placeholder="タイトルを追加"]',
|
||||||
|
'textarea[placeholder="제목 추가"]',
|
||||||
|
'textarea[name="Article Title"]',
|
||||||
|
],
|
||||||
|
addPhotosButton: [
|
||||||
|
'[aria-label="Add photos or video"]',
|
||||||
|
'[aria-label="添加照片或视频"]',
|
||||||
|
'[aria-label="写真や動画を追加"]',
|
||||||
|
'[aria-label="사진 또는 동영상 추가"]',
|
||||||
|
],
|
||||||
|
previewButton: [
|
||||||
|
'a[href*="/preview"]',
|
||||||
|
'[data-testid="previewButton"]',
|
||||||
|
'button[aria-label*="preview" i]',
|
||||||
|
'button[aria-label*="预览" i]',
|
||||||
|
'button[aria-label*="プレビュー" i]',
|
||||||
|
'button[aria-label*="미리보기" i]',
|
||||||
|
],
|
||||||
|
publishButton: [
|
||||||
|
'[data-testid="publishButton"]',
|
||||||
|
'button[aria-label*="publish" i]',
|
||||||
|
'button[aria-label*="发布" i]',
|
||||||
|
'button[aria-label*="公開" i]',
|
||||||
|
'button[aria-label*="게시" i]',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
function sleep(ms: number): Promise<void> {
|
function sleep(ms: number): Promise<void> {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
@@ -299,8 +330,9 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for editor (title textarea)
|
// Wait for editor (title textarea)
|
||||||
|
const titleSelectors = I18N_SELECTORS.titleInput.join(', ');
|
||||||
console.log('[x-article] Waiting for editor...');
|
console.log('[x-article] Waiting for editor...');
|
||||||
const editorFound = await waitForElement('textarea[placeholder="Add a title"], textarea[name="Article Title"]', 30_000);
|
const editorFound = await waitForElement(titleSelectors, 30_000);
|
||||||
if (!editorFound) {
|
if (!editorFound) {
|
||||||
console.log('[x-article] Editor not found. Please ensure you have X Premium and are logged in.');
|
console.log('[x-article] Editor not found. Please ensure you have X Premium and are logged in.');
|
||||||
await sleep(60_000);
|
await sleep(60_000);
|
||||||
@@ -312,8 +344,16 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
|
|||||||
console.log('[x-article] Uploading cover image...');
|
console.log('[x-article] Uploading cover image...');
|
||||||
|
|
||||||
// Click "Add photos or video" button
|
// Click "Add photos or video" button
|
||||||
|
const addPhotosSelectors = JSON.stringify(I18N_SELECTORS.addPhotosButton);
|
||||||
await cdp.send('Runtime.evaluate', {
|
await cdp.send('Runtime.evaluate', {
|
||||||
expression: `document.querySelector('[aria-label="Add photos or video"]')?.click()`,
|
expression: `(() => {
|
||||||
|
const selectors = ${addPhotosSelectors};
|
||||||
|
for (const sel of selectors) {
|
||||||
|
const el = document.querySelector(sel);
|
||||||
|
if (el) { el.click(); return true; }
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})()`,
|
||||||
}, { sessionId });
|
}, { sessionId });
|
||||||
await sleep(500);
|
await sleep(500);
|
||||||
|
|
||||||
@@ -351,8 +391,16 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
|
|||||||
console.log('[x-article] Filling title...');
|
console.log('[x-article] Filling title...');
|
||||||
|
|
||||||
// Focus title input
|
// Focus title input
|
||||||
|
const titleInputSelectors = JSON.stringify(I18N_SELECTORS.titleInput);
|
||||||
await cdp.send('Runtime.evaluate', {
|
await cdp.send('Runtime.evaluate', {
|
||||||
expression: `document.querySelector('textarea[placeholder="Add a title"], textarea[name="Article Title"]')?.focus()`,
|
expression: `(() => {
|
||||||
|
const selectors = ${titleInputSelectors};
|
||||||
|
for (const sel of selectors) {
|
||||||
|
const el = document.querySelector(sel);
|
||||||
|
if (el) { el.focus(); return true; }
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})()`,
|
||||||
}, { sessionId });
|
}, { sessionId });
|
||||||
await sleep(200);
|
await sleep(200);
|
||||||
|
|
||||||
@@ -633,15 +681,13 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
|
|||||||
|
|
||||||
// Click Preview button
|
// Click Preview button
|
||||||
console.log('[x-article] Opening preview...');
|
console.log('[x-article] Opening preview...');
|
||||||
|
const previewSelectors = JSON.stringify(I18N_SELECTORS.previewButton);
|
||||||
const previewClicked = await cdp.send<{ result: { value: boolean } }>('Runtime.evaluate', {
|
const previewClicked = await cdp.send<{ result: { value: boolean } }>('Runtime.evaluate', {
|
||||||
expression: `(() => {
|
expression: `(() => {
|
||||||
// Try multiple selectors for preview button
|
const selectors = ${previewSelectors};
|
||||||
const previewLink = document.querySelector('a[href*="/preview"]')
|
for (const sel of selectors) {
|
||||||
|| document.querySelector('[data-testid="previewButton"]')
|
const el = document.querySelector(sel);
|
||||||
|| document.querySelector('button[aria-label*="preview" i]');
|
if (el) { el.click(); return true; }
|
||||||
if (previewLink) {
|
|
||||||
previewLink.click();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
})()`,
|
})()`,
|
||||||
@@ -658,12 +704,13 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
|
|||||||
// Check for publish button
|
// Check for publish button
|
||||||
if (submit) {
|
if (submit) {
|
||||||
console.log('[x-article] Publishing...');
|
console.log('[x-article] Publishing...');
|
||||||
|
const publishSelectors = JSON.stringify(I18N_SELECTORS.publishButton);
|
||||||
await cdp.send('Runtime.evaluate', {
|
await cdp.send('Runtime.evaluate', {
|
||||||
expression: `(() => {
|
expression: `(() => {
|
||||||
const publishBtn = document.querySelector('[data-testid="publishButton"], button[aria-label*="publish" i], button[aria-label*="发布" i]');
|
const selectors = ${publishSelectors};
|
||||||
if (publishBtn && !publishBtn.disabled) {
|
for (const sel of selectors) {
|
||||||
publishBtn.click();
|
const el = document.querySelector(sel);
|
||||||
return true;
|
if (el && !el.disabled) { el.click(); return true; }
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
})()`,
|
})()`,
|
||||||
|
|||||||
Reference in New Issue
Block a user