commit ff379c3f76de285968a8dafc49d79a7b9791516f Author: 苍何 <2689458656@qq.com> Date: Mon Jan 26 18:10:24 2026 +0800 first commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..80c7a8b --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# 小红书封面生成器 + +根据用户输入的主题,自动生成小红书风格的封面图片。 + +## 使用方式 + +### 获取 API Key + +在使用之前,您需要先获取 API Key: + +1. 访问 https://api.canghe.ai/ +2. 注册/登录账号 +3. 获取您的 API Key + +### 配置 API Key + +有两种方式配置 API Key: + +**方式一:通过命令行参数传递** + +```bash +npm start "复古学院风穿搭" "your-api-key-here" +``` + +**方式二:通过环境变量设置** + +```bash +export CANGHE_API_KEY="your-api-key-here" +npm start "复古学院风穿搭" +``` + +### 使用示例 + +直接输入你想要的主题,例如: + +```bash +# 命令行参数方式 +npm start "复古学院风穿搭" "your-api-key-here" + +# 环境变量方式 +export CANGHE_API_KEY="your-api-key-here" +npm start "秋季护肤指南" + +# 更多示例 +npm start "职场新人必读书单" +npm start "周末探店推荐" +``` + +## 功能特点 + +- 自动生成符合小红书风格的封面图片 +- 支持任意主题输入 +- 图片比例为竖版 3:4(适合手机屏幕) +- 设计清新、精致、年轻化 +- 自动去除水印和logo + +## API Key 说明 + +- API Key 是必需的,没有 API Key 将无法使用 +- 如果 API Key 无效或过期,系统会提示您前往 https://api.canghe.ai/ 获取新的 API Key +- API Key 通过参数或环境变量传递,安全性更高 + +## 错误处理 + +如果出现以下错误: + +- `缺少 API Key`:请前往 https://api.canghe.ai/ 获取您的 API Key +- `API Key 无效`:请检查您的 API Key 是否正确,或前往 https://api.canghe.ai/ 获取新的 API Key +- `API Error`:请检查网络连接或稍后重试 + +## 输出 + +生成的图片会保存到当前目录,文件名格式为:`xiaohongshu-cover-{timestamp}.png` diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..9b62c31 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,54 @@ +--- +name: xiaohongshu-cover-generator +description: Generate Xiaohongshu (小红书) style cover images based on user topics. Use when the user wants to create a social media cover image in Xiaohongshu style, or mentions keywords like "小红书封面", "Xiaohongshu cover", "封面生成", or provides a topic for cover image generation. The generated image will be saved to the current project directory. +--- + +# Xiaohongshu Cover Generator + +This skill generates Xiaohongshu-style cover images based on user-provided topics. + +## Usage + +When a user requests a Xiaohongshu cover image: + +1. Confirm the topic with the user if not clear +2. Check for API key (CANGHE_API_KEY environment variable or ask user to provide it) +3. Run the generation script with the topic +4. The image will be saved to the current working directory with filename format: `xiaohongshu-cover-{timestamp}.png` + +## Running the Script + +The script is located at `scripts/handler.ts` and requires: +- Topic (required): The subject for the cover image +- API Key (required): Either via environment variable `CANGHE_API_KEY` or passed as argument + +Execute with: +```bash +cd ~/.codebuddy/skills/xiaohongshu-cover-generator +npx tsx scripts/handler.ts "" "" +``` + +Or with environment variable: +```bash +cd ~/.codebuddy/skills/xiaohongshu-cover-generator +CANGHE_API_KEY="your-api-key" npx tsx scripts/handler.ts "" +``` + +## API Key + +Users need a valid API key from https://api.canghe.ai/ + +If the API key is missing or invalid, provide the user with clear instructions to obtain one. + +## Output + +The generated image will be saved to **the directory where the skill was invoked** (current working directory), not the skill's directory. The filename format is `xiaohongshu-cover-{timestamp}.png` where timestamp is in milliseconds. + +## Style Specifications + +The generated images follow these specifications: +- Aspect ratio: 3:4 (vertical, mobile-friendly) +- Style: Clean, refined, youthful aesthetic +- Automatic removal of watermarks and logos +- High-quality output suitable for mobile viewing +- Text should be clear and readable with appropriate sizing diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c405704 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,670 @@ +{ + "name": "xiaohongshu-cover-generator", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "xiaohongshu-cover-generator", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "node-fetch": "^3.3.2" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "tsx": "^4.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c4316be --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "xiaohongshu-cover-generator", + "version": "1.0.0", + "description": "生成小红书风格封面图的 skill", + "type": "module", + "main": "handler.ts", + "scripts": { + "start": "tsx handler.ts" + }, + "keywords": [ + "xiaohongshu", + "cover", + "generator", + "image" + ], + "author": "", + "license": "MIT", + "dependencies": { + "node-fetch": "^3.3.2" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "tsx": "^4.0.0" + }, + "config": { + "apiKeyHelp": "请前往 https://api.canghe.ai/ 获取您的 API Key" + } +} diff --git a/scripts/handler.ts b/scripts/handler.ts new file mode 100644 index 0000000..7ae3f3d --- /dev/null +++ b/scripts/handler.ts @@ -0,0 +1,303 @@ +import { request } from 'https'; + +interface GenerationOptions { + topic: string; + referenceImage?: string; + apiKey?: string; +} + +interface ChatMessage { + role: 'user' | 'assistant'; + content: Array<{ + type: 'text' | 'image_url'; + text?: string; + image_url?: { + url: string; + }; + }>; +} + +interface APIResponse { + id: string; + model: string; + object: string; + created: number; + choices: Array<{ + index: number; + message: { + role: string; + content: string; + }; + finish_reason: string; + }>; + usage: { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + }; +} + +// 系统提示词 +const SYSTEM_PROMPT = `请生成一张小红书风格的图文内容图片。 +【合规特别注意的】注意不要带有任何小红书的logo,不要有右下角的用户id以及logo +【合规特别注意的】用户给到的参考图片里如果有水印和logo(尤其是注意右下角,左上角),请一定要去掉 + +页面内容:{page_content} + +页面类型:{page_type} + +如果当前页面类型不是封面页的话,你要参考最后一张图片作为封面的样式 + +后续生成风格要严格参考封面的风格,要保持风格统一。 + +设计要求: + +1. 整体风格 +- 小红书爆款图文风格 +- 清新、精致、有设计感 +- 适合年轻人审美 +- 配色和谐,视觉吸引力强 + +2. 文字排版 +- 文字清晰可读,字号适中 +- 重要信息突出显示 +- 排版美观,留白合理 +- 支持 emoji 和符号 +- 如果是封面,标题要大而醒目 + +3. 视觉元素 +- 背景简洁但不单调 +- 可以有装饰性元素(如图标、插画) +- 配色温暖或清新 +- 保持专业感 + +4. 页面类型特殊要求 + +[封面] 类型: +- 标题占据主要位置,字号最大 +- 副标题居中或在标题下方 +- 整体设计要有吸引力和冲击力 +- 背景可以更丰富,有视觉焦点 + +[内容] 类型: +- 信息层次分明 +- 列表项清晰展示 +- 重点内容用颜色或粗体强调 +- 可以有小图标辅助说明 + +[总结] 类型: +- 总结性文字突出 +- 可以有勾选框或完成标志 +- 给人完成感和满足感 +- 鼓励性的视觉元素 + +5. 技术规格 +- 竖版 3:4 比例(小红书标准) +- 高清画质 +- 适合手机屏幕查看 +- 所有文字内容必须完整呈现 +- 【特别注意】无论是给到的图片还是参考文字,请仔细思考,让其符合正确的竖屏观看的排版,不能左右旋转或者是倒置。 + +6. 整体风格一致性 +为确保所有页面风格统一,请参考完整的内容大纲和用户原始需求来确定: +- 整体色调和配色方案 +- 设计风格(清新/科技/温暖/专业等) +- 视觉元素的一致性 +- 排版布局的统一风格 + +用户原始需求: +{user_topic} + +完整内容大纲参考: +--- +{full_outline} +--- + +请根据以上要求,生成一张精美的小红书风格图片。请直接给出图片,不要有任何手机边框,或者是白色留边。`; + +export async function generate(options: GenerationOptions): Promise { + const { topic, referenceImage, apiKey } = options; + + // 验证 API Key + if (!apiKey) { + throw new Error( + '缺少 API Key。请前往 https://api.canghe.ai/ 获取您的 API Key。' + ); + } + + // 构建系统提示词,替换占位符 + const systemPrompt = SYSTEM_PROMPT + .replace('{page_content}', topic) + .replace('{page_type}', '[封面]') + .replace('{user_topic}', topic) + .replace('{full_outline}', topic); + + // 构建请求消息 + const messages: ChatMessage[] = [ + { + role: 'user', + content: [ + { + type: 'text', + text: systemPrompt, + }, + ...(referenceImage + ? [ + { + type: 'image_url' as const, + image_url: { + url: referenceImage, + }, + }, + ] + : []), + ], + }, + ]; + + // 调用 API + const response = await callAPI(messages, apiKey); + + // 提取 base64 图片数据 + const base64Image = extractBase64Image(response.choices[0].message.content); + + // 转换为 Buffer + return Buffer.from(base64Image, 'base64'); +} + +function callAPI(messages: ChatMessage[], apiKey: string): Promise { + return new Promise((resolve, reject) => { + const data = JSON.stringify({ + model: 'gemini-3-pro-image-preview', + messages, + max_tokens: 4096, + }); + + const req = request( + 'https://api.canghe.ai/v1/chat/completions', + { + method: 'POST', + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + }, + (res) => { + let responseData = ''; + + res.on('data', (chunk) => { + responseData += chunk; + }); + + res.on('end', () => { + try { + const jsonResponse = JSON.parse(responseData); + + // 检查 API 错误 + if (res.statusCode !== 200) { + if (jsonResponse.error) { + if (jsonResponse.error.code === 'invalid_api_key' || + jsonResponse.error.message?.includes('api key') || + jsonResponse.error.message?.includes('API Key') || + jsonResponse.error.message?.includes('unauthorized') || + res.statusCode === 401) { + reject(new Error( + 'API Key 无效。请前往 https://api.canghe.ai/ 获取正确的 API Key。' + )); + } else { + reject(new Error(`API Error (${res.statusCode}): ${jsonResponse.error.message}`)); + } + } else { + reject(new Error(`API Error (${res.statusCode})`)); + } + return; + } + + resolve(jsonResponse); + } catch (error) { + reject(new Error(`Failed to parse API response: ${error}`)); + } + }); + } + ); + + req.on('error', (error) => { + reject(error); + }); + + req.write(data); + req.end(); + }); +} + +function extractBase64Image(content: string): string { + // 匹配 ![image](data:image/jpeg;base64,...) 格式 + const match = content.match(/!\[image\]\(data:image\/[a-z]+;base64,([A-Za-z0-9+/=]+)\)/); + + if (!match || !match[1]) { + throw new Error('Failed to extract base64 image from response'); + } + + return match[1]; +} + +// 主函数 +export async function main(args: string[]): Promise { + const topic = args[0]; + const apiKey = args[1] || process.env.CANGHE_API_KEY; + + if (!topic) { + console.error('Usage: xiaohongshu-cover-generator [apiKey]'); + console.error('Or set environment variable: CANGHE_API_KEY'); + console.error(''); + console.error('Get your API Key from: https://api.canghe.ai/'); + process.exit(1); + } + + if (!apiKey) { + console.error('⚠️ 缺少 API Key'); + console.error(''); + console.error('请提供 API Key 才能使用此功能:'); + console.error('方式一:'); + console.error(' npm start "主题" "your-api-key"'); + console.error(''); + console.error('方式二:'); + console.error(' export CANGHE_API_KEY="your-api-key"'); + console.error(' npm start "主题"'); + console.error(''); + console.error('获取 API Key:https://api.canghe.ai/'); + process.exit(1); + } + + try { + const imageBuffer = await generate({ topic, apiKey }); + + // 保存图片到当前目录 + const fs = await import('fs'); + const path = await import('path'); + const timestamp = Date.now(); + const outputPath = path.join(process.cwd(), `xiaohongshu-cover-${timestamp}.png`); + + fs.writeFileSync(outputPath, imageBuffer); + console.log(`Cover image saved to: ${outputPath}`); + } catch (error) { + console.error(''); + console.error('❌ 生成封面失败:'); + if (error instanceof Error) { + console.error(error.message); + // 如果是 API Key 相关错误,提供获取链接 + if (error.message.includes('API Key') || error.message.includes('api key')) { + console.error(''); + console.error('获取新的 API Key:https://api.canghe.ai/'); + } + } else { + console.error(String(error)); + } + process.exit(1); + } +} + +// 调用主函数 +if (import.meta.url === `file://${process.argv[1]}`) { + main(process.argv.slice(2)).catch(console.error); +}