sophhub 0.2.3 → 0.3.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.
- package/package.json +1 -1
- package/skills/consensus/skill.json +20 -0
- package/skills/consensus/src/SKILL.md +93 -0
- package/skills/deepwiki/skill.json +20 -0
- package/skills/deepwiki/src/SKILL.md +45 -0
- package/skills/deepwiki/src/_meta.json +6 -0
- package/skills/deepwiki/src/scripts/deepwiki.js +135 -0
- package/skills/feishu-bitable/skill.json +20 -0
- package/skills/feishu-bitable/src/CHECKLIST.md +150 -0
- package/skills/feishu-bitable/src/README.md +178 -0
- package/skills/feishu-bitable/src/SKILL.md +113 -0
- package/skills/feishu-bitable/src/_meta.json +6 -0
- package/skills/feishu-bitable/src/api.js +381 -0
- package/skills/feishu-bitable/src/bin/cli.js +284 -0
- package/skills/feishu-bitable/src/description.md +143 -0
- package/skills/feishu-bitable/src/examples/create-records.json +52 -0
- package/skills/feishu-bitable/src/examples/create-table.json +64 -0
- package/skills/feishu-bitable/src/package-lock.json +324 -0
- package/skills/feishu-bitable/src/package.json +33 -0
- package/skills/feishu-bitable/src/publish-config.json +14 -0
- package/skills/feishu-bitable/src/test-simple.js +61 -0
- package/skills/feishu-bitable/src/utils.js +261 -0
- package/skills/google-maps/skill.json +20 -0
- package/skills/google-maps/src/SKILL.md +237 -0
- package/skills/google-maps/src/_meta.json +6 -0
- package/skills/google-maps/src/lib/map_helper.py +912 -0
- package/skills/large-task-router/skill.json +20 -0
- package/skills/large-task-router/src/SKILL.md +79 -0
- package/skills/large-task-router/src/templates/plan.md +74 -0
- package/skills/notes-hub-assistant/skill.json +20 -0
- package/skills/notes-hub-assistant/src/SKILL.md +233 -0
- package/skills/notes-hub-assistant/src/scripts/_resolve_lark_cli.py +48 -0
- package/skills/notes-hub-assistant/src/scripts/openclaw_meeting_minutes.py +473 -0
- package/skills/notes-hub-assistant/src/scripts/openclaw_notes_crud.py +596 -0
- package/skills/notes-hub-assistant/src/scripts/openclaw_wolai_notes_crud.py +364 -0
- package/skills/notes-hub-assistant/src/scripts/run_meeting_minutes.py +79 -0
- package/skills/notes-hub-assistant/src/scripts/run_note_crud.py +37 -0
- package/skills/notes-hub-assistant/src/scripts/run_notionbot.py +36 -0
- package/skills/notes-hub-assistant/src/scripts/run_wolai_note_crud.py +27 -0
- package/skills/skillhub/skill.json +11 -4
- package/skills/skillhub/src/SKILL.md +11 -1
- package/skills/sophnet-dailynews/skill.json +20 -0
- package/skills/sophnet-dailynews/src/SKILL.md +179 -0
- package/skills/sophnet-dailynews/src/cache.json +151 -0
- package/skills/sophnet-dailynews/src/sources.json +230 -0
- package/skills/sophnet-schedule/skill.json +20 -0
- package/skills/sophnet-schedule/src/ARCHITECTURE.md +321 -0
- package/skills/sophnet-schedule/src/IMPROVEMENTS.md +145 -0
- package/skills/sophnet-schedule/src/SKILL.md +1050 -0
- package/skills/sophnet-schedule/src/_meta.json +6 -0
- package/skills/sophnet-schedule/src/api/__init__.py +0 -0
- package/skills/sophnet-schedule/src/api/models.py +245 -0
- package/skills/sophnet-schedule/src/apps/add_event.py +237 -0
- package/skills/sophnet-schedule/src/apps/check_reminders.py +112 -0
- package/skills/sophnet-schedule/src/apps/check_roc.py +246 -0
- package/skills/sophnet-schedule/src/apps/generate_daily_plan.py +342 -0
- package/skills/sophnet-schedule/src/apps/import_events.py +216 -0
- package/skills/sophnet-schedule/src/apps/monitor_calendar_changes.py +140 -0
- package/skills/sophnet-schedule/src/apps/register_tasks.py +169 -0
- package/skills/sophnet-schedule/src/apps/sync_roc_to_gcal.py +174 -0
- package/skills/sophnet-schedule/src/compat.py +66 -0
- package/skills/sophnet-schedule/src/config/__init__.py +0 -0
- package/skills/sophnet-schedule/src/config/reminder_rules.yaml +96 -0
- package/skills/sophnet-schedule/src/config/roc_events.yaml +44 -0
- package/skills/sophnet-schedule/src/config/settings.py +133 -0
- package/skills/sophnet-schedule/src/config/task_registry.yaml +92 -0
- package/skills/sophnet-schedule/src/docs/FRONTEND_INTEGRATION_GUIDE.md +437 -0
- package/skills/sophnet-schedule/src/gcal/__init__.py +0 -0
- package/skills/sophnet-schedule/src/gcal/client.py +374 -0
- package/skills/sophnet-schedule/src/gcal/models.py +91 -0
- package/skills/sophnet-schedule/src/requirements.txt +6 -0
- package/skills/sophnet-schedule/src/scripts/setup_gcal_token.py +85 -0
- package/skills/sophnet-schedule/src/server.py +669 -0
- package/skills/sophnet-schedule/src/services/__init__.py +0 -0
- package/skills/sophnet-schedule/src/services/calendar_backend.py +139 -0
- package/skills/sophnet-schedule/src/services/conflict_detector.py +96 -0
- package/skills/sophnet-schedule/src/services/datetime_utils.py +117 -0
- package/skills/sophnet-schedule/src/services/event_classifier.py +100 -0
- package/skills/sophnet-schedule/src/services/event_diff.py +160 -0
- package/skills/sophnet-schedule/src/services/google_integration.py +500 -0
- package/skills/sophnet-schedule/src/services/job_store.py +100 -0
- package/skills/sophnet-schedule/src/services/local_event_store.py +266 -0
- package/skills/sophnet-schedule/src/services/reminder_planner.py +116 -0
- package/skills/sophnet-schedule/src/services/runtime_utils.py +31 -0
- package/skills/sophnet-schedule/src/services/table_parser.py +286 -0
- package/skills/sophnet-schedule/src/services/task_builder.py +167 -0
- package/skills/sophnet-schedule/src/services/time_window.py +72 -0
- package/skills/sophnet-stock/skill.json +20 -0
- package/skills/sophnet-stock/src/App-Plan.md +442 -0
- package/skills/sophnet-stock/src/README.md +214 -0
- package/skills/sophnet-stock/src/SKILL.md +236 -0
- package/skills/sophnet-stock/src/TODO.md +394 -0
- package/skills/sophnet-stock/src/_meta.json +6 -0
- package/skills/sophnet-stock/src/docs/ARCHITECTURE.md +408 -0
- package/skills/sophnet-stock/src/docs/CONCEPT.md +233 -0
- package/skills/sophnet-stock/src/docs/HOT_SCANNER.md +288 -0
- package/skills/sophnet-stock/src/docs/README.md +95 -0
- package/skills/sophnet-stock/src/docs/USAGE.md +465 -0
- package/skills/sophnet-stock/src/scripts/analyze_stock.py +2565 -0
- package/skills/sophnet-stock/src/scripts/dividends.py +365 -0
- package/skills/sophnet-stock/src/scripts/hot_scanner.py +582 -0
- package/skills/sophnet-stock/src/scripts/portfolio.py +548 -0
- package/skills/sophnet-stock/src/scripts/rumor_scanner.py +342 -0
- package/skills/sophnet-stock/src/scripts/test_stock_analysis.py +409 -0
- package/skills/sophnet-stock/src/scripts/watchlist.py +336 -0
- package/skills/xiaohongshu/skill.json +20 -0
- package/skills/xiaohongshu/src/SKILL.md +91 -0
- package/skills/xiaohongshu/src/_meta.json +6 -0
- package/skills/xiaohongshu/src/assets/card.html +216 -0
- package/skills/xiaohongshu/src/assets/cover.html +82 -0
- package/skills/xiaohongshu/src/assets/example.md +84 -0
- package/skills/xiaohongshu/src/assets/styles.css +318 -0
- package/skills/xiaohongshu/src/scripts/render_xhs_v2.py +737 -0
- package/skills/xiaohongshu/src/scripts/sign_server.py +158 -0
- package/skills/xiaohongshu/src/scripts/stealth.min.js +7 -0
- package/skills/xiaohongshu/src/scripts/xhs_tool.py +186 -0
- package/skills/xiaohongshu/src/workflow.py +185 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
小红书工具 - 使用内置签名
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from dotenv import load_dotenv
|
|
12
|
+
|
|
13
|
+
# 加载环境变量
|
|
14
|
+
env_path = Path(__file__).parent.parent / '.env'
|
|
15
|
+
load_dotenv(env_path)
|
|
16
|
+
|
|
17
|
+
def get_client():
|
|
18
|
+
"""获取 XHS 客户端(使用内置签名)"""
|
|
19
|
+
from xhs import XhsClient
|
|
20
|
+
from xhs.help import sign as local_sign
|
|
21
|
+
|
|
22
|
+
cookie = os.getenv('XHS_COOKIE')
|
|
23
|
+
if not cookie:
|
|
24
|
+
print("❌ 错误: 未配置 XHS_COOKIE")
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
|
|
27
|
+
def sign_func(uri, data=None, a1="", web_session=""):
|
|
28
|
+
return local_sign(uri, data, a1=a1)
|
|
29
|
+
|
|
30
|
+
client = XhsClient(cookie=cookie, sign=sign_func)
|
|
31
|
+
return client
|
|
32
|
+
|
|
33
|
+
def cmd_search(args):
|
|
34
|
+
"""搜索笔记"""
|
|
35
|
+
client = get_client()
|
|
36
|
+
print(f"🔍 搜索: {args.keyword}...")
|
|
37
|
+
result = client.get_note_by_keyword(args.keyword)
|
|
38
|
+
notes = result.get('items', [])
|
|
39
|
+
|
|
40
|
+
print(f"找到 {len(notes)} 条笔记\n")
|
|
41
|
+
for i, item in enumerate(notes[:args.limit], 1):
|
|
42
|
+
note = item.get('note_card', {})
|
|
43
|
+
print(f"{i}. {note.get('display_title', note.get('title', '无标题'))}")
|
|
44
|
+
print(f" 👤 {note.get('user', {}).get('nickname', '未知')}")
|
|
45
|
+
print(f" ❤️ {note.get('liked_count', 0)}")
|
|
46
|
+
print(f" 🔗 ID: {note.get('note_id', 'N/A')}")
|
|
47
|
+
print()
|
|
48
|
+
|
|
49
|
+
def cmd_note(args):
|
|
50
|
+
"""查看笔记详情"""
|
|
51
|
+
client = get_client()
|
|
52
|
+
print(f"📖 获取笔记...")
|
|
53
|
+
note = client.get_note_by_id(args.note_id, xsec_token=args.token or "")
|
|
54
|
+
|
|
55
|
+
info = note.get('note_card', note)
|
|
56
|
+
print(f"\n📝 {info.get('title', info.get('display_title', '无标题'))}")
|
|
57
|
+
print(f"{'='*50}")
|
|
58
|
+
print(f"👤 作者: {info.get('user', {}).get('nickname', '未知')}")
|
|
59
|
+
print(f"❤️ 点赞: {info.get('interact_info', {}).get('liked_count', 0)}")
|
|
60
|
+
print(f"⭐ 收藏: {info.get('interact_info', {}).get('collected_count', 0)}")
|
|
61
|
+
print(f"💬 评论: {info.get('interact_info', {}).get('comment_count', 0)}")
|
|
62
|
+
print(f"\n📄 内容:\n{info.get('desc', '无内容')}")
|
|
63
|
+
|
|
64
|
+
def cmd_user(args):
|
|
65
|
+
"""查看用户信息"""
|
|
66
|
+
client = get_client()
|
|
67
|
+
print(f"👤 获取用户信息...")
|
|
68
|
+
user = client.get_user_info(args.user_id)
|
|
69
|
+
|
|
70
|
+
info = user.get('basic_info', user)
|
|
71
|
+
print(f"\n👤 {info.get('nickname', '未知')}")
|
|
72
|
+
print(f"🔴 小红书号: {info.get('red_id', 'N/A')}")
|
|
73
|
+
print(f"📝 简介: {info.get('desc', '无')}")
|
|
74
|
+
print(f"👥 粉丝: {info.get('fans', 0)}")
|
|
75
|
+
|
|
76
|
+
def cmd_me(args):
|
|
77
|
+
"""查看自己的账号"""
|
|
78
|
+
client = get_client()
|
|
79
|
+
print(f"👤 获取账号信息...")
|
|
80
|
+
info = client.get_self_info()
|
|
81
|
+
|
|
82
|
+
basic = info.get('basic_info', info)
|
|
83
|
+
print(f"\n👤 我的账号")
|
|
84
|
+
print(f"{'='*50}")
|
|
85
|
+
print(f"昵称: {basic.get('nickname', 'N/A')}")
|
|
86
|
+
print(f"小红书号: {basic.get('red_id', 'N/A')}")
|
|
87
|
+
print(f"粉丝: {basic.get('fans', 0)}")
|
|
88
|
+
|
|
89
|
+
def cmd_publish(args):
|
|
90
|
+
"""发布图文笔记"""
|
|
91
|
+
client = get_client()
|
|
92
|
+
|
|
93
|
+
# 读取内容
|
|
94
|
+
if args.content:
|
|
95
|
+
desc = args.content
|
|
96
|
+
elif args.file:
|
|
97
|
+
with open(args.file, 'r', encoding='utf-8') as f:
|
|
98
|
+
desc = f.read()
|
|
99
|
+
else:
|
|
100
|
+
print("❌ 需要 --content 或 --file")
|
|
101
|
+
sys.exit(1)
|
|
102
|
+
|
|
103
|
+
# 验证图片
|
|
104
|
+
images = []
|
|
105
|
+
for img in args.images:
|
|
106
|
+
if os.path.exists(img):
|
|
107
|
+
images.append(os.path.abspath(img))
|
|
108
|
+
else:
|
|
109
|
+
print(f"⚠️ 图片不存在: {img}")
|
|
110
|
+
|
|
111
|
+
if not images:
|
|
112
|
+
print("❌ 没有有效图片")
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
|
|
115
|
+
print(f"📤 准备发布...")
|
|
116
|
+
print(f" 标题: {args.title}")
|
|
117
|
+
print(f" 图片: {len(images)} 张")
|
|
118
|
+
|
|
119
|
+
if args.dry_run:
|
|
120
|
+
print("\n⚠️ [试运行] 不会实际发布")
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
result = client.create_image_note(
|
|
124
|
+
title=args.title[:20],
|
|
125
|
+
desc=desc,
|
|
126
|
+
files=images,
|
|
127
|
+
is_private=args.private
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
print(f"\n✅ 发布成功!")
|
|
131
|
+
if isinstance(result, dict):
|
|
132
|
+
note_id = result.get('note_id') or result.get('id')
|
|
133
|
+
if note_id:
|
|
134
|
+
print(f"🔗 https://www.xiaohongshu.com/explore/{note_id}")
|
|
135
|
+
|
|
136
|
+
def main():
|
|
137
|
+
parser = argparse.ArgumentParser(description='小红书工具')
|
|
138
|
+
subparsers = parser.add_subparsers(dest='command')
|
|
139
|
+
|
|
140
|
+
# search
|
|
141
|
+
p = subparsers.add_parser('search', help='搜索笔记')
|
|
142
|
+
p.add_argument('keyword')
|
|
143
|
+
p.add_argument('-n', '--limit', type=int, default=10)
|
|
144
|
+
p.set_defaults(func=cmd_search)
|
|
145
|
+
|
|
146
|
+
# note
|
|
147
|
+
p = subparsers.add_parser('note', help='查看笔记')
|
|
148
|
+
p.add_argument('note_id')
|
|
149
|
+
p.add_argument('--token', default='')
|
|
150
|
+
p.set_defaults(func=cmd_note)
|
|
151
|
+
|
|
152
|
+
# user
|
|
153
|
+
p = subparsers.add_parser('user', help='查看用户')
|
|
154
|
+
p.add_argument('user_id')
|
|
155
|
+
p.set_defaults(func=cmd_user)
|
|
156
|
+
|
|
157
|
+
# me
|
|
158
|
+
p = subparsers.add_parser('me', help='查看自己')
|
|
159
|
+
p.set_defaults(func=cmd_me)
|
|
160
|
+
|
|
161
|
+
# publish
|
|
162
|
+
p = subparsers.add_parser('publish', help='发布笔记')
|
|
163
|
+
p.add_argument('-t', '--title', required=True)
|
|
164
|
+
p.add_argument('-c', '--content')
|
|
165
|
+
p.add_argument('-f', '--file')
|
|
166
|
+
p.add_argument('-i', '--images', nargs='+', required=True)
|
|
167
|
+
p.add_argument('--private', action='store_true')
|
|
168
|
+
p.add_argument('--dry-run', action='store_true')
|
|
169
|
+
p.set_defaults(func=cmd_publish)
|
|
170
|
+
|
|
171
|
+
args = parser.parse_args()
|
|
172
|
+
|
|
173
|
+
if not args.command:
|
|
174
|
+
parser.print_help()
|
|
175
|
+
sys.exit(1)
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
args.func(args)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
print(f"❌ 错误: {e}")
|
|
181
|
+
import traceback
|
|
182
|
+
traceback.print_exc()
|
|
183
|
+
sys.exit(1)
|
|
184
|
+
|
|
185
|
+
if __name__ == '__main__':
|
|
186
|
+
main()
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
小红书工作流 - 从内容到发布的完整流程
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from datetime import datetime, timedelta
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
# 添加路径
|
|
14
|
+
SKILL_DIR = Path(__file__).parent
|
|
15
|
+
sys.path.insert(0, str(SKILL_DIR / "social-auto-upload"))
|
|
16
|
+
|
|
17
|
+
from dotenv import load_dotenv
|
|
18
|
+
load_dotenv(SKILL_DIR / ".env")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def setup_cookie():
|
|
22
|
+
"""首次登录获取 Cookie(需要有显示器)"""
|
|
23
|
+
from uploader.xiaohongshu_uploader.main import xiaohongshu_cookie_gen
|
|
24
|
+
cookie_file = SKILL_DIR / "social-auto-upload" / "cookies" / "xhs_account.json"
|
|
25
|
+
cookie_file.parent.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
await xiaohongshu_cookie_gen(str(cookie_file))
|
|
27
|
+
print(f"✅ Cookie 已保存到: {cookie_file}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def check_cookie():
|
|
31
|
+
"""检查 Cookie 是否有效"""
|
|
32
|
+
from uploader.xiaohongshu_uploader.main import cookie_auth
|
|
33
|
+
cookie_file = SKILL_DIR / "social-auto-upload" / "cookies" / "xhs_account.json"
|
|
34
|
+
|
|
35
|
+
if not cookie_file.exists():
|
|
36
|
+
print("❌ Cookie 文件不存在,请先运行 setup 命令")
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
valid = await cookie_auth(str(cookie_file))
|
|
40
|
+
if valid:
|
|
41
|
+
print("✅ Cookie 有效")
|
|
42
|
+
else:
|
|
43
|
+
print("❌ Cookie 已失效,请重新登录")
|
|
44
|
+
return valid
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def publish_images(title: str, desc: str, images: list, schedule_time=None, is_private=False):
|
|
48
|
+
"""
|
|
49
|
+
发布图文笔记
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
title: 笔记标题
|
|
53
|
+
desc: 笔记描述/正文
|
|
54
|
+
images: 图片路径列表
|
|
55
|
+
schedule_time: 定时发布时间 (datetime 对象)
|
|
56
|
+
is_private: 是否私密
|
|
57
|
+
"""
|
|
58
|
+
from playwright.async_api import async_playwright
|
|
59
|
+
from conf import LOCAL_CHROME_PATH
|
|
60
|
+
|
|
61
|
+
cookie_file = SKILL_DIR / "social-auto-upload" / "cookies" / "xhs_account.json"
|
|
62
|
+
|
|
63
|
+
if not cookie_file.exists():
|
|
64
|
+
print("❌ Cookie 文件不存在")
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
async with async_playwright() as p:
|
|
68
|
+
browser = await p.chromium.launch(headless=True)
|
|
69
|
+
context = await browser.new_context(storage_state=str(cookie_file))
|
|
70
|
+
page = await context.new_page()
|
|
71
|
+
|
|
72
|
+
print("🌐 打开小红书创作者中心...")
|
|
73
|
+
await page.goto("https://creator.xiaohongshu.com/publish/publish")
|
|
74
|
+
await asyncio.sleep(3)
|
|
75
|
+
|
|
76
|
+
# 选择"上传图文"
|
|
77
|
+
print("📝 选择图文发布...")
|
|
78
|
+
try:
|
|
79
|
+
await page.click('text=发布图文')
|
|
80
|
+
await asyncio.sleep(2)
|
|
81
|
+
except:
|
|
82
|
+
pass # 可能已经在图文页面
|
|
83
|
+
|
|
84
|
+
# 上传图片
|
|
85
|
+
print(f"📷 上传 {len(images)} 张图片...")
|
|
86
|
+
upload_input = await page.query_selector('input[type="file"]')
|
|
87
|
+
if upload_input:
|
|
88
|
+
await upload_input.set_input_files(images)
|
|
89
|
+
await asyncio.sleep(5) # 等待上传
|
|
90
|
+
|
|
91
|
+
# 填写标题
|
|
92
|
+
print("✏️ 填写标题...")
|
|
93
|
+
title_input = await page.query_selector('[placeholder*="标题"]')
|
|
94
|
+
if title_input:
|
|
95
|
+
await title_input.fill(title[:20]) # 小红书标题限制
|
|
96
|
+
|
|
97
|
+
# 填写正文
|
|
98
|
+
print("📄 填写正文...")
|
|
99
|
+
desc_input = await page.query_selector('[placeholder*="正文"]')
|
|
100
|
+
if not desc_input:
|
|
101
|
+
desc_input = await page.query_selector('.ql-editor')
|
|
102
|
+
if desc_input:
|
|
103
|
+
await desc_input.fill(desc)
|
|
104
|
+
|
|
105
|
+
await asyncio.sleep(2)
|
|
106
|
+
|
|
107
|
+
# 发布
|
|
108
|
+
print("🚀 发布中...")
|
|
109
|
+
publish_btn = await page.query_selector('button:has-text("发布")')
|
|
110
|
+
if publish_btn:
|
|
111
|
+
await publish_btn.click()
|
|
112
|
+
await asyncio.sleep(5)
|
|
113
|
+
print("✅ 发布成功!")
|
|
114
|
+
|
|
115
|
+
await browser.close()
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def render_content(markdown_file: str, style: str = "xiaohongshu", output_dir: str = None):
|
|
120
|
+
"""渲染 Markdown 为图片"""
|
|
121
|
+
import subprocess
|
|
122
|
+
|
|
123
|
+
if output_dir is None:
|
|
124
|
+
output_dir = str(SKILL_DIR / "output")
|
|
125
|
+
|
|
126
|
+
cmd = [
|
|
127
|
+
str(SKILL_DIR / "venv" / "bin" / "python"),
|
|
128
|
+
str(SKILL_DIR / "scripts" / "render_xhs_v2.py"),
|
|
129
|
+
markdown_file,
|
|
130
|
+
"--style", style,
|
|
131
|
+
"-o", output_dir
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
135
|
+
print(result.stdout)
|
|
136
|
+
if result.returncode != 0:
|
|
137
|
+
print(result.stderr)
|
|
138
|
+
return []
|
|
139
|
+
|
|
140
|
+
# 返回生成的图片列表
|
|
141
|
+
output_path = Path(output_dir)
|
|
142
|
+
images = sorted(output_path.glob("*.png"))
|
|
143
|
+
return [str(img) for img in images]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def main():
|
|
147
|
+
import argparse
|
|
148
|
+
parser = argparse.ArgumentParser(description="小红书工作流")
|
|
149
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
150
|
+
|
|
151
|
+
# setup 命令
|
|
152
|
+
subparsers.add_parser("setup", help="首次登录获取 Cookie")
|
|
153
|
+
|
|
154
|
+
# check 命令
|
|
155
|
+
subparsers.add_parser("check", help="检查 Cookie 状态")
|
|
156
|
+
|
|
157
|
+
# render 命令
|
|
158
|
+
render_parser = subparsers.add_parser("render", help="渲染内容为图片")
|
|
159
|
+
render_parser.add_argument("markdown_file", help="Markdown 文件路径")
|
|
160
|
+
render_parser.add_argument("--style", default="xiaohongshu", help="样式")
|
|
161
|
+
render_parser.add_argument("-o", "--output", help="输出目录")
|
|
162
|
+
|
|
163
|
+
# publish 命令
|
|
164
|
+
pub_parser = subparsers.add_parser("publish", help="发布图文笔记")
|
|
165
|
+
pub_parser.add_argument("-t", "--title", required=True, help="标题")
|
|
166
|
+
pub_parser.add_argument("-d", "--desc", required=True, help="描述")
|
|
167
|
+
pub_parser.add_argument("-i", "--images", nargs="+", required=True, help="图片路径")
|
|
168
|
+
|
|
169
|
+
args = parser.parse_args()
|
|
170
|
+
|
|
171
|
+
if args.command == "setup":
|
|
172
|
+
asyncio.run(setup_cookie())
|
|
173
|
+
elif args.command == "check":
|
|
174
|
+
asyncio.run(check_cookie())
|
|
175
|
+
elif args.command == "render":
|
|
176
|
+
images = render_content(args.markdown_file, args.style, args.output)
|
|
177
|
+
print(f"生成了 {len(images)} 张图片")
|
|
178
|
+
elif args.command == "publish":
|
|
179
|
+
asyncio.run(publish_images(args.title, args.desc, args.images))
|
|
180
|
+
else:
|
|
181
|
+
parser.print_help()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
if __name__ == "__main__":
|
|
185
|
+
main()
|