def send(token, channel_id, message): ''' ## DESCRIPTION ## Given a token, channel_id, and message, decodes the token, and adds the message 'message' into the messages database with the user of the token. ## TYPES ## token - string channel_id - integer message - string ## RETURN VALUE ## {} ## EXCEPTIONS ## InputError if - Message is more than 1000 characters - Message is 0 characters. AccessError - Token is invalid - The user has not joined the channel they are trying to post to ''' global message_data, next_id if len(message) <= 0 or len(message) > 1000: raise InputError( description="Message must be between 1 and 1000 characters long.") user = aux_common.decode_token(token) if user['u_id'] == 'error_invalid_token' or not isinstance(user['u_id'], int): raise AccessError(description='Invalid token') if not aux_common.user_in_channel(user['u_id'], channel_id): raise AccessError( description='User is not in the channel that message is being sent to') next_msg_id = next_id.get('message_id') if not next_msg_id: # key does not exist yet next_id['message_id'] = 0 next_msg_id = 0 next_id['message_id'] += 1 message_dict = { 'message_id': next_msg_id, 'message': message, 'sending_u_id': user['u_id'], 'timestamp': datetime.utcnow(), 'channel_id': int(channel_id), 'reacting_u_ids': [], 'pinned': False } message_data.append(message_dict) return {'message_id': len(message_data) - 1}
def sethandle(token, handle_str): """ INPUTS: token, handle_str RETURNS: {} INPUTERROR: *2 <= handle_str <= 20 * handle already used DESCRIPTION: Updates an AUTHORISED user's handle """ global user_data if len(handle_str) >= 20 or len(handle_str) <= 1: raise InputError( description='Handle string must be between 1 and 20 characters') if aux_discrete.check_handle_in_use is False: raise InputError(description='Handle string is already in use') if aux_common.decode_token(token) == {'u_id': 'error_invalid_token'}: raise AccessError(description="Invalid token") user_id = aux_common.decode_token(token) for user in user_data: if user['u_id'] == user_id['u_id']: user['handle'] = handle_str return {}
def profile(token, u_id): """ INPUTS: token, u_id DESCRIPTION: For a valid user, Returns information about their user id, email first name last name and handle RETURNS: { user } INPUTERROR: * User with u_id is not a valid user """ if aux_common.decode_token(token) == {'u_id': 'error_invalid_token'}: raise AccessError(description="Invalid token") # Input Error if not aux_discrete.user_exists(u_id): raise InputError(description=f'User with u_id {u_id} is not valid') # Loop through all the users and return the right user through its identifiers (u_id) for user in user_data: if user['u_id'] == u_id: # check if i have the layout of the dictionary plss return { 'user': { 'u_id': user['u_id'], 'email': user['email'], 'name_first': user['first_name'], 'name_last': user['last_name'], 'handle_str': user['handle'], 'profile_img_url': user['profile_img_url'] } # Need to add handle_str to the global data } return {}
def setemail(token, email): """ INPUTS: token, email RETURNS: {} INPUTERROR: *Email not valid (Copy code) *Already in use DESCRIPTION: Updates authorised user's email address """ global user_data if not aux_discrete.check_email_valid(email): raise InputError(description='Email is not of valid form') if not aux_discrete.check_email_in_use(email): raise InputError(description='Email already in use') if aux_common.decode_token(token) == {'u_id': 'error_invalid_token'}: raise AccessError(description="Invalid token") user_id = aux_common.decode_token(token) for user in user_data: if user['u_id'] == user_id['u_id']: user['email'] = email return {}
def edit(token, message_id, message): ''' ## DESCRIPTION ## Given a token and message_id, if the user has requisite permissions, changes the message to the new message. ## TYPES ## token - string message_id - integer message - string ## RETURN VALUE ## {} ## EXCEPTIONS ## InputError if - message_id is invalid - new message is empty or greater than 1000 characters AccessError if: - Token is invalid AccessError if ALL OF THE FOLLOWING: - User is not the authorised user (the creator of message) - User is not an owner of this channel - User is not an owner of the slackr ''' global message_data if len(message) <= 0 or len(message) > 1000: raise InputError( description="Message must be between 0 and 1000 characters long") msg_index = aux_discrete.find_message(message_id) if msg_index == -1: raise InputError(description='Message ID is invalid.') user = aux_common.decode_token(token) if user['u_id'] == 'error_invalid_token' or not isinstance(user['u_id'], int): raise AccessError(description="Invalid token") if not aux_common.user_is_owner_channel(user['u_id'], message_data[msg_index]['channel_id']) \ and not aux_common.user_is_owner_of_slackr(user['u_id']) \ and message_data[msg_index]['sending_u_id'] != user['u_id']: raise AccessError( description="User does not have requisite permission to remove the message") message_data[msg_index]['message'] = message return {}
def remove(token, message_id): ''' ## DESCRIPTION ## Given a token and message_id, if the user has requisite permissions, removes the message message_id ## TYPES ## token - string message_id - integer ## RETURN VALUE ## {} ## EXCEPTIONS ## InputError if - message_id is invalid AccessError if: - Token is invalid AccessError if ALL OF THE FOLLOWING: - User is not the authorised user (the creator of message) - User is not an owner of this channel - User is not an owner of the slackr ''' global message_data msg_index = aux_discrete.find_message(message_id) if msg_index == -1: raise InputError(description='Message ID is invalid.') user = aux_common.decode_token(token) if user['u_id'] == 'error_invalid_token' or not isinstance(user['u_id'], int): raise AccessError(description="Invalid token") if not aux_common.user_is_owner_channel(user['u_id'], message_data[msg_index]['channel_id']) \ and not aux_common.user_is_owner_of_slackr(user['u_id']) and \ message_data[msg_index]['sending_u_id'] != user['u_id']: raise AccessError( description="User does not have requisite permission to remove the message") message_data.pop(msg_index) return {}
def token_valid(token): ''' Checks whether a token is valid. If not, raises AccessError. ''' authorized_caller = decode_token(token) if authorized_caller["u_id"] == { "u_id": "error_invalid_token" } or decode_token(token) == { "u_id": "error_invalid_token" }: raise AccessError('You do not have permission to do this.')
def send_later(token, channel_id, message, time_to_send): ''' ## DESCRIPTION ## Given a token, channel_id, and message, decodes the token, and adds 'message' into the pending messages database with the requested time_to_send instead of datetime.utcnow() ## TYPES ## token - string channel_id - integer message - string time_to_send - datetime ## RETURN VALUE ## {} ## EXCEPTIONS ## InputError if - Message is more than 1000 characters - Message is 0 characters. - The time_to_send is in the past. AccessError if - Token is invalid - The user has not joined the channel they are trying to post to ''' global pending_message_data if len(message) <= 0 or len(message) > 1000: raise InputError( description='Message must be between 1 and 1000 characters long.') if time_to_send < datetime.utcnow(): raise InputError(description='The date to send must be in the future.') user = aux_common.decode_token(token) if user['u_id'] == 'error_invalid_token' or not isinstance(user['u_id'], int) or \ not aux_common.user_in_channel(user['u_id'], channel_id): raise AccessError( description='User is not in the channel that message is being sent to') message_dict = { 'temporary_message_id': len(pending_message_data), 'message': message, 'sending_u_id': user['u_id'], 'timestamp': time_to_send, 'channel_id': int(channel_id), 'reacting_u_ids': [], 'pinned': False } print(time_to_send, flush=True) pending_message_data.append(message_dict) return {'message_id': (len(message_data) - 1)}
def start(token, channel_id, length): ''' Requirements: correct channel_id and token This will start a standup session for 'length' seconds. Messages sent to this session must be through 'send'. A message sent will be appended to the final message using send. At time = length, the message package will be sent over to message_data. #Raises 2 possible InputErrors ''' # preliminary checks if common.ch_id_exists(channel_id) == -1: raise InputError('Invalid Channel. Cannot start standup.') if active(token, channel_id)['is_active']: raise InputError( 'A standup is active. Only 1 standup may be running at a time.') authorized_caller = common.decode_token(token) if authorized_caller["u_id"] == { "u_id": "error_invalid_token" } or not common.user_in_channel(authorized_caller["u_id"], channel_id): raise AccessError('You do not have permission to do this.') # check if length is valid. Assuming that a standup lasts for at least 1 second. if length < 1: raise InputError( description= 'You cannot start a standup for a time less than 1 second.') # keep start and finish for better accuracy time_start = datetime.datetime.utcnow() time_finish = time_start + datetime.timedelta(seconds=length) discrete.standup_data(time_start, time_finish, channel_id, authorized_caller['u_id']) index = discrete.find_standup(channel_id) # at length = length seconds, sends all the messages from the queue as a complete package Timer(length, discrete.send_standup, [token, channel_id, index]).start() Timer(length + 1, data.standup_list.pop, [index]).start() return { 'time_finish': int(time_finish.replace(tzinfo=datetime.timezone.utc).timestamp()) }
def unpin(token, message_id): ''' ## DESCRIPTION ## Given a token and message_id, changes the 'pinned' attribute of the corresponding 'message_id' dictinoary to False. ## TYPES ## token - string message_id - integer ## RETURN VALUE ## {} ## EXCEPTIONS ## InputError if - message_id is invalid - the user is not an owner - message is not pinned AccessError if - The user is not a member of the channel that the message is within - Token is invalid ''' global message_data message_index = aux_discrete.find_message(message_id) if message_index == -1: raise InputError(description='Message ID is invalid.') user = aux_common.decode_token(token) if user['u_id'] == 'error_invalid_token' or not isinstance(user['u_id'], int): raise AccessError(description="Invalid token") if not aux_common.user_in_channel(user['u_id'], message_data[message_index]['channel_id']): raise InputError( description="User is not in the channel that the message to unpin to is in.") if not message_data[message_index]['pinned']: raise InputError(description="Message not yet pinned") if not aux_common.user_is_owner_of_slackr(user['u_id']) and not \ aux_common.user_is_owner_channel(user['u_id'], message_data[message_index]['channel_id']): raise InputError( description="User is not owner of the slackr and not owner of the channel") message_data[message_index]['pinned'] = False return {}
def react(token, message_id, react_id): ''' ## DESCRIPTION ## Given a token, message_id, and react_id, adds the 'token' user to the 'reacting_u_ids' list of the corresponding 'message_id'. ## TYPES ## token - string message_id - integer react_id - integer ## RETURN VALUE ## {} ## EXCEPTIONS ## InputError if - react_id is not 1 - message_id is not a valid message within a channel the authorised user has joined - User has already 'reacted' to this message. AccessError if - Token is invalid ''' global message_data message_index = aux_discrete.find_message(message_id) if message_index == -1: raise InputError(description='Message ID is invalid.') user = aux_common.decode_token(token) if user['u_id'] == 'error_invalid_token' or not isinstance(user['u_id'], int): raise AccessError(description="Invalid token") if not aux_common.user_in_channel(user['u_id'], message_data[message_index]['channel_id']): raise InputError( description="User is not in the channel that the message to react to is in.") if react_id != 1: raise InputError(description="Invalid react ID") if user['u_id'] in message_data[message_index]['reacting_u_ids']: raise InputError(description="Message already reacted to") message_data[message_index]['reacting_u_ids'].append(user['u_id']) return {}
def search(token, query_str): ''' ## DESCRIPTION ## Given a query string, return a collection of messages in all of the channels that the user has joined that match the query. Results are sorted from most recent message to least recent message. ## TYPES ## token - string query_str - string ## RETURN VALUE ## {messages} ## EXCEPTIONS ## N/A ''' message_list = [] user_id = aux_common.decode_token(token) if user_id == {'u_id': "error_invalid_token"}: raise AccessError(description="Invalid Token") for message in message_data: if aux_common.user_in_channel(user_id['u_id'], message['channel_id']): if query_str in message['message']: message_dict = { "message_id": message['message_id'], "u_id": message['sending_u_id'], "message": message['message'], "time_created": message['timestamp'].replace(tzinfo=datetime.timezone.utc).\ timestamp(), "reacts": [{'react_id': 1, 'u_ids': message['reacting_u_ids'], 'is_this_user_reacted': \ bool(user_id['u_id'] in message['reacting_u_ids'])}], "is_pinned": message['pinned'], } message_list.append(message_dict) #print(f"returning {'messages': message_dict}", flush=True) return{'messages': message_list}
def create(token, name, is_public): ''' ## DESCRIPTION ## Given a token, name, and is_public boolean, creates a new channel with the properties passed. ## TYPES ## token - string name - string is_public - boolean ## RETURN VALUE ## { channel_id } ## EXCEPTIONS ## AccessError if: - Token is invalid InputError if: - The name is < 1 or > 20 characters long ''' global all_channels, next_id user = aux_common.decode_token(token) if user['u_id'] == 'error_invalid_token' or not isinstance( user['u_id'], int): raise AccessError(description="Invalid token") if len(name) < 1 or len(name) > 20: raise InputError( description="Name of channel must be 1 to 20 characters long.") next_ch_id = next_id.get('channel_id') if not next_ch_id: # key does not exist yet next_id['channel_id'] = 0 next_ch_id = 0 next_id['channel_id'] += 1 new_channel_dict = { "name": name, "channel_id": next_ch_id, "owner_members": [user['u_id']], "all_members": [user['u_id']], "is_public": is_public } all_channels.append(new_channel_dict) return {'channel_id': next_ch_id}
def setname(token, first_name, last_name): """ INPUTS:token, name_first,name_last RETURNS: {} INPUTERROR: * Name_first is not between 1 and 50 charactors inclusive in length * Name_last is not between 1 and 50 characters inclusive in length DESCRIPTION: Updates the authorised user's first and last name """ global user_data # Raise errors first if len(first_name) >= 50 or len(first_name) <= 1: raise InputError( description='Length of first name must be between 1 - 50 characters' ) if len(last_name) >= 50 or len(last_name) <= 1: raise InputError( description='Length of last name must be between 1 - 50 characters' ) # decode token to get user_id, assuming u_id is the identifier for the user_data if aux_common.decode_token(token) == {'u_id': 'error_invalid_token'}: raise AccessError(description="Invalid token") # Reveals dictionary of form {"u_id": u_id} user_id = aux_common.decode_token(token) # remember user_id is a dictionary of 'u_id' # Go into that users dictionary and change the first_name and last_name to inputs for user in user_data: if user['u_id'] == user_id['u_id']: user['first_name'] = first_name user['last_name'] = last_name return {}
def send(token, channel_id, message): ''' Requirements: correct token, channel_id and len(message) <= 1000 Appends a message to the standup_messages list. It will depend on an aux_ function buffering it first before appending at the end of the list. #Raises 3 Possible InputErrors, 1 Access Error ''' # ch_id is invalid -> InputError index = common.ch_id_exists(channel_id) if index == -1: raise InputError('Channel does not exist.') # message > 1000 characters if len(message) > 1000 or len(message) == 0: raise InputError( description= f'Your message is {len(message)-1000} over the limit. Your message cannot be over 1000 \ characters') # user is not authorized -> AccessError authorized_caller = common.decode_token(token) if authorized_caller == {"u_id": "error_invalid_token"} or \ not common.user_in_channel(authorized_caller["u_id"], channel_id): raise AccessError( 'You cannot send a standup message in this channel which you are not part of.' ) # active standup false -> InputError if not active(token, channel_id)['is_active']: raise InputError('Standup not active.') index = discrete.find_standup(channel_id) u_index = discrete.find_user(authorized_caller['u_id']) data.standup_list[index][ 'message'] += f"\n{data.user_data[u_index]['handle']}: {message}" print(f"message now {data.standup_list[index]['message']}", flush=True) return {}
def all_(token): """ Returns a list of all users and their associated details """ # Loop through all users, and append to new dictionary and return that dictionary users_dictionary = {'users': []} if aux_common.decode_token(token) == {'u_id': 'error_invalid_token'}: raise AccessError(description="Invalid token") for user in user_data: user_dict = { 'u_id': user['u_id'], 'email': user['email'], 'name_first': user['first_name'], 'name_last': user['last_name'], 'handle_str': user['handle'], 'profile_img_url': user['profile_img_url'] } # Appends dictionaries to the list users_dictionary['users'].append(user_dict) return users_dictionary
def listall(token): ''' ## DESCRIPTION ## Given a token, returns all channels (except private ones) ## TYPES ## token - string ## RETURN VALUE ## {'channels': [ { 'channel_id': <integer>, 'name': string }, ... ]} ## EXCEPTIONS ## AccessError if: - Token is invalid ''' global all_channels user = aux_common.decode_token(token) if user['u_id'] == 'error_invalid_token' or not isinstance( user['u_id'], int): raise AccessError(description="Invalid token") channels_dict = {'channels': []} for channel in all_channels: if channel['is_public']: channels_dict['channels'].append({ 'channel_id': channel['channel_id'], 'name': channel['name'] }) return channels_dict
def upload_photo(token, img_url, x_start, y_start, x_end, y_end): ''' INPUTS: token (str), img_url (str), x_start, y_start, x_end, y_end (ints) RETURNS: {} AUTHERROR: invalid token INPUTERROR: Image URL request raises status code not 200 x_start, y_start, x_end, y_end are not in the dimensions of the image at the URL or are invalid Image URL is not linked to a valid image Image URL is not a JPEG/JPG file. DESCRIPTION: Updates an AUTHORISED user's handle ''' global user_data, next_id try: x_start = int(x_start) y_start = int(y_start) x_end = int(x_end) y_end = int(y_end) except ValueError: raise InputError(description="x & y values must be integers") u_id = aux_common.decode_token(token)['u_id'] if u_id == "error_invalid_token": raise AccessError(description="Invalid token") response = requests.get(img_url) if response.status_code != 200: raise InputError( description=f"Image URL invalid: status code {response.status_code}" ) try: img = Image.open(BytesIO(response.content)) except UnidentifiedImageError: raise InputError( description="That URL is not a direct link to an image") if img.format != "JPEG": raise InputError(description="Image must be a JPEG/JPG") width, height = img.size if x_start > width or x_end > width or y_start > height or y_end > height: raise InputError( description= "One or more crop parameters were out of the photo's range.") try: img = img.crop((x_start, y_start, x_end, y_end)) except SystemError: raise InputError( description= "Crop parameters were invalid. Are the ends greater than the starts?" ) next_img_name = next_id.get('image_name') if not next_img_name: # key does not exist yet next_id['image_name'] = 0 next_img_name = 0 try: img.save(f"./profile_images/{next_img_name}.jpg", "JPEG") except SystemError: raise InputError( description= "Error occurred while saving the image. Are the ends greater than the starts?" ) u_index = aux_discrete.find_user(u_id) user_data[u_index][ 'profile_img_url'] = f"http://127.0.0.1:8080/profileimages/{next_img_name}.jpg" next_id['image_name'] += 1 return {}
def guess(token, channel_id, X): ''' Input - valid token, channel ID and a guess X. Behavior: Given a message with some 2nd string 'X', determines whether the guess is correct or not. Output - {} ''' # ch_id is invalid -> InputError index = common.ch_id_exists(channel_id) if index == -1: raise InputError( description='The channel you are guessing on does not exist.') # check if token is valid authorized_caller = common.decode_token(token) if authorized_caller == {"u_id": "error_invalid_token"} or \ not common.user_in_channel(authorized_caller["u_id"], channel_id): raise AccessError( description='You do not have permission to do this.') # ensure channel has a hangman session # NOTE YOU must abstract these functions ASAP. index = None for session in hangman_sessions: if session["channel_id"] == channel_id: index = hangman_sessions.index(session) if index == None: raise InputError(description='A hangman session is not running right now') X = X[0].split(' ') # if word not specified (is list), generate a random word if len(X) != 1: X = X[1] # check if X has been guessed already if X in hangman_sessions[index]['guesses']: raise InputError(description='Your guess has already been guessed.') # catch empty input error if not isinstance(X, str): raise InputError(description='Usage: /guess [letter]') # SEND THE /guess X as a message to message data. message.send(token, channel_id, '/guess ' + X) # check if the guess results in the complete answer hm_word = hangman_sessions[index]['word'] guesses = hangman_sessions[index]['guesses'] guesses.append(X) prev_word = hangman_sessions[index]['decrement_word'] hangman_sessions[index]['decrement_word'] = hangman_sessions[index]['decrement_word'].replace(X, '') word_decrement = hangman_sessions[index]['decrement_word'] if prev_word == word_decrement: hangman_sessions[index]["bad_attempts"] += 1 # render ASCII or some image art. bot_message = _render_ascii( hangman_sessions[index]["bad_attempts"], channel_id, guesses, hm_word) if hm_word.replace(X, '') == hm_word: hangman_sessions[index]['remaining_guesses'] -= 1 # if guess results in complete answer, then done and send congratulations if word_decrement == '': hangman_sessions.pop(index) bot_message += "\n\(^O^)/\nYou've correctly guessed the word!" # decrement the amount of guesses available, and if no more guesses, # remove hangman session and send taunting message else: if hangman_sessions[index]['remaining_guesses'] == 0: hangman_sessions.pop(index) bot_message += f"\n(✖╭╮✖)\nThe word is {hm_word}, you have lost\nF to pay respects" else: bot_message += "\nLetters guessed:" for word in hangman_sessions[index]['guesses']: bot_message += f" {word}" # send message as the bot bot_send(bot_message, channel_id) return {}
def start(token, channel_id, word): ''' Input - valid token, channel_id and word. Word validity is handled in the frontend. Behavior: Starts a hangman session in a channel. Takes a word from the random_word mod, and parses it so that it only contains alphabetical characters. All hangman sessions are stored in a hangman data structure. Output - {} ''' # ch_id is invalid -> InputError index = common.ch_id_exists(channel_id) if index == -1: raise InputError( description='The channel you are attempting to start a hangman in does not exist.') # check if token is valid authorized_caller = common.decode_token(token) if authorized_caller == {"u_id": "error_invalid_token"} or \ not common.user_in_channel(authorized_caller["u_id"], channel_id): raise AccessError( description='You do not have permission to start hangman.') # check if a hangman session is ongoing for session in hangman_sessions: if session["channel_id"] == channel_id: raise InputError( description='A hangman session is already ongoing in this channel') final_word = word[0].split(' ') # if word not specified (is list), generate a random word if len(final_word) == 1: _site = "http://svnweb.freebsd.org/csrg/share/dict/words?view=co&content-type=text/plain" response = requests.get(_site) _words = response.content.splitlines() final_word = random.choice(_words).decode() else: final_word = final_word[1].strip() # add to hangman sessions hangman_sessions.append({ "channel_id": channel_id, "word": final_word.lower(), "decrement_word": final_word.lower(), "guesses": [], "remaining_guesses": 8, "bad_attempts": 0 }) # send message as the bot idX = discrete.find_user(authorized_caller["u_id"]) starter_name = user_data[idX]["first_name"] bot_message = f"{starter_name} has just started a hangman!" bot_message += _missing([], final_word) bot_send(bot_message, channel_id) return {}