diff --git a/docs/supportedsites.md b/docs/supportedsites.md
index 660558ca4aa..515876b18d1 100644
--- a/docs/supportedsites.md
+++ b/docs/supportedsites.md
@@ -271,6 +271,12 @@ Consider all listed sites to potentially be NSFW.
Favorites, Pools, Posts, Redirects, Tag Searches |
|
+
+ Girls With Muscle |
+ https://www.girlswithmuscle.com/ |
+ Posts, Galleries, Search Results, Favorites |
+ Supported |
+
Gofile |
https://gofile.io/ |
diff --git a/gallery_dl/extractor/__init__.py b/gallery_dl/extractor/__init__.py
index e103cb1b565..3ed09a02395 100644
--- a/gallery_dl/extractor/__init__.py
+++ b/gallery_dl/extractor/__init__.py
@@ -55,6 +55,7 @@
"gelbooru",
"gelbooru_v01",
"gelbooru_v02",
+ "girlswithmuscle",
"gofile",
"hatenablog",
"hentai2read",
diff --git a/gallery_dl/extractor/girlswithmuscle.py b/gallery_dl/extractor/girlswithmuscle.py
new file mode 100644
index 00000000000..30bcb35b918
--- /dev/null
+++ b/gallery_dl/extractor/girlswithmuscle.py
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+
+import re
+
+from .common import Extractor, Message
+from .. import text, exception
+from ..cache import cache
+
+
+class GirlswithmuscleExtractor(Extractor):
+ def login(self):
+ username, password = self._get_auth_info()
+ if username:
+ self.cookies_update(self._login_impl(username, password))
+
+ @staticmethod
+ def _is_logged_in(page_text: str) -> bool:
+ return 'Log in' not in page_text
+
+ @staticmethod
+ def _get_csrfmiddlewaretoken(page: str) -> str:
+ return text.extract(
+ page,
+ 'name="csrfmiddlewaretoken" value="',
+ '"'
+ )[0]
+
+ def _open_login_page(self):
+ """We need it to get second CSRF token"""
+ url = "https://www.girlswithmuscle.com/login/?next=/"
+ response = self.request(url)
+ return self._get_csrfmiddlewaretoken(response.text)
+
+ def _send_login_request(self, username, password, csrf_mw):
+ """Actual login action"""
+ data = {
+ "csrfmiddlewaretoken": csrf_mw,
+ "username": username,
+ "password": password,
+ "next": "/"
+ }
+
+ # Otherwise will be 403 Forbidden
+ self.session.headers['Origin'] = 'https://www.girlswithmuscle.com'
+ self.session.headers['Referer'] = \
+ 'https://www.girlswithmuscle.com/login/?next=/'
+
+ # if successful, will update cookies
+ url = "https://www.girlswithmuscle.com/login/"
+ response = self.request(url, method="post", data=data)
+
+ if "Wrong username or password" in response.text:
+ raise exception.AuthenticationError()
+ elif not self._is_logged_in(response.text):
+ raise exception.AuthenticationError("Account data is missing")
+
+ @cache(maxage=28 * 86400, keyarg=1)
+ def _login_impl(self, username, password):
+ self.log.info("Logging in as %s", username)
+
+ csrf_mw = self._open_login_page()
+ self._send_login_request(username, password, csrf_mw)
+ return {c.name: c.value for c in self.session.cookies}
+
+
+class GirlswithmusclePostExtractor(GirlswithmuscleExtractor):
+ """Extractor for individual posts on girlswithmuscle.com"""
+ category = "girlswithmuscle"
+ subcategory = "post"
+ directory_fmt = ("{category}", "{model}")
+ filename_fmt = "{model}_{id}.{extension}"
+ archive_fmt = "{type}_{model}_{id}"
+ pattern = (r"(?:https?://)?(?:www\.)?girlswithmuscle\.com"
+ r"/(\d+)/")
+
+ def __init__(self, match):
+ Extractor.__init__(self, match)
+ self.id = match.groups()[0]
+
+ def items(self):
+ self.login()
+ url = "https://girlswithmuscle.com/{}/".format(self.id)
+ page = self.request(url).text
+
+ if page is None:
+ raise exception.NotFoundError("post")
+
+ url = text.extr(page, 'class="main-image" src="', '"')
+ if url:
+ metadata = self.metadata(page, url, 'picture')
+ else:
+ url = text.extr(page, '