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 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 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 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 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 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 list_(token): ''' ## DESCRIPTION ## Given a token, returns the channels the user is in. ## 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 aux_common.user_in_channel(user['u_id'], channel['channel_id']): channels_dict['channels'].append({ 'channel_id': channel['channel_id'], 'name': channel['name'] }) return channels_dict
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 {}
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 {}