mirror of
https://github.com/1c7/chinese-independent-developer.git
synced 2026-05-01 14:44:26 +08:00
Compare commits
8 Commits
add-projec
...
add-projec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1fb36bce3 | ||
|
|
28b054672a | ||
|
|
fc867f0db9 | ||
|
|
442fd51ec0 | ||
|
|
e83689f38c | ||
|
|
e71a840143 | ||
|
|
57e9b3e238 | ||
|
|
811ae026ca |
@@ -1,3 +1,3 @@
|
|||||||
GITHUB_TOKEN="github_pat_*"
|
PAT_TOKEN="github_pat_*"
|
||||||
LLM_API_KEY="sk-*"
|
LLM_API_KEY="sk-*"
|
||||||
LLM_BASE_URL="https://api.deepseek.com"
|
LLM_BASE_URL="https://api.deepseek.com"
|
||||||
|
|||||||
29
.github/README.md
vendored
29
.github/README.md
vendored
@@ -1,29 +0,0 @@
|
|||||||
## `.github/` 的文档
|
|
||||||
|
|
||||||
## 概括
|
|
||||||
用户在 https://github.com/1c7/chinese-independent-developer/issues/160 提交评论。
|
|
||||||
大部分情况下,格式是不符合规范的(可以理解)
|
|
||||||
需要用程序自动化处理,减少我的时间投入。
|
|
||||||
|
|
||||||
## 流程
|
|
||||||
1. 我在用户提交的评论点击 🚀 图标(表情)
|
|
||||||
1. 触发 Github Action 执行(手动触发 或 定时执行(每 6 小时)
|
|
||||||
1. Github Action 会触发 .github/scripts/process_item.py
|
|
||||||
2. 查找 "当前日期-3天" 开始(这个时间点往后) 所有标记 🚀 图标 的评论
|
|
||||||
3. 处理格式,创建 Pull Request。
|
|
||||||
4. 给评论新增一个 🎉 图标(意思是"处理完成")
|
|
||||||
7. 回复这条评论:感谢提交,已添加。
|
|
||||||
|
|
||||||
我只需要修改 PR 然后 merge 就行。
|
|
||||||
|
|
||||||
一句话概括:我点击 🚀 标签,然后 PR 会自动创建,我只需要 merge PR。我大概点击 3 次左右就可以了(如果介绍语有改进空间,我还得改一下文字,然后才 merge)
|
|
||||||
|
|
||||||
## 本地运行(为了开发调试)
|
|
||||||
```bash
|
|
||||||
cp .env.example .env
|
|
||||||
|
|
||||||
uv sync
|
|
||||||
|
|
||||||
uv run .github/scripts/process_item.py
|
|
||||||
```
|
|
||||||
|
|
||||||
114
.github/scripts/process_item.py
vendored
114
.github/scripts/process_item.py
vendored
@@ -1,145 +1,155 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import datetime
|
import datetime
|
||||||
from github import Github # https://github.com/PyGithub/PyGithub
|
from github import Github
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
|
||||||
# ================= 配置区 =================
|
# ================= 配置区 =================
|
||||||
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
|
PAT_TOKEN = os.getenv("PAT_TOKEN")
|
||||||
API_KEY = os.getenv("LLM_API_KEY")
|
API_KEY = os.getenv("LLM_API_KEY")
|
||||||
BASE_URL = os.getenv("LLM_BASE_URL", "https://api.openai.com/v1")
|
BASE_URL = os.getenv("LLM_BASE_URL", "https://api.openai.com/v1")
|
||||||
REPO_NAME = "1c7/chinese-independent-developer" # os.getenv("GITHUB_REPOSITORY")
|
REPO_NAME = "1c7/chinese-independent-developer"
|
||||||
ISSUE_NUMBER = 160 # 你在维护的那个 Issue 编号
|
ISSUE_NUMBER = 160
|
||||||
ADMIN_HANDLE = "1c7" # 替换为你的 GitHub ID
|
ADMIN_HANDLE = "1c7"
|
||||||
TRIGGER_EMOJI = "rocket" # 🚀
|
TRIGGER_EMOJI = "rocket" # 🚀
|
||||||
SUCCESS_EMOJI = "hooray" # 🎉
|
SUCCESS_EMOJI = "hooray" # 🎉
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
|
||||||
def remove_quote_blocks(text: str) -> str:
|
def remove_quote_blocks(text: str) -> str:
|
||||||
"""移除 GitHub 引用回复块(以 > 开头的行)"""
|
"""移除 GitHub 引用回复块"""
|
||||||
lines = text.split('\n')
|
lines = text.split('\n')
|
||||||
cleaned_lines = []
|
cleaned_lines = []
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
# 检查去除前导空格后是否以 > 开头
|
|
||||||
if not line.lstrip().startswith('>'):
|
if not line.lstrip().startswith('>'):
|
||||||
cleaned_lines.append(line)
|
cleaned_lines.append(line)
|
||||||
|
|
||||||
# 重新拼接,并清理多余空行
|
|
||||||
result = '\n'.join(cleaned_lines)
|
result = '\n'.join(cleaned_lines)
|
||||||
|
|
||||||
# 移除连续多个空行,保留单个空行
|
|
||||||
result = re.sub(r'\n{3,}', '\n\n', result)
|
result = re.sub(r'\n{3,}', '\n\n', result)
|
||||||
|
|
||||||
return result.strip()
|
return result.strip()
|
||||||
|
|
||||||
def get_ai_format(raw_text):
|
def get_ai_project_line(raw_text):
|
||||||
|
"""只让 AI 提取项目名称、链接和描述行"""
|
||||||
client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
|
client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
|
||||||
prompt = f"""
|
prompt = f"""
|
||||||
你是一个严格的文案编辑。任务是将用户的项目介绍转换为标准的 Markdown 格式。
|
任务:将用户的项目介绍转换为单行 Markdown 格式。
|
||||||
|
要求:
|
||||||
4. 格式模板:
|
1. 在文字的开头,去掉“一款、一个、完全免费、高效、简洁、强大、快速、好用、安全”等营销废话。
|
||||||
#### 制作者名字 - [Github](链接)
|
2. 严禁使用加粗格式(不要使用 **)。
|
||||||
* :white_check_mark: [项目名](链接):用途描述
|
3. 将产品名称从文字的后面提升到最前面。比如"一个安全高效的 AI 生图网站,基于 nano banana pro",改成 "AI 生图网站,,基于 nano banana pro"
|
||||||
|
3. 仅输出以下格式的一行文字:
|
||||||
|
* :white_check_mark: [项目名](网址):用途描述
|
||||||
|
|
||||||
待处理文本:
|
待处理文本:
|
||||||
{raw_text}
|
{raw_text}
|
||||||
"""
|
"""
|
||||||
response = client.chat.completions.create(
|
response = client.chat.completions.create(
|
||||||
model="deepseek-reasoner", # 或者使用 deepseek-chat
|
model="deepseek-chat",
|
||||||
messages=[{"role": "user", "content": prompt}],
|
messages=[{"role": "user", "content": prompt}],
|
||||||
temperature=0.3
|
temperature=0.3
|
||||||
)
|
)
|
||||||
return response.choices[0].message.content.strip()
|
return response.choices[0].message.content.strip()
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
g = Github(GITHUB_TOKEN)
|
g = Github(PAT_TOKEN)
|
||||||
repo = g.get_repo(REPO_NAME)
|
repo = g.get_repo(REPO_NAME)
|
||||||
issue = repo.get_issue(ISSUE_NUMBER)
|
issue = repo.get_issue(ISSUE_NUMBER)
|
||||||
|
|
||||||
# 计算 3 天前的时间(GitHub API 使用的是 UTC 时间)
|
|
||||||
time_threshold = datetime.now(timezone.utc) - timedelta(days=3)
|
time_threshold = datetime.now(timezone.utc) - timedelta(days=3)
|
||||||
|
|
||||||
# 如果你一定要死守 2025-12-15,也可以手动指定:
|
|
||||||
# time_threshold = datetime(2025, 12, 15, tzinfo=timezone.utc)
|
|
||||||
|
|
||||||
# 重点:在这里加上 since 参数
|
|
||||||
comments = issue.get_comments(since=time_threshold)
|
comments = issue.get_comments(since=time_threshold)
|
||||||
|
|
||||||
processed_count = 0
|
processed_count = 0
|
||||||
|
|
||||||
for comment in comments:
|
for comment in comments:
|
||||||
# 1. 检查是否有你的 🚀 反应
|
|
||||||
reactions = comment.get_reactions()
|
reactions = comment.get_reactions()
|
||||||
has_trigger = any(r.content == TRIGGER_EMOJI and r.user.login == ADMIN_HANDLE for r in reactions)
|
has_trigger = any(r.content == TRIGGER_EMOJI and r.user.login == ADMIN_HANDLE for r in reactions)
|
||||||
# 2. 检查是否已经标记过成功 🎉
|
|
||||||
has_success = any(r.content == SUCCESS_EMOJI for r in reactions)
|
has_success = any(r.content == SUCCESS_EMOJI for r in reactions)
|
||||||
|
|
||||||
if has_trigger and not has_success:
|
if has_trigger and not has_success:
|
||||||
print(f"发现待处理评论 ID: {comment.id}")
|
print(f"\n{'='*60}")
|
||||||
|
print(f"处理评论:\n{comment.body}")
|
||||||
|
print(f"\n评论链接:{comment.html_url}")
|
||||||
|
print(f"{'='*60}\n")
|
||||||
|
|
||||||
# 清理引用块,然后 AI 格式化内容
|
|
||||||
cleaned_body = remove_quote_blocks(comment.body)
|
cleaned_body = remove_quote_blocks(comment.body)
|
||||||
formatted_entry = get_ai_format(cleaned_body)
|
|
||||||
|
# --- 新逻辑:判断用户是否自带了 Header ---
|
||||||
|
# 匹配以 #### 开头的行
|
||||||
|
header_match = re.search(r'^####\s+.*', cleaned_body, re.MULTILINE)
|
||||||
|
|
||||||
# 准备修改 README.md
|
if header_match:
|
||||||
|
# 1. 提取用户自己写的 Header
|
||||||
|
header_line = header_match.group(0).strip()
|
||||||
|
# 2. 从正文中移除这一行,避免干扰 AI
|
||||||
|
body_for_ai = cleaned_body.replace(header_line, "").strip()
|
||||||
|
print(f"检测到用户自带 Header: {header_line}")
|
||||||
|
else:
|
||||||
|
# 1. 自动生成 Header
|
||||||
|
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}")
|
||||||
|
|
||||||
|
# 3. AI 仅处理项目详情行
|
||||||
|
project_line = get_ai_project_line(body_for_ai)
|
||||||
|
|
||||||
|
# 组合成最终条目
|
||||||
|
formatted_entry = f"{header_line}\n{project_line}"
|
||||||
|
|
||||||
|
# 4. 更新 README.md 逻辑
|
||||||
content = repo.get_contents("README.md", ref="master")
|
content = repo.get_contents("README.md", ref="master")
|
||||||
readme_text = content.decoded_content.decode("utf-8")
|
readme_text = content.decoded_content.decode("utf-8")
|
||||||
|
|
||||||
# 插入日期逻辑
|
|
||||||
today_str = datetime.now().strftime("%Y 年 %m 月 %d 号添加")
|
today_str = datetime.now().strftime("%Y 年 %m 月 %d 号添加")
|
||||||
date_header = f"### {today_str}"
|
date_header = f"### {today_str}"
|
||||||
|
|
||||||
if date_header not in readme_text:
|
if date_header not in readme_text:
|
||||||
# 在 "3. 项目列表" 下方插入新日期
|
|
||||||
new_readme = readme_text.replace("3. 项目列表\n", f"3. 项目列表\n\n{date_header}\n")
|
new_readme = readme_text.replace("3. 项目列表\n", f"3. 项目列表\n\n{date_header}\n")
|
||||||
else:
|
else:
|
||||||
new_readme = readme_text
|
new_readme = readme_text
|
||||||
|
|
||||||
# 在日期标题下插入新条目
|
|
||||||
insertion_point = new_readme.find(date_header) + len(date_header)
|
insertion_point = new_readme.find(date_header) + len(date_header)
|
||||||
final_readme = new_readme[:insertion_point] + "\n\n" + formatted_entry + new_readme[insertion_point:]
|
final_readme = new_readme[:insertion_point] + "\n\n" + formatted_entry + new_readme[insertion_point:]
|
||||||
|
|
||||||
# 创建新分支并提交 PR
|
# 5. 提交 PR 逻辑
|
||||||
branch_name = f"add-project-{comment.id}"
|
branch_name = f"add-project-{comment.id}"
|
||||||
base = repo.get_branch("master")
|
base = repo.get_branch("master")
|
||||||
|
|
||||||
# 检查分支是否已存在,如果存在则删除
|
|
||||||
try:
|
try:
|
||||||
existing_ref = repo.get_git_ref(f"heads/{branch_name}")
|
repo.get_git_ref(f"heads/{branch_name}").delete()
|
||||||
existing_ref.delete()
|
|
||||||
print(f"已删除现有分支: {branch_name}")
|
|
||||||
except:
|
except:
|
||||||
pass # 分支不存在,继续
|
pass
|
||||||
|
|
||||||
repo.create_git_ref(ref=f"refs/heads/{branch_name}", sha=base.commit.sha)
|
repo.create_git_ref(ref=f"refs/heads/{branch_name}", sha=base.commit.sha)
|
||||||
|
|
||||||
repo.update_file(
|
repo.update_file(
|
||||||
"README.md",
|
"README.md",
|
||||||
f"docs: add new project from comment {comment.id}",
|
f"docs: add project from {comment.user.login}",
|
||||||
final_readme,
|
final_readme,
|
||||||
content.sha,
|
content.sha,
|
||||||
branch=branch_name
|
branch=branch_name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 为了彻底消除 Issue 里的 "mentioned this" 红框,
|
||||||
|
# 在 https:// 后面插入一个不可见字符 \u200b
|
||||||
|
safe_url = comment.html_url.replace("https://", "https://\u200b")
|
||||||
|
|
||||||
pr = repo.create_pull(
|
pr = repo.create_pull(
|
||||||
title=f"新增项目:来自评论 {comment.id}",
|
title=f"新增项目:来自 {comment.user.login} 的评论",
|
||||||
body=f"{comment.body}\n\n---\n原始评论:`{comment.html_url}`",
|
body=f"原评论内容:```{comment.body}``` \n\n 原评论链接:{safe_url} \n\n --- \n\n 此 PR 自动生成,触发机制:Github 用户 1c7 在评论下方点击了'火箭'图标。",
|
||||||
head=branch_name,
|
head=branch_name,
|
||||||
base="master"
|
base="master"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 用表情标记为成功,并回复
|
|
||||||
comment.create_reaction(SUCCESS_EMOJI)
|
comment.create_reaction(SUCCESS_EMOJI)
|
||||||
# comment.create_comment(f"感谢提交,已添加!\n\nPR 链接:{pr.html_url}")
|
|
||||||
|
# 构建包含引用的回复评论
|
||||||
|
# reply_body = f"@{comment.user.login} 感谢提交,已添加至待审核列表!\n\nPR 链接:{pr.html_url}\n\n---\n*回复 [此评论]({comment.html_url})*"
|
||||||
|
# issue.create_comment(reply_body)
|
||||||
|
|
||||||
processed_count += 1
|
processed_count += 1
|
||||||
print(f"评论 {comment.id} 处理成功,已创建 PR。")
|
|
||||||
|
|
||||||
if processed_count == 0:
|
if processed_count == 0:
|
||||||
print("没有发现需要处理的新评论。")
|
print("未发现新标记的任务。")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
2
.github/workflows/process_list.yml
vendored
2
.github/workflows/process_list.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Run script
|
- name: Run script
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
|
||||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||||
# 如果你用的不是 OpenAI 原生接口,可以设置这个环境变量,否则默认使用 OpenAI
|
# 如果你用的不是 OpenAI 原生接口,可以设置这个环境变量,否则默认使用 OpenAI
|
||||||
LLM_BASE_URL: "https://api.deepseek.com"
|
LLM_BASE_URL: "https://api.deepseek.com"
|
||||||
|
|||||||
@@ -21,6 +21,9 @@
|
|||||||
## 3. 项目列表
|
## 3. 项目列表
|
||||||
|
|
||||||
### 2025 年 12 月 20 号添加
|
### 2025 年 12 月 20 号添加
|
||||||
|
|
||||||
|
#### hwlvipone - [Github](https://github.com/hwlvipone)
|
||||||
|
* :white_check_mark: [palm reading online](https://palm-reading.app/):在线AI看手相网站
|
||||||
#### hwlvipone - [Github](https://github.com/hwlvipone)
|
#### hwlvipone - [Github](https://github.com/hwlvipone)
|
||||||
* :white_check_mark: [ZestyGen](https://zestygen.com/):基于 Nano Banana Pro 的图片视频聚合网站
|
* :white_check_mark: [ZestyGen](https://zestygen.com/):基于 Nano Banana Pro 的图片视频聚合网站
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user