'Create a relationship between a user and another user or subreddit
OAuth2 use requires appropriate scope based
on the \'type\' of the relationship:
* moderator: Use "moderator_invite"
* moderator_invite: `modothers`
* contributor: `modcontributors`
* banned: `modcontributors`
* muted: `modcontributors`
* wikibanned: `modcontributors` and `modwiki`
* wikicontributor: `modcontributors` and `modwiki`
* friend: Use [/api/v1/me/friends/{username}](#PUT_api_v1_me_friends_{username})
* enemy: Use [/api/block](#POST_api_block)
Complement to [POST_unfriend](#POST_api_unfriend)'

@allow_oauth2_access
@validatedForm(VUser(), VModhash(), friend=VExistingUname('name'), container=nop('container'), type=VOneOf('type', (('friend',) + _sr_friend_types)), type_and_permissions=VPermissions('type', 'permissions'), note=VLength('note', 300), ban_reason=VLength('ban_reason', 100), duration=VInt('duration', min=1, max=999), ban_message=VMarkdownLength('ban_message', max_length=1000, empty_error=None))
@api_doc(api_section.users, uses_site=True)
def POST_friend(self, form, jquery, friend, container, type, type_and_permissions, note, ban_reason, duration, ban_message):

    self.check_api_friend_oauth_scope(type)
    if (type in self._sr_friend_types):
        if isinstance(c.site, FakeSubreddit):
            abort(403, 'forbidden')
        container = c.site
    else:
        container = VByName('container').run(container)
        if (not container):
            return
    if ((type == 'moderator') and (not c.user_is_admin)):
        type = 'moderator_invite'
    fn = getattr(container, ('add_' + type))
    if (type in self._sr_friend_types):
        mod_action_by_type = {'banned': 'banuser', 'muted': 'muteuser', 'contributor': 'addcontributor', 'moderator': 'addmoderator', 'moderator_invite': 'invitemoderator'}
        action = mod_action_by_type.get(type, type)
        if c.user_is_admin:
            has_perms = True
        elif type.startswith('wiki'):
            has_perms = container.is_moderator_with_perms(c.user, 'wiki')
        elif (type == 'moderator_invite'):
            has_perms = container.is_unlimited_moderator(c.user)
        else:
            has_perms = container.is_moderator_with_perms(c.user, 'access')
        if (not has_perms):
            abort(403, 'forbidden')
        if c.user._spam:
            return
        VNotInTimeout().run(action_name=action, target=friend)
    if (type == 'moderator_invite'):
        invites = sum((1 for i in container.each_moderator_invite()))
        if (invites >= g.sr_invite_limit):
            c.errors.add(errors.SUBREDDIT_RATELIMIT, field='name')
            form.set_error(errors.SUBREDDIT_RATELIMIT, 'name')
            return
    if ((type in self._sr_friend_types) and (not c.user_is_admin) and container.use_quotas):
        sr_ratelimit = SimpleRateLimit(name=('sr_%s_%s' % (str(type), container._id36)), seconds=g.sr_quota_time, limit=getattr(g, ('sr_%s_quota' % type)))
        if (not sr_ratelimit.record_and_check()):
            form.set_text('.status', errors.SUBREDDIT_RATELIMIT)
            c.errors.add(errors.SUBREDDIT_RATELIMIT)
            form.set_error(errors.SUBREDDIT_RATELIMIT, None)
            return
    if ((type == 'friend') and (container != c.user)):
        abort(403, 'forbidden')
    elif form.has_errors('name', errors.USER_DOESNT_EXIST, errors.NO_USER):
        return
    elif form.has_errors(('note', 'ban_reason'), errors.TOO_LONG):
        return
    if (type == 'banned'):
        if form.has_errors('ban_message', errors.TOO_LONG):
            return
        if (ban_reason and note):
            note = ('%s:    %s' % (ban_reason, note))
        elif ban_reason:
            note = ban_reason
    if (type in self._sr_friend_types_with_permissions):
        if form.has_errors('type', errors.INVALID_PERMISSION_TYPE):
            return
        if form.has_errors('permissions', errors.INVALID_PERMISSIONS):
            return
    else:
        permissions = None
    if ((type == 'moderator_invite') and container.is_moderator(friend)):
        c.errors.add(errors.ALREADY_MODERATOR, field='name')
        form.set_error(errors.ALREADY_MODERATOR, 'name')
        return
    elif ((type in ('banned', 'muted')) and container.is_moderator(friend)):
        c.errors.add(errors.CANT_RESTRICT_MODERATOR, field='name')
        form.set_error(errors.CANT_RESTRICT_MODERATOR, 'name')
        return
    if ((type == 'muted') and (not container.can_mute(c.user, friend))):
        abort(403)
    unbanned_types = ('moderator', 'moderator_invite', 'contributor', 'wikicontributor')
    if (type in unbanned_types):
        if container.is_banned(friend):
            c.errors.add(errors.BANNED_FROM_SUBREDDIT, field='name')
            form.set_error(errors.BANNED_FROM_SUBREDDIT, 'name')
            return
        elif container.is_muted(friend):
            c.errors.add(errors.MUTED_FROM_SUBREDDIT, field='name')
            form.set_error(errors.MUTED_FROM_SUBREDDIT, 'name')
            return
    if (type == 'moderator'):
        container.remove_moderator_invite(friend)
    new = fn(friend, permissions=type_and_permissions[1])
    if ((type == 'friend') and c.user.gold):
        c.user.friend_rels_cache(_update=True)
        c.user.add_friend_note(friend, (note or ''))
    tempinfo = None
    log_details = None
    log_description = None
    if (type in ('banned', 'wikibanned', 'muted')):
        container.add_rel_note(type, friend, note)
        log_description = note
    if (type in ('banned', 'wikibanned')):
        existing_ban = None
        if (not new):
            existing_ban = container.get_tempbans(type, friend.name)
        if duration:
            ban_buffer = timedelta(hours=6)
            if existing_ban:
                now = datetime.now(g.tz)
                ban_remaining = (existing_ban[friend.name] - now)
                timediff = abs((timedelta(days=duration) - ban_remaining))
            if ((not existing_ban) or (timediff >= ban_buffer)):
                container.unschedule_unban(friend, type)
                tempinfo = container.schedule_unban(type, friend, c.user, duration)
                log_details = ('changed    to    ' if (not new) else '')
                log_details += ('%d    days' % duration)
        elif ((not new) and existing_ban):
            container.unschedule_unban(friend, type)
            log_details = 'changed    to    permanent'
        elif new:
            log_details = 'permanent'
    elif (new and (type == 'muted')):
        MutedAccountsBySubreddit.mute(container, friend, c.user)
    if ((new or log_details) and (type in self._sr_friend_types)):
        mod_action_by_type = {'banned': 'banuser', 'muted': 'muteuser', 'contributor': 'addcontributor', 'moderator': 'addmoderator', 'moderator_invite': 'invitemoderator'}
        action = mod_action_by_type.get(type, type)
        ModAction.create(container, c.user, action, target=friend, details=log_details, description=log_description)
    row_cls = dict(friend=FriendTableItem, moderator=ModTableItem, moderator_invite=InvitedModTableItem, contributor=ContributorTableItem, wikicontributor=WikiMayContributeTableItem, banned=BannedTableItem, muted=MutedTableItem, wikibanned=WikiBannedTableItem).get(type)
    form.set_inputs(name='')
    if note:
        form.set_inputs(note='')
    form.removeClass('edited')
    if (new and row_cls):
        new._thing2 = friend
        user_row = row_cls(new)
        if tempinfo:
            BannedListing.populate_from_tempbans(user_row, tempinfo)
        form.set_text('.status:first', user_row.executed_message)
        rev_types = ['moderator', 'moderator_invite', 'friend']
        index = (0 if (user_row.type not in rev_types) else (-1))
        table = jquery((('.' + type) + '-table')).show().find('table')
        table.insert_table_rows(user_row, index=index)
        table.find('.notfound').hide()
    if (type == 'banned'):
        if (friend.has_interacted_with(container) and (new or log_details)):
            send_ban_message(container, c.user, friend, ban_message, duration, new)
    elif new:
        notify_user_added(type, c.user, friend, container)
