def message_react(token, message_id, react_id): ''' adds a reaction to a messages list of reactions expects parameter types: token: str message_id: int react_id: int returns empty dictionary ''' u_id = check_token(token) message = get_message_by_msg_id(message_id) if react_id not in VALID_REACTS: raise InputError(description='Invalid react id') if not user_in_channel_by_msg_id(message_id, token): raise InputError(description='User is not in channel') for react in message['reacts']: if react['react_id'] == react_id: if u_id in react['u_ids']: raise InputError(description='Already reacted') react['u_ids'].append(u_id) return {}
def errorcheck_addowner_v1(token, channel_id, u_id): """ Checks the error cases for add_owner Arguments: token - jwt encoded data structure that stores auth_user info channel_id (int) - channel to be added into u_id (int) - user to be checked Exceptions: Refer to addowner function Return values: None """ data_structure = token_check(token) if is_valid_u_id(u_id) == False: raise InputError("u_id does not refer to a valid user") if is_valid_channel_id(channel_id) == False: raise InputError("Channel ID is not a valid channel") if is_user_channel_membertype(channel_id, u_id, CHANNEL_MEMBER) == False: raise InputError( "user with user id u_id is not already a member of the channel") if is_user_channel_membertype(channel_id, u_id, CHANNEL_OWNER) == True: raise InputError( "user with user id u_id is already an owner of the channel") if is_user_channel_membertype( channel_id, int(data_structure["u_id"]), CHANNEL_OWNER) == False and find_auth_user_permission_id( int(data_structure["u_id"])) == 2: raise AccessError( "the authorised user is not an owner of the Dreams or an owner of this channel" )
def user_profile_uploadphoto_v1(token, img_url, x_start, y_start, x_end, y_end): auth_user_id = token_check(token)["u_id"] response = requests.get(img_url) if response.status_code != 200: raise InputError("img_url returns an HTTP status other than 200.") path = generate_user_profile_photo(auth_user_id)["profile_img_path"] request.urlretrieve(img_url, path) image = Image.open(path) (left, top, right, bottom) = image.getbbox() if x_start < left or x_end > right or y_start < top or y_end > bottom: raise InputError( "any of x_start, y_start, x_end, y_end are not within the dimensions of the image at the URL." ) image = Image.open(path) img_crop = image.crop(box=(x_start, y_start, x_end, y_end)) img_crop.save(path) return {}
def get_user_information(u_id): '''Returns user information in the correct format for the slackr frontend. :param u_id: User id, corresponding to an existing slackr user :type u_id: int :return: dictionary containing all user information :rtype: dict ''' try: user = get_users()[u_id] except KeyError: raise InputError(description='No user exists with that id') if is_user_disabled(u_id): raise InputError(description="This user has been deleted") return { 'u_id': u_id, 'email': user['email'], 'name_first': user['name_first'], 'name_last': user['name_last'], 'handle_str': user['handle_str'], 'profile_img_url': f'{os.getenv("URL")}{user["profile_img_url"]}' }
def dm_remove_v1(token, dm_id): """ Summary Delete an existing DM. This can only be done by the original creator of the DM. Args: token (string): A user session token dm_id (int): A dm id number AccessError: When an invalid token is given, or the authorised user is not already a member of the DM InputError: When dm_id does not refer to a valid dm, InputError: When u_id does not refer to a valid user. """ global messages check_token(token) auth_user = user_from_token(token) # Check that auth user is the creator of the dm dm_found = False dm_members = [] for dm in dms: if dm['dm_id'] == dm_id: dm_found = True dm_members = dm['members'] if dm['creator'] != auth_user['u_id']: raise AccessError( f"User {auth_user['handle_str']} is not the creator of dm with id {dm_id}" ) if not dm_found: raise InputError(f"id {dm_id} does not refer to a valid dm") # Remove dm from all members' dm list for user in users: if user['u_id'] in dm_members: user['dms'].remove(dm_id) update_user_dm_stats(user['u_id']) # Delete messages sent in this dm for message in list(messages): if message['dm_id'] == dm_id: messages.remove(message) # Remove desired dm from dms list # Must be done this way to ensure that dms list is updated across all modules cleared_dms = [dm for dm in dms if dm['dm_id'] != dm_id] dms.clear() for dm in cleared_dms: dms.append(dm) update_dm_stats() return {}
def channel_join_v2(token, channel_id): ''' Summary: Given a channel_id of a channel that the authorised user can join, add them to that channel members Args: token (string): A user session token channel_id (int): A channel_id number Returns: a empty dictionary Raises: InputError when: Channel ID is not a valid channel AccessError when: channel_id refers to a channel that is private (when the authorised user is not a global owner) ''' global users, channels assert check_token(token) auth_user = user_from_token(token) if valid_channel(channel_id) == False: raise InputError(f"Channel ID {channel_id} is not a valid channel") current_channel = find_channel( channel_id) #current channel with the given channel id is_dreams_owner = auth_user['permission'] == 1 not_in_current_channel = auth_user['u_id'] not in current_channel[ 'all_members'] if current_channel['is_public'] == True: if not_in_current_channel: if is_dreams_owner: current_channel['owner_members'].append(auth_user['u_id']) current_channel['all_members'].append(auth_user['u_id']) auth_user['channels'].append(channel_id) update_user_channel_stats(auth_user['u_id']) else: if is_dreams_owner and not_in_current_channel: current_channel['owner_members'].append(auth_user['u_id']) current_channel['all_members'].append(auth_user['u_id']) auth_user['channels'].append(channel_id) update_user_channel_stats(auth_user['u_id']) else: raise AccessError( f"channel_id {channel_id} refers to a channel that is private ( authorised user is not a global owner)" ) return {}
def message_unreact(token, message_id, react_id): ''' removes a reaction from a messages list of reactions expects parameter types: token: str message_id: int react_id: int returns empty dictionary ''' u_id = check_token(token) message = get_message_by_msg_id(message_id) if react_id not in VALID_REACTS: raise InputError(description='Invalid react id') if not user_in_channel_by_msg_id(message_id, token): raise InputError(description='User is not in channel') for react in message['reacts']: if react['react_id'] == react_id: if u_id in react['u_ids']: react['u_ids'].remove(u_id) else: raise InputError(description='You have not made this reaction') return {}
def check_login_inputs(email, password): ''' Checks email is valid Checks email is associated with a user Checks password is correct Returns the u_id associated with the user ''' if not is_email_valid(email): raise InputError(description="Invalid Email") u_id = id_from_email(email) if u_id is None: raise InputError(description="This email has not been registered") glob_users = get_users() if is_user_disabled(u_id): raise InputError(description='This account has been removed from Slackr') password_hash = hash_string(password) # checking user password if glob_users[u_id]['password_hash'] != password_hash: raise InputError(description='Incorrect password') return u_id
def dm_leave_v1(user_token, dm_id): ''' The function is given the token who is gonna leave the dm without destroy the dm. Arguments: user_token (string) - some encoded information about one auth user dm_id (int) - the id of the dm needed removing ... Exceptions: AccessError - Occurs when the token is not a valid token InputError - Occurs when dm_id is not valid AccessError - Occurs when the auth_user is not the member of the dm Return Value: Returns {} ''' database = {} with open(file_address, "r") as f: database = json.load(f) #get the required value auth_user_id = findUser(user_token) #check if the input is valid if dm_id > database['dms'][-1]['dm_id'] or database['dms'][dm_id - 1]['dm_id'] == -1: raise InputError(description="The dm_id is not valid.") if auth_user_id not in database['dms'][dm_id - 1]['dm_members']: raise AccessError(description="The one who wants to leave the dm is not in the dm.") #remove the dm with some specific dm_id pos = database['dms'][dm_id - 1]['dm_members'].index(auth_user_id, 0, len(database['dms'][dm_id - 1]['dm_members'])) database['dms'][dm_id - 1]['dm_members'].pop(pos) if len(database['dms'][dm_id - 1]['dm_members']) == 0: database['dms'][dm_id - 1]['dm_id'] = -1 elif database['dms'][dm_id - 1]['dm_owner'] == auth_user_id: database['dms'][dm_id - 1]['dm_owner'] = database['dms'][dm_id - 1]['dm_members'][0] append_user_stats(findUser(user_token), database) append_dream_stats(findUser(user_token), database) #put the data back to the data file with open(file_address, "w") as f: json.dump(database, f) return {}
def check_reset_code(reset_code): '''Validates reset_code and returns users email :param reset_code: jwt token to be validated :type reset_code: str :raises InputError: If reset_code is invalid or expired :return: email :rtype: str ''' try: return decode(reset_code.encode('utf-8'), SECRET, algorithms=['HS256'])['email'] except InvalidTokenError: raise InputError(description='Reset code invalid or expired') from None
def auth_passwordreset_reset_v1(reset_code, new_password): ''' Given user email and password, return a new token for that session Arguments: reset_code (string) - Code received in email to change password new_password (string) - new password that the user wants to change their old password to ... Exceptions: InputError - reset_code is not a valid reset code InputError - Password entered is less than 6 characters long Return Value: Returns {} ''' with open("data.json") as json_file: data = load(json_file) if len(new_password) < 6: raise InputError('Password entered is less than 6 characters long') valid_reset_code = False for users in data['users']: if users['reset_code'] == reset_code: valid_reset_code = True users['password'] = hashlib.sha256( new_password.encode()).hexdigest() if valid_reset_code == False: raise InputError('Invalid reset code') with open("data.json", "w") as json_file: dump(data, json_file, indent=4) return {}
def dm_leave_v1(token, dm_id): with open('src/data.json', 'r') as FILE: data = json.load(FILE) # check that token is valid leaveid = None for i in range(len(data['users'])): if data['users'][i]['token'] == token: leaveid = data["users"][i]["u_id"] if leaveid is None: raise AccessError("Invalid token") # check if the dm_id exists if check_dm_exists(dm_id) == False: raise InputError("Invalid dm") # check that the user attempting to leave is a member of the dm if check_user_in_dm(leaveid, dm_id) == False: raise AccessError("Authorised user needs to be a member of the dm") memberslist = data["dms"][dm_id]["name"].split(", ") memberslist.remove(data["users"][leaveid]["handle"]) final_list = ", ".join(memberslist) data["dms"][dm_id]["name"] = final_list for i in range(len(data["dms"][dm_id]["members"])): if leaveid == data["dms"][dm_id]["members"][i]["u_id"]: del data["dms"][dm_id]["members"][i] break for i in range(len(data["dms"][dm_id]["owner_members"])): if leaveid == data["dms"][dm_id]["owner_members"][i]["u_id"]: del data["dms"][dm_id]["owner_members"][i] data["dms"][dm_id]["owner_members"].append( data["dms"][dm_id]["members"][0]) num_dms_joined = data["user_stats"][leaveid]["stats"]["dms_joined"][-1][ "num_dms_joined"] - 1 dms_joined = { "num_dms_joined": num_dms_joined, "time_stamp": create_timestamp() } data["user_stats"][leaveid]["stats"]["dms_joined"].append(dms_joined) with open('src/data.json', 'w') as FILE: json.dump(data, FILE, indent=4) calc_involement_rate(leaveid) calc_utilisation_rate() return {}
def dm_remove_v1(token, dm_id): """ Remove an existing DM. This can only be done by the original creator of the DM. Arguments: token (string) - jwt encoded data structure of auth_user dm_id (int) - dm_id that be removed Exceptions: InputError - Occurs when dm_id does not refer to a valid DM AccessError - Occurs when the user is not the original DM creator Return value: Returns user (dictionary) on success a dictionary with information about nothing """ time_stamp = datetime.now().replace(microsecond=0).timestamp() with open("data.json") as json_file: data = load(json_file) auth_user_id = token_check(token)["u_id"] is_dm_exist = False for dm_data in data["dms"]: if dm_data["dm_id"] == dm_id: dm = dm_data is_dm_exist = True break if is_dm_exist is False: raise InputError("dm_id does not refer to a valid DM") is_auth_user_creator = False if auth_user_id == dm["original_creator"]: is_auth_user_creator = True if is_auth_user_creator is False: raise AccessError("the user is not the original DM creator") dreams_stats_update(data, stat_types.DM_REMOVE, time_stamp) for member in dm["members"]: user_stats_update(data, member, stat_types.DM_LEAVE, time_stamp) data["dms"].remove(dm) with open("data.json", "w") as json_file: dump(data, json_file, indent=4) return {}
def user_profile_setemail_v2(token, email): """ Given valid token and email, changes a registered users email Arguments: token (string) - valid token associated with a registered user's session email (string) - email the user wants to update to Exceptions: InputError - Occurs if the email is not of valid formation InputError - Occurs if the email is already in use AccessError - Occurs if token is invalid Return value: {} """ #Retrieving data from export.json data = getData() #Checking for an invalid token u_id = findUser(token) #Checking if the inputted email is valid if check_email(email) == False: raise InputError(description='Invalid email') #Checking if the email is already in use for user in data['users']: if email == user['email']: raise InputError(description='Email address is already in use') #Changing the given users email for user in data['users']: if u_id == user['u_id']: user['email'] = email #Writing data to export.json writeData(data) return {}
def standup_send_v1(token, channel_id, message): check_token(token) sender = user_from_token(token) u_id = sender['u_id'] # InputError when Channel ID is not a valid channel if valid_channel(channel_id) == False: raise InputError(f"Channel ID {channel_id} is not a valid channel") # InputError when Message is more than 1000 characters (not including the username and colon) if len(message) > 1000: raise InputError(f"Message more than 1000 characters") # AccessError when The authorised user is not a member of the channel that the message is within if is_channel_member(u_id, channel_id) == False: raise AccessError( f"The authorised user {u_id} is not a member of the channel that the message is within" ) for channel in channels: if channel['channel_id'] == channel_id: # InputError when An active standup is not currently running in this channel if channel['standup']['is_active'] == False: raise InputError( "An active standup is not currently running in this channel" ) else: message_dict = { 'author_handle': sender['handle_str'], 'message': message } channel['standup']['messages'].append(message_dict) return {}
def user_profile_setimage(token, img_url, x_start, y_start, x_end, y_end): #pylint: disable=too-many-arguments ''' Update the authorised user's display photo Downloads the given img_url into the server folder 'images' Crops images if they do not fit the size :param token: :type token: string ''' user_id = check_token(token) user = get_users()[user_id] file_extension = img_url.rsplit('.', 1)[1].lower() if not file_extension in ALLOWED_EXTENSIONS: raise InputError(description="file not allowed") if not x_start < x_end: raise InputError(description="x_start must be larger than x_end") if not y_start < y_end: raise InputError(description="y_start must be larger than y_end") try: image = requests.get(img_url, allow_redirects=True).content except: raise InputError(description="could not download image") new_file_name = f'{generate_random_string(20)}.{file_extension}' new_image = open(f'images/original/{new_file_name}', 'wb') new_image.write(image) original_image = Image.open(f'images/original/{new_file_name}') original_image = original_image.crop((x_start, y_start, x_end, y_end)) cropped_image = open(f'images/cropped/{new_file_name}', 'wb') original_image.save(cropped_image) user['profile_img_url'] = f'/imgurl/{new_file_name}' return {}
def channel_join(token, channel_id): ''' Adds a user to a channel if they are authorised to join it ''' user_id = check_token(token) if not is_valid_channel(channel_id): raise InputError(description="No channel found with that ID") if not is_channel_public( channel_id) and not user_id in get_slackr_owners(): raise AccessError(description="This channel is private") get_channel_members(channel_id).append(user_id) return {}
def message_unpin(token, message_id): ''' Unpins a message in a channel ''' u_id = check_token(token) message_specific = get_message_by_msg_id(message_id) channel_specific = get_channel_by_msg_id(message_id) if u_id not in channel_specific['members'] and not is_channel_owner( token, channel_specific): raise AccessError( description= 'The authorised user is not a member of the channel that the message is within' ) if not is_channel_owner(token, channel_specific): raise InputError(description='The authorised user is not an owner') if message_specific['is_pinned'] is False: raise InputError( description='Message with ID message_id is already unpinned') if is_channel_owner(token, channel_specific ) is True and message_specific['is_pinned'] is True: message_specific['is_pinned'] = False return {}
def user_profile_sethandle(token, handle_str): '''Given a valid new handle_str and token, updates a user's handle_str :param token: jwt token :type token: str :param handle_str: new handle :type handle_str: str :raises InputError: If handle_str is invalid according to is_valid_handle() :return: empty dictionary :rtype: dict ''' user_id = check_token(token) if not is_valid_handle(user_id, handle_str): raise InputError(description='Handle invalid or already being used') user = get_users()[user_id] user['handle_str'] = handle_str return {}
def user_profile_setemail(token, email): '''Given a valid token and new email, updates a user's email address :param token: jwt token :type token: str :param email: new email :type email: str :raises InputError: If email is invalid according to is_new_email() :return: empty dictionary :rtype: dict ''' user_id = check_token(token) if not is_new_email(user_id, email): raise InputError(description='Email invalid or already being used') user = get_users()[user_id] user['email'] = email return {}
def dm_details_v1(token, dm_id): """Summary Allows users that are members of a direct message to view basic information about the DM Dictionary: Contains the name of the dm and details about each of its members Raises: AccessError: When an invalid token is given, or the authorised user referred to by the token is not a member of the dm InputError: When dm_id does not refer to a valid dm """ check_token(token) auth_user = user_from_token(token) # Find the desired dm, if it exists, and retrieve its name and list of member u_ids output = None for dm in dms: if dm['dm_id'] == dm_id: if auth_user['u_id'] in dm['members']: output = {'name': dm['name'], 'members': []} member_ids = dm['members'] else: raise AccessError( f"Authorised user {auth_user['handle_str']} is not a member of DM with id {dm_id}" ) if output == None: # If output is still None, DM with id dm_id was not found raise InputError(f"DM id {dm_id} does not refer to a valid DM") # Find all dm members, and append their details to output for user in users: if user['u_id'] in member_ids: member_details = { 'u_id': user['u_id'], 'email': user['email'], 'name_first': user['name_first'], 'name_last': user['name_last'], 'handle_str': user['handle_str'], 'profile_img_url': user['profile_img_url'] } output['members'].append(member_details) return output
def dm_details_v1(token, dm_id): """ Users that are part of this direct message can view basic information about the DM Arguments: token (string) - jwt encoded data structure of auth_user dm_id (int) - dm to get information of Exceptions: InputError - Occurs when DM ID is not a valid DM AccessError - Occurs when Authorised user is not a member of this DM with dm_id Return value: Returns user (dictionary) on success a dictionary with information about name, members """ with open("data.json") as json_file: data = load(json_file) auth_user_id = token_check(token)["u_id"] is_dm_exist = False for dm_data in data["dms"]: if dm_data["dm_id"] == dm_id: dm = dm_data is_dm_exist = True break if is_dm_exist is False: raise InputError("DM ID is not a valid DM") is_auth_user_member = False if auth_user_id in dm["members"]: is_auth_user_member = True if is_auth_user_member is False: raise AccessError( "Authorised user is not a member of this DM with dm_id") dm_name = dm["name"] dm_members = [] for member in dm["members"]: dm_members.append(user_profile_v2(token, member)["user"]) return {"name": dm_name, "members": dm_members}
def channel_details_v2(token, channel_id): ''' Given a Channel with ID channel_id that the authorised user is part of, provide basic details about the channel Arguments: auth_user_id (integer) - id of user who is part of the channel channel_id (integer) - id of channel Exceptions: InputError - Occurs when channel_id does not refer to a valid channel. AccessError - Occurs when the auth_user_id passed in is not valid - Occurs when the authorised user is not already a member of the channel Return Value: Returns {name, owner_members, all_members} ''' with open('src/data.json', 'r') as FILE: data = json.load(FILE) # check if token exists auth_user_id = False for i in range(len(data["users"])): if data["users"][i]["token"] == token: auth_user_id = data["users"][i]["u_id"] if auth_user_id is False: raise AccessError("Invalid token") auth_user_id = convert_token(token) # check if the channel_id exists if check_channel_exists(channel_id) == False: raise InputError("Invalid channel") # check whether user belongs in this channel if check_user_in_channel(auth_user_id, channel_id) == False: raise AccessError( "Authorised user needs to be a member of the channel") return { 'name': data["channels"][channel_id]['name'], 'is_public': data["channels"][channel_id]['is_public'], 'owner_members': data["channels"][channel_id]['owner_members'], 'all_members': data["channels"][channel_id]['all_members'], }
def user_profile_v1(token, u_id): with open('src/data.json', 'r') as FILE: data = json.load(FILE) if check_token_valid(token) is False: raise AccessError("Invalid Token") if check_user_exists(u_id) is False: raise InputError("Invalid user") return { 'user': { 'u_id': u_id, 'email': data['users'][u_id]['email'], 'name_first': data['users'][u_id]['name_first'], 'name_last': data['users'][u_id]['name_last'], 'handle_str': data['users'][u_id]['handle'], }, }
def dm_details_v1(token, dm_id): with open('src/data.json', 'r') as FILE: data = json.load(FILE) # check if token exists auth_user_id = None for user in data["users"]: if user["token"] == token: auth_user_id = user["u_id"] if auth_user_id is None: raise AccessError("Invalid token") # check if the dm_id exists if check_dm_exists(dm_id) == False: raise InputError("Invalid dm") # check whether user belongs in this dm if check_user_in_dm(auth_user_id, dm_id) == False: raise AccessError("Authorised user needs to be a \ member of the dm") return { 'name': data["dms"][dm_id]['name'], 'members': data["dms"][dm_id]['members'] }
def user_profile_setemail_v1(token, email): # token check if check_token_valid(token) is False: raise AccessError("Invalid Token") email_syntax = re.compile('^[a-zA-Z0-9]+[\\._]?[a-zA-Z0-9]+[@]\\w+[.]\\w{2,3}$') if not email_syntax.match(email): raise InputError("Invalid email") with open('src/data.json', 'r') as FILE: data = json.load(FILE) for i in range(len(data['users'])): if data['users'][i]['token'] == token: data['users'][i]['email'] = email with open('src/data.json', 'w') as FILE: json.dump(data,FILE, indent = 4) return { }
def auth_passwordreset_request_v1(email): with open('src/data.json', 'r') as FILE: data = json.load(FILE) verified = False for i in range(len(data["users"])): if data['users'][i]['email'] == email: verified = True if verified is False: raise InputError("Unregistered email") reset_code = randint(100000, 999999) reset_dict = {"email": email, "reset_code": reset_code} data["reset_codes"].append(reset_dict) with open('src/data.json', 'w') as FILE: json.dump(data, FILE, indent=4) return {"email": email, "reset_code": reset_code}
def user_profile_v2(token, u_id): """ For a valid user, returns information about their user_id, email, first name, last name, and handle Arguments: token (string) - jwt encoded data structure of auth_user u_id (int) - user to build profile of Exceptions: InputError - Occurs when user with u_id is not a valid user AccessError - Occurs when token is invalid Return value: Returns user (dictionary) on success a dictionary with information about user with user id u_id """ with open("data.json") as json_file: data = load(json_file) token_check(token) if is_valid_u_id(u_id) == False: raise InputError(description="User with u_id is not a valid user") for user in data["users"]: if user["u_id"] == u_id: found_user = user return { 'user': { 'u_id': u_id, 'email': found_user["email"], 'name_first': found_user["name_first"], 'name_last': found_user["name_last"], 'handle_str': found_user["handle_str"], "profile_img_url": found_user["profile_img_url"], 'unscramble_record': found_user["unscramble_record"] } }
def standup_active_v1(token, channel_id): ''' Checks if there is an active standup in a channel. Arguments: token (string) - token of user starting standup channel_id (integer) - id of channel standup is in Exceptions: InputError - Occurs when channel_id does not refer to a valid channel AccessError - Occurs when the token passed in is not valid Return Value: Returns {is_active, time_finish} ''' # exception checks with open('src/data.json', 'r') as FILE: data = json.load(FILE) u_id = None for i in range(len(data['users'])): if data['users'][i]['token'] == token: u_id = data['users'][i]['u_id'] if u_id is None: raise AccessError("Invalid token") if check_channel_exists(channel_id) == False: raise InputError("Invalid channel") active = False timeStampStr = None with open('src/data.json', 'r') as FILE: data = json.load(FILE) for i in range(len(data['standups'])): if data['standups'][i]['channel_id'] == channel_id: active = True timeStampStr = data['standups'][i]['time_finish'] return {'is_active': active, 'time_finish': timeStampStr}
def standup_start(token, channel_id, length): ''' starts a standup in a given channel for length amount of time creates blank message with time_finish timestamp, placeholder message_id, and stores it in glob_standups ''' u_id = check_token(token) check_standup_inputs(channel_id, u_id) if is_standup_active(channel_id): raise InputError( description='A standup is already active in this channel') glob_standups = get_standups() time_finish = get_current_timestamp(length) message_template = create_message(u_id, -1, time_finish, []) glob_standups[channel_id] = message_template standup = Timer(length, standup_end, args=[channel_id]) standup.start() return {'time_finish': time_finish}