Files
chinese-independent-developer/.github/scripts/process_item.py
Cheng Zheng 5708e95303 fix
2026-01-12 11:50:52 +08:00

217 lines
7.5 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.
import os
import re
import datetime
from github import Github
from openai import OpenAI
from datetime import datetime, timedelta, timezone
# ================= 配置区 =================
PAT_TOKEN = os.getenv("PAT_TOKEN") # GitHub Personal Access Token
API_KEY = os.getenv("LLM_API_KEY") # LLM API 密钥(如 DeepSeek、OpenAI
BASE_URL = os.getenv("LLM_BASE_URL", "https://api.openai.com/v1") # LLM API 基础 URL
REPO_NAME = "1c7/chinese-independent-developer" # GitHub 仓库名称
ISSUE_NUMBER = 160 # 用于收集项目提交的 Issue 编号
ADMIN_HANDLE = "1c7" # 管理员 GitHub 用户名
TRIGGER_EMOJI = "rocket" # 触发处理的表情符号 🚀
SUCCESS_EMOJI = "hooray" # 处理成功的表情符号 🎉
# ==========================================
def check_environment():
"""检查必需的环境变量是否存在"""
if not PAT_TOKEN:
raise ValueError("❌ 缺少环境变量 PAT_TOKEN请设置 GitHub Personal Access Token。")
if not API_KEY:
raise ValueError("❌ 缺少环境变量 LLM_API_KEY请设置 LLM API Key。")
print(f"✅ 环境变量检查通过")
print(f" - PAT_TOKEN: {'*' * 10}{PAT_TOKEN[-4:]}")
print(f" - API_KEY: {'*' * 10}{API_KEY[-4:]}")
print(f" - BASE_URL: {BASE_URL}\n")
def remove_quote_blocks(text: str) -> str:
"""移除 GitHub 引用回复块"""
lines = text.split('\n')
cleaned_lines = []
for line in lines:
if not line.lstrip().startswith('>'):
cleaned_lines.append(line)
result = '\n'.join(cleaned_lines)
result = re.sub(r'\n{3,}', '\n\n', result)
return result.strip()
def get_ai_project_line(raw_text):
"""让 AI 提取项目名称、链接和描述(支持多个产品)"""
client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
prompt = f"""
任务:将用户的项目介绍转换为 Markdown 格式。
要求:
1. 识别文本中的所有产品/项目(可能有多个)
2. 每个项目占一行
3. 在文字的开头,去掉"一款、一个、完全免费、高效、简洁、强大、快速、好用、安全"等营销废话
4. 严禁使用加粗格式(不要使用 **
5. 将产品名称从文字的后面提升到最前面
6. 每行格式:* :white_check_mark: [项目名](网址):用途描述
示例 1
输入https://example.com一款基于 AI 的高效视频生成网站
输出:* :white_check_mark: [example.com](https://example.com)AI 视频生成网站
示例 2
输入:[MyApp](https://myapp.com) 完全免费的强大工具,帮助用户管理任务
输出:* :white_check_mark: [MyApp](https://myapp.com):任务管理工具
示例 3多个项目
输入:
[ProductA](https://a.com)AI 绘画工具
[ProductB](https://b.com)AI 写作助手
输出:
* :white_check_mark: [ProductA](https://a.com)AI 绘画工具
* :white_check_mark: [ProductB](https://b.com)AI 写作助手
待处理文本:
{raw_text}
"""
response = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": prompt}],
temperature=0.3
)
return response.choices[0].message.content.strip()
def main():
# 检查环境变量
check_environment()
g = Github(PAT_TOKEN)
repo = g.get_repo(REPO_NAME)
issue = repo.get_issue(ISSUE_NUMBER)
time_threshold = datetime.now(timezone.utc) - timedelta(days=3)
comments = issue.get_comments(since=time_threshold)
# ===== 阶段 1收集待处理评论 =====
pending_comments = []
formatted_entries = []
for comment in comments:
reactions = comment.get_reactions()
has_trigger = any(r.content == TRIGGER_EMOJI and r.user.login == ADMIN_HANDLE for r in reactions)
has_success = any(r.content == SUCCESS_EMOJI for r in reactions)
if has_trigger and not has_success:
print(f"\n{'='*60}")
print(f"处理评论:\n{comment.body}")
print(f"\n评论链接:{comment.html_url}")
print(f"{'='*60}\n")
cleaned_body = remove_quote_blocks(comment.body)
# 判断用户是否自带了 Header
header_match = re.search(r'^####\s+.*', cleaned_body, re.MULTILINE)
if header_match:
header_line = header_match.group(0).strip()
body_for_ai = cleaned_body.replace(header_line, "").strip()
print(f"检测到用户自带 Header: {header_line}")
else:
author_name = comment.user.login
author_url = comment.user.html_url
header_line = f"#### {author_name} - [Github]({author_url})"
body_for_ai = cleaned_body
print(f"自动生成 Header: {header_line}")
# AI 处理项目详情行
project_line = get_ai_project_line(body_for_ai)
formatted_entry = f"{header_line}\n{project_line}"
pending_comments.append(comment)
formatted_entries.append(formatted_entry)
# ===== 阶段 2批量提交 =====
if not pending_comments:
print("无待处理评论")
return
print(f"\n共收集 {len(pending_comments)} 个待处理评论")
# 更新 README
content = repo.get_contents("README.md", ref="master")
readme_text = content.decoded_content.decode("utf-8")
today_str = datetime.now().strftime("%Y 年 %-m 月 %-d 号添加")
date_header = f"### {today_str}"
if date_header not in readme_text:
new_readme = readme_text.replace("3. 项目列表\n", f"3. 项目列表\n\n{date_header}\n")
else:
new_readme = readme_text
# 插入所有条目(用两个换行分隔)
insertion_point = new_readme.find(date_header) + len(date_header)
all_entries = "\n\n".join(formatted_entries)
final_readme = new_readme[:insertion_point] + "\n\n" + all_entries + new_readme[insertion_point:]
# 创建分支
branch_name = f"batch-add-projects-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
base = repo.get_branch("master")
try:
repo.get_git_ref(f"heads/{branch_name}").delete()
except:
pass
repo.create_git_ref(ref=f"refs/heads/{branch_name}", sha=base.commit.sha)
repo.update_file(
"README.md",
f"docs: batch add {len(pending_comments)} projects",
final_readme,
content.sha,
branch=branch_name
)
# 构建 PR body
comment_links = "\n".join([
f"- [{c.user.login}]({c.html_url})"
for c in pending_comments
])
formatted_list = "\n\n".join([
f"### {i+1}. {formatted_entries[i]}"
for i in range(len(formatted_entries))
])
pr_body = f"""批量添加 {len(pending_comments)} 个项目
## 原始评论链接
{comment_links}
## 格式化结果
{formatted_list}
---
自动生成,触发机制:用户 {ADMIN_HANDLE} 点击 🚀
"""
pr = repo.create_pull(
title=f"新增项目:批量添加 {len(pending_comments)} 个项目",
body=pr_body,
head=branch_name,
base="master"
)
print(f"\n✅ PR 创建成功:{pr.html_url}")
# 标记所有评论(添加 🎉 表情)
for comment in pending_comments:
comment.create_reaction(SUCCESS_EMOJI)
# 创建一条评论提及所有用户
user_mentions = " ".join([f"@{c.user.login}" for c in pending_comments])
reply_body = f"{user_mentions} 感谢提交,已添加!\n\n PR 链接:{pr.html_url}"
issue.create_comment(reply_body)
print(f"\n✅ 已标记所有 {len(pending_comments)} 个评论")
if __name__ == "__main__":
main()