Update: 解决问题 #438
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Auto Fix Frontmatter & Fill Images | |
| on: | |
| push: | |
| paths: | |
| - 'XHBlogs/moments/**/*.md' | |
| - 'XHBlogs/posts/**/*.md' | |
| - 'XHBlogs/chatters/**/*.md' | |
| - 'XHBlogs/public/moments/**' | |
| - 'XHBlogs/public/posts/**' | |
| - 'XHBlogs/public/chatters/**' | |
| workflow_dispatch: | |
| jobs: | |
| fix-and-fill: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| - name: Install dependencies | |
| run: pip install python-frontmatter pyyaml Pillow | |
| - name: Auto convert images to WebP, delete originals, fill and fix frontmatter | |
| run: | | |
| python << 'EOF' | |
| import os | |
| from datetime import datetime, timezone, timedelta | |
| import glob | |
| import re | |
| from pathlib import Path | |
| from PIL import Image | |
| # ==================== 第零部分:自动将图片转为 WebP 并删除原图 ==================== | |
| image_dirs = [ | |
| Path("XHBlogs/public/moments"), | |
| Path("XHBlogs/public/posts"), | |
| Path("XHBlogs/public/chatters") | |
| ] | |
| for img_base in image_dirs: | |
| if img_base.exists() and img_base.is_dir(): | |
| for img_path in list(img_base.glob("**/*")): | |
| if img_path.is_file() and img_path.suffix.lower() in ['.jpg', '.jpeg', '.png']: | |
| try: | |
| webp_path = img_path.with_suffix('.webp') | |
| if not webp_path.exists(): | |
| with Image.open(img_path) as im: | |
| im.save(webp_path, 'WEBP', quality=85) | |
| print(f"🔄 Converted to WebP: {img_path.name} -> {webp_path.name}") | |
| # 转换成功后,安全删除原图 | |
| img_path.unlink() | |
| print(f"🗑️ Deleted original image: {img_path.name}") | |
| except Exception as e: | |
| print(f"⚠️ Failed to process {img_path}: {e}") | |
| # ==================== 第一部分:自动填充图片路径(支持 moments 和 posts/chatters 的 cover/images) ==================== | |
| # 1. 处理 moments 目录 | |
| moments_dir = Path("XHBlogs/moments") | |
| public_moments_dir = Path("XHBlogs/public/moments") | |
| if moments_dir.exists() and moments_dir.is_dir(): | |
| for md_file in moments_dir.glob("*.md"): | |
| try: | |
| match = re.match(r'^(\d{4})-(\d{2})-(\d{2})-(.+)\.md$', md_file.name) | |
| if not match: | |
| continue | |
| year, month, day, title = match.groups() | |
| img_folder = public_moments_dir / title / year / month / day | |
| if not img_folder.exists() or not img_folder.is_dir(): | |
| continue | |
| images = [] | |
| for f in sorted(img_folder.iterdir(), key=lambda x: x.name): | |
| if f.suffix.lower() == '.webp': | |
| rel_path = f"/moments/{title}/{year}/{month}/{day}/{f.name}" | |
| images.append(rel_path) | |
| if not images: | |
| continue | |
| content = md_file.read_text(encoding='utf-8') | |
| images_yaml = "images:\n" + "\n".join(f"- {img}" for img in images) | |
| if re.search(r'^images:\s*', content, re.MULTILINE): | |
| new_content = re.sub( | |
| r'^images:.*?(?=\n\w|\n---|\Z)', | |
| images_yaml, | |
| content, | |
| flags=re.MULTILINE | re.DOTALL | |
| ) | |
| else: | |
| new_content = re.sub( | |
| r'(date:.*\n)', | |
| r'\1' + images_yaml + '\n', | |
| content, | |
| count=1 | |
| ) | |
| if new_content != content: | |
| md_file.write_text(new_content, encoding='utf-8') | |
| print(f"✅ Filled WebP images into moments: {md_file.name}") | |
| except Exception as e: | |
| print(f"⚠️ Error filling images for {md_file}: {e}") | |
| # 2. 自动修正所有 markdown 文件里的图片后缀为 .webp(包括 cover、images 等) | |
| all_md_files = glob.glob('XHBlogs/**/*.md', recursive=True) | |
| for md_path in all_md_files: | |
| try: | |
| with open(md_path, 'r', encoding='utf-8') as f: | |
| md_text = f.read() | |
| # 将路径中的 .jpg / .jpeg / .png 统一改成 .webp | |
| updated_text = re.sub(r'(\.(?:jpg|jpeg|png))', '.webp', md_text, flags=re.IGNORECASE) | |
| if updated_text != md_text: | |
| with open(md_path, 'w', encoding='utf-8') as f: | |
| f.write(updated_text) | |
| print(f"🔗 Updated image extension to .webp in: {md_path}") | |
| except Exception as e: | |
| print(f"⚠️ Error updating image extension in {md_path}: {e}") | |
| # ==================== 第二部分:修复格式、标签、日期与空行 ==================== | |
| patterns = [ | |
| 'XHBlogs/moments/**/*.md', | |
| 'XHBlogs/posts/**/*.md', | |
| 'XHBlogs/chatters/**/*.md', | |
| ] | |
| files = [] | |
| for p in patterns: | |
| files.extend(glob.glob(p, recursive=True)) | |
| changed = False | |
| beijing_tz = timezone(timedelta(hours=8)) | |
| now = datetime.now(beijing_tz).strftime("%Y-%m-%d %H:%M:%S") | |
| correct_line = f"date: '{now}'" | |
| valid_re = re.compile( | |
| r"^date:\s*'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})'\s*$", | |
| re.MULTILINE | |
| ) | |
| for path in files: | |
| if not os.path.isfile(path): | |
| continue | |
| try: | |
| with open(path, 'r', encoding='utf-8') as f: | |
| content = f.read() | |
| original = content | |
| file_changed = False | |
| # 1. 修复 images 格式 | |
| content = re.sub( | |
| r'^images:\s*(?!$)(?!-\s)(.+)$', | |
| r'images:\n- \1', | |
| content, | |
| flags=re.MULTILINE | |
| ) | |
| if content != original: | |
| file_changed = True | |
| # 1.3 修复 tags 格式 | |
| def fix_tags(match): | |
| tag_val = match.group(1).strip() | |
| if not tag_val or tag_val.startswith('-') or tag_val.startswith('\n'): | |
| return match.group(0) | |
| tags = [t.strip() for t in re.split(r'[,,]+', tag_val) if t.strip() and t.strip() != '-'] | |
| if not tags: | |
| return match.group(0) | |
| formatted_tags = "tags:\n" + "\n".join(f"- {t}" for t in tags) | |
| return formatted_tags | |
| content = re.sub( | |
| r'^tags:\s*([^\r\n\-].*?)$', | |
| fix_tags, | |
| content, | |
| flags=re.MULTILINE | |
| ) | |
| if content != original: | |
| file_changed = True | |
| # 1.5 删除图片路径中的 /XHBlogs/public | |
| new_content = re.sub( | |
| r'(/XHBlogs/public)(/[^\s]*)', | |
| r'\2', | |
| content | |
| ) | |
| if new_content != content: | |
| content = new_content | |
| file_changed = True | |
| # 2. 修复 date | |
| if not valid_re.search(content): | |
| new_content = re.sub( | |
| r'^date:\s*.*$', | |
| correct_line, | |
| content, | |
| flags=re.MULTILINE | |
| ) | |
| if new_content != content: | |
| content = new_content | |
| file_changed = True | |
| else: | |
| content = re.sub( | |
| r'^(---\s*\n)', | |
| f"\\1{correct_line}\n", | |
| content, | |
| count=1, | |
| flags=re.MULTILINE | |
| ) | |
| file_changed = True | |
| # 3. 处理结尾的 --- 空行 | |
| if content.count('---') >= 2: | |
| parts = content.split('---', 2) | |
| front = parts[0] + '---' + parts[1] + '---' | |
| body = parts[2] | |
| if body.startswith('\n\n'): | |
| pass | |
| elif body.startswith('\n'): | |
| content = front + '\n' + body | |
| file_changed = True | |
| else: | |
| content = front + '\n\n' + body | |
| file_changed = True | |
| if file_changed: | |
| with open(path, 'w', encoding='utf-8') as f: | |
| f.write(content) | |
| changed = True | |
| except Exception as e: | |
| print(f"⚠️ Skip {path}: {e}") | |
| if not changed: | |
| print("✅ No files need fixing.") | |
| else: | |
| print("✅ All fixes and image fills applied.") | |
| EOF | |
| - name: Commit changes | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add -A | |
| if git diff --staged --quiet; then | |
| echo "No changes to commit" | |
| else | |
| git commit -m "chore: auto convert webp, delete originals, update paths and fix frontmatter" | |
| git push | |
| fi |