|
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 |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
logger = logging.getLogger(__name__) |
|
|
|
app = Flask(__name__) |
|
|
|
|
|
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: |
|
|
|
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' |
|
|
|
|
|
resp = requests.get( |
|
image_url, |
|
headers=headers, |
|
cookies=request.cookies, |
|
timeout=30, |
|
allow_redirects=True |
|
) |
|
|
|
|
|
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('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']) |
|
def proxy(path): |
|
|
|
if path and path.strip(): |
|
|
|
url = f"{TARGET_BASE_URL}/{path}" |
|
else: |
|
|
|
url = f"{TARGET_BASE_URL}/etka/wap.php" |
|
|
|
|
|
if request.query_string: |
|
url = f"{url}?{request.query_string.decode('utf-8')}" |
|
|
|
|
|
logger.info(f"Constructed URL: {url}") |
|
|
|
|
|
logger.info(f"Received request: {request.method} {request.url}") |
|
logger.info(f"Forwarding to: {url}") |
|
|
|
|
|
headers = {key: value for key, value in request.headers if key.lower() != 'host'} |
|
|
|
try: |
|
|
|
|
|
resp = requests.request( |
|
method=request.method, |
|
url=url, |
|
headers=headers, |
|
data=request.get_data(), |
|
cookies=request.cookies, |
|
allow_redirects=False, |
|
timeout=30 |
|
) |
|
|
|
|
|
logger.info(f"Received response from target: {resp.status_code}") |
|
|
|
|
|
content_type = resp.headers.get('Content-Type', '') |
|
if 'text/html' in content_type: |
|
|
|
html_content = resp.content.decode('utf-8', errors='ignore') |
|
soup = BeautifulSoup(html_content, 'html.parser') |
|
|
|
|
|
for element in soup.find_all(string=re.compile('Полная версия ETKA')): |
|
|
|
element.replace_with('') |
|
|
|
|
|
for element in soup.find_all(string=re.compile('README', re.IGNORECASE)): |
|
element.replace_with('') |
|
|
|
|
|
|
|
part_number_links = soup.select('td:nth-child(2) a') |
|
for link in part_number_links: |
|
|
|
part_number = link.text.strip() |
|
|
|
if re.match(r'^[A-Z0-9 ]+$', part_number): |
|
|
|
google_search_url = f"https://www.google.com/search?q={part_number} avto.pro" |
|
|
|
link['href'] = google_search_url |
|
link['target'] = '_blank' |
|
|
|
|
|
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;' |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
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 = ` |
|
<button class="zoom-btn" onclick="changeZoom(0.1)">+</button> |
|
<button class="zoom-btn" onclick="changeZoom(-0.1)">-</button> |
|
<button class="zoom-btn" onclick="resetZoom()">Reset</button> |
|
`; |
|
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) |
|
|
|
|
|
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; |
|
} |
|
} |
|
''' |
|
|
|
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) |
|
|
|
|
|
nav_links = soup.find_all('a', href=True) |
|
breadcrumb_div = soup.new_tag('div') |
|
breadcrumb_div['class'] = 'breadcrumb' |
|
|
|
|
|
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: |
|
|
|
if not link.string or not link.string.strip(): |
|
continue |
|
|
|
|
|
if any(char in link.string for char in ['>', '<', '→', '←']): |
|
continue |
|
|
|
|
|
current_span = soup.new_tag('span') |
|
breadcrumb_div.append(current_span) |
|
|
|
|
|
new_link = soup.new_tag('a', href=link['href']) |
|
new_link.string = link.string.strip() |
|
current_span.append(new_link) |
|
|
|
|
|
if nav_container: |
|
nav_container.replace_with(breadcrumb_div) |
|
|
|
|
|
response = Response( |
|
soup.encode(), |
|
status=resp.status_code, |
|
content_type='text/html; charset=utf-8' |
|
) |
|
else: |
|
|
|
response = Response( |
|
resp.content, |
|
status=resp.status_code |
|
) |
|
|
|
|
|
for key, value in resp.headers.items(): |
|
if key.lower() not in ('transfer-encoding', 'content-encoding', 'content-length'): |
|
response.headers[key] = value |
|
|
|
|
|
for cookie in resp.cookies: |
|
response.set_cookie( |
|
key=cookie.name, |
|
value=cookie.value, |
|
|
|
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) |