@@ -58,7 +58,11 @@ def handle(self, *args, **options):
5858 self .stdout .write (self .style .ERROR ("Failed to download awards.yml from GitHub" ))
5959 return
6060
61- awards_data = yaml .safe_load (yaml_content )
61+ try :
62+ awards_data = yaml .safe_load (yaml_content )
63+ except yaml .YAMLError as e :
64+ self .stdout .write (self .style .ERROR (f"Failed to parse awards.yml: { e } " ))
65+ return
6266
6367 if not awards_data :
6468 self .stdout .write (self .style .ERROR ("Failed to parse awards.yml content" ))
@@ -75,6 +79,10 @@ def handle(self, *args, **options):
7579 # Print summary
7680 self ._print_summary ()
7781
82+ # Update badges after successful sync
83+ if not options ["dry_run" ]:
84+ self ._update_badges ()
85+
7886 except Exception as e :
7987 logger .exception ("Error syncing awards" )
8088 self .stdout .write (self .style .ERROR (f"Error syncing awards: { e !s} " ))
@@ -90,6 +98,7 @@ def _process_award(self, award_data: dict, *, dry_run: bool = False):
9098 award_name = award_data .get ("title" , "" )
9199 category = award_data .get ("category" , "" )
92100 year = award_data .get ("year" )
101+ award_description = award_data .get ("description" , "" ) or ""
93102 winners = award_data .get ("winners" , [])
94103
95104 if not award_name or not category or not year :
@@ -106,6 +115,7 @@ def _process_award(self, award_data: dict, *, dry_run: bool = False):
106115 "name" : winner_data .get ("name" , "" ),
107116 "info" : winner_data .get ("info" , "" ),
108117 "image" : winner_data .get ("image" , "" ),
118+ "description" : award_description ,
109119 }
110120
111121 self ._process_winner (winner_with_context , dry_run = dry_run )
@@ -138,6 +148,8 @@ def _process_winner(self, winner_data: dict, *, dry_run: bool = False):
138148 is_new = False
139149 except Award .DoesNotExist :
140150 is_new = True
151+ except Award .MultipleObjectsReturned :
152+ is_new = False
141153
142154 # Use the model's update_data method
143155 award = Award .update_data (winner_data , save = True )
@@ -239,15 +251,29 @@ def _extract_github_username(self, text: str) -> str | None:
239251 if not text :
240252 return None
241253
242- # Pattern 1: github.com/username
243- github_url_pattern = r"github\.com/([a-zA-Z0-9\-_]+)"
254+ # Pattern 1: github.com/<username> (exclude known non-user segments)
255+ excluded = {
256+ "orgs" ,
257+ "organizations" ,
258+ "topics" ,
259+ "enterprise" ,
260+ "marketplace" ,
261+ "settings" ,
262+ "apps" ,
263+ "features" ,
264+ "pricing" ,
265+ "sponsors" ,
266+ }
267+ github_url_pattern = r"(?:https?://)?(?:www\.)?github\.com/([A-Za-z0-9-]+)(?=[/\s]|$)"
244268 match = re .search (github_url_pattern , text , re .IGNORECASE )
245269 if match :
246- return match .group (1 )
270+ candidate = match .group (1 )
271+ if candidate .lower () not in excluded :
272+ return candidate
247273
248- # Pattern 2: @username mentions
249- mention_pattern = r"@([a-zA-Z0-9\-_ ]+)"
250- match = re .search (mention_pattern , text )
274+ # Pattern 2: @username mentions (avoid emails/local-parts)
275+ mention_pattern = r"(?<![A-Za-z0-9._%+-]) @([A-Za-z0-9- ]+)\b "
276+ match = re .search (mention_pattern , text , re . IGNORECASE )
251277 if match :
252278 return match .group (1 )
253279
@@ -259,31 +285,25 @@ def _generate_potential_logins(self, name: str, min_login_length: int = 2) -> li
259285 return []
260286
261287 potential_logins = []
262- clean_name = re .sub (r"[^a-zA-Z0-9\s\-_ ]" , "" , name ).strip ()
288+ clean_name = re .sub (r"[^a-zA-Z0-9\s\-]" , "" , name ).strip ()
263289
264290 # Convert to lowercase and replace spaces
265291 base_variations = [
266292 clean_name .lower ().replace (" " , "" ),
267293 clean_name .lower ().replace (" " , "-" ),
268- clean_name .lower ().replace (" " , "_" ),
269294 ]
270295
271296 # Add variations with different cases
272297 for variation in base_variations :
273- potential_logins .extend (
274- [
275- variation ,
276- variation .replace ("-" , "" ),
277- variation .replace ("_" , "" ),
278- variation .replace ("-" , "_" ),
279- variation .replace ("_" , "-" ),
280- ]
281- )
298+ potential_logins .extend ([variation , variation .replace ("-" , "" )])
282299
283300 # Remove duplicates while preserving order
284301 seen = set ()
285302 unique_logins = []
286303 for login in potential_logins :
304+ # Skip invalid characters for GitHub logins
305+ if "_" in login :
306+ continue
287307 if login and login not in seen and len (login ) >= min_login_length :
288308 seen .add (login )
289309 unique_logins .append (login )
@@ -306,3 +326,15 @@ def _print_summary(self):
306326 self .stdout .write (f" - { name } " )
307327
308328 self .stdout .write ("\n Sync completed successfully!" )
329+
330+ def _update_badges (self ):
331+ """Update user badges based on synced awards."""
332+ from django .core .management import call_command
333+
334+ self .stdout .write ("Updating user badges..." )
335+ try :
336+ call_command ("owasp_update_badges" )
337+ self .stdout .write ("Badge update completed successfully!" )
338+ except Exception as e :
339+ logger .exception ("Error updating badges" )
340+ self .stdout .write (self .style .ERROR (f"Error updating badges: { e !s} " ))
0 commit comments