|
"""
|
|
Rate Limits
|
|
|
|
https://getpocket.com/developer/docs/rate-limits
|
|
|
|
The Pocket API has two separate rate limits. These dictate how many calls can be made to the server within a given time.
|
|
Enforcing rate limits prevents a single app or user from overwhelming the server. The response codes will tell you if
|
|
you've hit your limit. Your application should be looking for these and if it encounters a rate limit status code, it
|
|
should back off until it hits the reset time. Ignoring these codes may cause your access to be disabled.
|
|
|
|
User Limit
|
|
|
|
Each user is limited to 320 calls per hour. This should be very sufficient for most users as the average user only makes
|
|
changes to their list periodically. To ensure the user stays within this limit, make use of the send method for batching
|
|
requests.
|
|
|
|
Consumer Key Limit
|
|
|
|
Each application is limited to 10,000 calls per hour. (...)
|
|
"""
|
|
import time
|
|
import typing
|
|
|
|
import gpauthentication
|
|
import gptagduration
|
|
import requests
|
|
|
|
|
|
class RateLimitResponseHeader(typing.NamedTuple):
|
|
"""
|
|
Response Headers
|
|
|
|
https://getpocket.com/developer/docs/rate-limits
|
|
|
|
The Pocket API responses include custom headers that provide information about the current status of rate limiting for
|
|
both the current user and consumer key.
|
|
|
|
- `'X-Limit-User-Limit'`: Current rate limit enforced per user
|
|
- `'X-Limit-User-Remaining'`: Number of calls remaining before hitting user's rate limit
|
|
- `'X-Limit-User-Reset'`: Seconds until user's rate limit resets
|
|
- `'X-Limit-Key-Limit'`: Current rate limit enforced per consumer key
|
|
- `'X-Limit-Key-Remaining'`: Number of calls remaining before hitting consumer key's rate limit
|
|
- `'X-Limit-Key-Reset'`: Seconds until consumer key rate limit resets
|
|
"""
|
|
|
|
remaining_header_key: str
|
|
reset_header_key: str
|
|
|
|
def remaining(self, response: requests.Response):
|
|
return int(response.headers.get(self.remaining_header_key, "1"))
|
|
|
|
def reset(self, response: requests.Response):
|
|
if self.remaining(response) > 0:
|
|
return 0
|
|
return int(response.headers.get(self.reset_header, "0"))
|
|
|
|
|
|
LIMIT_REMAINING_HEADERS = {
|
|
RateLimitResponseHeader(
|
|
remaining_header_key="X-Limit-User-Remaining",
|
|
reset_header_key="X-Limit-User-Reset",
|
|
),
|
|
RateLimitResponseHeader(
|
|
remaining_header_key="X-Limit-Key-Remaining",
|
|
reset_header_key="X-Limit-Key-Reset",
|
|
),
|
|
}
|
|
|
|
|
|
TAG_UNTAGGED = "_untagged_"
|
|
|
|
|
|
class RetrieveParameters(typing.TypedDict, total=False):
|
|
"""
|
|
Pocket API: Retrieving a User's Pocket Data
|
|
|
|
https://getpocket.com/developer/docs/v3/retrieve
|
|
|
|
`state`
|
|
- `'unread'` = only return unread items (default)
|
|
- `'archive'` = only return archived items
|
|
- `'all'` = return both unread and archived items
|
|
|
|
`favorite`
|
|
- `0` = only return un-favorited items
|
|
- `1` = only return favorited items
|
|
|
|
`tag`
|
|
- `str` = only return items tagged with tag_name
|
|
- `'_untagged_'` = only return untagged items
|
|
|
|
`contentType`
|
|
- `'article'` = only return articles
|
|
- `'video'` = only return videos or articles with embedded videos
|
|
- `'image'` = only return images
|
|
|
|
`sort`
|
|
- `'newest'` = return items in order of newest to oldest
|
|
- `'oldest'` = return items in order of oldest to newest
|
|
- `'title'` = return items in order of title alphabetically
|
|
- `'site'` = return items in order of url alphabetically
|
|
|
|
`detailType`
|
|
- `'simple'` = return basic information about each item, including title, url, status, and more
|
|
- `'complete'` = return all data about each item, including tags, images, authors, videos, and more
|
|
|
|
`search`
|
|
- `str` = Only return items whose title or url contain the search string
|
|
|
|
`domain`
|
|
- `str` = Only return items from a particular domain
|
|
|
|
`since`
|
|
- `int` = Only return items modified since the given since unix timestamp
|
|
|
|
`count`
|
|
- `int` = Only return count number of items
|
|
|
|
`offset`
|
|
- `int` = Used only with count; start returning from offset position of results
|
|
"""
|
|
|
|
state: typing.Literal[
|
|
"unread", # only return unread items (default)
|
|
"archive", # only return archived items
|
|
"all", # return both unread and archived items
|
|
]
|
|
favorite: typing.Literal[
|
|
0, 1, # only return un-favorited items # only return favorited items
|
|
]
|
|
tag: str # "${tag_name}" = only return items tagged with "${tag_name}", "_untagged_" = only return untagged items
|
|
contentType: typing.Literal[
|
|
"article", # only return articles
|
|
"video", # only return videos or articles with embedded videos
|
|
"image", # only return images
|
|
]
|
|
sort: typing.Literal[
|
|
"newest", # return items in order of newest to oldest
|
|
"oldest", # return items in order of oldest to newest
|
|
"title", # return items in order of title alphabetically
|
|
"site", # return items in order of url alphabetically
|
|
]
|
|
detailType: typing.Literal[
|
|
"simple", # return basic information about each item, including title, url, status, and more
|
|
"complete", # return all data about each item, including tags, images, authors, videos, and more
|
|
]
|
|
search: str # Only return items whose title or url contain the search string
|
|
domain: str # Only return items from a particular domain
|
|
since: int # Only return items modified since the given since unix timestamp
|
|
count: int # Only return count number of items
|
|
offset: int # Used only with count; start returning from offset position of results
|
|
|
|
|
|
def retrieve(data: RetrieveParameters):
|
|
"""
|
|
Pocket API: Retrieving a User's Pocket Data
|
|
|
|
https://getpocket.com/developer/docs/v3/retrieve
|
|
|
|
Pocket's /v3/get endpoint is a single call that is incredibly versatile. A few examples of the types of requests you can
|
|
make:
|
|
|
|
- Retrieve a user’s list of unread items
|
|
- Sync data that has changed since the last time your app checked
|
|
- Retrieve paged results sorted by the most recent saves
|
|
- Retrieve just videos that the user has saved
|
|
- Search for a given keyword in item’s title and url
|
|
- Retrieve all items for a given domain
|
|
and more
|
|
|
|
Required Permissions
|
|
|
|
In order to use the /v3/get endpoint, your consumer key must have the "Retrieve" permission.
|
|
"""
|
|
url = "https://getpocket.com/v3/get"
|
|
response = requests.post(
|
|
url=url, data={**gpauthentication.required_body_parameters(), **data},
|
|
)
|
|
for header, wait_interval in {
|
|
header: header.reset(response) for header in LIMIT_REMAINING_HEADERS
|
|
}.items():
|
|
if wait_interval > 0:
|
|
print(f"> Throttling; header: {header}, wait_interval: {wait_interval}.")
|
|
time.sleep(wait_interval + 2)
|
|
return retrieve(url, data)
|
|
else:
|
|
response.raise_for_status()
|
|
return response
|
|
|
|
|
|
def modify(actions: typing.Iterable[typing.Union[gptagduration.TagsAdd]]):
|
|
"""
|
|
Pocket API: Modifying a User's Pocket Data
|
|
|
|
https://getpocket.com/developer/docs/v3/modify
|
|
|
|
Pocket’s /v3/send endpoint allows you to make a change or batch several changes to a user’s list or Pocket data.
|
|
"""
|
|
response = requests.post(
|
|
url="https://getpocket.com/v3/send",
|
|
json={**gpauthentication.required_body_parameters(), "actions": actions},
|
|
)
|
|
for header, wait_interval in {
|
|
header: header.reset(response) for header in LIMIT_REMAINING_HEADERS
|
|
}.items():
|
|
if wait_interval > 0:
|
|
print(f"> Throttling; header: {header}, wait_interval: {wait_interval}.")
|
|
time.sleep(wait_interval + 2)
|
|
return modify(actions)
|
|
else:
|
|
response.raise_for_status()
|
|
return response
|