Cuando el diablo no sabe que hacer, programa en Node
Jorge
Posted on October 25, 2020
- WARNING
-
Detrás de ese título "provocador" sólo pretendo decir que YO no soy un programador con experiencia en este lenguaje sin entrar a valorar sus ventajas o desventajas. Simplemente se me ocurrió una tontería y quise ponerla en práctica usandolo y así practicar un poco de paso.
Una (si no la única) afición que tengo es la de viajar, y cuanto más lejos y menos visitado sea el sitio mejor. Obviamente con la situación actual de la Covid19 esto es imposible así que lo único que me queda es abrir de vez en cuando Google Maps y seleccionar un sitio al azar (vale, también reviso muchos sitios donde he estado).
Por una asociación de ideas recordé un juego que usa Google Street Maps para mostrarte un sitio random y hay que adivinar donde es en un tiempo corto, simplemente moviendote por las calles sin poder hacer zoom y buscándolo encontré random.earth
un site que me fascinó. Muestra luegares random usando imágenes de Google Maps, con la posibilidad de que la gente las vote.
Como ni tengo el tiempo ni la capacidad de hacer algo parecido se me ocurrió hacerme mi versión sencilla y en este post voy a contar cómo lo he hecho (y ya te digo que el código tendrá una calidad regulinchi y ójala una horda de trolls puedan venir a explicarme cómo hacerlo mejor porque así aprenderemos algo)
La idea es simple:
tener un comando que ejecutar al iniciar sesión que seleccione unas coordenadas random del planeta
construir la url en random.earth con esas coordenadas
descargar unos cuantos pantallazos de ese lugar aplicando diferente diferentes zoom desde el más lejano hasta el de máxima resolución
crear un gif animado con la secuencia de imágenes
subirlo a Mastodon (donde también tengo cuenta como @jagedn)
El resultado final es tener algo parecido a
Al principio jugué con la idea de hacerlo sólo mediante herramientas disponibles en la shell (curl
, httpie
, wget
, etc ) Asi encontré algunas herramientas en Tk que permiten indicarle una URL y hacen la captura del navegador, trasteé con la shell para generar números random, hice un bash que lo unificaba todo…. pero al final cambié de opinión y me decidí por hacerlo en un único lenguaje y opté por Node (sí, podía haberlo hecho en Groovy pero no quiero que me acusen de encasillarme)
Instalar Node
Lo primero es instalar Node. No sé si es dificil o no para Windows, pero para Linux no tiene mucha historia.
Básicamente instalar Nvm
y con él instalar la versión que nos interese de Node
Proyecto
En un directorio limpio crearemos el proyecto
npm init
Como el proyecto no lo voy a publicar como paquete simplemnte he ido aceptando las opciones por defecto que se ofrecen.
Al final tienes un fichero package.json
que puedes luego ajustar a mano si te interesa
Dependencias
Para este proyecto voy a usar las siguientes dependencias:
capture-website
dotenv
get-image-colors
gif-creation-service
mastodon
$ npm install --save capture-website
$ npm install --save dotenv
$ npm install --save get-image-colors
$ npm install --save gif-creation-service
$ npm install --save mastodon
Index.js
El script va a residir en un único fichero (luego he hecho algunas variantes) index.js
que iré exponiendo a continuación por partes:
Inicialización
index.js
const dotenv = require("dotenv");
dotenv.config();
const fs = require("fs");
const captureWebsite = require("capture-website");
const GifCreationService = require("gif-creation-service");
const path = require("path");
const getColors = require("get-image-colors");
const Masto = require("mastodon");
function getRndInteger(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
(async()=>{
// El código
})();
Básicamente cargamos las dependencias que vamos a usar así como creamos una función de utilidad que parece que Javascript no tiene por defecto.
Vamos a usar la capacidad de Javascript de llamar a funciones de forma asíncrona mediante await
así que el código estará embebida en una función de async
Elegir un lugar random
const outputGifFile = "output.gif";
let pngImages = [];
//vamos a buscar un sitio random pero puede ser agua que no interesa
//así que lo intentaremos varias veces hasta encontrar tierra
let doIt = false;
for (var j = 0; j < 20; j++) {
let lat = getRndInteger(-89, 89);
let latprec = getRndInteger(0, 9999);
let log = getRndInteger(0, 179);
let logprec = getRndInteger(0, 9999);
let west = getRndInteger(0, 1) == 0 ? 1 : -1;
log = log * west;
// tendriamos por ejemplo 32.3430,-113.4350
// añadimos al array una serie de urls con distinto zoom: ${i}z
// asociado a un nombre de png
pngImages = [];
for (let i = 1; i <= 19; ) {
pngImages.push([
`https://random.earth/@${lat}.${latprec},${log}.${logprec},${i}z,2t`,
`screenshot${i}.png`,
]);
// borramos el fichero si existe
try {
fs.unlinkSync(`screenshot${i}.png`);
} catch (e) {}
// según el número de steps que usemos tendremos un gif con mayor peso
i += 3;
}
// detectar si es tierra
...
}
Detectar si es tierra
Una vez inicializado el array de urls a descargar vamos a descargar la penúltima foto que tiene un un zoom elevado y ver si corresponde a tierra o mar analizando su paleta de colores.
Descargamos la imagen en check.png
quitando el elemento HTML #map
para que no meta ruido:
await captureWebsite.file(pngImages[pngImages.length - 2][0], "check.png", {element:"#map"});
El elemento #map es un objeto DOM que está en la página propia de random.earth, es decir, que si estás descargando otra página ese elemento no lo tendrás seguramente.
Una vez descargada vamos a analizar su paleta:
doIt = false;
await getColors(path.join(__dirname, "check.png")).then((colors) => {
if (("" + colors).startsWith("#e3e3dc") == false) {
doIt = true;
}
});
La idea es utilizar una librería que nos permite recuperar la paleta de colores de la imagen y ver si nos interesa. A parte de que el código que he usado es muy feo lo que he hecho ha sido comprobar que cuando es mar, la imagen descargada tiene una paleta de colores muy básica que siempre empieza por #e3e3dc, así que básicamente si es diferente he encontrado una imagen interesante.
Al cambiar el valor de doIt a true consigo que se salga del bucle inmediatamente marcando además que hemos encontradao la imagen.
Descargar todas las imagenes
Para descargar las imágenes simplemente llamamos a la librería con el array que habiamos preparado al principio, quitando algunos elementos del DOM para dejar las imágenes lo más limpias posibles
await Promise.all(
pngImages.map(([url, filename]) => {
return captureWebsite.file(url, filename, {
scaleFactor: 0.75,
hideElements: [
'#address','#search-box','#top-menu','#controls-box','#prev','#next'
]
});
})
);
Generar el Gif
De igual manera, generar el gif es tan sencillo como llamar a la libreria que lo hace:
GifCreationService.createAnimatedGifFromPngImages(
pngImages.map((obj) => {
return obj[1];
}),
outputGifFile,
{ repeat: true, fps: 1, quality: 10 }
).then((outputGifFile) => {
console.log(
`Alright, GIF ${outputGifFile} created for ${pngImages[5][0]}!`
);
})
Una vez generado el gif muestro con una traza la URL usada para saber el lugar elegido.
Subir a Mastadon
Subir la imagen con un mensaje a Mastodon es tan fácil como haber creado un token de aplicación y apuntar a la instancia donde tienes la cuenta, en mi caso en https://mastodon.madrid
var M = new Masto({
access_token: process.env.MASTODON,
timeout_ms: 60 * 1000,
api_url: "https://mastodon.madrid/api/v1/",
});
var id;
M.post("media", { file: fs.createReadStream(outputGifFile) }).then(
(resp) => {
id = resp.data.id;
M.post("statuses", {
status: "Un sitio random cada día",
media_ids: [id],
});
}
);
Para no tener el token en el código simplemente se crea un fichero .env
y se añade como clave-valor
Este código, sube la imagen Gif y una vez subida crea un Toot con un texto y el id de la imagen subida
Ejecución
Una vez tenemos index.js simplemente invocamos
node index.js
y si todo va bien, tendremos un toot subido con un gif animado adjunto
Variantes
Una vez he tenido el script completo he creado dos "variantes":
norandom.js, es una copia de index.js pero en lugar de buscar un sitio random utiliza los argumentos proporcionados en la linea de comandos como latitud y longitud
social.js, al final he separado en dos la lógica de crear el gif y la de subirlo a Mastodon, de tal forma que primero genero la imagen y si me gusta llamo al social.js para que la suba
Utilidad
Ninguna, pero me he divertido subiendo algún sitio aleatorio y haciendo encuestas para intentar adivinar de donde es, para una vez finalizada la encuesta subir el gif animado que muestre el lugar
Link al código
Si quieres, puedes descargar el código completo de todos los scripts desde
https://gitlab.com/-/snippets/2029648
Conclusión
Mi objetivo con este post NO es crear un tutorial de cómo usar Node, pues como se puede observar ni tengo conocimientos ni el código tiene una calidad mínima.
Digamos que me gustaría sirviera para demostrar que hay una gran cantidad de librerías que hacen las cosas más insospechadas, y que es bastante sencillo crearnos utilidades con ellas.
Posted on October 25, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.