feat: 重构项目结构并添加平台同步基础架构
- 重构项目目录结构,将功能模块移至 modules/ 目录 - 创建平台同步基础架构,包括发布器基类和 GitHub 发布器 - 新增 UI 状态管理模块 (modules/ui/state.py) 统一管理会话状态 - 更新依赖配置,添加平台同步所需依赖 (httpx, pyperclip) - 整理文档结构,将所有文档分类移至 docs/ 目录 - 添加 .cursorrules 文件定义项目开发规范 - 清理根目录重复文件,保持项目结构整洁
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
"""
|
||||
平台同步模块
|
||||
支持多平台文章发布功能
|
||||
"""
|
||||
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
平台发布器基类
|
||||
"""
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
class BasePublisher(ABC):
|
||||
"""发布器基类"""
|
||||
|
||||
def __init__(self, platform: str, account_config: Dict[str, Any]):
|
||||
self.platform = platform
|
||||
self.account_config = account_config
|
||||
|
||||
@abstractmethod
|
||||
def publish(self, content: str, title: str, **kwargs) -> Dict[str, Any]:
|
||||
"""
|
||||
发布内容
|
||||
|
||||
Args:
|
||||
content: 文章内容
|
||||
title: 文章标题
|
||||
**kwargs: 其他参数(标签、图片等)
|
||||
|
||||
Returns:
|
||||
{
|
||||
'success': bool,
|
||||
'publish_url': str,
|
||||
'publish_id': str,
|
||||
'error': str
|
||||
}
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def validate_account(self) -> bool:
|
||||
"""验证账号是否有效"""
|
||||
pass
|
||||
|
||||
def upload_image(self, image_path: str) -> Optional[str]:
|
||||
"""上传图片,返回图片URL(可选实现)"""
|
||||
return None
|
||||
|
||||
def refresh_token_if_needed(self) -> bool:
|
||||
"""刷新token(如果需要,可选实现)"""
|
||||
return True
|
||||
@@ -0,0 +1,337 @@
|
||||
"""
|
||||
一键复制管理器 - 用于无API平台的内容格式化
|
||||
"""
|
||||
import pyperclip
|
||||
from typing import Dict, Any, Optional
|
||||
import re
|
||||
|
||||
|
||||
class CopyManager:
|
||||
"""一键复制管理器"""
|
||||
|
||||
def __init__(self):
|
||||
self.templates = self._init_templates()
|
||||
|
||||
def _init_templates(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""初始化平台格式模板"""
|
||||
return {
|
||||
"头条号(资讯软文)": {
|
||||
"format": "title_content",
|
||||
"max_length": 2000,
|
||||
"supports_tags": True
|
||||
},
|
||||
"小红书(生活种草)": {
|
||||
"format": "title_content_tags",
|
||||
"max_length": 1000,
|
||||
"supports_tags": True,
|
||||
"supports_images": True
|
||||
},
|
||||
"抖音图文(短内容)": {
|
||||
"format": "title_content_tags",
|
||||
"max_length": 500,
|
||||
"supports_tags": True,
|
||||
"supports_images": True
|
||||
},
|
||||
"简书(文艺)": {
|
||||
"format": "title_content",
|
||||
"max_length": 3000,
|
||||
"supports_tags": True
|
||||
},
|
||||
"QQ空间(社交)": {
|
||||
"format": "title_content",
|
||||
"max_length": 1500,
|
||||
"supports_tags": True,
|
||||
"supports_images": True
|
||||
},
|
||||
"新浪博客(博客)": {
|
||||
"format": "title_content",
|
||||
"max_length": 3000,
|
||||
"supports_tags": True
|
||||
},
|
||||
"新浪新闻(资讯)": {
|
||||
"format": "title_content",
|
||||
"max_length": 2000,
|
||||
"supports_tags": False
|
||||
},
|
||||
"搜狐号(资讯)": {
|
||||
"format": "title_content",
|
||||
"max_length": 2500,
|
||||
"supports_tags": True
|
||||
},
|
||||
"一点号(资讯)": {
|
||||
"format": "title_content",
|
||||
"max_length": 2500,
|
||||
"supports_tags": True
|
||||
},
|
||||
"东方财富(财经)": {
|
||||
"format": "title_content",
|
||||
"max_length": 3000,
|
||||
"supports_tags": False
|
||||
},
|
||||
"邦阅网(外贸)": {
|
||||
"format": "title_content",
|
||||
"max_length": 2500,
|
||||
"supports_tags": True
|
||||
},
|
||||
"原创力文档(文档)": {
|
||||
"format": "title_content",
|
||||
"max_length": 5000,
|
||||
"supports_tags": False
|
||||
}
|
||||
}
|
||||
|
||||
def format_for_platform(self, platform: str, content: str, title: str,
|
||||
keyword: str = "", brand: str = "", **kwargs) -> str:
|
||||
"""
|
||||
为平台格式化内容
|
||||
|
||||
Args:
|
||||
platform: 平台名称
|
||||
content: 原始内容
|
||||
title: 标题
|
||||
keyword: 关键词
|
||||
brand: 品牌
|
||||
**kwargs: 其他参数(标签、摘要等)
|
||||
|
||||
Returns:
|
||||
格式化后的内容
|
||||
"""
|
||||
template_config = self.templates.get(platform, {})
|
||||
format_type = template_config.get("format", "title_content")
|
||||
max_length = template_config.get("max_length", 2000)
|
||||
|
||||
# 提取标题(如果内容中包含标题)
|
||||
if not title and content:
|
||||
# 尝试从内容第一行提取标题
|
||||
first_line = content.split('\n')[0].strip()
|
||||
if len(first_line) < 100 and not first_line.startswith('#'):
|
||||
title = first_line
|
||||
else:
|
||||
title = keyword or "文章标题"
|
||||
|
||||
# 清理内容
|
||||
formatted_content = self._clean_content(content, platform)
|
||||
|
||||
# 截断内容(如果需要)
|
||||
if max_length and len(formatted_content) > max_length:
|
||||
formatted_content = formatted_content[:max_length] + "..."
|
||||
|
||||
# 根据格式类型格式化
|
||||
if format_type == "title_content":
|
||||
return f"{title}\n\n{formatted_content}"
|
||||
elif format_type == "title_content_tags":
|
||||
tags = kwargs.get('tags', [])
|
||||
tags_str = " ".join([f"#{tag}" for tag in tags[:10]]) if tags else ""
|
||||
return f"{title}\n\n{formatted_content}\n\n{tags_str}"
|
||||
else:
|
||||
return f"{title}\n\n{formatted_content}"
|
||||
|
||||
def _clean_content(self, content: str, platform: str) -> str:
|
||||
"""清理内容,移除平台不支持的格式"""
|
||||
# 移除Markdown图片语法,保留描述
|
||||
content = re.sub(r'!\[([^\]]*)\]\([^\)]+\)', r'【配图:\1】', content)
|
||||
|
||||
# 移除Markdown链接,保留文本
|
||||
content = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', content)
|
||||
|
||||
# 移除Markdown代码块标记,保留内容
|
||||
content = re.sub(r'```[\w]*\n', '', content)
|
||||
content = re.sub(r'```', '', content)
|
||||
|
||||
# 移除Markdown标题标记
|
||||
content = re.sub(r'^#+\s+', '', content, flags=re.MULTILINE)
|
||||
|
||||
# 移除Markdown加粗/斜体
|
||||
content = re.sub(r'\*\*([^\*]+)\*\*', r'\1', content)
|
||||
content = re.sub(r'\*([^\*]+)\*', r'\1', content)
|
||||
content = re.sub(r'__([^_]+)__', r'\1', content)
|
||||
content = re.sub(r'_([^_]+)_', r'\1', content)
|
||||
|
||||
return content.strip()
|
||||
|
||||
def copy_to_clipboard(self, text: str) -> bool:
|
||||
"""复制到剪贴板"""
|
||||
try:
|
||||
pyperclip.copy(text)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"复制失败: {e}")
|
||||
return False
|
||||
|
||||
def generate_publish_guide(self, platform: str) -> str:
|
||||
"""生成发布指南"""
|
||||
guides = {
|
||||
"头条号(资讯软文)": """
|
||||
📝 发布步骤:
|
||||
1. 登录头条号后台(https://mp.toutiao.com/)
|
||||
2. 点击"发布" → "文章"
|
||||
3. 粘贴标题和内容
|
||||
4. 添加封面图和标签
|
||||
5. 选择分类和频道
|
||||
6. 点击"发布"
|
||||
|
||||
💡 提示:
|
||||
- 标题要吸引人,控制在30字以内
|
||||
- 内容建议800-2000字
|
||||
- 配图建议3-5张
|
||||
""",
|
||||
"小红书(生活种草)": """
|
||||
📝 发布步骤:
|
||||
1. 打开小红书APP
|
||||
2. 点击"+"号发布
|
||||
3. 选择"图文"
|
||||
4. 粘贴标题和内容
|
||||
5. 添加图片(建议3-9张)
|
||||
6. 添加标签和话题
|
||||
7. 选择位置(可选)
|
||||
8. 发布
|
||||
|
||||
💡 提示:
|
||||
- 标题要吸引人,控制在20字以内
|
||||
- 内容建议500-1000字
|
||||
- 图片要清晰美观
|
||||
- 标签要相关且热门
|
||||
""",
|
||||
"抖音图文(短内容)": """
|
||||
📝 发布步骤:
|
||||
1. 打开抖音APP
|
||||
2. 点击"+"号发布
|
||||
3. 选择"图文"
|
||||
4. 粘贴标题和内容
|
||||
5. 添加图片(建议3-9张)
|
||||
6. 添加话题标签
|
||||
7. 选择位置(可选)
|
||||
8. 发布
|
||||
|
||||
💡 提示:
|
||||
- 标题要吸引人,控制在30字以内
|
||||
- 内容建议200-500字
|
||||
- 图片要清晰美观
|
||||
- 话题要热门
|
||||
""",
|
||||
"简书(文艺)": """
|
||||
📝 发布步骤:
|
||||
1. 登录简书(https://www.jianshu.com/)
|
||||
2. 点击"写文章"
|
||||
3. 粘贴标题和内容
|
||||
4. 添加标签
|
||||
5. 选择专题(可选)
|
||||
6. 点击"发布"
|
||||
|
||||
💡 提示:
|
||||
- 标题要有文艺范
|
||||
- 内容建议1500-3000字
|
||||
- 标签要相关
|
||||
""",
|
||||
"QQ空间(社交)": """
|
||||
📝 发布步骤:
|
||||
1. 打开QQ空间
|
||||
2. 点击"说说"或"日志"
|
||||
3. 粘贴标题和内容
|
||||
4. 添加图片(可选)
|
||||
5. 选择可见范围
|
||||
6. 发布
|
||||
|
||||
💡 提示:
|
||||
- 内容要轻松活泼
|
||||
- 建议500-1500字
|
||||
- 配图要生活化
|
||||
""",
|
||||
"新浪博客(博客)": """
|
||||
📝 发布步骤:
|
||||
1. 登录新浪博客(https://blog.sina.com.cn/)
|
||||
2. 点击"发博文"
|
||||
3. 粘贴标题和内容
|
||||
4. 添加标签
|
||||
5. 选择分类
|
||||
6. 点击"发布"
|
||||
|
||||
💡 提示:
|
||||
- 内容要有深度
|
||||
- 建议1500-3000字
|
||||
- 配图要相关
|
||||
""",
|
||||
"新浪新闻(资讯)": """
|
||||
📝 发布步骤:
|
||||
1. 登录新浪新闻后台
|
||||
2. 点击"发布文章"
|
||||
3. 粘贴标题和内容
|
||||
4. 添加配图
|
||||
5. 选择分类
|
||||
6. 提交审核
|
||||
|
||||
💡 提示:
|
||||
- 内容要客观专业
|
||||
- 建议800-2000字
|
||||
- 配图要清晰
|
||||
""",
|
||||
"搜狐号(资讯)": """
|
||||
📝 发布步骤:
|
||||
1. 登录搜狐号(https://mp.sohu.com/)
|
||||
2. 点击"发布" → "文章"
|
||||
3. 粘贴标题和内容
|
||||
4. 添加封面图和标签
|
||||
5. 选择分类
|
||||
6. 点击"发布"
|
||||
|
||||
💡 提示:
|
||||
- 内容要专业
|
||||
- 建议1000-2500字
|
||||
- 配图要相关
|
||||
""",
|
||||
"一点号(资讯)": """
|
||||
📝 发布步骤:
|
||||
1. 登录一点号后台
|
||||
2. 点击"发布文章"
|
||||
3. 粘贴标题和内容
|
||||
4. 添加配图和标签
|
||||
5. 选择分类
|
||||
6. 提交审核
|
||||
|
||||
💡 提示:
|
||||
- 内容要有深度
|
||||
- 建议1000-2500字
|
||||
""",
|
||||
"东方财富(财经)": """
|
||||
📝 发布步骤:
|
||||
1. 登录东方财富后台
|
||||
2. 点击"发布文章"
|
||||
3. 粘贴标题和内容
|
||||
4. 添加配图
|
||||
5. 选择财经分类
|
||||
6. 提交审核
|
||||
|
||||
💡 提示:
|
||||
- 内容要专业准确
|
||||
- 建议1500-3000字
|
||||
- 数据要准确
|
||||
""",
|
||||
"邦阅网(外贸)": """
|
||||
📝 发布步骤:
|
||||
1. 登录邦阅网后台
|
||||
2. 点击"发布文章"
|
||||
3. 粘贴标题和内容
|
||||
4. 添加标签
|
||||
5. 选择外贸分类
|
||||
6. 提交审核
|
||||
|
||||
💡 提示:
|
||||
- 内容要专业实用
|
||||
- 建议1000-2500字
|
||||
""",
|
||||
"原创力文档(文档)": """
|
||||
📝 发布步骤:
|
||||
1. 登录原创力文档(https://www.doc88.com/)
|
||||
2. 点击"上传文档"
|
||||
3. 选择"新建文档"
|
||||
4. 粘贴标题和内容
|
||||
5. 设置文档属性
|
||||
6. 提交审核
|
||||
|
||||
💡 提示:
|
||||
- 内容要结构化
|
||||
- 建议2000-5000字
|
||||
- 格式要规范
|
||||
"""
|
||||
}
|
||||
return guides.get(platform, "请参考平台官方发布指南")
|
||||
@@ -0,0 +1,114 @@
|
||||
"""
|
||||
GitHub发布器 - 最简单的实现示例
|
||||
"""
|
||||
import base64
|
||||
import httpx
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
class GitHubPublisher:
|
||||
"""GitHub发布器"""
|
||||
|
||||
def __init__(self, api_key: str, repo_owner: str, repo_name: str):
|
||||
self.api_key = api_key
|
||||
self.repo_owner = repo_owner
|
||||
self.repo_name = repo_name
|
||||
self.base_url = "https://api.github.com"
|
||||
self.headers = {
|
||||
"Authorization": f"token {api_key}",
|
||||
"Accept": "application/vnd.github.v3+json"
|
||||
}
|
||||
|
||||
def publish(self, content: str, title: str, file_path: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
发布内容到GitHub
|
||||
|
||||
Args:
|
||||
content: Markdown内容
|
||||
title: 文章标题
|
||||
file_path: 文件路径(可选)
|
||||
|
||||
Returns:
|
||||
{
|
||||
'success': bool,
|
||||
'publish_url': str,
|
||||
'publish_id': str,
|
||||
'error': str
|
||||
}
|
||||
"""
|
||||
try:
|
||||
# 生成文件路径
|
||||
if not file_path:
|
||||
safe_title = title.replace(' ', '_').replace('/', '_').replace('\\', '_')
|
||||
safe_title = ''.join(c for c in safe_title if c.isalnum() or c in ('_', '-', '.'))[:50]
|
||||
file_path = f"content/{safe_title}.md"
|
||||
|
||||
# 编码内容
|
||||
content_bytes = content.encode('utf-8')
|
||||
content_base64 = base64.b64encode(content_bytes).decode('utf-8')
|
||||
|
||||
# API URL
|
||||
url = f"{self.base_url}/repos/{self.repo_owner}/{self.repo_name}/contents/{file_path}"
|
||||
|
||||
# 检查文件是否存在
|
||||
response = httpx.get(url, headers=self.headers, timeout=30.0)
|
||||
sha = None
|
||||
if response.status_code == 200:
|
||||
sha = response.json().get('sha')
|
||||
|
||||
# 准备数据
|
||||
data = {
|
||||
"message": f"Publish: {title}",
|
||||
"content": content_base64,
|
||||
"branch": "main"
|
||||
}
|
||||
if sha:
|
||||
data["sha"] = sha
|
||||
|
||||
# 创建或更新文件
|
||||
response = httpx.put(url, json=data, headers=self.headers, timeout=30.0)
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
result = response.json()
|
||||
html_url = result.get('content', {}).get('html_url', '')
|
||||
return {
|
||||
'success': True,
|
||||
'publish_url': html_url,
|
||||
'publish_id': result.get('content', {}).get('sha', ''),
|
||||
'error': None
|
||||
}
|
||||
else:
|
||||
error_text = response.text
|
||||
try:
|
||||
error_json = response.json()
|
||||
error_text = error_json.get('message', error_text)
|
||||
except:
|
||||
pass
|
||||
return {
|
||||
'success': False,
|
||||
'publish_url': '',
|
||||
'publish_id': '',
|
||||
'error': f"GitHub API错误: {error_text}"
|
||||
}
|
||||
except httpx.TimeoutException:
|
||||
return {
|
||||
'success': False,
|
||||
'publish_url': '',
|
||||
'publish_id': '',
|
||||
'error': '请求超时,请稍后重试'
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'publish_url': '',
|
||||
'publish_id': '',
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def validate_account(self) -> bool:
|
||||
"""验证GitHub账号"""
|
||||
try:
|
||||
response = httpx.get(f"{self.base_url}/user", headers=self.headers, timeout=10.0)
|
||||
return response.status_code == 200
|
||||
except:
|
||||
return False
|
||||
Reference in New Issue
Block a user