Documentación de Flomo Quick Note
- 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.
- 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, instalarlibnotify)
- Mejoras opcionales:
python3-requests(si está instalado, el script lo usará; de lo contrario, usaráurllib)
- 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:
-
Abrir "Configuración" → "Teclado" → "Ver y personalizar atajos".
-
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
-
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.
- 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
- 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.
- 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 | sí | 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"]
}
- 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 |
- Extensibilidad y planes futuros
9.1 Posibles extensiones
- Captura de texto seleccionado: usar
xclippara obtener el texto seleccionado como contenido por defecto. - Etiquetas por defecto: leer
etiquetas_por_defectode 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
requestsy 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.
- 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.
- 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()