feat(telegram): Mini App estatica para elegir la zona vigilada
La sirve la web en /telegram/ y la abre el bot con un boton web_app (Bot API 6.0).
Mapa con marcador arrastrable y circulo de radio, y devuelve {lat,lon,km} con
WebApp.sendData(). Sin backend: no habla con Meteor ni necesita sesion. Leaflet
vendorizado en public/telegram/vendor para no depender de una CDN.
Sustituye al flujo de cinco pasos del bot de 2018. Detalle en
tcef-notifications/docs/legacy-bot.md §10.
This commit is contained in:
parent
bba2ad5523
commit
23ac2ab99b
8 changed files with 796 additions and 0 deletions
129
public/telegram/index.html
Normal file
129
public/telegram/index.html
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>Zona a vigilar</title>
|
||||
<!--
|
||||
Mini App de Telegram para elegir la zona vigilada (fase 8).
|
||||
|
||||
Sustituye al flujo de cinco pasos del bot de 2018 (compartir ubicación o escribir el pueblo →
|
||||
geocodificación de Google → mapa de confirmación → menú de cinco radios fijos). Aquí se marca
|
||||
el punto en el mapa y se elige el radio que se quiera.
|
||||
|
||||
Es una página ESTÁTICA a propósito: no habla con el servidor de la web ni necesita sesión. El
|
||||
resultado se devuelve con `Telegram.WebApp.sendData()`, que Telegram entrega al bot como un
|
||||
mensaje de servicio del propio chat — así que no hay que validar `initData` en ningún sitio ni
|
||||
exponer un endpoint nuevo. Leaflet va vendorizado en ./vendor para no depender de una CDN
|
||||
(la del bot puede abrirse desde redes con poca cobertura).
|
||||
-->
|
||||
<link rel="stylesheet" href="vendor/leaflet.css">
|
||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||
<script src="vendor/leaflet.js"></script>
|
||||
<style>
|
||||
:root { color-scheme: light dark; }
|
||||
html, body { margin: 0; height: 100%; font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
|
||||
body { display: flex; flex-direction: column; background: var(--tg-theme-bg-color, #fff); color: var(--tg-theme-text-color, #000); }
|
||||
#map { flex: 1 1 auto; min-height: 0; }
|
||||
.panel { padding: 12px 16px calc(12px + env(safe-area-inset-bottom)); display: grid; gap: 10px; }
|
||||
.fila { display: flex; align-items: center; gap: 12px; }
|
||||
.fila label { font-size: 15px; white-space: nowrap; }
|
||||
#km { flex: 1 1 auto; }
|
||||
#valor { font-variant-numeric: tabular-nums; font-weight: 600; min-width: 4.5em; text-align: right; }
|
||||
button { font: inherit; font-weight: 600; padding: 12px; border: 0; border-radius: 10px; cursor: pointer;
|
||||
background: var(--tg-theme-button-color, #2ea6ff); color: var(--tg-theme-button-text-color, #fff); }
|
||||
button[disabled] { opacity: .5; cursor: default; }
|
||||
.ayuda { font-size: 13px; opacity: .7; margin: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
<div class="panel">
|
||||
<p class="ayuda" id="ayuda">Toca el mapa o arrastra el marcador para elegir la zona que quieres vigilar.</p>
|
||||
<div class="fila">
|
||||
<label for="km">Radio</label>
|
||||
<input id="km" type="range" min="1" max="100" step="1" value="10">
|
||||
<span id="valor">10 km</span>
|
||||
</div>
|
||||
<button id="enviar" disabled>Guardar zona</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var tg = window.Telegram && window.Telegram.WebApp;
|
||||
if (tg) { tg.ready(); tg.expand(); }
|
||||
|
||||
// El idioma lo dice Telegram; si no, español. Solo hay tres cadenas, no vale la pena i18next.
|
||||
var LANG = ((tg && tg.initDataUnsafe && tg.initDataUnsafe.user && tg.initDataUnsafe.user.language_code) || 'es').slice(0, 2);
|
||||
var T = {
|
||||
es: { ayuda: 'Toca el mapa o arrastra el marcador para elegir la zona que quieres vigilar.', radio: 'Radio', guardar: 'Guardar zona' },
|
||||
en: { ayuda: 'Tap the map or drag the marker to choose the area you want to watch.', radio: 'Radius', guardar: 'Save area' },
|
||||
gl: { ayuda: 'Toca o mapa ou arrastra o marcador para escoller a zona que queres vixiar.', radio: 'Raio', guardar: 'Gardar zona' },
|
||||
pt: { ayuda: 'Toca no mapa ou arrasta o marcador para escolher a zona a vigiar.', radio: 'Raio', guardar: 'Guardar zona' }
|
||||
}[LANG] || null;
|
||||
if (T) {
|
||||
document.getElementById('ayuda').textContent = T.ayuda;
|
||||
document.querySelector('label[for=km]').textContent = T.radio;
|
||||
document.getElementById('enviar').textContent = T.guardar;
|
||||
}
|
||||
|
||||
L.Icon.Default.prototype.options.imagePath = 'vendor/images/';
|
||||
|
||||
var map = L.map('map', { zoomControl: true }).setView([40.4168, -3.7038], 6);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© OpenStreetMap'
|
||||
}).addTo(map);
|
||||
|
||||
var marcador = null;
|
||||
var circulo = null;
|
||||
var km = Number(document.getElementById('km').value);
|
||||
var boton = document.getElementById('enviar');
|
||||
var valor = document.getElementById('valor');
|
||||
|
||||
function pinta(latlng) {
|
||||
if (!marcador) {
|
||||
marcador = L.marker(latlng, { draggable: true }).addTo(map);
|
||||
marcador.on('dragend', function () { pinta(marcador.getLatLng()); });
|
||||
circulo = L.circle(latlng, { radius: km * 1000, color: '#e2504f', weight: 2, fillOpacity: 0.15 }).addTo(map);
|
||||
} else {
|
||||
marcador.setLatLng(latlng);
|
||||
circulo.setLatLng(latlng);
|
||||
}
|
||||
circulo.setRadius(km * 1000);
|
||||
boton.disabled = false;
|
||||
}
|
||||
|
||||
map.on('click', function (e) { pinta(e.latlng); });
|
||||
|
||||
document.getElementById('km').addEventListener('input', function (e) {
|
||||
km = Number(e.target.value);
|
||||
valor.textContent = km + ' km';
|
||||
if (circulo) circulo.setRadius(km * 1000);
|
||||
});
|
||||
|
||||
// Arranque: si el navegador da la posición, se centra ahí y se propone ya una zona.
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(function (pos) {
|
||||
var ll = L.latLng(pos.coords.latitude, pos.coords.longitude);
|
||||
map.setView(ll, 11);
|
||||
pinta(ll);
|
||||
}, function () { /* sin permiso: se elige a mano */ }, { timeout: 8000 });
|
||||
}
|
||||
|
||||
boton.addEventListener('click', function () {
|
||||
if (!marcador) return;
|
||||
var ll = marcador.getLatLng();
|
||||
var datos = JSON.stringify({ v: 1, lat: Number(ll.lat.toFixed(6)), lon: Number(ll.lng.toFixed(6)), km: km });
|
||||
if (tg && tg.sendData) {
|
||||
tg.sendData(datos); // Telegram cierra la ventana y entrega esto al bot
|
||||
} else {
|
||||
// Fuera de Telegram (abrir la URL en un navegador normal) no hay a quién enviárselo.
|
||||
boton.disabled = true;
|
||||
document.getElementById('ayuda').textContent = 'Esta página solo funciona abierta desde el bot de Telegram.';
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue