from flask import Flask, request, Response import requests import logging from urllib.parse import urljoin from urllib.parse import quote from bs4 import BeautifulSoup import re # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = Flask(__name__) # Target URL base TARGET_BASE_URL = "https://superetka.com" @app.route('/proxy_image') def proxy_image(): from urllib.parse import unquote image_url = unquote(request.args.get('url')) if not image_url: return 'No URL provided', 400 try: # Get headers from the incoming request headers = {key: value for key, value in request.headers if key.lower() != 'host'} headers['Referer'] = TARGET_BASE_URL headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36' # Forward the request to get the image resp = requests.get( image_url, headers=headers, cookies=request.cookies, timeout=30, allow_redirects=True ) # Check response status if resp.status_code != 200: logger.error(f"Error response from target: {resp.status_code}") return f"Error: {resp.status_code}", resp.status_code return Response(resp.content, mimetype=resp.headers.get('Content-Type', 'image/jpeg')) except Exception as e: logger.error(f"Error proxying image: {str(e)}") return str(e), 500 @app.route('/', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']) @app.route('/', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']) def proxy(path): # Construct target URL with path and query parameters if path and path.strip(): # If path is provided, use it url = f"{TARGET_BASE_URL}/{path}" else: # Default to wap.php if no path is provided url = f"{TARGET_BASE_URL}/etka/wap.php" # If there are query parameters, append them to the URL if request.query_string: url = f"{url}?{request.query_string.decode('utf-8')}" # Log the constructed URL logger.info(f"Constructed URL: {url}") # Log the request logger.info(f"Received request: {request.method} {request.url}") logger.info(f"Forwarding to: {url}") # Get headers from the incoming request headers = {key: value for key, value in request.headers if key.lower() != 'host'} try: # Forward the request to the target server # Don't pass params separately as they're already in the URL resp = requests.request( method=request.method, url=url, headers=headers, data=request.get_data(), cookies=request.cookies, allow_redirects=False, timeout=30 ) # Log the response logger.info(f"Received response from target: {resp.status_code}") # Check if response is HTML and filter content if needed content_type = resp.headers.get('Content-Type', '') if 'text/html' in content_type: # Parse HTML content html_content = resp.content.decode('utf-8', errors='ignore') soup = BeautifulSoup(html_content, 'html.parser') # Filter out "Полная версия ETKA" for element in soup.find_all(string=re.compile('Полная версия ETKA')): # Replace the text with empty string element.replace_with('') # Filter out README content for element in soup.find_all(string=re.compile('README', re.IGNORECASE)): element.replace_with('') # Redirect part number links to Google search # Look for links that contain part numbers (typically in the second column of the table) part_number_links = soup.select('td:nth-child(2) a') for link in part_number_links: # Get the part number from the link text part_number = link.text.strip() # Check if it matches a part number pattern (alphanumeric with possible spaces) if re.match(r'^[A-Z0-9 ]+$', part_number): # Create a Google search URL for this part number with avto.pro google_search_url = f"https://www.google.com/search?q={part_number} avto.pro" # Update the link's href attribute and add target="_blank" to open in new tab link['href'] = google_search_url link['target'] = '_blank' # Update image sources to use our proxy for img in soup.find_all('img'): img_src = img['src'] if not img_src.startswith('http'): img_src = urljoin(TARGET_BASE_URL, img_src) proxy_url = f"/proxy_image?url={quote(img_src)}" img['src'] = proxy_url img['style'] = 'max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin: 10px 0;' # Add "Open Original Image" button next to each image button = soup.new_tag('a') button['href'] = proxy_url button['target'] = '_blank' button['class'] = 'open-image-btn' button.string = 'Открыть оригинал изображения' button['style'] = 'display: inline-block; margin: 10px; padding: 8px 15px; background-color: #4a90e2; border: none; border-radius: 4px; text-decoration: none; color: white; font-weight: 500; transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(0,0,0,0.1);' img.insert_after(button) # Add modal container for fullscreen image view modal_div = soup.new_tag('div') modal_div['id'] = 'imageModal' modal_div['class'] = 'modal' modal_div['style'] = 'display: none;' modal_img = soup.new_tag('img') modal_img['id'] = 'modalImage' modal_img['src'] = '' modal_img['alt'] = 'Fullscreen Image' close_btn = soup.new_tag('span') close_btn['class'] = 'close' close_btn.string = '×' modal_div.append(close_btn) modal_div.append(modal_img) if soup.body: soup.body.append(modal_div) else: body_tag = soup.new_tag('body') body_tag.append(modal_div) if soup.html: soup.html.append(body_tag) else: html_tag = soup.new_tag('html') html_tag.append(body_tag) soup.append(html_tag) # Add JavaScript for modal functionality script_tag = soup.new_tag('script') script_tag.string = ''' document.addEventListener('DOMContentLoaded', function() { var modal = document.getElementById('imageModal'); var modalImg = document.getElementById('modalImage'); var closeBtn = document.getElementsByClassName('close')[0]; var scale = 1; var isDragging = false; var startX, startY, translateX = 0, translateY = 0; // Add zoom controls var zoomControls = document.createElement('div'); zoomControls.className = 'zoom-controls'; zoomControls.innerHTML = ` `; modal.appendChild(zoomControls); // Zoom functions window.changeZoom = function(delta) { scale = Math.min(Math.max(scale + delta, 0.5), 3); applyTransform(); }; window.resetZoom = function() { scale = 1; translateX = 0; translateY = 0; applyTransform(); }; function applyTransform() { modalImg.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; } // Mouse wheel zoom modal.addEventListener('wheel', function(e) { e.preventDefault(); const delta = e.deltaY * -0.001; scale = Math.min(Math.max(scale + delta, 0.5), 3); applyTransform(); }); // Touch gestures for mobile let initialDistance = 0; modal.addEventListener('touchstart', function(e) { if (e.touches.length === 2) { initialDistance = Math.hypot( e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY ); } }); modal.addEventListener('touchmove', function(e) { if (e.touches.length === 2) { e.preventDefault(); const currentDistance = Math.hypot( e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY ); const delta = (currentDistance - initialDistance) * 0.01; scale = Math.min(Math.max(scale + delta, 0.5), 3); initialDistance = currentDistance; applyTransform(); } }); // Add click event to all images document.querySelectorAll('img:not(#modalImage)').forEach(function(img) { img.style.cursor = 'pointer'; img.addEventListener('click', function() { modal.style.display = 'flex'; modalImg.src = this.src; document.body.style.overflow = 'hidden'; scale = 1; translateX = 0; translateY = 0; applyTransform(); setTimeout(function() { modal.classList.add('show'); }, 10); }); }); // Close modal on click function closeModal() { modal.classList.remove('show'); setTimeout(function() { modal.style.display = 'none'; document.body.style.overflow = 'auto'; }, 300); } closeBtn.onclick = closeModal; modal.onclick = function(e) { if (e.target === modal) closeModal(); }; // Close on escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeModal(); }); // Image dragging modalImg.addEventListener('mousedown', function(e) { isDragging = true; startX = e.clientX - translateX; startY = e.clientY - translateY; modalImg.style.cursor = 'grabbing'; }); document.addEventListener('mousemove', function(e) { if (isDragging) { translateX = e.clientX - startX; translateY = e.clientY - startY; applyTransform(); } }); document.addEventListener('mouseup', function() { isDragging = false; modalImg.style.cursor = 'grab'; }); }); ''' if soup.body: soup.body.append(script_tag) # Add CSS styles to improve design style_tag = soup.new_tag('style') style_tag.string = ''' :root { --primary-gradient: linear-gradient(135deg, #3498db 0%, #2980b9 100%); --hover-gradient: linear-gradient(135deg, #2980b9 0%, #2471a3 100%); --bg-gradient: linear-gradient(135deg, #f5f7fa 0%, #e4e9f2 100%); --card-shadow: 0 8px 20px rgba(0,0,0,0.08); --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } body { font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; line-height: 1.7; padding: 30px; max-width: 1400px; margin: 0 auto; background: var(--bg-gradient); color: #2c3e50; min-height: 100vh; } .breadcrumb { display: flex; flex-wrap: wrap; gap: 15px; align-items: center; padding: 20px; background: rgba(255, 255, 255, 0.98); border-radius: 16px; box-shadow: var(--card-shadow); margin-bottom: 30px; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.8); } .breadcrumb a { text-decoration: none; color: #3498db; padding: 8px 16px; border-radius: 8px; transition: var(--transition); font-weight: 500; background: rgba(52, 152, 219, 0.1); } .breadcrumb a:hover { background: var(--primary-gradient); transform: translateY(-2px); color: white; } .breadcrumb > span:after { content: '›'; margin-left: 15px; color: #95a5a6; font-size: 1.4em; font-weight: 300; } ul { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; margin: 30px 0; } li { background: rgba(255, 255, 255, 0.98); border-radius: 16px; overflow: hidden; transition: var(--transition); box-shadow: var(--card-shadow); border: 1px solid rgba(255, 255, 255, 0.8); } li:hover { transform: translateY(-5px); box-shadow: 0 12px 25px rgba(0,0,0,0.1); } li a { display: block; padding: 20px; text-decoration: none; color: #2c3e50; font-weight: 500; transition: var(--transition); background: rgba(255, 255, 255, 0.98); } li a:hover { background: var(--primary-gradient); color: white; } table { width: 100%; border-collapse: separate; border-spacing: 0; background: rgba(255, 255, 255, 0.98); border-radius: 16px; overflow: hidden; box-shadow: var(--card-shadow); margin: 30px 0; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.8); } td { padding: 20px; border: 1px solid rgba(236, 240, 241, 0.8); transition: var(--transition); } tr:nth-child(even) { background: rgba(245, 247, 250, 0.5); } tr:hover { background: var(--bg-gradient); transform: scale(1.002); } img { border-radius: 16px; box-shadow: var(--card-shadow); transition: var(--transition); max-width: 100%; height: auto; display: block; margin: 10px 0; } img:hover { transform: scale(1.03); box-shadow: 0 12px 25px rgba(0,0,0,0.15); } .open-image-btn { display: inline-block; margin: 15px 0; padding: 12px 24px; background: var(--primary-gradient); color: white !important; border-radius: 12px; text-decoration: none; transition: var(--transition); font-weight: 500; box-shadow: var(--card-shadow); border: 1px solid rgba(255, 255, 255, 0.2); } .open-image-btn:hover { background: var(--hover-gradient); transform: translateY(-3px); box-shadow: 0 12px 25px rgba(0,0,0,0.15); } hr { border: none; height: 1px; background: linear-gradient(to right, transparent, rgba(44, 62, 80, 0.2), transparent); margin: 30px 0; } .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.9); z-index: 1000; justify-content: center; align-items: center; opacity: 0; transition: opacity 0.3s ease; } .modal.show { opacity: 1; } .modal img { max-width: 90%; max-height: 90vh; margin: auto; display: block; box-shadow: 0 0 30px rgba(0, 0, 0, 0.5); transform: scale(0.9); transition: transform 0.3s ease; cursor: grab; transform-origin: center center; } .modal.show img { transform: scale(1); } .zoom-controls { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 10px; z-index: 1001; } .zoom-btn { background: rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.4); color: white; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 16px; transition: all 0.2s ease; } .zoom-btn:hover { background: rgba(255, 255, 255, 0.3); } .close { position: absolute; top: 15px; right: 25px; color: #f1f1f1; font-size: 40px; font-weight: bold; cursor: pointer; z-index: 1001; transition: color 0.3s ease; } .close:hover { color: #3498db; } @media (max-width: 768px) { body { padding: 15px; } .breadcrumb { flex-direction: column; align-items: flex-start; padding: 15px; } ul { grid-template-columns: 1fr; gap: 15px; } table { font-size: 14px; } td { padding: 12px; } .open-image-btn { width: 100%; text-align: center; } .modal img { max-width: 95%; max-height: 95vh; } .close { top: 10px; right: 15px; } } ''' # Check if head exists, if not create it if not soup.head: head_tag = soup.new_tag('head') if soup.html: soup.html.insert(0, head_tag) else: html_tag = soup.new_tag('html') html_tag.append(head_tag) soup.append(html_tag) soup.head.append(style_tag) # Convert navigation links to breadcrumb nav_links = soup.find_all('a', href=True) breadcrumb_div = soup.new_tag('div') breadcrumb_div['class'] = 'breadcrumb' # Find the main navigation container nav_container = None for link in nav_links: if link.parent.name == 'font' and link.parent.parent.name == 'td': nav_container = link.parent.parent break if nav_container: nav_links = nav_container.find_all('a', href=True) current_span = None for link in nav_links: # Skip empty links or those without text if not link.string or not link.string.strip(): continue # Skip navigation arrows and special characters if any(char in link.string for char in ['>', '<', '→', '←']): continue # Create new span for each link current_span = soup.new_tag('span') breadcrumb_div.append(current_span) # Clone the link to avoid modifying original new_link = soup.new_tag('a', href=link['href']) new_link.string = link.string.strip() current_span.append(new_link) # Replace old navigation with new breadcrumb if we found valid navigation if nav_container: nav_container.replace_with(breadcrumb_div) # Create a Flask response object with filtered content response = Response( soup.encode(), status=resp.status_code, content_type='text/html; charset=utf-8' ) else: # Create a Flask response object with original content response = Response( resp.content, status=resp.status_code ) # Copy headers from the target response for key, value in resp.headers.items(): if key.lower() not in ('transfer-encoding', 'content-encoding', 'content-length'): response.headers[key] = value # Copy cookies from target response for cookie in resp.cookies: response.set_cookie( key=cookie.name, value=cookie.value, # domain=cookie.domain, path=cookie.path, expires=cookie.expires, secure=cookie.secure, httponly=cookie.httponly ) return response except requests.RequestException as e: logger.error(f"Error forwarding request: {str(e)}") return Response(f"Error forwarding request: {str(e)}", status=500) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)