commit 5ae289decb698075a4170c05e918465420eaf239 Author: 唐天洛 <563415956@qq.com> Date: Mon Mar 30 12:32:57 2026 +0800 项目初始化:小说创作工具集 - 创建飞书同步工具 (Python版) - 创建字数统计工具 - 创建章节生成器 - 创建番茄黄金三章模板 - 完善项目文档和结构 - 配置完整的工具链 diff --git a/PROJECT.md b/PROJECT.md new file mode 100644 index 0000000..c0f4e9c --- /dev/null +++ b/PROJECT.md @@ -0,0 +1,96 @@ +# 小说创作工具项目 + +## 项目信息 +- **名称**: 小说创作工具集 +- **类型**: 工具、脚本、自动化 +- **状态**: 持续开发中 +- **目标**: 提供高效的小说创作工具链 +- **作者**: 唐天洛 +- **创建时间**: 2026年3月30日 + +## 项目目标 +1. **提升效率**: 自动化重复性工作 +2. **质量控制**: 辅助质量检查和优化 +3. **进度管理**: 跟踪创作进度和数据 +4. **同步备份**: 管理多平台同步和备份 + +## 核心工具 + +### 1. 创作管理工具 +- **章节生成器**: 结构化章节生成 +- **大纲助手**: 大纲规划和梳理 +- **人物管理**: 人物设定和关系管理 +- **情节设计**: 爽点设计和情节编排 + +### 2. 质量检查工具 +- **字数统计**: 章节字数、总字数统计 +- **节奏分析**: 节奏快慢和爽点密度分析 +- **对话检查**: 对话占比和口语化检查 +- **逻辑校验**: 时间线、空间位置逻辑检查 + +### 3. 同步工具 +- **飞书同步**: 自动同步到飞书文档 +- **Git同步**: 版本控制和云端备份 +- **多平台适配**: 适配番茄、起点等平台格式 +- **数据同步**: 进度数据自动同步 + +### 4. 自动化工具 +- **定时检查**: 自动检查创作进度 +- **模板应用**: 快速应用创作模板 +- **文件整理**: 自动整理和归档文件 +- **数据分析**: 创作数据分析和报告 + +## 项目结构 +``` +小说工具/ +├── PROJECT.md # 项目说明(本文件) +├── README.md # 详细使用说明 +├── generators/ # 生成器工具 +│ ├── chapter_generator.py +│ ├── outline_generator.py +│ └── character_generator.py +├── analyzers/ # 分析工具 +│ ├── word_count.py +│ ├── rhythm_analyzer.py +│ └── dialogue_checker.py +├── sync_tools/ # 同步工具 +│ ├── feishu_sync.py +│ ├── git_sync.py +│ └── platform_adapter.py +├── automation/ # 自动化脚本 +│ ├── daily_check.sh +│ ├── file_organizer.py +│ └── data_analyzer.py +├── templates/ # 模板文件 +│ ├── 番茄黄金三章.md +│ ├── 玄幻大纲模板.md +│ └── 悬疑情节模板.md +└── configs/ # 配置文件 + ├── feishu_config.json + ├── git_config.json + └── platform_config.json +``` + +## 当前已有工具 +1. **feishu_novel_sync.sh**: 飞书小说同步脚本 +2. **sync_novel_to_feishu.py**: Python版同步脚本 +3. **inkos_monitor.sh**: inkos创作监控脚本 +4. **番茄创作工作流.md**: 番茄平台创作工作流文档 + +## 开发计划 +1. **第一阶段**: 完善已有工具,统一接口 +2. **第二阶段**: 开发Web界面,方便使用 +3. **第三阶段**: 集成AI辅助创作功能 +4. **第四阶段**: 开发团队协作功能 + +## 使用说明 +1. **安装依赖**: 根据工具要求安装Python/Shell依赖 +2. **配置环境**: 配置飞书、Git等平台API +3. **定时运行**: 设置定时任务自动运行 +4. **监控反馈**: 监控运行结果,调整配置 + +## 项目价值 +1. **个人效率**: 大幅提升个人创作效率 +2. **质量保障**: 系统化保障作品质量 +3. **数据驱动**: 基于数据的创作决策 +4. **可扩展性**: 可扩展到团队协作和多平台 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c91142b --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# 小说创作工具集 + +## 项目简介 +为番茄小说创作者提供完整的工具链,涵盖创作、管理、同步、分析全流程。 + +## 核心功能 +### 1. 创作管理 +- 章节生成器 +- 大纲助手 +- 人物管理 +- 情节设计 + +### 2. 质量检查 +- 字数统计 +- 节奏分析 +- 对话检查 +- 逻辑校验 + +### 3. 同步工具 +- 飞书同步 +- Git同步 +- 多平台适配 +- 数据同步 + +### 4. 自动化 +- 定时检查 +- 模板应用 +- 文件整理 +- 数据分析 + +## 快速开始 +### 安装 +```bash +# 克隆项目 +git clone https://gitea.nevadalice.top:226/liyuchen/novel-tools.git + +# 安装依赖 +pip install -r requirements.txt +``` + +### 使用 +```bash +# 创建新小说项目 +./create_novel_project.sh 末日重生 "末日重生-开局囤货十亿物资" 末世重生 + +# 统计章节 +cd 末日重生 && ./tools/章节统计.sh + +# 同步到飞书 +python sync_tools/feishu_sync.py +``` + +## 项目结构 +``` +小说工具/ +├── README.md # 项目说明 +├── create_novel_project.sh # 项目创建工具 +├── generators/ # 生成器 +├── analyzers/ # 分析工具 +├── sync_tools/ # 同步工具 +├── automation/ # 自动化脚本 +├── templates/ # 模板文件 +└── configs/ # 配置文件 +``` + +## 已有工具 +1. `create_novel_project.sh` - 快速创建小说项目 +2. `feishu_novel_sync.sh` - 飞书小说同步(原版) +3. `sync_novel_to_feishu.py` - Python版同步 +4. `inkos_monitor.sh` - inkos创作监控 + +## 开发计划 +### 第一阶段(已完成) +- [x] 基础工具框架 +- [x] 项目创建工具 +- [x] 基础同步工具 + +### 第二阶段(进行中) +- [ ] 章节生成器 +- [ ] 质量分析工具 +- [ ] Web管理界面 + +### 第三阶段(规划中) +- [ ] AI辅助创作 +- [ ] 数据分析平台 +- [ ] 团队协作功能 + +## 贡献指南 +1. Fork本项目 +2. 创建功能分支 +3. 提交Pull Request +4. 通过代码审查 + +## 许可证 +MIT License diff --git a/analyzers/word_count.py b/analyzers/word_count.py new file mode 100755 index 0000000..b9cea68 --- /dev/null +++ b/analyzers/word_count.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 +""" +小说字数统计工具 +统计章节字数、总字数、进度等 +""" + +import os +import re +from datetime import datetime +import json + +class WordCounter: + def __init__(self): + self.total_words = 0 + self.total_chars = 0 + self.chapter_stats = [] + + def count_file(self, file_path): + """统计单个文件""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + + # 计算字数(中文字符+英文单词) + chinese_chars = len(re.findall(r'[\u4e00-\u9fff]', content)) + english_words = len(re.findall(r'\b[a-zA-Z]+\b', content)) + total_words = chinese_chars + english_words + + # 计算总字符数 + total_chars = len(content) + + # 计算段落数 + paragraphs = len([p for p in content.split('\n\n') if p.strip()]) + + # 计算对话行数 + dialogue_lines = len(re.findall(r'["「][^"」]+["」]', content)) + + return { + "file": file_path, + "words": total_words, + "chars": total_chars, + "paragraphs": paragraphs, + "dialogue_lines": dialogue_lines, + "dialogue_ratio": round(dialogue_lines / max(paragraphs, 1), 2) + } + except Exception as e: + print(f"统计文件失败 {file_path}: {e}") + return None + + def count_project(self, project_path): + """统计整个项目""" + chapters_dir = os.path.join(project_path, "chapters") + + if not os.path.exists(chapters_dir): + print(f"章节目录不存在: {chapters_dir}") + return None + + # 获取所有章节文件 + chapter_files = [] + for root, dirs, files in os.walk(chapters_dir): + for file in files: + if file.endswith('.md'): + chapter_files.append(os.path.join(root, file)) + + if not chapter_files: + print("没有找到章节文件") + return None + + # 按文件名排序 + chapter_files.sort() + + # 统计每个章节 + self.chapter_stats = [] + self.total_words = 0 + self.total_chars = 0 + + for chapter_file in chapter_files: + stats = self.count_file(chapter_file) + if stats: + self.chapter_stats.append(stats) + self.total_words += stats['words'] + self.total_chars += stats['chars'] + + return { + "project": project_path, + "total_chapters": len(self.chapter_stats), + "total_words": self.total_words, + "total_chars": self.total_chars, + "avg_words_per_chapter": round(self.total_words / max(len(self.chapter_stats), 1)), + "chapters": self.chapter_stats + } + + def generate_report(self, project_path, output_path=None): + """生成统计报告""" + stats = self.count_project(project_path) + if not stats: + return None + + # 番茄平台标准 + tomato_standard = 2500 # 每章标准字数 + daily_target = 4000 # 日更目标 + + # 计算进度 + completed_chapters = stats['total_chapters'] + total_words = stats['total_words'] + avg_words = stats['avg_words_per_chapter'] + + # 评估 + if avg_words < 2000: + word_rating = "⚠️ 字数偏少" + elif avg_words < 2500: + word_rating = "✅ 符合标准" + elif avg_words < 3500: + word_rating = "✅ 字数充足" + else: + word_rating = "⚠️ 字数偏多" + + # 对话占比评估 + avg_dialogue_ratio = sum(s['dialogue_ratio'] for s in self.chapter_stats) / len(self.chapter_stats) + if avg_dialogue_ratio < 0.3: + dialogue_rating = "⚠️ 对话偏少(影响听书分成)" + elif avg_dialogue_ratio < 0.5: + dialogue_rating = "✅ 对话适中" + else: + dialogue_rating = "✅ 对话丰富(适合听书)" + + # 生成报告 + report = f""" +# 小说字数统计报告 + +## 项目信息 +- 项目路径: {project_path} +- 统计时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} +- 章节数量: {completed_chapters} 章 +- 总字数: {total_words} 字 +- 总字符数: {stats['total_chars']} 字符 + +## 章节统计 +- 平均每章字数: {avg_words} 字 +- 字数评估: {word_rating} +- 平均对话占比: {avg_dialogue_ratio:.1%} +- 对话评估: {dialogue_rating} + +## 番茄平台适配 +### 字数要求 +- 标准章节字数: 2500-3500字 +- 当前平均: {avg_words} 字 +- 状态: {"符合" if 2000 <= avg_words <= 3500 else "需要调整"} + +### 更新要求 +- 日更全勤要求: 4000字/天 +- 当前总字数: {total_words} 字 +- 相当于: {total_words // 4000} 天的全勤更新量 + +### 听书优化 +- 推荐对话占比: >30% +- 当前对话占比: {avg_dialogue_ratio:.1%} +- 状态: {"适合听书" if avg_dialogue_ratio >= 0.3 else "需要增加对话"} + +## 详细章节数据 +""" + # 添加每个章节的详细数据 + for i, chapter in enumerate(self.chapter_stats, 1): + chapter_name = os.path.basename(chapter['file']).replace('.md', '') + report += f"\n### 第{i}章: {chapter_name}\n" + report += f"- 字数: {chapter['words']} 字\n" + report += f"- 字符: {chapter['chars']} 字符\n" + report += f"- 段落: {chapter['paragraphs']} 段\n" + report += f"- 对话行: {chapter['dialogue_lines']} 行\n" + report += f"- 对话占比: {chapter['dialogue_ratio']:.1%}\n" + + # 保存报告 + if output_path: + os.makedirs(os.path.dirname(output_path), exist_ok=True) + with open(output_path, 'w', encoding='utf-8') as f: + f.write(report) + + print(f"报告已保存: {output_path}") + + return report + +def main(): + """主函数""" + import argparse + + parser = argparse.ArgumentParser(description="小说字数统计工具") + parser.add_argument("--project", help="项目路径", default=".") + parser.add_argument("--output", help="输出报告路径") + + args = parser.parse_args() + + counter = WordCounter() + report = counter.generate_report(args.project, args.output) + + if report: + print(report) + +if __name__ == "__main__": + main() diff --git a/create_novel_project.sh b/create_novel_project.sh new file mode 100755 index 0000000..dcaad79 --- /dev/null +++ b/create_novel_project.sh @@ -0,0 +1,205 @@ +#!/bin/bash +# 小说项目创建工具 +# 用法: ./create_novel_project.sh 项目名称 "小说名称" 类型 + +if [ $# -lt 3 ]; then + echo "用法: $0 项目目录名 小说名称 类型" + echo "示例: $0 末日重生_囤货 \"末日重生-开局囤货十亿物资\" 末世重生" + exit 1 +fi + +PROJECT_DIR="$1" +NOVEL_NAME="$2" +NOVEL_TYPE="$3" +PROJECTS_ROOT="/root/.openclaw/workspace/projects" +TARGET_DIR="$PROJECTS_ROOT/$PROJECT_DIR" + +# 检查是否已存在 +if [ -d "$TARGET_DIR" ]; then + echo "错误: 项目目录 '$PROJECT_DIR' 已存在" + exit 1 +fi + +# 创建项目目录 +echo "创建项目: $NOVEL_NAME" +mkdir -p "$TARGET_DIR" +cd "$TARGET_DIR" + +# 初始化Git仓库 +git init +git config user.name "唐天洛" +git config user.email "563415956@qq.com" + +# 创建基础目录结构 +mkdir -p chapters outlines assets/characters assets/settings assets/plots tools sync progress + +# 创建PROJECT.md +cat > PROJECT.md << EOF +# 《$NOVEL_NAME》项目 + +## 项目信息 +- **书名**: $NOVEL_NAME +- **类型**: $NOVEL_TYPE +- **状态**: 新项目 +- **目标平台**: 番茄小说 +- **作者**: 唐天洛 +- **创建时间**: $(date '+%Y年%m月%d日') + +## 项目结构 +\`\`\` +$PROJECT_DIR/ +├── PROJECT.md # 项目说明(本文件) +├── README.md # 项目详细介绍 +├── chapters/ # 章节文件 +├── outlines/ # 大纲和规划 +├── assets/ # 资源文件 +│ ├── characters/ # 人物资料 +│ ├── settings/ # 世界观设定 +│ └── plots/ # 情节设计 +├── tools/ # 项目工具 +├── sync/ # 同步配置 +└── progress/ # 进度跟踪 +\`\`\` + +## 当前进度 +- **章节**: 0章 +- **字数**: 0字 +- **状态**: 规划阶段 + +## 平台要求 +- **番茄黄金三章**: 待完成 +- **章节结构**: 承接→推进→高潮→钩子 +- **字数要求**: 每章2500-3500字 +- **更新要求**: 日更4000字(全勤奖最低要求) + +## 创作计划 +1. **第一阶段**: 人物设定和世界观构建 +2. **第二阶段**: 大纲规划和爽点设计 +3. **第三阶段**: 黄金三章创作 +4. **第四阶段**: 持续更新 +EOF + +# 创建README.md +cat > README.md << EOF +# 《$NOVEL_NAME》 + +## 项目简介 +$NOVEL_TYPE题材小说,$NOVEL_NAME + +## 项目状态 +- 🟡 规划阶段 +- 📅 创建时间: $(date '+%Y-%m-%d') +- ✍️ 作者: 唐天洛 + +## 快速开始 +1. 在 \`outlines/\` 目录下规划大纲 +2. 在 \`assets/\` 目录下创建人物和设定 +3. 在 \`chapters/\` 目录下创作章节 +4. 使用 \`tools/\` 中的工具进行管理 + +## 目录说明 +- \`chapters/\` - 章节文件 (.md格式) +- \`outlines/\` - 大纲文件 +- \`assets/characters/\` - 人物设定 +- \`assets/settings/\` - 世界观设定 +- \`assets/plots/\` - 情节设计 +- \`tools/\` - 管理工具 +- \`sync/\` - 同步配置 +- \`progress/\` - 进度跟踪 + +## 更新日志 +- $(date '+%Y-%m-%d'): 项目创建 +EOF + +# 创建.gitignore +cat > .gitignore << EOF +# 临时文件 +*.tmp +*.log +*.bak + +# 系统文件 +.DS_Store +Thumbs.db + +# 编辑器文件 +.vscode/ +.idea/ +*.swp + +# 配置文件(包含敏感信息) +config.json +*.key +*.pem +.env + +# 大文件 +*.zip +*.rar +*.7z +EOF + +# 创建基础工具 +cat > tools/章节统计.sh << 'EOF' +#!/bin/bash +# 章节统计工具 + +echo "=== 章节统计 ===" +echo "项目: 《'$NOVEL_NAME'》" +echo "统计时间: $(date '+%Y-%m-%d %H:%M:%S')" +echo "" + +# 统计章节数量 +if [ -d "chapters" ]; then + CHAPTER_COUNT=$(find chapters -name "*.md" | wc -l) + echo "章节数量: $CHAPTER_COUNT" + + # 统计总字数 + TOTAL_WORDS=0 + for file in chapters/*.md; do + if [ -f "$file" ]; then + WORDS=$(wc -w < "$file" 2>/dev/null || echo 0) + TOTAL_WORDS=$((TOTAL_WORDS + WORDS)) + fi + done + echo "总字数: $TOTAL_WORDS" + + # 显示最近章节 + echo "最近章节:" + find chapters -name "*.md" -exec ls -lt {} + | head -5 | awk '{print $9}' +else + echo "chapters目录不存在" +fi + +echo "" +echo "=== 统计完成 ===" +EOF + +chmod +x tools/章节统计.sh + +# 创建进度跟踪文件 +cat > progress/更新日志.md << EOF +# 更新日志 + +## $(date '+%Y年%m月') +### $(date '+%Y-%m-%d') +- [x] 项目创建 +- [ ] 人物设定完成 +- [ ] 大纲规划完成 +- [ ] 第一章完成 +EOF + +# 初始化提交 +git add . +git commit -m "项目初始化: $NOVEL_NAME" + +echo "" +echo "✅ 项目创建完成: $TARGET_DIR" +echo "📖 小说: $NOVEL_NAME" +echo "📁 目录结构已创建" +echo "🔧 Git仓库已初始化" +echo "" +echo "下一步:" +echo "1. 在 outlines/ 目录下规划大纲" +echo "2. 在 assets/ 目录下创建人物设定" +echo "3. 开始创作第一章" \ No newline at end of file diff --git a/generators/chapter_generator.py b/generators/chapter_generator.py new file mode 100755 index 0000000..4f6b095 --- /dev/null +++ b/generators/chapter_generator.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +""" +章节生成器 +根据模板生成标准化的章节结构 +""" + +import os +import re +from datetime import datetime + +class ChapterGenerator: + def __init__(self, template_path=None): + """初始化生成器""" + self.template = self.load_template(template_path) or self.default_template() + + def load_template(self, template_path): + """加载模板文件""" + if template_path and os.path.exists(template_path): + with open(template_path, 'r', encoding='utf-8') as f: + return f.read() + return None + + def default_template(self): + """默认模板(番茄黄金三章模板)""" + return """# 第{chapter_number}章:{chapter_title} + +## 承接(100-300字) +{setup} + +## 推进(800-2000字) +{development} + +## 高潮(500-1000字) +{climax} + +## 钩子(100-300字) +{hook} + +--- + +**字数统计**: {word_count}字 +**更新时间**: {update_time} +**作者备注**: {note} +""" + + def generate(self, chapter_number, chapter_title, setup, development, climax, hook, note=""): + """生成章节内容""" + # 计算字数 + word_count = len(setup + development + climax + hook) + + # 填充模板 + content = self.template.format( + chapter_number=chapter_number, + chapter_title=chapter_title, + setup=setup, + development=development, + climax=climax, + hook=hook, + word_count=word_count, + update_time=datetime.now().strftime('%Y-%m-%d %H:%M'), + note=note or "正常更新" + ) + + return content + + def save_chapter(self, project_path, chapter_number, chapter_title, content, **kwargs): + """保存章节文件""" + # 创建章节目录 + chapters_dir = os.path.join(project_path, "chapters") + os.makedirs(chapters_dir, exist_ok=True) + + # 生成文件名 + filename = f"ch{chapter_number:02d}-{chapter_title}.md" + filepath = os.path.join(chapters_dir, filename) + + # 保存文件 + with open(filepath, 'w', encoding='utf-8') as f: + f.write(content) + + print(f"章节已保存: {filepath}") + return filepath + + def generate_from_outline(self, project_path, outline_file): + """根据大纲文件生成章节""" + if not os.path.exists(outline_file): + print(f"大纲文件不存在: {outline_file}") + return [] + + # 读取大纲 + with open(outline_file, 'r', encoding='utf-8') as f: + outline_content = f.read() + + # 解析大纲中的章节信息 + # 这里假设大纲有特定的格式 + chapters = [] + + # 简单示例:解析 ## 第X章:标题 格式 + chapter_pattern = r'##\s*第(\d+)章[::]\s*(.+?)\n(.*?)(?=##\s*第|\Z)' + matches = re.findall(chapter_pattern, outline_content, re.DOTALL) + + for match in matches: + chapter_num = int(match[0]) + chapter_title = match[1].strip() + chapter_content = match[2].strip() + + # 从内容中提取各个部分(简单实现) + sections = self.parse_chapter_content(chapter_content) + + # 生成章节 + content = self.generate( + chapter_number=chapter_num, + chapter_title=chapter_title, + setup=sections.get('承接', ''), + development=sections.get('推进', ''), + climax=sections.get('高潮', ''), + hook=sections.get('钩子', ''), + note="从大纲生成" + ) + + # 保存章节 + filepath = self.save_chapter(project_path, chapter_num, chapter_title, content) + chapters.append(filepath) + + return chapters + + def parse_chapter_content(self, content): + """解析章节内容为各个部分""" + sections = {} + + # 简单按行分割 + current_section = None + section_content = [] + + for line in content.split('\n'): + line = line.strip() + + # 检查是否是新的章节部分 + if line.startswith('###'): + if current_section and section_content: + sections[current_section] = '\n'.join(section_content).strip() + + # 提取章节标题 + section_title = line.replace('###', '').strip() + if '承接' in section_title: + current_section = '承接' + elif '推进' in section_title: + current_section = '推进' + elif '高潮' in section_title: + current_section = '高潮' + elif '钩子' in section_title: + current_section = '钩子' + else: + current_section = None + + section_content = [] + elif current_section and line: + section_content.append(line) + + # 处理最后一个部分 + if current_section and section_content: + sections[current_section] = '\n'.join(section_content).strip() + + return sections + +def main(): + """主函数""" + import argparse + + parser = argparse.ArgumentParser(description="章节生成器") + parser.add_argument("--project", help="项目路径", default=".") + parser.add_argument("--number", type=int, help="章节编号", required=True) + parser.add_argument("--title", help="章节标题", required=True) + parser.add_argument("--setup", help="承接部分", default="") + parser.add_argument("--development", help="推进部分", default="") + parser.add_argument("--climax", help="高潮部分", default="") + parser.add_argument("--hook", help="钩子部分", default="") + parser.add_argument("--note", help="作者备注", default="") + parser.add_argument("--outline", help="从大纲文件生成") + + args = parser.parse_args() + + generator = ChapterGenerator() + + if args.outline: + # 从大纲生成 + chapters = generator.generate_from_outline(args.project, args.outline) + print(f"从大纲生成了 {len(chapters)} 个章节") + else: + # 直接生成 + content = generator.generate( + chapter_number=args.number, + chapter_title=args.title, + setup=args.setup or "承接内容待补充", + development=args.development or "推进内容待补充", + climax=args.climax or "高潮内容待补充", + hook=args.hook or "钩子内容待补充", + note=args.note + ) + + generator.save_chapter(args.project, args.number, args.title, content) + print("章节生成完成") + +if __name__ == "__main__": + main() diff --git a/sync_tools/feishu_sync.py b/sync_tools/feishu_sync.py new file mode 100755 index 0000000..3a76289 --- /dev/null +++ b/sync_tools/feishu_sync.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +""" +飞书小说同步工具 +将本地章节同步到飞书文档 +""" + +import os +import json +import time +from datetime import datetime +import requests + +class FeishuNovelSync: + def __init__(self, config_path="configs/feishu_config.json"): + """初始化同步工具""" + self.config = self.load_config(config_path) + self.base_url = "https://open.feishu.cn/open-apis" + self.headers = { + "Authorization": f"Bearer {self.config.get('access_token')}", + "Content-Type": "application/json" + } + + def load_config(self, config_path): + """加载配置文件""" + if os.path.exists(config_path): + with open(config_path, 'r', encoding='utf-8') as f: + return json.load(f) + else: + # 默认配置 + return { + "app_id": "", + "app_secret": "", + "access_token": "", + "refresh_token": "", + "expire_time": 0 + } + + def save_config(self, config_path="configs/feishu_config.json"): + """保存配置文件""" + os.makedirs(os.path.dirname(config_path), exist_ok=True) + with open(config_path, 'w', encoding='utf-8') as f: + json.dump(self.config, f, ensure_ascii=False, indent=2) + + def get_access_token(self): + """获取访问令牌""" + if self.config.get('expire_time', 0) > time.time(): + return self.config.get('access_token') + + url = f"{self.base_url}/auth/v3/tenant_access_token/internal" + data = { + "app_id": self.config['app_id'], + "app_secret": self.config['app_secret'] + } + + try: + response = requests.post(url, json=data) + result = response.json() + + if result.get('code') == 0: + self.config['access_token'] = result['tenant_access_token'] + self.config['expire_time'] = time.time() + result['expire'] - 60 + self.save_config() + return self.config['access_token'] + else: + print(f"获取token失败: {result}") + return None + except Exception as e: + print(f"请求失败: {e}") + return None + + def create_doc(self, title, folder_token=None): + """创建飞书文档""" + token = self.get_access_token() + if not token: + return None + + url = f"{self.base_url}/drive/v1/files/create" + headers = self.headers.copy() + headers["Authorization"] = f"Bearer {token}" + + data = { + "title": title, + "type": "doc", + "folder_token": folder_token or "" + } + + try: + response = requests.post(url, headers=headers, json=data) + result = response.json() + + if result.get('code') == 0: + doc_token = result['data']['token'] + print(f"文档创建成功: {title} (token: {doc_token})") + return doc_token + else: + print(f"创建文档失败: {result}") + return None + except Exception as e: + print(f"创建文档请求失败: {e}") + return None + + def update_doc(self, doc_token, content): + """更新文档内容""" + token = self.get_access_token() + if not token: + return False + + url = f"{self.base_url}/drive/v1/files/{doc_token}/content" + headers = self.headers.copy() + headers["Authorization"] = f"Bearer {token}" + + # 构建文档内容 + doc_content = { + "title": "小说章节", + "body": { + "blocks": [ + { + "type": "paragraph", + "paragraph": { + "elements": [ + { + "type": "text", + "text": content + } + ] + } + } + ] + } + } + + try: + response = requests.put(url, headers=headers, json=doc_content) + result = response.json() + + if result.get('code') == 0: + print(f"文档更新成功: {doc_token}") + return True + else: + print(f"更新文档失败: {result}") + return False + except Exception as e: + print(f"更新文档请求失败: {e}") + return False + + def sync_chapter(self, chapter_path, doc_token=None, title=None): + """同步单个章节""" + if not os.path.exists(chapter_path): + print(f"章节文件不存在: {chapter_path}") + return None + + # 读取章节内容 + with open(chapter_path, 'r', encoding='utf-8') as f: + content = f.read() + + # 获取章节标题 + if not title: + title = os.path.basename(chapter_path).replace('.md', '') + + # 如果没有文档token,先创建文档 + if not doc_token: + doc_token = self.create_doc(title) + if not doc_token: + return None + + # 更新文档内容 + if self.update_doc(doc_token, content): + return doc_token + else: + return None + + def sync_project(self, project_path, folder_token=None): + """同步整个项目""" + if not os.path.exists(project_path): + print(f"项目路径不存在: {project_path}") + return False + + chapters_dir = os.path.join(project_path, "chapters") + if not os.path.exists(chapters_dir): + print(f"章节目录不存在: {chapters_dir}") + return False + + # 获取所有章节文件 + chapter_files = [] + for root, dirs, files in os.walk(chapters_dir): + for file in files: + if file.endswith('.md'): + chapter_files.append(os.path.join(root, file)) + + if not chapter_files: + print("没有找到章节文件") + return False + + print(f"找到 {len(chapter_files)} 个章节文件") + + # 按文件名排序 + chapter_files.sort() + + # 同步每个章节 + results = [] + for i, chapter_file in enumerate(chapter_files, 1): + print(f"[{i}/{len(chapter_files)}] 同步: {os.path.basename(chapter_file)}") + + chapter_title = os.path.basename(chapter_file).replace('.md', '') + doc_token = self.sync_chapter(chapter_file, None, chapter_title) + + if doc_token: + results.append({ + "file": chapter_file, + "title": chapter_title, + "doc_token": doc_token, + "status": "success" + }) + print(f" 成功: https://example.feishu.cn/docx/{doc_token}") + else: + results.append({ + "file": chapter_file, + "title": chapter_title, + "doc_token": None, + "status": "failed" + }) + print(f" 失败") + + # 避免请求过快 + time.sleep(1) + + # 保存同步结果 + sync_log = { + "project": project_path, + "sync_time": datetime.now().isoformat(), + "total_chapters": len(chapter_files), + "success": len([r for r in results if r['status'] == 'success']), + "failed": len([r for r in results if r['status'] == 'failed']), + "details": results + } + + log_file = os.path.join(project_path, "sync", "sync_log.json") + os.makedirs(os.path.dirname(log_file), exist_ok=True) + + with open(log_file, 'w', encoding='utf-8') as f: + json.dump(sync_log, f, ensure_ascii=False, indent=2) + + print(f"\n同步完成:") + print(f" 总章节: {len(chapter_files)}") + print(f" 成功: {sync_log['success']}") + print(f" 失败: {sync_log['failed']}") + print(f" 日志: {log_file}") + + return sync_log['success'] > 0 + +def main(): + """主函数""" + import argparse + + parser = argparse.ArgumentParser(description="飞书小说同步工具") + parser.add_argument("--project", help="项目路径", default=".") + parser.add_argument("--config", help="配置文件路径", default="configs/feishu_config.json") + parser.add_argument("--chapter", help="单个章节文件路径") + parser.add_argument("--init", action="store_true", help="初始化配置") + + args = parser.parse_args() + + sync_tool = FeishuNovelSync(args.config) + + if args.init: + # 初始化配置 + app_id = input("请输入 App ID: ") + app_secret = input("请输入 App Secret: ") + + sync_tool.config.update({ + "app_id": app_id, + "app_secret": app_secret + }) + sync_tool.save_config() + + print("配置已保存") + return + + if args.chapter: + # 同步单个章节 + sync_tool.sync_chapter(args.chapter) + else: + # 同步整个项目 + sync_tool.sync_project(args.project) + +if __name__ == "__main__": + main() diff --git a/templates/番茄黄金三章模板.md b/templates/番茄黄金三章模板.md new file mode 100644 index 0000000..51eb3b7 --- /dev/null +++ b/templates/番茄黄金三章模板.md @@ -0,0 +1,116 @@ +# 番茄黄金三章模板 + +## 核心原则 +1. **第一章300字内出冲突** +2. **第一章出现金手指** +3. **前三章必须有第一个小爽点打脸** + +## 第一章模板 +### 结构 +1. **开篇(50字)**: 吸引注意力,立即进入场景 +2. **冲突(100字)**: 立即出现矛盾或危机 +3. **金手指(100字)**: 展现主角的特殊能力或优势 +4. **初步解决(50字)**: 展现金手指的作用 +5. **钩子(50字)**: 引出下一章的内容 + +### 示例 +``` +# 第一章:重生末日七天前 + +**开篇**:冰冷的触感从指尖传来,苏晨猛地睁开眼睛。 + +**冲突**:手机日期显示:2026年3月30日。七天前。他重生了,回到了末日降临前的七天。 + +**金手指**:【叮!末日生存系统激活!当前任务:在七天内囤积足够物资】 + +**初步解决**:苏晨看着账户里的五十万存款,笑了。这一世,他要活下来。 + +**钩子**:门铃突然响起,快递员送来一个神秘木盒:“苏先生,寄件人说您看到里面的东西就会明白。” +``` + +## 第二章模板 +### 结构 +1. **承接(100字)**: 连接第一章的钩子 +2. **发展(800字)**: 展开金手指,开始行动 +3. **小高潮(300字)**: 第一个小爽点 +4. **新挑战(100字)**: 引出新的问题 +5. **钩子(100字)**: 为第三章铺垫 + +## 第三章模板 +### 结构 +1. **承接(100字)**: 解决第二章的挑战 +2. **推进(600字)**: 主角继续发展 +3. **第一个爽点(400字)**: 第一次打脸,让读者爽 +4. **世界观展开(200字)**: 展现更大的世界 +5. **钩子(100字)**: 引出长期目标 + +## 后续章节模板(第4章起) +### 标准结构 +``` +# 第X章:章节标题 + +## 承接(100-300字) +连接上一章,平稳过渡 + +## 推进(800-2000字) +发展情节,展现人物成长 + +## 高潮(500-1000字) +本章的核心爽点 + +## 钩子(100-300字) +引出下一章的内容 +``` + +## 字数要求 +- **每章**: 2500-3500字 +- **黄金三章**: 每章至少3000字 +- **对话占比**: >30%(适合听书) +- **段落长度**: 每段不超过3行 + +## 爽点设计 +### 常见爽点类型 +1. **重生先知**: 利用未来信息碾压 +2. **系统外挂**: 系统任务和奖励 +3. **家世背景**: 富二代/世家子弟 +4. **实力碾压**: 等级压制,装备碾压 +5. **打脸反派**: 反派挑衅→主角打脸 + +### 爽点频率 +- **第一章**: 至少1个爽点 +- **前三章**: 至少3个爽点 +- **后续每章**: 至少1-2个爽点 + +## 对话技巧 +### 听书优化 +1. **多对话**: 对话占比30-50% +2. **短对话**: 每句对话不超过2行 +3. **口语化**: 使用自然的口语表达 +4. **情绪化**: 通过对话展现人物情绪 + +### 对话示例 +``` +"就这?"苏鸣歪头,"我还以为多厉害呢。" + +"你……你到底是什么境界?"林浩脸色惨白。 + +"比你高一点点。"苏鸣微笑,"大概高一个大境界吧。" +``` + +## 避坑指南 +1. **不要直白写设定**: 通过对话和行动展现 +2. **不要过多心理描写**: 减少内心独白 +3. **不要拖沓**: 节奏要快,300字内必出进展 +4. **不要降智反派**: 反派要有合理动机 + +## 变现优化 +### 全勤奖要求 +- **日更4000字**: 600元 + 5%分成 +- **日更6000字**: 800元 + 5%分成 +- **月更10万字**: 听读≥500 + +### 听书分成 +- **对话占比高**: 提升听书体验 +- **节奏明快**: 减少听书疲劳 +- **口语化**: 适合语音朗读 +- **章节分明**: 每章完整故事