from typing import Dict, List import focs_gitea from canvasapi.paginated_list import PaginatedList from mattermostdriver import Driver from joint_teapot.config import settings from joint_teapot.utils.logger import logger from joint_teapot.workers.gitea import Gitea class Mattermost: def __init__( self, access_token: str = "", # nosec team_name: str = "", domain_name: str = "", suffix: str = "", ): access_token = access_token or settings.mattermost_access_token team_name = team_name or settings.mattermost_team domain_name = domain_name or settings.mattermost_domain_name suffix = suffix or settings.mattermost_suffix self.url = domain_name self.url_suffix = suffix self.endpoint = Driver( { "url": domain_name, "port": 443, "basepath": suffix + "/api/v4", "token": access_token, } ) try: operator = self.endpoint.login() except Exception: logger.error("Cannot login to Mattermost") return if "admin" not in operator["roles"] and "system_user" not in operator["roles"]: logger.error("Please make sure you have enough permission") try: self.team = self.endpoint.teams.get_team_by_name(team_name) except Exception as e: logger.error(f"Cannot get team {team_name}: {e}") return def create_channels_for_groups( self, groups: Dict[str, List[str]], suffix: str = "", invite_teaching_team: bool = True, ) -> None: for group_name, members in groups.items(): channel_name = group_name + suffix try: channel = self.endpoint.channels.create_channel( { "team_id": self.team["id"], "name": channel_name, "display_name": channel_name, "type": "P", # create private channels } ) logger.info(f"Added group {channel_name} to Mattermost") except Exception as e: logger.warning( f"Error when creating channel {channel_name}: {e} Perhaps channel already exists?" ) continue if invite_teaching_team: members.extend(settings.mattermost_teaching_team) for member in members: try: mmuser = self.endpoint.users.get_user_by_username(member) except Exception: logger.warning( f"User {member} is not found on the Mattermost server" ) self.endpoint.posts.create_post( { "channel_id": channel["id"], "message": f"User {member} is not found on the Mattermost server", } ) continue # code for adding student to mm, disabled since there is no need to do that # try: # mmuser = self.endpoint.users.create_user({'email':f"{member}@sjtu.edu.cn", 'username':member, auth_service:"gitlab"}) # except e: # logger.error(f"Error creating user {member}") # continue try: self.endpoint.channels.add_user( channel["id"], {"user_id": mmuser["id"]} ) except Exception: logger.warning(f"User {member} is not in the team") self.endpoint.posts.create_post( { "channel_id": channel["id"], "message": f"User {member} is not in the team", } ) logger.info(f"Added member {member} to channel {channel_name}") def update_channels_for_groups( self, groups: Dict[str, List[str]], suffix: str = "", update_teaching_team: bool = True, dry_run: bool = False, ) -> None: for group_name, members in groups.items(): channel_name = group_name + suffix try: channel = self.endpoint.channels.get_channel_by_name(self.team["id"], channel_name) logger.info(f"Channel {channel_name} exists, updating members") except Exception: # channel does not exist if dry_run: info_members = list(members) if update_teaching_team: info_members = info_members + settings.mattermost_teaching_team logger.info(f"Dry run: would create channel {channel_name} and add members: {info_members}") continue try: channel = self.endpoint.channels.create_channel( { "team_id": self.team["id"], "name": channel_name, "display_name": channel_name, "type": "P", } ) logger.info(f"Created channel {channel_name} on Mattermost") except Exception as e: logger.warning( f"Error when creating channel {channel_name}: {e} Perhaps channel already exists?" ) continue current_members = set() try: mm_members = self.endpoint.channels.get_channel_members(channel["id"]) or [] for m in mm_members: uname = None if isinstance(m, dict): uname = m.get("username") or m.get("name") if not uname and "user" in m and isinstance(m["user"], dict): uname = m["user"].get("username") or m["user"].get("name") if not uname and "user_id" in m: try: u = self.endpoint.users.get_user(m["user_id"]) or {} if isinstance(u, dict): uname = u.get("username") or u.get("name") except Exception: uname = None if uname: current_members.add(uname.lower()) except Exception: current_members = set() add_members = list(members) if update_teaching_team: add_members = add_members + settings.mattermost_teaching_team for member in add_members: if member.lower() in current_members: logger.debug(f"Member {member} already in channel {channel_name}") continue if dry_run: logger.info(f"Dry run: would add member {member} to channel {channel_name}") continue try: mmuser = self.endpoint.users.get_user_by_username(member) except Exception: logger.warning(f"User {member} is not found on the Mattermost server") self.endpoint.posts.create_post( {"channel_id": channel["id"], "message": f"User {member} is not found on the Mattermost server"} ) continue try: self.endpoint.channels.add_user(channel["id"], {"user_id": mmuser["id"]}) except Exception: logger.warning(f"User {member} is not in the team") self.endpoint.posts.create_post( {"channel_id": channel["id"], "message": f"User {member} is not in the team"} ) logger.info(f"Added member {member} to channel {channel_name}") def create_channels_for_individuals( self, students: PaginatedList, invite_teaching_team: bool = True, ) -> None: for student in students: display_name = student.name channel_name = student.sis_id try: channel = self.endpoint.channels.create_channel( { "team_id": self.team["id"], "name": channel_name, "display_name": display_name, "type": "P", # create private channels } ) logger.info( f"Added channel {display_name} ({channel_name}) to Mattermost" ) except Exception as e: logger.warning( f"Error when creating channel {channel_name}: {e} Perhaps channel already exists?" ) continue members = [student.login_id] if invite_teaching_team: members.extend(settings.mattermost_teaching_team) for member in members: try: mmuser = self.endpoint.users.get_user_by_username(member) except Exception: logger.warning( f"User {member} is not found on the Mattermost server" ) self.endpoint.posts.create_post( { "channel_id": channel["id"], "message": f"User {member} is not found on the Mattermost server", } ) continue # code for adding student to mm, disabled since there is no need to do that # try: # mmuser = self.endpoint.users.create_user({'email':f"{member}@sjtu.edu.cn", 'username':member, auth_service:"gitlab"}) # except e: # logger.error(f"Error creating user {member}") # continue try: self.endpoint.channels.add_user( channel["id"], {"user_id": mmuser["id"]} ) except Exception: logger.warning(f"User {member} is not in the team") self.endpoint.posts.create_post( { "channel_id": channel["id"], "message": f"User {member} is not in the team", } ) logger.info(f"Added member {member} to channel {channel_name}") def update_channels_for_individuals( self, students: PaginatedList, update_teaching_team: bool = True, dry_run: bool = False, ) -> None: for student in students: display_name = student.name channel_name = student.sis_id try: channel = self.endpoint.channels.get_channel_by_name(self.team["id"], channel_name) logger.info(f"Channel {channel_name} exists, updating members") except Exception: if dry_run: members_info = [student.login_id] if update_teaching_team: members_info = members_info + settings.mattermost_teaching_team logger.info(f"Dry run: would create channel {display_name} ({channel_name}) and add members: {members_info}") continue try: channel = self.endpoint.channels.create_channel( { "team_id": self.team["id"], "name": channel_name, "display_name": display_name, "type": "P", } ) logger.info(f"Created channel {display_name} ({channel_name}) on Mattermost") except Exception as e: logger.warning( f"Error when creating channel {channel_name}: {e} Perhaps channel already exists?" ) continue current_members = set() try: mm_members = self.endpoint.channels.get_channel_members(channel["id"]) or [] for m in mm_members: uname = None if isinstance(m, dict): uname = m.get("username") or m.get("name") if not uname and "user" in m and isinstance(m["user"], dict): uname = m["user"].get("username") or m["user"].get("name") if not uname and "user_id" in m: try: u = self.endpoint.users.get_user(m["user_id"]) or {} if isinstance(u, dict): uname = u.get("username") or u.get("name") except Exception: uname = None if uname: current_members.add(uname.lower()) except Exception: current_members = set() members = [student.login_id] if update_teaching_team: members = members + settings.mattermost_teaching_team for member in members: if member.lower() in current_members: logger.debug(f"Member {member} already in channel {channel_name}") continue if dry_run: logger.info(f"Dry run: would add member {member} to channel {channel_name}") continue try: mmuser = self.endpoint.users.get_user_by_username(member) except Exception: logger.warning(f"User {member} is not found on the Mattermost server") self.endpoint.posts.create_post( {"channel_id": channel["id"], "message": f"User {member} is not found on the Mattermost server"} ) continue try: self.endpoint.channels.add_user(channel["id"], {"user_id": mmuser["id"]}) except Exception: logger.warning(f"User {member} is not in the team") self.endpoint.posts.create_post( {"channel_id": channel["id"], "message": f"User {member} is not in the team"} ) logger.info(f"Added member {member} to channel {channel_name}") def create_webhooks_for_repos( self, repos: List[str], gitea: Gitea, gitea_suffix: bool ) -> None: # one group corresponds to one repo so these concepts can be used interchangeably for repo in repos: channel_name = f"{repo}-gitea" if gitea_suffix else repo logger.info( f"Creating webhooks for repo {gitea.org_name}/{repo} and channel {channel_name}" ) try: mm_channel = self.endpoint.channels.get_channel_by_name( self.team["id"], channel_name ) except Exception as e: logger.warning( f"Error when getting channel {channel_name} from Mattermost team {self.team['name']}: {e}" ) continue try: mm_webhook = self.endpoint.webhooks.create_incoming_hook( { "channel_id": mm_channel["id"], "display_name": f"Gitea integration for {self.team['name']}/{repo}", "channel_locked": True, } ) except Exception as e: logger.error(f"Error when creating incoming webhook at Mattermost: {e}") continue try: gitea.repository_api.repo_create_hook( gitea.org_name, repo, body=focs_gitea.CreateHookOption( active=True, type="slack", events=[ "issues_only", "issue_comment", "issue_assign", "pull_request_only", "pull_request_comment", "pull_request_review", "pull_request_review_request", "push", "create", "delete", "release", "wiki", ], config={ "url": f"https://{self.url}{self.url_suffix}/hooks/{mm_webhook['id']}", "username": "FOCS Gitea", "icon_url": f"https://{self.url}{self.url_suffix}/api/v4/brand/image", "content_type": "json", "channel": channel_name, }, ), ) except Exception as e: logger.warning(f"Error when creating outgoing webhook at Gitea: {e}") # unused since we can give students invitation links instead def invite_students_to_team(self, students: List[str]) -> None: for student in students: try: mmuser = self.endpoint.users.get_user_by_username(student) except Exception: logger.warning(f"User {student} is not found on the Mattermost server") continue self.endpoint.teams.add_user_to_team( self.team["id"], {"user_id": mmuser["id"], "team_id": self.team["id"]} ) logger.info(f"Added user {student} to team {self.team['name']}")