[tsumino] export 'jsurl' code
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright 2019-2025 Mike Fährmann
|
# Copyright 2019-2026 Mike Fährmann
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License version 2 as
|
# it under the terms of the GNU General Public License version 2 as
|
||||||
@@ -153,7 +153,7 @@ class TsuminoSearchExtractor(TsuminoBase, Extractor):
|
|||||||
try:
|
try:
|
||||||
if query[0] == "?":
|
if query[0] == "?":
|
||||||
return self._parse_simple(query)
|
return self._parse_simple(query)
|
||||||
return self._parse_jsurl(query)
|
return self.utils("/jsurl").parse(query)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise exception.AbortExtraction(
|
raise exception.AbortExtraction(
|
||||||
f"Invalid search query '{query}' ({exc})")
|
f"Invalid search query '{query}' ({exc})")
|
||||||
@@ -177,128 +177,3 @@ class TsuminoSearchExtractor(TsuminoBase, Extractor):
|
|||||||
"Tags[0][Text]": text.unquote(value).replace("+", " "),
|
"Tags[0][Text]": text.unquote(value).replace("+", " "),
|
||||||
"Tags[0][Exclude]": "false",
|
"Tags[0][Exclude]": "false",
|
||||||
}
|
}
|
||||||
|
|
||||||
def _parse_jsurl(self, data):
|
|
||||||
"""Parse search query in JSURL format
|
|
||||||
|
|
||||||
Nested lists and dicts are handled in a special way to deal
|
|
||||||
with the way Tsumino expects its parameters -> expand(...)
|
|
||||||
|
|
||||||
Example: ~(name~'John*20Doe~age~42~children~(~'Mary~'Bill))
|
|
||||||
Ref: https://github.com/Sage/jsurl
|
|
||||||
"""
|
|
||||||
i = 0
|
|
||||||
imax = len(data)
|
|
||||||
|
|
||||||
def eat(expected):
|
|
||||||
nonlocal i
|
|
||||||
|
|
||||||
if data[i] != expected:
|
|
||||||
raise ValueError(
|
|
||||||
f"bad JSURL syntax: expected '{expected}', got {data[i]}")
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
def decode():
|
|
||||||
nonlocal i
|
|
||||||
|
|
||||||
beg = i
|
|
||||||
result = ""
|
|
||||||
|
|
||||||
while i < imax:
|
|
||||||
ch = data[i]
|
|
||||||
|
|
||||||
if ch not in "~)*!":
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
elif ch == "*":
|
|
||||||
if beg < i:
|
|
||||||
result += data[beg:i]
|
|
||||||
if data[i + 1] == "*":
|
|
||||||
result += chr(int(data[i+2:i+6], 16))
|
|
||||||
i += 6
|
|
||||||
else:
|
|
||||||
result += chr(int(data[i+1:i+3], 16))
|
|
||||||
i += 3
|
|
||||||
beg = i
|
|
||||||
|
|
||||||
elif ch == "!":
|
|
||||||
if beg < i:
|
|
||||||
result += data[beg:i]
|
|
||||||
result += "$"
|
|
||||||
i += 1
|
|
||||||
beg = i
|
|
||||||
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
return result + data[beg:i]
|
|
||||||
|
|
||||||
def parse_one():
|
|
||||||
nonlocal i
|
|
||||||
|
|
||||||
eat('~')
|
|
||||||
result = ""
|
|
||||||
ch = data[i]
|
|
||||||
|
|
||||||
if ch == "(":
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
if data[i] == "~":
|
|
||||||
result = []
|
|
||||||
if data[i+1] == ")":
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
result.append(parse_one())
|
|
||||||
while data[i] == "~":
|
|
||||||
result.append(parse_one())
|
|
||||||
|
|
||||||
else:
|
|
||||||
result = {}
|
|
||||||
|
|
||||||
if data[i] != ")":
|
|
||||||
while True:
|
|
||||||
key = decode()
|
|
||||||
value = parse_one()
|
|
||||||
for ekey, evalue in expand(key, value):
|
|
||||||
result[ekey] = evalue
|
|
||||||
if data[i] != "~":
|
|
||||||
break
|
|
||||||
i += 1
|
|
||||||
eat(")")
|
|
||||||
|
|
||||||
elif ch == "'":
|
|
||||||
i += 1
|
|
||||||
result = decode()
|
|
||||||
|
|
||||||
else:
|
|
||||||
beg = i
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
while i < imax and data[i] not in "~)":
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
sub = data[beg:i]
|
|
||||||
if ch in "0123456789-":
|
|
||||||
fval = float(sub)
|
|
||||||
ival = int(fval)
|
|
||||||
result = ival if ival == fval else fval
|
|
||||||
else:
|
|
||||||
if sub not in ("true", "false", "null"):
|
|
||||||
raise ValueError("bad value keyword: " + sub)
|
|
||||||
result = sub
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def expand(key, value):
|
|
||||||
if isinstance(value, list):
|
|
||||||
for index, cvalue in enumerate(value):
|
|
||||||
ckey = f"{key}[{index}]"
|
|
||||||
yield from expand(ckey, cvalue)
|
|
||||||
elif isinstance(value, dict):
|
|
||||||
for ckey, cvalue in value.items():
|
|
||||||
ckey = f"{key}[{ckey}]"
|
|
||||||
yield from expand(ckey, cvalue)
|
|
||||||
else:
|
|
||||||
yield key, value
|
|
||||||
|
|
||||||
return parse_one()
|
|
||||||
|
|||||||
133
gallery_dl/extractor/utils/jsurl.py
Normal file
133
gallery_dl/extractor/utils/jsurl.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright 2026 Mike Fährmann
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License version 2 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
|
||||||
|
|
||||||
|
def parse(data):
|
||||||
|
"""Parse JSURL data
|
||||||
|
|
||||||
|
Nested lists and dicts are handled in a special way to deal
|
||||||
|
with the way Tsumino expects its parameters -> expand(...)
|
||||||
|
|
||||||
|
Example: ~(name~'John*20Doe~age~42~children~(~'Mary~'Bill))
|
||||||
|
Ref: https://github.com/Sage/jsurl
|
||||||
|
"""
|
||||||
|
i = 0
|
||||||
|
imax = len(data)
|
||||||
|
|
||||||
|
def eat(expected):
|
||||||
|
nonlocal i
|
||||||
|
|
||||||
|
if data[i] != expected:
|
||||||
|
raise ValueError(
|
||||||
|
f"bad JSURL syntax: expected '{expected}', got {data[i]}")
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
def decode():
|
||||||
|
nonlocal i
|
||||||
|
|
||||||
|
beg = i
|
||||||
|
result = ""
|
||||||
|
|
||||||
|
while i < imax:
|
||||||
|
ch = data[i]
|
||||||
|
|
||||||
|
if ch not in "~)*!":
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
elif ch == "*":
|
||||||
|
if beg < i:
|
||||||
|
result += data[beg:i]
|
||||||
|
if data[i + 1] == "*":
|
||||||
|
result += chr(int(data[i+2:i+6], 16))
|
||||||
|
i += 6
|
||||||
|
else:
|
||||||
|
result += chr(int(data[i+1:i+3], 16))
|
||||||
|
i += 3
|
||||||
|
beg = i
|
||||||
|
|
||||||
|
elif ch == "!":
|
||||||
|
if beg < i:
|
||||||
|
result += data[beg:i]
|
||||||
|
result += "$"
|
||||||
|
i += 1
|
||||||
|
beg = i
|
||||||
|
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
return result + data[beg:i]
|
||||||
|
|
||||||
|
def parse_one():
|
||||||
|
nonlocal i
|
||||||
|
|
||||||
|
eat('~')
|
||||||
|
result = ""
|
||||||
|
ch = data[i]
|
||||||
|
|
||||||
|
if ch == "(":
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if data[i] == "~":
|
||||||
|
result = []
|
||||||
|
if data[i+1] == ")":
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
result.append(parse_one())
|
||||||
|
while data[i] == "~":
|
||||||
|
result.append(parse_one())
|
||||||
|
|
||||||
|
else:
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
if data[i] != ")":
|
||||||
|
while True:
|
||||||
|
key = decode()
|
||||||
|
value = parse_one()
|
||||||
|
for ekey, evalue in expand(key, value):
|
||||||
|
result[ekey] = evalue
|
||||||
|
if data[i] != "~":
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
eat(")")
|
||||||
|
|
||||||
|
elif ch == "'":
|
||||||
|
i += 1
|
||||||
|
result = decode()
|
||||||
|
|
||||||
|
else:
|
||||||
|
beg = i
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
while i < imax and data[i] not in "~)":
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
sub = data[beg:i]
|
||||||
|
if ch in "0123456789-":
|
||||||
|
fval = float(sub)
|
||||||
|
ival = int(fval)
|
||||||
|
result = ival if ival == fval else fval
|
||||||
|
else:
|
||||||
|
if sub not in ("true", "false", "null"):
|
||||||
|
raise ValueError("bad value keyword: " + sub)
|
||||||
|
result = sub
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def expand(key, value):
|
||||||
|
if isinstance(value, list):
|
||||||
|
for index, cvalue in enumerate(value):
|
||||||
|
ckey = f"{key}[{index}]"
|
||||||
|
yield from expand(ckey, cvalue)
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
for ckey, cvalue in value.items():
|
||||||
|
ckey = f"{key}[{ckey}]"
|
||||||
|
yield from expand(ckey, cvalue)
|
||||||
|
else:
|
||||||
|
yield key, value
|
||||||
|
|
||||||
|
return parse_one()
|
||||||
Reference in New Issue
Block a user