Archives du mot-clé Perl

RequestT­ra­cker : telle­ment puis­sant

RequestT­ra­cker (RT) est un outil de tickets extrê­me­ment puis­sant et flexible. Telle­ment flexible qu’on en vient à se prendre la tête pour faire des trucs de oufs.

Je suis en train d’en mettre un en place pour Frama­soft depuis quelques semaines. Je mets long­temps à le mettre en place pour plusieurs raisons :

  • j’ai d’autres projets à fouet­ter
  • il a fallu que je fasse des aller/retours avec Pouhiou pour cerner au mieux les besoins de notre équipe de support
  • je fais faire des trucs rigo­los à RT qui sont assez galère à mettre en place

Je vais faire un petit tour des trucs que j’ai mis en place sur un RT 4.4.0.

Utili­ser le plus addres­sing

De façon simple, pour que RT traite les tickets qui arrivent sur l’adresse email dédiée, on la met dans le /etc/aliases de sa machine. Ça fait un truc comme ça :

rt:         "|/opt/rt4/bin/rt-mailgate --queue general --action correspond --url https://rt.example.org/"
rt-comment: "|/opt/rt4/bin/rt-mailgate --queue general --action comment --url https://rt.example.org/"

Vous note­rez que cela met les mails à desti­na­tion de cette adresse dans la queue (ou file) general. Or on utilise géné­ra­le­ment plus d’une queue dans un système de ticket : cela permet de diri­ger auto­ma­tique­ment vers les personnes les plus à même d’y répondre.

Le problème avec ce système, c’est qu’il faudrait ajou­ter une adresse dédiée à chaque fois que l’on crée une nouvelle file. Ça peut vite deve­nir usant.

On va donc utili­ser le plus addres­sing. Cette tech­nique, toute bête, consiste à ajou­ter un discri­mi­nant à une adresse mail, précédé géné­ra­le­ment d’un + (mais on peut confi­gu­rer son serveur de mail pour utili­ser n’im­porte quel carac­tère). rt@example.org aura alors pour alias (par exemple) rt+file_bidule@example.org.

Pour que RT puisse utili­ser cette tech­nique, il faut ajou­ter --extension=queue dans la commande du /etc/aliases :

rt:         "|/opt/rt4/bin/rt-mailgate --extension=queue --queue general --action correspond --url https://rt.example.org/"
rt-comment: "|/opt/rt4/bin/rt-mailgate --extension=queue --queue general --action comment --url https://rt.example.org/"

Voilà ! Il ne vous reste plus qu’à créer vos files via l’in­ter­face web. Atten­tion, créez-les avec un nom qui passera dans une adresse mail. Pas d’es­pace par exemple.

RT récu­pé­rera le nom de la file kiva­bien dans la partie entre le + et le @ et placera auto­ma­tique­ment le ticket dans cette file, tout en gardant la file general par défaut.

Les articles

Quoi de plus casse-pieds que de se répé­ter encore et encore en donnant toujours la même réponse ? Heureu­se­ment il y a les articles qui peuvent vous servir de réponses pré-enre­gis­trées :-)

Créa­tion des classes et des articles

Allez dans le menu Administration > Articles > Classes > Ajouter, créez vos classes (j’en ai créé une par file, n’ayant pas réussi à assi­gner auto­ma­tique­ment des articles aux files), cochez Tous les articles de cette classe doivent être disponibles sous forme de liste sur la page de réponse d'un ticket, déco­chez Inclure le résumé de l'article et Inclure le nom de l'article et cochez Include le champs personnalisé 'Content' > Value (oh la belle typo de traduc­tion) qui appa­raî­tra après avoir enre­gis­tré la classe (pour ces trois derniers, vous faites comme vous le sentez hein).

Liez la classe à une file via le menu S'applique à.

Voilà, vous n’avez plus qu’à créer vos articles dans la classe que vous venez de créer via le menu Articles > Ajouter.

Et là, magie, lorsque vous répon­dez via l’in­ter­face web, vous pour­rez choi­sir une réponse pré-enre­gis­trée.

Place­ment des articles dans la réponse

Je suis un grand fan du bottom-post­ing, mais RT place l’ar­ticle au-dessus de la cita­tion du message précé­dent. Remé­dions à cela.

cd /opt/rt4
mkdir -p local/html/Elements/
cp share/html/Elements/MessageBox local/html/Elements/
vi local/html/Elements/MessageBox

Cher­chez la ligne conte­nant

% $m->comp('/Articles/Elements/IncludeArticle', %ARGS) if $IncludeArticle;

et rempla­cez-la par

% if ($IncludeArticle) {
%    my $article = $m->scomp('/Articles/Elements/IncludeArticle', %ARGS);
%    $article    =~ s{\n}{<br />}g;
%    $article    = RT::Interface::Email::ConvertHTMLToText($article);
%    $Default   .= $article unless ($Default =~ s/(.*)(-- .*)/$1$article$2/m);
% }

Hop ! votre article se trouve main­te­nant entre la cita­tion et votre signa­ture :-)

(un redé­mar­rage de RT est peut-être néces­saire pour que cela soit pris en compte)

Ajout des articles perti­nents dans le mail de noti­fi­ca­tion d’un nouveau message

Une des forces de RT est de permettre aux inter­ve­nants de répondre aux tickets par mail. Le problème est que cela empêche de piocher dans les réponses pré-enre­gis­trées.

Qu’à cela ne tienne, ajou­tons-les au mail de noti­fi­ca­tion envoyé aux membres du support.

Allez dans Administration > Global > Modèles > Choisir. Il faut modi­fier le modèle Notification de modification HTML (oui, j’ai traduit le nom de mes modèles, mais il est simple à repé­rer, il est utilisé par les scrips 8 et 11).

Ajou­tez ceci en bas du modèle :

{ my $hotlist = RT::Articles->new( RT->SystemUser );
  $hotlist->LimitHotlistClasses;
  $hotlist->LimitAppliedClasses( Queue => $Ticket->QueueObj );
  my $content   = "-- \n<p><b>Réponses pré-enregistrées pour cette catégorie de tickets:</b></p>";

  if ($hotlist->Count) {
    while (my $article = $hotlist->Next) {
      $content .= '<p><b>'.$article->Name.'</b><br/>';
      my $class   = $article->ClassObj;
      my $cfs     = $class->ArticleCustomFields;
      my %include = (Name => 1, Summary => 1);
      $include{"CF-Title-".$_->Id} = $include{"CF-Value-".$_->Id} = 1 while $_ = $cfs->Next;
      $include{$_} = not $class->FirstAttribute("Skip-$_") for keys %include;

      while (my $cf = $cfs->Next) {
        next unless $include{"CF-Title-".$cf->Id} or $include{"CF-Value-".$cf->Id};
        my $values = $article->CustomFieldValues($cf->Id);
        if ($values->Count == 1) {
          my $value = $values->First;
          if ($value && $include{"CF-Value-".$cf->Id}) {
            $content .= '<br/>';
            my $c     = $value->Content || $value->LargeContent;
            $c =~ s/\r?\n/<br\/>/g;
            $content .= $c;
          }
        } else {
          my $val = $values->Next;
          if ($val && $include{"CF-Value-".$cf->Id}) {
            $content .= '<br/>';
            my $c     = $value->Content || $value->LargeContent;
            $c =~ s/\r?\n/<br\/>/g;
            $content .= $c;
          }
          while ($val = $values->Next) {
            if ($include{"CF-Value-".$cf->Id}) {
              $content .= '<br/>';
              my $c     = $value->Content || $value->LargeContent;
              $c =~ s/\r?\n/<br\/>/g;
              $content .= $c;
            }
          }
        }
      }
      $content .= "<br/>-----------</p>\n";
    }
  }
  $content;
}
{$content}

C’est moche et long, je sais. Dites-vous que j’ai passé plus d’une après-midi pour trou­ver ça, la docu­men­ta­tion est inexis­tante pour faire ça.

Les inter­ve­nants n’au­ront plus qu’à copier-coller l’ar­ticle qui se trouve au bas de leur mail de noti­fi­ca­tion dans leur réponse :-)

Commandes par mail

C’est beau de répondre par mail, mais il faut encore se connec­ter à l’in­ter­face web pour effec­tuer certaines actions. Comme je suis fier d’être fainéant, j’ai créé un scrip pour auto­ri­ser certaines actions par mail.

Mais avant ça, préci­sions :

  • RT permet aux inter­ve­nants de discu­ter du ticket sans que cela soit vu par le créa­teur du ticket : c’est le but de l’adresse rt-comment@example.org du début de l’ar­ticle. On va utili­ser cette adresse pour pilo­ter RT par mail
  • un scrip est une action effec­tuée par RT en réponse à un évène­ment, en utili­sant de façon option­nelle un modèle. Typique­ment, il y a un scrip qui envoie (action) un mail (d’après un modèle) aux membres du support lorsqu’un ticket est créé (évène­ment).

Créons donc un scrip. Menu Administration > Scrips > Ajouter.

  • Condi­tion (évène­ment) => Lors d’un commen­taire
  • Action => défi­nie par l’uti­li­sa­teur
  • Modèle => Modèle vide

Dans le Programme de préparation d'action personnalisé: :

if ($self->TransactionObj->Attachments->First->Content =~ m/#(JePrends|Fermeture|Spam)/i) {
    return 1;
} else {
    return 0;
}

Oui, j’au­rais pu faire un one-liner, mais il faut que ça reste lisible faci­le­ment, et quand on passe des heures à faire des bidouilles comme ça, on appré­cie les codes lisibles en un coup d’œil.

Dans le Code d'action personnalisée (commit): :

if ($self->TransactionObj->Attachments->First->Content =~ m/#JePrends/i) {
    if ( $self->TicketObj->OwnerAsString eq '' ) {
        my $id = $self->TransactionObj->Creator;
        $RT::Logger->info("Setting owner to ".$id);
        $self->TicketObj->SetOwner($id, 'SET');
    }
} elsif ($self->TransactionObj->Attachments->First->Content =~ m/#Fermeture/i) {
    $RT::Logger->info("Closing ticket");
    $self->TicketObj->SetStatus('resolved');
} elsif ($self->TransactionObj->Attachments->First->Content =~ m/#Spam/i) {
    my $ticket = $self->TicketObj;
    my ($status, $msg) = $ticket->SetStatus('rejected');
    $RT::Logger->error("Couldn't delete ticket: $msg") unless $status;

    my $requestors = $ticket->Requestor->UserMembersObj;
    while (my $requestor = $requestors->Next) {
        $requestor->SetDisabled(1);
        $RT::Logger->info("Disabling user ".$requestor->Format." because he's likely a spammer");
    }
}

return 1;

Voilà, enre­gis­trez et c’est bon.

Lorsqu’un commen­taire contien­dra une commande, elle sera exécu­tée :

  • #JePrends => l’in­ter­ve­nant s’as­signe le ticket
  • #Fermeture => le ticket est marqué comme résolu
  • #Spam => le ticket est supprimé et son auteur ne pourra plus ouvrir de tickets, son adresse mail sera black­lis­tée

Et le spam alors ?

Pour le spam, prépa­rez d’abord un spamassassin pour votre serveur de mails. Ce n’est pas l’objet de cet article, il n’y a qu’à fouiller un peu le web pour trou­ver des tutos.

On va recréer un scrip, mais avant cela on va créer une nouvelle file nommée spam (menu Administration > Files > Ajouter).

Pour notre nouveau scrip :

  • Condi­tion (évène­ment) => Lors d’une créa­tion
  • Action => défi­nie par l’uti­li­sa­teur
  • Modèle => Modèle vide

Dans le Programme de préparation d'action personnalisé: :

if ( $self->TicketObj->Subject !~ /\[ .* \]/i ) {
  my $inMessage = $self->TransactionObj->Attachments->First;

  # if no message attachment - assume web UI
  return 0 if (!$inMessage);

  # exit if not email message
  return 0 if (!$inMessage->GetHeader('Received'));

  return ($inMessage->GetHeader('X-Spam-Level') =~ m/\*+/) ? 1 : 0;
} else {
  return 1;
}

Dans le Code d'action personnalisée (commit): :

my $spamlevel = $self->TransactionObj->Attachments->First->GetHeader('X-Spam-Level');
if ($spamlevel =~ m/\*\*\*+/) {
  if ($spamlevel =~ m/\*\*\*\*\*/) {
    $RT::Logger->info("This mail seems to be a spam => deleting");
    $self->TicketObj->Delete();
  } else {
    $RT::Logger->info("This mail seems to be a spam => queue spam");
    $self->TicketObj->SetQueue('spam');
  }
}
return 1;

Avec cela, les mails ayant un score de 5 ou plus au spamLe­vel seront suppri­més, et ceux qui ont entre 3 et 5 vont au purga­toire, dans la file spam.

Prenez soin de dépla­cer ce scrip tout en haut de la liste pour qu’il soit le premier exécuté.

Plugins

En vrac, les plugins que j’uti­lise :

Les deux premiers sont main­te­nant inté­grés à RT, il n’y a pas besoin de les instal­ler, juste de les confi­gu­rer. Ils servent respec­ti­ve­ment à assu­rer l’au­then­ti­fi­ca­tion LDAP à l’in­ter­face web, et à impor­ter en masse les comptes du LDAP pour permettre à l’ad­mi­nis­tra­teur de mettre les colla­bo­ra­teurs dans les bons groupes sans attendre qu’ils se soient logués une première fois.

Le dernier plugin ajoute un S dans le menu des tickets, permet­tant de les décla­rer comme spam d’un simple clic.

Conclu­sion

On peut faire de merveilleuses choses avec RT, pour peu que l’on ait le temps de fouiller dans la docu­men­ta­tion (offi­cielle ou non)… et dans le code !

Une fois bien confi­guré, il devrait permettre d’al­lé­ger la charge de travail du groupe de support et je peux vous dire que pour en faire depuis plus de deux ans pour Frama­soft et bien plus pour mon ancien boulot, ce n’est pas quelque chose à négli­ger :-)

NB : bien évidem­ment, ce superbe logi­ciel est en Perl :D

Crédits de la photo d’illus­tra­tion : CC-BY GotC­re­dit, trou­vée sur Flickr

Me soutenir sur Tipeee Me soutenir sur Liberapay

Lut.im, un service d’hé­ber­ge­ment d’images gratuit, libre et anonyme

Que celui qui n’a jamais voulu parta­ger simple­ment une capture d’écran lève le doigt. Personne ?

Logo de LUTIm

Le partage d’images nous confronte souvent à divers problèmes :

  • un email prend du temps (retrou­ver l’adresse du desti­na­taire, l’en­voi, etc.) ;
  • un email prend de la place. Ce n’est pas grand chose, mais pour une image jetable, c’est de l’es­pace disque perdu, que ce soit dans le dossier « Envoyé » de l’ex­pé­di­teur ou celui du desti­na­taire. Oui, on peut suppri­mer le mail, mais c’est encore une action à effec­tuer.
  • une solu­tion commme imgur nous ramène au sempi­ter­nel problème des Condi­tions Géné­rales d’Uti­li­sa­tion imbi­tables, pas traduites et qu’on ne lit de toute façon jamais en entier. Pour ce genre de service, on risque de four­nir certains droits à l’hé­ber­geur… et ça c’est pas cool !
  • un owncloud (ou équi­valent) fera bien le travail, au prix d’une certaine complexité de partage et de liens à la longueur ahuris­sante.

Pour répondre à cette problè­ma­tique, j’ai codé LUTIm (pronon­cez comme lutin). Écrit en Perl avec le frame­work Mojo­li­cious, utili­sant le Twit­ter Boots­trap, un sous ensemble de Font Awesome et un plugin jQuery légè­re­ment modi­fié pour la gestion du glis­ser/dépo­ser, LUTIm est un logi­ciel libre (licence AGPL) de partage d’image anonyme et gratuit.

Capture d'écran de l'interface de LUTIm

Le prin­cipe est simple : on glisse/dépose des images (ou via le sélec­teur de fichier clas­sique) et on récu­père 3 liens :

  • un lien vers l’image (utili­sable dans une balise img par exemple) ;
  • un lien de télé­char­ge­ment de l’image (pour éviter le Clic droit > Enre­gis­trer sous) ;
  • un lien vers une page qui affiche l’image et qui est utili­sable sur Twit­ter (l’image appa­raî­tra dans le tweet).

Des options du formu­laire d’en­voi permettent de suppri­mer auto­ma­tique­ment les images après la première consul­ta­tion ou après 24h.

Bien évidem­ment, pour des ques­tions légales, il n’est pas possible d’avoir un service tota­le­ment anonyme : les IPs des envoyeurs d’image et celles des consul­teurs sont enre­gis­trées dans les logs, mais c’est quelque chose de tout à fait habi­tuel sur tout site web. Les IPs des envoyeurs ainsi que celle du dernier consul­teur sont enre­gis­trées dans la base SQLite pour accé­le­rer la recherche d’in­for­ma­tions en cas de requète judi­ciaire (je sais comme il peut être fasti­dieux et long de cher­cher dans des logs).

Lors de la suppres­sion auto­ma­tique d’une image, le fichier est bel et bien supprimé, mais son entrée en base de données persiste et contient l’em­preinte SHA512 du fichier.

De par sa nature libre, vous pouvez bien évidem­ment instal­ler et utili­ser très faci­le­ment LUTIm sur votre propre serveur, mais vous pouvez aussi vous conten­ter d’uti­li­ser l’ins­tance offi­cielle : http://lut.im

LUTIm est dispo­nible en français et en anglais, la langue étant choi­sie selon les préfé­rences du navi­ga­teur. Toutes les bonnes volon­tés sont les bien­ve­nues pour propo­ser d’autres langues !

Enfin, LUTIm propose un plugin pour Shut­ter, logi­ciel de capture d’écran, pour permettre à celui-ci d’en­voyer les capture sur http://lut.im direc­te­ment (plugin à instal­ler soi-même, le site du projet ayant l’air cassé, je n’ai pu leur remon­ter le plugin).

Me soutenir sur Tipeee Me soutenir sur Liberapay