diff --git a/github/MainClass.py b/github/MainClass.py index 00a18650dc..c68e419846 100644 --- a/github/MainClass.py +++ b/github/MainClass.py @@ -144,6 +144,7 @@ def __set_per_page(self, value): def rate_limiting(self): """ First value is requests remaining, second value is request limit. + :type: (int, int) """ remaining, limit = self.__requester.rate_limiting @@ -155,6 +156,7 @@ def rate_limiting(self): def rate_limiting_resettime(self): """ Unix timestamp indicating when rate limiting will reset. + :type: int """ if self.__requester.rate_limiting_resettime == 0: @@ -163,16 +165,16 @@ def rate_limiting_resettime(self): def get_rate_limit(self): """ - Don't forget you can access the rate limit returned in headers of last Github API v3 response, by :attr:`github.MainClass.Github.rate_limiting` and :attr:`github.MainClass.Github.rate_limiting_resettime`. + Rate limit status for different resources (core/search/graphql). :calls: `GET /rate_limit `_ :rtype: :class:`github.RateLimit.RateLimit` """ - headers, attributes = self.__requester.requestJsonAndCheck( + headers, data = self.__requester.requestJsonAndCheck( 'GET', '/rate_limit' ) - return RateLimit.RateLimit(self.__requester, headers, attributes, True) + return RateLimit.RateLimit(self.__requester, headers, data["resources"], True) @property def oauth_scopes(self): diff --git a/github/RateLimit.py b/github/RateLimit.py index d3b15cba91..8886fc09ac 100644 --- a/github/RateLimit.py +++ b/github/RateLimit.py @@ -37,18 +37,44 @@ class RateLimit(github.GithubObject.NonCompletableGithubObject): """ def __repr__(self): - return self.get__repr__({"rate": self._rate.value}) + return self.get__repr__({"core": self._core.value}) @property - def rate(self): + def core(self): """ + Rate limit for rest of the API. + + :type: class:`github.Rate.Rate` + """ + return self._core.value + + @property + def search(self): + """ + Rate limit for Search API. + + :type: class:`github.Rate.Rate` + """ + return self._search.value + + @property + def graphql(self): + """ + Experimental rate limit for GraphQL, use with caution. + :type: class:`github.Rate.Rate` """ - return self._rate.value + return self._graphql.value def _initAttributes(self): - self._rate = github.GithubObject.NotSet + self._core = github.GithubObject.NotSet + self._search = github.GithubObject.NotSet + self._graphql = github.GithubObject.NotSet def _useAttributes(self, attributes): - if "rate" in attributes: # pragma no branch - self._rate = self._makeClassAttribute(github.Rate.Rate, attributes["rate"]) + if "core" in attributes: # pragma no branch + self._core = self._makeClassAttribute(github.Rate.Rate, attributes["core"]) + if "search" in attributes: # pragma no branch + self._search = self._makeClassAttribute(github.Rate.Rate, attributes["search"]) + if "graphql" in attributes: # pragma no branch + self._graphql = self._makeClassAttribute(github.Rate.Rate, attributes["graphql"]) diff --git a/github/tests/ExposeAllAttributes.py b/github/tests/ExposeAllAttributes.py index 77e3dcc913..024b496008 100644 --- a/github/tests/ExposeAllAttributes.py +++ b/github/tests/ExposeAllAttributes.py @@ -66,7 +66,6 @@ def testAllClasses(self): status = self.g.get_api_status() statusMessage = self.g.get_last_api_status_message() rateLimit = self.g.get_rate_limit() - rate = rateLimit.rate hook = repository.get_hooks()[0] hookResponse = hook.last_response hookDescription = self.g.get_hooks()[0] @@ -123,7 +122,6 @@ def testAllClasses(self): pullRequestComment, # pullRequestMergeStatus, # Only obtained when merging a pull request through the API pullRequestPart, - rate, rateLimit, repository, # repositoryKey, # Security issue if put as-is in ReplayData diff --git a/github/tests/Issue142.py b/github/tests/Issue142.py index cfb1ae309d..d32904e8f1 100644 --- a/github/tests/Issue142.py +++ b/github/tests/Issue142.py @@ -33,4 +33,4 @@ class Issue142(unittest.TestCase): # https://github.com/jacquev6/PyGithub/issue def testDecodeJson(self): # This test has to hit GitHub for real, because the record-replay framework looses types # and python3 does not behave like python2 for strings and bytes - self.assertEqual(github.Github().get_rate_limit().rate.limit, 60) + self.assertEqual(github.Github().get_rate_limit().core.limit, 60) diff --git a/github/tests/RateLimiting.py b/github/tests/RateLimiting.py index c3bd44a3d7..2b75f9b64e 100644 --- a/github/tests/RateLimiting.py +++ b/github/tests/RateLimiting.py @@ -35,16 +35,16 @@ class RateLimiting(Framework.TestCase): def testRateLimiting(self): - self.assertEqual(self.g.rate_limiting, (5000, 5000)) + self.assertEqual(self.g.rate_limiting, (4929, 5000)) self.g.get_user("jacquev6") - self.assertEqual(self.g.rate_limiting, (4999, 5000)) - self.assertEqual(self.g.rate_limiting_resettime, 1375802816) + self.assertEqual(self.g.rate_limiting, (4928, 5000)) + self.assertEqual(self.g.rate_limiting_resettime, 1536123356) def testResetTime(self): - self.assertEqual(self.g.rate_limiting_resettime, 1375802816) + self.assertEqual(self.g.rate_limiting_resettime, 1536123356) def testGetRateLimit(self): rateLimit = self.g.get_rate_limit() - self.assertEqual(rateLimit.rate.limit, 5000) - self.assertEqual(rateLimit.rate.remaining, 5000) - self.assertEqual(rateLimit.rate.reset, datetime.datetime(2013, 9, 6, 10, 29, 57)) + self.assertEqual(rateLimit.core.limit, 5000) + self.assertEqual(rateLimit.core.remaining, 4929) + self.assertEqual(rateLimit.core.reset, datetime.datetime(2018, 9, 5, 4, 55, 56)) diff --git a/github/tests/ReplayData/ExposeAllAttributes.testAllClasses.txt b/github/tests/ReplayData/ExposeAllAttributes.testAllClasses.txt index 8a3fa0f435..3d4c1d0da1 100644 --- a/github/tests/ReplayData/ExposeAllAttributes.testAllClasses.txt +++ b/github/tests/ReplayData/ExposeAllAttributes.testAllClasses.txt @@ -292,8 +292,8 @@ None {'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} None 200 -[('status', '200 OK'), ('x-ratelimit-remaining', '4873'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('x-github-request-id', '7d8f5ceb-ff02-47d5-a6c5-bdc47a1b1306'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept-Encoding'), ('content-length', '59'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('cache-control', 'no-cache'), ('date', 'Fri, 06 Sep 2013 15:05:42 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1378482241')] -{"rate":{"limit":5000,"remaining":4873,"reset":1378482241}} +[('Date', 'Wed, 05 Sep 2018 03:59:43 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Server', 'GitHub.com'), ('Status', '200 OK'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4929'), ('X-RateLimit-Reset', '1536123356'), ('Cache-Control', 'no-cache'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-Runtime-rack', '0.021146'), ('Content-Encoding', 'gzip'), ('Vary', 'Accept-Encoding'), ('X-GitHub-Request-Id', 'C8ED:54D0:B4DAB8:EF7B5E:5B8F54AE')] +{"resources":{"core":{"limit":5000,"remaining":4929,"reset":1536123356},"search":{"limit":30,"remaining":30,"reset":1536120043},"graphql":{"limit":5000,"remaining":5000,"reset":1536123583}},"rate":{"limit":5000,"remaining":4929,"reset":1536123356}} https GET diff --git a/github/tests/ReplayData/RateLimiting.testGetRateLimit.txt b/github/tests/ReplayData/RateLimiting.testGetRateLimit.txt index dca139cb3d..3b3b0344c6 100644 --- a/github/tests/ReplayData/RateLimiting.testGetRateLimit.txt +++ b/github/tests/ReplayData/RateLimiting.testGetRateLimit.txt @@ -6,6 +6,6 @@ None {'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} None 200 -[('status', '200 OK'), ('x-ratelimit-remaining', '5000'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('x-github-request-id', '1a3fa558-6663-4055-91e3-b6824f5f850e'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept-Encoding'), ('content-length', '59'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('cache-control', 'no-cache'), ('date', 'Fri, 06 Sep 2013 09:29:57 GMT'), ('access-control-allow-origin', '*'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-reset', '1378463397')] -{"rate":{"limit":5000,"remaining":5000,"reset":1378463397}} +[('Date', 'Wed, 05 Sep 2018 03:59:43 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Server', 'GitHub.com'), ('Status', '200 OK'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4929'), ('X-RateLimit-Reset', '1536123356'), ('Cache-Control', 'no-cache'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-Runtime-rack', '0.021146'), ('Content-Encoding', 'gzip'), ('Vary', 'Accept-Encoding'), ('X-GitHub-Request-Id', 'C8ED:54D0:B4DAB8:EF7B5E:5B8F54AE')] +{"resources":{"core":{"limit":5000,"remaining":4929,"reset":1536123356},"search":{"limit":30,"remaining":30,"reset":1536120043},"graphql":{"limit":5000,"remaining":5000,"reset":1536123583}},"rate":{"limit":5000,"remaining":4929,"reset":1536123356}} diff --git a/github/tests/ReplayData/RateLimiting.testRateLimiting.txt b/github/tests/ReplayData/RateLimiting.testRateLimiting.txt index df34e96ca7..a0197b78a1 100644 --- a/github/tests/ReplayData/RateLimiting.testRateLimiting.txt +++ b/github/tests/ReplayData/RateLimiting.testRateLimiting.txt @@ -6,8 +6,8 @@ None {'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} None 200 -[('status', '200 OK'), ('x-ratelimit-remaining', '5000'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('cache-control', 'max-age=0, private, must-revalidate'), ('vary', 'Accept-Encoding'), ('content-length', '59'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('etag', '"47ba6b48c8b2986ec54f249b51b0a9ec"'), ('access-control-allow-credentials', 'true'), ('date', 'Tue, 06 Aug 2013 14:52:12 GMT'), ('x-oauth-scopes', 'user, public_repo, repo, gist'), ('content-type', 'application/json; charset=utf-8'), ('access-control-allow-origin', '*'), ('x-ratelimit-reset', '1375802816')] -{"rate":{"limit":5000,"remaining":5000,"reset":1375802816}} +[('Date', 'Wed, 05 Sep 2018 04:03:25 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Server', 'GitHub.com'), ('Status', '200 OK'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4929'), ('X-RateLimit-Reset', '1536123356'), ('Cache-Control', 'no-cache'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-Runtime-rack', '0.020832'), ('Content-Encoding', 'gzip'), ('Vary', 'Accept-Encoding'), ('X-GitHub-Request-Id', 'C91A:07E1:B39E09:EEBCC4:5B8F558C')] +{"resources":{"core":{"limit":5000,"remaining":4929,"reset":1536123356},"search":{"limit":30,"remaining":30,"reset":1536120264},"graphql":{"limit":5000,"remaining":5000,"reset":1536123804}},"rate":{"limit":5000,"remaining":4929,"reset":1536123356}} https GET @@ -17,6 +17,6 @@ None {'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} None 200 -[('status', '200 OK'), ('x-ratelimit-remaining', '4999'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('access-control-allow-credentials', 'true'), ('vary', 'Accept, Authorization, Cookie, Accept-Encoding'), ('content-length', '1293'), ('server', 'GitHub.com'), ('access-control-allow-origin', '*'), ('last-modified', 'Mon, 05 Aug 2013 07:28:42 GMT'), ('x-ratelimit-limit', '5000'), ('etag', '"7d9b8600b27332ec98f57ee9e18639e9"'), ('cache-control', 'private, max-age=60, s-maxage=60'), ('date', 'Tue, 06 Aug 2013 14:52:12 GMT'), ('x-oauth-scopes', 'user, public_repo, repo, gist'), ('content-type', 'application/json; charset=utf-8'), ('x-accepted-oauth-scopes', 'user, user:email, user:follow, site_admin'), ('x-ratelimit-reset', '1375802816')] -{"login":"jacquev6","id":327146,"avatar_url":"https://secure.gravatar.com/avatar/b68de5ae38616c296fa345d2b9df2225?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png","gravatar_id":"b68de5ae38616c296fa345d2b9df2225","url":"https://api.github.com/users/jacquev6","html_url":"https://github.com/jacquev6","followers_url":"https://api.github.com/users/jacquev6/followers","following_url":"https://api.github.com/users/jacquev6/following{/other_user}","gists_url":"https://api.github.com/users/jacquev6/gists{/gist_id}","starred_url":"https://api.github.com/users/jacquev6/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jacquev6/subscriptions","organizations_url":"https://api.github.com/users/jacquev6/orgs","repos_url":"https://api.github.com/users/jacquev6/repos","events_url":"https://api.github.com/users/jacquev6/events{/privacy}","received_events_url":"https://api.github.com/users/jacquev6/received_events","type":"User","name":"Vincent Jacques","company":"","blog":"http://vincent-jacques.net","location":"Paris, France","email":"vincent@vincent-jacques.net","hireable":false,"bio":"","public_repos":16,"followers":27,"following":39,"created_at":"2010-07-09T06:10:06Z","updated_at":"2013-08-05T07:28:42Z","public_gists":3} +[('Date', 'Wed, 05 Sep 2018 04:03:25 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Server', 'GitHub.com'), ('Status', '200 OK'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4928'), ('X-RateLimit-Reset', '1536123356'), ('Cache-Control', 'no-cache'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-Runtime-rack', '0.020832'), ('Content-Encoding', 'gzip'), ('Vary', 'Accept-Encoding'), ('X-GitHub-Request-Id', 'C91A:07E1:B39E09:EEBCC4:5B8F558C')] +{"resources":{"core":{"limit":5000,"remaining":4928,"reset":1536123356},"search":{"limit":30,"remaining":30,"reset":1536120264},"graphql":{"limit":5000,"remaining":5000,"reset":1536123804}},"rate":{"limit":5000,"remaining":4928,"reset":1536123356}} diff --git a/github/tests/ReplayData/RateLimiting.testResetTime.txt b/github/tests/ReplayData/RateLimiting.testResetTime.txt index 787d395681..205daee604 100755 --- a/github/tests/ReplayData/RateLimiting.testResetTime.txt +++ b/github/tests/ReplayData/RateLimiting.testResetTime.txt @@ -6,5 +6,6 @@ None {'Authorization': 'Basic login_and_password_removed', 'User-Agent': 'PyGithub/Python'} None 200 -[('status', '200 OK'), ('x-ratelimit-remaining', '5000'), ('x-github-media-type', 'github.beta; format=json'), ('x-content-type-options', 'nosniff'), ('access-control-expose-headers', 'ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes'), ('cache-control', 'max-age=0, private, must-revalidate'), ('vary', 'Accept-Encoding'), ('content-length', '59'), ('server', 'GitHub.com'), ('x-ratelimit-limit', '5000'), ('etag', '"47ba6b48c8b2986ec54f249b51b0a9ec"'), ('access-control-allow-credentials', 'true'), ('date', 'Tue, 06 Aug 2013 14:52:12 GMT'), ('x-oauth-scopes', 'user, public_repo, repo, gist'), ('content-type', 'application/json; charset=utf-8'), ('access-control-allow-origin', '*'), ('x-ratelimit-reset', '1375802816')] -{"rate":{"limit":5000,"remaining":5000,"reset":1375802816}} +[('Date', 'Wed, 05 Sep 2018 04:07:10 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Server', 'GitHub.com'), ('Status', '200 OK'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4928'), ('X-RateLimit-Reset', '1536123356'), ('Cache-Control', 'no-cache'), ('X-OAuth-Scopes', 'admin:gpg_key, admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion'), ('X-Accepted-OAuth-Scopes', ''), ('X-GitHub-Media-Type', 'github.v3; format=json'), ('Access-Control-Expose-Headers', 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'), ('Access-Control-Allow-Origin', '*'), ('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'), ('X-Frame-Options', 'deny'), ('X-Content-Type-Options', 'nosniff'), ('X-XSS-Protection', '1; mode=block'), ('Referrer-Policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('Content-Security-Policy', "default-src 'none'"), ('X-Runtime-rack', '0.021308'), ('Content-Encoding', 'gzip'), ('Vary', 'Accept-Encoding'), ('X-GitHub-Request-Id', 'C949:54D0:B51EC6:EFD5CD:5B8F566D')] +{"resources":{"core":{"limit":5000,"remaining":4928,"reset":1536123356},"search":{"limit":30,"remaining":30,"reset":1536120490},"graphql":{"limit":5000,"remaining":5000,"reset":1536124030}},"rate":{"limit":5000,"remaining":4928,"reset":1536123356}} +