Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions logs/2026/06/2026-06-13.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# 2026-06-13 · KiriMonoLog

## 今日素材
- **情绪短句**:Brush your teeth the moment you get up.(AdviceSlip)
- **趣味小知识**:Dogs and cats consume over $11 billion worth of pet food a year(djtech.net)
- **治愈文案**:等待,不是为了你能回来,而是找个借口不离开。(九ちのセカィ
- **情绪短句**:Cars are bad investments.(AdviceSlip)
- **趣味小知识**:There are 1,575 steps from the ground floor to the top of the Empire State building.(djtech.net)
- **治愈文案**:我于杀戮之中盛放,亦如黎明中的花朵。(英雄联盟

## 今日心情日志(双语)
<table>
<tr><th>中文原版</th><th>多语言版本</th></tr>
<tr><td>2026-06-13的日记。<br>桐雾(Kiri)在想,幸福大概就是这些细碎的、不完美的日常。试着对镜子里的自己笑了一下,感觉还不错。<br>等待,不是为了你能回来,而是找个借口不离开。</td><td>2026年6月13日の日記。<br>キリは、幸福とはおそらく、これらの美しく不完全な日常のことであると考えています。鏡の中の自分に微笑んでみてください。気持ちいいです。<br>待っていることは、あなたが戻ってくることではなく、去らないという言い訳を見つけることです。</td></tr>
<tr><td>2026-06-13的日记。<br>桐雾(Kiri)在想,幸福大概就是这些细碎的、不完美的日常。试着对镜子里的自己笑了一下,感觉还不错。<br>我于杀戮之中盛放,亦如黎明中的花朵。</td><td>2026年6月13日の日記。<br>キリは、幸福とはおそらく、これらの美しく不完全な日常のことであると考えています。鏡の中の自分に微笑んでみてください。気持ちいいです。<br>私は夜明けの花のように、屠殺の真っ只中に咲く。</td></tr>
</table>

> 本日随机语种:**日本語**
> 本日内容语种:**ZH + JP**
25 changes: 20 additions & 5 deletions scripts/kirimonolog/composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import random
from typing import Dict, List, Tuple

from kirimonolog.config import LANGUAGE_OPTIONS
from kirimonolog.config import LANGUAGE_OPTIONS, LANGUAGE_SHORT_NAMES

# Material 类型别名
Material = Dict[str, str]
Expand All @@ -31,6 +31,20 @@ def choose_target_language() -> Tuple[str, str]:
return code, LANGUAGE_OPTIONS[code]


def build_language_tag(lang_code: str) -> str:
"""生成前端显示用的语言标签。

每日日志包含中文和一种随机语言,标签格式为 "ZH + XX"。

Args:
lang_code: 目标语言 ISO 代码(如 "en"/"ja"/"ko")

Returns:
语言标签字符串,如 "ZH + JP"
"""
return f"ZH + {LANGUAGE_SHORT_NAMES.get(lang_code, lang_code.upper())}"


def _materials_block(materials: List[Material]) -> str:
"""将素材列表渲染为 Markdown 无序列表。

Expand All @@ -54,20 +68,20 @@ def _to_html_lines(text: str) -> str:
return "<br>".join(html.escape(line) for line in text.splitlines() if line.strip())


def render_markdown(date_value: dt.date, materials: List[Material], zh_text: str, lang_name: str, translated_text: str) -> str:
def render_markdown(date_value: dt.date, materials: List[Material], zh_text: str, lang_code: str, translated_text: str) -> str:
"""渲染完整的日志 Markdown 文档。

输出结构:
1. 标题:# YYYY-MM-DD · KiriMonoLog
2. 今日素材:无序列表
3. 今日心情日志(双语):HTML 表格,左栏中文,右栏翻译
4. 随机语种标注
4. 内容语种标注(格式:ZH + XX)

Args:
date_value : 日志日期
materials : 当日素材列表
zh_text : 中文日记正文
lang_name : 目标语言名称(用于标注
lang_code : 目标语言 ISO 代码(如 "en"/"ja"/"ko"
translated_text: 翻译后的文本

Returns:
Expand All @@ -77,6 +91,7 @@ def render_markdown(date_value: dt.date, materials: List[Material], zh_text: str
# 将纯文本转为 HTML 行,适配表格单元格内的多行排版
zh_html = _to_html_lines(zh_text)
translated_html = _to_html_lines(translated_text)
lang_tag = build_language_tag(lang_code)

return (
f"# {date_str} · KiriMonoLog\n\n"
Expand All @@ -87,5 +102,5 @@ def render_markdown(date_value: dt.date, materials: List[Material], zh_text: str
" <tr><th>中文原版</th><th>多语言版本</th></tr>\n"
f" <tr><td>{zh_html}</td><td>{translated_html}</td></tr>\n"
"</table>\n\n"
f"> 本日随机语种:**{lang_name}**\n"
f"> 本日内容语种:**{lang_tag}**\n"
)
25 changes: 25 additions & 0 deletions scripts/kirimonolog/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,31 @@
"ko": "한국어",
}

# 语言短名称映射(用于前端标签显示)
# 每日日志内容包含中文和一种随机语言,标签格式为 "ZH + XX"
LANGUAGE_SHORT_NAMES = {
"en": "EN",
"ja": "JP",
"ko": "KO",
"zh": "ZH",
}

# 旧版语言名称映射(兼容旧日志格式)
# 旧日志使用语言全称(如 "日本語"、"English"),需转为短名称
LEGACY_LANGUAGE_NAMES = {
"English": "EN",
"日本語": "JP",
"한국어": "KO",
}

# 短名称到语言全称映射(用于前端显示)
SHORT_TO_FULL_NAME = {
"EN": "English",
"JP": "日本語",
"KO": "한국어",
"ZH": "中文",
}

# ── 3. AI 接口配置(Pollinations) ──
# Pollinations.ai 提供免费的文本生成 API,无需注册和 API Key。
# POLLINATIONS_TEXT_API : API 基础 URL
Expand Down
138 changes: 130 additions & 8 deletions scripts/kirimonolog/web_exporter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
"""网页日志数据导出模块。"""
"""网页日志数据导出模块。

本模块负责扫描 logs/ 目录下的日志文件,提取关键信息并导出为 web/logs.json,
供前端主页渲染使用。

数据流:
logs/YYYY/MM/YYYY-MM-DD.md → _parse_log() → web/logs.json

提取字段:
- date: 日志日期(YYYY-MM-DD)
- log_path: 相对路径(用于链接跳转)
- mood_record: 情绪短句素材
- zh_diary: 中文日记正文
- translated_diary: 翻译后的日记内容
- language_tag: 内容语种标签(格式:ZH + XX)
"""

from __future__ import annotations

Expand All @@ -9,46 +24,153 @@
from pathlib import Path
from typing import Any

from kirimonolog.config import LEGACY_LANGUAGE_NAMES

# ── 正则表达式 ──

# 匹配双栏表格,提取中文原版和多语言版本两个单元格
# 使用非贪婪匹配 (.*?) 确保正确提取每个 <td> 的内容
ZH_TABLE_PATTERN = (
r"## 今日心情日志(双语)\s*<table>.*?<tr><th>中文原版</th><th>多语言版本</th></tr>\s*<tr><td>(.*?)</td><td>"
r"## 今日心情日志(双语)\s*<table>.*?<tr><th>中文原版</th><th>多语言版本</th></tr>\s*"
r"<tr><td>(.*?)</td><td>(.*?)</td></tr>"
)
# 日志素材可能出现全角(中文)或半角(英文)括号,统一兼容两种结束符。

# 匹配情绪短句素材,兼容全角(中文)和半角(英文)括号
MOOD_PATTERN = r"- \*\*情绪短句\*\*:(.+?)(?:(|\(|$)"

# 新版语言标签格式:提取 "ZH + XX" 中的语言部分
LANGUAGE_TAG_PATTERN = r"> 本日内容语种:\*\*(.+?)\*\*"

# 旧版语言标签格式:提取语言名称(如 "日本語"、"English")
LEGACY_LANGUAGE_PATTERN = r"> 本日随机语种:\*\*(.+?)\*\*"


def _extract(pattern: str, content: str) -> str:
def _extract(pattern: str, content: str) -> str | tuple[str, str, ...]:
"""从内容中提取正则匹配值。

根据正则中的捕获组数量返回不同值:
- 无捕获组或匹配失败:返回空字符串 ""
- 单个捕获组:返回匹配的字符串
- 多个捕获组:返回匹配的字符串元组

Args:
pattern: 正则表达式模式
content: 要搜索的文本内容

Returns:
匹配的字符串或字符串元组,匹配失败返回空字符串
"""
match = re.search(pattern, content, flags=re.DOTALL)
return match.group(1).strip() if match else ""
if not match:
return ""
# 多个捕获组时返回元组
if match.lastindex and match.lastindex >= 2:
return tuple(match.group(i).strip() for i in range(1, match.lastindex + 1))
return match.group(1).strip()


def _clean_html_cell(text: str) -> str:
"""清理 HTML 单元格内容,转换为纯文本。

处理流程:
1. 将 <br> 标签替换为换行符
2. 移除所有 HTML 标签
3. 解码 HTML 实体(如 &amp; → &)
4. 去除首尾空白

Args:
text: 包含 HTML 的单元格内容

Returns:
清理后的纯文本
"""
no_break = text.replace("<br>", "\n")
plain = re.sub(r"<[^>]+>", "", no_break)
return html.unescape(plain).strip()


def _parse_language_tag(raw: str) -> str:
"""解析日志文件中的语言标签。

兼容新旧两种格式:
- 新版:"> 本日内容语种:**ZH + JP**" → "ZH + JP"
- 旧版:"> 本日随机语种:**日本語**" → "ZH + JP"(转为短名称)

Args:
raw: 日志文件原始内容

Returns:
标准化的语言标签(格式:ZH + XX)
"""
# 尝试匹配新版格式
new_tag = _extract(LANGUAGE_TAG_PATTERN, raw)
if new_tag:
return new_tag

# 回退到旧版格式,将语言全称转为短名称
old_lang = _extract(LEGACY_LANGUAGE_PATTERN, raw)
if old_lang:
short_name = LEGACY_LANGUAGE_NAMES.get(old_lang, old_lang)
return f"ZH + {short_name}"

return "ZH + ?"


def _parse_log(path: Path, repo_root: Path) -> dict[str, str]:
"""解析单个日志文件,提取结构化数据。

Args:
path: 日志文件路径
repo_root: 仓库根目录路径

Returns:
包含日期、路径、心情记录、双语日记和语言标签的字典
"""
raw = path.read_text(encoding="utf-8")
date_text = path.stem
zh_cell = _extract(ZH_TABLE_PATTERN, raw)

# 提取双栏表格内容
table_cells = _extract(ZH_TABLE_PATTERN, raw)
if isinstance(table_cells, tuple) and len(table_cells) == 2:
zh_cell, translated_cell = table_cells
else:
zh_cell, translated_cell = "", ""
zh_diary = _clean_html_cell(zh_cell)
translated_diary = _clean_html_cell(translated_cell)
# 提取情绪短句素材
mood_record = html.unescape(_extract(MOOD_PATTERN, raw))
random_language = _extract(r"> 本日随机语种:\*\*(.+?)\*\*", raw)

# 解析语言标签
language_tag = _parse_language_tag(raw)

return {
"date": date_text,
"log_path": path.relative_to(repo_root).as_posix(),
"mood_record": mood_record or "今日心情记录已归档",
"zh_diary": zh_diary or "暂无日志内容",
"random_language": random_language or "Unknown",
"translated_diary": translated_diary or "暂无翻译内容",
"language_tag": language_tag,
}


def export_web_logs_data(repo_root: Path) -> Path:
"""扫描日志目录并导出为 JSON 数据文件。

遍历 logs/ 下所有 YYYY/MM/YYYY-MM-DD.md 文件,提取结构化数据,
按日期降序排列,输出到 web/logs.json。

Args:
repo_root: 仓库根目录路径

Returns:
输出文件路径
"""
logs_root = repo_root / "logs"
output_dir = repo_root / "web"
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / "logs.json"

# 按日期降序排列所有日志文件
records: list[dict[str, str]] = []
for path in sorted(logs_root.glob("*/*/*.md"), reverse=True):
records.append(_parse_log(path, repo_root))
Expand Down
2 changes: 1 addition & 1 deletion scripts/run_daily_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def main() -> int:
translated = translate_text(zh_text, lang_code, lang_name)

# Step 4: 渲染 Markdown
markdown = render_markdown(target_date, materials, zh_text, lang_name, translated)
markdown = render_markdown(target_date, materials, zh_text, lang_code, translated)

# Step 5: 写入文件
output = build_log_path(repo_root, target_date)
Expand Down
Loading