Exemple #1
0
 async def test_voice_unban_user_not_found(self):
     """Should include info to return dict when user was not found from guild."""
     self.guild.get_member.return_value = None
     self.guild.fetch_member.side_effect = NotFound(Mock(status=404),
                                                    "Not found")
     result = await self.cog.pardon_voice_ban(self.user.id, self.guild)
     self.assertEqual(result, {"Info": "User was not found in the guild."})
Exemple #2
0
    async def test_empty_diff_for_db_users_not_in_guild(self):
        """When the DB knows a user, but the guild doesn't, no difference is found."""
        self.bot.api_client.get.return_value = {
            "count": 3,
            "next_page_no": None,
            "previous_page_no": None,
            "results": [fake_user(), fake_user(id=63, in_guild=False)]
        }
        guild = self.get_guild(fake_user())
        guild.get_member.side_effect = [
            self.get_mock_member(fake_user()),
            None
        ]
        guild.fetch_member.side_effect = NotFound(mock.Mock(status=404), "Not found")

        actual_diff = await UserSyncer._get_diff(guild)
        expected_diff = ([], [], None)

        self.assertEqual(actual_diff, expected_diff)
Exemple #3
0
    async def test_diff_sets_in_guild_false_for_leaving_users(self):
        """When a user leaves the guild, the `in_guild` flag is updated to `False`."""
        self.bot.api_client.get.return_value = {
            "count": 3,
            "next_page_no": None,
            "previous_page_no": None,
            "results": [fake_user(), fake_user(id=63)]
        }
        guild = self.get_guild(fake_user())
        guild.get_member.side_effect = [
            self.get_mock_member(fake_user()),
            None
        ]
        guild.fetch_member.side_effect = NotFound(mock.Mock(status=404), "Not found")

        actual_diff = await UserSyncer._get_diff(guild)
        expected_diff = ([], [{"id": 63, "in_guild": False}], None)

        self.assertEqual(actual_diff, expected_diff)
Exemple #4
0
    async def test_diff_for_new_updated_and_leaving_users(self):
        """When users are added, updated, and removed, all of them are returned properly."""
        new_user = fake_user(id=99, name="new")

        updated_user = fake_user(id=55, name="updated")

        self.bot.api_client.get.return_value = {
            "count": 3,
            "next_page_no": None,
            "previous_page_no": None,
            "results": [fake_user(), fake_user(id=55), fake_user(id=63)]
        }
        guild = self.get_guild(fake_user(), new_user, updated_user)
        guild.get_member.side_effect = [
            self.get_mock_member(fake_user()),
            self.get_mock_member(updated_user),
            None
        ]
        guild.fetch_member.side_effect = NotFound(mock.Mock(status=404), "Not found")

        actual_diff = await UserSyncer._get_diff(guild)
        expected_diff = ([new_user], [{"id": 55, "name": "updated"}, {"id": 63, "in_guild": False}], None)

        self.assertEqual(actual_diff, expected_diff)
Exemple #5
0
    def request(self, verb, url, payload=None, multipart=None, *, files=None, reason=None):
        headers = {}
        data = None
        files = files or []
        if payload:
            headers['Content-Type'] = 'application/json'
            data = utils.to_json(payload)

        if reason:
            headers['X-Audit-Log-Reason'] = _uriquote(reason, safe='/ ')

        if multipart is not None:
            data = {'payload_json': multipart.pop('payload_json')}

        base_url = url.replace(self._request_url, '/') or '/'
        _id = self._webhook_id
        for tries in range(5):
            for file in files:
                file.reset(seek=tries)

            r = self.session.request(verb, url, headers=headers, data=data, files=multipart)
            r.encoding = 'utf-8'
            # Coerce empty responses to return None for hygiene purposes
            response = r.text or None

            # compatibility with aiohttp
            r.status = r.status_code

            log.debug('Webhook ID %s with %s %s has returned status code %s', _id, verb, base_url, r.status)
            if r.headers['Content-Type'] == 'application/json':
                response = json.loads(response)

            # check if we have rate limit header information
            remaining = r.headers.get('X-Ratelimit-Remaining')
            if remaining == '0' and r.status != 429 and self.sleep:
                delta = utils._parse_ratelimit_header(r)
                log.debug('Webhook ID %s has been pre-emptively rate limited, waiting %.2f seconds', _id, delta)
                time.sleep(delta)

            if 300 > r.status >= 200:
                return response

            # we are being rate limited
            if r.status == 429:
                if self.sleep:
                    if not r.headers.get('Via'):
                        # Banned by Cloudflare more than likely.
                        raise HTTPException(r, data)

                    retry_after = response['retry_after'] / 1000.0
                    log.warning('Webhook ID %s is rate limited. Retrying in %.2f seconds', _id, retry_after)
                    time.sleep(retry_after)
                    continue
                else:
                    raise HTTPException(r, response)

            if self.sleep and r.status in (500, 502):
                time.sleep(1 + tries * 2)
                continue

            if r.status == 403:
                raise Forbidden(r, response)
            elif r.status == 404:
                raise NotFound(r, response)
            else:
                raise HTTPException(r, response)

        # no more retries
        if r.status >= 500:
            raise DiscordServerError(r, response)
        raise HTTPException(r, response)
Exemple #6
0
    async def request(self, verb, url, payload=None, multipart=None, *, files=None, reason=None):
        headers = {}
        data = None
        files = files or []
        if payload:
            headers['Content-Type'] = 'application/json'
            data = utils.to_json(payload)

        if reason:
            headers['X-Audit-Log-Reason'] = _uriquote(reason, safe='/ ')


        base_url = url.replace(self._request_url, '/') or '/'
        _id = self._webhook_id
        for tries in range(5):
            for file in files:
                file.reset(seek=tries)

            if multipart:
                data = aiohttp.FormData()
                for key, value in multipart.items():
                    if key.startswith('file'):
                        data.add_field(key, value[1], filename=value[0], content_type=value[2])
                    else:
                        data.add_field(key, value)

            async with self.session.request(verb, url, headers=headers, data=data) as r:
                log.debug('Webhook ID %s with %s %s has returned status code %s', _id, verb, base_url, r.status)
                # Coerce empty strings to return None for hygiene purposes
                response = (await r.text(encoding='utf-8')) or None
                if r.headers['Content-Type'] == 'application/json':
                    response = json.loads(response)

                # check if we have rate limit header information
                remaining = r.headers.get('X-Ratelimit-Remaining')
                if remaining == '0' and r.status != 429:
                    delta = utils._parse_ratelimit_header(r)
                    log.debug('Webhook ID %s has been pre-emptively rate limited, waiting %.2f seconds', _id, delta)
                    await asyncio.sleep(delta)

                if 300 > r.status >= 200:
                    return response

                # we are being rate limited
                if r.status == 429:
                    if not r.headers.get('Via'):
                        # Banned by Cloudflare more than likely.
                        raise HTTPException(r, data)

                    retry_after = response['retry_after'] / 1000.0
                    log.warning('Webhook ID %s is rate limited. Retrying in %.2f seconds', _id, retry_after)
                    await asyncio.sleep(retry_after)
                    continue

                if r.status in (500, 502):
                    await asyncio.sleep(1 + tries * 2)
                    continue

                if r.status == 403:
                    raise Forbidden(r, response)
                elif r.status == 404:
                    raise NotFound(r, response)
                else:
                    raise HTTPException(r, response)

        # no more retries
        if r.status >= 500:
            raise DiscordServerError(r, response)
        raise HTTPException(r, response)
Exemple #7
0
def request(self: discord.http.HTTPClient,
            route: discord.http.Route,
            rate_limit_info: RateLimitInfo,
            *,
            header_bypass_delay=None,
            **kwargs):
    global global_over

    bucket = route.bucket
    method = route.method
    url = route.url

    lock = self._locks.get(bucket)
    if lock is None:
        lock = asyncio.Lock(loop=self.loop)
        if bucket is not None:
            self._locks[bucket] = lock

    # header creation
    headers = {
        'User-Agent': self.user_agent,
    }

    if self.token is not None:
        headers[
            'Authorization'] = 'Bot ' + self.token if self.bot_token else self.token

    # some checking if it's a JSON request
    if 'json' in kwargs:
        headers['Content-Type'] = 'application/json'
        kwargs['data'] = utils.to_json(kwargs.pop('json'))

    kwargs['headers'] = headers

    if not global_over.is_set():
        # wait until the global lock is complete
        yield from global_over.wait()

    yield from lock
    with discord.http.MaybeUnlock(lock) as maybe_lock:
        for tries in range(5):
            r = yield from self.session.request(method, url, **kwargs)
            log.debug(
                self.REQUEST_LOG.format(method=method,
                                        url=url,
                                        status=r.status,
                                        json=kwargs.get('data')))
            try:
                # even errors have text involved in them so this is safe to call
                data = yield from discord.http.json_or_text(r)

                # check if we have rate limit header information
                rate_limit_info.remaining = r.headers.get(
                    'X-Ratelimit-Remaining')
                if rate_limit_info.remaining is not None:
                    rate_limit_info.limit = r.headers['X-Ratelimit-Limit']
                    rate_limit_info.now = discord.http.parsedate_to_datetime(
                        r.headers['Date'])
                    rate_limit_info.reset = datetime.datetime.fromtimestamp(
                        int(r.headers['X-Ratelimit-Reset']),
                        datetime.timezone.utc)

                if rate_limit_info.remaining == '0' and r.status != 429:
                    # we've depleted our current bucket
                    if header_bypass_delay is None:
                        now = discord.http.parsedate_to_datetime(
                            r.headers['Date'])
                        reset = datetime.datetime.fromtimestamp(
                            int(r.headers['X-Ratelimit-Reset']),
                            datetime.timezone.utc)
                        delta = (reset - now).total_seconds()
                    else:
                        delta = header_bypass_delay

                    fmt = 'A rate limit bucket has been exhausted (bucket: {bucket}, retry: {delta}).'
                    log.info(fmt.format(bucket=bucket, delta=delta))
                    maybe_lock.defer()
                    self.loop.call_later(delta, lock.release)

                # the request was successful so just return the text/json
                if 300 > r.status >= 200:
                    log.debug(
                        self.SUCCESS_LOG.format(method=method,
                                                url=url,
                                                text=data))
                    return data

                # we are being rate limited
                if r.status == 429:
                    fmt = 'We are being rate limited. Retrying in {:.2} seconds. Handled under the bucket "{}"'

                    # sleep a bit
                    retry_after = data['retry_after'] / 1000.0
                    log.info(fmt.format(retry_after, bucket))

                    # check if it's a global rate limit
                    is_global = data.get('global', False)
                    if is_global:
                        log.info(
                            'Global rate limit has been hit. Retrying in {:.2} seconds.'
                            .format(retry_after))
                        global_over.clear()

                    yield from asyncio.sleep(retry_after, loop=self.loop)
                    log.debug('Done sleeping for the rate limit. Retrying...')

                    # release the global lock now that the
                    # global rate limit has passed
                    if is_global:
                        global_over.set()
                        log.debug('Global rate limit is now over.')

                    continue

                # we've received a 502, unconditional retry
                if r.status == 502 and tries <= 5:
                    yield from asyncio.sleep(1 + tries * 2, loop=self.loop)
                    continue

                # the usual error cases
                if r.status == 403:
                    raise Forbidden(r, data)
                elif r.status == 404:
                    raise NotFound(r, data)
                else:
                    raise HTTPException(r, data)
            finally:
                # clean-up just in case
                yield from r.release()