rename 'StopExtraction' to 'AbortExtraction'

for cases where StopExtraction was used to report errors
This commit is contained in:
Mike Fährmann
2025-07-09 21:07:28 +02:00
parent f17ed0569a
commit d8ef1d693f
57 changed files with 149 additions and 156 deletions

View File

@@ -351,13 +351,11 @@ def main():
else: else:
input_manager.success() input_manager.success()
except exception.StopExtraction:
pass
except exception.TerminateExtraction:
pass
except exception.RestartExtraction: except exception.RestartExtraction:
log.debug("Restarting '%s'", url) log.debug("Restarting '%s'", url)
continue continue
except exception.ControlException:
pass
except exception.NoExtractorError: except exception.NoExtractorError:
log.error("Unsupported URL '%s'", url) log.error("Unsupported URL '%s'", url)
retval |= 64 retval |= 64

View File

@@ -47,6 +47,7 @@ class GalleryDLException(Exception):
message = f"{message.__class__.__name__}: {message}" message = f"{message.__class__.__name__}: {message}"
if fmt and self.msgfmt is not None: if fmt and self.msgfmt is not None:
message = self.msgfmt.replace("{}", message) message = self.msgfmt.replace("{}", message)
self.message = message
Exception.__init__(self, message) Exception.__init__(self, message)
@@ -151,11 +152,6 @@ class ControlException(GalleryDLException):
class StopExtraction(ControlException): class StopExtraction(ControlException):
"""Stop data extraction""" """Stop data extraction"""
def __init__(self, message=None, *args):
ControlException.__init__(self)
self.message = message % args if args else message
self.code = 1 if message else 0
class AbortExtraction(ExtractionError, ControlException): class AbortExtraction(ExtractionError, ControlException):
"""Abort data extraction due to an error""" """Abort data extraction due to an error"""

View File

@@ -144,7 +144,7 @@ class Ao3WorkExtractor(Ao3Extractor):
page = response.text page = response.text
if len(page) < 20000 and \ if len(page) < 20000 and \
'<h2 class="landmark heading">Adult Content Warning</' in page: '<h2 class="landmark heading">Adult Content Warning</' in page:
raise exception.StopExtraction("Adult Content") raise exception.AbortExtraction("Adult Content")
extr = text.extract_from(page) extr = text.extract_from(page)

View File

@@ -171,9 +171,8 @@ class ArcaliveAPI():
return data return data
self.log.debug("Server response: %s", data) self.log.debug("Server response: %s", data)
msg = data.get("message") msg = f": {msg}" if (msg := data.get("message")) else ""
raise exception.StopExtraction( raise exception.AbortExtraction(f"API request failed{msg}")
"API request failed%s", ": " + msg if msg else "")
def _pagination(self, endpoint, params, key): def _pagination(self, endpoint, params, key):
while True: while True:

View File

@@ -112,7 +112,7 @@ class BilibiliAPI():
if data["code"] != 0: if data["code"] != 0:
self.extractor.log.debug("Server response: %s", data) self.extractor.log.debug("Server response: %s", data)
raise exception.StopExtraction("API request failed") raise exception.AbortExtraction("API request failed")
return data return data
@@ -140,8 +140,8 @@ class BilibiliAPI():
page, "window.__INITIAL_STATE__=", "};") + "}") page, "window.__INITIAL_STATE__=", "};") + "}")
except Exception: except Exception:
if "window._riskdata_" not in page: if "window._riskdata_" not in page:
raise exception.StopExtraction( raise exception.AbortExtraction(
"%s: Unable to extract INITIAL_STATE data", article_id) f"{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):
@@ -163,8 +163,9 @@ class BilibiliAPI():
if data["code"] != 0: if data["code"] != 0:
self.extractor.log.debug("Server response: %s", data) self.extractor.log.debug("Server response: %s", data)
raise exception.StopExtraction("API request failed,Are you login?") raise exception.AbortExtraction(
"API request failed. Are you logges in?")
try: try:
return data["data"]["profile"]["mid"] return data["data"]["profile"]["mid"]
except Exception: except Exception:
raise exception.StopExtraction("API request failed") raise exception.AbortExtraction("API request failed")

View File

@@ -95,7 +95,7 @@ class BlueskyExtractor(Extractor):
uri = record["value"]["subject"]["uri"] uri = record["value"]["subject"]["uri"]
if "/app.bsky.feed.post/" in uri: if "/app.bsky.feed.post/" in uri:
yield from self.api.get_post_thread_uri(uri, depth) yield from self.api.get_post_thread_uri(uri, depth)
except exception.StopExtraction: except exception.ControlException:
pass # deleted post pass # deleted post
except Exception as exc: except Exception as exc:
self.log.debug(record, exc_info=exc) self.log.debug(record, exc_info=exc)
@@ -579,7 +579,7 @@ class BlueskyAPI():
msg = f"{msg} ({response.status_code} {response.reason})" msg = f"{msg} ({response.status_code} {response.reason})"
self.extractor.log.debug("Server response: %s", response.text) self.extractor.log.debug("Server response: %s", response.text)
raise exception.StopExtraction(msg) raise exception.AbortExtraction(msg)
def _pagination(self, endpoint, params, def _pagination(self, endpoint, params,
key="feed", root=None, check_empty=False): key="feed", root=None, check_empty=False):

View File

@@ -381,7 +381,7 @@ class BoostyAPI():
else: else:
self.extractor.log.debug(response.text) self.extractor.log.debug(response.text)
raise exception.StopExtraction("API request failed") raise exception.AbortExtraction("API request failed")
def _pagination(self, endpoint, params, transform=None, key=None): def _pagination(self, endpoint, params, transform=None, key=None):
if "is_only_allowed" not in params and self.extractor.only_allowed: if "is_only_allowed" not in params and self.extractor.only_allowed:

View File

@@ -124,7 +124,7 @@ class BunkrAlbumExtractor(LolisafeAlbumExtractor):
pass pass
else: else:
if not DOMAINS: if not DOMAINS:
raise exception.StopExtraction( raise exception.AbortExtraction(
"All Bunkr domains require solving a CF challenge") "All Bunkr domains require solving a CF challenge")
# select alternative domain # select alternative domain
@@ -169,7 +169,7 @@ class BunkrAlbumExtractor(LolisafeAlbumExtractor):
info[-1], "%H:%M:%S %d/%m/%Y") info[-1], "%H:%M:%S %d/%m/%Y")
yield file yield file
except exception.StopExtraction: except exception.ControlException:
raise raise
except Exception as exc: except Exception as exc:
self.log.error("%s: %s", exc.__class__.__name__, exc) self.log.error("%s: %s", exc.__class__.__name__, exc)

View File

@@ -336,8 +336,8 @@ class Extractor():
if input is None: if input is None:
input = output.TTY_STDIN input = output.TTY_STDIN
if not input: if not input:
raise exception.StopExtraction( raise exception.AbortExtraction(
"User input required (%s)", prompt.strip(" :")) f"User input required ({prompt.strip(' :')})")
def _get_auth_info(self): def _get_auth_info(self):
"""Return authentication information as (username, password) tuple""" """Return authentication information as (username, password) tuple"""

View File

@@ -126,7 +126,7 @@ class DeviantartExtractor(Extractor):
self.group = False self.group = False
elif group == "skip": elif group == "skip":
self.log.info("Skipping group '%s'", self.user) self.log.info("Skipping group '%s'", self.user)
raise exception.StopExtraction() raise exception.AbortExtraction()
else: else:
self.subcategory = "group-" + self.subcategory self.subcategory = "group-" + self.subcategory
self.group = True self.group = True
@@ -1373,7 +1373,7 @@ class DeviantartSearchExtractor(DeviantartExtractor):
response = self.request(url, params=params) response = self.request(url, params=params)
if response.history and "/users/login" in response.url: if response.history and "/users/login" in response.url:
raise exception.StopExtraction("HTTP redirect to login page") raise exception.AbortExtraction("HTTP redirect to login page")
page = response.text page = response.text
for dev in DeviantartDeviationExtractor.pattern.findall( for dev in DeviantartDeviationExtractor.pattern.findall(

View File

@@ -165,7 +165,7 @@ class DiscordExtractor(Extractor):
yield from self.extract_channel( yield from self.extract_channel(
channel["channel_id"], safe=True) channel["channel_id"], safe=True)
elif not safe: elif not safe:
raise exception.StopExtraction( raise exception.AbortExtraction(
"This channel type is not supported." "This channel type is not supported."
) )
except exception.HttpError as exc: except exception.HttpError as exc:

View File

@@ -59,7 +59,7 @@ class ExhentaiExtractor(Extractor):
def login(self): def login(self):
"""Login and set necessary cookies""" """Login and set necessary cookies"""
if self.LIMIT: if self.LIMIT:
raise exception.StopExtraction("Image limit reached!") raise exception.AbortExtraction("Image limit reached!")
if self.cookies_check(self.cookies_names): if self.cookies_check(self.cookies_names):
return return
@@ -178,7 +178,7 @@ class ExhentaiGalleryExtractor(ExhentaiExtractor):
self.image_token = text.extr(gpage, 'hentai.org/s/', '"') self.image_token = text.extr(gpage, 'hentai.org/s/', '"')
if not self.image_token: if not self.image_token:
self.log.debug("Page content:\n%s", gpage) self.log.debug("Page content:\n%s", gpage)
raise exception.StopExtraction( raise exception.AbortExtraction(
"Failed to extract initial image token") "Failed to extract initial image token")
ipage = self._image_page() ipage = self._image_page()
else: else:
@@ -186,7 +186,7 @@ class ExhentaiGalleryExtractor(ExhentaiExtractor):
part = text.extr(ipage, 'hentai.org/g/', '"') part = text.extr(ipage, 'hentai.org/g/', '"')
if not part: if not part:
self.log.debug("Page content:\n%s", ipage) self.log.debug("Page content:\n%s", ipage)
raise exception.StopExtraction( raise exception.AbortExtraction(
"Failed to extract gallery token") "Failed to extract gallery token")
self.gallery_token = part.split("/")[1] self.gallery_token = part.split("/")[1]
gpage = self._gallery_page() gpage = self._gallery_page()
@@ -301,7 +301,7 @@ class ExhentaiGalleryExtractor(ExhentaiExtractor):
data = self.request_json(self.api_url, method="POST", json=data) data = self.request_json(self.api_url, method="POST", json=data)
if "error" in data: if "error" in data:
raise exception.StopExtraction(data["error"]) raise exception.AbortExtraction(data["error"])
return data["gmetadata"][0] return data["gmetadata"][0]
@@ -326,8 +326,8 @@ class ExhentaiGalleryExtractor(ExhentaiExtractor):
data["_fallback"] = self._fallback_1280(nl, self.image_num) data["_fallback"] = self._fallback_1280(nl, self.image_num)
except IndexError: except IndexError:
self.log.debug("Page content:\n%s", page) self.log.debug("Page content:\n%s", page)
raise exception.StopExtraction( raise exception.AbortExtraction(
"Unable to parse image info for '%s'", url) f"Unable to parse image info for '{url}'")
data["num"] = self.image_num data["num"] = self.image_num
data["image_token"] = self.key_start = extr('var startkey="', '";') data["image_token"] = self.key_start = extr('var startkey="', '";')
@@ -377,8 +377,8 @@ class ExhentaiGalleryExtractor(ExhentaiExtractor):
nl, request["page"], imgkey) nl, request["page"], imgkey)
except IndexError: except IndexError:
self.log.debug("Page content:\n%s", page) self.log.debug("Page content:\n%s", page)
raise exception.StopExtraction( raise exception.AbortExtraction(
"Unable to parse image info for '%s'", url) f"Unable to parse image info for '{url}'")
data["num"] = request["page"] data["num"] = request["page"]
data["image_token"] = imgkey data["image_token"] = imgkey
@@ -401,7 +401,7 @@ class ExhentaiGalleryExtractor(ExhentaiExtractor):
if " requires GP" in page: if " requires GP" in page:
gp = self.config("gp") gp = self.config("gp")
if gp == "stop": if gp == "stop":
raise exception.StopExtraction("Not enough GP") raise exception.AbortExtraction("Not enough GP")
elif gp == "wait": elif gp == "wait":
self.input("Press ENTER to continue.") self.input("Press ENTER to continue.")
return response.url return response.url
@@ -463,7 +463,7 @@ class ExhentaiGalleryExtractor(ExhentaiExtractor):
if not action or action == "stop": if not action or action == "stop":
ExhentaiExtractor.LIMIT = True ExhentaiExtractor.LIMIT = True
raise exception.StopExtraction(msg) raise exception.AbortExtraction(msg)
self.log.warning(msg) self.log.warning(msg)
if action == "wait": if action == "wait":

View File

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

View File

@@ -451,7 +451,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.StopExtraction("API request failed: %s", msg) raise exception.AbortExtraction(f"API request failed: {msg}")
return data return data
def _pagination(self, method, params, key="photos"): def _pagination(self, method, params, key="photos"):

View File

@@ -95,7 +95,7 @@ class GofileFolderExtractor(Extractor):
raise exception.NotFoundError("content") raise exception.NotFoundError("content")
if response["status"] == "error-passwordRequired": if response["status"] == "error-passwordRequired":
raise exception.AuthorizationError("Password required") raise exception.AuthorizationError("Password required")
raise exception.StopExtraction( raise exception.AbortExtraction(
"%s failed (Status: %s)", endpoint, response["status"]) f"{endpoint} failed (Status: {response['status']})")
return response["data"] return response["data"]

View File

@@ -184,7 +184,7 @@ class IdolcomplexTagExtractor(IdolcomplexExtractor):
tags = self.tags.split() tags = self.tags.split()
if not self.logged_in and len(tags) > 4: if not self.logged_in and len(tags) > 4:
raise exception.StopExtraction( raise exception.AbortExtraction(
"Non-members can only search up to 4 tags at once") "Non-members can only search up to 4 tags at once")
return {"search_tags": " ".join(tags)} return {"search_tags": " ".join(tags)}

View File

@@ -152,4 +152,4 @@ class ImagechestAPI():
else: else:
self.extractor.log.debug(response.text) self.extractor.log.debug(response.text)
raise exception.StopExtraction("API request failed") raise exception.AbortExtraction("API request failed")

View File

@@ -31,7 +31,7 @@ class ImagefapExtractor(Extractor):
msg = text.extr(response.text, '<div class="mt-4', '<') msg = text.extr(response.text, '<div class="mt-4', '<')
if msg: if msg:
msg = " ".join(msg.partition(">")[2].split()) msg = " ".join(msg.partition(">")[2].split())
raise exception.StopExtraction("'%s'", msg) raise exception.AbortExtraction(f"'{msg}'")
self.log.warning("HTTP redirect to %s", response.url) self.log.warning("HTTP redirect to %s", response.url)
return response return response

View File

@@ -350,7 +350,7 @@ class InkbunnyAPI():
self.authenticate(invalidate=True) self.authenticate(invalidate=True)
continue continue
raise exception.StopExtraction(data.get("error_message")) raise exception.AbortExtraction(data.get("error_message"))
def _pagination_search(self, params): def _pagination_search(self, params):
params["page"] = 1 params["page"] = 1

View File

@@ -136,9 +136,9 @@ class InstagramExtractor(Extractor):
else: else:
page = None page = None
if page: if page is not None:
raise exception.StopExtraction("HTTP redirect to %s page (%s)", raise exception.AbortExtraction(
page, url.partition("?")[0]) f"HTTP redirect to {page} page ({url.partition('?')[0]})")
www_claim = response.headers.get("x-ig-set-www-claim") www_claim = response.headers.get("x-ig-set-www-claim")
if www_claim is not None: if www_claim is not None:
@@ -979,7 +979,7 @@ class InstagramGraphqlAPI():
self.user_id = api.user_id self.user_id = api.user_id
def _unsupported(self, _=None): def _unsupported(self, _=None):
raise exception.StopExtraction("Unsupported with GraphQL API") raise exception.AbortExtraction("Unsupported with GraphQL API")
def highlights_tray(self, user_id): def highlights_tray(self, user_id):
query_hash = "d4d88dc1500312af6f937f7b804c68c3" query_hash = "d4d88dc1500312af6f937f7b804c68c3"
@@ -1065,9 +1065,10 @@ class InstagramGraphqlAPI():
if not info["has_next_page"]: if not info["has_next_page"]:
return extr._update_cursor(None) return extr._update_cursor(None)
elif not data["edges"]: elif not data["edges"]:
s = "" if self.extractor.item.endswith("s") else "s" user = self.extractor.item
raise exception.StopExtraction( s = "" if user.endswith("s") else "s"
"%s'%s posts are private", self.extractor.item, s) raise exception.AbortExtraction(
f"{user}'{s} posts are private")
variables["after"] = extr._update_cursor(info["end_cursor"]) variables["after"] = extr._update_cursor(info["end_cursor"])

View File

@@ -136,7 +136,7 @@ class LofterAPI():
if info["meta"]["status"] != 200: if info["meta"]["status"] != 200:
self.extractor.log.debug("Server response: %s", info) self.extractor.log.debug("Server response: %s", info)
raise exception.StopExtraction("API request failed") raise exception.AbortExtraction("API request failed")
return info["response"] return info["response"]

View File

@@ -32,9 +32,9 @@ class LusciousExtractor(Extractor):
if response.status_code >= 400: if response.status_code >= 400:
self.log.debug("Server response: %s", response.text) self.log.debug("Server response: %s", response.text)
raise exception.StopExtraction( raise exception.AbortExtraction(
"GraphQL query failed ('%s %s')", f"GraphQL query failed "
response.status_code, response.reason) f"('{response.status_code} {response.reason}')")
return response.json()["data"] return response.json()["data"]

View File

@@ -112,10 +112,10 @@ class MangadexChapterExtractor(MangadexExtractor):
data = self._transform(chapter) data = self._transform(chapter)
if data.get("_external_url") and not data["count"]: if data.get("_external_url") and not data["count"]:
raise exception.StopExtraction( raise exception.AbortExtraction(
"Chapter %s%s is not available on MangaDex and can instead be " f"Chapter {data['chapter']}{data['chapter_minor']} is not "
"read on the official publisher's website at %s.", f"available on MangaDex and can instead be read on the "
data["chapter"], data["chapter_minor"], data["_external_url"]) f"official publisher's website at {data['_external_url']}.")
yield Message.Directory, data yield Message.Directory, data
@@ -356,8 +356,8 @@ class MangadexAPI():
msg = ", ".join('{title}: "{detail}"'.format_map(error) msg = ", ".join('{title}: "{detail}"'.format_map(error)
for error in response.json()["errors"]) for error in response.json()["errors"])
raise exception.StopExtraction( raise exception.AbortExtraction(
"%s %s (%s)", response.status_code, response.reason, msg) f"{response.status_code} {response.reason} ({msg})")
def _pagination_chapters(self, endpoint, params=None, auth=False): def _pagination_chapters(self, endpoint, params=None, auth=False):
if params is None: if params is None:

View File

@@ -176,8 +176,8 @@ class MangaparkMangaExtractor(MangaparkBase, Extractor):
not lang or data["lang"] == lang): not lang or data["lang"] == lang):
return data["id"] return data["id"]
raise exception.StopExtraction( raise exception.AbortExtraction(
"'%s' does not match any available source", source) f"'{source}' does not match any available source")
QUERIES = { QUERIES = {

View File

@@ -315,10 +315,9 @@ class MastodonAPI():
if code < 400: if code < 400:
return response return response
if code == 401: if code == 401:
raise exception.StopExtraction( raise exception.AbortExtraction(
"Invalid or missing access token.\n" f"Invalid or missing access token.\nRun 'gallery-dl oauth:"
"Run 'gallery-dl oauth:mastodon:%s' to obtain one.", f"mastodon:{self.extractor.instance}' to obtain one.")
self.extractor.instance)
if code == 404: if code == 404:
raise exception.NotFoundError() raise exception.NotFoundError()
if code == 429: if code == 429:
@@ -327,7 +326,7 @@ class MastodonAPI():
"%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%S.%fZ",
)) ))
continue continue
raise exception.StopExtraction(response.json().get("error")) raise exception.AbortExtraction(response.json().get("error"))
def _pagination(self, endpoint, params): def _pagination(self, endpoint, params):
url = endpoint url = endpoint

View File

@@ -366,7 +366,7 @@ class NewgroundsExtractor(Extractor):
return return
if "errors" in data: if "errors" in data:
msg = ", ".join(text.unescape(e) for e in data["errors"]) msg = ", ".join(text.unescape(e) for e in data["errors"])
raise exception.StopExtraction(msg) raise exception.AbortExtraction(msg)
items = data.get("items") items = data.get("items")
if not items: if not items:

View File

@@ -386,8 +386,8 @@ class OAuthMastodon(OAuthBase):
data = self.request_json(url, method="POST", data=data) data = self.request_json(url, method="POST", data=data)
if "client_id" not in data or "client_secret" not in data: if "client_id" not in data or "client_secret" not in data:
raise exception.StopExtraction( raise exception.AbortExtraction(
"Failed to register new application: '%s'", data) f"Failed to register new application: '{data}'")
data["client-id"] = data.pop("client_id") data["client-id"] = data.pop("client_id")
data["client-secret"] = data.pop("client_secret") data["client-secret"] = data.pop("client_secret")

View File

@@ -306,7 +306,7 @@ class PatreonExtractor(Extractor):
except Exception: except Exception:
pass pass
raise exception.StopExtraction("Unable to extract bootstrap data") raise exception.AbortExtraction("Unable to extract bootstrap data")
class PatreonCreatorExtractor(PatreonExtractor): class PatreonCreatorExtractor(PatreonExtractor):
@@ -354,21 +354,21 @@ class PatreonCreatorExtractor(PatreonExtractor):
data = None data = None
data = self._extract_bootstrap(page) data = self._extract_bootstrap(page)
return data["campaign"]["data"]["id"] return data["campaign"]["data"]["id"]
except exception.StopExtraction: except exception.ControlException:
pass pass
except Exception as exc: except Exception as exc:
if data: if data:
self.log.debug(data) self.log.debug(data)
raise exception.StopExtraction( raise exception.AbortExtraction(
"Unable to extract campaign ID (%s: %s)", f"Unable to extract campaign ID "
exc.__class__.__name__, exc) f"({exc.__class__.__name__}: {exc})")
# Next.js 13 # Next.js 13
if cid := text.extr( if cid := text.extr(
page, r'{\"value\":{\"campaign\":{\"data\":{\"id\":\"', '\\"'): page, r'{\"value\":{\"campaign\":{\"data\":{\"id\":\"', '\\"'):
return cid return cid
raise exception.StopExtraction("Failed to extract campaign ID") raise exception.AbortExtraction("Failed to extract campaign ID")
def _get_filters(self, query): def _get_filters(self, query):
return "".join( return "".join(

View File

@@ -175,7 +175,7 @@ class PexelsAPI():
else: else:
self.extractor.log.debug(response.text) self.extractor.log.debug(response.text)
raise exception.StopExtraction("API request failed") raise exception.AbortExtraction("API request failed")
def _pagination(self, endpoint, params): def _pagination(self, endpoint, params):
while True: while True:

View File

@@ -160,8 +160,7 @@ class PhilomenaAPI():
# error # error
self.extractor.log.debug(response.content) self.extractor.log.debug(response.content)
raise exception.StopExtraction( raise exception.HttpError("", response)
"%s %s", response.status_code, response.reason)
def _pagination(self, endpoint, params): def _pagination(self, endpoint, params):
extr = self.extractor extr = self.extractor

View File

@@ -543,7 +543,7 @@ class PinterestAPI():
resource = self.extractor.subcategory.rpartition("-")[2] resource = self.extractor.subcategory.rpartition("-")[2]
raise exception.NotFoundError(resource) raise exception.NotFoundError(resource)
self.extractor.log.debug("Server response: %s", response.text) self.extractor.log.debug("Server response: %s", response.text)
raise exception.StopExtraction("API request failed") raise exception.AbortExtraction("API request failed")
def _pagination(self, resource, options): def _pagination(self, resource, options):
while True: while True:

View File

@@ -694,7 +694,7 @@ class PixivRankingExtractor(PixivExtractor):
try: try:
self.mode = mode = mode_map[mode] self.mode = mode = mode_map[mode]
except KeyError: except KeyError:
raise exception.StopExtraction("Invalid mode '%s'", mode) raise exception.AbortExtraction(f"Invalid mode '{mode}'")
date = query.get("date") date = query.get("date")
if date: if date:
@@ -747,7 +747,7 @@ class PixivSearchExtractor(PixivExtractor):
try: try:
self.word = query["word"] self.word = query["word"]
except KeyError: except KeyError:
raise exception.StopExtraction("Missing search term") raise exception.AbortExtraction("Missing search term")
sort = query.get("order", "date_d") sort = query.get("order", "date_d")
sort_map = { sort_map = {
@@ -760,7 +760,7 @@ class PixivSearchExtractor(PixivExtractor):
try: try:
self.sort = sort = sort_map[sort] self.sort = sort = sort_map[sort]
except KeyError: except KeyError:
raise exception.StopExtraction("Invalid search order '%s'", sort) raise exception.AbortExtraction(f"Invalid search order '{sort}'")
target = query.get("s_mode", "s_tag_full") target = query.get("s_mode", "s_tag_full")
target_map = { target_map = {
@@ -771,7 +771,7 @@ class PixivSearchExtractor(PixivExtractor):
try: try:
self.target = target = target_map[target] self.target = target = target_map[target]
except KeyError: except KeyError:
raise exception.StopExtraction("Invalid search mode '%s'", target) raise exception.AbortExtraction(f"Invalid search mode '{target}'")
self.date_start = query.get("scd") self.date_start = query.get("scd")
self.date_end = query.get("ecd") self.date_end = query.get("ecd")
@@ -1288,7 +1288,7 @@ class PixivAppAPI():
self.extractor.wait(seconds=300) self.extractor.wait(seconds=300)
continue continue
raise exception.StopExtraction("API request failed: %s", error) raise exception.AbortExtraction(f"API request failed: {error}")
def _pagination(self, endpoint, params, def _pagination(self, endpoint, params,
key_items="illusts", key_data=None): key_items="illusts", key_data=None):

View File

@@ -53,8 +53,8 @@ class PixnetExtractor(Extractor):
pnext = text.extr(page, 'class="nextBtn"', '>') pnext = text.extr(page, 'class="nextBtn"', '>')
if pnext is None and 'name="albumpass">' in page: if pnext is None and 'name="albumpass">' in page:
raise exception.StopExtraction( raise exception.AbortExtraction(
"Album %s is password-protected.", self.item_id) f"Album {self.item_id} is password-protected.")
if "href" not in pnext: if "href" not in pnext:
return return
url = self.root + text.extr(pnext, 'href="', '"') url = self.root + text.extr(pnext, 'href="', '"')

View File

@@ -102,8 +102,8 @@ class PostmillSubmissionsExtractor(PostmillExtractor):
if response.history: if response.history:
redirect_url = response.url redirect_url = response.url
if redirect_url == self.root + "/login": if redirect_url == self.root + "/login":
raise exception.StopExtraction( raise exception.AbortExtraction(
"HTTP redirect to login page (%s)", redirect_url) f"HTTP redirect to login page ({redirect_url})")
page = response.text page = response.text
for nav in text.extract_iter(page, for nav in text.extract_iter(page,

View File

@@ -37,9 +37,9 @@ class ReadcomiconlineBase():
"the CAPTCHA, and press ENTER to continue", response.url) "the CAPTCHA, and press ENTER to continue", response.url)
self.input() self.input()
else: else:
raise exception.StopExtraction( raise exception.AbortExtraction(
"Redirect to \n%s\nVisit this URL in your browser and " f"Redirect to \n{response.url}\nVisit this URL in your "
"solve the CAPTCHA to continue", response.url) f"browser and solve the CAPTCHA to continue")
class ReadcomiconlineIssueExtractor(ReadcomiconlineBase, ChapterExtractor): class ReadcomiconlineIssueExtractor(ReadcomiconlineBase, ChapterExtractor):

View File

@@ -507,7 +507,8 @@ class RedditAPI():
try: try:
data = response.json() data = response.json()
except ValueError: except ValueError:
raise exception.StopExtraction(text.remove_html(response.text)) raise exception.AbortExtraction(
text.remove_html(response.text))
if "error" in data: if "error" in data:
if data["error"] == 403: if data["error"] == 403:
@@ -515,7 +516,7 @@ class RedditAPI():
if data["error"] == 404: if data["error"] == 404:
raise exception.NotFoundError() raise exception.NotFoundError()
self.log.debug(data) self.log.debug(data)
raise exception.StopExtraction(data.get("message")) raise exception.AbortExtraction(data.get("message"))
return data return data
def _pagination(self, endpoint, params): def _pagination(self, endpoint, params):

View File

@@ -308,7 +308,7 @@ class SankakuAPI():
("unauthorized", "invalid-token", "invalid_token")): ("unauthorized", "invalid-token", "invalid_token")):
_authenticate_impl.invalidate(self.username) _authenticate_impl.invalidate(self.username)
continue continue
raise exception.StopExtraction(code) raise exception.AbortExtraction(code)
return data return data
def _pagination(self, endpoint, params): def _pagination(self, endpoint, params):

View File

@@ -45,8 +45,8 @@ class SeigaExtractor(Extractor):
url = f"{self.root}/image/source/{image_id}" url = f"{self.root}/image/source/{image_id}"
location = self.request_location(url, notfound="image") location = self.request_location(url, notfound="image")
if "nicovideo.jp/login" in location: if "nicovideo.jp/login" in location:
raise exception.StopExtraction( raise exception.AbortExtraction(
"HTTP redirect to login page (%s)", location.partition("?")[0]) f"HTTP redirect to login page ({location.partition('?')[0]})")
return location.replace("/o/", "/priv/", 1) return location.replace("/o/", "/priv/", 1)
def login(self): def login(self):

View File

@@ -211,9 +211,9 @@ class SmugmugAPI(oauth.OAuth1API):
if data["Code"] == 404: if data["Code"] == 404:
raise exception.NotFoundError() raise exception.NotFoundError()
if data["Code"] == 429: if data["Code"] == 429:
raise exception.StopExtraction("Rate limit reached") raise exception.AbortExtraction("Rate limit reached")
self.log.debug(data) self.log.debug(data)
raise exception.StopExtraction("API request failed") raise exception.AbortExtraction("API request failed")
def _expansion(self, endpoint, expands, params=None): def _expansion(self, endpoint, expands, params=None):
endpoint = self._extend(endpoint, expands) endpoint = self._extend(endpoint, expands)

View File

@@ -74,7 +74,7 @@ class SteamgriddbExtractor(Extractor):
def _call(self, endpoint, **kwargs): def _call(self, endpoint, **kwargs):
data = self.request_json(self.root + endpoint, **kwargs) data = self.request_json(self.root + endpoint, **kwargs)
if not data["success"]: if not data["success"]:
raise exception.StopExtraction(data["error"]) raise exception.AbortExtraction(data["error"])
return data["data"] return data["data"]
@@ -87,7 +87,7 @@ class SteamgriddbAssetsExtractor(SteamgriddbExtractor):
id = int(match[2]) id = int(match[2])
self.game_id = id if list_type == "game" else None self.game_id = id if list_type == "game" else None
self.collection_id = id if list_type == "collection" else None self.collection_id = id if list_type == "collection" else None
self.page = int(match[3] or 1) self.page = int(p) if (p := match[3]) else 1
def assets(self): def assets(self):
limit = 48 limit = 48
@@ -96,7 +96,7 @@ class SteamgriddbAssetsExtractor(SteamgriddbExtractor):
sort = self.config("sort", "score_desc") sort = self.config("sort", "score_desc")
if sort not in ("score_desc", "score_asc", "score_old_desc", if sort not in ("score_desc", "score_asc", "score_old_desc",
"score_old_asc", "age_desc", "age_asc"): "score_old_asc", "age_desc", "age_asc"):
raise exception.StopExtractor("Invalid sort '%s'", sort) raise exception.AbortExtraction(f"Invalid sort '{sort}'")
json = { json = {
"static" : self.config("static", True), "static" : self.config("static", True),
@@ -149,7 +149,7 @@ class SteamgriddbAssetsExtractor(SteamgriddbExtractor):
for i in value: for i in value:
if i not in valid_values: if i not in valid_values:
raise exception.StopExtraction("Invalid %s '%s'", type_name, i) raise exception.AbortExtraction(f"Invalid {type_name} '{i}'")
return value return value

View File

@@ -65,8 +65,8 @@ class SubscribestarExtractor(Extractor):
if response.history and ( if response.history and (
"/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.StopExtraction( raise exception.AbortExtraction(
"HTTP redirect to %s", response.url) f"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

@@ -86,9 +86,9 @@ class TsuminoGalleryExtractor(TsuminoBase, GalleryExtractor):
response = self.request(url, headers=headers, fatal=False) response = self.request(url, headers=headers, fatal=False)
if "/Auth/" in response.url: if "/Auth/" in response.url:
raise exception.StopExtraction( raise exception.AbortExtraction(
"Failed to get gallery JSON data. Visit '%s' in a browser " f"Failed to get gallery JSON data. Visit '{response.url}' "
"and solve the CAPTCHA to continue.", response.url) f"in a browser and solve the CAPTCHA to continue.")
page = response.text page = response.text
tpl, pos = text.extract(page, 'data-cdn="', '"') tpl, pos = text.extract(page, 'data-cdn="', '"')
@@ -155,8 +155,8 @@ class TsuminoSearchExtractor(TsuminoBase, Extractor):
return self._parse_simple(query) return self._parse_simple(query)
return self._parse_jsurl(query) return self._parse_jsurl(query)
except Exception as exc: except Exception as exc:
raise exception.StopExtraction( raise exception.AbortExtraction(
"Invalid search query '%s' (%s)", query, exc) f"Invalid search query '{query}' ({exc})")
def _parse_simple(self, query): def _parse_simple(self, query):
"""Parse search query with format '?<key>=value>'""" """Parse search query with format '?<key>=value>'"""

View File

@@ -499,8 +499,8 @@ class TumblrAPI(oauth.OAuth1API):
continue continue
t = (datetime.now() + timedelta(0, float(reset))).time() t = (datetime.now() + timedelta(0, float(reset))).time()
raise exception.StopExtraction( raise exception.AbortExtraction(
"Aborting - Rate limit will reset at %s", f"Aborting - Rate limit will reset at "
f"{t.hour:02}:{t.minute:02}:{t.second:02}") f"{t.hour:02}:{t.minute:02}:{t.second:02}")
# hourly rate limit # hourly rate limit
@@ -510,7 +510,7 @@ class TumblrAPI(oauth.OAuth1API):
self.extractor.wait(seconds=reset) self.extractor.wait(seconds=reset)
continue continue
raise exception.StopExtraction(data) raise exception.AbortExtraction(data)
def _pagination(self, endpoint, params, def _pagination(self, endpoint, params,
blog=None, key="posts", cache=False): blog=None, key="posts", cache=False):

View File

@@ -154,8 +154,7 @@ class TwibooruAPI():
# error # error
self.extractor.log.debug(response.content) self.extractor.log.debug(response.content)
raise exception.StopExtraction( raise exception.HttpError("", response)
"%s %s", response.status_code, response.reason)
def _pagination(self, endpoint, params): def _pagination(self, endpoint, params):
extr = self.extractor extr = self.extractor

View File

@@ -725,7 +725,7 @@ class TwitterTimelineExtractor(TwitterExtractor):
return self.api.user_media return self.api.user_media
if strategy == "with_replies": if strategy == "with_replies":
return self.api.user_tweets_and_replies return self.api.user_tweets_and_replies
raise exception.StopExtraction("Invalid strategy '%s'", strategy) raise exception.AbortExtraction(f"Invalid strategy '{strategy}'")
class TwitterTweetsExtractor(TwitterExtractor): class TwitterTweetsExtractor(TwitterExtractor):
@@ -940,8 +940,8 @@ class TwitterTweetExtractor(TwitterExtractor):
try: try:
self._assign_user(tweet["core"]["user_results"]["result"]) self._assign_user(tweet["core"]["user_results"]["result"])
except KeyError: except KeyError:
raise exception.StopExtraction( raise exception.AbortExtraction(
"'%s'", tweet.get("reason") or "Unavailable") f"'{tweet.get('reason') or 'Unavailable'}'")
yield tweet yield tweet
@@ -1253,7 +1253,7 @@ class TwitterAPI():
raise exception.AuthorizationError("NSFW Tweet") raise exception.AuthorizationError("NSFW Tweet")
if reason == "Protected": if reason == "Protected":
raise exception.AuthorizationError("Protected Tweet") raise exception.AuthorizationError("Protected Tweet")
raise exception.StopExtraction("Tweet unavailable ('%s')", reason) raise exception.AbortExtraction(f"Tweet unavailable ('{reason}')")
return tweet return tweet
@@ -1634,8 +1634,8 @@ class TwitterAPI():
except Exception: except Exception:
pass pass
raise exception.StopExtraction( raise exception.AbortExtraction(
"%s %s (%s)", response.status_code, response.reason, errors) f"{response.status_code} {response.reason} ({errors})")
def _pagination_legacy(self, endpoint, params): def _pagination_legacy(self, endpoint, params):
extr = self.extractor extr = self.extractor
@@ -1809,7 +1809,7 @@ class TwitterAPI():
raise exception.AuthorizationError( raise exception.AuthorizationError(
f"{user['screen_name']}'s Tweets are protected") f"{user['screen_name']}'s Tweets are protected")
raise exception.StopExtraction( raise exception.AbortExtraction(
"Unable to retrieve Tweets from this timeline") "Unable to retrieve Tweets from this timeline")
tweets = [] tweets = []
@@ -1988,7 +1988,7 @@ class TwitterAPI():
def _handle_ratelimit(self, response): def _handle_ratelimit(self, response):
rl = self.extractor.config("ratelimit") rl = self.extractor.config("ratelimit")
if rl == "abort": if rl == "abort":
raise exception.StopExtraction("Rate limit exceeded") raise exception.AbortExtraction("Rate limit exceeded")
elif rl and isinstance(rl, str) and rl.startswith("wait:"): elif rl and isinstance(rl, str) and rl.startswith("wait:"):
until = None until = None
seconds = text.parse_float(rl.partition(":")[2]) or 60.0 seconds = text.parse_float(rl.partition(":")[2]) or 60.0
@@ -2172,7 +2172,7 @@ def _login_impl(extr, username, password):
raise exception.AuthenticationError( raise exception.AuthenticationError(
"No 'auth_token' cookie received") "No 'auth_token' cookie received")
else: else:
raise exception.StopExtraction("Unrecognized subtask %s", subtask) raise exception.AbortExtraction(f"Unrecognized subtask {subtask}")
inputs = {"subtask_id": subtask} inputs = {"subtask_id": subtask}
inputs.update(data) inputs.update(data)

View File

@@ -40,5 +40,5 @@ class UrlshortenerLinkExtractor(UrlshortenerExtractor):
location = self.request_location( location = self.request_location(
url, headers=self.config_instance("headers"), notfound="URL") url, headers=self.config_instance("headers"), notfound="URL")
if not location: if not location:
raise exception.StopExtraction("Unable to resolve short URL") raise exception.AbortExtraction("Unable to resolve short URL")
yield Message.Queue, location, {} yield Message.Queue, location, {}

View File

@@ -96,8 +96,8 @@ class VkExtractor(Extractor):
response = self.request( response = self.request(
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.StopExtraction( raise exception.AbortExtraction(
"HTTP redirect to 'challenge' page<:\n%s", response.url) f"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:

View File

@@ -200,9 +200,9 @@ class WallhavenAPI():
continue continue
self.extractor.log.debug("Server response: %s", response.text) self.extractor.log.debug("Server response: %s", response.text)
raise exception.StopExtraction( raise exception.AbortExtraction(
"API request failed (%s %s)", f"API request failed "
response.status_code, response.reason) f"({response.status_code} {response.reason})")
def _pagination(self, endpoint, params=None, metadata=None): def _pagination(self, endpoint, params=None, metadata=None):
if params is None: if params is None:

View File

@@ -40,8 +40,8 @@ class WebtoonsBase():
def request(self, url, **kwargs): def request(self, url, **kwargs):
response = Extractor.request(self, url, **kwargs) response = Extractor.request(self, url, **kwargs)
if response.history and "/ageGate" in response.url: if response.history and "/ageGate" in response.url:
raise exception.StopExtraction( raise exception.AbortExtraction(
"HTTP redirect to age gate check ('%s')", response.url) f"HTTP redirect to age gate check ('{response.url}')")
return response return response

View File

@@ -46,9 +46,9 @@ class WeiboExtractor(Extractor):
if response.history: if response.history:
if "login.sina.com" in response.url: if "login.sina.com" in response.url:
raise exception.StopExtraction( raise exception.AbortExtraction(
"HTTP redirect to login page (%s)", f"HTTP redirect to login page "
response.url.partition("?")[0]) f"({response.url.partition('?')[0]})")
if "passport.weibo.com" in response.url: if "passport.weibo.com" in response.url:
self._sina_visitor_system(response) self._sina_visitor_system(response)
response = Extractor.request(self, url, **kwargs) response = Extractor.request(self, url, **kwargs)
@@ -179,8 +179,8 @@ class WeiboExtractor(Extractor):
if not data.get("ok"): if not data.get("ok"):
self.log.debug(response.content) self.log.debug(response.content)
if "since_id" not in params: # first iteration if "since_id" not in params: # first iteration
raise exception.StopExtraction( raise exception.AbortExtraction(
'"%s"', data.get("msg") or "unknown error") f'"{data.get("msg") or "unknown error"}"')
data = data["data"] data = data["data"]
statuses = data["list"] statuses = data["list"]

View File

@@ -51,7 +51,7 @@ class WikimediaExtractor(BaseExtractor):
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
raise exception.StopExtraction("Unable to find API endpoint") raise exception.AbortExtraction("Unable to find API endpoint")
def prepare(self, image): def prepare(self, image):
"""Adjust the content of an image object""" """Adjust the content of an image object"""

View File

@@ -38,7 +38,7 @@ class XfolioExtractor(Extractor):
response = Extractor.request(self, url, **kwargs) response = Extractor.request(self, url, **kwargs)
if "/system/recaptcha" in response.url: if "/system/recaptcha" in response.url:
raise exception.StopExtraction("Bot check / CAPTCHA page") raise exception.AbortExtraction("Bot check / CAPTCHA page")
return response return response

View File

@@ -95,7 +95,7 @@ class YoutubeDLExtractor(Extractor):
ytdl_instance.get_info_extractor(self.ytdl_ie_key), ytdl_instance.get_info_extractor(self.ytdl_ie_key),
False, {}, True) False, {}, True)
except ytdl_module.utils.YoutubeDLError: except ytdl_module.utils.YoutubeDLError:
raise exception.StopExtraction("Failed to extract video data") raise exception.AbortExtraction("Failed to extract video data")
if not info_dict: if not info_dict:
return return

View File

@@ -238,7 +238,7 @@ class ZerochanTagExtractor(ZerochanExtractor):
self.log.warning("HTTP redirect to %s", url) self.log.warning("HTTP redirect to %s", url)
if self.config("redirects"): if self.config("redirects"):
continue continue
raise exception.StopExtraction() raise exception.AbortExtraction()
data = response.json() data = response.json()
try: try:

View File

@@ -153,9 +153,10 @@ class Job():
try: try:
for msg in extractor: for msg in extractor:
self.dispatch(msg) self.dispatch(msg)
except exception.StopExtraction as exc: except exception.StopExtraction:
if exc.message: pass
log.error(exc.message) except exception.AbortExtraction as exc:
log.error(exc.message)
self.status |= exc.code self.status |= exc.code
except (exception.TerminateExtraction, exception.RestartExtraction): except (exception.TerminateExtraction, exception.RestartExtraction):
raise raise

View File

@@ -189,7 +189,7 @@ class UpdateExtractor(Extractor):
try: try:
path_repo = REPOS[repo or "stable"] path_repo = REPOS[repo or "stable"]
except KeyError: except KeyError:
raise exception.StopExtraction("Invalid channel '%s'", repo) raise exception.AbortExtraction(f"Invalid channel '{repo}'")
path_tag = tag if tag == "latest" else "tags/" + tag path_tag = tag if tag == "latest" else "tags/" + tag
url = f"{self.root_api}/repos/{path_repo}/releases/{path_tag}" url = f"{self.root_api}/repos/{path_repo}/releases/{path_tag}"

View File

@@ -41,7 +41,7 @@ def construct_YoutubeDL(module, obj, user_opts, system_opts=None):
try: try:
opts = parse_command_line(module, argv) if argv else user_opts opts = parse_command_line(module, argv) if argv else user_opts
except SystemExit: except SystemExit:
raise exception.StopExtraction("Invalid command-line option") raise exception.AbortExtraction("Invalid command-line option")
if opts.get("format") is None: if opts.get("format") is None:
opts["format"] = config("format") opts["format"] = config("format")