| 140 | |
| 141 | |
| 142 | def _normalize_proxy_url(proxy_url: Optional[str]) -> Optional[str]: |
| 143 | raw = (proxy_url or "").strip() |
| 144 | if not raw: |
| 145 | return None |
| 146 | |
| 147 | st5_match = re.match(r"^st5\s+(.+)$", raw, re.IGNORECASE) |
| 148 | if st5_match: |
| 149 | rest = st5_match.group(1).strip() |
| 150 | if "@" in rest: |
| 151 | return f"socks5://{rest}" |
| 152 | parts = rest.split(":") |
| 153 | if len(parts) >= 4 and parts[1].isdigit(): |
| 154 | return f"socks5://{parts[2]}:{':'.join(parts[3:])}@{parts[0]}:{parts[1]}" |
| 155 | return None |
| 156 | |
| 157 | if "://" not in raw: |
| 158 | if "@" in raw: |
| 159 | return f"http://{raw}" |
| 160 | parts = raw.split(":") |
| 161 | if len(parts) == 2 and parts[1].isdigit(): |
| 162 | return f"http://{parts[0]}:{parts[1]}" |
| 163 | if len(parts) >= 4 and parts[1].isdigit(): |
| 164 | return f"http://{parts[2]}:{':'.join(parts[3:])}@{parts[0]}:{parts[1]}" |
| 165 | return None |
| 166 | |
| 167 | if re.match(r"^(http|https|socks5h?|socks5)://", raw, re.IGNORECASE): |
| 168 | if "@" in raw: |
| 169 | return raw |
| 170 | scheme, _, rest = raw.partition("://") |
| 171 | parts = rest.split(":") |
| 172 | if len(parts) == 2 and parts[1].isdigit(): |
| 173 | return raw |
| 174 | if len(parts) >= 4 and parts[1].isdigit(): |
| 175 | return f"{scheme}://{parts[2]}:{':'.join(parts[3:])}@{parts[0]}:{parts[1]}" |
| 176 | return None |
| 177 | |
| 178 | |
| 179 | def _append_login_hint(target_url: str, email: Optional[str]) -> str: |