@@ -390,13 +390,57 @@ async def _notify_failed_invites(self, skipped_failed: list[str]) -> None:
390390 f"`{ username } ` because a failed invitation record already exists."
391391 )
392392
393- async def _apply_org_additions (self , usernames : list [str ]) -> list [str ]:
393+ async def _try_dm_user_invite (self , github_username : str , keycloak_user : dict | None ) -> None :
394+ """DM a user using their Discord ID from Keycloak."""
395+ if not keycloak_user :
396+ logger .debug (f"No Keycloak user data for GitHub username { github_username } " )
397+ return
398+
399+ # Extract Discord ID from Keycloak attributes
400+ discord_ids = keycloak_user .get ("attributes" , {}).get ("discordId" )
401+ if not discord_ids or not isinstance (discord_ids , list ) or not discord_ids :
402+ logger .debug (f"No Discord ID found in Keycloak for { keycloak_user .get ('username' , 'unknown' )} (GitHub: { github_username } )" )
403+ return
404+
405+ try :
406+ discord_id = int (discord_ids [0 ])
407+ member = self .bot .get_user (discord_id ) or await self .bot .fetch_user (discord_id )
408+
409+ await member .send (
410+ f"You've been invited to join the { CONFIG .github_org } GitHub organization!\n \n "
411+ f"Accept your invitation here: https://github.com/orgs/{ CONFIG .github_org } /invitation"
412+ )
413+ except ValueError :
414+ logger .debug (f"Invalid Discord ID in Keycloak: { discord_ids [0 ]} " )
415+ except discord .Forbidden :
416+ logger .debug (f"Could not DM user { discord_id } - DMs disabled" )
417+ except discord .HTTPException as e :
418+ logger .opt (exception = e ).warning (f"Failed to DM user { discord_id } " )
419+ except Exception as e : # noqa: BLE001
420+ logger .opt (exception = e ).warning (f"Error sending DM invite to { github_username } " )
421+
422+ async def _apply_org_additions (
423+ self ,
424+ usernames : list [str ],
425+ keycloak_identities : dict [str , dict [str , str ]],
426+ resolved_logins_by_user_id : dict [str , str ],
427+ ) -> list [str ]:
394428 """Apply organisation additions and return successfully added logins."""
429+ # Build reverse map: GitHub login -> Keycloak user data
430+ github_login_to_keycloak = {}
431+ for identity in keycloak_identities .values ():
432+ user_id = identity .get ("user_id" , "" ).strip ()
433+ if user_id in resolved_logins_by_user_id :
434+ github_login = resolved_logins_by_user_id [user_id ]
435+ github_login_to_keycloak [github_login ] = identity
436+
395437 added = []
396438 for username in usernames :
397439 try :
398440 await add_org_member (username )
399441 added .append (username )
442+ keycloak_user = github_login_to_keycloak .get (username )
443+ await self ._try_dm_user_invite (username , keycloak_user )
400444 except GitHubError as e :
401445 logger .opt (exception = e ).error (f"GitHub: Failed to add { username } to org" )
402446
@@ -526,7 +570,11 @@ async def _sync_github_members(
526570 await self ._report_org_sync_plan (report_thread , plan )
527571 await self ._notify_failed_invites (plan .skipped_failed )
528572
529- added = await self ._apply_org_additions (plan .diff .to_add )
573+ added = await self ._apply_org_additions (
574+ plan .diff .to_add ,
575+ common_info .keycloak_identities ,
576+ common_info .resolved_logins_by_user_id ,
577+ )
530578 removed = await self ._apply_org_removals (plan .diff .to_remove )
531579
532580 return added , removed
0 commit comments