Пример #1
0
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}
Пример #2
0
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 {}
Пример #3
0
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 {}
Пример #4
0
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 {}
Пример #5
0
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 {}
Пример #6
0
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 {}
Пример #7
0
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.')
Пример #8
0
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)}
Пример #9
0
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())
    }
Пример #10
0
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 {}
Пример #11
0
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 {}
Пример #12
0
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}
Пример #13
0
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}
Пример #14
0
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 {}
Пример #15
0
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 {}
Пример #16
0
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
Пример #17
0
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
Пример #18
0
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 {}
Пример #19
0
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 {}
Пример #20
0
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 {}