Publication de UA (Universal Aggregator)

Posté par . Édité par Benoît Sibaud, Nils Ratusznik et NeoX. Modéré par Nils Ratusznik. Licence CC by-sa
39
19
mar.
2014
Internet

La plupart des lecteurs vivent probablement la même expérience que moi : chaque jour, de très (trop) nombreuses minutes gaspillées à aller voir d’éventuelles mises à jour sur différents sites et forums. Avec RSS et maintenant Atom, un premier pas a été fait pour résoudre ce problème, avec des agrégateurs spécialisés. Restaient deux soucis :

  1. Que faire des sites ne possédant pas de flux RSS ?
  2. L’information à surveiller reste dispersée à deux endroits différents: le client de courriel et l’agrégateur RSS.

Pour le second point, différentes solutions sont apparues, toutes avec des noms assez explicites (liste loin d’être exhaustive) : rss2mail, rss2email, rss-to-mail, et ma propre solution maildir-feed.

Restait le premier point. Jusqu’ici, ma solution (par exemple pour le site mangareader) était d’écrire un serveur web par source d’information qui faisait la transformation sous RSS. Solution vaguement satisfaisante jusqu’à un certain point que j’ai fini par atteindre, et qui m’a forcé à penser à une solution un peu plus générique, dont UA (Universal Aggregator) est le résultat.

Sommaire

UA est une solution très générique, dans le sens où il s’agit plus d’un méta-projet rassemblant plusieurs minuscules projets :

  • tout d’abord, la définition d’un format (JSON) pour décrire un message, pour que les différents composants communiquent entre eux ;
  • des producteurs, qui produisent des messages à partir d’une source d’information. Pour l’instant, seuls trois existent: rss2json (le nom se suffit à lui-même), mangareader2json (l’équivalent à mon exemple plus haut) et ipboard2json (exporte les messages d’un forum IPBoard) ;
  • d’éventuels filtres pour transformer les messages. Pour l’instant un seul est présent, ua-inline, qui télécharge les images pour les introduire dans le message lui-même (pour les clients de courriels interdisant le téléchargement de ressources externes) ;
  • un consommateur, qui présente ces messages à l’utilisateur. Pour l’instant, un seul est présent, maildir-put, qui envoie les messages sous forme de courriel dans une boîte de type maildir ;
  • un « chef d’orchestre » pour faire le lien entre tout ce beau monde, ggs.

Configuration

La configuration se fait à partir du chef d’orchestre, ggs. Il est donc nécessaire de le présenter un peu. Il s’agit d’un service lançant périodiquement des commandes, un peu comme cron. Trois points le différencient de ce dernier :

  • il fonctionne à l’aide d’une logique d’intervalle entre deux lancements (« lance moi cette commande toutes les deux heures »), alors que cron fonctionne avec une logique d’évènement temporel (« lance moi cette commande toutes les heures impaires ») ;
  • il sait limiter le nombre de processus lancés en parallèle (à l’aide de la directive de configuration workers) et le temps alloué à chaque tâche (timeout=) ;
  • sa configuration est beaucoup plus riche: le fichier de configuration est un script shell.

Commençons simple : nous voulons recevoir par courriel tous les journaux de LinuxFr.org :

    command 2000 "rss2json https://linuxfr.org/journaux.atom | maildir-put"

Cette ligne signifie « toutes les 2000 secondes » (environ une demi-heure), lance la commande rss2json https://linuxfr.org/journaux.atom | maildir-put. Cette commande n’a rien de spécifique à ua : vous pouvez tout autant la lancer telle quelle dans le shell et aura le même résultat. Vous comprenez immédiatement que pour une autre source, il suffit donc de remplacer la partie gauche du pipe. Effectuons-le pour mangareader, par exemple:

    command 2000 "mangareader2json http://mangareader.net/yotsubato | maildir-put"
    command 2000 "mangareader2json http://mangareader.net/kuroshitsuji | maildir-put"

Ajoutons les news de LinuxFr.org, ainsi que le flux pour XKCD et SMBC. Pour peu que vous ayez beaucoup de sources d’informations actives, votre boîte risque d’être rapidement en désordre. Le choix de maildir (plutôt que d’envoyer les messages en SMTP) vient de cette problématique : la possibilité d’envoyer les messages dans des dossiers spécifiques. Par exemple, mettons les messages de LinuxFr.org dans le dossier DLFP, mangareader dans mangareader et XKCD et SMBC dans Fun :

command 2000 "rss2json https://linuxfr.org/journaux.atom | maildir-put -folder DLFP"
command 2000 "rss2json https://linuxfr.org/news.atom | maildir-put -folder DLFP"
command 2000 "mangareader2json http://mangareader.net/yotsubato | maildir-put -folder Mangas"
command 2000 "mangareader2json http://mangareader.net/kuroshitsuji | maildir-put -folder Mangas"
command 2000 "rss2json http://xkcd.com/atom.xml | maildir-put -folder Fun"
command 2000 "rss2json http://feeds.feedburner.com/smbc-comics/PvLb | maildir-put -folder Fun"

Tout ceci est un peu fastidieux à écrire. Aucun problème, le fichier de configuration est un script shell, il est tout à fait possible de factoriser :

    rss() {
        command 2000 "rss2json \"$1\" |"\
        " maildir-put -folder \"$2\""
    }
    manga() {
        command 2000 "mangareader2json http://mangareader.net/$1 | maildir-put -folder Manga"
    }
    rss https://linuxfr.org/journaux.atom DLFP
    rss https://linuxfr.org/news.atom DLFP
    manga yotsubato
    manga kuroshitsuji
    rss http://xkcd.com/atom.xml Fun
    rss http://feeds.feedburner.com/smbc-comics/PvLb Fun

Dernier souci : le client de courriel n’affiche pas les images par défaut, ce qui est particulièrement gênant pour XKCD et SMBC. Si vous avez suivi la description plus haut, vous aurez vu qu’il y a un filtre pour ça :

    rss() {
        command 2000 "rss2json \"$1\" | ua-inline |"\
         "maildir-put -folder \"$2\""
    }
    manga() {
        command 2000 "mangareader2json http://mangareader.net/$1 | maildir-put -folder Manga"
    }
    rss https://linuxfr.org/journaux.atom DLFP
    rss https://linuxfr.org/news.atom DLFP
    manga yotsubato
    manga kuroshitsuji
    rss http://xkcd.com/atom.xml Fun
    rss http://feeds.feedburner.com/smbc-comics/PvLb Fun

Et voilà le résultat :

Screenshot

Installation

Pour les utilisateurs d’Arch, un PKGBUILD est disponible sur AUR. Pour les autres, rien de plus simple :

    git clone https://github.com/sloonz/ua.git
    make && sudo make install

Vous aurez besoin de libxml et go ; les scripts mangareader2json et ipboard2json nécessitent Python 3, PyQuery et aiohttp. Le tout est installé dans /usr/local ; vous pouvez polluer /usr en ajoutant PREFIX=/usr à make install.

Licence

Ce logiciel est distribué sous licence MIT. Quant au nom, toute suggestion pour en trouver un meilleur est la bienvenue.

  • # Bravo

    Posté par . Évalué à 4.

    Je ne l'ai pas essayé mais ça a l'air très intéressant comme projet.

    Ça a l'air très flexible et assez facilement extensible, y a t'il une description des formats d'entrée/sortie des différents outils ?
    Qui s'occupe de la dé-duplication des items ? maildir-put ?

    Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: Bravo

      Posté par . Évalué à 5. Dernière modification le 19/03/14 à 09:48.

      Ça a l'air très flexible et assez facilement extensible, y a t'il une description des formats d'entrée/sortie des différents outils ?

      La description commune est dans maildir-put (ou man maildir-put)

      Qui s'occupe de la dé-duplication des items ? maildir-put ?

      Oui. Dans mon précédent projet c’était fait au niveau de rss2maildir (dans mon ancien projet producteur et consommateur étaient mélangés, la seule séparation étant faire entre le spawner et le reste), et ça c’est révélé une très mauvaise décision, m’empêchant de coder simplement une source pour medscape (ce que je vais faire ce week end pour ua).

      Ceci dit si d’autres consommateurs voient le jour, il semblerait logique d’en faire un filtre.

  • # Complément d’information

    Posté par . Évalué à 3.

    Pour information, si vous voulez utiliser ua-inline et que votre client mail est Geary, vous devez utiliser la 0.6 : https://bugzilla.gnome.org/show_bug.cgi?id=726468

  • # Et vers rss?

    Posté par . Évalué à 1.

    Je suppose qu'il est ensuite relativement facile de créer un 2em "consommateur" qui génère des rss normalisés?
    Est-ce facile d'écrire une telle chose avec un flux rss par input? ou tag/catégorie que des inputs ou filtres pourraient créer?

    (Ce mécanisme de tag est présent dans logstash, qui est un aggrégateur de logs (il fonctionne de façon étrangement similaire à UA: des inputs, des filtres, des outputs; d'ailleurs UA pourrait certainement être réécrit en en logstash).

    • [^] # Re: Et vers rss?

      Posté par . Évalué à 2.

      Je pense que ce que tu cherche à faire n'est pas possible facilement. RSS c'est du polling et là il faudrait que ton consommateur fasse du push. Pour reproduire un agrégateur classique il faut créer un consommateur qui stocke les flux dans une base de données (SQL, noSQL, fichier,…) et que tu crée un client RSS capable d'aller se plugger sur ces données.

      Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: Et vers rss?

      Posté par . Évalué à 2.

      Je suppose qu'il est ensuite relativement facile de créer un 2em "consommateur" qui génère des rss normalisés?

      Oui, même si j’ai du mal à voir l’intérêt, ça donnerait rss2json http://linuxfr.org/news.atom | json2rss :)

      Est-ce facile d'écrire une telle chose avec un flux rss par input? ou tag/catégorie que des inputs ou filtres pourraient créer?

      Je ne suis pas sûr d’avoir bien compris ta question…

      • [^] # Re: Et vers rss?

        Posté par . Évalué à 3.

        Oui, même si j’ai du mal à voir l’intérêt, ça donnerait rss2json http://linuxfr.org/news.atom | json2rss :)

        Ça peut avoir un intérêt si tu fait des traitement dans le pipe :

        rss2json http://linuxfr.org/news.atom | completrss | json2rss :)
        

        Et paf tu as tes flux anorexiques qui reprennent des couleurs ^

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

      • [^] # Re: Et vers rss?

        Posté par . Évalué à 2.

        Ca peut avoir un interet pour des sites qui ne proposent pas de flux RSS… Par exemple mangaReader :D

      • [^] # Re: Et vers rss?

        Posté par . Évalué à 1.

        L'intérêt comme dit plus haut c'est 1/ de pouvoir générer des flux rss pour des sources qui n'en ont pas, et 2/ pouvoir enrichir, ou filtrer des items d'un flux rss, et 3/ avoir des flux rss en sortie, pour pouvoir brancher n'importe quel client rss, que je préfère pour l'instant encore à une sortie mail.

        L'output actuel c'est un maildir, avec un sous répertoire par input, par exemple. Je souhaitais en savoir plus sur comment les items d'inputs sont dispatchés dans tel ou tel répertoire du maildir, afin de savoir s'il était facile de faire pareil pour générer des sorties rss.

        Mon objectif global est de savoir si je peux remplacer mes scripts perso en python qui font de la création et modification de flux rss par UA, histoire de factoriser les efforts.

        • [^] # Re: Et vers rss?

          Posté par . Évalué à 2. Dernière modification le 21/03/14 à 13:33.

          Grosso modo le dispatch se fait par flux.

          Tu as

          rss http://linuxfr.org/news.atom DLFP
          

          Qui revient à

          command 2000 "rss2json http://linuxfr.org/news.atom | ua-inline | maildir-put -folder DLFP"
          

          Qui dit de mettre le flux des actualités de linuxfr dans le dossier DLFP.

          Par contre tous les items du flux sont forcément dans DLFP. Avec grep tu devrait être capable de séparer les deux, par exemple séparer les articles firefox du reste :

          command 2000 "rss2json http://linuxfr.org/news.atom | grep -i firefox | ua-inline | maildir-put -folder DLFP/Firefox"
          command 2000 "rss2json http://linuxfr.org/news.atom | grep -vi firefox | ua-inline | maildir-put -folder DLFP"
          
  • # Temps-réel?

    Posté par . Évalué à 1.

    Les flux RSS/atom ont la possibilité de pusher en temps-réel les nouvelles entrées PubSubHubHub; assez souvent on perd ce genre de fonctionnalités dans tous les outils du genre qui essayent d'abstraire les flux RSS en une notion plus vague et générique.

    Tous les exemples montrent du polling bête et méchant sur les inputs. Une évolution future gérant des inputs en temps réel est-elle prévue? Ou bien est-ce que c'est fondamentalement incompatible avec les choix architecturaux de UA?

    De même une fois qu'on a des inputs en temps réel, on voudrait les outputs aussi (et dans le cas d'un output RSS, réimplémenter un serveur PubSubHubHub) (pour les output mails ce serait certainement plutôt au serveur imap de surveiller le FS je suppose).

    • [^] # Re: Temps-réel?

      Posté par . Évalué à 3.

      Pour les input en temps réel il « suffirait » de coder un pubsubhubhub2json qui fasse un flux continu. Grosso-modo ça donnerait:

       timeout=0 command 0 "pubsubhubhub2json | ua-inline | maildir-put"
      

      pubsubhubhub2json serait un serveur web qui enverrait sur stdout et dans le bon format toutes les notifications qu’il reçoit.

      Donc pour la première question oui c’est possible, pour la seconde question (prévu à court terme) très certainement que non, vu qu’à ma connaissance les seuls services proposant pubsubhubhub sont des agrégateurs qui font eux-même du polling, ce qui limite fortement l’intérêt de la chose :)

      Pour les output c’est effectivement le rôle du serveur IMAP de gérer ça ; je sais pas comment c’est implémenté dans dovecot mais il le fait très bien : dès que j’ajoute un mail il apparait dans mon client mail.

      • [^] # Re: Temps-réel?

        Posté par . Évalué à 2.

        Grosso-modo ça donnerait:

        timeout=0 command 0 "pubsubhubhub2json | ua-inline | maildir-put"
        

        Je me suis probablement planté, mais j'ai pas vu où c'était géré par rapport au nombre de processus maximum lancé. Pour moi il faudrait décompter ce processus.

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

        • [^] # Re: Temps-réel?

          Posté par . Évalué à 2. Dernière modification le 19/03/14 à 18:09.

          Il prendrait un slot effectivement, mais vu que le nombre de workers est configurable ce n’est pas un vraiment un problème.

          Vu que le nombre de workers par défaut est 5, il suffit d’ajouter workers=6 dans le fichier de conf :)

          (ou de se dire que 4 pour le reste des processus est suffisant)

      • [^] # Re: Temps-réel?

        Posté par . Évalué à 1.

        Donc pour la première question oui c’est possible, pour la seconde question (prévu à court terme) très certainement que non, vu qu’à ma connaissance les seuls services proposant pubsubhubhub sont des agrégateurs qui font eux-même du polling, ce qui limite fortement l’intérêt de la chose :)

        L'intérêt de PubSubHubhub (PuSH) c'est que c'est chainable: même si la source originelle n'est pas compatible, le next hop dans la chaine peut faire du polling une fois pour toutes: les autres acteurs pourront ensuite utiliser PuSH sur ce next hop, ou sur un suivant, et ne pas spammer à nouveau la source originelle à coup de polling. UA ressemble bien à un agrégateur, du coup je pense que c'est bien utile qu'il gère PuSH.

        Donc ça a l'air faisable, grâce aux pipes ou sockets qui peuvent marcher en mode stream. Parfait.

    • [^] # Re: Temps-réel?

      Posté par . Évalué à 2.

      Tous les exemples montrent du polling bête et méchant sur les inputs. Une évolution future gérant des inputs en temps réel est-elle prévue? Ou bien est-ce que c'est fondamentalement incompatible avec les choix architecturaux de UA?

      Pour faire ça, je présume qu'il faut commencer par avoir un démon client PubSubHubHub. De ce que je vois ggs n'est pas vraiment fait pour lancer un démon mais ça ne doit pas être monstrueux (par rapport à créer le client PSHH), ensuite de l'autre coté c'est pareil, il te faut un démon et faire en sorte qu'il lise sur une socket unix ou un pipe nomé.

      AMHA avis le plus gros boulot c'est d'avoir d'écrire le client et le serveur PSHH.

      Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

      • [^] # Re: Temps-réel?

        Posté par (page perso) . Évalué à 3.

        PSHH

        PSHB (Le nom correct est PubSubHubBub).

        Ouais, ce nom est relativement naze.

        • [^] # Re: Temps-réel?

          Posté par . Évalué à 1.

          En fait le nom correct est PubSubHubbub (pas de B majuscule), et c'est généralement abrégé en PuSH.

  • # Intérêt du JSON

    Posté par (page perso) . Évalué à 2.

    Super projet ! J'adore l'idée de séparer les bouts de logique et de faire communiquer sur stdout. Philisophie UNIX toussa.

    Par contre je m'interroge sur l'intérêt de mettre une couche intermédiaire de JSON. À ce que je sache, le JSON n'apporte rien fonctionnellement ou techniquement par rapport aux entrées qui sont déjà formatées et normalisées, que ce soit RSS ou Atom.

    • [^] # Re: Intérêt du JSON

      Posté par . Évalué à 8.

      Pour parser du JSON vers une structure native au langage (généralement dictionnaire ou objet anonyme + liste), c’est à peu près une ligne de code de type json_decode dans quasiment tous les langages, le plus souvent intégré dans la lib standard. Pour RSS/Atom c’est déjà plus compliqué, surtout que chacun de ces deux formats a plusieurs version. Et à l’intérieur d’une seule version d’un format tu as des subtilités peu intéressantes que je ne veux pas me trimballer d’un bout à l’autre du pipe (par exemple la différence entre publication date, creation date et modification date: typiquement un truc que le producteur veut abstraire pour simplifier tout le reste de la chaine de traitement).

      Niveau production, le JSON est tout aussi simple, un json_encode d’une structure langage du standard (la même que json_decode) et c’est plié.

      Par comparaison avec RSS/Atom, dans la plupart des langages dans la lib standard tu as au mieux une API type DOM pour faire du XML. Et lire/produire du RSS/Atom à partir d’une simple API type DOM, ce n’est certes pas la fin du monde, mais c’est infiniment plus compliqué et ce n’est certainement pas quelque chose que tu veux refaire n fois un dans un projet composé d’une multitude d’exécutables différents et indépendants et même écrits dans des langages différents.

      tl;dr: haters gonna hate, mais aujourd’hui en 2014 JSON a gagné haut la main et c’est LE langage d’interopérabilité par excellence. On ne devrait plus justifier l’utilisation de JSON mais bien plutôt sa non-utilisation, et dans le cas de la non-utilisation la justification devrait être solide.

      • [^] # Re: Intérêt du JSON

        Posté par (page perso) . Évalué à 3.

        Oh oui je suis tout à fait d'accord, le JSON est bien plus facile à utiliser dans n'importe quel langage puisqu'il y aura à peu près tout le temps une librairie pour le faire. C'est aussi plus agréable de bosser avec.

        Je demandais ça parce que le RSS et l'Atom ont déjà un format, qui en plus est fait pour les traitements automatisés. Ce format est normalisé et spécifié un peu partout (contrairement au tien qui bien que simple, n'existe que sur un README en plus du code) et pour lequel tu trouveras déjà de la documentation et des outils.

        Si tout le monde utilisait des flux RSS en JSON, ça serait quand même plus simple.

  • # interfacer avec weboob ?

    Posté par (page perso) . Évalué à 3.

    Je pensais bricoler la même chose en partant de Weboob.
    Ce srait pas mal de les interfacer pour pouvoir utiliser UA sur des sites avec authentification ou tout bêtement pour recevoir les infos weboob dans son courriel.

    "La liberté est à l'homme ce que les ailes sont à l'oiseau" Jean-Pierre Rosnay

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.