1414 is_org_member ,
1515 add_user_to_team ,
1616 is_user_in_team ,
17+ list_issue_comments ,
1718 list_open_join_issues ,
1819 update_issue_title ,
1920)
2021from libs .utils import load_simple_yaml
2122
23+ def _normalize_list (value ):
24+ if not value :
25+ return []
26+ if isinstance (value , list ):
27+ return [str (v ).strip () for v in value if str (v ).strip ()]
28+ return [str (value ).strip ()]
29+
30+ def _is_approval_comment (body , keywords ):
31+ if not body :
32+ return False
33+ text = body .lower ()
34+ for kw in keywords :
35+ if kw and kw .lower () in text :
36+ return True
37+ return False
38+
39+ def _is_issue_approved (token , repo , org , issue , team_cfg , team_slug , verbose = False ):
40+ reviewers = team_cfg .get ("reviewers" ) or {}
41+ required_users = _normalize_list (reviewers .get ("users" ))
42+ required_teams = _normalize_list (reviewers .get ("teams" ))
43+
44+ approval_label = team_cfg .get ("approval_label" ) or team_cfg .get ("approved_label" )
45+ if approval_label and has_label (issue , approval_label ):
46+ return True
47+
48+ approval_keywords = _normalize_list (team_cfg .get ("approval_keywords" ))
49+ if not approval_keywords :
50+ approval_keywords = ["/approve" , "approve" , "approved" , "lgtm" , "同意" , "批准" , "通过" , "已批准" ]
51+
52+ if not required_users and not required_teams :
53+ if verbose :
54+ print (" ⊘ 跳过: 未配置审核人/团队" )
55+ return False
56+
57+ try :
58+ comments = list_issue_comments (token , repo , issue ["number" ])
59+ except RuntimeError as e :
60+ if verbose :
61+ print (f" ✗ 获取评论失败: { e } " )
62+ return False
63+
64+ approvals = set ()
65+ for c in comments :
66+ body = c .get ("body" , "" )
67+ if not _is_approval_comment (body , approval_keywords ):
68+ continue
69+ author = c .get ("user" , {}).get ("login" , "" )
70+ if author :
71+ approvals .add (author )
72+
73+ # Check required teams approvals (need at least one member approval per team)
74+ team_member_cache = {}
75+ for team in required_teams :
76+ approved_by_team = False
77+ for approver in approvals :
78+ cache_key = (team , approver )
79+ if cache_key not in team_member_cache :
80+ team_member_cache [cache_key ] = is_user_in_team (token , org , team , approver )
81+ if team_member_cache [cache_key ]:
82+ approved_by_team = True
83+ break
84+ if not approved_by_team :
85+ if verbose :
86+ print (f" ⊘ 跳过: 缺少团队 @{ org } /{ team } 成员的批准" )
87+ return False
88+
89+ # Check required users approvals
90+ for user in required_users :
91+ if user not in approvals :
92+ if verbose :
93+ print (f" ⊘ 跳过: 缺少审核人 @{ user } 的批准" )
94+ return False
95+
96+ return True
97+
2298def scan (verbose = False ):
2399 # Load configuration first
24100 config_path = Path (__file__ ).parent .parent / "config" / "join-config.yml"
@@ -57,6 +133,7 @@ def scan(verbose=False):
57133 "title_updated" : 0 ,
58134 "not_member_yet" : 0 ,
59135 "reminder_sent" : 0 ,
136+ "waiting_approval" : 0 ,
60137 "team_added" : 0 ,
61138 "team_add_failed" : 0 ,
62139 "completed" : 0 ,
@@ -92,6 +169,7 @@ def scan(verbose=False):
92169
93170 team_cfg = teams_cfg [target ]
94171 team_slug = team_cfg .get ("team_slug" , "" ) or ""
172+ team_mode = team_cfg .get ("mode" , "auto" )
95173
96174 if verbose :
97175 print (f" 目标团队: { target } (team_slug={ team_slug } )" )
@@ -114,6 +192,14 @@ def scan(verbose=False):
114192 if verbose :
115193 print (f" ✓ @{ author } 已是组织成员" )
116194
195+ # If approval required, ensure it is approved before adding to team
196+ if team_mode == "approval" and team_slug :
197+ if not _is_issue_approved (token , repo , org , it , team_cfg , team_slug , verbose = verbose ):
198+ summary ["waiting_approval" ] += 1
199+ if verbose :
200+ print (f" ⊘ 跳过: @{ author } 等待审核\n " )
201+ continue
202+
117203 # If needs team, ensure team membership
118204 if team_slug :
119205 if not is_user_in_team (token , org , team_slug , author ):
0 commit comments