replace 2-element f-strings with simple '+' concatenations

Python's 'ast' module and its 'NodeVisitor' class
were incredibly helpful in identifying these
This commit is contained in:
Mike Fährmann
2025-12-21 15:09:03 +01:00
parent d0f06be0d2
commit 00c6821a3f
74 changed files with 229 additions and 237 deletions

View File

@@ -25,7 +25,7 @@ class _2chThreadExtractor(Extractor):
def __init__(self, match):
tld = match[1]
self.root = f"https://2ch.{'org' if tld == 'hk' else tld}"
self.root = "https://2ch." + ("org" if tld == "hk" else tld)
Extractor.__init__(self, match)
def items(self):
@@ -71,14 +71,14 @@ class _2chBoardExtractor(Extractor):
def __init__(self, match):
tld = match[1]
self.root = f"https://2ch.{'su' if tld == 'hk' else tld}"
self.root = "https://2ch." + ("org" if tld == "hk" else tld)
Extractor.__init__(self, match)
def items(self):
base = f"{self.root}/{self.groups[1]}"
# index page
url = f"{base}/index.json"
url = base + "/index.json"
index = self.request_json(url)
index["_extractor"] = _2chThreadExtractor
for thread in index["threads"]:

View File

@@ -84,7 +84,7 @@ class ArcalivePostExtractor(ArcaliveExtractor):
url = src
fallback = ()
query = f"?type=orig&{query}"
query = "?type=orig&" + query
if orig := text.extr(media, 'data-orig="', '"'):
path, _, ext = url.rpartition(".")
if ext != orig:
@@ -169,8 +169,11 @@ class ArcaliveAPI():
return data
self.log.debug("Server response: %s", data)
msg = f": {msg}" if (msg := data.get("message")) else ""
raise exception.AbortExtraction(f"API request failed{msg}")
if msg := data.get("message"):
msg = "API request failed: " + msg
else:
msg = "API request failed"
raise exception.AbortExtraction(msg)
def _pagination(self, endpoint, params, key):
while True:

View File

@@ -24,8 +24,8 @@ class ArenaChannelExtractor(GalleryExtractor):
example = "https://are.na/evan-collins-1522646491/cassette-futurism"
def metadata(self, page):
channel = self.request_json(
f"https://api.are.na/v2/channels/{self.groups[0]}")
url = "https://api.are.na/v2/channels/" + self.groups[0]
channel = self.request_json(url)
channel["date"] = self.parse_datetime_iso(
channel["created_at"])

View File

@@ -95,7 +95,7 @@ class ArtstationExtractor(Extractor):
if not self.external:
return
asset["extension"] = "mp4"
return f"ytdl:{url}"
return "ytdl:" + url
self.log.debug(player)
self.log.warning("Failed to extract embedded player URL (%s)",
@@ -328,9 +328,9 @@ class ArtstationChallengeExtractor(ArtstationExtractor):
def items(self):
base = f"{self.root}/contests/_/challenges/{self.challenge_id}"
challenge_url = f"{base}.json"
submission_url = f"{base}/submissions.json"
update_url = f"{self.root}/contests/submission_updates.json"
challenge_url = base + ".json"
submission_url = base + "/submissions.json"
update_url = self.root + "/contests/submission_updates.json"
challenge = self.request_json(challenge_url)
yield Message.Directory, "", {"challenge": challenge}
@@ -388,7 +388,7 @@ class ArtstationSearchExtractor(ArtstationExtractor):
"value" : value.split(","),
})
url = f"{self.root}/api/v2/search/projects.json"
url = self.root + "/api/v2/search/projects.json"
data = {
"query" : self.query,
"page" : None,
@@ -419,7 +419,7 @@ class ArtstationArtworkExtractor(ArtstationExtractor):
return {"artwork": self.query}
def projects(self):
url = f"{self.root}/projects.json"
url = self.root + "/projects.json"
return self._pagination(url, self.query.copy())

View File

@@ -24,7 +24,7 @@ class BellazonExtractor(Extractor):
archive_fmt = "{post[id]}/{id}_{filename}"
def items(self):
native = (f"{self.root}/", f"{self.root[6:]}/")
native = (self.root + "/", self.root[6:] + "/")
extract_urls = text.re(
r'(?s)<('
r'(?:video .*?<source [^>]*?src|a [^>]*?href)="([^"]+).*?</a>'
@@ -82,7 +82,7 @@ class BellazonExtractor(Extractor):
dc["extension"] = text.ext_from_url(url)
if url[0] == "/":
url = f"https:{url}"
url = "https:" + url
yield Message.Url, url, dc
else:
@@ -91,10 +91,10 @@ class BellazonExtractor(Extractor):
yield Message.Queue, url, data
def _pagination(self, base, pnum=None):
base = f"{self.root}{base}"
base = self.root + base
if pnum is None:
url = f"{base}/"
url = base + "/"
pnum = 1
else:
url = f"{base}/page/{pnum}/"
@@ -112,7 +112,7 @@ class BellazonExtractor(Extractor):
url = f"{base}/page/{pnum}/"
def _pagination_reverse(self, base, pnum=None):
base = f"{self.root}{base}"
base = self.root + base
url = f"{base}/page/{'9999' if pnum is None else pnum}/"
with self.request(url) as response:
@@ -127,7 +127,7 @@ class BellazonExtractor(Extractor):
if pnum > 1:
url = f"{base}/page/{pnum}/"
elif pnum == 1:
url = f"{base}/"
url = base + "/"
else:
return
@@ -198,9 +198,9 @@ class BellazonPostExtractor(BellazonExtractor):
def posts(self):
path, post_id = self.groups
page = self.request(f"{self.root}{path}").text
page = self.request(self.root + path).text
pos = page.find(f'id="elComment_{post_id}')
pos = page.find('id="elComment_' + post_id)
if pos < 0:
raise exception.NotFoundError("post")
html = text.extract(page, "<article ", "</article>", pos-100)[0]

View File

@@ -146,7 +146,7 @@ class BilibiliAPI():
except Exception:
if "window._riskdata_" not in page:
raise exception.AbortExtraction(
f"{article_id}: Unable to extract INITIAL_STATE data")
article_id + ": Unable to extract INITIAL_STATE data")
self.extractor.wait(seconds=300)
def user_favlist(self):

View File

@@ -424,7 +424,7 @@ class BoostyAPI():
params["offset"] = offset
def dialog(self, dialog_id):
endpoint = f"/v1/dialog/{dialog_id}"
endpoint = "/v1/dialog/" + dialog_id
return self._call(endpoint)
def dialog_messages(self, dialog_id, limit=300, offset=None):

View File

@@ -116,7 +116,7 @@ class BoothShopExtractor(BoothExtractor):
BoothExtractor.__init__(self, match)
def shop_items(self):
return self._pagination(f"{self.root}/items")
return self._pagination(self.root + "/items")
def _fallback(url):

View File

@@ -189,7 +189,7 @@ class BunkrAlbumExtractor(LolisafeAlbumExtractor):
json={"id": data_id})
if data.get("encrypted"):
key = f"SECRET_KEY_{data['timestamp'] // 3600}"
key = "SECRET_KEY_" + str(data["timestamp"] // 3600)
file_url = util.decrypt_xor(data["url"], key.encode())
else:
file_url = data["url"]
@@ -221,7 +221,7 @@ class BunkrMediaExtractor(BunkrAlbumExtractor):
def fetch_album(self, album_id):
try:
page = self.request(f"{self.root}{album_id}").text
page = self.request(self.root + album_id).text
data_id = text.extr(page, 'data-file-id="', '"')
file = self._extract_file(data_id)
file["name"] = text.unquote(text.unescape(text.extr(

View File

@@ -580,10 +580,10 @@ class CivitaiUserCollectionsExtractor(CivitaiExtractor):
params = self._parse_query(query)
params["userId"] = self.api.user(text.unquote(user))[0]["id"]
base = f"{self.root}/collections/"
base = self.root + "/collections/"
for collection in self.api.collections(params):
collection["_extractor"] = CivitaiCollectionExtractor
yield Message.Queue, f"{base}{collection['id']}", collection
yield Message.Queue, base + str(collection["id"]), collection
class CivitaiGeneratedExtractor(CivitaiExtractor):
@@ -591,7 +591,7 @@ class CivitaiGeneratedExtractor(CivitaiExtractor):
subcategory = "generated"
filename_fmt = "{filename}.{extension}"
directory_fmt = ("{category}", "generated")
pattern = f"{BASE_PATTERN}/generate"
pattern = BASE_PATTERN + "/generate"
example = "https://civitai.com/generate"
def items(self):
@@ -647,12 +647,12 @@ class CivitaiRestAPI():
})
def model(self, model_id):
endpoint = f"/v1/models/{model_id}"
endpoint = "/v1/models/" + str(model_id)
return self._call(endpoint)
@memcache(keyarg=1)
def model_version(self, model_version_id):
endpoint = f"/v1/model-versions/{model_version_id}"
endpoint = "/v1/model-versions/" + str(model_version_id)
return self._call(endpoint)
def models(self, params):
@@ -945,7 +945,7 @@ class CivitaiSearchAPI():
if auth := extractor.config("token"):
if " " not in auth:
auth = f"Bearer {auth}"
auth = "Bearer " + auth
else:
auth = ("Bearer 8c46eb2508e21db1e9828a97968d"
"91ab1ca1caa5f70a00e88a2ba1e286603b61")

View File

@@ -44,7 +44,7 @@ class ComickCoversExtractor(ComickBase, GalleryExtractor):
covers.reverse()
return [
(f"https://meo.comick.pictures/{cover['b2key']}", {
("https://meo.comick.pictures/" + cover["b2key"], {
"id" : cover["id"],
"width" : cover["w"],
"height": cover["h"],
@@ -128,7 +128,7 @@ class ComickChapterExtractor(ComickBase, ChapterExtractor):
return ()
return [
(f"https://meo.comick.pictures/{img['b2key']}", {
("https://meo.comick.pictures/" + img["b2key"], {
"width" : img["w"],
"height" : img["h"],
"size" : img["s"],

View File

@@ -766,7 +766,7 @@ class GalleryExtractor(Extractor):
Extractor.__init__(self, match)
if url is None and (path := self.groups[0]) and path[0] == "/":
self.page_url = f"{self.root}{path}"
self.page_url = self.root + path
else:
self.page_url = url
@@ -863,7 +863,7 @@ class MangaExtractor(Extractor):
Extractor.__init__(self, match)
if url is None and (path := self.groups[0]) and path[0] == "/":
self.page_url = f"{self.root}{path}"
self.page_url = self.root + path
else:
self.page_url = url

View File

@@ -20,7 +20,7 @@ class CyberfileExtractor(Extractor):
root = "https://cyberfile.me"
def request_api(self, endpoint, data):
url = f"{self.root}{endpoint}"
url = self.root + endpoint
headers = {
"X-Requested-With": "XMLHttpRequest",
"Origin": self.root,
@@ -29,7 +29,7 @@ class CyberfileExtractor(Extractor):
url, method="POST", headers=headers, data=data)
if "albumPasswordModel" in resp.get("javascript", ""):
url_pw = f"{self.root}/ajax/folder_password_process"
url_pw = self.root + "/ajax/folder_password_process"
data_pw = {
"folderPassword": self._get_auth_info(password=True)[1],
"folderId": text.extr(

View File

@@ -155,7 +155,7 @@ class DanbooruExtractor(BaseExtractor):
return
if prefix:
params["page"] = f"{prefix}{posts[-1]['id']}"
params["page"] = prefix + str(posts[-1]["id"])
elif params["page"]:
params["page"] += 1
else:
@@ -174,9 +174,8 @@ class DanbooruExtractor(BaseExtractor):
else:
ext = data["ZIP:ZipFileName"].rpartition(".")[2]
fmt = ("{:>06}." + ext).format
delays = data["Ugoira:FrameDelays"]
return [{"file": fmt(index), "delay": delay}
return [{"file": f"{index:>06}.{ext}", "delay": delay}
for index, delay in enumerate(delays)]
def _collection_posts(self, cid, ctype):

View File

@@ -31,7 +31,7 @@ class DandadanChapterExtractor(DandadanBase, ChapterExtractor):
return {
"manga" : "Dandadan",
"chapter" : text.parse_int(chapter),
"chapter_minor": f"{sep}{minor}",
"chapter_minor": sep + minor,
"lang" : "en",
}

View File

@@ -34,7 +34,7 @@ class DankefuerslesenChapterExtractor(DankefuerslesenBase, ChapterExtractor):
def _init(self):
self.zip = self.config("zip", False)
if self.zip:
self.filename_fmt = f"{self.directory_fmt[-1]}.{{extension}}"
self.filename_fmt = self.directory_fmt[-1] + ".{extension}"
self.directory_fmt = self.directory_fmt[:-1]
def metadata(self, page):

View File

@@ -66,7 +66,7 @@ class DeviantartExtractor(Extractor):
self.quality = "-fullview.png?"
self.quality_sub = text.re(r"-fullview\.[a-z0-9]+\?").sub
else:
self.quality = f",q_{self.quality}"
self.quality = ",q_" + str(self.quality)
self.quality_sub = text.re(r",q_\d+").sub
if self.intermediary:

View File

@@ -25,10 +25,10 @@ class EromeExtractor(Extractor):
_cookies = True
def items(self):
base = f"{self.root}/a/"
base = self.root + "/a/"
data = {"_extractor": EromeAlbumExtractor}
for album_id in self.albums():
yield Message.Queue, f"{base}{album_id}", data
yield Message.Queue, base + album_id, data
def albums(self):
return ()
@@ -141,7 +141,7 @@ class EromeSearchExtractor(EromeExtractor):
example = "https://www.erome.com/search?q=QUERY"
def albums(self):
url = f"{self.root}/search"
url = self.root + "/search"
params = text.parse_query(self.groups[0])
return self._pagination(url, params)

View File

@@ -237,16 +237,14 @@ class FacebookExtractor(Extractor):
if res.url.startswith(self.root + "/login"):
raise exception.AuthRequired(
message=(f"You must be logged in to continue viewing images."
f"{LEFT_OFF_TXT}")
)
message=("You must be logged in to continue viewing images." +
LEFT_OFF_TXT))
if b'{"__dr":"CometErrorRoot.react"}' in res.content:
raise exception.AbortExtraction(
f"You've been temporarily blocked from viewing images.\n"
f"Please try using a different account, "
f"using a VPN or waiting before you retry.{LEFT_OFF_TXT}"
)
"You've been temporarily blocked from viewing images.\n"
"Please try using a different account, "
"using a VPN or waiting before you retry." + LEFT_OFF_TXT)
return res

View File

@@ -340,7 +340,7 @@ class FanboxExtractor(Extractor):
url = (f"https://docs.google.com/forms/d/e/"
f"{content_id}/viewform?usp=sf_link")
else:
self.log.warning(f"service not recognized: {provider}")
self.log.warning("service not recognized: %s", provider)
if url:
final_post["embed"] = embed

View File

@@ -135,7 +135,7 @@ class FanslyExtractor(Extractor):
files.append({
"file": file,
"url": f"ytdl:{location['location']}",
"url": "ytdl:" + location["location"],
"_fallback": fallback,
"_ytdl_manifest":
"dash" if mime == "application/dash+xml" else "hls",
@@ -184,7 +184,7 @@ class FanslyListExtractor(FanslyExtractor):
example = "https://fansly.com/lists/1234567890"
def items(self):
base = f"{self.root}/"
base = self.root + "/"
for account in self.api.lists_itemsnew(self.groups[0]):
account["_extractor"] = FanslyCreatorPostsExtractor
url = f"{base}{account['username']}/posts"
@@ -197,7 +197,7 @@ class FanslyListsExtractor(FanslyExtractor):
example = "https://fansly.com/lists"
def items(self):
base = f"{self.root}/lists/"
base = self.root + "/lists/"
for list in self.api.lists_account():
list["_extractor"] = FanslyListExtractor
url = f"{base}{list['id']}#{list['label']}"
@@ -308,7 +308,7 @@ class FanslyAPI():
return self._pagination(endpoint, params)
def timeline_new(self, account_id, wall_id):
endpoint = f"/v1/timelinenew/{account_id}"
endpoint = "/v1/timelinenew/" + str(account_id)
params = {
"before" : "0",
"after" : "0",

View File

@@ -456,7 +456,7 @@ class FlickrAPI(oauth.OAuth1API):
except ValueError:
data = {"code": -1, "message": response.content}
if "code" in data:
msg = data.get("message")
msg = data.get("message", "")
self.log.debug("Server response: %s", data)
if data["code"] == 1:
raise exception.NotFoundError(self.extractor.subcategory)
@@ -466,7 +466,7 @@ class FlickrAPI(oauth.OAuth1API):
raise exception.AuthenticationError(msg)
elif data["code"] == 99:
raise exception.AuthorizationError(msg)
raise exception.AbortExtraction(f"API request failed: {msg}")
raise exception.AbortExtraction("API request failed: " + msg)
return data
def _pagination(self, method, params, key="photos"):

View File

@@ -278,7 +278,7 @@ class FoolfuukaGalleryExtractor(FoolfuukaExtractor):
base = f"{self.root}/_/api/chan/gallery/?board={self.board}&page="
for pnum in pages:
posts = self.request_json(f"{base}{pnum}")
posts = self.request_json(base + str(pnum))
if not posts:
return
yield from posts

View File

@@ -322,7 +322,7 @@ class FuraffinityUserExtractor(Dispatch, FuraffinityExtractor):
def items(self):
base = self.root
user = f"{self.user}/"
user = self.user + "/"
return self._dispatch_extractors((
(FuraffinityGalleryExtractor , f"{base}/gallery/{user}"),
(FuraffinityScrapsExtractor , f"{base}/scraps/{user}"),

View File

@@ -22,14 +22,14 @@ class GirlsreleasedExtractor(Extractor):
def items(self):
data = {"_extractor": GirlsreleasedSetExtractor}
base = f"{self.root}/set/"
base = self.root + "/set/"
for set in self._pagination():
yield Message.Queue, f"{base}{set[0]}", data
yield Message.Queue, base + set[0], data
def _pagination(self):
base = f"{self.root}/api/0.2/sets/{self._path}/{self.groups[0]}/page/"
for pnum in itertools.count():
sets = self.request_json(f"{base}{pnum}")["sets"]
sets = self.request_json(base + str(pnum))["sets"]
if not sets:
return

View File

@@ -32,14 +32,14 @@ class HitomiExtractor(Extractor):
language = tag
tag = "index"
else:
ns = f"{ns}/"
ns += "/"
url = (f"https://ltn.{self.domain}/n/{ns}"
f"/{tag.replace('_', ' ')}-{language}.nozomi")
if headers is None:
headers = {}
headers["Origin"] = self.root
headers["Referer"] = f"{self.root}/"
headers["Referer"] = self.root + "/"
return decode_nozomi(self.request(url, headers=headers).content)

View File

@@ -28,10 +28,7 @@ class ImagehostImageExtractor(Extractor):
def __init__(self, match):
Extractor.__init__(self, match)
if self.root:
self.page_url = f"{self.root}{match[1]}"
else:
self.page_url = f"http{'s' if self._https else ''}://{match[1]}"
self.page_url = (self.root or "https://") + match[1]
self.token = match[2]
if self._params == "simple":
@@ -461,8 +458,8 @@ class ImgdriveImageExtractor(ImagehostImageExtractor):
def __init__(self, match):
path, category, self.token = match.groups()
self.page_url = f"https://{path}"
self.category = f"img{category}"
self.page_url = "https://" + path
self.category = "img" + category
Extractor.__init__(self, match)
def get_info(self, page):

View File

@@ -136,8 +136,8 @@ class ImgbbAlbumExtractor(ImgbbExtractor):
'data-text="image-count">', "<")),
}
url = f"{self.root}/json"
params["pathname"] = f"/album/{album['id']}"
url = self.root + "/json"
params["pathname"] = "/album/" + album["id"]
return self._pagination(page, url, params)
@@ -190,11 +190,11 @@ class ImgbbUserExtractor(ImgbbExtractor):
if response.status_code < 300:
params["pathname"] = "/"
return self._pagination(response.text, f"{url}json", params)
return self._pagination(response.text, url + "json", params)
if response.status_code == 301:
raise exception.NotFoundError("user")
redirect = f"HTTP redirect to {response.headers.get('Location')}"
redirect = "HTTP redirect to " + response.headers.get("Location", "")
if response.status_code == 302:
raise exception.AuthRequired(
("username & password", "authenticated cookies"),

View File

@@ -22,9 +22,6 @@ class ImgpileExtractor(Extractor):
"{post[title]} ({post[id_slug]})")
archive_fmt = "{post[id_slug]}_{id}"
def items(self):
pass
class ImgpilePostExtractor(ImgpileExtractor):
subcategory = "post"
@@ -71,7 +68,7 @@ class ImgpilePostExtractor(ImgpileExtractor):
"id_slug": text.extr(media, 'data-id="', '"'),
"id" : text.parse_int(text.extr(
media, 'data-media-id="', '"')),
"url": f"""http{text.extr(media, '<a href="http', '"')}""",
"url": "http" + text.extr(media, '<a href="http', '"'),
})
return files
@@ -82,13 +79,12 @@ class ImgpileUserExtractor(ImgpileExtractor):
example = "https://imgpile.com/u/USER"
def items(self):
url = f"{self.root}/api/v1/posts"
url = self.root + "/api/v1/posts"
params = {
"limit" : "100",
"sort" : "latest",
"period" : "all",
"visibility": "public",
# "moderation_status": "approved",
"username" : self.groups[0],
}
headers = {
@@ -101,7 +97,7 @@ class ImgpileUserExtractor(ImgpileExtractor):
"Sec-Fetch-Site": "same-origin",
}
base = f"{self.root}/p/"
base = self.root + "/p/"
while True:
data = self.request_json(url, params=params, headers=headers)
@@ -111,7 +107,7 @@ class ImgpileUserExtractor(ImgpileExtractor):
for item in data["data"]:
item["_extractor"] = ImgpilePostExtractor
url = f"{base}{item['slug']}"
url = base + item["slug"]
yield Message.Queue, url, item
url = data["links"].get("next")

View File

@@ -269,11 +269,11 @@ class ImgurAPI():
return self._pagination(endpoint, params)
def gallery_subreddit(self, subreddit):
endpoint = f"/3/gallery/r/{subreddit}"
endpoint = "/3/gallery/r/" + subreddit
return self._pagination(endpoint)
def gallery_tag(self, tag):
endpoint = f"/3/gallery/t/{tag}"
endpoint = "/3/gallery/t/" + tag
return self._pagination(endpoint, key="items")
def image(self, image_hash):

View File

@@ -636,7 +636,7 @@ class InstagramStoriesTrayExtractor(InstagramExtractor):
example = "https://www.instagram.com/stories/me/"
def items(self):
base = f"{self.root}/stories/id:"
base = self.root + "/stories/id:"
for story in self.api.reels_tray():
story["date"] = self.parse_timestamp(story["latest_reel_media"])
story["_extractor"] = InstagramStoriesExtractor

View File

@@ -34,7 +34,7 @@ class ItakuExtractor(Extractor):
for image in images:
image["date"] = self.parse_datetime_iso(image["date_added"])
for category, tags in image.pop("categorized_tags").items():
image[f"tags_{category.lower()}"] = [
image["tags_" + category.lower()] = [
t["name"] for t in tags]
image["tags"] = [t["name"] for t in image["tags"]]
@@ -73,9 +73,9 @@ class ItakuExtractor(Extractor):
return
if users := self.users():
base = f"{self.root}/profile/"
base = self.root + "/profile/"
for user in users:
url = f"{base}{user['owner_username']}"
url = base + user["owner_username"]
user["_extractor"] = ItakuUserExtractor
yield Message.Queue, url, user
return
@@ -182,11 +182,11 @@ class ItakuUserExtractor(Dispatch, ItakuExtractor):
def items(self):
base = f"{self.root}/profile/{self.groups[0]}/"
return self._dispatch_extractors((
(ItakuGalleryExtractor , f"{base}gallery"),
(ItakuPostsExtractor , f"{base}posts"),
(ItakuFollowersExtractor, f"{base}followers"),
(ItakuFollowingExtractor, f"{base}following"),
(ItakuStarsExtractor , f"{base}stars"),
(ItakuGalleryExtractor , base + "gallery"),
(ItakuPostsExtractor , base + "posts"),
(ItakuFollowersExtractor, base + "followers"),
(ItakuFollowingExtractor, base + "following"),
(ItakuStarsExtractor , base + "stars"),
), ("gallery",))
@@ -246,7 +246,7 @@ class ItakuAPI():
def __init__(self, extractor):
self.extractor = extractor
self.root = f"{extractor.root}/api"
self.root = extractor.root + "/api"
self.headers = {
"Accept": "application/json, text/plain, */*",
}
@@ -309,7 +309,7 @@ class ItakuAPI():
def _call(self, endpoint, params=None):
if not endpoint.startswith("http"):
endpoint = f"{self.root}{endpoint}"
endpoint = self.root + endpoint
return self.extractor.request_json(
endpoint, params=params, headers=self.headers)

View File

@@ -79,10 +79,10 @@ class IwaraExtractor(Extractor):
continue
yield Message.Directory, "", info
yield Message.Url, f"https:{download_url}", info
yield Message.Url, "https:" + download_url, info
def items_user(self, users, key=None):
base = f"{self.root}/profile/"
base = self.root + "/profile/"
for user in users:
if key is not None:
user = user[key]
@@ -90,7 +90,7 @@ class IwaraExtractor(Extractor):
continue
user["type"] = "user"
user["_extractor"] = IwaraUserExtractor
yield Message.Queue, f"{base}{username}", user
yield Message.Queue, base + username, user
def items_by_type(self, type, results):
if type == "image":
@@ -164,9 +164,9 @@ class IwaraUserExtractor(Dispatch, IwaraExtractor):
def items(self):
base = f"{self.root}/profile/{self.groups[0]}/"
return self._dispatch_extractors((
(IwaraUserImagesExtractor , f"{base}images"),
(IwaraUserVideosExtractor , f"{base}videos"),
(IwaraUserPlaylistsExtractor, f"{base}playlists"),
(IwaraUserImagesExtractor , base + "images"),
(IwaraUserVideosExtractor , base + "videos"),
(IwaraUserPlaylistsExtractor, base + "playlists"),
), ("user-images", "user-videos"))
@@ -196,12 +196,12 @@ class IwaraUserPlaylistsExtractor(IwaraExtractor):
example = "https://www.iwara.tv/profile/USERNAME/playlists"
def items(self):
base = f"{self.root}/playlist/"
base = self.root + "/playlist/"
for playlist in self.api.playlists(self._user_params()[1]):
playlist["type"] = "playlist"
playlist["_extractor"] = IwaraPlaylistExtractor
url = f"{base}{playlist['id']}"
url = base + playlist["id"]
yield Message.Queue, url, playlist
@@ -298,7 +298,7 @@ class IwaraAPI():
def __init__(self, extractor):
self.extractor = extractor
self.headers = {
"Referer" : f"{extractor.root}/",
"Referer" : extractor.root + "/",
"Content-Type": "application/json",
"Origin" : extractor.root,
}
@@ -308,15 +308,15 @@ class IwaraAPI():
self.authenticate = util.noop
def image(self, image_id):
endpoint = f"/image/{image_id}"
endpoint = "/image/" + image_id
return self._call(endpoint)
def video(self, video_id):
endpoint = f"/video/{video_id}"
endpoint = "/video/" + video_id
return self._call(endpoint)
def playlist(self, playlist_id):
endpoint = f"/playlist/{playlist_id}"
endpoint = "/playlist/" + playlist_id
return self._pagination(endpoint)
def detail(self, media):
@@ -356,7 +356,7 @@ class IwaraAPI():
@memcache(keyarg=1)
def profile(self, username):
endpoint = f"/profile/{username}"
endpoint = "/profile/" + username
return self._call(endpoint)
def user_following(self, user_id):
@@ -387,7 +387,7 @@ class IwaraAPI():
if refresh_token is None:
self.extractor.log.info("Logging in as %s", username)
url = f"{self.root}/user/login"
url = self.root + "/user/login"
json = {
"email" : username,
"password": self.password
@@ -403,15 +403,15 @@ class IwaraAPI():
self.extractor.log.info("Refreshing access token for %s", username)
url = f"{self.root}/user/token"
headers = {"Authorization": f"Bearer {refresh_token}", **self.headers}
url = self.root + "/user/token"
headers = {"Authorization": "Bearer " + refresh_token, **self.headers}
data = self.extractor.request_json(
url, method="POST", headers=headers, fatal=False)
if not (access_token := data.get("accessToken")):
self.extractor.log.debug(data)
raise exception.AuthenticationError(data.get("message"))
return f"Bearer {access_token}"
return "Bearer " + access_token
def _call(self, endpoint, params=None, headers=None):
if headers is None:

View File

@@ -52,7 +52,7 @@ class KabeuchiUserExtractor(Extractor):
return self._pagination(target_id)
def _pagination(self, target_id):
url = f"{self.root}/get_posts.php"
url = self.root + "/get_posts.php"
data = {
"user_id" : "0",
"target_id" : target_id,

View File

@@ -200,7 +200,7 @@ class KemonoExtractor(Extractor):
username = username[0]
self.log.info("Logging in as %s", username)
url = f"{self.root}/api/v1/authentication/login"
url = self.root + "/api/v1/authentication/login"
data = {"username": username, "password": password}
response = self.request(url, method="POST", json=data, fatal=False)
@@ -434,7 +434,7 @@ class KemonoDiscordExtractor(KemonoExtractor):
attachment["type"] = "attachment"
files.append(attachment)
for path in find_inline(post["content"] or ""):
files.append({"path": f"https://cdn.discordapp.com{path}",
files.append({"path": "https://cdn.discordapp.com" + path,
"name": path, "type": "inline", "hash": ""})
post.update(data)
@@ -577,7 +577,7 @@ class KemonoAPI():
def __init__(self, extractor):
self.extractor = extractor
self.root = f"{extractor.root}/api"
self.root = extractor.root + "/api"
self.headers = {"Accept": "text/css"}
def posts(self, offset=0, query=None, tags=None):
@@ -586,7 +586,7 @@ class KemonoAPI():
return self._pagination(endpoint, params, 50, "posts")
def file(self, file_hash):
endpoint = f"/v1/file/{file_hash}"
endpoint = "/v1/file/" + file_hash
return self._call(endpoint)
def creators(self):
@@ -643,18 +643,18 @@ class KemonoAPI():
return self._call(endpoint)
def discord_channel(self, channel_id, post_count=None):
endpoint = f"/v1/discord/channel/{channel_id}"
endpoint = "/v1/discord/channel/" + channel_id
if post_count is None:
return self._pagination(endpoint, {}, 150)
else:
return self._pagination_reverse(endpoint, {}, 150, post_count)
def discord_channel_lookup(self, server_id):
endpoint = f"/v1/discord/channel/lookup/{server_id}"
endpoint = "/v1/discord/channel/lookup/" + server_id
return self._call(endpoint)
def discord_server(self, server_id):
endpoint = f"/v1/discord/server/{server_id}"
endpoint = "/v1/discord/server/" + server_id
return self._call(endpoint)
def account_favorites(self, type):
@@ -669,7 +669,7 @@ class KemonoAPI():
headers = {**self.headers, **headers}
return self.extractor.request_json(
f"{self.root}{endpoint}", params=params, headers=headers,
self.root + endpoint, params=params, headers=headers,
encoding="utf-8", fatal=fatal)
def _pagination(self, endpoint, params, batch=50, key=None):

View File

@@ -35,7 +35,7 @@ class LeakgalleryExtractor(Extractor):
else:
media["creator"] = creator
media["url"] = url = f"https://cdn.leakgallery.com/{path}"
media["url"] = url = "https://cdn.leakgallery.com/" + path
text.nameext_from_url(url, media)
yield Message.Directory, "", media
yield Message.Url, url, media
@@ -43,7 +43,7 @@ class LeakgalleryExtractor(Extractor):
def _pagination(self, type, base, params=None, creator=None, pnum=1):
while True:
try:
data = self.request_json(f"{base}{pnum}", params=params)
data = self.request_json(base + str(pnum), params=params)
if not data:
return

View File

@@ -85,7 +85,7 @@ class MadokamiMangaExtractor(MadokamiExtractor):
else:
ch["volume"] = ch["chapter"] = ch["chapter_end"] = 0
url = f"{self.root}{ch['path']}"
url = self.root + ch["path"]
text.nameext_from_url(url, ch)
yield Message.Directory, "", ch

View File

@@ -66,7 +66,7 @@ class MangadexExtractor(Extractor):
"title" : cattributes["title"],
"volume" : text.parse_int(cattributes["volume"]),
"chapter" : text.parse_int(chnum),
"chapter_minor": f"{sep}{minor}",
"chapter_minor": sep + minor,
"chapter_id": chapter["id"],
"date" : self.parse_datetime_iso(cattributes["publishAt"]),
"group" : [group["attributes"]["name"]
@@ -96,7 +96,7 @@ class MangadexCoversExtractor(MangadexExtractor):
text.nameext_from_url(name, data)
data["cover_id"] = data["filename"]
yield Message.Directory, "", data
yield Message.Url, f"{base}{name}", data
yield Message.Url, base + name, data
def _transform_cover(self, cover):
relationships = defaultdict(list)
@@ -148,9 +148,9 @@ class MangadexChapterExtractor(MangadexExtractor):
enum = util.enumerate_reversed if self.config(
"page-reverse") else enumerate
for data["page"], page in enum(chapter[key], 1):
text.nameext_from_url(page, data)
yield Message.Url, f"{base}{page}", data
for data["page"], path in enum(chapter[key], 1):
text.nameext_from_url(path, data)
yield Message.Url, base + path, data
class MangadexMangaExtractor(MangadexExtractor):
@@ -253,22 +253,22 @@ class MangadexAPI():
else text.ensure_http_scheme(server).rstrip("/"))
def athome_server(self, uuid):
return self._call(f"/at-home/server/{uuid}")
return self._call("/at-home/server/" + uuid)
def author(self, uuid, manga=False):
params = {"includes[]": ("manga",)} if manga else None
return self._call(f"/author/{uuid}", params)["data"]
return self._call("/author/" + uuid, params)["data"]
def chapter(self, uuid):
params = {"includes[]": ("scanlation_group",)}
return self._call(f"/chapter/{uuid}", params)["data"]
return self._call("/chapter/" + uuid, params)["data"]
def covers_manga(self, uuid):
params = {"manga[]": uuid}
return self._pagination_covers("/cover", params)
def list(self, uuid):
return self._call(f"/list/{uuid}", None, True)["data"]
return self._call("/list/" + uuid, None, True)["data"]
def list_feed(self, uuid):
return self._pagination_chapters(f"/list/{uuid}/feed", None, True)
@@ -276,7 +276,7 @@ class MangadexAPI():
@memcache(keyarg=1)
def manga(self, uuid):
params = {"includes[]": ("artist", "author")}
return self._call(f"/manga/{uuid}", params)["data"]
return self._call("/manga/" + uuid, params)["data"]
def manga_author(self, uuid_author):
params = {"authorOrArtist": uuid_author}
@@ -339,17 +339,17 @@ class MangadexAPI():
_refresh_token_cache.update(
(username, "personal"), data["refresh_token"])
return f"Bearer {access_token}"
return "Bearer " + access_token
@cache(maxage=900, keyarg=1)
def _authenticate_impl_legacy(self, username, password):
if refresh_token := _refresh_token_cache(username):
self.extractor.log.info("Refreshing access token")
url = f"{self.root}/auth/refresh"
url = self.root + "/auth/refresh"
json = {"token": refresh_token}
else:
self.extractor.log.info("Logging in as %s", username)
url = f"{self.root}/auth/login"
url = self.root + "/auth/login"
json = {"username": username, "password": password}
self.extractor.log.debug("Using legacy login method")
@@ -360,10 +360,10 @@ class MangadexAPI():
if refresh_token != data["token"]["refresh"]:
_refresh_token_cache.update(username, data["token"]["refresh"])
return f"Bearer {data['token']['session']}"
return "Bearer " + data["token"]["session"]
def _call(self, endpoint, params=None, auth=False):
url = f"{self.root}{endpoint}"
url = self.root + endpoint
headers = self.headers_auth if auth else self.headers
while True:

View File

@@ -75,7 +75,7 @@ class MangafireMangaExtractor(MangafireBase, MangaExtractor):
chapters = _manga_chapters(self, (manga_id, "chapter", lang))
return [
(f"""{self.root}{text.extr(anchor, 'href="', '"')}""", {
(self.root + text.extr(anchor, 'href="', '"'), {
**manga,
**_chapter_info(anchor),
})
@@ -160,7 +160,7 @@ def _chapter_info(info):
chapter, sep, minor = text.extr(info, 'data-number="', '"').partition(".")
return {
"chapter" : text.parse_int(chapter),
"chapter_minor" : f"{sep}{minor}",
"chapter_minor" : sep + minor,
"chapter_string": chapter_info,
"chapter_id" : text.parse_int(text.extr(info, 'data-id="', '"')),
"title" : text.unescape(text.extr(info, 'title="', '"')),

View File

@@ -28,7 +28,7 @@ class MangahereChapterExtractor(MangahereBase, ChapterExtractor):
def __init__(self, match):
self.part, self.volume, self.chapter = match.groups()
self.base = f"{self.root_mobile}/manga/{self.part}/"
ChapterExtractor.__init__(self, match, f"{self.base}1.html")
ChapterExtractor.__init__(self, match, self.base + "1.html")
def _init(self):
self.session.headers["Referer"] = self.root_mobile + "/"

View File

@@ -138,9 +138,9 @@ def _manga_info(self, manga_path):
current[chap] = {
"title" : name.partition(":")[2].strip(),
"chapter" : text.parse_int(chapter),
"chapter_minor" : f"{sep}{minor}",
"chapter_minor" : sep + minor,
"chapter_string": chap,
"chapter_url" : f"{base}{path}",
"chapter_url" : base + path,
"lang" : lang,
}
@@ -162,7 +162,7 @@ def _manga_info(self, manga_path):
"chapter" : 0,
"chapter_minor" : "",
"chapter_string": voln,
"chapter_url" : f"{base}{path}",
"chapter_url" : base + path,
"lang" : lang,
}

View File

@@ -75,7 +75,7 @@ class MangataroMangaExtractor(MangataroBase, MangaExtractor):
results.append((url, {
**manga,
"chapter" : text.parse_int(chapter),
"chapter_minor": f".{minor}" if sep else "",
"chapter_minor": "." + minor if sep else "",
"chapter_id" : text.parse_int(chapter_id),
}))
return results

View File

@@ -104,7 +104,7 @@ class MoebooruTagExtractor(MoebooruExtractor):
def posts(self):
params = {"tags": self.tags}
return self._pagination(f"{self.root}/post.json", params)
return self._pagination(self.root + "/post.json", params)
class MoebooruPoolExtractor(MoebooruExtractor):
@@ -129,7 +129,7 @@ class MoebooruPoolExtractor(MoebooruExtractor):
def posts(self):
params = {"tags": "pool:" + self.pool_id}
return self._pagination(f"{self.root}/post.json", params)
return self._pagination(self.root + "/post.json", params)
class MoebooruPostExtractor(MoebooruExtractor):
@@ -140,7 +140,7 @@ class MoebooruPostExtractor(MoebooruExtractor):
def posts(self):
params = {"tags": "id:" + self.groups[-1]}
return self.request_json(f"{self.root}/post.json", params=params)
return self.request_json(self.root + "/post.json", params=params)
class MoebooruPopularExtractor(MoebooruExtractor):

View File

@@ -93,8 +93,8 @@ class MotherlessExtractor(Extractor):
title = self._extract_group_title(page, gid)
return {
f"{category}_id": gid,
f"{category}_title": title,
category + "_id": gid,
category + "_title": title,
"uploader": text.remove_html(extr(
f'class="{category}-member-username">', "</")),
"count": text.parse_int(

View File

@@ -142,7 +142,7 @@ class NaverBlogBlogExtractor(NaverBlogBase, Extractor):
)
# setup params for API calls
url = f"{self.root}/PostViewBottomTitleListAsync.nhn"
url = self.root + "/PostViewBottomTitleListAsync.nhn"
params = {
"blogId" : self.blog_id,
"logNo" : post_num or "0",

View File

@@ -510,7 +510,7 @@ class NewgroundsFavoriteExtractor(NewgroundsExtractor):
def _extract_favorites(self, page):
return [
self.root + path
for path in text.extract_iter(page, f'href="{self.root}', '"')
for path in text.extract_iter(page, 'href="' + self.root, '"')
]
@@ -519,7 +519,6 @@ class NewgroundsFollowingExtractor(NewgroundsFavoriteExtractor):
subcategory = "following"
pattern = (USER_PATTERN + r"/favorites/(following)"
r"(?:(?:/page/|/?\?page=)(\d+))?")
example = "https://USER.newgrounds.com/favorites/following"
def items(self):

View File

@@ -140,7 +140,7 @@ class NijieExtractor(AsynchronousMixin, BaseExtractor):
def _login_impl(self, username, password):
self.log.info("Logging in as %s", username)
url = f"{self.root}/login_int.php"
url = self.root + "/login_int.php"
data = {"email": username, "password": password, "save": "on"}
response = self.request(url, method="POST", data=data)

View File

@@ -97,7 +97,7 @@ class PahealTagExtractor(PahealExtractor):
while True:
try:
page = self.request(f"{base}{pnum}").text
page = self.request(base + str(pnum)).text
except exception.HttpError as exc:
if exc.status == 404:
return

View File

@@ -300,8 +300,8 @@ class PatreonExtractor(Extractor):
order = "-published_at"
elif order in {"a", "asc", "r", "reverse"}:
order = "published_at"
return f"&sort={order}"
return f"&sort={sort}" if sort else ""
return "&sort=" + order
return "&sort=" + sort if sort else ""
def _build_file_generators(self, filetypes):
if filetypes is None:
@@ -382,8 +382,8 @@ class PatreonCollectionExtractor(PatreonExtractor):
elif order in {"d", "desc", "r", "reverse"}:
# "-collection_order" results in a '400 Bad Request' error
order = "-published_at"
return f"&sort={order}"
return f"&sort={sort}" if sort else ""
return "&sort=" + order
return "&sort=" + sort if sort else ""
class PatreonCreatorExtractor(PatreonExtractor):

View File

@@ -116,7 +116,7 @@ class PhilomenaGalleryExtractor(PhilomenaExtractor):
raise exception.NotFoundError("gallery")
def posts(self):
gallery_id = f"gallery_id:{self.groups[-1]}"
gallery_id = "gallery_id:" + self.groups[-1]
params = {"sd": "desc", "sf": gallery_id, "q": gallery_id}
return self.api.search(params)

View File

@@ -657,7 +657,7 @@ class PixivFavoriteExtractor(PixivExtractor):
for preview in self.api.user_following(self.user_id, restrict):
user = preview["user"]
user["_extractor"] = PixivUserExtractor
url = f"https://www.pixiv.net/users/{user['id']}"
url = "https://www.pixiv.net/users/" + str(user["id"])
yield Message.Queue, url, user
@@ -1302,7 +1302,7 @@ class PixivAppAPI():
msg = (f"'{msg}'" if (msg := error.get("user_message")) else
f"'{msg}'" if (msg := error.get("message")) else
error)
raise exception.AbortExtraction(f"API request failed: {msg}")
raise exception.AbortExtraction("API request failed: " + msg)
def _pagination(self, endpoint, params,
key_items="illusts", key_data=None, key_user=None):

View File

@@ -54,7 +54,7 @@ class PoipikuExtractor(Extractor):
for post_url in self.posts():
if post_url[0] == "/":
post_url = f"{self.root}{post_url}"
post_url = self.root + post_url
page = self.request(post_url).text
extr = text.extract_from(page)
parts = post_url.rsplit("/", 2)
@@ -148,7 +148,7 @@ class PoipikuExtractor(Extractor):
return files
def _show_illust_detail(self, post):
url = f"{self.root}/f/ShowIllustDetailF.jsp"
url = self.root + "/f/ShowIllustDetailF.jsp"
data = {
"ID" : post["user_id"],
"TD" : post["post_id"],
@@ -160,7 +160,7 @@ class PoipikuExtractor(Extractor):
interval=False)
def _show_append_file(self, post):
url = f"{self.root}/f/ShowAppendFileF.jsp"
url = self.root + "/f/ShowAppendFileF.jsp"
data = {
"UID": post["user_id"],
"IID": post["post_id"],
@@ -183,7 +183,7 @@ class PoipikuUserExtractor(PoipikuExtractor):
def posts(self):
pnum, user_id = self.groups
url = f"{self.root}/IllustListPcV.jsp"
url = self.root + "/IllustListPcV.jsp"
params = {
"PG" : text.parse_int(pnum, 0),
"ID" : user_id,

View File

@@ -65,7 +65,7 @@ class RawkumaMangaExtractor(RawkumaBase, MangaExtractor):
manga = text.unescape(text.extr(page, "<title>", " &#8211; "))
manga_id = text.parse_int(text.extr(page, "manga_id=", "&"))
url = f"{self.root}/wp-admin/admin-ajax.php"
url = self.root + "/wp-admin/admin-ajax.php"
params = {
"manga_id": manga_id,
"page" : "1",

View File

@@ -49,7 +49,7 @@ class RealbooruExtractor(booru.BooruExtractor):
tags.append(tag)
tags_categories[tag_type].append(tag)
for key, value in tags_categories.items():
post[f"tags_{key}"] = ", ".join(value)
post["tags_" + key] = ", ".join(value)
tags.sort()
post["tags"] = ", ".join(tags)

View File

@@ -223,10 +223,10 @@ class RedditExtractor(Extractor):
self.log.debug(src)
elif url := data.get("dashUrl"):
submission["_ytdl_manifest"] = "dash"
yield f"ytdl:{url}"
yield "ytdl:" + url
elif url := data.get("hlsUrl"):
submission["_ytdl_manifest"] = "hls"
yield f"ytdl:{url}"
yield "ytdl:" + url
def _extract_video_ytdl(self, submission):
return "https://www.reddit.com" + submission["permalink"]
@@ -506,7 +506,7 @@ class RedditAPI():
return "Bearer " + data["access_token"]
def _call(self, endpoint, params):
url = f"{self.root}{endpoint}"
url = self.root + endpoint
params["raw_json"] = "1"
while True:

View File

@@ -135,7 +135,7 @@ class RedgifsCollectionsExtractor(RedgifsExtractor):
def items(self):
base = f"{self.root}/users/{self.key}/collections/"
for collection in self.api.collections(self.key):
url = f"{base}{collection['folderId']}"
url = base + collection["folderId"]
collection["_extractor"] = RedgifsCollectionExtractor
yield Message.Queue, url, collection

View File

@@ -120,13 +120,13 @@ class Rule34xyzExtractor(BooruExtractor):
def _login_impl(self, username, password):
self.log.info("Logging in as %s", username)
url = f"{self.root}/api/v2/auth/signin"
url = self.root + "/api/v2/auth/signin"
data = {"email": username, "password": password}
response = self.request_json(
url, method="POST", json=data, fatal=False)
if jwt := response.get("jwt"):
return f"Bearer {jwt}"
return "Bearer " + jwt
raise exception.AuthenticationError(
(msg := response.get("message")) and f'"{msg}"')

View File

@@ -18,7 +18,7 @@ class S3ndpicsExtractor(Extractor):
"""Base class for s3ndpics extractors"""
category = "s3ndpics"
root = "https://s3nd.pics"
root_api = f"{root}/api"
root_api = root + "/api"
directory_fmt = ("{category}", "{user[username]}",
"{date} {title:?/ /}({id})")
filename_fmt = "{num:>02}.{extension}"
@@ -41,7 +41,7 @@ class S3ndpicsExtractor(Extractor):
post["type"] = file["type"]
path = file["url"]
text.nameext_from_url(path, post)
yield Message.Url, f"{base}{path}", post
yield Message.Url, base + path, post
def _pagination(self, url, params):
params["page"] = 1
@@ -76,7 +76,7 @@ class S3ndpicsUserExtractor(S3ndpicsExtractor):
url = f"{self.root_api}/users/username/{self.groups[0]}"
self.kwdict["user"] = user = self.request_json(url)["user"]
url = f"{self.root_api}/posts"
url = self.root_api + "/posts"
params = {
"userId": user["_id"],
"limit" : "12",
@@ -91,7 +91,7 @@ class S3ndpicsSearchExtractor(S3ndpicsExtractor):
example = "https://s3nd.pics/search?QUERY"
def posts(self):
url = f"{self.root_api}/posts"
url = self.root_api + "/posts"
params = text.parse_query(self.groups[0])
params.setdefault("limit", "20")
self.kwdict["search_tags"] = \

View File

@@ -193,7 +193,7 @@ class SankakuBooksExtractor(SankakuExtractor):
params = {"tags": self.tags, "pool_type": "0"}
for pool in self.api.pools_keyset(params):
pool["_extractor"] = SankakuPoolExtractor
url = f"https://sankaku.app/books/{pool['id']}"
url = "https://sankaku.app/books/" + pool["id"]
yield Message.Queue, url, pool

View File

@@ -64,7 +64,7 @@ class SchalenetworkExtractor(Extractor):
def _token(self, required=True):
if token := self.config("token"):
return f"Bearer {token.rpartition(' ')[2]}"
return "Bearer " + token.rpartition(' ')[2]
if required:
raise exception.AuthRequired("'token'", "your favorites")
@@ -172,7 +172,7 @@ class SchalenetworkGalleryExtractor(SchalenetworkExtractor, GalleryExtractor):
if self.config("cbz", False):
headers["Authorization"] = self._token()
dl = self.request_json(
f"{url}&action=dl", method="POST", headers=headers)
url + "&action=dl", method="POST", headers=headers)
# 'crt' parameter here is necessary for 'hdoujin' downloads
url = f"{dl['base']}?crt={self._crt()}"
info = text.nameext_from_url(url)
@@ -259,7 +259,7 @@ class SchalenetworkFavoriteExtractor(SchalenetworkExtractor):
params = text.parse_query(self.groups[1])
params["page"] = text.parse_int(params.get("page"), 1)
self.headers["Authorization"] = self._token()
return self._pagination(f"/books/favorites?crt={self._crt()}", params)
return self._pagination("/books/favorites?crt=" + self._crt(), params)
SchalenetworkExtractor.extr_class = SchalenetworkGalleryExtractor

View File

@@ -282,7 +282,7 @@ class SexcomFeedExtractor(SexcomExtractor):
def pins(self):
if not self.cookies_check(("sess_sex",)):
self.log.warning("no 'sess_sex' cookie set")
url = f"{self.root}/feed/"
url = self.root + "/feed/"
return self._pagination(url)
@@ -341,10 +341,10 @@ class SexcomSearchExtractor(SexcomExtractor):
pin["type"] = "gif"
if gifs and pin["extension"] == "webp":
pin["extension"] = "gif"
pin["_fallback"] = (f"{root}{path}",)
path = f"{path[:-4]}gif"
pin["_fallback"] = (root + path,)
path = path[:-4] + "gif"
pin["url"] = f"{root}{path}"
pin["url"] = root + path
yield Message.Directory, "", pin
yield Message.Url, pin["url"], pin

View File

@@ -58,7 +58,7 @@ class SubscribestarExtractor(Extractor):
text.nameext_from_url(url, item)
if url[0] == "/":
url = f"{self.root}{url}"
url = self.root + url
yield Message.Url, url, item
def posts(self):
@@ -72,7 +72,7 @@ class SubscribestarExtractor(Extractor):
"/verify_subscriber" in response.url or
"/age_confirmation_warning" in response.url):
raise exception.AbortExtraction(
f"HTTP redirect to {response.url}")
"HTTP redirect to " + response.url)
content = response.content
if len(content) < 250 and b">redirected<" in content:

View File

@@ -90,12 +90,12 @@ class ThehentaiworldExtractor(Extractor):
post["tags"] = tags_list = []
for key, value in tags.items():
tags_list.extend(value)
post[f"tags_{key}" if key else "tags_general"] = value
post["tags_" + key if key else "tags_general"] = value
return post
def _pagination(self, endpoint):
base = f"{self.root}{endpoint}"
base = self.root + endpoint
pnum = self.page_start
while True:

View File

@@ -30,7 +30,7 @@ class TsuminoBase():
@cache(maxage=14*86400, keyarg=1)
def _login_impl(self, username, password):
self.log.info("Logging in as %s", username)
url = f"{self.root}/Account/Login"
url = self.root + "/Account/Login"
headers = {"Referer": url}
data = {"Username": username, "Password": password}
@@ -119,9 +119,9 @@ class TsuminoSearchExtractor(TsuminoBase, Extractor):
def galleries(self):
"""Return all gallery results matching 'self.query'"""
url = f"{self.root}/Search/Operate?type=Book"
url = self.root + "/Search/Operate?type=Book"
headers = {
"Referer": f"{self.root}/",
"Referer": self.root + "/",
"X-Requested-With": "XMLHttpRequest",
}
data = {

View File

@@ -32,7 +32,7 @@ class TumblrExtractor(Extractor):
def _init(self):
if name := self.groups[1]:
self.blog = f"{name}.tumblr.com"
self.blog = name + ".tumblr.com"
else:
self.blog = self.groups[0] or self.groups[2]

View File

@@ -730,19 +730,19 @@ class TwitterUserExtractor(Dispatch, TwitterExtractor):
def items(self):
user, user_id = self.groups
if user_id is not None:
user = f"id:{user_id}"
user = "id:" + user_id
base = f"{self.root}/{user}/"
return self._dispatch_extractors((
(TwitterInfoExtractor , f"{base}info"),
(TwitterAvatarExtractor , f"{base}photo"),
(TwitterBackgroundExtractor, f"{base}header_photo"),
(TwitterTimelineExtractor , f"{base}timeline"),
(TwitterTweetsExtractor , f"{base}tweets"),
(TwitterMediaExtractor , f"{base}media"),
(TwitterRepliesExtractor , f"{base}with_replies"),
(TwitterHighlightsExtractor, f"{base}highlights"),
(TwitterLikesExtractor , f"{base}likes"),
(TwitterInfoExtractor , base + "info"),
(TwitterAvatarExtractor , base + "photo"),
(TwitterBackgroundExtractor, base + "header_photo"),
(TwitterTimelineExtractor , base + "timeline"),
(TwitterTweetsExtractor , base + "tweets"),
(TwitterMediaExtractor , base + "media"),
(TwitterRepliesExtractor , base + "with_replies"),
(TwitterHighlightsExtractor, base + "highlights"),
(TwitterLikesExtractor , base + "likes"),
), ("timeline",))
@@ -1990,10 +1990,10 @@ class TwitterAPI():
extr.log.info("Retrying API request as guest")
continue
raise exception.AuthorizationError(
f"{user['screen_name']} blocked your account")
user["screen_name"] + " blocked your account")
elif user.get("protected"):
raise exception.AuthorizationError(
f"{user['screen_name']}'s Tweets are protected")
user["screen_name"] + "'s Tweets are protected")
raise exception.AbortExtraction(
"Unable to retrieve Tweets from this timeline")
@@ -2042,7 +2042,7 @@ class TwitterAPI():
pinned = None
elif pinned := extr._user_obj["legacy"].get(
"pinned_tweet_ids_str"):
pinned = f"-tweet-{pinned[0]}"
pinned = "-tweet-" + pinned[0]
for idx, entry in enumerate(tweets):
if entry["entryId"].endswith(pinned):
# mark as pinned / set 'pinned = True'
@@ -2248,7 +2248,7 @@ class TwitterAPI():
def _update_variables_search(self, variables, cursor, tweet):
try:
tweet_id = tweet.get("id_str") or tweet["legacy"]["id_str"]
max_id = f"max_id:{int(tweet_id)-1}"
max_id = "max_id:" + str(int(tweet_id)-1)
query, n = text.re(r"\bmax_id:\d+").subn(
max_id, variables["rawQuery"])

View File

@@ -94,7 +94,7 @@ class VipergirlsExtractor(Extractor):
def _login_impl(self, username, password):
self.log.info("Logging in as %s", username)
url = f"{self.root}/login.php?do=login"
url = self.root + "/login.php?do=login"
data = {
"vb_login_username": username,
"vb_login_password": password,

View File

@@ -101,7 +101,7 @@ class VkExtractor(Extractor):
url, method="POST", headers=headers, data=data)
if response.history and "/challenge.html" in response.url:
raise exception.AbortExtraction(
f"HTTP redirect to 'challenge' page:\n{response.url}")
"HTTP redirect to 'challenge' page:\n" + response.url)
payload = response.json()["payload"][1]
if len(payload) < 4:
@@ -236,7 +236,7 @@ class VkTaggedExtractor(VkExtractor):
self.user_id = match[1]
def photos(self):
return self._pagination(f"tag{self.user_id}")
return self._pagination("tag" + self.user_id)
def metadata(self):
return {"user": {"id": self.user_id}}

View File

@@ -158,7 +158,7 @@ class VscoGalleryExtractor(VscoExtractor):
tkn = data["users"]["currentUser"]["tkn"]
sid = str(data["sites"]["siteByUsername"][self.user]["site"]["id"])
url = f"{self.root}/api/3.0/medias/profile"
url = self.root + "/api/3.0/medias/profile"
params = {
"site_id" : sid,
"limit" : "14",

View File

@@ -113,7 +113,7 @@ class WallhavenCollectionsExtractor(WallhavenExtractor):
base = f"{self.root}/user/{self.username}/favorites/"
for collection in self.api.collections(self.username):
collection["_extractor"] = WallhavenCollectionExtractor
url = f"{base}{collection['id']}"
url = base + str(collection["id"])
yield Message.Queue, url, collection

View File

@@ -114,13 +114,13 @@ class WeiboExtractor(Extractor):
if not url:
continue
if url.startswith("http:"):
url = f"https:{url[5:]}"
url = "https:" + url[5:]
if "filename" not in file:
text.nameext_from_url(url, file)
if file["extension"] == "json":
file["extension"] = "mp4"
if file["extension"] == "m3u8":
url = f"ytdl:{url}"
url = "ytdl:" + url
file["_ytdl_manifest"] = "hls"
file["extension"] = "mp4"
num += 1
@@ -307,11 +307,11 @@ class WeiboUserExtractor(WeiboExtractor):
def items(self):
base = f"{self.root}/u/{self._user_id()}?tabtype="
return Dispatch._dispatch_extractors(self, (
(WeiboHomeExtractor , f"{base}home"),
(WeiboFeedExtractor , f"{base}feed"),
(WeiboVideosExtractor , f"{base}video"),
(WeiboNewvideoExtractor, f"{base}newVideo"),
(WeiboAlbumExtractor , f"{base}album"),
(WeiboHomeExtractor , base + "home"),
(WeiboFeedExtractor , base + "feed"),
(WeiboVideosExtractor , base + "video"),
(WeiboNewvideoExtractor, base + "newVideo"),
(WeiboAlbumExtractor , base + "album"),
), ("feed",))

View File

@@ -47,7 +47,7 @@ class WikimediaExtractor(BaseExtractor):
def _init(self):
if api_path := self.config_instance("api-path"):
if api_path[0] == "/":
self.api_url = f"{self.root}{api_path}"
self.api_url = self.root + api_path
else:
self.api_url = api_path
else:
@@ -66,7 +66,7 @@ class WikimediaExtractor(BaseExtractor):
def _search_api_path(self, root):
self.log.debug("Probing possible API endpoints")
for path in ("/api.php", "/w/api.php", "/wiki/api.php"):
url = f"{root}{path}"
url = root + path
response = self.request(url, method="HEAD", fatal=None)
if response.status_code < 400:
return url
@@ -122,10 +122,10 @@ class WikimediaExtractor(BaseExtractor):
yield Message.Url, image["url"], image
if self.subcategories:
base = f"{self.root}/wiki/"
base = self.root + "/wiki/"
params["gcmtype"] = "subcat"
for subcat in self._pagination(params):
url = f"{base}{subcat['title'].replace(' ', '_')}"
url = base + subcat["title"].replace(" ", "_")
subcat["_extractor"] = WikimediaArticleExtractor
yield Message.Queue, url, subcat

View File

@@ -118,7 +118,7 @@ class XenforoExtractor(BaseExtractor):
def _login_impl(self, username, password):
self.log.info("Logging in as %s", username)
url = f"{self.root}/login/login"
url = self.root + "/login/login"
page = self.request(url).text
data = {
"_xfToken": text.extr(page, 'name="_xfToken" value="', '"'),
@@ -140,10 +140,10 @@ class XenforoExtractor(BaseExtractor):
}
def _pagination(self, base, pnum=None):
base = f"{self.root}{base}"
base = self.root + base
if pnum is None:
url = f"{base}/"
url = base + "/"
pnum = 1
else:
url = f"{base}/page-{pnum}"
@@ -160,7 +160,7 @@ class XenforoExtractor(BaseExtractor):
url = f"{base}/page-{pnum}"
def _pagination_reverse(self, base, pnum=None):
base = f"{self.root}{base}"
base = self.root + base
url = f"{base}/page-{'9999' if pnum is None else pnum}"
with self.request_page(url) as response:
@@ -180,7 +180,7 @@ class XenforoExtractor(BaseExtractor):
if pnum > 1:
url = f"{base}/page-{pnum}"
elif pnum == 1:
url = f"{base}/"
url = base + "/"
else:
return
@@ -345,4 +345,4 @@ class XenforoForumExtractor(XenforoExtractor):
pnum = self.groups[-1]
for page in self._pagination(path, pnum):
for path in extract_threads(page):
yield Message.Queue, f"{self.root}{text.unquote(path)}", data
yield Message.Queue, self.root + text.unquote(path), data

View File

@@ -117,5 +117,5 @@ class XvideosUserExtractor(XvideosBase, Extractor):
base = f"{self.root}/profiles/{self.user}/photos/"
for gallery in galleries:
url = f"{base}{gallery['id']}"
url = base + str(gallery["id"])
yield Message.Queue, url, gallery