feat: generate 以 board_select 为唯一列表主人并写回 shown keys

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-14 10:36:53 +08:00
parent 0c324f9ace
commit d448002e7a
4 changed files with 1001 additions and 106 deletions

View File

@@ -7,6 +7,7 @@ from typing import Any
from daily.config import wecom_skill_desc_limit
from daily.localize import LocalizeJob, localize_brief_descriptions, needs_chinese
from daily.skills_group import group_skills_by_source, skill_id as board_skill_id
from daily.text_utils import trim_brief
ICONS = {
@@ -55,25 +56,177 @@ def _skill_line(rank: int, item: dict[str, Any], *, badge: str = "") -> list[str
return lines
def _ai_news_lines(items: list[dict[str, Any]]) -> list[str]:
def _ai_news_link_label(item: dict[str, Any], *, merged: bool = False) -> str:
title = (item.get("title") or "?").strip() or "?"
source = (item.get("source_name") or "").strip()
if merged and source:
return f"{source} - {title}"
desc = (item.get("desc_short") or "").strip()
if desc:
return desc
return title
def _ai_news_lines(items: list[dict[str, Any]], *, merged: bool = False) -> list[str]:
lines: list[str] = []
for i, item in enumerate(items, 1):
title = item.get("title", "?")
label = _ai_news_link_label(item, merged=merged)
link = item.get("link", "")
source = item.get("source_name", "?")
pub = item.get("published_fmt", "")
desc = item.get("desc_short", "")
pub_suffix = f" · {pub}" if pub else ""
desc = (item.get("desc_short") or "").strip()
pub_suffix = "" if merged else (f" · {pub}" if pub else "")
if link:
head = f"{i}. [**{title}**]({link}) · `{source}`{pub_suffix}"
if merged and desc:
head = f"{i}. [{label}]({link}) — {desc}{pub_suffix}"
elif merged:
head = f"{i}. [{label}]({link}){pub_suffix}"
else:
head = f"{i}. [{label}]({link}) · `{source}`{pub_suffix}"
elif merged and desc:
head = f"{i}. {label}{desc}{pub_suffix}"
else:
head = f"{i}. **{title}** · `{source}`{pub_suffix}"
head = f"{i}. {label} · `{source}`{pub_suffix}"
lines.append(head)
if desc:
lines.append(f" > {desc}")
return lines
_NEWS_BLOCK_END = re.compile(
r"\n\n(📈|🔥|🐙|🌱|🤖|📦|🎯|💡|🌍|🇨🇳|📰|🔧)",
)
def _build_merged_news_block(
items: list[dict[str, Any]],
tech_items: list[dict[str, Any]] | None = None,
) -> str:
combined = list(items or [])
if tech_items:
combined.extend(tech_items)
if not combined:
return ""
parts = [f"📰 **AI 时讯精选 Top {len(combined)}**"]
parts.extend(_ai_news_lines(combined, merged=True))
return "\n".join(parts) + "\n"
def _replace_merged_news_block(
md: str,
items: list[dict[str, Any]],
tech_items: list[dict[str, Any]] | None = None,
) -> str:
if not items and not tech_items:
return md
block = _build_merged_news_block(items, tech_items)
start_pat = re.compile(
r"^📰 \*\*AI 时讯精选[^\n]*\*\*\s*$",
re.MULTILINE,
)
match = start_pat.search(md)
if not match:
anchor = re.search(r"^(💡|🎯).*$", md, re.MULTILINE)
if anchor:
insert_at = anchor.end()
return md[:insert_at] + "\n\n" + block + md[insert_at:].lstrip("\n")
anchor2 = re.search(r"^(🌍|🇨🇳|📈|🔧).*$", md, re.MULTILINE)
if anchor2:
insert_at = anchor2.start()
return md[:insert_at] + block + md[insert_at:].lstrip("\n")
return md.rstrip() + "\n\n" + block
start = match.start()
tail = md[match.end() :]
end_rel = _NEWS_BLOCK_END.search(tail)
end = match.end() + (end_rel.start() if end_rel else len(tail))
return md[:start] + block + md[end:].lstrip("\n")
def _remove_news_blocks(md: str, icons: tuple[str, ...], *, label_must_contain: str = "") -> str:
out = md
for icon in icons:
start_pat = re.compile(rf"^{re.escape(icon)} \*\*[^\n]+\*\*\s*$", re.MULTILINE)
while True:
match = None
for candidate in start_pat.finditer(out):
if label_must_contain and label_must_contain not in candidate.group(0):
continue
match = candidate
break
if not match:
break
start = match.start()
tail = out[match.end() :]
end_rel = _NEWS_BLOCK_END.search(tail)
end = match.end() + (end_rel.start() if end_rel else len(tail))
out = out[:start] + out[end:].lstrip("\n")
return out
def _replace_news_block(
md: str,
icon: str,
items: list[dict[str, Any]],
label: str,
*,
merged: bool = False,
) -> str:
if not items:
return md
if merged:
start_pat = re.compile(
rf"^{re.escape(icon)} \*\*{re.escape(label)}[^\n]*\*\*\s*$",
re.MULTILINE,
)
else:
start_pat = re.compile(rf"^{re.escape(icon)} \*\*[^\n]+\*\*\s*$", re.MULTILINE)
match = start_pat.search(md)
block = (
f"{icon} **{label} Top {len(items)}**\n"
+ "\n".join(_ai_news_lines(items, merged=merged))
+ "\n"
)
if not match:
anchor = re.search(r"^(💡|🎯).*$", md, re.MULTILINE)
if anchor:
insert_at = anchor.end()
return md[:insert_at] + "\n\n" + block + md[insert_at:].lstrip("\n")
# 插在 🌍/🇨🇳 原位置,或 Skills 区块前
anchor2 = re.search(r"^(🌍|🇨🇳|📈).*$", md, re.MULTILINE)
if anchor2:
insert_at = anchor2.start()
return md[:insert_at] + block + md[insert_at:].lstrip("\n")
return md.rstrip() + "\n\n" + block
start = match.start()
tail = md[match.end() :]
end_rel = _NEWS_BLOCK_END.search(tail)
end = match.end() + (end_rel.start() if end_rel else len(tail))
return md[:start] + block + md[end:].lstrip("\n")
def replace_wecom_news_sections(
md: str,
*,
ai_news: list[dict[str, Any]] | None = None,
cn_ai_news: list[dict[str, Any]] | None = None,
tech_ai_news: list[dict[str, Any]] | None = None,
merged: bool = False,
) -> str:
"""用 Python 整理后的新闻列表替换 Agent/模板中的时讯区块。"""
if merged and (ai_news or tech_ai_news):
out = _remove_news_blocks(md, ("🌍", "🇨🇳"))
out = _remove_news_blocks(out, ("📰",), label_must_contain="AI 时讯精选")
out = _remove_news_blocks(out, ("🔧",), label_must_contain="技术类时讯")
return _replace_merged_news_block(out, ai_news or [], tech_ai_news)
out = md
if cn_ai_news:
out = _replace_news_block(out, "🇨🇳", cn_ai_news, "国内 AI 时讯")
if ai_news:
out = _replace_news_block(out, "🌍", ai_news, "国际 AI 时讯")
return out
WECOM_GITHUB_DESC_LIMIT = 40
def _github_repo_lines(repos: list[dict[str, Any]], *, show_created: bool = False) -> list[str]:
lines: list[str] = []
for i, repo in enumerate(repos, 1):
@@ -94,12 +247,45 @@ def _github_repo_lines(repos: list[dict[str, Any]], *, show_created: bool = Fals
meta_parts.append(f"创建于 {created}")
meta = f" · {' · '.join(meta_parts)}" if meta_parts else ""
lines.append(f"{i}. [{name}]({url}){meta}")
desc = repo.get("desc_short") or repo.get("description", "")
desc = repo.get("wecom_desc") or repo.get("desc_short") or repo.get("description", "")
if desc:
lines.append(f" > {desc}")
lines.append(f" {desc}")
return lines
def finalize_wecom_github_repos(
items: list[dict[str, Any]],
*,
desc_limit: int | None = None,
) -> list[dict[str, Any]]:
"""为企微 GitHub 条目生成简短中文简介。"""
if desc_limit is None:
desc_limit = WECOM_GITHUB_DESC_LIMIT
limit = desc_limit if desc_limit > 0 else 40
copies: list[tuple[str, dict[str, Any]]] = []
jobs: list[LocalizeJob] = []
for item in items:
copy = dict(item)
desc = (copy.get("description") or copy.get("desc_short") or "").strip()
key = f"github:{copy.get('repo', '?')}"
if needs_chinese(desc) or len(desc) > limit:
jobs.append(LocalizeJob(key, desc, limit))
else:
copy["wecom_desc"] = desc
copies.append((key, copy))
zh_map = localize_brief_descriptions(jobs, archive=True)
out: list[dict[str, Any]] = []
for key, copy in copies:
if key in zh_map:
copy["wecom_desc"] = zh_map[key]
elif "wecom_desc" not in copy:
fallback = (copy.get("description") or copy.get("desc_short") or "").strip()
copy["wecom_desc"] = _brief_fallback_desc(fallback, limit) if fallback else ""
out.append(copy)
return out
def _fallback_skill_desc(item: dict[str, Any]) -> str:
if item.get("cluster"):
count = int(item.get("cluster_count") or 1)
@@ -173,6 +359,7 @@ def _grouped_skill_to_wecom_item(
if not item.get("wecom_desc"):
desc = _brief_fallback_desc(desc, limit)
return {
"id": str(item.get("id") or f"{item.get('source', '?')}/{item.get('title', '')}"),
"title": item.get("title", ""),
"source": item.get("source", "?"),
"installs_fmt": installs_fmt,
@@ -196,39 +383,178 @@ def build_skills_board_section(icon_key: str, board_label: str, items: list[dict
def _move_to_wecom_skill_item(move: dict[str, Any]) -> dict[str, Any]:
installs = int(move.get("installs") or 0)
rank = move.get("rank", "?")
board_badge = (move.get("badge") or "").strip()
badge = f"[新入 #{rank}]"
if board_badge:
badge = f"{badge} {board_badge}"
return {
"title": move.get("title", "?"),
"source": move.get("source", "?"),
"installs_fmt": move.get("installs_fmt") or str(installs),
"link": move.get("link", ""),
"description": (move.get("description") or "").strip(),
"badge": badge,
}
def _move_to_skill_row(move: dict[str, Any]) -> dict[str, Any]:
title = str(move.get("title") or "?")
source = str(move.get("source") or "?")
installs = int(move.get("installs") or 0)
sid = str(move.get("id") or f"{source}/{title}")
return {
"id": sid,
"title": title,
"source": source,
"installs": installs,
"link": move.get("link", ""),
"description": (move.get("description") or "").strip(),
}
def _flatten_skill_board_item(item: dict[str, Any]) -> list[dict[str, Any]]:
if item.get("cluster"):
source = str(item.get("source") or "?")
installs = int(item.get("installs") or 0)
titles = [str(t) for t in (item.get("cluster_skills") or []) if t]
if not titles:
titles = [str(item.get("title") or "?")]
top_title = str(item.get("title") or titles[0])
rows: list[dict[str, Any]] = []
for title in titles:
link = item.get("link", "")
if title != top_title:
link = f"https://www.skills.sh/{source}/{title}"
rows.append(
{
"id": f"{source}/{title}",
"title": title,
"source": source,
"installs": installs,
"link": link,
"description": (item.get("description") or "").strip(),
}
)
return rows
title = str(item.get("title") or "?")
source = str(item.get("source") or "?")
return [
{
"id": str(item.get("id") or f"{source}/{title}"),
"title": title,
"source": source,
"installs": int(item.get("installs") or 0),
"link": item.get("link", ""),
"description": (item.get("description") or "").strip(),
}
]
def _skill_keys_in_board_item(item: dict[str, Any]) -> set[str]:
if item.get("cluster"):
source = str(item.get("source") or "?")
titles = item.get("cluster_skills") or [item.get("title", "")]
return {f"{source}/{t}" for t in titles if t}
return {_skill_group_key(item)}
def _prepare_grouped_wecom_skills(
flat_rows: list[dict[str, Any]],
*,
limit: int,
) -> tuple[list[dict[str, Any]], set[str]]:
if not flat_rows:
return [], set()
grouped = group_skills_by_source(flat_rows, limit=limit, pool_size=max(len(flat_rows), limit))
prepared = finalize_wecom_skill_groups(grouped)
wecom_items = [_grouped_skill_to_wecom_item(x) for x in prepared]
keys: set[str] = set()
for item in grouped:
keys.update(_skill_keys_in_board_item(item))
return wecom_items[:limit], keys
def _merge_skill_board_items(
moves: list[dict[str, Any]],
full_items: list[dict[str, Any]],
limit: int,
*,
exclude_keys: set[str] | None = None,
recent_keys: set[str] | None = None,
) -> tuple[list[dict[str, Any]], set[str]]:
from daily.delta import skill_id as move_skill_id
exclude = exclude_keys or set()
recent = recent_keys or set()
seen: set[str] = set()
flat: list[dict[str, Any]] = []
for move in moves:
key = move_skill_id(move)
if not key or key in seen or key in exclude:
continue
seen.add(key)
flat.append(_move_to_skill_row(move))
pad_pool = max(limit * 5, len(flat), 50)
for item in full_items:
if len(flat) >= pad_pool:
break
for row in _flatten_skill_board_item(item):
key = board_skill_id(row)
if not key or key in seen or key in exclude or key in recent:
continue
seen.add(key)
flat.append(row)
return _prepare_grouped_wecom_skills(flat, limit=limit)
def build_skills_delta_sections(
trending_moves: list[dict[str, Any]],
hot_moves: list[dict[str, Any]],
*,
trending_full: list[dict[str, Any]] | None = None,
hot_full: list[dict[str, Any]] | None = None,
trending_limit: int = 10,
hot_limit: int = 10,
pad: bool = False,
recent_trending: set[str] | None = None,
recent_hot: set[str] | None = None,
) -> str:
sections: list[str] = []
trending_keys: set[str] = set()
if pad:
t_items, trending_keys = _merge_skill_board_items(
trending_moves,
trending_full or [],
trending_limit,
recent_keys=recent_trending,
)
if t_items:
lines = [f"{ICONS['trending']} **Skills Trending Top {len(t_items)}**"]
for rank, item in enumerate(t_items, 1):
lines.extend(_skill_line(rank, item))
sections.append("\n".join(lines))
h_items, _ = _merge_skill_board_items(
hot_moves,
hot_full or [],
hot_limit,
exclude_keys=trending_keys,
recent_keys=recent_hot,
)
if h_items:
lines = [f"{ICONS['hot']} **Skills Hot Top {len(h_items)}**"]
for rank, item in enumerate(h_items, 1):
lines.extend(_skill_line(rank, item))
sections.append("\n".join(lines))
return "\n\n".join(sections)
if trending_moves:
prepared = finalize_wecom_skill_groups([_move_to_wecom_skill_item(m) for m in trending_moves])
items = [_grouped_skill_to_wecom_item(x) for x in prepared]
flat = [_move_to_skill_row(m) for m in trending_moves]
items, _ = _prepare_grouped_wecom_skills(flat, limit=len(flat))
lines = [f"{ICONS['trending']} **Skills Trending 变化**"]
for rank, item in enumerate(items, 1):
lines.extend(_skill_line(rank, item, badge=item.get("badge", "")))
lines.extend(_skill_line(rank, item))
sections.append("\n".join(lines))
if hot_moves:
prepared = finalize_wecom_skill_groups([_move_to_wecom_skill_item(m) for m in hot_moves])
items = [_grouped_skill_to_wecom_item(x) for x in prepared]
flat = [_move_to_skill_row(m) for m in hot_moves]
items, _ = _prepare_grouped_wecom_skills(flat, limit=len(flat))
lines = [f"{ICONS['hot']} **Skills Hot 变化**"]
for rank, item in enumerate(items, 1):
lines.extend(_skill_line(rank, item, badge=item.get("badge", "")))
lines.extend(_skill_line(rank, item))
sections.append("\n".join(lines))
return "\n\n".join(sections)
@@ -243,12 +569,72 @@ def _github_move_to_repo(move: dict[str, Any]) -> dict[str, Any]:
"created_at": move.get("created_at", ""),
"description": move.get("description", ""),
"desc_short": (move.get("description") or "").strip(),
"badge": f"[新入 #{move.get('rank', '?')}]",
}
def build_github_delta_sections(movement: dict[str, Any], *, topic_name: str) -> str:
def _merge_github_board_items(
moves: list[dict[str, Any]],
full_repos: list[dict[str, Any]],
limit: int,
*,
recent_repos: set[str] | None = None,
) -> list[dict[str, Any]]:
recent = recent_repos or set()
seen: set[str] = set()
merged: list[dict[str, Any]] = []
for move in moves:
repo = _github_move_to_repo(move)
key = str(repo.get("repo") or "")
if not key or key in seen:
continue
seen.add(key)
merged.append(repo)
for repo in full_repos:
if len(merged) >= limit:
break
key = str(repo.get("repo") or "")
if not key or key in seen or key in recent:
continue
seen.add(key)
merged.append(repo)
return finalize_wecom_github_repos(merged)[:limit]
def build_github_delta_sections(
movement: dict[str, Any],
*,
topic_name: str,
github_trending: list[dict[str, Any]] | None = None,
github_emerging: list[dict[str, Any]] | None = None,
github_topic: list[dict[str, Any]] | None = None,
trending_limit: int = 10,
emerging_limit: int = 10,
topic_limit: int = 10,
pad: bool = False,
recent_board_keys: dict[str, set[str]] | None = None,
) -> str:
sections: list[str] = []
recent = recent_board_keys or {}
if pad:
mapping = [
("github_trending_moves", "github_trending", github_trending or [], trending_limit, "github", "GitHub Trending", False),
("github_emerging_moves", "github_emerging", github_emerging or [], emerging_limit, "emerging", "GitHub 新兴", True),
("github_topic_moves", "github_topic", github_topic or [], topic_limit, "topic", f"Topic `{topic_name}`", False),
]
for move_key, board_key, full_repos, limit, icon_key, label, show_created in mapping:
repos = _merge_github_board_items(
movement.get(move_key) or [],
full_repos,
limit,
recent_repos=recent.get(board_key),
)
if not repos:
continue
lines = [f"{ICONS[icon_key]} **{label} Top {len(repos)}**"]
lines.extend(_github_repo_lines(repos, show_created=show_created))
sections.append("\n".join(lines))
return "\n\n".join(sections)
mapping = [
("github_trending_moves", "github", "GitHub Trending 变化", False),
("github_emerging_moves", "emerging", "GitHub 新兴 变化", True),
@@ -259,13 +645,8 @@ def build_github_delta_sections(movement: dict[str, Any], *, topic_name: str) ->
if not moves:
continue
lines = [f"{ICONS[icon_key]} **{label}**"]
for i, move in enumerate(moves, 1):
repo = _github_move_to_repo(move)
badge = repo.pop("badge", "")
chunk = _github_repo_lines([repo], show_created=show_created)
if chunk:
chunk[0] = f"{i}. {badge} " + chunk[0].split(". ", 1)[-1]
lines.extend(chunk)
repos = finalize_wecom_github_repos([_github_move_to_repo(m) for m in moves])
lines.extend(_github_repo_lines(repos, show_created=show_created))
sections.append("\n".join(lines))
return "\n\n".join(sections)
@@ -287,6 +668,105 @@ def _strip_board_sections(md: str) -> str:
return re.sub(r"\n{3,}", "\n\n", md).rstrip()
def resolve_wecom_board_items(
*,
mode: str,
movement: dict[str, Any],
trending: list[dict[str, Any]],
hot: list[dict[str, Any]],
topic_name: str,
github_trending: list[dict[str, Any]] | None = None,
github_emerging: list[dict[str, Any]] | None = None,
github_topic: list[dict[str, Any]] | None = None,
wecom_trending: int = 10,
wecom_hot: int = 10,
wecom_github: int = 10,
wecom_emerging: int = 10,
wecom_topic: int = 10,
pad: bool = False,
date_str: str | None = None,
trending_pad: list[dict[str, Any]] | None = None,
hot_pad: list[dict[str, Any]] | None = None,
github_trending_pad: list[dict[str, Any]] | None = None,
github_emerging_pad: list[dict[str, Any]] | None = None,
github_topic_pad: list[dict[str, Any]] | None = None,
) -> dict[str, list[dict[str, Any]]]:
"""返回最终企微正文各榜 items与 replace_wecom_board_sections 同源),供写回 shown。"""
from daily.delta import load_recent_board_keys, partition_skill_moves_for_wecom
if mode != "delta":
return {
"skills_trending": list(trending),
"skills_hot": list(hot),
"github_trending": list(github_trending or []),
"github_emerging": list(github_emerging or []),
"github_topic": list(github_topic or []),
}
recent_board_keys: dict[str, set[str]] = {}
if pad and date_str:
recent_board_keys = load_recent_board_keys(date_str)
t_moves, h_moves = partition_skill_moves_for_wecom(
movement.get("skills_trending_moves") or [],
movement.get("skills_hot_moves") or [],
)
if pad:
t_items, trending_keys = _merge_skill_board_items(
t_moves,
trending_pad if trending_pad else trending,
wecom_trending,
recent_keys=recent_board_keys.get("skills_trending"),
)
h_items, _ = _merge_skill_board_items(
h_moves,
hot_pad if hot_pad else hot,
wecom_hot,
exclude_keys=trending_keys,
recent_keys=recent_board_keys.get("skills_hot"),
)
return {
"skills_trending": t_items,
"skills_hot": h_items,
"github_trending": _merge_github_board_items(
movement.get("github_trending_moves") or [],
github_trending_pad if github_trending_pad else (github_trending or []),
wecom_github,
recent_repos=recent_board_keys.get("github_trending"),
),
"github_emerging": _merge_github_board_items(
movement.get("github_emerging_moves") or [],
github_emerging_pad if github_emerging_pad else (github_emerging or []),
wecom_emerging,
recent_repos=recent_board_keys.get("github_emerging"),
),
"github_topic": _merge_github_board_items(
movement.get("github_topic_moves") or [],
github_topic_pad if github_topic_pad else (github_topic or []),
wecom_topic,
recent_repos=recent_board_keys.get("github_topic"),
),
}
t_flat = [_move_to_skill_row(m) for m in t_moves]
h_flat = [_move_to_skill_row(m) for m in h_moves]
t_items, _ = _prepare_grouped_wecom_skills(t_flat, limit=len(t_flat) or 1) if t_flat else ([], set())
h_items, _ = _prepare_grouped_wecom_skills(h_flat, limit=len(h_flat) or 1) if h_flat else ([], set())
return {
"skills_trending": t_items,
"skills_hot": h_items,
"github_trending": finalize_wecom_github_repos(
[_github_move_to_repo(m) for m in (movement.get("github_trending_moves") or [])]
),
"github_emerging": finalize_wecom_github_repos(
[_github_move_to_repo(m) for m in (movement.get("github_emerging_moves") or [])]
),
"github_topic": finalize_wecom_github_repos(
[_github_move_to_repo(m) for m in (movement.get("github_topic_moves") or [])]
),
}
def replace_wecom_board_sections(
md: str,
*,
@@ -295,16 +775,56 @@ def replace_wecom_board_sections(
trending: list[dict[str, Any]],
hot: list[dict[str, Any]],
topic_name: str,
github_trending: list[dict[str, Any]] | None = None,
github_emerging: list[dict[str, Any]] | None = None,
github_topic: list[dict[str, Any]] | None = None,
wecom_trending: int = 10,
wecom_hot: int = 10,
wecom_github: int = 10,
wecom_emerging: int = 10,
wecom_topic: int = 10,
pad: bool = False,
date_str: str | None = None,
trending_pad: list[dict[str, Any]] | None = None,
hot_pad: list[dict[str, Any]] | None = None,
github_trending_pad: list[dict[str, Any]] | None = None,
github_emerging_pad: list[dict[str, Any]] | None = None,
github_topic_pad: list[dict[str, Any]] | None = None,
) -> str:
from daily.delta import partition_skill_moves_for_wecom
from daily.delta import load_recent_board_keys, partition_skill_moves_for_wecom
recent_board_keys: dict[str, set[str]] = {}
if pad and date_str:
recent_board_keys = load_recent_board_keys(date_str)
if mode == "delta":
t_moves, h_moves = partition_skill_moves_for_wecom(
movement.get("skills_trending_moves") or [],
movement.get("skills_hot_moves") or [],
)
skills_sec = build_skills_delta_sections(t_moves, h_moves)
github_sec = build_github_delta_sections(movement, topic_name=topic_name)
skills_sec = build_skills_delta_sections(
t_moves,
h_moves,
trending_full=trending_pad if pad and trending_pad else trending,
hot_full=hot_pad if pad and hot_pad else hot,
trending_limit=wecom_trending,
hot_limit=wecom_hot,
pad=pad,
recent_trending=recent_board_keys.get("skills_trending"),
recent_hot=recent_board_keys.get("skills_hot"),
)
github_sec = build_github_delta_sections(
movement,
topic_name=topic_name,
github_trending=github_trending_pad if pad and github_trending_pad else github_trending,
github_emerging=github_emerging_pad if pad and github_emerging_pad else github_emerging,
github_topic=github_topic_pad if pad and github_topic_pad else github_topic,
trending_limit=wecom_github,
emerging_limit=wecom_emerging,
topic_limit=wecom_topic,
pad=pad,
recent_board_keys=recent_board_keys,
)
board_block = "\n\n".join(x for x in [skills_sec, github_sec] if x)
md = _strip_board_sections(md)
if board_block:
@@ -331,6 +851,21 @@ def replace_wecom_skill_sections(
mode: str = "full",
movement: dict[str, Any] | None = None,
topic_name: str = "llm",
github_trending: list[dict[str, Any]] | None = None,
github_emerging: list[dict[str, Any]] | None = None,
github_topic: list[dict[str, Any]] | None = None,
wecom_trending: int = 10,
wecom_hot: int = 10,
wecom_github: int = 10,
wecom_emerging: int = 10,
wecom_topic: int = 10,
pad: bool = False,
date_str: str | None = None,
trending_pad: list[dict[str, Any]] | None = None,
hot_pad: list[dict[str, Any]] | None = None,
github_trending_pad: list[dict[str, Any]] | None = None,
github_emerging_pad: list[dict[str, Any]] | None = None,
github_topic_pad: list[dict[str, Any]] | None = None,
) -> str:
"""用 Python 合并后的 Skills 榜替换或插入 Agent 早报中的对应区块。"""
return replace_wecom_board_sections(
@@ -340,6 +875,21 @@ def replace_wecom_skill_sections(
trending=trending,
hot=hot,
topic_name=topic_name,
github_trending=github_trending,
github_emerging=github_emerging,
github_topic=github_topic,
wecom_trending=wecom_trending,
wecom_hot=wecom_hot,
wecom_github=wecom_github,
wecom_emerging=wecom_emerging,
wecom_topic=wecom_topic,
pad=pad,
date_str=date_str,
trending_pad=trending_pad,
hot_pad=hot_pad,
github_trending_pad=github_trending_pad,
github_emerging_pad=github_emerging_pad,
github_topic_pad=github_topic_pad,
)
@@ -378,6 +928,8 @@ def build_wecom_report(
topic_repos: list[dict[str, Any]],
ai_news: list[dict[str, Any]] | None = None,
cn_ai_news: list[dict[str, Any]] | None = None,
merged_ai_news: list[dict[str, Any]] | None = None,
merged_tech_ai_news: list[dict[str, Any]] | None = None,
pick_command: str,
pick_why: str = "",
pick_title: str = "",
@@ -396,15 +948,21 @@ def build_wecom_report(
lines.append(f"{ICONS['theme']} {theme_line}")
lines.append("")
if ai_news:
lines.append(f"{ICONS['ainews']} **国际 AI 时讯 Top {len(ai_news)}**")
lines.extend(_ai_news_lines(ai_news))
lines.append("")
if merged_ai_news or merged_tech_ai_news:
block = _build_merged_news_block(merged_ai_news or [], merged_tech_ai_news)
if block:
lines.append(block.rstrip())
lines.append("")
else:
if ai_news:
lines.append(f"{ICONS['ainews']} **国际 AI 时讯 Top {len(ai_news)}**")
lines.extend(_ai_news_lines(ai_news))
lines.append("")
if cn_ai_news:
lines.append(f"{ICONS['cnainews']} **国内 AI 时讯 Top {len(cn_ai_news)}**")
lines.extend(_ai_news_lines(cn_ai_news))
lines.append("")
if cn_ai_news:
lines.append(f"{ICONS['cnainews']} **国内 AI 时讯 Top {len(cn_ai_news)}**")
lines.extend(_ai_news_lines(cn_ai_news))
lines.append("")
if include_boards:
lines.append(f"{ICONS['trending']} **Skills Trending Top {len(trending)}**")
@@ -419,17 +977,17 @@ def build_wecom_report(
if repos:
lines.append(f"{ICONS['github']} **GitHub Trending Top {len(repos)}**")
lines.extend(_github_repo_lines(repos))
lines.extend(_github_repo_lines(finalize_wecom_github_repos(repos)))
lines.append("")
if emerging:
lines.append(f"{ICONS['emerging']} **新兴项目 Top {len(emerging)}**")
lines.extend(_github_repo_lines(emerging, show_created=True))
lines.extend(_github_repo_lines(finalize_wecom_github_repos(emerging), show_created=True))
lines.append("")
if topic_repos:
lines.append(f"{ICONS['topic']} **Topic `{topic_name}` Top {len(topic_repos)}**")
lines.extend(_github_repo_lines(topic_repos))
lines.extend(_github_repo_lines(finalize_wecom_github_repos(topic_repos)))
lines.append("")
lines.append(f"{ICONS['pick']} **今日首推**")