pytpswapper / app.py
huijio's picture
Update app.py
636c11a verified
import gradio as gr
import base64
from urllib.parse import urlparse, urlunparse, parse_qs, urlencode
import re
import requests
import json
import time
from datetime import datetime
import pandas as pd
# Storage for history (in production, use a database)
history_data = []
TEST_TIMEOUT = 10 # seconds
def base64_url_decode(encoded_str):
"""Decode URL-safe base64 string"""
encoded_str = encoded_str.replace('_', '/').replace('-', '+')
padding = len(encoded_str) % 4
if padding:
encoded_str += '=' * (4 - padding)
try:
decoded_bytes = base64.b64decode(encoded_str)
return decoded_bytes.decode('utf-8')
except Exception as e:
raise ValueError(f"Base64 decoding error: {str(e)}")
def base64_url_encode(decoded_str):
"""Encode string to URL-safe base64"""
encoded_bytes = base64.b64encode(decoded_str.encode('utf-8'))
encoded_str = encoded_bytes.decode('utf-8')
return encoded_str.replace('/', '_').replace('+', '-').rstrip('=')
def is_valid_ip(ip):
"""Validate IP address format"""
ip_pattern = r'^(\d{1,3}\.){3}\d{1,3}$'
if not re.match(ip_pattern, ip):
return False
parts = ip.split('.')
for part in parts:
if not 0 <= int(part) <= 255:
return False
return True
def extract_ip_from_url(url):
"""Extract IP address from URL"""
try:
parsed = urlparse(url)
return parsed.hostname
except:
return None
def test_url_connectivity(url):
"""Test if URL is accessible"""
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(url, timeout=TEST_TIMEOUT, headers=headers, verify=False)
status_info = {
'status': 'working' if response.status_code == 200 else 'not_working',
'status_code': response.status_code,
'response_time': response.elapsed.total_seconds(),
'timestamp': datetime.now().isoformat()
}
return status_info
except requests.exceptions.RequestException as e:
return {
'status': 'not_working',
'status_code': None,
'error': str(e),
'response_time': None,
'timestamp': datetime.now().isoformat()
}
def process_single_url(original_url, new_ip):
"""Process a single URL with IP replacement"""
try:
parsed = urlparse(original_url)
old_ip = parsed.hostname
# Replace IP in main URL
new_netloc = new_ip
if parsed.port:
new_netloc += f":{parsed.port}"
# Parse query parameters
query_params = parse_qs(parsed.query)
# Process 'r' parameter if it exists
if 'r' in query_params:
try:
r_param = query_params['r'][0]
decoded_r = base64_url_decode(r_param)
# Replace IP in decoded URL
decoded_parsed = urlparse(decoded_r)
decoded_new_netloc = new_ip
if decoded_parsed.port:
decoded_new_netloc += f":{decoded_parsed.port}"
new_decoded_url = decoded_parsed._replace(netloc=decoded_new_netloc).geturl()
new_encoded_r = base64_url_encode(new_decoded_url)
query_params['r'] = [new_encoded_r]
except Exception as e:
print(f"Note: Could not process 'r' parameter: {e}")
# Rebuild query string
new_query = urlencode(query_params, doseq=True)
# Construct final URL
new_url = parsed._replace(netloc=new_netloc, query=new_query).geturl()
return new_url, old_ip
except Exception as e:
raise ValueError(f"Error processing URL: {str(e)}")
def decode_r_parameter(url):
"""Decode the r parameter from URL"""
try:
parsed = urlparse(url)
query_params = parse_qs(parsed.query)
if 'r' in query_params:
r_param = query_params['r'][0]
decoded_r = base64_url_decode(r_param)
return decoded_r
else:
return "No 'r' parameter found in URL"
except Exception as e:
return f"Error decoding 'r' parameter: {str(e)}"
def ip_swapper_handler(original_url, single_ip, multiple_ips, test_connectivity):
"""Main handler function for IP swapping"""
try:
if not original_url.strip():
return "Please enter a URL", "", "", gr.update(visible=False), "about:blank", "No URL to display"
# Extract original IP for display
original_ip = extract_ip_from_url(original_url)
# Process IP inputs
ips_to_process = []
if single_ip.strip():
if not is_valid_ip(single_ip):
return f"Error: Invalid IP address format: {single_ip}", "", "", gr.update(visible=False), "about:blank", "Invalid IP format"
ips_to_process.append(single_ip)
if multiple_ips.strip():
ip_list = [ip.strip() for ip in multiple_ips.split('\n') if ip.strip()]
for ip in ip_list:
if not is_valid_ip(ip):
return f"Error: Invalid IP address format: {ip}", "", "", gr.update(visible=False), "about:blank", f"Invalid IP: {ip}"
ips_to_process.extend(ip_list)
if not ips_to_process:
return "Please enter at least one valid IP address", "", "", gr.update(visible=False), "about:blank", "No IPs provided"
# Process URLs
results = []
if len(ips_to_process) == 1:
new_url, old_ip = process_single_url(original_url, ips_to_process[0])
results.append({
'ip': ips_to_process[0],
'url': new_url,
'original_url': original_url
})
result_text = new_url
iframe_url = new_url
iframe_title = f"Testing: {ips_to_process[0]}"
else:
result_lines = []
for i, ip in enumerate(ips_to_process, 1):
new_url, old_ip = process_single_url(original_url, ip)
results.append({
'ip': ip,
'url': new_url,
'original_url': original_url
})
result_lines.append(f"Option {i} (IP: {ip}):")
result_lines.append(new_url)
result_lines.append("")
result_text = "\n".join(result_lines)
# For multiple IPs, show the first one in iframe
iframe_url = results[0]['url'] if results else "about:blank"
iframe_title = f"Testing: {results[0]['ip']}" if results else "Multiple IPs"
# Get decoded r parameter
decoded_r = decode_r_parameter(original_url)
# Test connectivity if requested
test_results = []
if test_connectivity and results:
progress = gr.Progress()
progress(0, desc="Testing URLs...")
for i, result in enumerate(results):
progress(i / len(results), desc=f"Testing {result['ip']}...")
test_result = test_url_connectivity(result['url'])
# Add to history
history_entry = {
'timestamp': datetime.now().isoformat(),
'original_url': original_url,
'ip': result['ip'],
'modified_url': result['url'],
'status': test_result['status'],
'status_code': test_result.get('status_code'),
'response_time': test_result.get('response_time'),
'error': test_result.get('error')
}
history_data.append(history_entry)
test_results.append({
'ip': result['ip'],
'url': result['url'],
'status': test_result['status'],
'status_code': test_result.get('status_code'),
'response_time': test_result.get('response_time')
})
# Update result text with status
if len(test_results) == 1:
status = "✅ WORKING" if test_results[0]['status'] == 'working' else "❌ NOT WORKING"
result_text = f"{result_text}\n\n---\nStatus: {status}"
if test_results[0].get('status_code'):
result_text += f" (HTTP {test_results[0]['status_code']})"
if test_results[0].get('response_time'):
result_text += f" - Response time: {test_results[0]['response_time']:.2f}s"
else:
result_lines = []
for test in test_results:
status = "✅ WORKING" if test['status'] == 'working' else "❌ NOT WORKING"
status_info = f" - {status}"
if test.get('status_code'):
status_info += f" (HTTP {test['status_code']})"
if test.get('response_time'):
status_info += f" - {test['response_time']:.2f}s"
result_lines.append(f"Option - IP: {test['ip']}{status_info}")
result_lines.append(test['url'])
result_lines.append("")
result_text = "\n".join(result_lines)
# Show history button if we have test results
history_visible = len(test_results) > 0
return original_url, result_text, decoded_r, gr.update(visible=history_visible), iframe_url, iframe_title
except Exception as e:
return f"Error: {str(e)}", "", "", gr.update(visible=False), "about:blank", f"Error: {str(e)}"
def get_history_df():
"""Convert history data to DataFrame for display"""
if not history_data:
return pd.DataFrame(columns=['Timestamp', 'IP', 'Status', 'HTTP Code', 'Response Time', 'Error'])
df_data = []
for entry in history_data[-50:]: # Show last 50 entries
df_data.append({
'Timestamp': entry['timestamp'][11:19], # Show only time
'IP': entry['ip'],
'Status': '✅ Working' if entry['status'] == 'working' else '❌ Not Working',
'HTTP Code': entry.get('status_code', 'N/A'),
'Response Time': f"{entry.get('response_time', 0):.2f}s" if entry.get('response_time') else 'N/A',
'Error': entry.get('error', '')
})
return pd.DataFrame(df_data)
def clear_all():
"""Clear all inputs and outputs"""
return "", "", "", "", "", gr.update(visible=False), "about:blank", "Ready for testing..."
def show_history():
"""Display history in a DataFrame"""
history_df = get_history_df()
if history_df.empty:
return "No history data available. Test some URLs first!"
# Count stats
working_count = len([h for h in history_data if h['status'] == 'working'])
total_count = len(history_data)
stats_html = f"<div style='margin-bottom: 15px; padding: 10px; background: #f0f8ff; border-radius: 5px;'>"
stats_html += f"<strong>Stats:</strong> {working_count}/{total_count} IPs working "
stats_html += f"({working_count/total_count*100:.1f}% success rate)"
stats_html += "</div>"
return stats_html + history_df.to_html(classes='history-table', index=False, escape=False)
def export_history():
"""Export history as CSV"""
if not history_data:
return None
df = pd.DataFrame(history_data)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"ip_swapper_history_{timestamp}.csv"
df.to_csv(filename, index=False)
return filename
def load_url_in_iframe(url, ip_address):
"""Load a specific URL in the iframe"""
if not url or url.startswith("Error:") or url.startswith("Please enter"):
return "about:blank", "No valid URL to display"
# Validate URL format
try:
parsed = urlparse(url)
if parsed.scheme and parsed.netloc:
return url, f"Testing: {ip_address}"
else:
return "about:blank", "Invalid URL format"
except:
return "about:blank", "Invalid URL"
# Custom CSS for better styling
custom_css = """
.gradio-container {
font-family: 'Inter', sans-serif;
}
.header {
text-align: center;
margin-bottom: 2rem;
}
.gradient-text {
background: linear-gradient(90deg, #8B5CF6, #EC4899, #EF4444);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.code-block {
font-family: 'Courier New', monospace;
background-color: #1E293B;
color: #E2E8F0;
border-radius: 0.5rem;
padding: 1rem;
white-space: pre-wrap;
word-break: break-all;
}
.history-table {
width: 100%;
border-collapse: collapse;
}
.history-table th, .history-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
.history-table th {
background-color: #4f46e5;
color: white;
}
.history-table tr:nth-child(even) {
background-color: #f2f2f2;
}
.status-working {
color: #10b981;
font-weight: bold;
}
.status-not-working {
color: #ef4444;
font-weight: bold;
}
.test-btn {
background: linear-gradient(90deg, #10b981, #059669) !important;
}
.iframe-container {
border: 2px solid #e5e7eb;
border-radius: 10px;
overflow: hidden;
margin-top: 10px;
}
.iframe-header {
background: #4f46e5;
color: white;
padding: 8px 12px;
font-weight: bold;
display: flex;
justify-content: between;
align-items: center;
}
.iframe-content {
height: 500px;
width: 100%;
}
.url-test-buttons {
margin: 10px 0;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.url-test-btn {
background: #4f46e5 !important;
color: white !important;
border: none !important;
}
.url-test-btn:hover {
background: #4338ca !important;
}
.example-box {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px;
border-radius: 10px;
margin: 10px 0;
}
.example-steps {
background: #f8f9fa;
border-left: 4px solid #4f46e5;
padding: 15px;
margin: 15px 0;
border-radius: 0 8px 8px 0;
}
"""
# Create Gradio interface
with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
# Header
gr.HTML("""
<div class="header">
<h1 class="gradient-text" style="font-size: 2.5rem; font-weight: bold; margin-bottom: 0.5rem;">
🔄 IP Swapper Wizard
</h1>
<p style="color: #6B7280; font-size: 1.1rem;">
Swap IP addresses in URLs while maintaining all parameters • Test Connectivity • Track History • Live Preview
</p>
</div>
""")
# Example Section
with gr.Accordion("🎯 Real Example - Try This!", open=True):
gr.Markdown("""
<div class="example-box">
<h3>🚀 Quick Start Example</h3>
<p><strong>We've pre-loaded a real URL for you to test:</strong></p>
</div>
<div class="example-steps">
<h4>📝 Try This Example:</h4>
<ol>
<li><strong>URL is already loaded</strong> below (contains IP <code>51.158.55.104</code>)</li>
<li><strong>Enter new IPs</strong> in either field:
<ul>
<li>Single IP: <code>192.168.1.100</code></li>
<li>Multiple IPs (one per line):
<pre>192.168.1.100
10.0.0.50
172.16.0.25</pre>
</li>
</ul>
</li>
<li><strong>Check "Test connectivity"</strong> to automatically test URLs</li>
<li><strong>Click "Swap IP(s)"</strong> to generate and test new URLs</li>
<li><strong>Test live</strong> using the iframe preview buttons</li>
</ol>
</div>
<div style="background: #fffbeb; padding: 10px; border-radius: 5px; border-left: 4px solid #f59e0b;">
<strong>💡 What happens:</strong> The tool will replace <code>51.158.55.104</code> with your new IP(s) in both the main URL AND the encoded 'r' parameter!
</div>
""")
with gr.Row():
with gr.Column(scale=1):
# Input Section
with gr.Group():
gr.Markdown("### 📥 Input Parameters")
url_input = gr.Textbox(
label="Enter your URL:",
placeholder="Paste URL here...",
value="https://51.158.55.104/__cpi.php?s=UkQ2YXlSaWJuc3ZoeGR2dG04WW9LamZUQTBwaG1wTzM1aCtMajhkTXRaV1BwQ3lCSG0vWmxUV2lkVDVlc2pveHFuM0RaY0ZDaUgwNzdkMnFMWVJnY1pzSFNXYXB6c1EzRXpqVFViNzBqSlE9&r=aHR0cHM6Ly81MS4xNTguNTUuMTA0L3B0cC8%2FdXNlcj1wbGF0Zm9ybXNpbmNvbWUmc3ViaWQ9MTMxMzcwJl9fY3BvPWFIUjBjSE02THk5aFpHNWhaR1V1Ym1WMA%3D%3D&__cpo=1",
lines=3
)
single_ip_input = gr.Textbox(
label="Single IP Address:",
placeholder="e.g., 192.168.1.100",
max_lines=1
)
multiple_ips_input = gr.Textbox(
label="Multiple IP Addresses:",
placeholder="Enter one IP per line\n192.168.1.100\n10.0.0.50\n172.16.0.25",
lines=4
)
test_connectivity = gr.Checkbox(
label="🔍 Test URL connectivity after swapping",
value=True,
info="Test if the modified URLs are accessible"
)
# Action Buttons
with gr.Row():
process_btn = gr.Button("🔄 Swap IP(s)", variant="primary", size="lg")
clear_btn = gr.Button("🗑️ Clear All", variant="secondary")
decode_btn = gr.Button("🔍 Decode Only", variant="secondary")
with gr.Column(scale=1):
# Output Section
with gr.Group():
gr.Markdown("### 📤 Results")
original_url_output = gr.Textbox(
label="Original URL:",
interactive=False,
lines=2
)
modified_url_output = gr.Textbox(
label="Modified URL(s):",
interactive=False,
lines=4
)
decoded_r_output = gr.Textbox(
label="Decoded 'r' Parameter:",
interactive=False,
lines=3
)
# Hidden history button that becomes visible after testing
history_btn = gr.Button(
"📊 Show Test History",
visible=False,
variant="secondary"
)
# Live Preview Section
with gr.Accordion("🌐 Live URL Preview & Testing", open=True):
gr.Markdown("**Test your generated URLs directly in the browser below:**")
with gr.Row():
with gr.Column(scale=3):
iframe_title = gr.Textbox(
label="Current Preview:",
interactive=False,
max_lines=1,
value="Ready for testing..."
)
# URL testing buttons will be dynamically generated
url_test_buttons = gr.HTML(
value="<div style='padding: 10px; background: #f3f4f6; border-radius: 5px; text-align: center;'>Generated URLs will appear here after processing</div>"
)
with gr.Column(scale=7):
iframe_component = gr.HTML(
value="""
<div class="iframe-container">
<div class="iframe-header">
<span>🔍 Live Browser Preview</span>
</div>
<iframe src="about:blank" class="iframe-content" id="preview-iframe"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
loading="lazy"></iframe>
</div>
"""
)
# History Section
with gr.Accordion("📊 Test History & Analytics", open=False):
with gr.Row():
refresh_history_btn = gr.Button("🔄 Refresh History", size="sm")
export_history_btn = gr.Button("📤 Export History as CSV", size="sm")
clear_history_btn = gr.Button("🗑️ Clear History", size="sm", variant="stop")
history_output = gr.HTML(label="Test History")
# Instructions
with gr.Accordion("📖 How It Works", open=False):
gr.Markdown("""
## 🎯 Real Example Walkthrough
**Original URL Analysis:**
```
https://51.158.55.104/__cpi.php?s=...&r=aHR0cHM6Ly81MS4xNTguNTUuMTA0L3B0cC8...
```
**What the tool does:**
1. **Extracts main IP**: `51.158.55.104`
2. **Decodes 'r' parameter**: Contains another URL with the same IP
3. **Replaces IP in both places** with your new IP(s)
4. **Re-encodes 'r' parameter** with new IP
5. **Generates final URL** with swapped IPs
**Step-by-step process:**
1. **Paste your URL** containing IP addresses
2. **Enter IP address(es)** - single or multiple (one per line)
3. **Check 'Test connectivity'** to automatically test URLs
4. **Click 'Swap IP(s)'** to process and test
5. **View results** with status indicators (✅ Working / ❌ Not Working)
6. **Test URLs live** in the built-in browser
7. **Check history** for past test results
**Live Preview Features:**
- Test URLs directly in the app
- Built-in browser with iframe
- Quick navigation between multiple IPs
- Safe sandboxed environment
**Status Indicators:**
- ✅ Working: URL responded with HTTP 200
- ❌ Not Working: Timeout, error, or non-200 response
""")
# Event handlers
process_btn.click(
fn=ip_swapper_handler,
inputs=[url_input, single_ip_input, multiple_ips_input, test_connectivity],
outputs=[original_url_output, modified_url_output, decoded_r_output, history_btn, iframe_component, iframe_title]
).then(
fn=lambda modified_url, single_ip, multiple_ips: generate_url_buttons(modified_url, single_ip, multiple_ips),
inputs=[modified_url_output, single_ip_input, multiple_ips_input],
outputs=[url_test_buttons]
)
decode_btn.click(
fn=lambda url: (url, "", decode_r_parameter(url), gr.update(visible=False), "about:blank", "Decode Only Mode") if url else ("", "", "Please enter a URL", gr.update(visible=False), "about:blank", "No URL"),
inputs=[url_input],
outputs=[original_url_output, modified_url_output, decoded_r_output, history_btn, iframe_component, iframe_title]
).then(
fn=lambda modified_url, single_ip, multiple_ips: "<div style='padding: 10px; background: #f3f4f6; border-radius: 5px; text-align: center;'>Use 'Swap IP(s)' to generate testable URLs</div>",
inputs=[modified_url_output, single_ip_input, multiple_ips_input],
outputs=[url_test_buttons]
)
clear_btn.click(
fn=clear_all,
inputs=[],
outputs=[url_input, single_ip_input, multiple_ips_input, original_url_output, modified_url_output, decoded_r_output, history_btn, iframe_title]
).then(
fn=lambda: "about:blank",
inputs=[],
outputs=[iframe_component]
).then(
fn=lambda: "<div style='padding: 10px; background: #f3f4f6; border-radius: 5px; text-align: center;'>Generated URLs will appear here after processing</div>",
inputs=[],
outputs=[url_test_buttons]
)
# History handlers
history_btn.click(
fn=show_history,
inputs=[],
outputs=[history_output]
)
refresh_history_btn.click(
fn=show_history,
inputs=[],
outputs=[history_output]
)
export_history_btn.click(
fn=export_history,
inputs=[],
outputs=gr.File(label="Download History CSV")
)
clear_history_btn.click(
fn=lambda: (history_data.clear(), "History cleared!"),
inputs=[],
outputs=[history_output]
)
def generate_url_buttons(modified_url, single_ip, multiple_ips):
"""Generate clickable buttons for testing URLs in iframe"""
if not modified_url or modified_url.startswith("Error:") or modified_url.startswith("Please enter"):
return "<div style='padding: 10px; background: #f3f4f6; border-radius: 5px; text-align: center;'>No valid URLs to test</div>"
# Extract URLs from the modified_url text
urls = []
ips = []
if single_ip and is_valid_ip(single_ip):
# Single IP case - extract the URL directly
urls.append(modified_url.split('\n')[0]) # Take first line as URL
ips.append(single_ip)
if multiple_ips:
# Multiple IPs case - parse the formatted output
ip_list = [ip.strip() for ip in multiple_ips.split('\n') if ip.strip() and is_valid_ip(ip)]
lines = modified_url.split('\n')
for i, line in enumerate(lines):
if line.startswith('http'):
# Find the corresponding IP
for ip in ip_list:
if ip in lines[i-1] if i > 0 else False:
urls.append(line)
ips.append(ip)
break
# If we couldn't parse structured output, try to extract URLs directly
if not urls:
import re
url_pattern = r'https?://[^\s]+'
found_urls = re.findall(url_pattern, modified_url)
urls = found_urls
# Use the provided IPs or generate placeholders
if single_ip:
ips = [single_ip] * len(urls)
elif multiple_ips:
ip_list = [ip.strip() for ip in multiple_ips.split('\n') if ip.strip()]
ips = ip_list[:len(urls)]
else:
ips = [f"IP_{i+1}" for i in range(len(urls))]
if not urls:
return "<div style='padding: 10px; background: #f3f4f6; border-radius: 5px; text-align: center;'>No valid URLs found in output</div>"
# Generate buttons HTML
buttons_html = '<div class="url-test-buttons">'
buttons_html += '<strong>Test URLs in Live Preview:</strong><br>'
for i, (url, ip) in enumerate(zip(urls, ips)):
button_id = f"url-btn-{i}"
buttons_html += f'''
<button class="url-test-btn" onclick="loadIframe('{url}', '{ip}')" style="margin: 2px; padding: 5px 10px; border-radius: 4px;">
🌐 Test {ip}
</button>
'''
buttons_html += '</div>'
buttons_html += '''
<script>
function loadIframe(url, ip) {
const iframe = document.getElementById('preview-iframe');
const title = document.querySelector('[data-testid="iframe_title"] input');
iframe.src = url;
if (title) title.value = 'Testing: ' + ip;
// Update Gradio state (optional)
if (window.gradio_app) {
// You can add Gradio state updates here if needed
}
}
</script>
'''
return buttons_html
# Launch configuration
if __name__ == "__main__":
# Disable SSL warnings for testing
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=True,
show_error=True
)