pay / main.py
Starchik1's picture
Update main.py
9e0ec62 verified
raw
history blame
26.1 kB
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('/<path:path>', 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 = `
<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)
# 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)