def get_standup_members(server_config: Dict) -> List[str]: d = Driver(server_config) d.login() channel_id = d.channels.get_channel_by_name_and_team_name( 'team_name', 'channel')['id'] channel_members = d.channels.get_channel_members(channel_id) d.logout() return channel_members
def post_mattermost(msg, hashtag="", data=None): """ Post a message to a mattermost server. Optionally pass a dictionary to print as a table. All messages prefixed with #API :param msg: String message :param hashtag: e.g. #Oncore :param data: Dictionary :return: """ try: driver = Driver( dict(url=MATTERMOST_URL, login_id=MATTERMOST_USER, password=MATTERMOST_PW, scheme='https', verify=False, port=443)) if data is not None: msg = f'| #API {hashtag} | {msg} |\n' \ f'| :--- | :--- |\n' for k in data: v = data[k] if isinstance(v, str) and v is not "": msg += f'| {k} | {v} |\n' elif isinstance(v, dict): for inner_key in v: inner_val = v[inner_key] if isinstance(inner_val, str) and inner_val is not "": msg += f'| {inner_key} | {v[inner_key]} |\n' else: msg = f'#API {hashtag} {msg}' driver.login() channel_id = driver.channels.get_channel_by_name_and_team_name( MATTERMOST_TEAM, MATTERMOST_CHANNEL)['id'] driver.posts.create_post(options={ 'channel_id': channel_id, 'message': msg }) driver.logout() except Exception as e: logging.error("Error while posting to mattermost") logging.error(e)
class Connection: """ This class allows the user to connect to a Mattermost server and start the bot. There should be only one instance per program.""" instance = None def __init__(self, settings): assert Connection.instance is None self.driver = Driver(settings) #self.driver.client.activate_verbose_logging() self.bot = Bot(convert_to_mmpy_bot(settings)) Connection.instance = self # workaround for python versions < 3.7 if sys.version_info.major < 3 or sys.version_info.minor < 7: api.load_driver_attributes() def __getattr__(self, name): return getattr(self.driver, name) def login(self): logger.info("login...") self.driver.login() api.me = api.User.by_id(self.driver.client.userid) logger.info(f"logged in as {api.me}") def start(self): # start bot at the end because the method will not return unless an exception occurs logger.info("start bot...") self.bot.run() logger.info("bot started") def stop(self): self.driver.logout() # TODO stop bot def __enter__(self): self.start() return self def __exit__(self, type, value, tb): self.stop()
class ConnectorMattermost(Connector): """A connector for Mattermost.""" def __init__(self, config, opsdroid=None): """Create the connector.""" super().__init__(config, opsdroid=opsdroid) _LOGGER.debug(_("Starting Mattermost connector")) self.name = config.get("name", "mattermost") self.token = config["token"] self.url = config["url"] self.team_name = config["team-name"] self.scheme = config.get("scheme", "https") self.port = config.get("port", 8065) self.verify = config.get("ssl-verify", True) self.timeout = config.get("connect-timeout", 30) self.request_timeout = None self.mfa_token = None self.debug = False self.listening = True self.bot_id = None self.mm_driver = Driver({ "url": self.url, "token": self.token, "scheme": self.scheme, "port": self.port, "verify": self.verify, "timeout": self.timeout, "request_timeout": self.request_timeout, "mfa_token": self.mfa_token, "debug": self.debug, }) async def connect(self): """Connect to the chat service.""" _LOGGER.info(_("Connecting to Mattermost")) login_response = self.mm_driver.login() _LOGGER.info(login_response) if "id" in login_response: self.bot_id = login_response["id"] if "username" in login_response: self.bot_name = login_response["username"] _LOGGER.info(_("Connected as %s"), self.bot_name) self.mm_driver.websocket = Websocket(self.mm_driver.options, self.mm_driver.client.token) _LOGGER.info(_("Connected successfully")) async def disconnect(self): """Disconnect from Mattermost.""" self.listening = False self.mm_driver.logout() async def listen(self): """Listen for and parse new messages.""" await self.mm_driver.websocket.connect(self.process_message) async def process_message(self, raw_message): """Process a raw message and pass it to the parser.""" _LOGGER.info(raw_message) message = json.loads(raw_message) if "event" in message and message["event"] == "posted": data = message["data"] post = json.loads(data["post"]) # if connected to Mattermost, don't parse our own messages # (https://github.com/opsdroid/opsdroid/issues/1775) if self.bot_id is None or self.bot_id != post["user_id"]: await self.opsdroid.parse( Message( text=post["message"], user=data["sender_name"], target=data["channel_name"], connector=self, raw_event=message, )) @register_event(Message) async def send_message(self, message): """Respond with a message.""" _LOGGER.debug(_("Responding with: '%s' in room %s"), message.text, message.target) channel_id = self.mm_driver.channels.get_channel_by_name_and_team_name( self.team_name, message.target)["id"] self.mm_driver.posts.create_post(options={ "channel_id": channel_id, "message": message.text })
class MattermostBackend(ErrBot): def __init__(self, config): super().__init__(config) identity = config.BOT_IDENTITY self._login = identity.get('login', None) self._password = identity.get('password', None) self._personal_access_token = identity.get('token', None) self._mfa_token = identity.get('mfa_token', None) self.team = identity.get('team') self._scheme = identity.get('scheme', 'https') self._port = identity.get('port', 8065) self.cards_hook = identity.get('cards_hook', None) self.url = identity.get('server').rstrip('/') self.insecure = identity.get('insecure', False) self.timeout = identity.get('timeout', DEFAULT_TIMEOUT) self.teamid = '' self.token = '' self.bot_identifier = None self.driver = None self.md = md() @property def userid(self): return "{}".format(self.bot_identifier.userid) @property def mode(self): return 'mattermost' def username_to_userid(self, name): """Converts a name prefixed with @ to the userid""" name = name.lstrip('@') user = self.driver.users.get_user_by_username(username=name) if user is None: raise UserDoesNotExistError("Cannot find user {}".format(name)) return user['id'] @asyncio.coroutine def mattermost_event_handler(self, payload): if not payload: return payload = json.loads(payload) if 'event' not in payload: log.debug("Message contains no event: {}".format(payload)) return event_handlers = { 'posted': self._message_event_handler, 'status_change': self._status_change_event_handler, 'hello': self._hello_event_handler, 'user_added': self._room_joined_event_handler, 'user_removed': self._room_left_event_handler, } event = payload['event'] event_handler = event_handlers.get(event) if event_handler is None: log.debug("No event handler available for {}, ignoring.".format(event)) return # noinspection PyBroadException try: event_handler(payload) except Exception: log.exception("{} event handler raised an exception".format(event)) def _room_joined_event_handler(self, message): log.debug('User added to channel') if message['data']['user_id'] == self.userid: self.callback_room_joined(self) def _room_left_event_handler(self, message): log.debug('User removed from channel') if message['broadcast']['user_id'] == self.userid: self.callback_room_left(self) def _message_event_handler(self, message): log.debug(message) data = message['data'] # In some cases (direct messages) team_id is an empty string if data['team_id'] != '' and self.teamid != data['team_id']: log.info("Message came from another team ({}), ignoring...".format(data['team_id'])) return broadcast = message['broadcast'] if 'channel_id' in data: channelid = data['channel_id'] elif 'channel_id' in broadcast: channelid = broadcast['channel_id'] else: log.error("Couldn't find a channelid for event {}".format(message)) return channel_type = data['channel_type'] if channel_type != 'D': channel = data['channel_name'] else: channel = channelid text = '' post_id = '' file_ids = None userid = None if 'post' in data: post = json.loads(data['post']) text = post['message'] userid = post['user_id'] if 'file_ids' in post: file_ids = post['file_ids'] post_id = post['id'] if 'type' in post and post['type'] == 'system_add_remove': log.info("Ignoring message from System") return if 'user_id' in data: userid = data['user_id'] if not userid: log.error('No userid in event {}'.format(message)) return mentions = [] if 'mentions' in data: # TODO: Only user, not channel mentions are in here at the moment mentions = self.mentions_build_identifier(json.loads(data['mentions'])) # Thread root post id root_id = post.get('root_id') if root_id is '': root_id = post_id msg = Message( text, extras={ 'id': post_id, 'root_id': root_id, 'mattermost_event': message, 'url': '{scheme:s}://{domain:s}:{port:s}/{teamname:s}/pl/{postid:s}'.format( scheme=self.driver.options['scheme'], domain=self.driver.options['url'], port=str(self.driver.options['port']), teamname=self.team, postid=post_id ) } ) if file_ids: msg.extras['attachments'] = file_ids # TODO: Slack handles bots here, but I am not sure if bot users is a concept in mattermost if channel_type == 'D': msg.frm = MattermostPerson(self.driver, userid=userid, channelid=channelid, teamid=self.teamid) msg.to = MattermostPerson( self.driver, userid=self.bot_identifier.userid, channelid=channelid, teamid=self.teamid) elif channel_type == 'O' or channel_type == 'P': msg.frm = MattermostRoomOccupant(self.driver, userid=userid, channelid=channelid, teamid=self.teamid, bot=self) msg.to = MattermostRoom(channel, teamid=self.teamid, bot=self) else: log.warning('Unknown channel type \'{}\'! Unable to handle {}.'.format( channel_type, channel )) return self.callback_message(msg) if mentions: self.callback_mention(msg, mentions) def _status_change_event_handler(self, message): """Event handler for the 'presence_change' event""" idd = MattermostPerson(self.driver, message['data']['user_id']) status = message['data']['status'] if status == 'online': status = ONLINE elif status == 'away': status = AWAY else: log.error( "It appears the Mattermost API changed, I received an unknown status type %s" % status ) status = ONLINE self.callback_presence(Presence(identifier=idd, status=status)) def _hello_event_handler(self, message): """Event handler for the 'hello' event""" self.connect_callback() self.callback_presence(Presence(identifier=self.bot_identifier, status=ONLINE)) @lru_cache(1024) def get_direct_channel(self, userid, other_user_id): """ Get the direct channel to another user. If it does not exist, it will be created. """ try: return self.driver.channels.create_direct_message_channel(options=[userid, other_user_id]) except (InvalidOrMissingParameters, NotEnoughPermissions): raise RoomDoesNotExistError("Could not find Direct Channel for users with ID {} and {}".format( userid, other_user_id )) def build_identifier(self, txtrep): """ Convert a textual representation into a :class:`~MattermostPerson` or :class:`~MattermostRoom` Supports strings with the following formats:: @username ~channelname channelid """ txtrep = txtrep.strip() if txtrep.startswith('~'): # Channel channelid = self.channelname_to_channelid(txtrep[1:]) if channelid is not None: return MattermostRoom(channelid=channelid, teamid=self.teamid, bot=self) else: # Assuming either a channelid or a username if txtrep.startswith('@'): # Username userid = self.username_to_userid(txtrep[1:]) else: # Channelid userid = txtrep if userid is not None: return MattermostPerson( self.driver, userid=userid, channelid=self.get_direct_channel(self.userid, userid)['id'], teamid=self.teamid ) raise Exception( 'Invalid or unsupported Mattermost identifier: %s' % txtrep ) def mentions_build_identifier(self, mentions): identifier = [] for mention in mentions: if mention != self.bot_identifier.userid: identifier.append( self.build_identifier(mention) ) return identifier def serve_once(self): self.driver = Driver({ 'scheme': self._scheme, 'url': self.url, 'port': self._port, 'verify': not self.insecure, 'timeout': self.timeout, 'login_id': self._login, 'password': self._password, 'token': self._personal_access_token, 'mfa_token': self._mfa_token }) self.driver.login() self.teamid = self.driver.teams.get_team_by_name(name=self.team)['id'] userid = self.driver.users.get_user(user_id='me')['id'] self.token = self.driver.client.token self.bot_identifier = MattermostPerson(self.driver, userid=userid, teamid=self.teamid) # noinspection PyBroadException try: loop = self.driver.init_websocket(event_handler=self.mattermost_event_handler) self.reset_reconnection_count() loop.run_forever() except KeyboardInterrupt: log.info("Interrupt received, shutting down..") return True except Exception: log.exception("Error reading from RTM stream:") finally: log.debug("Triggering disconnect callback") self.disconnect_callback() def _prepare_message(self, message): to_name = "<unknown>" if message.is_group: to_channel_id = message.to.id if message.to.name: to_name = message.to.name else: self.channelid_to_channelname(channelid=to_channel_id) else: to_name = message.to.username if isinstance(message.to, RoomOccupant): # private to a room occupant -> this is a divert to private ! log.debug("This is a divert to private message, sending it directly to the user.") channel = self.get_direct_channel(self.userid, self.username_to_userid(to_name)) to_channel_id = channel['id'] else: to_channel_id = message.to.channelid return to_name, to_channel_id def send_message(self, message): super().send_message(message) try: to_name, to_channel_id = self._prepare_message(message) message_type = "direct" if message.is_direct else "channel" log.debug('Sending %s message to %s (%s)' % (message_type, to_name, to_channel_id)) body = self.md.convert(message.body) log.debug('Message size: %d' % len(body)) limit = min(self.bot_config.MESSAGE_SIZE_LIMIT, MATTERMOST_MESSAGE_LIMIT) parts = self.prepare_message_body(body, limit) root_id = None if message.parent is not None: root_id = message.parent.extras.get('root_id') for part in parts: self.driver.posts.create_post(options={ 'channel_id': to_channel_id, 'message': part, 'root_id': root_id, }) except (InvalidOrMissingParameters, NotEnoughPermissions): log.exception( "An exception occurred while trying to send the following message " "to %s: %s" % (to_name, message.body) ) def send_card(self, card: Card): if isinstance(card.to, RoomOccupant): card.to = card.to.room to_humanreadable, to_channel_id = self._prepare_message(card) attachment = {} if card.summary: attachment['pretext'] = card.summary if card.title: attachment['title'] = card.title if card.link: attachment['title_link'] = card.link if card.image: attachment['image_url'] = card.image if card.thumbnail: attachment['thumb_url'] = card.thumbnail attachment['text'] = card.body if card.color: attachment['color'] = COLORS[card.color] if card.color in COLORS else card.color if card.fields: attachment['fields'] = [{'title': key, 'value': value, 'short': True} for key, value in card.fields] data = { 'attachments': [attachment] } if card.to: if isinstance(card.to, MattermostRoom): data['channel'] = card.to.name try: log.debug('Sending data:\n%s', data) # We need to send a webhook - mattermost has no api endpoint for attachments/cards # For this reason, we need to build our own url, since we need /hooks and not /api/v4 # Todo: Reminder to check if this is still the case self.driver.webhooks.call_webhook(self.cards_hook, options=data) except ( InvalidOrMissingParameters, NotEnoughPermissions, ContentTooLarge, FeatureDisabled, NoAccessTokenProvided ): log.exception( "An exception occurred while trying to send a card to %s.[%s]" % (to_humanreadable, card) ) def prepare_message_body(self, body, size_limit): """ Returns the parts of a message chunked and ready for sending. This is a staticmethod for easier testing. Args: body (str) size_limit (int): chunk the body into sizes capped at this maximum Returns: [str] """ fixed_format = body.startswith('```') # hack to fix the formatting parts = list(split_string_after(body, size_limit)) if len(parts) == 1: # If we've got an open fixed block, close it out if parts[0].count('```') % 2 != 0: parts[0] += '\n```\n' else: for i, part in enumerate(parts): starts_with_code = part.startswith('```') # If we're continuing a fixed block from the last part if fixed_format and not starts_with_code: parts[i] = '```\n' + part # If we've got an open fixed block, close it out if parts[i].count('```') % 2 != 0: parts[i] += '\n```\n' return parts def change_presence(self, status: str=ONLINE, message: str=''): pass # Mattermost does not have a request/websocket event to change the presence def is_from_self(self, message: Message): return self.bot_identifier.userid == message.frm.userid def shutdown(self): self.driver.logout() super().shutdown() def query_room(self, room): """ Room can either be a name or a channelid """ return MattermostRoom(room, teamid=self.teamid, bot=self) def prefix_groupchat_reply(self, message: Message, identifier): super().prefix_groupchat_reply(message, identifier) message.body = '@{0}: {1}'.format(identifier.nick, message.body) def build_reply(self, message, text=None, private=False, threaded=False): response = self.build_message(text) response.frm = self.bot_identifier if private: response.to = message.frm else: response.to = message.frm.room if isinstance(message.frm, RoomOccupant) else message.frm if threaded: response.extras['root_id'] = message.extras.get('root_id') self.driver.posts.get_post(message.extras.get('root_id')) response.parent = message return response def get_public_channels(self): channels = [] page = 0 channel_page_limit = 200 while True: channel_list = self.driver.channels.get_public_channels( team_id=self.teamid, params={'page': page, 'per_page': channel_page_limit} ) if len(channel_list) == 0: break else: channels.extend(channel_list) page += 1 return channels def channels(self, joined_only=False): channels = [] channels.extend(self.driver.channels.get_channels_for_user(user_id=self.userid, team_id=self.teamid)) if not joined_only: public_channels = self.get_public_channels() for channel in public_channels: if channel not in channels: channels.append(channel) return channels def rooms(self): """Return public and private channels, but no direct channels""" rooms = self.channels(joined_only=True) channels = [channel for channel in rooms if channel['type'] != 'D'] return [MattermostRoom(channelid=channel['id'], teamid=channel['team_id'], bot=self) for channel in channels] def channelid_to_channelname(self, channelid): """Convert the channelid in the current team to the channel name""" channel = self.driver.channels.get_channel(channel_id=channelid) if 'name' not in channel: raise RoomDoesNotExistError("No channel with ID {} exists in team with ID {}".format( id, self.teamid )) return channel['name'] def channelname_to_channelid(self, name): """Convert the channelname in the current team to the channel id""" channel = self.driver.channels.get_channel_by_name(team_id=self.teamid, channel_name=name) if 'id' not in channel: raise RoomDoesNotExistError("No channel with name {} exists in team with ID {}".format( name, self.teamid )) return channel['id'] def __hash__(self): return 0 # This is a singleton anyway
myUserID = myuser['id'] myTeam = foo.teams.get_user_teams(myUserID) myTeamID = myTeam[0]['id'] myChannelsList = foo.channels.get_channels_for_user(myUserID, myTeamID) for myChannel in myChannelsList: #parcours de mes chans myChannelID = myChannel['id'] page = 0 myChannelMessageLength = 1 # tant qu'il reste des messages dans le set de msg de la page while (myChannelMessageLength != 0): # provient de l'API V4, et permet d'accéder # à des options supplémentaires options = {'page': page, 'per_page': PER_PAGE} myChannelsMessageList = foo.posts.get_posts_for_channel( myChannelID, options) myChannelMessage = myChannelsMessageList['order'] myChannelMessageLength = len(myChannelMessage) print("taille :" + str(myChannelMessageLength) + " - page :" + str(page)) # si on est dans un set de msg avec une seule page if (myChannelMessageLength < PER_PAGE): delete_posts(foo, myChannelMessage) break # s'il y a plusieurs pages, on boucle else: delete_posts(foo, myChannelMessage) page = page + 1 foo.logout()
class MattermostBackend(ErrBot): def __init__(self, config): super().__init__(config) identity = config.BOT_IDENTITY self._login = identity.get("login", None) self._password = identity.get("password", None) self._personal_access_token = identity.get("token", None) self._mfa_token = identity.get("mfa_token", None) self.team = identity.get("team") self._scheme = identity.get("scheme", "https") self._port = identity.get("port", 8065) self.cards_hook = identity.get("cards_hook", None) self.url = identity.get("server").rstrip("/") self.insecure = identity.get("insecure", False) self.timeout = identity.get("timeout", DEFAULT_TIMEOUT) self.teamid = "" self.token = "" self.bot_identifier = None self.driver = None self.md = md() self.event_handlers = { "posted": [self._message_event_handler], "status_change": [self._status_change_event_handler], "hello": [self._hello_event_handler], "user_added": [self._room_joined_event_handler], "user_removed": [self._room_left_event_handler], } def set_message_size_limit(self, limit=16377, hard_limit=16383): """ Mattermost message limit is 16383 chars, need to leave some space for backticks when messages are split """ super().set_message_size_limit(limit, hard_limit) @property def userid(self): return "{}".format(self.bot_identifier.userid) @property def mode(self): return "mattermost" def username_to_userid(self, name): """Converts a name prefixed with @ to the userid""" name = name.lstrip("@") user = self.driver.users.get_user_by_username(username=name) if user is None: raise UserDoesNotExistError("Cannot find user {}".format(name)) return user["id"] def register_handler(self, event, handler): if event not in self.event_handlers: self.event_handlers[event] = [] self.event_handlers[event].append(handler) @asyncio.coroutine def mattermost_event_handler(self, payload): if not payload: return payload = json.loads(payload) if "event" not in payload: log.debug("Message contains no event: {}".format(payload)) return event = payload["event"] event_handlers = self.event_handlers.get(event) if event_handlers is None: log.debug( "No event handlers available for {}, ignoring.".format(event)) return # noinspection PyBroadException for event_handler in event_handlers: try: event_handler(payload) except Exception: log.exception( "{} event handler raised an exception".format(event)) def _room_joined_event_handler(self, message): log.debug("User added to channel") if message["data"]["user_id"] == self.userid: self.callback_room_joined(self) def _room_left_event_handler(self, message): log.debug("User removed from channel") if message["broadcast"]["user_id"] == self.userid: self.callback_room_left(self) def _message_event_handler(self, message): log.debug(message) data = message["data"] # In some cases (direct messages) team_id is an empty string if data["team_id"] != "" and self.teamid != data["team_id"]: log.info("Message came from another team ({}), ignoring...".format( data["team_id"])) return broadcast = message["broadcast"] if "channel_id" in data: channelid = data["channel_id"] elif "channel_id" in broadcast: channelid = broadcast["channel_id"] else: log.error("Couldn't find a channelid for event {}".format(message)) return channel_type = data["channel_type"] if channel_type != "D": channel = data["channel_name"] else: channel = channelid text = "" post_id = "" file_ids = None userid = None if "post" in data: post = json.loads(data["post"]) text = post["message"] userid = post["user_id"] if "file_ids" in post: file_ids = post["file_ids"] post_id = post["id"] if "type" in post and post["type"] == "system_add_remove": log.info("Ignoring message from System") return if "user_id" in data: userid = data["user_id"] if not userid: log.error("No userid in event {}".format(message)) return mentions = [] if "mentions" in data: # TODO: Only user, not channel mentions are in here at the moment mentions = self.mentions_build_identifier( json.loads(data["mentions"])) # Thread root post id root_id = post.get("root_id", "") if root_id == "": root_id = post_id msg = Message( text, extras={ "id": post_id, "root_id": root_id, "mattermost_event": message, "url": "{scheme:s}://{domain:s}:{port:s}/{teamname:s}/pl/{postid:s}". format( scheme=self.driver.options["scheme"], domain=self.driver.options["url"], port=str(self.driver.options["port"]), teamname=self.team, postid=post_id, ), }, ) if file_ids: msg.extras["attachments"] = file_ids # TODO: Slack handles bots here, but I am not sure if bot users is a concept in mattermost if channel_type == "D": msg.frm = MattermostPerson(self.driver, userid=userid, channelid=channelid, teamid=self.teamid) msg.to = MattermostPerson( self.driver, userid=self.bot_identifier.userid, channelid=channelid, teamid=self.teamid, ) elif channel_type == "O" or channel_type == "P": msg.frm = MattermostRoomOccupant( self.driver, userid=userid, channelid=channelid, teamid=self.teamid, bot=self, ) msg.to = MattermostRoom(channel, teamid=self.teamid, bot=self) else: log.warning( "Unknown channel type '{}'! Unable to handle {}.".format( channel_type, channel)) return self.callback_message(msg) if mentions: self.callback_mention(msg, mentions) def _status_change_event_handler(self, message): """Event handler for the 'presence_change' event""" idd = MattermostPerson(self.driver, message["data"]["user_id"]) status = message["data"]["status"] if status == "online": status = ONLINE elif status == "away": status = AWAY else: log.error( "It appears the Mattermost API changed, I received an unknown status type %s" % status) status = ONLINE self.callback_presence(Presence(identifier=idd, status=status)) def _hello_event_handler(self, message): """Event handler for the 'hello' event""" self.connect_callback() self.callback_presence( Presence(identifier=self.bot_identifier, status=ONLINE)) @lru_cache(1024) def get_direct_channel(self, userid, other_user_id): """ Get the direct channel to another user. If it does not exist, it will be created. """ try: return self.driver.channels.create_direct_message_channel( options=[userid, other_user_id]) except (InvalidOrMissingParameters, NotEnoughPermissions): raise RoomDoesNotExistError( "Could not find Direct Channel for users with ID {} and {}". format(userid, other_user_id)) def build_identifier(self, txtrep): """ Convert a textual representation into a :class:`~MattermostPerson` or :class:`~MattermostRoom` Supports strings with the following formats:: @username ~channelname channelid """ txtrep = txtrep.strip() if txtrep.startswith("~"): # Channel channelid = self.channelname_to_channelid(txtrep[1:]) if channelid is not None: return MattermostRoom(channelid=channelid, teamid=self.teamid, bot=self) else: # Assuming either a channelid or a username if txtrep.startswith("@"): # Username userid = self.username_to_userid(txtrep[1:]) else: # Channelid userid = txtrep if userid is not None: return MattermostPerson( self.driver, userid=userid, channelid=self.get_direct_channel(self.userid, userid)["id"], teamid=self.teamid, ) raise Exception("Invalid or unsupported Mattermost identifier: %s" % txtrep) def mentions_build_identifier(self, mentions): identifier = [] for mention in mentions: if mention != self.bot_identifier.userid: identifier.append(self.build_identifier(mention)) return identifier def serve_once(self): self.driver = Driver({ "scheme": self._scheme, "url": self.url, "port": self._port, "verify": not self.insecure, "timeout": self.timeout, "login_id": self._login, "password": self._password, "token": self._personal_access_token, "mfa_token": self._mfa_token, }) self.driver.login() self.teamid = self.driver.teams.get_team_by_name(name=self.team)["id"] userid = self.driver.users.get_user(user_id="me")["id"] self.token = self.driver.client.token self.bot_identifier = MattermostPerson(self.driver, userid=userid, teamid=self.teamid) # noinspection PyBroadException try: loop = self.driver.init_websocket( event_handler=self.mattermost_event_handler) self.reset_reconnection_count() loop.run_forever() except KeyboardInterrupt: log.info("Interrupt received, shutting down..") return True except Exception: log.exception("Error reading from RTM stream:") finally: log.debug("Triggering disconnect callback") self.disconnect_callback() def _prepare_message(self, message): to_name = "<unknown>" if message.is_group: to_channel_id = message.to.id if message.to.name: to_name = message.to.name else: self.channelid_to_channelname(channelid=to_channel_id) else: to_name = message.to.username if isinstance( message.to, RoomOccupant ): # private to a room occupant -> this is a divert to private ! log.debug( "This is a divert to private message, sending it directly to the user." ) channel = self.get_direct_channel( self.userid, self.username_to_userid(to_name)) to_channel_id = channel["id"] else: to_channel_id = message.to.channelid return to_name, to_channel_id def send_message(self, message): super().send_message(message) try: to_name, to_channel_id = self._prepare_message(message) message_type = "direct" if message.is_direct else "channel" log.debug("Sending %s message to %s (%s)" % (message_type, to_name, to_channel_id)) body = self.md.convert(message.body) log.debug("Message size: %d" % len(body)) parts = self.prepare_message_body(body, self.message_size_limit) root_id = None if message.parent is not None: root_id = message.parent.extras.get("root_id") for part in parts: self.driver.posts.create_post( options={ "channel_id": to_channel_id, "message": part, "root_id": root_id, }) except (InvalidOrMissingParameters, NotEnoughPermissions): log.exception( "An exception occurred while trying to send the following message " "to %s: %s" % (to_name, message.body)) def send_card(self, card: Card): if isinstance(card.to, RoomOccupant): card.to = card.to.room to_humanreadable, to_channel_id = self._prepare_message(card) attachment = {} if card.summary: attachment["pretext"] = card.summary if card.title: attachment["title"] = card.title if card.link: attachment["title_link"] = card.link if card.image: attachment["image_url"] = card.image if card.thumbnail: attachment["thumb_url"] = card.thumbnail attachment["text"] = card.body if card.color: attachment["color"] = (COLORS[card.color] if card.color in COLORS else card.color) if card.fields: attachment["fields"] = [{ "title": key, "value": value, "short": True } for key, value in card.fields] data = {"attachments": [attachment]} if card.to: if isinstance(card.to, MattermostRoom): data["channel"] = card.to.name try: log.debug("Sending data:\n%s", data) # We need to send a webhook - mattermost has no api endpoint for attachments/cards # For this reason, we need to build our own url, since we need /hooks and not /api/v4 # Todo: Reminder to check if this is still the case self.driver.webhooks.call_webhook(self.cards_hook, options=data) except ( InvalidOrMissingParameters, NotEnoughPermissions, ContentTooLarge, FeatureDisabled, NoAccessTokenProvided, ): log.exception( "An exception occurred while trying to send a card to %s.[%s]" % (to_humanreadable, card)) def prepare_message_body(self, body, size_limit): """ Returns the parts of a message chunked and ready for sending. This is a staticmethod for easier testing. Args: body (str) size_limit (int): chunk the body into sizes capped at this maximum Returns: [str] """ fixed_format = body.startswith("```") # hack to fix the formatting parts = list(split_string_after(body, size_limit)) if len(parts) == 1: # If we've got an open fixed block, close it out if parts[0].count("```") % 2 != 0: parts[0] += "\n```\n" else: for i, part in enumerate(parts): starts_with_code = part.startswith("```") # If we're continuing a fixed block from the last part if fixed_format and not starts_with_code: parts[i] = "```\n" + part # If we've got an open fixed block, close it out if parts[i].count("```") % 2 != 0: parts[i] += "\n```\n" return parts def change_presence(self, status: str = ONLINE, message: str = ""): pass # Mattermost does not have a request/websocket event to change the presence def is_from_self(self, message: Message): return self.bot_identifier.userid == message.frm.userid def shutdown(self): self.driver.logout() super().shutdown() def query_room(self, room): """Room can either be a name or a channelid""" return MattermostRoom(room, teamid=self.teamid, bot=self) def prefix_groupchat_reply(self, message: Message, identifier): super().prefix_groupchat_reply(message, identifier) message.body = "@{0}: {1}".format(identifier.nick, message.body) def build_reply(self, message, text=None, private=False, threaded=False): response = self.build_message(text) response.frm = self.bot_identifier if private: response.to = message.frm else: response.to = (message.frm.room if isinstance( message.frm, RoomOccupant) else message.frm) if threaded: response.extras["root_id"] = message.extras.get("root_id") self.driver.posts.get_post(message.extras.get("root_id")) response.parent = message return response def get_public_channels(self): channels = [] page = 0 channel_page_limit = 200 while True: channel_list = self.driver.channels.get_public_channels( team_id=self.teamid, params={ "page": page, "per_page": channel_page_limit }, ) if len(channel_list) == 0: break else: channels.extend(channel_list) page += 1 return channels def channels(self, joined_only=False): channels = [] channels.extend( self.driver.channels.get_channels_for_user(user_id=self.userid, team_id=self.teamid)) if not joined_only: public_channels = self.get_public_channels() for channel in public_channels: if channel not in channels: channels.append(channel) return channels def rooms(self): """Return public and private channels, but no direct channels""" rooms = self.channels(joined_only=True) channels = [channel for channel in rooms if channel["type"] != "D"] return [ MattermostRoom(channelid=channel["id"], teamid=channel["team_id"], bot=self) for channel in channels ] def channelid_to_channelname(self, channelid): """Convert the channelid in the current team to the channel name""" channel = self.driver.channels.get_channel(channel_id=channelid) if "name" not in channel: raise RoomDoesNotExistError( "No channel with ID {} exists in team with ID {}".format( id, self.teamid)) return channel["name"] def channelname_to_channelid(self, name): """Convert the channelname in the current team to the channel id""" channel = self.driver.channels.get_channel_by_name(team_id=self.teamid, channel_name=name) if "id" not in channel: raise RoomDoesNotExistError( "No channel with name {} exists in team with ID {}".format( name, self.teamid)) return channel["id"] def __hash__(self): return 0 # This is a singleton anyway
class MattermostBackend(): def __init__(self): self.url = 'mattermost.example.com' self._login = '******' self._scheme = 'https' self._port = 443 self.insecure = False self.timeout = DEFAULT_TIMEOUT self.teamid = '' self.token = '' self.driver = None # Get password from Gnome keyring, matching the stored Chromium password self._password = Secret.password_lookup_sync( SECRET_SCHEMA, { 'username_value': self._login, 'action_url': 'https://mattermost.example.com/login' }, None) @asyncio.coroutine def mattermost_event_handler(self, payload): if not payload: return payload = json.loads(payload) if 'event' not in payload: log.debug('Message contains no event: {}'.format(payload)) return event_handlers = { 'posted': self._message_event_handler, } event = payload['event'] event_handler = event_handlers.get(event) if event_handler is None: log.debug( 'No event handler available for {}, ignoring.'.format(event)) return try: event_handler(payload) except Exception: log.exception( '{} event handler raised an exception. Exiting.'.format(event)) sys.exit(1) def _message_event_handler(self, message): log.debug(message) data = message['data'] broadcast = message['broadcast'] if 'channel_id' in data: channelid = data['channel_id'] elif 'channel_id' in broadcast: channelid = broadcast['channel_id'] else: log.error("Couldn't find a channelid for event {}".format(message)) return channel_type = data['channel_type'] if channel_type != 'D': channel = data['channel_name'] if 'team_id' in data: teamid = data['team_id'] if teamid: team = self.driver.api['teams'].get_team(team_id=teamid) teamname = team['display_name'] else: channel = channelid teamname = None text = '' userid = None if 'post' in data: post = json.loads(data['post']) text = post['message'] userid = post['user_id'] if 'type' in post and post['type'] == 'system_add_remove': log.info('Ignoring message from System') return if 'user_id' in data: userid = data['user_id'] if not userid: log.error('No userid in event {}'.format(message)) return mentions = [] if 'mentions' in data: mentions = json.loads(data['mentions']) if mentions: username = self.driver.api['users'].get_user( user_id=userid)['username'] print('mentioned: ', teamname, '"', mentions, '"', username, text) if self.userid in mentions: if teamname: self.notify( '{} in {}/{}'.format(username, teamname, channel), text) else: self.notify('{} in DM'.format(username), text) log.info('"posted" event from {}: {}'.format( self.driver.api['users'].get_user(user_id=userid)['username'], text)) def notify(self, summary, desc=''): self._notification = Notification(summary, desc) def serve_once(self): self.driver = Driver({ 'scheme': self._scheme, 'url': self.url, 'port': self._port, 'verify': not self.insecure, 'timeout': self.timeout, 'login_id': self._login, 'password': self._password }) self.driver.login() self.userid = self.driver.api['users'].get_user(user_id='me')['id'] self.token = self.driver.client.token try: loop = self.driver.init_websocket( event_handler=self.mattermost_event_handler) loop.run_forever() # loop.stop() except KeyboardInterrupt: log.info("Interrupt received, shutting down..") Notify.uninit() self.driver.logout() return True except Exception: log.exception("Error reading from RTM stream:") finally: log.debug("Triggering disconnect callback")
class MycroftChat(MycroftSkill): def __init__(self): MycroftSkill.__init__(self) def initialize(self): self.register_entity_file('channel.entity') self.register_entity_file('service.entity') self.state = "idle" self.mm = None # login data self.username = self.settings.get("username", "") self.token = self.settings.get("token", "") self.login_id = self.settings.get("login_id", "") self.password = self.settings.get("password", "") # monitoring self.ttl = self.settings.get("ttl", 10) * 60 self.notify_on_updates = self.settings.get("notify_on_updates", False) LOG.debug("username: {}".format(self.username)) mm_driver_config = { 'url': self.settings.get("url", "chat.mycroft.ai"), 'scheme': self.settings.get("scheme", "https"), 'port': self.settings.get("port", 443), 'verify': self.settings.get("verify", True) } if self.settings.get("url", "chat.mycroft.ai") == "chat.mycroft.ai": self.service_name = "Mycroft Chat" else: self.service_name = "Mattermost" if self.token: mm_driver_config['token'] = self.token elif self.login_id and self.password: mm_driver_config['login_id'] = self.login_id mm_driver_config['password'] = self.password if self.username: self.mm = Driver(mm_driver_config) try: self.mm.login() self.userid = \ self.mm.users.get_user_by_username(self.username)['id'] # TODO check if user is member of several teams self.teamid = self.mm.teams.get_team_members_for_user( self.userid)[0]['team_id'] LOG.debug("userid: {} teamid: {}".format( self.userid, self.teamid)) except (mme.ResourceNotFound, mme.HTTPError, mme.NoAccessTokenProvided, mme.NotEnoughPermissions, mme.InvalidOrMissingParameters) as e: LOG.debug("Exception: {}".format(e)) self.mm = None self.speak_dialog("mattermost.error", {'exception': e}) if self.mm: # info on all subscribed public channels as returned by MM self.channel_subscriptions = None self.channel_subs_ts = 0 # basic info of channels # channel_id, display_name, (unread) msg_count, mentions self.channel_info = None self.channel_info_ts = 0 self.usercache = {} self.prev_unread = 0 self.prev_mentions = 0 if self.settings.get('monitoring', False): self.monitoring = True self.schedule_repeating_event( self._mattermost_monitoring_handler, None, self.ttl, 'Mattermost') else: self.monitoring = False # Check and then monitor for credential changes self.settings_change_callback = self.on_websettings_changed def on_websettings_changed(self): LOG.debug("websettings changed!") if self.mm: self.mm.logout() if self.monitoring: self.cancel_scheduled_event('Mattermost') self.initialize() @intent_file_handler('read.unread.channel.intent') def read_channel_messages(self, message): if not self.mm: self.speak_dialog("skill.not.initialized") return elif self.state != "idle": return else: self.state = "speaking" channel_name = message.data.get('channel') LOG.debug("data {}".format(message.data)) if not channel_name: self.speak_dialog('channel.unknown', data={'channel': ''}) self.state = "idle" return # do some fuzzy matching on channel best_chan = {} best_score = 66 # minimum score required for chan in self._get_channel_info(): score = fuzz.ratio(channel_name.lower(), chan['display_name'].lower()) # LOG.debug("{}->{}".format(unr['display_name'], score)) if score > best_score: best_chan = chan best_score = score LOG.debug("{} -> {}".format(best_chan, best_score)) if not best_chan: self.speak_dialog('channel.unknown', data={'channel': channel_name}) elif best_chan['msg_count'] == 0: self.speak_dialog('no.unread.channel.messages', data={'channel': channel_name}) else: self._read_unread_channel(best_chan) self.state = "idle" @intent_file_handler('start.monitoring.intent') def start_monitoring_mattermost(self, message): if not self.mm: self.speak_dialog("skill.not.initialized") return LOG.debug("start monitoring with ttl {} secs".format(self.ttl)) self.schedule_repeating_event(self._mattermost_monitoring_handler, None, self.ttl, 'Mattermost') self.monitoring = True self.settings['monitoring'] = True self.settings.store(force=True) self.speak_dialog('monitoring.active', {'service': self.service_name}) @intent_file_handler('end.monitoring.intent') def end_monitoring_mattermost(self, message): LOG.debug("end monitoring") self.cancel_scheduled_event('Mattermost') self.monitoring = False self.settings['monitoring'] = False self.settings.store(force=True) self.speak_dialog('monitoring.inactive', data={'service': self.service_name}) @intent_file_handler('read.unread.messages.intent') def read_unread_messages(self, message): if not self.mm: self.speak_dialog("skill.not.initialized") return elif self.state != "idle": return else: self.state = "speaking" for chan in self._get_channel_info(): if self.state == "stopped": break self._read_unread_channel(chan) self.state = "idle" @intent_file_handler('list.unread.channels.intent') def list_unread_channels(self, message): if not self.mm: self.speak_dialog("skill.not.initialized") return elif self.state != "idle": return else: self.state = "speaking" count = 0 for ch in self._get_channel_info(): responses = [] if (ch['msg_count'] and ch['mention_count']): responses.append( self.dialog_renderer.render( "channel.unread.and.mentioned", { 'msg_count': ch['msg_count'], # TODO use nice_number 'display_name': ch['display_name'], 'mention_count': ch['mention_count'] })) elif ch['msg_count']: responses.append( self.dialog_renderer.render( "channel.unread", { 'msg_count': ch['msg_count'], # TODO use nice_number 'display_name': ch['display_name'] })) elif ch['mention_count']: responses.append( self.dialog_renderer.render( "channel.mentioned", { 'mention_count': ch['mention_count'], 'display_name': ch['display_name'] })) if responses: count += 1 for res in responses: if self.state == "stopped": break self.speak(res, wait=True) if count == 0: # no unread/mentions self.speak_dialog('no.unread.messages', data={'service': self.service_name}) self.state = "idle" @intent_file_handler('unread.messages.intent') def check_unread_messages_and_mentions(self, message): if not self.mm: self.speak_dialog("skill.not.initialized") return elif self.state != "idle": return else: self.state = "speaking" unreadmsg = self._get_unread_msg_count() mentions = self._get_mention_count() response = self.__render_unread_dialog(unreadmsg, mentions, self.service_name) self.enclosure.deactivate_mouth_events() self.enclosure.mouth_text('unread: {} mentions: {}'.format( unreadmsg, mentions)) self.speak(response, wait=True) self.enclosure.activate_mouth_events() self.enclosure.mouth_reset() self.state = "idle" def stop(self): # this requires mycroft-stop skill installed if self.state == "speaking": LOG.debug("stopping") self.state = "stopped" return True return False def _read_unread_channel(self, chan): if self.state == "stopped": return msg_count = chan['msg_count'] if msg_count: channel_message = self.dialog_renderer.render( "messages.for.channel", {'display_name': chan['display_name']}) LOG.debug(channel_message) self.speak(channel_message) pfc = self.mm.posts.get_posts_for_channel(chan['channel_id']) order = pfc['order'] # in case returned posts are less than number of unread # avoid 'index out of bounds' msg_count = msg_count if msg_count < len(order) else len(order) prev_date = "" for i in range(0, msg_count): if self.state == "stopped": break # order starts with newest to oldest, # start to read the oldest of the unread post = pfc['posts'][order[msg_count - i - 1]] create_at = "" # nice_date does only support en-us yet - bummer! # MM timestamps are in millisecs, python in secs msg_date = nice_date(datetime.fromtimestamp(post['create_at'] / 1000), self.lang, now=datetime.now()) if prev_date != msg_date: create_at = msg_date + " " prev_date = msg_date msg_time = nice_time( datetime.fromtimestamp(post['create_at'] / 1000), self.lang) create_at += msg_time msg = self.dialog_renderer.render( "message", { 'user_name': self._get_user_name(post['user_id']), 'create_at': create_at, 'message': post['message'] }) LOG.debug(msg) self.speak(msg, wait=True) time.sleep(.3) # mark channel as read self.mm.channels.view_channel(self.userid, {'channel_id': chan['channel_id']}) # TODO clarify when to reset prev_unread/prev_mentions self.prev_unread = 0 self.prev_mentions = 0 def _get_unread_msg_count(self): unreadmsg = 0 for chan in self._get_channel_info(): if (chan['msg_count']): unreadmsg += chan['msg_count'] return unreadmsg def _get_mention_count(self): mentions = 0 for chan in self._get_channel_info(): if (chan['mention_count']): mentions += chan['mention_count'] return mentions def __render_unread_dialog(self, unreadmsg, mentions, service=None): if not service: service = self.service_name LOG.debug("unread {} mentions {}".format(unreadmsg, mentions)) response = "" if unreadmsg: response += self.dialog_renderer.render('unread.messages', { 'unreadmsg': unreadmsg, 'service': service }) response += " " if mentions: response += self.dialog_renderer.render('mentioned', { 'mentions': mentions, 'service': service }) if not response: response = self.dialog_renderer.render('no.unread.messages', {'service': service}) return response def _mattermost_monitoring_handler(self): LOG.debug("mm monitoring handler") # do not update when last run was less than 30secs before if (time.time() - self.channel_subs_ts) > 30: self._get_channel_subscriptions() if (time.time() - self.channel_info_ts) > 30: self._get_channel_info() LOG.debug("check for notifications") unreadmsg = self._get_unread_msg_count() mentions = self._get_mention_count() # TODO clarify when to reset prev_unread/prev_mentions if unreadmsg != self.prev_unread: self.prev_unread = unreadmsg else: unreadmsg = 0 if mentions != self.prev_mentions: self.prev_mentions = mentions else: mentions = 0 LOG.debug("unread: {} mentions: {}".format(unreadmsg, mentions)) if unreadmsg or mentions: # display unread and mentions on Mark-1/2 display display_text = self.dialog_renderer.render('display.message.count', { 'unread': unreadmsg, 'mentions': mentions }) if self.config_core.get("enclosure").get("platform", "") == \ 'mycroft_mark_1': self.enclosure.deactivate_mouth_events() self.enclosure.mouth_text(display_text) # clear display after 30 seconds self.schedule_event(self._mattermost_display_handler, 30, None, 'mmdisplay') elif self.config_core.get("enclosure").get("platform", "") == \ 'mycroft_mark_2': self.gui.show_text(display_text, self.service_name) if self.notify_on_updates: self.speak(self.__render_unread_dialog(unreadmsg, mentions)) def _mattermost_display_handler(self): # clear display and reset display handler self.enclosure.activate_mouth_events() self.enclosure.mouth_reset() self.cancel_scheduled_event('mmdisplay') def _get_channel_subscriptions(self): # update channel subscriptions only every second ttl interval if (time.time() - self.channel_subs_ts) > (self.ttl * 2): LOG.debug("get channel subscriptions...") self.channel_subscriptions = \ self.mm.channels.get_channels_for_user(self.userid, self.teamid) self.channel_subs_ts = time.time() LOG.debug("...done") # LOG.debug(self.channel_subscriptions) return self.channel_subscriptions def _get_channel_info(self): if (time.time() - self.channel_info_ts) > self.ttl: LOG.debug("get channel info...") info = [] for chan in self._get_channel_subscriptions(): if chan['team_id'] != self.teamid: continue unr = self.mm.channels.get_unread_messages( self.userid, chan['id']) info.append({ 'display_name': chan['display_name'], 'msg_count': unr['msg_count'], 'mention_count': unr['mention_count'], 'channel_id': chan['id'] }) self.channel_info = info self.channel_info_ts = time.time() LOG.debug("...done") # LOG.debug(self.channel_info) return self.channel_info def _get_user_name(self, userid): if not (userid in self.usercache): user = self.mm.users.get_user(userid) self.usercache[userid] = user['username'] # LOG.debug("usercache {}->{}".format(userid, user['username'])) return self.usercache[userid]
class Bot: bot = None host = None port = None https = None username = None passwd = None driver = None bot_info = None def __init__(self, host, username, passwd, port=443, https=True): Bot.bot = self self.host = host self.port = port self.https = https self.username = username self.passwd = passwd ClientLogger.log(LoggingMode.INFO, "Creating mettermost driver... ") self.create_driver() ClientLogger.log(LoggingMode.INFO, "Logging in... ") self.login() ClientLogger.log(LoggingMode.INFO, 'Successfully logged in!') from connection.WebSocket import WebSocket ClientLogger.log(LoggingMode.INFO, "Setting up web socket...") websocket = WebSocket() ClientLogger.log(LoggingMode.INFO, "Listening for user commands...") asyncio.get_event_loop().run_until_complete(websocket.listen()) @classmethod def get_bot(cls): return Bot.bot def create_driver(self): self.driver = Driver({ 'url': self.host, 'login_id': self.username, 'password': self.passwd, 'scheme': 'https' if self.https else 'http', 'port': self.port }) def login(self): self.driver.login() self.bot_info = self.driver.users.get_user_by_username( Config.get_user()) def logout(self): self.driver.logout() def kill(self): self.logout() sys.exit() def get_bot_id(self): return self.get_id_from_json_data(self.bot_info) def get_bot_channel_id(self): return self.get_id_from_json_data( self.driver.channels.get_channel_by_name_and_team_name( Config.get_team(), Config.get_channel())) def create_message(self, text): self.driver.posts.create_post({ 'user_id': self.get_bot_id(), 'channel_id': self.get_bot_channel_id(), 'message': text }) def get_all_unread_messages(self): return self.driver.channels.get_unread_messages( self.get_bot_id(), self.get_bot_channel_id()) def get_auth_token(self): return self.driver.users.get_au @staticmethod def get_team_name(): return Config.get_team() @staticmethod def get_id_from_json_data(data): json.loads(json.dumps(data)) return data['id']
def main(): module = AnsibleModule( argument_spec={ "username": { "required": True, "type": "str" }, "password": { "required": True, "type": "str", "no_log": True }, "token_description": { "required": False, "type": "str", "default": "" }, "url": { "required": False, "type": "str", "default": "localhost" }, "scheme": { "required": False, "type": "str", "default": "http" }, "port": { "required": False, "type": "int", "default": 8065 }, "path": { "required": True, "type": "str" }, }) token = None try: token = open(module.params["path"]).read().strip() except FileNotFoundError: pass if token is not None: test_driver = Driver({ "token": token, "url": module.params['url'], "port": module.params['port'], "scheme": module.params['scheme'], }) try: test_driver.login() userid = test_driver.client.userid test_driver.logout() module.exit_json(changed=False, meta={ "token": token, "userid": userid }) except HTTPError: pass driver = Driver({ "login_id": module.params['username'], "password": module.params['password'], "url": module.params['url'], "port": module.params['port'], "scheme": module.params['scheme'], }) driver.login() userid = driver.client.userid token = driver.users.create_user_access_token( driver.client.userid, {"description": module.params["token_description"]}) driver.logout() open(module.params["path"], "w").write(token["token"]) module.exit_json(changed=True, meta={ "token": token["token"], "userid": userid })
class EmojiContext: """ Custom Click Context class to store global settings and manage authentication """ def __init__(self): self.output = "table" self.mattermost = None @contextmanager def authenticate(self, url, token, login_id, password, mfa_token, insecure): """Authenticate against the Mattermost server""" if token and (login_id or password or mfa_token): click.echo( "Warning: Token specified along" " with Login-ID/Password/MFA-token." "Only Token will be used.", err=True, ) settings = { "scheme": url.scheme, "url": url.hostname, "basepath": getattr(url, "path", ""), "verify": not insecure, "login_id": login_id, "password": password, "token": token, "mfa_token": mfa_token, } if url.port: settings["port"] = url.port elif url.scheme == "https": settings["port"] = 443 else: settings["port"] = 80 self.mattermost = Mattermost(settings) try: try: yield self.mattermost.login() finally: # Logout is unnecessary if token was used if token is None: self.mattermost.logout() except (requests.exceptions.ConnectionError, MethodNotAllowed): raise click.ClickException( "Unable to reach Mattermost API at {}".format( self.mattermost.client.url)) except requests.exceptions.HTTPError as e: raise click.ClickException(e.args if e.args != () else repr(e)) def print_dict(self, data): """Print dataset generated by a command to the standard output""" dataset = Dataset() dataset.dict = data if dataset.height: if self.output == "table": click.echo(tabulate(dataset.dict, headers="keys")) else: # we will probably implement JSON output only in the long run # and get rid of the `tablib` dependency click.echo(dataset.export(self.output)) click.echo( "\n({} emoji{})".format(dataset.height, "" if dataset.height == 1 else "s"), err=True, )
class Mattermost: """Class that is responsible for connecting to mattermost bot.""" def __init__(self): connection_parameters = { 'url': settings.MATTERMOST_SERVER, 'port': settings.MATTERMOST_PORT, 'username': settings.MATTERMOST_LOGIN_ID, 'token': settings.MATTERMOST_ACCESS_TOKEN } self.mattermost_connection = Driver(connection_parameters) def __enter__(self): try: self.mattermost_connection.login() except HTTPError as e: # Log exception to Sentry if call fails, but do not break the server. if not settings.DEBUG: # noinspection PyUnresolvedReferences from sentry_sdk import capture_exception capture_exception(e) return self def __exit__(self, *args): self.mattermost_connection.logout() def get_usernames_from_emails(self, emails: list[str]) -> list[str]: """ Function that helps to get mattermost usernames from emails. :param emails: Emails of the users. :return: Mattermost usernames of the users. """ usernames = [] for email in emails: try: username = self.mattermost_connection.users.get_user_by_email( email).get('username') usernames.append(username) except HTTPError as e: # Log exception to Sentry if call fails, but do not break the server. if not settings.DEBUG: # noinspection PyUnresolvedReferences from sentry_sdk import capture_exception capture_exception(e) return usernames def post_message_to_channel(self, channel_name: str, message: str) -> None: """ Post the message to the channel using the channel name. :param channel_name: Name of the channel to post message. :param message: Message to be posted. """ try: channels = self.mattermost_connection.channels channel_id = channels.get_channel_by_name_and_team_name( settings.MATTERMOST_TEAM_NAME, channel_name).get('id') self.mattermost_connection.posts.create_post({ 'channel_id': channel_id, 'message': message }) except HTTPError as e: # Log exception to Sentry if call fails, but do not break the server. if not settings.DEBUG: # noinspection PyUnresolvedReferences from sentry_sdk import capture_exception capture_exception(e)