Alternativa para la entrada rápida de notas Flomo en Linux

Documentación de Flomo Quick Note

  1. Resumen del proyecto

Flomo Quick Note es una herramienta ligera de captura de ideas para entornos de escritorio Linux (especialmente GNOME). Utiliza la API Webhook de Flomo y una interfaz gráfica simple para registrar pensamientos rápidamente. Soporta entrada multilínea, límite diario de envíos, persistencia de configuración y notificaciones del sistema. El proyecto está escrito en Python 3 y emplea herramientas nativas del sistema (Zenity, notify-send) para minimizar dependencias y lograr una alta integración.

1.1 Características principales

  • Invocación global: Se puede iniciar rápidamente mediante combinaciones de teclas personalizadas en GNOME o con un icono en el escritorio.
  • Entrada multilínea: Basada en el cuadro de diálogo de texto editable de Zenity, compatible con texto largo y saltos de línea.
  • Protección de límite diario: Lleva un registro local de la cantidad de envíos del día. Al alcanzar el límite de 100 memorias diarias de la versión gratuita de Flomo, muestra una advertencia e impide el envío.
  • Asistente de configuración: En la primera ejecución guía al usuario para ingresar la dirección Webhook. La configuración se guarda en el directorio estándar XDG.
  • Retroalimentación instantánea: Tras un envío exitoso o fallido se muestra una notificación del sistema.
  • Amigable sin conexión: Maneja automáticamente las excepciones de red y notifica al usuario.
  1. Requisitos del sistema

  • Sistema operativo: Cualquier distribución Linux con Python 3.6+ (se recomienda Fedora/GNOME).
  • Paquetes necesarios:
    • python3 (normalmente preinstalado)
    • zenity (para diálogos gráficos)
    • notify-send (generalmente incluido en el entorno de escritorio; en caso contrario, instalar libnotify)
  • Mejoras opcionales:
    • python3-requests (si está instalado, el script lo usará; de lo contrario, usará urllib)
  1. Instalación y despliegue

3.1 Instalación de dependencias

sudo dnf install zenity   # Fedora
# u otros gestores: apt install zenity, etc.

3.2 Obtener el script

Colocar los siguientes archivos en directorios adecuados del usuario:

  • Script principal: ~/.local/bin/flomo-quick
  • Directorio de configuración: ~/.config/flomo-quick/ (se crea automáticamente la primera vez)
  • Archivo de entrada de escritorio (opcional): ~/.local/share/applications/flomo-quick.desktop

Dar permisos de ejecución al script:

chmod +x ~/.local/bin/flomo-quick

3.3 Crear acceso directo en el escritorio (opcional)

[Desktop Entry]
Version=1.0
Name=Flomo Quick Note
Comment=Captura ideas rápidamente en Flomo
Exec=/home/usuario/.local/bin/flomo-quick
Icon=flomo-quick
Terminal=false
Type=Application
Categories=Utility;
StartupNotify=true

Colocar el archivo de icono (p. ej., flomo-quick.svg) en ~/.local/share/icons/hicolor/scalable/apps/ y luego ejecutar:

update-desktop-database ~/.local/share/applications/

3.4 Configurar atajo de teclado global (GNOME)

El usuario debe añadir manualmente un atajo personalizado:

  1. Abrir "Configuración" → "Teclado" → "Ver y personalizar atajos".

  2. Desplazarse al final y hacer clic en "+" para añadir:

    • Nombre: Flomo Quick Note
    • Comando: /usr/bin/python3 /home/usuario/.local/bin/flomo-quick
    • Atajo: por ejemplo, Super+N
  3. Guía de uso


4.1 Primera ejecución

  • Al ejecutar el script (mediante atajo o terminal) aparecerá un asistente de configuración que solicitará la dirección Webhook de Flomo.
  • El formato de la dirección debe ser https://flomoapp.com/api/v1/memo/xxxx-xxxx-xxxx.
  • Tras introducirla, se guarda y ya se puede usar directamente.

4.2 Uso normal

  • Al invocar el script aparece una ventana de texto editable multilínea.
  • Introducir el contenido; se admite cualquier texto. Flomo procesa automáticamente las etiquetas (p. ej., #trabajo).
  • Hacer clic en "Aceptar" para enviar. Si es exitoso se recibe una notificación; si falla se muestra un mensaje de error.

4.3 Límite diario

  • El script lleva un conteo local de envíos del día. Al alcanzar 100 memorias muestra un diálogo de advertencia e impide el envío.
  • El conteo se reinicia cada día natural.
  1. Estructura del código

flomo-quick.py
├── Constantes de configuración
├── Funciones auxiliares
│   ├── verificar_zenity()        # Comprueba si zenity está disponible
│   ├── notificar()               # Envía notificación de escritorio
│   ├── cargar_configuracion()    # Lee configuración JSON
│   ├── guardar_configuracion()   # Escribe configuración JSON
│   ├── asistente_configuracion() # Asistente de primera configuración
│   ├── obtener_entrada()         # Obtiene entrada del usuario (multilínea)
│   ├── cargar_contador()         # Lee/reinicia contador diario
│   ├── guardar_contador()        # Guarda contador diario
│   ├── verificar_limite()        # Comprueba límite diario
│   ├── incrementar_contador()    # Incrementa contador
│   └── enviar_a_flomo()          # Envía petición a la API
└── principal()                   # Flujo principal

  1. Detalles de implementación clave

6.1 Entrada multilínea

Se usa zenity --text-info --editable para crear un área de texto editable que admite múltiples líneas. El texto devuelto conserva los saltos de línea internos, pero se eliminan los saltos de línea finales sobrantes.

resultado = subprocess.run(
    [COMANDO_ZENITY, "--text-info", "--editable",
     "--title=Flomo Inspiración\nEl agua no compite, pero siempre gana.",
     "--width=600", "--height=400"],
    capture_output=True, text=True, check=True
)
texto = resultado.stdout.rstrip('\n')

6.2 Persistencia local del límite diario

El archivo contador counter.json guarda el siguiente formato:

{
  "fecha": "2025-03-21",
  "contador": 42
}

Tras cada envío exitoso, contador aumenta en 1. Si la fecha cambia, se reinicia automáticamente a 0.

6.3 Envío a la API

Se utiliza urllib.request para enviar una solicitud POST con un tiempo de espera de 10 segundos. Si se detecta la biblioteca requests instalada, se podría reemplazar por una implementación más elegante (la versión actual no incluye esa opción, pero es extensible).

datos = json.dumps({"content": contenido}).encode('utf-8')
cabeceras = {'Content-Type': 'application/json'}
solicitud = Request(url_api, data=datos, headers=cabeceras, method='POST')
with urlopen(solicitud, timeout=10) as respuesta:
    # Procesar respuesta

6.4 Manejo de errores

  • Configuración ausente → entra automáticamente al asistente.
  • Configuración corrupta → elimina el archivo y vuelve a configurar.
  • Zenity no instalado → imprime error y sale.
  • Excepción de red → notifica al usuario.
  • Usuario cancela la entrada → no provoca efectos secundarios.
  1. Descripción del archivo de configuración

7.1 Ubicación del archivo de configuración

~/.config/flomo-quick/config.json

7.2 Definición de campos

Campo Tipo Obligatorio Descripción
api_url string Dirección completa del Webhook de Flomo
etiquetas_por_defecto array no Campo reservado para futura extensión: etiquetas añadidas automáticamente

7.3 Ejemplo

{
  "api_url": "https://flomoapp.com/api/v1/memo/abc-123-def",
  "etiquetas_por_defecto": ["inspiración"]
}

  1. Manejo de errores y mensajes al usuario

Escenario Comportamiento
Zenity no instalado Imprime error en terminal y sale (no puede mostrar interfaz gráfica)
Archivo de configuración no existe Inicia automáticamente el asistente de configuración
Archivo de configuración corrupto Muestra aviso, lo elimina e inicia el asistente
Usuario cancela la configuración Sale sin notificación
Entrada vacía Sale directamente
Error de red Notificación roja con "Error de red: xxx" mediante notify-send
HTTP 4xx/5xx La notificación muestra el código de estado concreto
Límite diario alcanzado Ventana de advertencia de Zenity; sale
  1. Extensibilidad y planes futuros

9.1 Posibles extensiones

  • Captura de texto seleccionado: usar xclip para obtener el texto seleccionado como contenido por defecto.
  • Etiquetas por defecto: leer etiquetas_por_defecto de la configuración y añadirlas automáticamente al final de la entrada.
  • Historial local: guardar las últimas ideas para evitar pérdidas accidentales.
  • Múltiples cuentas: cambiar entre distintos Webhooks mediante variables de entorno o archivos de configuración.
  • Biblioteca API mejorada: detectar requests y usarlo preferentemente para mejor gestión de coenxiones y mensajes de error.

9.2 Sugerencias de implementación

Si se añaden estas funcionalidades, se recomienda mantener la modularidad y el principio de responsabilidad única. Por ejemplo, separar la "obtención de entrada" del "preprocesamiento del texto" para facilitar la composición.

  1. Guía para contribuciones

Se aceptan Issues y Pull Requests. Al desarrollar, seguir estas normas:

  • Estilo de código según PEP 8.
  • Las nuevas funcionalidades deben actualizar la documentación.
  • Mantener las dependencias mínimas, priorizando la biblioteca estándar.
  • El manejo de errores debe ser completo para garantizar una buena experiencia de usuario.
  1. Licencia

Este proyecto se distribuye bajo la licencia MIT. Consulte el archivo LICENSE para más detalles.


Última actualización: marzo de 2026

Apéndice: Código

#!/usr/bin/env python3
"""
Flomo Quick Note (Mejorado)
- Entrada multilínea mediante Zenity text-info
- Límite diario de envíos (100) con contador local
"""

import os
import json
import subprocess
import sys
from datetime import date
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError

# ---------- Configuración ----------
DIRECTORIO_CONFIG = os.path.expanduser("~/.config/flomo-quick")
ARCHIVO_CONFIG = os.path.join(DIRECTORIO_CONFIG, "config.json")
ARCHIVO_CONTADOR = os.path.join(DIRECTORIO_CONFIG, "counter.json")
COMANDO_ZENITY = "zenity"
COMANDO_NOTIFICAR = "notify-send"
LIMITE_DIARIO = 100
# -----------------------------------

def verificar_zenity():
    """Comprueba si zenity está instalado."""
    try:
        subprocess.run([COMANDO_ZENITY, "--version"], capture_output=True, check=True)
    except (subprocess.CalledProcessError, FileNotFoundError):
        print("Zenity no encontrado. Instálelo: sudo dnf install zenity", file=sys.stderr)
        sys.exit(1)

def notificar(mensaje, es_error=False):
    """Muestra una notificación de escritorio."""
    urgencia = "critical" if es_error else "normal"
    subprocess.run([COMANDO_NOTIFICAR, "-a", "Flomo", "-u", urgencia, mensaje])

def cargar_configuracion():
    """Carga la configuración desde un archivo JSON. Devuelve None si no existe."""
    if not os.path.exists(ARCHIVO_CONFIG):
        return None
    try:
        with open(ARCHIVO_CONFIG, 'r') as f:
            return json.load(f)
    except (json.JSONDecodeError, IOError):
        return None

def guardar_configuracion(config):
    """Guarda la configuración en un archivo JSON."""
    os.makedirs(DIRECTORIO_CONFIG, exist_ok=True)
    with open(ARCHIVO_CONFIG, 'w') as f:
        json.dump(config, f, indent=2)

def asistente_configuracion():
    """Asistente de primera ejecución: pide la URL del Webhook mediante Zenity."""
    try:
        resultado = subprocess.run(
            [COMANDO_ZENITY, "--entry", "--title=Flomo Configuración inicial",
             "--text=Introduce la dirección Webhook de Flomo (se obtiene en la configuración de Flomo):"],
            capture_output=True, text=True, check=True
        )
        url = resultado.stdout.strip()
        if not url:
            notificar("No se introdujo la dirección Webhook. Configuración cancelada.", es_error=True)
            sys.exit(0)
        if not url.startswith("https://"):
            subprocess.run([COMANDO_ZENITY, "--error", "--text=La dirección debe comenzar con https://"])
            return asistente_configuracion()  # reintentar
        config = {"api_url": url}
        guardar_configuracion(config)
        notificar("Configuración de Flomo guardada")
        return config
    except subprocess.CalledProcessError:
        # Usuario canceló
        sys.exit(0)

def obtener_entrada():
    """
    Muestra un cuadro de diálogo multilínea usando zenity --text-info.
    Devuelve el texto introducido, o None si se cancela.
    """
    try:
        # Usar --text-info con marca editable para entrada multilínea
        resultado = subprocess.run(
            [COMANDO_ZENITY, "--text-info", "--editable",
             "--title=Flomo Inspiración\nEl agua no compite, pero siempre gana.",
             "--width=600", "--height=400"],
            capture_output=True, text=True, check=True
        )
        # Zenity devuelve el texto exacto, incluyendo saltos de línea
        texto = resultado.stdout
        # Eliminar solo saltos de línea finales, conservar internos
        texto = texto.rstrip('\n')
        return texto if texto else None
    except subprocess.CalledProcessError:
        # Usuario canceló (cerró ventana o presionó Cancelar)
        return None

def cargar_contador():
    """Carga el contador diario; lo reinicia si cambió la fecha."""
    hoy = date.today().isoformat()
    if not os.path.exists(ARCHIVO_CONTADOR):
        return {"fecha": hoy, "contador": 0}
    try:
        with open(ARCHIVO_CONTADOR, 'r') as f:
            contador = json.load(f)
        if contador.get("fecha") != hoy:
            contador = {"fecha": hoy, "contador": 0}
        return contador
    except (json.JSONDecodeError, IOError):
        return {"fecha": hoy, "contador": 0}

def guardar_contador(contador):
    """Guarda el contador diario."""
    with open(ARCHIVO_CONTADOR, 'w') as f:
        json.dump(contador, f, indent=2)

def verificar_limite():
    """Comprueba si se ha alcanzado el límite de envíos del día. Si es así, muestra advertencia y devuelve False."""
    contador = cargar_contador()
    if contador["contador"] >= LIMITE_DIARIO:
        # Muestra diálogo de advertencia
        subprocess.run([
            COMANDO_ZENITY, "--warning",
            "--title=Límite diario alcanzado",
            f"--text=Hoy has registrado {LIMITE_DIARIO} ideas, has llegado al límite diario de Flomo.\n¡Continúa mañana!"
        ])
        return False
    return True

def incrementar_contador():
    """Incrementa el contador diario tras un envío exitoso."""
    contador = cargar_contador()
    contador["contador"] += 1
    guardar_contador(contador)

def enviar_a_flomo(url_api, contenido):
    """Envía el contenido a Flomo mediante Webhook."""
    datos = json.dumps({"content": contenido}).encode('utf-8')
    cabeceras = {'Content-Type': 'application/json'}
    solicitud = Request(url_api, data=datos, headers=cabeceras, method='POST')
    try:
        with urlopen(solicitud, timeout=10) as respuesta:
            if respuesta.status == 200:
                return True, "Idea registrada"
            else:
                return False, f"HTTP {respuesta.status}: {respuesta.reason}"
    except HTTPError as e:
        return False, f"HTTP {e.code}: {e.reason}"
    except URLError as e:
        return False, f"Error de red: {e.reason}"
    except Exception as e:
        return False, f"Error desconocido: {str(e)}"

def principal():
    verificar_zenity()

    # Cargar configuración o ejecutar asistente
    config = cargar_configuracion()
    if config is None:
        config = asistente_configuracion()
        if config is None:
            sys.exit(0)

    url_api = config.get("api_url")
    if not url_api:
        notificar("Falta api_url en el archivo de configuración. Vuelva a configurar.", es_error=True)
        os.remove(ARCHIVO_CONFIG)
        config = asistente_configuracion()
        if not config:
            sys.exit(0)
        url_api = config["api_url"]

    # Verificar límite diario
    if not verificar_limite():
        sys.exit(0)

    # Obtener entrada del usuario (multilínea)
    contenido = obtener_entrada()
    if contenido is None:
        sys.exit(0)  # cancelado o vacío

    # Enviar a Flomo
    exito, mensaje = enviar_a_flomo(url_api, contenido)
    if exito:
        incrementar_contador()
    notificar(mensaje, es_error=not exito)

if __name__ == "__main__":
    principal()

Etiquetas: Flomo Webhook linux GNOME Python

Publicado el 6-21 02:56