Update app.py
Browse files
app.py
CHANGED
|
@@ -7,42 +7,39 @@ import random
|
|
| 7 |
import logging
|
| 8 |
import json # برای تبدیل دیکشنری پایتون به رشته JSON
|
| 9 |
|
| 10 |
-
app = Flask(__name__)
|
| 11 |
SECRET_NAME_FOR_TARGET_URL = "TARGET_HF_SPACE_URL"
|
| 12 |
TARGET_URL_FROM_SECRET = os.environ.get(SECRET_NAME_FOR_TARGET_URL)
|
| 13 |
REQUEST_TIMEOUT = 45
|
| 14 |
|
| 15 |
# --- Logging Setup (حداقل لاگ ممکن) ---
|
| 16 |
log = logging.getLogger('werkzeug')
|
| 17 |
-
log.setLevel(logging.ERROR)
|
| 18 |
-
app.logger.setLevel(logging.CRITICAL + 1)
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
COMMON_USER_AGENTS = [
|
| 24 |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36",
|
| 25 |
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15",
|
|
|
|
| 26 |
]
|
| 27 |
|
| 28 |
-
# دیکشنری ساده برای کلماتی که میخواهید جایگزین شوند
|
| 29 |
SIMPLE_TEXT_REPLACEMENTS = {
|
| 30 |
"Aya Vision": "تحلیل",
|
| 31 |
"Chat with Aya": "چت🤖",
|
| 32 |
"Visualize with Aya": "عکس",
|
| 33 |
"Speak with Aya": "", # این مورد باعث مخفی شدن المان والدش میشود
|
| 34 |
-
#
|
| 35 |
-
"
|
| 36 |
-
"
|
| 37 |
-
"License:": "مجوز:",
|
| 38 |
-
"Input Prompt": "فرمان ورودی",
|
| 39 |
-
"Input Image": "تصویر ورودی",
|
| 40 |
-
"Drop Image Here": "تصویر را اینجا رها کنید",
|
| 41 |
-
"- or -": "- یا -",
|
| 42 |
-
"Click to Upload": "برای آپلود کلیک کنید",
|
| 43 |
-
"Ask anything in our 23 languages ...": "به هر یک از ۲۳ زبان سوال خود را بپرسید ...",
|
| 44 |
-
"Connecting Our World": "اتصال دنیای ما",
|
| 45 |
-
# ... کلمات بیشتر
|
| 46 |
}
|
| 47 |
|
| 48 |
GRADIO_HIDE_CSS_STANDARD = """
|
|
@@ -72,349 +69,176 @@ GRADIO_HIDE_CSS_STANDARD = """
|
|
| 72 |
</style>
|
| 73 |
"""
|
| 74 |
|
| 75 |
-
# --- تابع برای حذف لینکها و فوتر Gradio (با تزریق CSS) ---
|
| 76 |
def process_html_content_server_side(html_string):
|
| 77 |
"""فقط حذفهای اولیه سمت سرور با Regex (بدون تزریق CSS در اینجا)."""
|
| 78 |
processed_html = html_string
|
| 79 |
-
|
| 80 |
-
gradio_built_with_patterns = [
|
| 81 |
-
re.compile(r'<[^>]*(?:class|id)\s*=\s*["\'][^"\']*(?:footer|meta-footer|built-with|gradio-footer)[^"\']*["\'][^>]*>.*?Built with Gradio.*?<\/[^>]+>', re.IGNORECASE | re.DOTALL),
|
| 82 |
-
re.compile(r'<footer\b[^>]*>.*?Built with Gradio.*?</footer>', re.IGNORECASE | re.DOTALL),
|
| 83 |
-
re.compile(r'<a\b[^>]*href="https?://(?:www\.)?gradio\.app[^"]*"[^>]*>.*?Built with Gradio.*?</a>', re.IGNORECASE | re.DOTALL),
|
| 84 |
-
re.compile(r'<div\b[^>]*>.*?Built with Gradio.*?gradio\.app.*?</div>', re.IGNORECASE | re.DOTALL),
|
| 85 |
-
re.compile(r'<a\b[^>]*href="https?://(?:www\.)?gradio\.app[^"]*"[^>]*>.*?</a>', re.IGNORECASE | re.DOTALL),
|
| 86 |
-
]
|
| 87 |
-
for pattern in gradio_built_with_patterns:
|
| 88 |
-
processed_html = pattern.sub('', processed_html)
|
| 89 |
-
|
| 90 |
-
settings_patterns = [
|
| 91 |
-
re.compile(r'<[^>]*(?:class|id)\s*=\s*["\'][^"\']*(?:settings|options)[^"\']*["\'][^>]*>.*?Settings.*?<\/[^>]+>', re.IGNORECASE | re.DOTALL),
|
| 92 |
-
re.compile(r'<(?:button|a)\b[^>]*>.*?Settings.*?</(?:button|a)>', re.IGNORECASE | re.DOTALL),
|
| 93 |
-
]
|
| 94 |
-
for pattern in settings_patterns:
|
| 95 |
-
processed_html = pattern.sub('', processed_html)
|
| 96 |
-
|
| 97 |
-
link_pattern = re.compile(r'<a\b[^>]*>.*?</a>', re.IGNORECASE | re.DOTALL) # حذف همه لینکها
|
| 98 |
-
processed_html = link_pattern.sub('', processed_html)
|
| 99 |
-
|
| 100 |
return processed_html
|
| 101 |
|
| 102 |
@app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'])
|
| 103 |
-
@app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'])
|
| 104 |
def proxy_request_handler(path):
|
| 105 |
-
# ... (بخش زیادی از کد شما بدون تغییر باقی میماند) ...
|
| 106 |
-
current_request_path_with_query = f"/{path}"
|
| 107 |
-
if request.query_string:
|
| 108 |
-
current_request_path_with_query += "?" + request.query_string.decode()
|
| 109 |
-
|
| 110 |
if not TARGET_URL_FROM_SECRET:
|
| 111 |
app.logger.critical(f"CRITICAL: Secret '{SECRET_NAME_FOR_TARGET_URL}' is not set.")
|
| 112 |
return "Proxy Configuration Error: Target URL secret is not configured.", 500
|
| 113 |
|
| 114 |
base_target_url = TARGET_URL_FROM_SECRET.rstrip('/')
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
try:
|
| 119 |
parsed_target_url_for_host = urlparse(TARGET_URL_FROM_SECRET)
|
| 120 |
target_hostname = parsed_target_url_for_host.hostname
|
| 121 |
user_agent_to_send = random.choice(COMMON_USER_AGENTS)
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
forward_headers = {key: value for key, value in request.headers.items() if key.lower() not in excluded_incoming_headers}
|
| 124 |
forward_headers['User-Agent'] = user_agent_to_send
|
| 125 |
forward_headers['Host'] = target_hostname
|
| 126 |
-
forward_headers['Accept-Encoding'] = 'gzip, deflate, br'
|
| 127 |
-
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
request_body = request.get_data() if request.method not in ['GET', 'HEAD', 'OPTIONS'] else None
|
| 130 |
|
| 131 |
with requests.Session() as s:
|
| 132 |
-
target_response = s.request(
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
if 300 <= target_response.status_code < 400 and 'Location' in target_response.headers:
|
| 135 |
location = target_response.headers['Location']
|
|
|
|
|
|
|
| 136 |
parsed_location = urlparse(location)
|
| 137 |
-
rewritten_location_header_val = location
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
| 140 |
new_path_on_target = urlparse(abs_target_redirect_url).path
|
| 141 |
new_query_on_target = urlparse(abs_target_redirect_url).query
|
|
|
|
| 142 |
rewritten_location_path = new_path_on_target
|
| 143 |
-
if new_query_on_target:
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
| 145 |
rewritten_location_header_val = rewritten_location_path if rewritten_location_path else "/"
|
| 146 |
-
|
| 147 |
final_redirect_headers = {'Location': rewritten_location_header_val}
|
|
|
|
| 148 |
for h_key, h_val in target_response.headers.items():
|
| 149 |
if h_key.lower() in ['set-cookie', 'cache-control', 'expires', 'pragma', 'vary']:
|
| 150 |
final_redirect_headers[h_key] = h_val
|
| 151 |
return Response(response=None, status=target_response.status_code, headers=final_redirect_headers)
|
| 152 |
-
|
|
|
|
| 153 |
if target_response.status_code >= 400:
|
| 154 |
app.logger.warning(f"Target {target_full_url} error: {target_response.status_code}")
|
| 155 |
-
error_content = target_response.content #
|
| 156 |
-
error_headers_from_target = {
|
|
|
|
|
|
|
|
|
|
| 157 |
return Response(response=error_content, status=target_response.status_code, headers=error_headers_from_target)
|
| 158 |
|
| 159 |
content_type = target_response.headers.get('Content-Type', 'application/octet-stream')
|
| 160 |
-
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
final_response_headers['Content-Type'] = content_type
|
| 163 |
-
# کنترل کش برای جلوگیری از کش شدن توسط مرورگر یا پراکسیهای میانی
|
| 164 |
final_response_headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
| 165 |
final_response_headers['Pragma'] = 'no-cache'
|
| 166 |
final_response_headers['Expires'] = '0'
|
| 167 |
final_response_headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
|
| 168 |
|
| 169 |
def generate_response_content_stream():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
is_html_content_type = 'text/html' in content_type.lower()
|
| 171 |
-
# فلگها برای اطمینان از تزریق فقط یک بار
|
| 172 |
base_tag_injected_flag = False
|
| 173 |
-
css_and_js_injected_flag = False
|
| 174 |
|
| 175 |
current_buffer = b''
|
| 176 |
charset_match = re.search(r'charset=([\w-]+)', content_type, re.IGNORECASE)
|
| 177 |
html_processing_encoding = charset_match.group(1) if charset_match else 'utf-8'
|
| 178 |
|
| 179 |
-
# محاسبه base href
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
path_for_base_tag_calc = parsed_eff_url_for_base_tag.path.rstrip('/') + '/' if parsed_eff_url_for_base_tag.path else "/"
|
| 183 |
-
# اگر آخرین بخش مسیر یک فایل است، از دایرکتوری والد استفاده کن
|
| 184 |
-
if '.' in os.path.basename(parsed_eff_url_for_base_tag.path.rstrip('/')) and path_for_base_tag_calc != '/':
|
| 185 |
-
path_for_base_tag_calc = os.path.dirname(path_for_base_tag_calc.rstrip('/')) + '/'
|
| 186 |
-
if path_for_base_tag_calc == "//": path_for_base_tag_calc = "/" # اصلاح حالت //
|
| 187 |
-
|
| 188 |
-
calculated_final_base_href = urljoin(f"{parsed_eff_url_for_base_tag.scheme}://{parsed_eff_url_for_base_tag.netloc}", path_for_base_tag_calc)
|
| 189 |
-
escaped_href_for_final_base_tag = calculated_final_base_href.replace('"', '"') # escape کردن "
|
| 190 |
-
base_tag_html_to_inject = f'<base href="{escaped_href_for_final_base_tag}">'
|
| 191 |
-
|
| 192 |
-
# آمادهسازی اسکریپت جاوا اسکریپت
|
| 193 |
-
js_replacements_json = json.dumps(SIMPLE_TEXT_REPLACEMENTS)
|
| 194 |
-
mutation_observer_script = f"""
|
| 195 |
-
<script id="grProxyTextReplacerScript">
|
| 196 |
-
(function() {{
|
| 197 |
-
if (document.getElementById('grProxyTextReplacerScriptLoaded')) return; // جلوگیری از اجرای مجدد
|
| 198 |
-
const el = document.createElement('div');
|
| 199 |
-
el.id = 'grProxyTextReplacerScriptLoaded';
|
| 200 |
-
el.style.display = 'none';
|
| 201 |
-
document.body.appendChild(el);
|
| 202 |
-
|
| 203 |
-
const replacements = {js_replacements_json};
|
| 204 |
-
|
| 205 |
-
function escapeRegExp(string) {{
|
| 206 |
-
return string.replace(/[.*+?^${{}}()|[\]\\\\]/g, '\\\\$&');
|
| 207 |
-
}}
|
| 208 |
-
|
| 209 |
-
function applyReplacementsToNode(node) {{
|
| 210 |
-
if (!node) return;
|
| 211 |
-
|
| 212 |
-
if (node.nodeType === Node.ELEMENT_NODE) {{
|
| 213 |
-
for (let i = 0; i < node.childNodes.length; i++) {{
|
| 214 |
-
applyReplacementsToNode(node.childNodes[i]);
|
| 215 |
-
}}
|
| 216 |
-
}} else if (node.nodeType === Node.TEXT_NODE) {{
|
| 217 |
-
if (!node.nodeValue || node.nodeValue.trim() === '') return;
|
| 218 |
-
|
| 219 |
-
let textContent = node.nodeValue;
|
| 220 |
-
let textChangedInLoop = false;
|
| 221 |
-
|
| 222 |
-
for (const originalKey in replacements) {{
|
| 223 |
-
const replacementValue = replacements[originalKey];
|
| 224 |
-
const regex = new RegExp(escapeRegExp(originalKey), 'gi');
|
| 225 |
-
|
| 226 |
-
if (regex.test(textContent)) {{
|
| 227 |
-
if (replacementValue === "" && node.parentElement && node.parentElement.tagName !== 'BODY') {{
|
| 228 |
-
// اگر متن اصلی گره فقط یا عمدتا شامل کلید اصلی بود، والد را مخفی کن
|
| 229 |
-
let normalizedNodeText = node.nodeValue.trim().toLowerCase();
|
| 230 |
-
let normalizedOriginalKey = originalKey.trim().toLowerCase();
|
| 231 |
-
// این شرط را سادهتر میکنیم: اگر کلید در متن بود و جایگزین خالی بود، والد را مخفی کن
|
| 232 |
-
if (normalizedNodeText.includes(normalizedOriginalKey)) {{
|
| 233 |
-
node.parentElement.classList.add('gr-proxy-item-hidden-by-text');
|
| 234 |
-
}}
|
| 235 |
-
// متن را نیز خالی میکنیم
|
| 236 |
-
textContent = textContent.replace(regex, '');
|
| 237 |
-
textChangedInLoop = true;
|
| 238 |
-
}} else if (replacementValue !== "") {{
|
| 239 |
-
textContent = textContent.replace(regex, replacementValue);
|
| 240 |
-
textChangedInLoop = true;
|
| 241 |
-
}}
|
| 242 |
-
}}
|
| 243 |
-
}}
|
| 244 |
-
|
| 245 |
-
if (textChangedInLoop) {{
|
| 246 |
-
node.nodeValue = textContent;
|
| 247 |
-
}}
|
| 248 |
-
}}
|
| 249 |
-
}}
|
| 250 |
-
|
| 251 |
-
function processMutations(mutationsList, observer) {{
|
| 252 |
-
applyReplacementsToNode(document.body); // همیشه کل body را بررسی کن
|
| 253 |
-
}}
|
| 254 |
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
subtree: true,
|
| 258 |
-
characterData: true
|
| 259 |
-
}};
|
| 260 |
|
| 261 |
-
const observer = new MutationObserver(processMutations);
|
| 262 |
-
|
| 263 |
-
function initialLoadAndObserve() {{
|
| 264 |
-
if (document.body) {{
|
| 265 |
-
applyReplacementsToNode(document.body);
|
| 266 |
-
observer.observe(document.body, observerConfig);
|
| 267 |
-
}} else {{
|
| 268 |
-
setTimeout(initialLoadAndObserve, 50);
|
| 269 |
-
}}
|
| 270 |
-
}}
|
| 271 |
-
|
| 272 |
-
if (document.readyState === 'loading') {{
|
| 273 |
-
document.addEventListener('DOMContentLoaded', initialLoadAndObserve);
|
| 274 |
-
}} else {{
|
| 275 |
-
initialLoadAndObserve();
|
| 276 |
-
}}
|
| 277 |
-
}})();
|
| 278 |
-
</script>
|
| 279 |
-
"""
|
| 280 |
try:
|
| 281 |
for chunk_data in target_response.iter_content(chunk_size=8192): # chunk_size میتواند تنظیم شود
|
| 282 |
if not chunk_data: continue
|
| 283 |
|
| 284 |
if is_html_content_type:
|
| 285 |
current_buffer += chunk_data
|
| 286 |
-
#
|
| 287 |
-
#
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
if not css_and_js_injected_flag: # این فلگ برای CSS و JS با هم است
|
| 302 |
-
head_tag_match = re.search(r'(<head\b[^>]*>)', processed_chunk_string, re.IGNORECASE)
|
| 303 |
-
if head_tag_match:
|
| 304 |
-
insert_position = head_tag_match.end(1)
|
| 305 |
-
injection_payload = ""
|
| 306 |
-
if not base_tag_injected_flag:
|
| 307 |
-
injection_payload += base_tag_html_to_inject
|
| 308 |
-
base_tag_injected_flag = True
|
| 309 |
-
injection_payload += GRADIO_HIDE_CSS_STANDARD
|
| 310 |
-
# اسکریپت JS را اینجا تزریق نمیکنیم، به انتهای body میرود
|
| 311 |
-
|
| 312 |
-
processed_chunk_string = processed_chunk_string[:insert_position] + injection_payload + processed_chunk_string[insert_position:]
|
| 313 |
-
# css_and_js_injected_flag = True # فقط CSS تزریق شد، JS بعدا
|
| 314 |
-
# فلگ را فعلا True میکنیم تا CSS دوباره تزریق نشود.
|
| 315 |
-
# یا دو فلگ جدا برای CSS و JS داشته باشیم. بهتر است.
|
| 316 |
-
# css_injected_flag = True (جدید)
|
| 317 |
-
# js_injected_flag = False (جدید)
|
| 318 |
-
# اینجا چون css_and_js_injected_flag برای هر دو است، آن را True میکنیم
|
| 319 |
-
# و JS را در جای دیگری مدیریت میکنیم
|
| 320 |
-
css_and_js_injected_flag = True # فرض میکنیم JS را هم اینجا میخواهیم تزریق کنیم برای سادگی
|
| 321 |
-
# processed_chunk_string = processed_chunk_string[:insert_position] + injection_payload + mutation_observer_script + processed_chunk_string[insert_position:]
|
| 322 |
-
|
| 323 |
-
# پردازش HTML اولیه سمت سرور (حذفها)
|
| 324 |
-
processed_chunk_string = process_html_content_server_side(processed_chunk_string)
|
| 325 |
-
|
| 326 |
-
# جایگزینیهای اولیه متن سمت سرور (اینها توسط JS هم انجام خواهند شد)
|
| 327 |
-
temp_string_for_replacement = processed_chunk_string
|
| 328 |
-
for original_word, replacement_text in SIMPLE_TEXT_REPLACEMENTS.items():
|
| 329 |
-
if replacement_text == "": continue # جایگزینی با خالی توسط JS و CSS انجام میشود
|
| 330 |
-
try:
|
| 331 |
-
pattern = re.compile(r'\b' + re.escape(original_word) + r'\b', re.IGNORECASE)
|
| 332 |
-
new_string, num_replacements = pattern.subn(replacement_text, temp_string_for_replacement)
|
| 333 |
-
if num_replacements > 0:
|
| 334 |
-
temp_string_for_replacement = new_string
|
| 335 |
-
except re.error: # اگر re.escape کافی نبود
|
| 336 |
-
pass
|
| 337 |
-
processed_chunk_string = temp_string_for_replacement
|
| 338 |
-
|
| 339 |
-
# تزریق اسکریپت JS به انتهای body (اگر پیدا شد)
|
| 340 |
-
body_end_match = re.search(r'(</body>)', processed_chunk_string, re.IGNORECASE)
|
| 341 |
-
if body_end_match:
|
| 342 |
-
insert_pos = body_end_match.start(1)
|
| 343 |
-
processed_chunk_string = processed_chunk_string[:insert_pos] + mutation_observer_script + processed_chunk_string[insert_pos:]
|
| 344 |
-
# اینجا میتوانیم یک فلگ js_injected_flag=True قرار دهیم
|
| 345 |
-
|
| 346 |
-
yield processed_chunk_string.encode(html_processing_encoding, errors='replace')
|
| 347 |
-
current_buffer = b'' # خالی کردن بافر پس از پردازش
|
| 348 |
-
|
| 349 |
else: # محتوای غیر HTML
|
| 350 |
-
if current_buffer: # اگر بافری از قبل مانده (نباید اتفاق بیفتد برای غیر HTML)
|
| 351 |
-
yield current_buffer
|
| 352 |
-
current_buffer = b''
|
| 353 |
yield chunk_data
|
| 354 |
|
| 355 |
# پردازش باقیمانده بافر (اگر چیزی مانده)
|
| 356 |
if current_buffer:
|
| 357 |
if is_html_content_type:
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
buffer_as_string = current_buffer.decode('latin-1', errors='replace')
|
| 362 |
-
|
| 363 |
-
modified_buffer_string = buffer_as_string
|
| 364 |
-
|
| 365 |
-
# تزریق نهایی CSS و base tag اگر هنوز انجام نشده (برای فایلهای کوچک یا آخرین chunk)
|
| 366 |
-
if not css_and_js_injected_flag: # یا فلگ جداگانه css_injected_flag
|
| 367 |
-
head_tag_match = re.search(r'(<head\b[^>]*>)', modified_buffer_string, re.IGNORECASE)
|
| 368 |
-
injection_payload = ""
|
| 369 |
-
if not base_tag_injected_flag:
|
| 370 |
-
injection_payload += base_tag_html_to_inject
|
| 371 |
-
injection_payload += GRADIO_HIDE_CSS_STANDARD
|
| 372 |
-
|
| 373 |
-
if head_tag_match:
|
| 374 |
-
insert_position = head_tag_match.end(1)
|
| 375 |
-
modified_buffer_string = modified_buffer_string[:insert_position] + injection_payload + modified_buffer_string[insert_position:]
|
| 376 |
-
else: # اگر head نبود، یک head بساز (ساده)
|
| 377 |
-
html_tag_match = re.search(r'(<html\b[^>]*>)', modified_buffer_string, re.IGNORECASE)
|
| 378 |
-
if html_tag_match:
|
| 379 |
-
insert_pos = html_tag_match.end(1)
|
| 380 |
-
modified_buffer_string = modified_buffer_string[:insert_pos] + f"<head>{injection_payload}</head>" + modified_buffer_string[insert_pos:]
|
| 381 |
-
else: # اگر تگ html هم نبود، به ابتدای همه چیز اضافه کن
|
| 382 |
-
modified_buffer_string = f"<head>{injection_payload}</head>" + modified_buffer_string
|
| 383 |
-
css_and_js_injected_flag = True # یا css_injected_flag = True
|
| 384 |
-
|
| 385 |
-
modified_buffer_string = process_html_content_server_side(modified_buffer_string)
|
| 386 |
-
|
| 387 |
-
temp_string_for_replacement = modified_buffer_string
|
| 388 |
-
for original_word, replacement_text in SIMPLE_TEXT_REPLACEMENTS.items():
|
| 389 |
-
if replacement_text == "": continue
|
| 390 |
-
try:
|
| 391 |
-
pattern = re.compile(r'\b' + re.escape(original_word) + r'\b', re.IGNORECASE)
|
| 392 |
-
new_string, num_replacements = pattern.subn(replacement_text, temp_string_for_replacement)
|
| 393 |
-
if num_replacements > 0: temp_string_for_replacement = new_string
|
| 394 |
-
except re.error:
|
| 395 |
-
pass
|
| 396 |
-
modified_buffer_string = temp_string_for_replacement
|
| 397 |
-
|
| 398 |
-
# تزریق نهایی اسکریپت JS (اگر هنوز تزریق نشده یا body end پیدا نشده بود)
|
| 399 |
-
if not re.search(r'<script id="grProxyTextReplacerScript">', modified_buffer_string, re.IGNORECASE):
|
| 400 |
-
body_end_match = re.search(r'(</body>)', modified_buffer_string, re.IGNORECASE)
|
| 401 |
-
if body_end_match:
|
| 402 |
-
insert_pos = body_end_match.start(1)
|
| 403 |
-
modified_buffer_string = modified_buffer_string[:insert_pos] + mutation_observer_script + modified_buffer_string[insert_pos:]
|
| 404 |
-
else: # اگر تگ پایانی body پیدا نشد، به آخر همه چیز اضافه میکنیم
|
| 405 |
-
modified_buffer_string += mutation_observer_script
|
| 406 |
-
|
| 407 |
-
yield modified_buffer_string.encode(html_processing_encoding, errors='replace')
|
| 408 |
-
else: # غیر HTML
|
| 409 |
yield current_buffer
|
| 410 |
except Exception as stream_err:
|
| 411 |
app.logger.error(f"Error during HTML/Stream processing for {target_full_url}: {stream_err}")
|
| 412 |
-
# اگر خطایی در پردازش استریم رخ داد، ممکن است بخواهیم بقیه محتوای خام را ارسال کنیم
|
| 413 |
-
# یا یک پیام خطا به کلاینت بفرستیم. برای سادگی، اینجا چیزی yield نمیکنیم.
|
| 414 |
finally:
|
| 415 |
target_response.close()
|
| 416 |
|
| 417 |
-
|
| 418 |
if request.method == 'HEAD':
|
| 419 |
return Response(response=None, status=target_response.status_code, headers=final_response_headers)
|
| 420 |
else:
|
|
@@ -423,26 +247,25 @@ def proxy_request_handler(path):
|
|
| 423 |
except requests.exceptions.Timeout:
|
| 424 |
app.logger.error(f"Timeout ({REQUEST_TIMEOUT}s) fetching {target_full_url}")
|
| 425 |
return "Error: Request to target site timed out.", 504
|
| 426 |
-
except requests.exceptions.HTTPError as http_err:
|
| 427 |
-
app.logger.error(f"HTTPError {http_err.response.status_code} from {target_full_url}.
|
| 428 |
-
|
| 429 |
-
return
|
| 430 |
except requests.exceptions.ConnectionError as conn_err:
|
| 431 |
app.logger.error(f"ConnectionError for {target_full_url}: {conn_err}")
|
| 432 |
-
return f"Error: Could not connect to target.
|
| 433 |
-
except requests.exceptions.RequestException as req_err:
|
| 434 |
app.logger.error(f"RequestException for {target_full_url}: {req_err}")
|
| 435 |
-
return f"Error fetching content
|
| 436 |
-
except Exception as general_err:
|
| 437 |
-
app.logger.exception(f"Unexpected Python error proxying {target_full_url}")
|
| 438 |
-
return f"Unexpected server error
|
| 439 |
|
| 440 |
if __name__ == '__main__':
|
| 441 |
-
port = int(os.environ.get("PORT", 7860))
|
| 442 |
-
|
| 443 |
-
#
|
| 444 |
-
debug_mode =
|
| 445 |
-
|
| 446 |
-
print(f"INFO: Starting Flask app on host 0.0.0.0, port {port}, debug: {debug_mode}. App Log Level: {logging.getLevelName(app.logger.getEffectiveLevel())}, Werkzeug Log Level: {logging.getLevelName(log.getEffectiveLevel())}")
|
| 447 |
|
|
|
|
| 448 |
app.run(host='0.0.0.0', port=port, debug=debug_mode)
|
|
|
|
| 7 |
import logging
|
| 8 |
import json # برای تبدیل دیکشنری پایتون به رشته JSON
|
| 9 |
|
| 10 |
+
app = Flask(__name__) # اصلاح: __name__ به جای name
|
| 11 |
SECRET_NAME_FOR_TARGET_URL = "TARGET_HF_SPACE_URL"
|
| 12 |
TARGET_URL_FROM_SECRET = os.environ.get(SECRET_NAME_FOR_TARGET_URL)
|
| 13 |
REQUEST_TIMEOUT = 45
|
| 14 |
|
| 15 |
# --- Logging Setup (حداقل لاگ ممکن) ---
|
| 16 |
log = logging.getLogger('werkzeug')
|
| 17 |
+
log.setLevel(logging.ERROR) # فقط خطاهای Werkzeug را لاگ کن
|
| 18 |
+
# app.logger.setLevel(logging.CRITICAL + 1) # غیرفعال کردن کامل لاگهای اپلیکیشن Flask
|
| 19 |
+
# برای دیباگ میتوانید این خط را کامنت کنید یا سطح را تغییر دهید
|
| 20 |
+
if not (os.environ.get('FLASK_DEBUG', '0') == '1' or os.environ.get('FLASK_ENV') == 'development'):
|
| 21 |
+
app.logger.setLevel(logging.CRITICAL + 1)
|
| 22 |
+
else:
|
| 23 |
+
app.logger.setLevel(logging.INFO) # لاگ در حالت دیباگ
|
| 24 |
+
|
| 25 |
+
print(f"APP_STARTUP: TARGET_URL_FROM_SECRET: {'SET' if TARGET_URL_FROM_SECRET else 'NOT SET'}")
|
| 26 |
+
if TARGET_URL_FROM_SECRET:
|
| 27 |
+
print(f"APP_STARTUP: Target URL is '{TARGET_URL_FROM_SECRET[:20]}...'") # بخشی از URL برای تایید
|
| 28 |
|
| 29 |
COMMON_USER_AGENTS = [
|
| 30 |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36",
|
| 31 |
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15",
|
| 32 |
+
# ... (سایر User-Agent ها)
|
| 33 |
]
|
| 34 |
|
|
|
|
| 35 |
SIMPLE_TEXT_REPLACEMENTS = {
|
| 36 |
"Aya Vision": "تحلیل",
|
| 37 |
"Chat with Aya": "چت🤖",
|
| 38 |
"Visualize with Aya": "عکس",
|
| 39 |
"Speak with Aya": "", # این مورد باعث مخفی شدن المان والدش میشود
|
| 40 |
+
# ... (سایر جایگزینیهای متن)
|
| 41 |
+
"Gradio": "App", # مثال: جایگزینی کلمه Gradio
|
| 42 |
+
"Built with Gradio": "" # حذف این عبارت
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
}
|
| 44 |
|
| 45 |
GRADIO_HIDE_CSS_STANDARD = """
|
|
|
|
| 69 |
</style>
|
| 70 |
"""
|
| 71 |
|
|
|
|
| 72 |
def process_html_content_server_side(html_string):
|
| 73 |
"""فقط حذفهای اولیه سمت سرور با Regex (بدون تزریق CSS در اینجا)."""
|
| 74 |
processed_html = html_string
|
| 75 |
+
# ... (کد مربوط به حذف با Regex شما) ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
return processed_html
|
| 77 |
|
| 78 |
@app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'])
|
| 79 |
+
@app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD']) # اصلاح: <path:path>
|
| 80 |
def proxy_request_handler(path):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
if not TARGET_URL_FROM_SECRET:
|
| 82 |
app.logger.critical(f"CRITICAL: Secret '{SECRET_NAME_FOR_TARGET_URL}' is not set.")
|
| 83 |
return "Proxy Configuration Error: Target URL secret is not configured.", 500
|
| 84 |
|
| 85 |
base_target_url = TARGET_URL_FROM_SECRET.rstrip('/')
|
| 86 |
+
# اطمینان از اینکه path با / شروع نمیشود اگر base_target_url خودش با / ختم شده
|
| 87 |
+
target_full_url = urljoin(base_target_url + "/", path.lstrip('/'))
|
| 88 |
+
if request.query_string:
|
| 89 |
+
target_full_url += "?" + request.query_string.decode()
|
| 90 |
+
|
| 91 |
+
app.logger.debug(f"Proxying request for path: /{path} to {target_full_url}")
|
| 92 |
|
| 93 |
try:
|
| 94 |
parsed_target_url_for_host = urlparse(TARGET_URL_FROM_SECRET)
|
| 95 |
target_hostname = parsed_target_url_for_host.hostname
|
| 96 |
user_agent_to_send = random.choice(COMMON_USER_AGENTS)
|
| 97 |
+
|
| 98 |
+
excluded_incoming_headers = [
|
| 99 |
+
'host', 'cookie', 'connection', 'upgrade-insecure-requests',
|
| 100 |
+
'if-none-match', 'if-modified-since', 'referer', 'x-hf-space-host',
|
| 101 |
+
'content-length' # اجازه به requests برای محاسبه مجدد
|
| 102 |
+
]
|
| 103 |
forward_headers = {key: value for key, value in request.headers.items() if key.lower() not in excluded_incoming_headers}
|
| 104 |
forward_headers['User-Agent'] = user_agent_to_send
|
| 105 |
forward_headers['Host'] = target_hostname
|
| 106 |
+
forward_headers['Accept-Encoding'] = 'gzip, deflate, br'
|
| 107 |
+
|
| 108 |
+
# مدیریت X-Forwarded-For
|
| 109 |
+
if request.headers.getlist("X-Forwarded-For"):
|
| 110 |
+
forward_headers["X-Forwarded-For"] = request.headers.getlist("X-Forwarded-For")[0] + ", " + request.remote_addr
|
| 111 |
+
else:
|
| 112 |
+
forward_headers["X-Forwarded-For"] = request.remote_addr
|
| 113 |
+
|
| 114 |
+
# برای POST, PUT, etc.
|
| 115 |
request_body = request.get_data() if request.method not in ['GET', 'HEAD', 'OPTIONS'] else None
|
| 116 |
|
| 117 |
with requests.Session() as s:
|
| 118 |
+
target_response = s.request(
|
| 119 |
+
method=request.method,
|
| 120 |
+
url=target_full_url,
|
| 121 |
+
headers=forward_headers,
|
| 122 |
+
data=request_body,
|
| 123 |
+
stream=True, # مهم برای stream کردن پاسخ
|
| 124 |
+
timeout=REQUEST_TIMEOUT,
|
| 125 |
+
allow_redirects=False # ریدایرکتها را خودمان مدیریت میکنیم
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
# مدیریت ریدایرکتها
|
| 129 |
if 300 <= target_response.status_code < 400 and 'Location' in target_response.headers:
|
| 130 |
location = target_response.headers['Location']
|
| 131 |
+
# ... (کد مدیریت ریدایرکت شما، اطمینان از بازنویسی صحیح Location) ...
|
| 132 |
+
# این بخش نیاز به بازبینی دقیق دارد تا Location به درستی به دامنه پروکسی بازنویسی شود
|
| 133 |
parsed_location = urlparse(location)
|
| 134 |
+
rewritten_location_header_val = location
|
| 135 |
+
# اگر ریدایرکت نسبی است یا به همان هاست هدف است، آن را برای پروکسی بازنویسی کن
|
| 136 |
+
if not parsed_location.scheme or parsed_location.hostname == target_hostname:
|
| 137 |
+
# URL کامل ریدایرکت را بر اساس URL نهایی که requests به آن رسیده، بساز
|
| 138 |
+
abs_target_redirect_url = urljoin(target_response.url, location)
|
| 139 |
new_path_on_target = urlparse(abs_target_redirect_url).path
|
| 140 |
new_query_on_target = urlparse(abs_target_redirect_url).query
|
| 141 |
+
|
| 142 |
rewritten_location_path = new_path_on_target
|
| 143 |
+
if new_query_on_target:
|
| 144 |
+
rewritten_location_path += "?" + new_query_on_target
|
| 145 |
+
# اطمینان از اینکه با / شروع میشود اگر خالی نیست
|
| 146 |
+
if not rewritten_location_path.startswith('/') and rewritten_location_path:
|
| 147 |
+
rewritten_location_path = '/' + rewritten_location_path
|
| 148 |
rewritten_location_header_val = rewritten_location_path if rewritten_location_path else "/"
|
| 149 |
+
|
| 150 |
final_redirect_headers = {'Location': rewritten_location_header_val}
|
| 151 |
+
# سایر هدرهای مهم ریدایرکت را هم منتقل کن
|
| 152 |
for h_key, h_val in target_response.headers.items():
|
| 153 |
if h_key.lower() in ['set-cookie', 'cache-control', 'expires', 'pragma', 'vary']:
|
| 154 |
final_redirect_headers[h_key] = h_val
|
| 155 |
return Response(response=None, status=target_response.status_code, headers=final_redirect_headers)
|
| 156 |
+
|
| 157 |
+
# مدیریت خطاهای سرور هدف
|
| 158 |
if target_response.status_code >= 400:
|
| 159 |
app.logger.warning(f"Target {target_full_url} error: {target_response.status_code}")
|
| 160 |
+
error_content = target_response.content # ممکن است بزرگ باشد
|
| 161 |
+
error_headers_from_target = {
|
| 162 |
+
k:v for k,v in target_response.headers.items()
|
| 163 |
+
if k.lower() not in ['content-encoding', 'transfer-encoding', 'content-length']
|
| 164 |
+
}
|
| 165 |
return Response(response=error_content, status=target_response.status_code, headers=error_headers_from_target)
|
| 166 |
|
| 167 |
content_type = target_response.headers.get('Content-Type', 'application/octet-stream')
|
| 168 |
+
|
| 169 |
+
excluded_response_headers = [
|
| 170 |
+
'content-encoding', 'transfer-encoding', 'connection', 'keep-alive',
|
| 171 |
+
'x-frame-options', 'strict-transport-security', 'public-key-pins',
|
| 172 |
+
'content-length', 'server', 'x-powered-by', 'date',
|
| 173 |
+
'link', # <<< مهم: حذف هدر Link از پاسخ
|
| 174 |
+
'x-canonical-url' # <<< مهم: حذف هدر احتمالی دیگر برای URL کنونیکال
|
| 175 |
+
]
|
| 176 |
+
final_response_headers = {
|
| 177 |
+
key: value for key, value in target_response.headers.items()
|
| 178 |
+
if key.lower() not in excluded_response_headers
|
| 179 |
+
}
|
| 180 |
final_response_headers['Content-Type'] = content_type
|
|
|
|
| 181 |
final_response_headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
| 182 |
final_response_headers['Pragma'] = 'no-cache'
|
| 183 |
final_response_headers['Expires'] = '0'
|
| 184 |
final_response_headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
|
| 185 |
|
| 186 |
def generate_response_content_stream():
|
| 187 |
+
# ... (کد طولانی شما برای generate_response_content_stream با تزریق CSS/JS) ...
|
| 188 |
+
# این بخش بسیار طولانی است و برای سادگی اینجا خلاصه شده.
|
| 189 |
+
# اگر این کد Flask اجرا شود، این تابع مسئول تغییر محتوای HTML خواهد بود.
|
| 190 |
+
# در حال حاضر با تنظیمات Nginx، این تابع اجرا نمیشود.
|
| 191 |
is_html_content_type = 'text/html' in content_type.lower()
|
|
|
|
| 192 |
base_tag_injected_flag = False
|
| 193 |
+
css_and_js_injected_flag = False
|
| 194 |
|
| 195 |
current_buffer = b''
|
| 196 |
charset_match = re.search(r'charset=([\w-]+)', content_type, re.IGNORECASE)
|
| 197 |
html_processing_encoding = charset_match.group(1) if charset_match else 'utf-8'
|
| 198 |
|
| 199 |
+
# محاسبه base href (همانطور که داشتید)
|
| 200 |
+
# ...
|
| 201 |
+
# base_tag_html_to_inject = f'<base href="{escaped_href_for_final_base_tag}">'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
|
| 203 |
+
# js_replacements_json = json.dumps(SIMPLE_TEXT_REPLACEMENTS)
|
| 204 |
+
# mutation_observer_script = f"""...""" # اسکریپت JS شما
|
|
|
|
|
|
|
|
|
|
| 205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
try:
|
| 207 |
for chunk_data in target_response.iter_content(chunk_size=8192): # chunk_size میتواند تنظیم شود
|
| 208 |
if not chunk_data: continue
|
| 209 |
|
| 210 |
if is_html_content_type:
|
| 211 |
current_buffer += chunk_data
|
| 212 |
+
# ... (منطق پردازش بافر و تزریق HTML/CSS/JS شما) ...
|
| 213 |
+
# مثال ساده:
|
| 214 |
+
# buffer_as_string = current_buffer.decode(html_processing_encoding, errors='replace')
|
| 215 |
+
# processed_chunk_string = buffer_as_string
|
| 216 |
+
# if not css_and_js_injected_flag and '<head>' in processed_chunk_string:
|
| 217 |
+
# # تزریق CSS و base tag
|
| 218 |
+
# css_and_js_injected_flag = True
|
| 219 |
+
# if '</body>' in processed_chunk_string:
|
| 220 |
+
# # تزریق JS
|
| 221 |
+
# yield processed_chunk_string.encode(html_processing_encoding, errors='replace')
|
| 222 |
+
# current_buffer = b''
|
| 223 |
+
# این بخش باید کامل از کد شما کپی شود اگر میخواهید از Flask استفاده کنید.
|
| 224 |
+
# برای سادگی، اینجا فقط محتوای خام را برمیگردانیم:
|
| 225 |
+
yield chunk_data # در حالت فعلی، برای اینکه کد خلاصه شود.
|
| 226 |
+
# اگر این کد Flask فعال شود، باید منطق پردازش HTML شما اینجا باشد.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
else: # محتوای غیر HTML
|
|
|
|
|
|
|
|
|
|
| 228 |
yield chunk_data
|
| 229 |
|
| 230 |
# پردازش باقیمانده بافر (اگر چیزی مانده)
|
| 231 |
if current_buffer:
|
| 232 |
if is_html_content_type:
|
| 233 |
+
# ... (منطق پردازش باقیمانده بافر HTML شما) ...
|
| 234 |
+
yield current_buffer # باز هم، برای خلاصه بودن.
|
| 235 |
+
else:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
yield current_buffer
|
| 237 |
except Exception as stream_err:
|
| 238 |
app.logger.error(f"Error during HTML/Stream processing for {target_full_url}: {stream_err}")
|
|
|
|
|
|
|
| 239 |
finally:
|
| 240 |
target_response.close()
|
| 241 |
|
|
|
|
| 242 |
if request.method == 'HEAD':
|
| 243 |
return Response(response=None, status=target_response.status_code, headers=final_response_headers)
|
| 244 |
else:
|
|
|
|
| 247 |
except requests.exceptions.Timeout:
|
| 248 |
app.logger.error(f"Timeout ({REQUEST_TIMEOUT}s) fetching {target_full_url}")
|
| 249 |
return "Error: Request to target site timed out.", 504
|
| 250 |
+
except requests.exceptions.HTTPError as http_err:
|
| 251 |
+
app.logger.error(f"HTTPError {http_err.response.status_code} from {target_full_url}. Resp: {http_err.response.text[:200]}")
|
| 252 |
+
# ... (کد مدیریت خطا) ...
|
| 253 |
+
return "HTTP Error from target.", 502 # یا وضعیت خطای اصلی
|
| 254 |
except requests.exceptions.ConnectionError as conn_err:
|
| 255 |
app.logger.error(f"ConnectionError for {target_full_url}: {conn_err}")
|
| 256 |
+
return f"Error: Could not connect to target.", 502
|
| 257 |
+
except requests.exceptions.RequestException as req_err:
|
| 258 |
app.logger.error(f"RequestException for {target_full_url}: {req_err}")
|
| 259 |
+
return f"Error fetching content.", 502
|
| 260 |
+
except Exception as general_err:
|
| 261 |
+
app.logger.exception(f"Unexpected Python error proxying {target_full_url}")
|
| 262 |
+
return f"Unexpected server error.", 500
|
| 263 |
|
| 264 |
if __name__ == '__main__':
|
| 265 |
+
port = int(os.environ.get("PORT", 7860)) # پورت استاندارد Hugging Face Spaces
|
| 266 |
+
# debug_mode = os.environ.get('FLASK_ENV') == 'development' or os.environ.get('FLASK_DEBUG', '0') == '1'
|
| 267 |
+
# در Hugging Face معمولا FLASK_DEBUG تنظیم نمیشود، مگر اینکه خودتان در Dockerfile یا secrets تنظیم کنید
|
| 268 |
+
debug_mode = os.environ.get('FLASK_DEBUG', '0') == '1'
|
|
|
|
|
|
|
| 269 |
|
| 270 |
+
print(f"INFO: Starting Flask app on host 0.0.0.0, port {port}, debug: {debug_mode}")
|
| 271 |
app.run(host='0.0.0.0', port=port, debug=debug_mode)
|