[pinterest] implement oauth:pinterest (#83)
Pinterest access tokens are rate limited at 200 requests per hour (or maybe per 2 or 3 hours?) so having just one access token for all users isn't going to work in the long run.
This commit is contained in:
@@ -194,7 +194,7 @@ OAuth
|
|||||||
-----
|
-----
|
||||||
|
|
||||||
*gallery-dl* supports user authentication via OAuth_ for
|
*gallery-dl* supports user authentication via OAuth_ for
|
||||||
``deviantart``, ``flickr``, ``reddit`` and ``tumblr``.
|
``deviantart``, ``flickr``, ``pinterest``, ``reddit`` and ``tumblr``.
|
||||||
This is entirely optional, but grants *gallery-dl* the ability
|
This is entirely optional, but grants *gallery-dl* the ability
|
||||||
to issue requests on your account's behalf and enables it to access resources
|
to issue requests on your account's behalf and enables it to access resources
|
||||||
which would otherwise be unavailable to a public user.
|
which would otherwise be unavailable to a public user.
|
||||||
|
|||||||
@@ -788,7 +788,9 @@ extractor.deviantart.client-id & .client-secret
|
|||||||
-----------------------------------------------
|
-----------------------------------------------
|
||||||
=========== =====
|
=========== =====
|
||||||
Type ``string``
|
Type ``string``
|
||||||
How To - login and visit DeviantArt's `Applications & Keys`_ section
|
How To - login and visit DeviantArt's
|
||||||
|
`Applications & Keys <https://www.deviantart.com/developers/apps>`__
|
||||||
|
section
|
||||||
- click "Register your Application"
|
- click "Register your Application"
|
||||||
- click "Save" (top right; default settings are fine)
|
- click "Save" (top right; default settings are fine)
|
||||||
- copy ``client_id`` and ``client_secret`` of your new "Untitled"
|
- copy ``client_id`` and ``client_secret`` of your new "Untitled"
|
||||||
@@ -800,7 +802,8 @@ extractor.flickr.api-key & .api-secret
|
|||||||
--------------------------------------
|
--------------------------------------
|
||||||
=========== =====
|
=========== =====
|
||||||
Type ``string``
|
Type ``string``
|
||||||
How To - login and `Create an App`_ in Flickr's `App Garden`_
|
How To - login and `Create an App <https://www.flickr.com/services/apps/create/apply/>`__
|
||||||
|
in Flickr's `App Garden <https://www.flickr.com/services/>`__
|
||||||
- click "APPLY FOR A NON-COMMERCIAL KEY"
|
- click "APPLY FOR A NON-COMMERCIAL KEY"
|
||||||
- fill out the form with a random name and description
|
- fill out the form with a random name and description
|
||||||
and click "SUBMIT"
|
and click "SUBMIT"
|
||||||
@@ -817,11 +820,19 @@ How To
|
|||||||
=========== =====
|
=========== =====
|
||||||
|
|
||||||
|
|
||||||
extractor.pinterest.access-token
|
extractor.pinterest.client-id & .secret
|
||||||
--------------------------------
|
---------------------------------------
|
||||||
=========== =====
|
=========== =====
|
||||||
Type ``string``
|
Type ``string``
|
||||||
How To
|
How To - login and visit Pinterest's
|
||||||
|
`Apps <https://developers.pinterest.com/apps/>`__ section
|
||||||
|
- click "Create app"
|
||||||
|
- choose a random name and description and click "Create"
|
||||||
|
- scroll down and set a Site URL (e.g. https://example.org/)
|
||||||
|
and allow https://mikf.github.io/gallery-dl/oauth-redirect.html
|
||||||
|
as Redirect URI
|
||||||
|
- scroll back up again, copy the "App ID" and "App secret" values
|
||||||
|
and put them in your configuration file
|
||||||
=========== =====
|
=========== =====
|
||||||
|
|
||||||
|
|
||||||
@@ -829,7 +840,8 @@ extractor.reddit.client-id & .user-agent
|
|||||||
----------------------------------------
|
----------------------------------------
|
||||||
=========== =====
|
=========== =====
|
||||||
Type ``string``
|
Type ``string``
|
||||||
How To - login and visit the apps_ section of your account's preferences
|
How To - login and visit the `apps <https://www.reddit.com/prefs/apps/>`__
|
||||||
|
section of your account's preferences
|
||||||
- click the "are you a developer? create an app..." button
|
- click the "are you a developer? create an app..." button
|
||||||
- fill out the form, choose "installed app", preferably set
|
- fill out the form, choose "installed app", preferably set
|
||||||
"http://localhost:6414/" as "redirect uri" and finally click
|
"http://localhost:6414/" as "redirect uri" and finally click
|
||||||
@@ -838,7 +850,8 @@ How To - login and visit the apps_ section of your account's preferences
|
|||||||
"installed app") and put it in your configuration file
|
"installed app") and put it in your configuration file
|
||||||
- use "``Python:<application name>:v1.0 (by /u/<username>)``" as
|
- use "``Python:<application name>:v1.0 (by /u/<username>)``" as
|
||||||
user-agent and replace ``<application name>`` and ``<username>``
|
user-agent and replace ``<application name>`` and ``<username>``
|
||||||
accordingly (see Reddit's `API access rules`_)
|
accordingly (see Reddit's
|
||||||
|
`API access rules <https://github.com/reddit/reddit/wiki/API>`__)
|
||||||
=========== =====
|
=========== =====
|
||||||
|
|
||||||
|
|
||||||
@@ -846,7 +859,8 @@ extractor.tumblr.api-key
|
|||||||
------------------------
|
------------------------
|
||||||
=========== =====
|
=========== =====
|
||||||
Type ``string``
|
Type ``string``
|
||||||
How To - login and visit Tumblr's Applications_ section
|
How To - login and visit Tumblr's
|
||||||
|
`Applications <https://www.tumblr.com/oauth/apps>`__ section
|
||||||
- click "Register application"
|
- click "Register application"
|
||||||
- fill out the form: use a random name and description, set
|
- fill out the form: use a random name and description, set
|
||||||
https://example.org/ as "Application Website" and "Default
|
https://example.org/ as "Application Website" and "Default
|
||||||
@@ -886,10 +900,3 @@ How To - login and visit Tumblr's Applications_ section
|
|||||||
.. _webbrowser.open(): https://docs.python.org/3/library/webbrowser.html
|
.. _webbrowser.open(): https://docs.python.org/3/library/webbrowser.html
|
||||||
.. _datetime.max: https://docs.python.org/3/library/datetime.html#datetime.datetime.max
|
.. _datetime.max: https://docs.python.org/3/library/datetime.html#datetime.datetime.max
|
||||||
.. _Authentication: https://github.com/mikf/gallery-dl#5authentication
|
.. _Authentication: https://github.com/mikf/gallery-dl#5authentication
|
||||||
|
|
||||||
.. _`Applications & Keys`: https://www.deviantart.com/developers/apps
|
|
||||||
.. _`Create an App`: https://www.flickr.com/services/apps/create/apply/
|
|
||||||
.. _`App Garden`: https://www.flickr.com/services/
|
|
||||||
.. _apps: https://www.reddit.com/prefs/apps/
|
|
||||||
.. _`API access rules`: https://github.com/reddit/reddit/wiki/API
|
|
||||||
.. _Applications: https://www.tumblr.com/oauth/apps
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ Niconico Seiga http://seiga.nicovideo.jp Images from Users, indi
|
|||||||
nijie https://nijie.info/ |Images from Use-3| Required
|
nijie https://nijie.info/ |Images from Use-3| Required
|
||||||
Nyafuu Archive https://archive.nyafuu.org/ Threads
|
Nyafuu Archive https://archive.nyafuu.org/ Threads
|
||||||
Pawoo https://pawoo.net Images from Users, Images from Statuses
|
Pawoo https://pawoo.net Images from Users, Images from Statuses
|
||||||
Pinterest https://www.pinterest.com Boards, Pins, pin.it Links
|
Pinterest https://www.pinterest.com Boards, Pins, pin.it Links Optional (OAuth)
|
||||||
Pixiv https://www.pixiv.net/ |Images from Use-4| Required
|
Pixiv https://www.pixiv.net/ |Images from Use-4| Required
|
||||||
PowerManga https://powermanga.org/ Chapters, Manga
|
PowerManga https://powermanga.org/ Chapters, Manga
|
||||||
Pure Mashiro http://reader.puremashiro.moe/ Chapters, Manga
|
Pure Mashiro http://reader.puremashiro.moe/ Chapters, Manga
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"""Utility classes to setup OAuth and link a users account to gallery-dl"""
|
"""Utility classes to setup OAuth and link a users account to gallery-dl"""
|
||||||
|
|
||||||
from .common import Extractor, Message
|
from .common import Extractor, Message
|
||||||
from . import deviantart, flickr, reddit, tumblr
|
from . import deviantart, flickr, pinterest, reddit, tumblr
|
||||||
from .. import text, util, config
|
from .. import text, util, config
|
||||||
import os
|
import os
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
@@ -50,14 +50,7 @@ class OAuthBase(Extractor):
|
|||||||
|
|
||||||
data = self.client.recv(1024).decode()
|
data = self.client.recv(1024).decode()
|
||||||
path = data.split(" ", 2)[1]
|
path = data.split(" ", 2)[1]
|
||||||
query = path.partition("?")[2]
|
return text.parse_query(path.partition("?")[2])
|
||||||
return {
|
|
||||||
key: urllib.parse.unquote(value)
|
|
||||||
for key, _, value in [
|
|
||||||
part.partition("=")
|
|
||||||
for part in query.split("&")
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def send(self, msg):
|
def send(self, msg):
|
||||||
"""Send 'msg' to the socket opened in 'recv()'"""
|
"""Send 'msg' to the socket opened in 'recv()'"""
|
||||||
@@ -69,8 +62,7 @@ class OAuthBase(Extractor):
|
|||||||
"""Open 'url' in browser amd return response parameters"""
|
"""Open 'url' in browser amd return response parameters"""
|
||||||
import webbrowser
|
import webbrowser
|
||||||
url += "?" + urllib.parse.urlencode(params)
|
url += "?" + urllib.parse.urlencode(params)
|
||||||
browser = self.config("browser", True)
|
if not self.config("browser", True) or not webbrowser.open(url):
|
||||||
if not browser or not webbrowser.open(url):
|
|
||||||
print("Please open this URL in your browser:")
|
print("Please open this URL in your browser:")
|
||||||
print(url, end="\n\n", flush=True)
|
print(url, end="\n\n", flush=True)
|
||||||
return self.recv()
|
return self.recv()
|
||||||
@@ -80,7 +72,7 @@ class OAuthBase(Extractor):
|
|||||||
"""Perform the OAuth 1.0a authorization flow"""
|
"""Perform the OAuth 1.0a authorization flow"""
|
||||||
del self.session.params["oauth_token"]
|
del self.session.params["oauth_token"]
|
||||||
|
|
||||||
# Get a Request Token
|
# get a request token
|
||||||
params = {"oauth_callback": self.redirect_uri}
|
params = {"oauth_callback": self.redirect_uri}
|
||||||
data = self.session.get(request_token_url, params=params).text
|
data = self.session.get(request_token_url, params=params).text
|
||||||
|
|
||||||
@@ -88,25 +80,29 @@ class OAuthBase(Extractor):
|
|||||||
self.session.params["oauth_token"] = token = data["oauth_token"]
|
self.session.params["oauth_token"] = token = data["oauth_token"]
|
||||||
self.session.token_secret = data["oauth_token_secret"]
|
self.session.token_secret = data["oauth_token_secret"]
|
||||||
|
|
||||||
# Get the User's Authorization
|
# get the user's authorization
|
||||||
params = {"oauth_token": token, "perms": "read"}
|
params = {"oauth_token": token, "perms": "read"}
|
||||||
data = self.open(authorize_url, params)
|
data = self.open(authorize_url, params)
|
||||||
|
|
||||||
# Exchange the Request Token for an Access Token
|
# exchange the request token for an access token
|
||||||
data = self.session.get(access_token_url, params=data).text
|
data = self.session.get(access_token_url, params=data).text
|
||||||
|
|
||||||
data = text.parse_query(data)
|
data = text.parse_query(data)
|
||||||
self.send(OAUTH1_MSG_TEMPLATE.format(
|
self.send(OAUTH1_MSG_TEMPLATE.format(
|
||||||
category=self.subcategory,
|
category=self.subcategory,
|
||||||
token=data["oauth_token"],
|
token=data["oauth_token"],
|
||||||
token_secret=data["oauth_token_secret"]))
|
token_secret=data["oauth_token_secret"],
|
||||||
|
))
|
||||||
|
|
||||||
def _oauth2_authorization_code_grant(
|
def _oauth2_authorization_code_grant(
|
||||||
self, client_id, client_secret, auth_url, token_url, scope):
|
self, client_id, client_secret, auth_url, token_url,
|
||||||
|
scope="read", key="refresh_token", auth=True):
|
||||||
"""Perform an OAuth2 authorization code grant"""
|
"""Perform an OAuth2 authorization code grant"""
|
||||||
|
|
||||||
state = "gallery-dl:{}:{}".format(
|
state = "gallery-dl_{}_{}".format(
|
||||||
self.subcategory, util.OAuthSession.nonce(8))
|
self.subcategory,
|
||||||
|
util.OAuthSession.nonce(8)
|
||||||
|
)
|
||||||
|
|
||||||
auth_params = {
|
auth_params = {
|
||||||
"client_id": client_id,
|
"client_id": client_id,
|
||||||
@@ -117,25 +113,33 @@ class OAuthBase(Extractor):
|
|||||||
"scope": scope,
|
"scope": scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
# receive 'code'
|
# receive an authorization code
|
||||||
params = self.open(auth_url, auth_params)
|
params = self.open(auth_url, auth_params)
|
||||||
|
|
||||||
# check auth response
|
# check authorization response
|
||||||
if state != params.get("state"):
|
if state != params.get("state"):
|
||||||
self.send("'state' mismatch: expected {}, got {}.".format(
|
self.send("'state' mismatch: expected {}, got {}.".format(
|
||||||
state, params.get("state")))
|
state, params.get("state")
|
||||||
|
))
|
||||||
return
|
return
|
||||||
if "error" in params:
|
if "error" in params:
|
||||||
self.send(params["error"])
|
self.send(params["error"])
|
||||||
return
|
return
|
||||||
|
|
||||||
# exchange 'code' for 'refresh_token'
|
# exchange the authorization code for a token
|
||||||
data = {
|
data = {
|
||||||
"grant_type": "authorization_code",
|
"grant_type": "authorization_code",
|
||||||
"code": params["code"],
|
"code": params["code"],
|
||||||
"redirect_uri": self.redirect_uri,
|
"redirect_uri": self.redirect_uri,
|
||||||
}
|
}
|
||||||
auth = (client_id, client_secret)
|
|
||||||
|
if auth:
|
||||||
|
auth = (client_id, client_secret)
|
||||||
|
else:
|
||||||
|
auth = None
|
||||||
|
data["client_id"] = client_id
|
||||||
|
data["client_secret"] = client_secret
|
||||||
|
|
||||||
data = self.session.post(token_url, data=data, auth=auth).json()
|
data = self.session.post(token_url, data=data, auth=auth).json()
|
||||||
|
|
||||||
# check token response
|
# check token response
|
||||||
@@ -143,10 +147,13 @@ class OAuthBase(Extractor):
|
|||||||
self.send(data["error"])
|
self.send(data["error"])
|
||||||
return
|
return
|
||||||
|
|
||||||
# display refresh token
|
# display token
|
||||||
|
part = key.partition("_")[0]
|
||||||
self.send(OAUTH2_MSG_TEMPLATE.format(
|
self.send(OAUTH2_MSG_TEMPLATE.format(
|
||||||
category=self.subcategory,
|
category=self.subcategory,
|
||||||
token=data["refresh_token"]
|
key=part,
|
||||||
|
Key=part.capitalize(),
|
||||||
|
token=data[key],
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
@@ -165,7 +172,7 @@ class OAuthDeviantart(OAuthBase):
|
|||||||
"client-secret", deviantart.DeviantartAPI.CLIENT_SECRET),
|
"client-secret", deviantart.DeviantartAPI.CLIENT_SECRET),
|
||||||
"https://www.deviantart.com/oauth2/authorize",
|
"https://www.deviantart.com/oauth2/authorize",
|
||||||
"https://www.deviantart.com/oauth2/token",
|
"https://www.deviantart.com/oauth2/token",
|
||||||
"browse",
|
scope="browse",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -191,6 +198,27 @@ class OAuthFlickr(OAuthBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OAuthPinterest(OAuthBase):
|
||||||
|
subcategory = "pinterest"
|
||||||
|
pattern = ["oauth:pinterest$"]
|
||||||
|
redirect_uri = "https://mikf.github.io/gallery-dl/oauth-redirect.html"
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
yield Message.Version, 1
|
||||||
|
|
||||||
|
self._oauth2_authorization_code_grant(
|
||||||
|
self.oauth_config(
|
||||||
|
"client-id", pinterest.PinterestAPI.CLIENT_ID),
|
||||||
|
self.oauth_config(
|
||||||
|
"client-secret", pinterest.PinterestAPI.CLIENT_SECRET),
|
||||||
|
"https://api.pinterest.com/oauth/",
|
||||||
|
"https://api.pinterest.com/v1/oauth/token",
|
||||||
|
scope="read_public",
|
||||||
|
key="access_token",
|
||||||
|
auth=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OAuthReddit(OAuthBase):
|
class OAuthReddit(OAuthBase):
|
||||||
subcategory = "reddit"
|
subcategory = "reddit"
|
||||||
pattern = ["oauth:reddit$"]
|
pattern = ["oauth:reddit$"]
|
||||||
@@ -204,7 +232,7 @@ class OAuthReddit(OAuthBase):
|
|||||||
"",
|
"",
|
||||||
"https://www.reddit.com/api/v1/authorize",
|
"https://www.reddit.com/api/v1/authorize",
|
||||||
"https://www.reddit.com/api/v1/access_token",
|
"https://www.reddit.com/api/v1/access_token",
|
||||||
"read",
|
scope="read",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -253,18 +281,18 @@ Example:
|
|||||||
|
|
||||||
|
|
||||||
OAUTH2_MSG_TEMPLATE = """
|
OAUTH2_MSG_TEMPLATE = """
|
||||||
Your Refresh Token is
|
Your {Key} Token is
|
||||||
|
|
||||||
{token}
|
{token}
|
||||||
|
|
||||||
Put this value into your configuration file as
|
Put this value into your configuration file as
|
||||||
'extractor.{category}.refesh-token'.
|
'extractor.{category}.{key}-token'.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
{{
|
{{
|
||||||
"extractor": {{
|
"extractor": {{
|
||||||
"{category}": {{
|
"{category}": {{
|
||||||
"refresh-token": "{token}"
|
"{key}-token": "{token}"
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ class PinterestPinitExtractor(PinterestExtractor):
|
|||||||
|
|
||||||
class PinterestAPI():
|
class PinterestAPI():
|
||||||
"""Minimal interface for the pinterest API"""
|
"""Minimal interface for the pinterest API"""
|
||||||
|
CLIENT_ID = "4959725425749142746"
|
||||||
|
CLIENT_SECRET = ("2ea77dc64ca02974a728e46c5a9d2adf"
|
||||||
|
"cdd42f4d4ffb40ad064072165ad4b10d")
|
||||||
|
|
||||||
def __init__(self, extractor, access_token="AfyIXxi1MJ6et0NlIl_vBchHbex-"
|
def __init__(self, extractor, access_token="AfyIXxi1MJ6et0NlIl_vBchHbex-"
|
||||||
"FSWylPyr2GJE2uu3W8A97QAAAAA"):
|
"FSWylPyr2GJE2uu3W8A97QAAAAA"):
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ AUTH_MAP = {
|
|||||||
"flickr" : "Optional (OAuth)",
|
"flickr" : "Optional (OAuth)",
|
||||||
"idolcomplex": "Optional",
|
"idolcomplex": "Optional",
|
||||||
"nijie" : "Required",
|
"nijie" : "Required",
|
||||||
|
"pinterest" : "Optional (OAuth)",
|
||||||
"pixiv" : "Required",
|
"pixiv" : "Required",
|
||||||
"reddit" : "Optional (OAuth)",
|
"reddit" : "Optional (OAuth)",
|
||||||
"sankaku" : "Optional",
|
"sankaku" : "Optional",
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ class TestExtractorResults(unittest.TestCase):
|
|||||||
config.set(("extractor", "deviantart", "client-id"), "7777")
|
config.set(("extractor", "deviantart", "client-id"), "7777")
|
||||||
config.set(("extractor", "deviantart", "client-secret"),
|
config.set(("extractor", "deviantart", "client-secret"),
|
||||||
"ff14994c744d9208e5caeec7aab4a026")
|
"ff14994c744d9208e5caeec7aab4a026")
|
||||||
|
config.set(("extractor", "pinterest", "access-token"),
|
||||||
|
"Ab1gUJFF5TFoWXRbX0p7_ue7jOHeFSX8iOrCIOZE24bOp0A6TQAAAAA")
|
||||||
config.set(("extractor", "tumblr", "api-key"),
|
config.set(("extractor", "tumblr", "api-key"),
|
||||||
"0cXoHfIqVzMQcc3HESZSNsVlulGxEXGDTTZCDrRrjaa0jmuTc6")
|
"0cXoHfIqVzMQcc3HESZSNsVlulGxEXGDTTZCDrRrjaa0jmuTc6")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user