diff --git a/docs/configuration.rst b/docs/configuration.rst
index 193f35cf..75033307 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -382,6 +382,7 @@ Description
* ``e621`` (*)
* ``e926`` (*)
* ``exhentai``
+ * ``gfycat``
* ``idolcomplex``
* ``imgbb``
* ``inkbunny``
diff --git a/docs/supportedsites.md b/docs/supportedsites.md
index 444e4db5..a3c0ee10 100644
--- a/docs/supportedsites.md
+++ b/docs/supportedsites.md
@@ -251,7 +251,7 @@ Consider all sites to be NSFW unless otherwise known.
Gfycat |
https://gfycat.com/ |
Collections, individual Images, Search Results, User Profiles |
- |
+ Supported |
| Gofile |
diff --git a/gallery_dl/extractor/gfycat.py b/gallery_dl/extractor/gfycat.py
index 2d057f48..ccebdf98 100644
--- a/gallery_dl/extractor/gfycat.py
+++ b/gallery_dl/extractor/gfycat.py
@@ -10,6 +10,7 @@
from .common import Extractor, Message
from .. import text, exception
+from ..cache import cache
class GfycatExtractor(Extractor):
@@ -221,6 +222,8 @@ class GfycatAPI():
def __init__(self, extractor):
self.extractor = extractor
+ self.headers = {}
+ self.username, self.password = extractor._get_auth_info()
def collection(self, user, collection):
endpoint = "/v1/users/{}/collections/{}/gfycats".format(
@@ -252,9 +255,45 @@ class GfycatAPI():
params = {"count": 100}
return self._pagination(endpoint, params)
+ def authenticate(self):
+ self.headers["Authorization"] = \
+ self._authenticate_impl(self.username, self.password)
+
+ @cache(maxage=3600, keyarg=1)
+ def _authenticate_impl(self, username, password):
+ self.extractor.log.info("Logging in as %s", username)
+
+ url = "https://weblogin.gfycat.com/oauth/webtoken"
+ headers = {"Origin": "https://gfycat.com"}
+ data = {
+ "access_key": "Anr96uuqt9EdamSCwK4txKPjMsf2"
+ "M95Rfa5FLLhPFucu8H5HTzeutyAa",
+ }
+ response = self.extractor.request(
+ url, method="POST", headers=headers, json=data).json()
+
+ url = "https://weblogin.gfycat.com/oauth/weblogin"
+ headers["authorization"] = "Bearer " + response["access_token"]
+ data = {
+ "grant_type": "password",
+ "username" : username,
+ "password" : password,
+ }
+ response = self.extractor.request(
+ url, method="POST", headers=headers, json=data, fatal=None).json()
+
+ if "errorMessage" in response:
+ raise exception.AuthenticationError(
+ response["errorMessage"]["description"])
+ return "Bearer " + response["access_token"]
+
def _call(self, endpoint, params=None):
+ if self.username:
+ self.authenticate()
+
url = self.API_ROOT + endpoint
- return self.extractor.request(url, params=params).json()
+ return self.extractor.request(
+ url, params=params, headers=self.headers).json()
def _pagination(self, endpoint, params, key="gfycats"):
while True:
diff --git a/scripts/supportedsites.py b/scripts/supportedsites.py
index 78e6843e..fb6ffa7b 100755
--- a/scripts/supportedsites.py
+++ b/scripts/supportedsites.py
@@ -312,6 +312,7 @@ AUTH_MAP = {
"fanbox" : _COOKIES,
"fantia" : _COOKIES,
"flickr" : _OAUTH,
+ "gfycat" : "Supported",
"furaffinity" : _COOKIES,
"horne" : "Required",
"idolcomplex" : "Supported",
diff --git a/test/test_results.py b/test/test_results.py
index 03a17c40..3c7d2844 100644
--- a/test/test_results.py
+++ b/test/test_results.py
@@ -325,7 +325,7 @@ def setup_test_config():
for category in ("danbooru", "atfbooru", "aibooru", "e621", "e926",
"instagram", "twitter", "subscribestar", "deviantart",
"inkbunny", "tapas", "pillowfort", "mangadex",
- "vipergirls"):
+ "vipergirls", "gfycat"):
config.set(("extractor", category), "username", None)
config.set(("extractor", "mastodon.social"), "access-token",