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): def __init__(self, match):
tld = match[1] 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) Extractor.__init__(self, match)
def items(self): def items(self):
@@ -71,14 +71,14 @@ class _2chBoardExtractor(Extractor):
def __init__(self, match): def __init__(self, match):
tld = match[1] 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) Extractor.__init__(self, match)
def items(self): def items(self):
base = f"{self.root}/{self.groups[1]}" base = f"{self.root}/{self.groups[1]}"
# index page # index page
url = f"{base}/index.json" url = base + "/index.json"
index = self.request_json(url) index = self.request_json(url)
index["_extractor"] = _2chThreadExtractor index["_extractor"] = _2chThreadExtractor
for thread in index["threads"]: for thread in index["threads"]:

View File

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

View File

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

View File

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

View File

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

View File

@@ -146,7 +146,7 @@ class BilibiliAPI():
except Exception: except Exception:
if "window._riskdata_" not in page: if "window._riskdata_" not in page:
raise exception.AbortExtraction( 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) self.extractor.wait(seconds=300)
def user_favlist(self): def user_favlist(self):

View File

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

View File

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

View File

@@ -189,7 +189,7 @@ class BunkrAlbumExtractor(LolisafeAlbumExtractor):
json={"id": data_id}) json={"id": data_id})
if data.get("encrypted"): 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()) file_url = util.decrypt_xor(data["url"], key.encode())
else: else:
file_url = data["url"] file_url = data["url"]
@@ -221,7 +221,7 @@ class BunkrMediaExtractor(BunkrAlbumExtractor):
def fetch_album(self, album_id): def fetch_album(self, album_id):
try: 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="', '"') data_id = text.extr(page, 'data-file-id="', '"')
file = self._extract_file(data_id) file = self._extract_file(data_id)
file["name"] = text.unquote(text.unescape(text.extr( file["name"] = text.unquote(text.unescape(text.extr(

View File

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

View File

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

View File

@@ -766,7 +766,7 @@ class GalleryExtractor(Extractor):
Extractor.__init__(self, match) Extractor.__init__(self, match)
if url is None and (path := self.groups[0]) and path[0] == "/": 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: else:
self.page_url = url self.page_url = url
@@ -863,7 +863,7 @@ class MangaExtractor(Extractor):
Extractor.__init__(self, match) Extractor.__init__(self, match)
if url is None and (path := self.groups[0]) and path[0] == "/": 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: else:
self.page_url = url self.page_url = url

View File

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

View File

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

View File

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

View File

@@ -34,7 +34,7 @@ class DankefuerslesenChapterExtractor(DankefuerslesenBase, ChapterExtractor):
def _init(self): def _init(self):
self.zip = self.config("zip", False) self.zip = self.config("zip", False)
if self.zip: 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] self.directory_fmt = self.directory_fmt[:-1]
def metadata(self, page): def metadata(self, page):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -456,7 +456,7 @@ class FlickrAPI(oauth.OAuth1API):
except ValueError: except ValueError:
data = {"code": -1, "message": response.content} data = {"code": -1, "message": response.content}
if "code" in data: if "code" in data:
msg = data.get("message") msg = data.get("message", "")
self.log.debug("Server response: %s", data) self.log.debug("Server response: %s", data)
if data["code"] == 1: if data["code"] == 1:
raise exception.NotFoundError(self.extractor.subcategory) raise exception.NotFoundError(self.extractor.subcategory)
@@ -466,7 +466,7 @@ class FlickrAPI(oauth.OAuth1API):
raise exception.AuthenticationError(msg) raise exception.AuthenticationError(msg)
elif data["code"] == 99: elif data["code"] == 99:
raise exception.AuthorizationError(msg) raise exception.AuthorizationError(msg)
raise exception.AbortExtraction(f"API request failed: {msg}") raise exception.AbortExtraction("API request failed: " + msg)
return data return data
def _pagination(self, method, params, key="photos"): 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=" base = f"{self.root}/_/api/chan/gallery/?board={self.board}&page="
for pnum in pages: for pnum in pages:
posts = self.request_json(f"{base}{pnum}") posts = self.request_json(base + str(pnum))
if not posts: if not posts:
return return
yield from posts yield from posts

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -85,7 +85,7 @@ class MadokamiMangaExtractor(MadokamiExtractor):
else: else:
ch["volume"] = ch["chapter"] = ch["chapter_end"] = 0 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) text.nameext_from_url(url, ch)
yield Message.Directory, "", ch yield Message.Directory, "", ch

View File

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

View File

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

View File

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

View File

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

View File

@@ -75,7 +75,7 @@ class MangataroMangaExtractor(MangataroBase, MangaExtractor):
results.append((url, { results.append((url, {
**manga, **manga,
"chapter" : text.parse_int(chapter), "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), "chapter_id" : text.parse_int(chapter_id),
})) }))
return results return results

View File

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

View File

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

View File

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

View File

@@ -510,7 +510,7 @@ class NewgroundsFavoriteExtractor(NewgroundsExtractor):
def _extract_favorites(self, page): def _extract_favorites(self, page):
return [ return [
self.root + path 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" subcategory = "following"
pattern = (USER_PATTERN + r"/favorites/(following)" pattern = (USER_PATTERN + r"/favorites/(following)"
r"(?:(?:/page/|/?\?page=)(\d+))?") r"(?:(?:/page/|/?\?page=)(\d+))?")
example = "https://USER.newgrounds.com/favorites/following" example = "https://USER.newgrounds.com/favorites/following"
def items(self): def items(self):

View File

@@ -140,7 +140,7 @@ class NijieExtractor(AsynchronousMixin, BaseExtractor):
def _login_impl(self, username, password): def _login_impl(self, username, password):
self.log.info("Logging in as %s", username) 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"} data = {"email": username, "password": password, "save": "on"}
response = self.request(url, method="POST", data=data) response = self.request(url, method="POST", data=data)

View File

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

View File

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

View File

@@ -116,7 +116,7 @@ class PhilomenaGalleryExtractor(PhilomenaExtractor):
raise exception.NotFoundError("gallery") raise exception.NotFoundError("gallery")
def posts(self): 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} params = {"sd": "desc", "sf": gallery_id, "q": gallery_id}
return self.api.search(params) 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): for preview in self.api.user_following(self.user_id, restrict):
user = preview["user"] user = preview["user"]
user["_extractor"] = PixivUserExtractor 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 yield Message.Queue, url, user
@@ -1302,7 +1302,7 @@ class PixivAppAPI():
msg = (f"'{msg}'" if (msg := error.get("user_message")) else msg = (f"'{msg}'" if (msg := error.get("user_message")) else
f"'{msg}'" if (msg := error.get("message")) else f"'{msg}'" if (msg := error.get("message")) else
error) error)
raise exception.AbortExtraction(f"API request failed: {msg}") raise exception.AbortExtraction("API request failed: " + msg)
def _pagination(self, endpoint, params, def _pagination(self, endpoint, params,
key_items="illusts", key_data=None, key_user=None): key_items="illusts", key_data=None, key_user=None):

View File

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

View File

@@ -65,7 +65,7 @@ class RawkumaMangaExtractor(RawkumaBase, MangaExtractor):
manga = text.unescape(text.extr(page, "<title>", " &#8211; ")) manga = text.unescape(text.extr(page, "<title>", " &#8211; "))
manga_id = text.parse_int(text.extr(page, "manga_id=", "&")) 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 = { params = {
"manga_id": manga_id, "manga_id": manga_id,
"page" : "1", "page" : "1",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -90,12 +90,12 @@ class ThehentaiworldExtractor(Extractor):
post["tags"] = tags_list = [] post["tags"] = tags_list = []
for key, value in tags.items(): for key, value in tags.items():
tags_list.extend(value) 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 return post
def _pagination(self, endpoint): def _pagination(self, endpoint):
base = f"{self.root}{endpoint}" base = self.root + endpoint
pnum = self.page_start pnum = self.page_start
while True: while True:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -158,7 +158,7 @@ class VscoGalleryExtractor(VscoExtractor):
tkn = data["users"]["currentUser"]["tkn"] tkn = data["users"]["currentUser"]["tkn"]
sid = str(data["sites"]["siteByUsername"][self.user]["site"]["id"]) 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 = { params = {
"site_id" : sid, "site_id" : sid,
"limit" : "14", "limit" : "14",

View File

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

View File

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

View File

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

View File

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