def message_prune(token, channel_id, num_messages): ''' Prunes the last num_messages messages from a channel NOTE: this function cannot be placed in message.py due to circular imports ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_id(channel_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif channel is None: raise InputError('Invalid channel') elif auth_user not in channel.get_all_members(): raise AccessError('Invalid permission') elif auth_user not in channel.get_owner_members( ) and auth_user.get_permission_id() != 1: raise AccessError('Invalid permission for pruning messages') total_messages = len(channel.get_messages()) if num_messages > total_messages: raise InputError( f'Attempted to prune more messages than there are messages in the channel' ) # Prune last num_messages messages del channel.get_messages()[-num_messages:] return {}
def channel_invite(token, channel_id, u_id): ''' Invites a user (with user id u_id) to join a channel with ID channel_id. Once invited the user is added to the channel immediately Input: token (str), channel_id (int), u_id (int) Output: empty dict ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_id(channel_id) invited_user = user_with_id(u_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif channel is None: raise InputError('Invalid channel_id') elif invited_user is None: raise InputError('Invalid u_id') elif auth_user not in channel.get_all_members(): raise AccessError('Authorised user not a member of channel') # Append invited user to all_members (if they're not already a member) if invited_user not in channel.get_all_members(): channel.get_all_members().append(invited_user) return { }
def user_profile_sethandle(token, handle_str): ''' Update the authorised user's handle (i.e. display name) Input: token (str), handle_str (str) Output: empty dict ''' # Retrieve data auth_user = user_with_token(token) # Error check if auth_user is None: # Invalid token raise AccessError('Invalid token') elif len(handle_str) not in range(3, 21): # Handle length raise InputError('Handle must be between 3 and 20 characters') elif handle_str.isspace(): # Invalid handle raise InputError('Invalid handle') elif handle_str in user_handle_list(): # Handle in use raise InputError('Handle already taken') # Update handle auth_user.set_handle(handle_str) return {}
def standup_active(token, channel_id): ''' For a given channel, return whether a standup is active in it, and what time the standup finishes. If no standup is active, then time_finish returns None. Input: token (str), channel_id (int) Output: is_active (bool), time_finish (UNIX timestamp int) ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_id(channel_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif channel is None: raise InputError('Invalid channel') elif auth_user not in channel.get_all_members(): raise AccessError('User not member of channel') standup_status = channel.get_standup_status() return { 'is_active': standup_status['is_active'], 'time_finish': standup_status['time_finish'], }
def message_unpin(token, message_id): ''' Given a message within a channel, remove it's mark as unpinned. Input: token (str), message_id (int) Output: empty dict ''' auth_user = user_with_token(token) channel = channel_with_message_id(message_id) message = message_with_message_id(message_id) if auth_user is None: raise AccessError('Invalid token') elif message is None: raise InputError('Invalid message_id') elif auth_user not in channel.get_all_members(): raise AccessError('Invalid permission') elif auth_user not in channel.get_owner_members( ) and auth_user.get_permission_id() != 1: raise AccessError('Invalid permission for unpinning messages') elif not message.get_is_pinned(): raise InputError('Message already unpinned') message.set_is_pinned(False) return {}
def message_edit(token, message_id, message): ''' Given a message, update its text with new text. If the new message is an empty string, the message is deleted. Input: token (str), message_id (int), message (str) Output: empty dict ''' # Remove message if empty ('') if not message: return message_remove(token, message_id) # Retrieve data auth_user = user_with_token(token) channel = channel_with_message_id(message_id) message_object = message_with_message_id(message_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif message_object is None: raise InputError('Invalid Message ID') sender = message_object.get_sender() user_is_channel_owner = (auth_user in channel.get_owner_members()) user_is_flockr_owner = (auth_user.get_permission_id() == 1) if auth_user is not sender and not user_is_channel_owner and not user_is_flockr_owner: raise AccessError('Invalid permissions') # Update message message_object.set_message(message) return {}
def message_remove(token, message_id): ''' Given a message_id for a message, this message is removed from the channel Input: token (str), message_id (int) Output: empty dict ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_message_id(message_id) message = message_with_message_id(message_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif message is None: raise InputError('Invalid Message ID') user_is_channel_owner = (auth_user in channel.get_owner_members()) user_is_flockr_owner = (auth_user.get_permission_id() == 1) if auth_user is not message.get_sender( ) and not user_is_channel_owner and not user_is_flockr_owner: raise AccessError('Invalid permissions') # Remove message channel.get_messages().remove(message) return {}
def channel_addowner(token, channel_id, u_id): ''' Make user with user id u_id an owner of this channel Input: token (str), channel_id (int), u_id (int) Output: empty dict ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_id(channel_id) new_owner = user_with_id(u_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif channel is None: raise InputError('Invalid channel_id') elif new_owner is None or new_owner not in channel.get_all_members(): raise AccessError('Invalid u_id or user is not a member in the channel') elif auth_user not in channel.get_owner_members() and auth_user.get_permission_id() != 1: raise AccessError('Authorised user is not an owner of channel and not Flockr owner') elif auth_user not in channel.get_all_members(): # Note that Flockr owner may not be a channel member raise AccessError('Authorised user is not a member in the channel') elif new_owner in channel.get_owner_members(): raise InputError('User to be added as an owner is already an owner in the channel') # Add user as owner channel.get_owner_members().append(new_owner) return { }
def channel_kick(token, channel_id, u_id): ''' Remove user with user id u_id as a member of the channel Input: token (str), channel_id (int), u_id (int) Output: empty dict ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_id(channel_id) old_user = user_with_id(u_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif channel is None: raise InputError('Invalid channel_id') elif old_user is None: raise AccessError('Invalid u_id') elif auth_user not in channel.get_all_members(): # Flockr owner may not be a channel member raise AccessError('Authorised user is not a member in the channel') elif auth_user not in channel.get_owner_members() and auth_user.get_permission_id() != 1: raise AccessError('Authorised user is not an owner of channel and not Flockr owner') elif old_user in channel.get_owner_members(): # User cannot be an owner of the channel raise InputError('User to be kicked cannot be an owner in the channel') elif old_user not in channel.get_all_members(): raise InputError('User to be kicked is not a member in the channel') # Remove owner channel.get_all_members().remove(old_user) return { }
def message_send(token, channel_id, message): ''' Sends the message (str) by storing it in the channel with channel_id (int) and sender as user with token (str) Output: message_id (int) of message stored ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_id(channel_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif channel is None: raise InputError('Invalid channel') elif auth_user not in channel.get_all_members(): raise AccessError('User not in channel') elif not message: raise InputError('Empty message not allowed') elif len(message) > 1000: raise InputError('Message should be 1000 characters or less') # Store message new_message = Message(auth_user, message, current_time()) channel.get_messages().append(new_message) bot_message_parser(token, channel_id, message) return { 'message_id': new_message.get_message_id(), }
def message_unreact(token, message_id, react_id): ''' Given a message that has a react on it, a matching user can unreact the message based on user's token. Input: token (str), message_id (int), react_id (int) Output: empty dict ''' auth_user = user_with_token(token) message = message_with_message_id(message_id) if auth_user is None: raise AccessError('Invalid token') elif message is None: raise AccessError('Invalid message_id') elif react_id != 1: raise InputError('Invalid react_id') react = react_with_id_for_message(message, react_id) if react is None or not react.get_reactors(): raise InputError( 'Message does not contain an active react with react_id') elif auth_user not in react.get_reactors(): raise AccessError( f'You have not reacted to this message with react_id {react_id}') message.remove_react(auth_user, react_id) return {}
def user_profile_setname(token, name_first, name_last): ''' Update the authorised user's first and last name Input: token (str), name_first (str), name_last (str) Output: empty dict ''' # Retrieve data auth_user = user_with_token(token) # Error check if auth_user is None: # Invalid token raise AccessError('Invalid token') elif len(name_first) not in range(1, 51): # name_first length raise InputError( 'First name should be between 1 and 50 characters inclusive') elif name_first.isspace(): # name_first invalid raise InputError('First name cannot be empty') elif len(name_last) not in range(1, 51): # name_last length raise InputError( 'Last name should be between 1 and 50 characters inclusive') elif name_last.isspace(): # name_last invalid raise InputError('Last name cannot be empty') # Update name auth_user.set_name_first(name_first) auth_user.set_name_last(name_last) return {}
def admin_userpermission_change(token, u_id, permission_id): ''' Given a User by user ID, set their permissions to new permissions described by permission_id Input: token (str), u_id (int), permission_id (int) Output: empty dict ''' # Retrieve data auth_user = user_with_token(token) target_user = user_with_id(u_id) # Error check if auth_user is None: # Invalid token raise AccessError('Invalid token') elif auth_user.get_permission_id() != 1: # Requested user not a Flockr owner raise AccessError('Invalid permission') elif target_user is None: # Invalid user raise InputError('Invalid user') elif permission_id not in [1, 2]: # Invalid permission raise InputError('Invalid Permission ID') # Edit target_user's permissions target_user.set_permission_id(permission_id) return { }
def user_profile(token, u_id): ''' Returns information about a specified user. Input: token (str), u_id (int) Output: dict containing user's id, email, first name, last name, and handle ''' # Retrieve data auth_user = user_with_token(token) target_user = user_with_id(u_id) # Error check if auth_user is None: # Invalid token raise AccessError('Invalid token') elif target_user is None: # Invalid u_id raise InputError('Invalid user') return { 'user': { 'u_id': target_user.get_u_id(), 'email': target_user.get_email(), 'name_first': target_user.get_name_first(), 'name_last': target_user.get_name_last(), 'handle_str': target_user.get_handle(), 'profile_img_url': target_user.get_profile_img_url(), } }
def standup_send(token, channel_id, message): ''' Send a message to get buffered in the standup queue, assuming a standup is currently active Input: token (str), channel_id (int), message (str) Output: empty dict ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_id(channel_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif channel is None: raise InputError('Invalid channel') elif auth_user not in channel.get_all_members(): raise AccessError('User not member of channel') elif not channel.get_standup_status()['is_active']: raise InputError('An active standup is not currently running') elif not message: raise InputError('Empty message not allowed') elif len(message) > 1000: raise InputError('Message should be 1000 characters or less') # Add message to queue msg = Message(auth_user, message, time_created=current_time()) channel.get_standup_status()['queued_messages'].append(msg) return {}
def standup_start(token, channel_id, length): ''' For a given channel, start the standup period whereby for the next "length" seconds if someone calls "standup_send" with a message, it is buffered in the standup queue then at the end of the standup period a message will be added to the message queue in the channel from the user who started the standup. Input: token (str), channel_id (int), length (int) Output: time_finish (UNIX timestamp int) ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_id(channel_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif channel is None: raise InputError('Invalid channel') elif auth_user not in channel.get_all_members(): raise AccessError('User not member of channel') elif length <= 0: raise InputError('Invalid standup time') elif channel.get_standup_status()['is_active']: raise InputError('An active standup is currently running') end_time = channel.start_standup(initiator=auth_user, length=length) return { 'time_finish': end_time, }
def message_sendlater(token, channel_id, message, time_sent): ''' Send a message from authorised_user to the channel specified by channel_id automatically at a specified time in the future. Input: token (str), channel_id (int), message (str), time_sent (UNIX timestamp - float) Output: message_id (int) of message to be sent ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_id(channel_id) time_diff = time_sent - current_time() # Error check if auth_user is None: raise AccessError('Invalid token') elif channel is None: raise InputError('Invalid channel') elif auth_user not in channel.get_all_members(): raise AccessError('User not in channel') elif not message: raise InputError('Empty message not allowed') elif len(message) > 1000: raise InputError('Message should be 1000 characters or less') elif time_diff < 0: raise InputError('Time is in the past') # Note that message will still be sent later even if the user # leaves channel or logs out before message is actually sent new_message = Message(auth_user, message, time_created=time_sent) t = Timer(time_diff, channel.get_messages().append, args=[new_message]) t.start() return { 'message_id': new_message.get_message_id(), }
def channel_leave(token, channel_id): ''' Given a channel ID, the user removed as a member of this channel Input: token (str), channel_id (int) Output: empty dict ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_id(channel_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif channel is None: raise InputError('Invalid channel_id') elif auth_user not in channel.get_all_members(): raise AccessError('Authorised user not a member of channel') # Remove user from all_members channel.get_all_members().remove(auth_user) # Attempt to remove user from owner_members if they are an owner if auth_user in channel.get_owner_members(): channel.get_owner_members().remove(auth_user) return { }
def message_pin(token, message_id): ''' Given a message within a channel, mark it as "pinned" to be given special display treatment by the frontend. Input: token (str), message_id (int) Output: empty dict ''' auth_user = user_with_token(token) channel = channel_with_message_id(message_id) message = message_with_message_id(message_id) if auth_user is None: raise AccessError('Invalid token') elif message is None: raise InputError('Invalid message_id') elif auth_user not in channel.get_all_members(): raise AccessError('Invalid permission') elif auth_user not in channel.get_owner_members( ) and auth_user.get_permission_id() != 1: raise AccessError('Invalid permission for pinning messages') elif message.get_is_pinned(): raise InputError('Message already pinned') message.set_is_pinned(True) return {}
def channel_messages(token, channel_id, start): ''' Given a Channel with ID channel_id that the authorised user is part of, return up to 50 messages between index "start" and "start + 50". Message with index 0 is the most recent message in the channel. This function returns a new index "end" which is the value of "start + 50", or, if this function has returned the least recent messages in the channel, returns -1 in "end" to indicate there are no more messages to load after this return. Input: token (str), channel_id (int), start (int) Ouput: dict ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_id(channel_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif channel is None: raise InputError('Invalid channel_id') elif auth_user not in channel.get_all_members(): raise AccessError('Authorised user not a member of channel') elif start < 0 or start > len(channel.get_messages()): raise InputError('Invalid start index') # Messages originally ordered chronologically - # reverse and retrieve a maximum of 50 most recent messages messages = list(reversed(channel.get_messages()))[start : start + 50] if len(messages) == 0: # The end is reached there are no messages end = -1 else: # The end is also reached if the first message (id) is included in messages first_message_id = channel.get_messages()[0].get_message_id() first_message_reached = any(message.get_message_id() == first_message_id for message in messages) end = -1 if first_message_reached else start + 50 return { 'messages': [ { 'message_id': message.get_message_id(), 'u_id': message.get_sender().get_u_id(), 'time_created': message.get_time_created(), 'message': message.get_message(), 'reacts': [ { 'react_id': react.get_react_id(), 'u_ids': [reactor.get_u_id() for reactor in react.get_reactors()], 'is_this_user_reacted': auth_user in react.get_reactors(), } for react in message.get_reacts() ], 'is_pinned': message.get_is_pinned(), } for message in messages ], 'start': start, 'end': end, }
def bot_hangman_guess(token, channel_id, message): ''' Registers a guess for an active hangman game ''' channel = channel_with_id(channel_id) try: global hangman_status if not hangman_status['active']: raise InputError( 'No active hangman games.. please type /hangman start to start one!' ) # Extract character or word game_win = False if len(message) < 8: raise InputError( "Invalid guess format. Please ensure it's /guess C") elif len(message) == 8: # Character guessed character = message[7].upper() hangman_status['guessed_letters'].add(character) hangman_status['guesses_remaining'] -= 1 # Win condition (all letters in word have been guessed, as a subset) if hangman_status['word_set'].issubset( hangman_status['guessed_letters']): game_win = True else: # Word guessed hangman_status['guesses_remaining'] -= 1 # Check win condition if hangman_status['word'] == message[7:].upper(): game_win = True # Process end condition if game_win: # Win condition (word guessed) user = user_with_token(token) bot_msg = f"Congratulations {user.get_handle()} on guessing the word {hangman_status['word']}!" bot_send_message(channel, bot_msg, temporary=False) bot_hangman_reset() elif hangman_status['guesses_remaining'] == 0: # Loss condition bot_msg = f"Unlucky, the word was {hangman_status['word']}!" bot_send_message(channel, bot_msg, temporary=False) bot_hangman_reset() else: # Continue condition bot_hangman_word_display(channel) except Exception as e: bot_msg = f'Failed to register guess: {e}' bot_send_message(channel, bot_msg, temporary=False)
def bot_message_prune(token, channel_id, message): ''' Wrapper command for pruning messages ''' bot_init() channel = channel_with_id(channel_id) try: auth_user = user_with_token(token) num_messages = int(message[6:]) message_prune(token, channel_id, num_messages) bot_msg = f'{num_messages} messages have been successfully pruned by {auth_user.get_handle()}' bot_send_message(channel, bot_msg, temporary=True) except Exception as e: bot_msg = f'Failed to prune: {e}' bot_send_message(channel, bot_msg, temporary=False)
def channels_listall(token): ''' Provide a list of all channels (and their associated details) Input: token (str) Output: dict ''' # Error check if user_with_token(token) is None: raise AccessError('Invalid token') return { 'channels': [{ 'channel_id': channel.get_channel_id(), 'name': channel.get_name(), } for channel in data['channels']], }
def bot_kick(token, channel_id, message): ''' Wrapper command for kicking a user from a channel ''' user = user_with_token(token) channel = channel_with_id(channel_id) try: handle = message[6:] kick_user = user_with_handle(handle) if kick_user is None: raise InputError('Please provide a valid user handle!') channel_kick(token, channel_id, kick_user.get_u_id()) bot_msg = f'⚽️ {kick_user.get_handle()} has been kicked by {user.get_handle()}!' bot_send_message(channel, bot_msg, temporary=False) except Exception as e: bot_msg = f'Failed to kick: {e}' bot_send_message(channel, bot_msg, temporary=False)
def auth_logout(token): ''' Logs a user out Input: token (str) Output: is_success (bool) as a dict ''' user = user_with_token(token) # Check for valid token if user is None: raise AccessError('Invalid token') # Invalidate user token user.set_token('') return { 'is_success': True, }
def channels_list(token): ''' Provide a list of all channels (and their associated details) that the auth user is part of Input: token (str) Output: dict ''' auth_user = user_with_token(token) # Error check if auth_user is None: raise AccessError('Invalid token') return { 'channels': [{ 'channel_id': channel.get_channel_id(), 'name': channel.get_name(), } for channel in data['channels'] if auth_user in channel.get_all_members()], }
def search(token, query_str): ''' Given a query string, return a collection of messages in all of the channels that the user has joined that match the query Input: token (str), query_str (str) Output: dict where 'messages' maps to a list of dictionaries containing messages where query_str is a substring of the message content ''' # Error check auth_user = user_with_token(token) if auth_user is None: # Invalid token raise AccessError('Invalid token') messages = [] for channel in data['channels']: # Channels the auth_user is a member of if auth_user in channel.get_all_members(): for message in channel.get_messages(): # query_str is a substring of message if query_str in message.get_message(): messages.append(message) return { 'messages': [ { 'message_id': message.get_message_id(), 'u_id': message.get_sender().get_u_id(), 'time_created': message.get_time_created(), 'message': message.get_message(), 'reacts': [ { 'react_id': react.get_react_id(), 'u_ids': [reactor.get_u_id() for reactor in react.get_reactors()], 'is_this_user_reacted': auth_user in react.get_reactors(), } for react in message.get_reacts() ], 'is_pinned': message.get_is_pinned(), } for message in messages ], }
def message_react(token, message_id, react_id): ''' Given a message within a channel, add react to the message using the provided id. Input: token (str), message_id (int), react_id (int) Output: empty dict ''' auth_user = user_with_token(token) message = message_with_message_id(message_id) if auth_user is None: raise AccessError('Invalid token') elif message is None: raise AccessError('Invalid message_id') elif react_id != 1: raise InputError('Invalid react_id') message.add_react(auth_user, react_id) return {}
def channel_details(token, channel_id): ''' Given a Channel with ID channel_id that the authorised user is part of, provide basic details about the channel Input: token (str), channel_id (int) Output: dict ''' # Retrieve data auth_user = user_with_token(token) channel = channel_with_id(channel_id) # Error check if auth_user is None: raise AccessError('Invalid token') elif channel is None: raise InputError('Invalid channel_id') elif auth_user not in channel.get_all_members(): raise AccessError('Authorised user not a member of channel') return { 'name': channel.get_name(), 'owner_members': [ { 'u_id': owner.get_u_id(), 'name_first': owner.get_name_first(), 'name_last': owner.get_name_last(), 'profile_img_url': owner.get_profile_img_url(), } for owner in channel.get_owner_members() ], 'all_members': [ { 'u_id': member.get_u_id(), 'name_first': member.get_name_first(), 'name_last': member.get_name_last(), 'profile_img_url': member.get_profile_img_url(), } for member in channel.get_all_members() ], }
def user_profile_uploadphoto(token, url_root, img_url, x_start, y_start, x_end, y_end): ''' Helper function for cropping the image with url within provided bounds, and setting this image as their profile picture. Input: token (str), url_root (root of url - str), img_url (str), x_start (int), y_start (int), x_end (int), y_end (int) Output: empty dict ''' auth_user = user_with_token(token) if auth_user is None: raise AccessError('Invalid token') # Retrieve image image_name = f'static/{auth_user.get_handle()}.jpg' try: urllib.request.urlretrieve(img_url, image_name) except urllib.request.URLError: raise InputError('Invalid image url') # Crop and save image try: img = Image.open(image_name) width, height = img.size except: raise InputError('Invalid image url') if not valid_crop_dimensions(width, height, x_start, y_start, x_end, y_end): raise InputError(f'Invalid image dimensions provided. \ Original image has width {width} and height {height}.') img = img.crop((x_start, y_start, x_end, y_end)) img.save(image_name) # Update profile pic auth_user.set_profile_img_url(url_root + image_name) return {}