Files
soul-yongping/content_upload.py
卡若 5724fba877 feat: 小程序超级个体/个人资料/CKB获客;VIP列表展示过滤;管理端与API联调
- 超级个体:去掉首位特例;列表仅展示有头像且非微信默认昵称(vip.go)
- 个人资料:居中头像、低调联系方式、点头像优先走存客宝 lead(ckbLeadToken)
- 阅读页分享朋友圈复制与 toast 去重
- soul-api: miniprogram users 带 ckbLeadToken;其它 handler 与路由调整
- 脚本:content_upload、miniprogram 上传辅助等

Made-with: Cursor
2026-03-22 08:34:28 +08:00

238 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
将书稿 md 上传到小程序对应 chapters 表(与 Soul创业实验 Skill「上传」一致
依赖: pip install pymysql
数据库配置:复用 scripts/migrate_2026_sections.py 中的 DB_CONFIG与现网一致
"""
from __future__ import annotations
import argparse
import importlib.util
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent
PART_2026 = "part-2026-daily"
CHAPTER_2026 = "chapter-2026-daily"
TITLE_2026 = "2026每日派对干货"
def load_db_config() -> dict:
mig = ROOT / "scripts" / "migrate_2026_sections.py"
if not mig.is_file():
print("缺少 scripts/migrate_2026_sections.py无法读取 DB_CONFIG", file=sys.stderr)
sys.exit(1)
spec = importlib.util.spec_from_file_location("_mig_db", mig)
mod = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(mod)
cfg = getattr(mod, "DB_CONFIG", None)
if not isinstance(cfg, dict):
print("migrate_2026_sections.py 中无有效 DB_CONFIG", file=sys.stderr)
sys.exit(1)
return cfg
def strip_md_title_line(text: str) -> str:
lines = text.splitlines()
if lines and lines[0].lstrip().startswith("#"):
return "\n".join(lines[1:]).lstrip("\n")
return text
def for_miniprogram_body(text: str) -> str:
"""上传 README少用 --- 分割线;正文内独立一行的 --- 改为空行分段。"""
out_lines: list[str] = []
for line in text.splitlines():
if line.strip() == "---":
out_lines.append("")
out_lines.append("")
else:
out_lines.append(line)
return "\n".join(out_lines).strip() + "\n"
def next_10_id(cur) -> str:
cur.execute(
"""
SELECT id FROM chapters
WHERE id REGEXP '^10\\\\.[0-9]+$'
ORDER BY CAST(SUBSTRING_INDEX(id, '.', -1) AS UNSIGNED) DESC
LIMIT 1
"""
)
row = cur.fetchone()
if not row:
return "10.01"
last = row[0]
n = int(last.split(".")[-1])
return f"10.{n + 1:02d}"
def list_structure(cur):
cur.execute(
"""
SELECT DISTINCT part_id, part_title, chapter_id, chapter_title
FROM chapters
ORDER BY part_id, chapter_id
"""
)
print("篇章结构distinct part/chapter:")
for r in cur.fetchall():
print(f" part={r[0]!r} chapter={r[2]!r} | {r[1]} / {r[3]}")
def list_chapters_2026(cur):
cur.execute(
"""
SELECT id, section_title, sort_order
FROM chapters
WHERE part_id = %s AND chapter_id = %s
ORDER BY COALESCE(sort_order, 999999) ASC, id ASC
""",
(PART_2026, CHAPTER_2026),
)
print(f"2026每日派对干货 ({PART_2026} / {CHAPTER_2026}):")
for r in cur.fetchall():
print(f" {r[0]}\torder={r[2]}\t{r[1]}")
def main():
try:
import pymysql
except ImportError:
print("需要: pip install pymysql", file=sys.stderr)
sys.exit(1)
p = argparse.ArgumentParser(description="上传书稿 md 到 soul_miniprogram.chapters")
p.add_argument("--id", help="业务 id如 10.27;省略则自动取当前最大 10.xx +1")
p.add_argument("--title", help="小节标题,如 第128场主题")
p.add_argument("--content-file", type=Path, help="文章 md 绝对或相对路径")
p.add_argument("--part", default=PART_2026)
p.add_argument("--chapter", default=CHAPTER_2026)
p.add_argument("--part-title", default=TITLE_2026)
p.add_argument("--chapter-title", default=TITLE_2026)
p.add_argument("--price", type=float, default=1.0)
p.add_argument("--free", action="store_true", help="标记为免费")
p.add_argument("--list-structure", action="store_true")
p.add_argument("--list-chapters", action="store_true")
p.add_argument("--dry-run", action="store_true")
args = p.parse_args()
cfg = load_db_config()
conn = pymysql.connect(**cfg)
cur = conn.cursor()
if args.list_structure:
list_structure(cur)
conn.close()
return
if args.list_chapters:
list_chapters_2026(cur)
conn.close()
return
if not args.title or not args.content_file:
p.error("上传时必须提供 --title 与 --content-file")
path = args.content_file.expanduser().resolve()
if not path.is_file():
print(f"文件不存在: {path}", file=sys.stderr)
sys.exit(1)
raw = path.read_text(encoding="utf-8")
body = for_miniprogram_body(strip_md_title_line(raw))
word_count = len(body)
is_free = 1 if args.free else 0
price = 0.0 if args.free else float(args.price)
section_id = args.id
if not section_id:
section_id = next_10_id(cur)
print(f"未指定 --id使用新 id: {section_id}")
cur.execute("SELECT mid FROM chapters WHERE id = %s", (section_id,))
row = cur.fetchone()
exists = row is not None
cur.execute("SELECT COALESCE(MAX(sort_order), -1) FROM chapters")
max_sort = cur.fetchone()[0]
next_sort = int(max_sort) + 1
if args.dry_run:
print(f"id={section_id} exists={exists} next_sort={next_sort} words={word_count}")
print(body[:500] + ("..." if len(body) > 500 else ""))
conn.close()
return
if exists:
cur.execute(
"""
UPDATE chapters SET
section_title = %s,
content = %s,
word_count = %s,
price = %s,
is_free = %s,
part_id = %s,
part_title = %s,
chapter_id = %s,
chapter_title = %s,
updated_at = NOW()
WHERE id = %s
""",
(
args.title,
body,
word_count,
price,
is_free,
args.part,
args.part_title,
args.chapter,
args.chapter_title,
section_id,
),
)
print(f"已更新 {section_id} | {args.title}")
else:
cur.execute(
"""
INSERT INTO chapters (
id, part_id, part_title, chapter_id, chapter_title,
section_title, content, word_count, is_free, price,
sort_order, status, edition_standard, edition_premium,
hot_score, created_at, updated_at
) VALUES (
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s,
%s, 'published', 1, 0,
0, NOW(), NOW()
)
""",
(
section_id,
args.part,
args.part_title,
args.chapter,
args.chapter_title,
args.title,
body,
word_count,
is_free,
price,
next_sort,
),
)
print(f"已创建 {section_id} | {args.title} | sort_order={next_sort}")
conn.commit()
conn.close()
if __name__ == "__main__":
main()