Sistema Super Simple de Blogging (3SB)

Super Simple Blogging System aka 3sb
Interfaz de Usuario para 3sb

Autor: Drk0027

Versión: 1.0

ChangeLog: Changelog

Proyecto Desplegado: 3sb.interlan.ec 3SB en GitHub Pages

Documentación: Documentación

GitHub: 3SB en GitHub

Tabla de contenidos

Motivación

La motivación de este proyecto, simplemente es crear una herramienta practica, con la cual pueda entrenar mas mis conocimientos de JavaScript y PWA, además de tener un sistema de blogging muy simple que dependa muy poco del servidor.

Características

Este blog permite toda la gama de CRUD (Crear, Actualizar, Borrar) para cada elemento de la base de datos (entradas.json), pero los cambios se almacenan de forma local en el LocalStorage, por lo que si se quieren hacer públicos los cambios, es necesario exportar el nuevo archivo entradas.json y reemplazar al anterior.

Las características iniciales de este proyecto son las siguientes

  • CRUD completo Es decir, crear, actualizar y eliminar registros.
  • Personalización Se pueden usar plantillas Bootstrap, pero también, al ser tan simple, siempre y cuando se mantengan las etiquetas originales con sus id, puede ser modificado completamente a gusto
  • PWA Al disponer de Service Workers y ser servido mediante https, los datos pueden permanecer en el cliente fuera de lineal, pudiendo ser consultados libremente.
  • Responsive Las características del cliente definen la calidad de la lectura al ajustarse a los parámetros necesarios.
  • Notificaciones push Para poder notificar al cliente si hay una versión mas reciente del archivo entradas.json o indicar que el archivo local ha sido modificado y debe guardarse para mostrar los cambios de forma global.(tal vez próxima versión)
  • Edición con guardado automático Al editar una entrada, los cambios se almacenan automáticamente en el LocalStorage

Sistema de Blogging tradicional

El objetivo de este proyecto no es reescribir la rueda, sino ofrecer una alternativa a los modelos tradicionales de forma didáctica, para que quienes deseen, puedan aprender directamente del código.

Para esto, se mantienen aspectos tradicionales de los blogs como:

  • Presentación de entradas en forma descendente Muestra las entradas mas recientes primero en la pantalla inicial
  • Búsqueda simple por contenido de las entradas La búsqueda es muy simple, se selecciona un array de palabras ingresadas en el campo de búsqueda y se confirma si estas existen en las entradas. Se creara una lista de entradas que contengan los criterios de búsqueda (por implementar)
  • Búsqueda por etiquetas Se mostraran solo las entradas que tengan las etiquetas seleccionadas
  • ¿Comentarios? Dado que solo se cargan paginas estáticas, no es posible almacenar comentarios. En todo caso, tal vez sea posible vincular las url con sistemas como Discus y otros.
  • Gestión SEO No es precisamente imposible, pero la mayor parte del procesamiento se realiza desde el lado del cliente, por lo que, si bien, los motores de búsqueda pueden encontrar por ejemplo, el archivo index.html, no podrían leer los contenidos de las entradas, ya que esto requiere JavaScript y no solo texto plano. He leído que hay algunos buscadores que ya crean una representación virtual del tiempo de ejecución, por lo que pueden localizar información que solo se carga durante la ejecución en el cliente, pero no he investigado mas.

Limites del sistema

Realmente no se que limites podría tener, pero estimo que esta bastante limitado, debido a que carga un archivo json que crecerá bastante con el tiempo.

Al parecer cada navegador tiene sus propias restricciones de memoria, pero básicamente la mayoría tienen espacio ilimitado para el LocalStorage, el problema esta mas relacionado con la memoria RAM.

Si bien el almacenamiento no es mayor problema, el archivo Json es cargado directamente en la memoria RAM, así que su consumo es directamente proporcional al tamaño neto del archivo.

Los procesos del sistema no consumen demasiada memoria, pero al renderizar tienen que cargar el archivo por completo para procesarlo, es posible que comiencen a sentirse los efectos pasado los 10MB, puesto que el consumo de memoria suele incrementarse de forma exponencial al tamaño del archivo. Es bastante impresionante lo que puede cargar una base de datos normal. Mis respetos.

Solo por pura diversión, intentare hacer pruebas en un dispositivo kaiOS, Después de todo, este proyecto comenzó pensando en un Alcatel 3078A

Sobre las imágenes y archivos

Se pueden utilizar las mismas etiquetas para incrustar imágenes en el documento, siempre y cuando se hayan subido primero las imágenes al directorio destinado imágenes o en su defecto, donde se desee almacenar los recursos requeridos

Instalación

Basta con copiar los archivos en la carpeta publica del servidor. Tal vez cuando lo suba a GitHub también se pueda usar el comando git clone para descargar el código y ejecutarlo

Webgrafía

How to Build a Lightweight Blog

Is It Possible to Build a Blog With HTML Only?

Deploy a JavaScript only Blog With CMS.JS

CMS.JS Proyecto muerto

Bitácora de desarrollo: Bot Descargar Musica 31/5/22

El articulo a continuación es una bitácora del desarrollo de un bot o plataforma de bots que actualmente tengo funcionando. Se puede acceder usando el siguiente URL: Descargar_Musica

He comenzado esta especie de portafolio con un pequeño bot de Telegram para descargar música desde YouTube. Para esto, he recurrido a las siguientes tecnologías:

Los cambios que he realizado recientemente son muy escasos. Mi conocimiento sobre javascript es relativamente superficial, por lo que he decidido aprender clases, módulos y librerías en javascript. Debido a esto, ahora se han realizado los siguientes cambios:

Creación del directorio data/descargar_musica/descargas

Con el fin de crear un sistema de control para bots unificado, he creado este directorio para que en el futuro, pueda agregar mas directorios de datos para cada bot. En esta situación en especifico, este cambio se hace por la necesidad de asegurar que si hay datos corruptos o incompletos, o un sistema de borrado deficiente, al modificar o borrar los archivos, esto no afecte a otros bots o al propio código fuente.

Creación de las librerias db_logging_system y telegram_logging_system

Con el fin de crear un código mas simple y fácil de mantener, he creado estas librerías universales, que permiten registrar a los nuevos usuarios que utilicen el bot y otra que registre los eventos en un canal distinto.

PD: Sigo sin entender mucho esto, pero ahi logre hacerlo funcionar.

Creación de un vídeo tutorial

Este bot no tiene mayor complicación. Lo único que requiere es que envíen un enlace y comienza a trabajar, sin embargo, siguen mandandole enlaces de spotify e incluso itunes. Si bajar música de estas plataformas fuera legal (y hubiese alguna ganancia aparte de practicar programación) seguro que implementaría una función adecuada, pero por el momento, esto no es asi.

Telegram tiene muchas funciones interesantes y una de mis preferidas son los bots inline. Es posible conseguir la url de un vídeo de youtube, usando el bot @vid y una vez seleccionado el vídeo, mi bot comenzará a trabajar.

Uso de los directorios data/descargar_musica/descargas y data/descargar_musica/info

En la revisión anterior, los vídeos se descargaban en el mismo directorio de ejecución. Sean estos archivos temporales o archivos completos, todos se guardaban sin reparo allí, por lo que si ocurría un error o el sistema caía abruptamente, este directorio que contiene los ejecutables, se llenaba de basura que era difícil de limpiar. Para esto, se ha creado el directorio descargas, dentro de uno asignado al bot en cuestión, donde irán todas las descargas realizadas y las que hayan fallado también.

el directorio info, existe en cambio para guardar datos permanentes que no son parte del código, como el vídeo instructivo mencionado previamente

Otros cambios

  • Se agregaron mas registros para mas eventos (errores, eventos, arranque y comandos)
  • Se limpió el código aplicando las nuevas librerías creadas
  • Se optimizo el tiempo de respuesta del bot
  • Se agregaron nuevos mensajes de respuesta
  • Se agregaron nuevas instrucciones para situaciones no controladas
    • En caso de introducir un enlace no reconocido, se agregó la misma instrucción del comando sobre_mi.
    • En caso de haber un error al descargar, se agregó un mensaje de notificación.
    • En caso de haber un error al convertir el archivo.
    • En caso de haber un error al eliminar los archivos temporales.

Proximos cambios

El bot ya se encuentra en un estado bastante aceptable para lo que queria aprender, pero deseo aprender mas cosas, por lo que espero poder agregar lo siguiente:

  • Registro particular para cada bot (sistema “Panoptic”)
  • Control de depuración por comandos para el bot
  • Control web para el bot
  • Control general para todos los bots en el sistema
  • Mejor registro de información común
  • Mejor registro de información especifica
  • Control personalizado de acuerdo al usuario
  • Integración con otras plataformas de bot (discord, twitch, etc)

Actualización del estado del bot de descargar música

No he tenido tiempo para hacer nuevas modificaciones al bot, pero por curiosidad revise las pocas estadísticas que he logrado recopilar. Son justamente estas estadísticas las que me disuaden de hacer mejoras, puesto que implican mucho esfuerzo adicional.

  • Cantidad de usuarios únicos: 580
  • Cantidad de nuevos usuarios por mes: 145
  • Cantidad de respuestas exitosas : 675
  • Cantidad de consultas de información: 45
  • Personas a las que invite personalmente: 3
  • Cantidad de URL correctas: 843
  • Cantidad de solicitudes malformadas: 348
  • Mensajes de spam: 2
  • Usuarios que escribieron mas de un mensaje: 320
  • Usuarios intensivos (mas de 10 mensajes): 30
  • Usuario que mas usa el bot: yo <3

Entre estos datos hay algunas cosas interesantes y desconcertantes. Por ejemplo, siendo que solo he invitado a tres personas, ¿De donde salieron esos 580 usuarios en apenas tres meses?

También es interesante notar que, a pesar de las instrucciones, hay cerca de 348 mensajes que no cumplen los requisitos para funcionar el bot. Muchos de usuarios, luego de un solo intento fallido, no vuelven a usar el bot nunca mas.

Además, se supone que un bot es una maquina que no es atendida a menos que requiera de cierto mantenimiento, dos de esos mensajes fueron intentos de estafa, donde me pedían depósitos a cierto numero de cuenta bancaria.

Después de ver estas cosas, he notado que hacen falta las siguientes cosas, que podrían ser mejoras inmediatas para el bot:

  • Mensajes variables: Actualmente los mensajes del bot están Hard Coded, por lo que para actualizar los mensajes, es necesario modificar el código del bot, para lo cual es necesario detenerlo y reiniciarlo, causando que usuarios poco pacientes pierdan el interés en el.
  • Sistemas de Login: Aunque he creado un sistema que hace que el bot me reporte mediante mensajes en un canal de Telegram, no se guarda nada en la base de datos, razón por la que no puedo obtener unas estadísticas confiables de los eventos que implican a mi bot.
  • Panel de control: Realmente no se si los otros bots lo tengan, todos los que he usado dependen únicamente de la interfaz de chat de Telegram, pero una interfaz web para el administrador suena genial, podría intentarlo.

El juego de la vida en JavaScript

Programando el Juego de la vida en javascript

Actualización: Este post esta muy desactualizado, de hecho los contenidos citados aquí están desactualizados desde antes de escribir este post, al punto de que he tenido que hacer un poco de minería de código para entender como funciona y realizar una reescritura completa en vainilla JavaScript. Todo el proyecto de reescritura se encuentra documentado en este post.

He localizado el lugar de donde extraje este texto, el cual también copia de algún otro lugar que seguro también esta caído. Tomare algunas ideas de allí para cuando haga nuevos post, pero la mayoría de mi contenido tratará de ser mas original.

El juego de la vida en HTML5 usando Canvas

El juego de la vida de Conway (también conocido simplemente como “Life”) es un ejemplo clásicos de Autómatas Celulares creado por John Horton Conway en los 70.

Consiste en una grilla de puntos (el universo) donde cada punto puede contener un individuo o célula (un punto de la grilla que está encendido o vivo (los puntos tienen estado binario: vivos o muertos).

El juego funciona sólo (se lo conoce como un juego de cero jugadores), y lo único que se puede hacer es preparar el estado inicial y luego echarlo a correr. La corrida involucra generaciones, o pasadas por la grilla completa para analizar el estado y calcular el de la siguiente pasada. El cálculo se hace analizando para cada punto su estado y el de los ocho que lo rodean (sus vecinos):

Para determinar si la célula analizada vive o muere, se aplican las siguientes reglas:

  • Una célula viva con menos de dos vecinos vivos se muere (de soledad)
  • Una con dos o tres vecinos vivos sobrevive
  • Una con más de tres vecinos vivos se muere (por sobrepoblación)
  • Una célula muerta con exactamente tres vecinos vivos, nace (por reproducción)

La grilla debe considerarse como un toroide, o sea que los puntos de la última fila continúan en la superior, y los de la última columna derecha continúan en la primera de la derecha y viceversa.

Parece sencillo, pero ha sido estudiado durante todos estos años no solamente como un interesante Code Kata, sino como un interesante ejercicio de simulación de ecosistemas.

Como no hay mejor manera de entenderlo que verlo en acción, les recomiendo ver esta versión implementada en HTML 5, utilizando el tag Canvas.

Cosas interesantes que ocurren con este juego es que espontáneamente aparecen configuraciones estables (que permanecen constantes durante muchas generaciones, variando entre un número determinado de estados. Es un interesante ejemplo de comportamiento emergente, y aunque parezca un oximorón, un ejemplo sencillo de sistemas complejos.

Por supuesto, una de las cosas muy interesantes de ver es código fuente. Son sólo 234 líneas de JavaScript y aunque parece un poco largo para un post, creo que vale la pena pegarlo completo, aunque recomiendo que si alguien lo quiere tocar baje siempre la última versión disponible en GitHub.

/*
 * Copyright 2011 Julian Pulgarin 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var Point = function(x, y) {
    this.x = x;
    this.y = y;
};

var graphics = function() {
    var canvas;
    var ctx;
    var canvasId;

    var cellSize = 10; // pixels
    var onColour = 'rgb(0, 200, 0)';
    var offColour = 'rgb(200, 0, 0)';
    var gridColour = 'rgb(50, 50, 50)';

    var initCanvas = function(canvasId) {
        this.canvas = $(canvasId).get(0);
        this.ctx = this.canvas.getContext('2d'); 
        this.canvasId = canvasId;
    }

    var drawCell = function(x, y, alive) {
        var g = graphics;
        g.ctx.fillStyle = (alive)? onColour : offColour;
        g.ctx.fillRect(x * cellSize + 1, y * cellSize + 1, cellSize - 1, cellSize - 1);
    }

    var handleMouse = function(e) {
        var l = life;
        var g = graphics;
        var that = this;
        var cell = getCellPointUnderMouse(e);
        var state;
        processCell(cell);
        $(g.canvasId).mousemove(function(e) {
            cell = getCellPointUnderMouse(e);
            processCell(cell);
        });
        function getCellPointUnderMouse(e) {
            return new Point((e.pageX - that.offsetLeft) / g.cellSize | 0, ((e.pageY - that.offsetTop) / g.cellSize) | 0);
        }
        function processCell(cell) {
            var x = cell.x;
            var y = cell.y;
            if (x > l.xCells - 1 || y > l.yCells - 1) {
                return;
            }
            if (typeof state == 'undefined')
            {
                state = !l.prev[x][y];
            } 
            l.prev[x][y] = state;
            drawCell(x, y, state);
            // TODO: Consider setting next as well
        }
    }

    function paint() {
        var g = graphics;
        var l = life;

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                g.drawCell(x, y, l.prev[x][y]);
            }
        }
    }

    return {
        canvas: canvas,
        ctx: ctx,
        canvasId: canvasId,
        cellSize: cellSize,
        onColour: onColour,
        offColour: offColour,
        gridColour: gridColour,
        initCanvas: initCanvas,
        drawCell: drawCell,
        handleMouse: handleMouse,
        paint: paint,
    }
}(); 

var life = function() { 

    var yCells; 
    var xCells;
    var prev = []; // previous generation
    var next = []; // next generation

    var _timeout;
    var _alive = false;

    var initUniverse = function(canvasId) {
        var l = life;
        var g = graphics;
        g.initCanvas(canvasId);
        l.xCells = ((g.canvas.width - 1) / g.cellSize) | 0;
        l.yCells = ((g.canvas.height - 1) / g.cellSize) | 0; 
        g.ctx.fillStyle = g.offColour;
        g.ctx.fillRect(0, 0, l.xCells * g.cellSize, l.yCells * g.cellSize);
        g.ctx.fillStyle = g.gridColour;

        for (var x = 0; x < l.xCells; x++) {
            l.prev[x] = [];
            l.next[x] = [];
            g.ctx.fillRect(x * g.cellSize, 0, 1, l.yCells * g.cellSize);
            for(var y = 0; y < l.yCells; y++)
            {
                l.prev[x][y] = false;
            }
        }
        g.ctx.fillRect(l.xCells * g.cellSize, 0, 1, l.yCells * g.cellSize);
        for(var y = 0; y < l.yCells; y++)
        {
            g.ctx.fillRect(0, y * g.cellSize, l.xCells * g.cellSize, 1);
        }
        g.ctx.fillRect(0, l.yCells * g.cellSize, l.xCells * g.cellSize, 1);
        $(canvasId).mousedown(g.handleMouse);
        $('body').mouseup(function(e)
        {
            $(g.canvasId).unbind('mousemove');
        });
    }

    var nextGen = function() {
        var l = life;
        var g = graphics;

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                l.next[x][y] = l.prev[x][y];
            }
        }

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                count = _neighbourCount(x, y);

                // Game of Life rules
                if (prev[x][y]) {
                    if (count < 2 || count > 3) {
                        next[x][y] = false;
                    }
                } else if (count == 3) {
                    next[x][y] = true;
                } 
            }
        }

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                l.prev[x][y] = l.next[x][y];
            }
        }

        g.paint();
    }

    var toggleLife = function() {
        var l = life;

        if (!l._alive) {
            l._alive = true;
            l._timeout = setInterval("life.nextGen()", 100);
        } else {
            l._alive = false;
            clearInterval(l._timeout);
        }
    }

    var clear = function() {
        var l = life;
        var g = graphics;

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                l.prev[x][y] = false;
            }
        }
        g.paint();
    }

    var _neighbourCount = function(x, y) {
        var l = life;
        var count = 0;
        var neighbours = [
            l.prev[x][(y - 1 + l.yCells) % l.yCells],
            l.prev[(x + 1 + l.xCells) % l.xCells][(y - 1 + l.yCells) % l.yCells],
            l.prev[(x + 1 + l.xCells) % l.xCells][y],
            l.prev[(x + 1 + l.xCells) % l.xCells][(y + 1 + l.yCells) % l.yCells],
            l.prev[x][(y + 1 + l.yCells) % l.yCells],
            l.prev[(x - 1 + l.xCells) % l.xCells][(y + 1 + l.yCells) % l.yCells],
            l.prev[(x - 1 + l.xCells) % l.xCells][y],
            l.prev[(x - 1 + l.xCells) % l.xCells][(y - 1 + l.yCells) % l.yCells],
        ];

        for (var i = 0; i < neighbours.length; i++) {
            if (neighbours[i]) {
                count++;
            }
        }
             
        return count;
    }

    return {
        yCells: yCells,
        xCells: xCells,
        prev: prev,
        next: next,
        initUniverse: initUniverse,
        nextGen: nextGen,
        toggleLife: toggleLife,
        clear: clear,
    }
}();

A %d blogueros les gusta esto: