This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
openclaw-skills/skills/obsidian-kb/src/storage.py
T
OpenClaw Agent f26d4d8f7f feat: implement obsidian-kb skill with long video chunking
- Multi-source content aggregation (Bilibili/YouTube/Zhihu)
- Smart inbox management with AI quality scoring
- Long video chunking analysis (30min+ auto-segmentation)
- Topic-based organization
- Git auto-sync to Obsidian
- Template system with Jinja2 and fallback rendering
- CLI commands: subscribe, inbox, process, skip, status, search
- Comprehensive test suite with unit and integration tests
- Documentation: README.md and SKILL.md

Checkpoint 7-8 completed by Agent E
2026-02-06 18:15:30 +08:00

169 lines
5.9 KiB
Python

"""Obsidian KB - 存储管理"""
import os
import json
from pathlib import Path
from typing import List, Optional, Dict, Any
from datetime import datetime
from models import Content, ContentStatus
from config import get_config
class Storage:
"""JSON文件存储管理"""
def __init__(self, base_path: Optional[str] = None):
config = get_config()
self.base_path = base_path or config.obsidian_path
self.inbox_path = os.path.join(self.base_path, 'Inbox')
self.topics_path = os.path.join(self.base_path, 'Topics')
self._ensure_dirs()
def _ensure_dirs(self) -> None:
"""确保目录存在"""
os.makedirs(self.inbox_path, exist_ok=True)
os.makedirs(self.topics_path, exist_ok=True)
def _content_file_path(self, content_id: str) -> str:
"""获取内容文件路径"""
return os.path.join(self.inbox_path, f'{content_id}.json')
def _topic_index_path(self, topic: str) -> str:
"""获取主题索引路径"""
topic_dir = os.path.join(self.topics_path, topic)
os.makedirs(topic_dir, exist_ok=True)
return os.path.join(topic_dir, 'index.json')
# ===== Inbox 管理 =====
def save_content(self, content: Content) -> None:
"""保存内容到Inbox"""
file_path = self._content_file_path(content.id)
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(content.to_dict(), f, ensure_ascii=False, indent=2)
def load_content(self, content_id: str) -> Optional[Content]:
"""加载指定内容"""
file_path = self._content_file_path(content_id)
if not os.path.exists(file_path):
return None
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return Content.from_dict(data)
def delete_content(self, content_id: str) -> bool:
"""删除内容"""
file_path = self._content_file_path(content_id)
if os.path.exists(file_path):
os.remove(file_path)
return True
return False
def list_inbox(self, status: Optional[ContentStatus] = None) -> List[Content]:
"""列出Inbox中的内容"""
contents = []
if not os.path.exists(self.inbox_path):
return contents
for filename in os.listdir(self.inbox_path):
if filename.endswith('.json'):
file_path = os.path.join(self.inbox_path, filename)
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
content = Content.from_dict(data)
if status is None or content.status == status:
contents.append(content)
# 按创建时间排序
contents.sort(key=lambda x: x.created_at, reverse=True)
return contents
def get_pending_count(self) -> int:
"""获取待处理内容数量"""
return len(self.list_inbox(ContentStatus.CONFIRMED))
def get_stats(self) -> Dict[str, int]:
"""获取统计信息"""
all_contents = self.list_inbox()
stats = {status.value: 0 for status in ContentStatus}
for content in all_contents:
stats[content.status.value] += 1
return stats
# ===== Topics 管理 =====
def add_to_topic(self, content: Content, topic: str) -> None:
"""将内容添加到主题"""
index_path = self._topic_index_path(topic)
# 读取现有索引
entries = []
if os.path.exists(index_path):
with open(index_path, 'r', encoding='utf-8') as f:
entries = json.load(f)
# 添加新条目
entry = {
'id': content.id,
'title': content.title,
'url': content.url,
'added_at': datetime.now().isoformat(),
'output_path': content.output_path
}
# 避免重复
entries = [e for e in entries if e['id'] != content.id]
entries.append(entry)
# 保存索引
with open(index_path, 'w', encoding='utf-8') as f:
json.dump(entries, f, ensure_ascii=False, indent=2)
def get_topic_contents(self, topic: str) -> List[Dict[str, Any]]:
"""获取主题下的所有内容"""
index_path = self._topic_index_path(topic)
if not os.path.exists(index_path):
return []
with open(index_path, 'r', encoding='utf-8') as f:
return json.load(f)
def list_topics(self) -> List[str]:
"""列出所有主题"""
if not os.path.exists(self.topics_path):
return []
return [d for d in os.listdir(self.topics_path)
if os.path.isdir(os.path.join(self.topics_path, d))]
def search(self, query: str) -> List[Content]:
"""搜索内容"""
results = []
all_contents = self.list_inbox()
query_lower = query.lower()
for content in all_contents:
if (query_lower in content.title.lower() or
query_lower in content.summary.lower() or
query_lower in content.author.lower() or
any(query_lower in tag.lower() for tag in content.tags)):
results.append(content)
return results
def move_to_archive(self, content_id: str) -> bool:
"""将内容移动到归档"""
content = self.load_content(content_id)
if not content:
return False
archive_path = os.path.join(self.base_path, 'Archive')
os.makedirs(archive_path, exist_ok=True)
source_file = self._content_file_path(content_id)
target_file = os.path.join(archive_path, f'{content_id}.json')
if os.path.exists(source_file):
os.rename(source_file, target_file)
return True
return False