Spaces:
Running on Zero
Running on Zero
feat(shortener): honor gateway-injected link_lifetime for permanent paid-tier links
Browse files- GenerateRequest.link_lifetime (server-set by qr-gateway from install entitlement)
- build_generation_kwargs passes it through
- generate_standard_qr/generate_artistic_qr accept and forward to shortener
- _maybe_shorten_url_for_qr sends permanent:true when link_lifetime=permanent
Paired with qrcut-shortener key-gated permanent flag + qr-gateway credit-aware injection.
- app.py +12 -1
- qr_modal_contract.py +4 -0
app.py
CHANGED
|
@@ -821,6 +821,7 @@ def _maybe_shorten_url_for_qr(
|
|
| 821 |
original_input: str,
|
| 822 |
input_type: str,
|
| 823 |
use_temporary_short_link: bool,
|
|
|
|
| 824 |
request: Any | None = None,
|
| 825 |
) -> Mapping[str, Any]:
|
| 826 |
fallback_qr_text = _normalize_qr_text_for_validation(original_input, input_type)
|
|
@@ -847,7 +848,13 @@ def _maybe_shorten_url_for_qr(
|
|
| 847 |
"error": "shortener is not configured on the server",
|
| 848 |
}
|
| 849 |
|
| 850 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 851 |
# Set an explicit User-Agent: urllib's default "Python-urllib/3.x" is blocked
|
| 852 |
# by Cloudflare WAF (Error 1010) at the qrcut.co edge, which surfaced as
|
| 853 |
# "HTTP Error 403" → "short link unavailable, using normal link" in the UI.
|
|
@@ -2986,6 +2993,7 @@ def generate_standard_qr(
|
|
| 2986 |
text_input: str = "",
|
| 2987 |
input_type: str = "URL",
|
| 2988 |
use_temporary_short_link: bool = False,
|
|
|
|
| 2989 |
image_size: int = 512,
|
| 2990 |
border_size: int = 4,
|
| 2991 |
error_correction: str = "Medium (15%)",
|
|
@@ -3031,6 +3039,7 @@ def generate_standard_qr(
|
|
| 3031 |
original_input=text_input,
|
| 3032 |
input_type=input_type,
|
| 3033 |
use_temporary_short_link=use_temporary_short_link,
|
|
|
|
| 3034 |
request=request,
|
| 3035 |
)
|
| 3036 |
qr_text_input = str(shortener_result.get("effective_qr_text") or qr_text_input)
|
|
@@ -3295,6 +3304,7 @@ def generate_artistic_qr(
|
|
| 3295 |
text_input: str = "",
|
| 3296 |
input_type: str = "URL",
|
| 3297 |
use_temporary_short_link: bool = False,
|
|
|
|
| 3298 |
image_size: int = 512,
|
| 3299 |
border_size: int = 4,
|
| 3300 |
error_correction: str = "Medium (15%)",
|
|
@@ -3357,6 +3367,7 @@ def generate_artistic_qr(
|
|
| 3357 |
original_input=text_input,
|
| 3358 |
input_type=input_type,
|
| 3359 |
use_temporary_short_link=use_temporary_short_link,
|
|
|
|
| 3360 |
request=request,
|
| 3361 |
)
|
| 3362 |
qr_text_input = str(shortener_result.get("effective_qr_text") or qr_text_input)
|
|
|
|
| 821 |
original_input: str,
|
| 822 |
input_type: str,
|
| 823 |
use_temporary_short_link: bool,
|
| 824 |
+
link_lifetime: str | None = None,
|
| 825 |
request: Any | None = None,
|
| 826 |
) -> Mapping[str, Any]:
|
| 827 |
fallback_qr_text = _normalize_qr_text_for_validation(original_input, input_type)
|
|
|
|
| 848 |
"error": "shortener is not configured on the server",
|
| 849 |
}
|
| 850 |
|
| 851 |
+
# link_lifetime is injected by the qr-gateway from the install's entitlement:
|
| 852 |
+
# "permanent" => paid-tier (Pro or credit-funded) => the short link never
|
| 853 |
+
# expires. Anything else => default rolling inactivity expiry.
|
| 854 |
+
permanent_link = str(link_lifetime or "").strip().lower() == "permanent"
|
| 855 |
+
request_body = json.dumps(
|
| 856 |
+
{"url": str(original_input or ""), "reuse_existing": False, "permanent": permanent_link}
|
| 857 |
+
).encode("utf-8")
|
| 858 |
# Set an explicit User-Agent: urllib's default "Python-urllib/3.x" is blocked
|
| 859 |
# by Cloudflare WAF (Error 1010) at the qrcut.co edge, which surfaced as
|
| 860 |
# "HTTP Error 403" → "short link unavailable, using normal link" in the UI.
|
|
|
|
| 2993 |
text_input: str = "",
|
| 2994 |
input_type: str = "URL",
|
| 2995 |
use_temporary_short_link: bool = False,
|
| 2996 |
+
link_lifetime: str | None = None,
|
| 2997 |
image_size: int = 512,
|
| 2998 |
border_size: int = 4,
|
| 2999 |
error_correction: str = "Medium (15%)",
|
|
|
|
| 3039 |
original_input=text_input,
|
| 3040 |
input_type=input_type,
|
| 3041 |
use_temporary_short_link=use_temporary_short_link,
|
| 3042 |
+
link_lifetime=link_lifetime,
|
| 3043 |
request=request,
|
| 3044 |
)
|
| 3045 |
qr_text_input = str(shortener_result.get("effective_qr_text") or qr_text_input)
|
|
|
|
| 3304 |
text_input: str = "",
|
| 3305 |
input_type: str = "URL",
|
| 3306 |
use_temporary_short_link: bool = False,
|
| 3307 |
+
link_lifetime: str | None = None,
|
| 3308 |
image_size: int = 512,
|
| 3309 |
border_size: int = 4,
|
| 3310 |
error_correction: str = "Medium (15%)",
|
|
|
|
| 3367 |
original_input=text_input,
|
| 3368 |
input_type=input_type,
|
| 3369 |
use_temporary_short_link=use_temporary_short_link,
|
| 3370 |
+
link_lifetime=link_lifetime,
|
| 3371 |
request=request,
|
| 3372 |
)
|
| 3373 |
qr_text_input = str(shortener_result.get("effective_qr_text") or qr_text_input)
|
qr_modal_contract.py
CHANGED
|
@@ -26,6 +26,9 @@ class GenerateRequest(BaseModel):
|
|
| 26 |
include_svg: bool = Field(default=False)
|
| 27 |
include_image_base64: bool = Field(default=True)
|
| 28 |
use_temporary_short_link: bool = Field(default=False)
|
|
|
|
|
|
|
|
|
|
| 29 |
analytics_opt_in: bool = Field(default=False)
|
| 30 |
enable_freeu: bool = Field(default=True)
|
| 31 |
negative_prompt: str = Field(
|
|
@@ -75,6 +78,7 @@ def build_generation_kwargs(
|
|
| 75 |
"text_input": request.qr_text,
|
| 76 |
"input_type": request.input_type,
|
| 77 |
"use_temporary_short_link": request.use_temporary_short_link,
|
|
|
|
| 78 |
"image_size": request.image_size,
|
| 79 |
"border_size": request.border_size,
|
| 80 |
"error_correction": request.error_correction,
|
|
|
|
| 26 |
include_svg: bool = Field(default=False)
|
| 27 |
include_image_base64: bool = Field(default=True)
|
| 28 |
use_temporary_short_link: bool = Field(default=False)
|
| 29 |
+
# Server-set by the qr-gateway from the install's entitlement (never trust
|
| 30 |
+
# client-supplied values for it): "permanent" => paid-tier link, no expiry.
|
| 31 |
+
link_lifetime: str | None = Field(default=None)
|
| 32 |
analytics_opt_in: bool = Field(default=False)
|
| 33 |
enable_freeu: bool = Field(default=True)
|
| 34 |
negative_prompt: str = Field(
|
|
|
|
| 78 |
"text_input": request.qr_text,
|
| 79 |
"input_type": request.input_type,
|
| 80 |
"use_temporary_short_link": request.use_temporary_short_link,
|
| 81 |
+
"link_lifetime": request.link_lifetime,
|
| 82 |
"image_size": request.image_size,
|
| 83 |
"border_size": request.border_size,
|
| 84 |
"error_correction": request.error_correction,
|