From 01922222ac686c2507052c0cc1d755495145d154 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Sat, 10 May 2008 22:23:05 +0000 Subject: [PATCH] ADD Bannissement (sans le slap) --- css/1/pageMinichat.css | 14 +++++ doc/graphiques/icones/ban.xcf | Bin 0 -> 1973 bytes doc/graphiques/icones/kick.xcf | Bin 0 -> 1904 bytes doc/graphiques/icones/slap.xcf | Bin 0 -> 1936 bytes doc/graphiques/path2383.png | Bin 0 -> 596 bytes doc/protocole3.txt | 3 +- img/ban.gif | Bin 0 -> 153 bytes img/kick.gif | Bin 0 -> 143 bytes img/slap.gif | Bin 0 -> 144 bytes js/euphorik.js | 37 +++++++++++- js/pageMinichat.js | 62 ++++++++++++++++--- modules/erl/euphorik_bd.erl | 75 +++++++++++++++++++---- modules/erl/euphorik_protocole.erl | 92 ++++++++++++++++++----------- 13 files changed, 224 insertions(+), 59 deletions(-) create mode 100644 doc/graphiques/icones/ban.xcf create mode 100644 doc/graphiques/icones/kick.xcf create mode 100644 doc/graphiques/icones/slap.xcf create mode 100644 doc/graphiques/path2383.png create mode 100644 img/ban.gif create mode 100644 img/kick.gif create mode 100644 img/slap.gif diff --git a/css/1/pageMinichat.css b/css/1/pageMinichat.css index d7cd580..234f9f5 100755 --- a/css/1/pageMinichat.css +++ b/css/1/pageMinichat.css @@ -21,6 +21,20 @@ opacity: 0.5; } +#outilsBan { + border-width: 1px 1px 1px 1px; + border-color: #253f18; + border-style: solid; + padding: 2px; + position: absolute; + display: none; +} +#outilsBan img { + float: right; + vertical-align: middle; + margin: 0px 0px 0px 0px; +} + #page.minichat .titreSmiles:hover { background-color: #2d8800; } diff --git a/doc/graphiques/icones/ban.xcf b/doc/graphiques/icones/ban.xcf new file mode 100644 index 0000000000000000000000000000000000000000..5819921fd10e994cb9d74a6a1cdbd70938594cb4 GIT binary patch literal 1973 zcmeHHOODe(5UqAT4*7^MeAdn`vj`gC27?4*F@h^(9LHmk*rUWF60yLn*|6aTX6qxc z>_cz?cI+U88KrsUbO%QSX+T1=p_FpJdR@PCm+c4RQ)lK5onbKY2-4Qd_y$x6JZ?g3 zhi{2kA-AAqt#|awez)QO6l=>z+MXMY$6gr2Cf?k8IPvJ$)oO6Mx$urbn~S*@sIn?!QF#gc7S_& zYagM8S%5FXpuIqoMMzY?9(U5Y~i-XIi+0>LXdj_p|7*(ci(XHjqkU78f|5H#*- zC@t|kL<+yRd}Hq(HXPC+iZqNo-fzCynH}%!ItZsTd+z$SA5J`i45c!@3e_~cu0uQf zr$nrf8_-KqZ^@D8-NgJS7zY8;j@@`V^`Z49HqP6ksrA6Bu?y6HH?$sz_Iu4>{YS`+?B3f zgdVI3Ret-=fJ5i$%yGjgu|w9Vxl-aTI1K-aYcT>Ur$@sh6z^1eO0z)l55- z_d|;I9=m=F;KUGdkx%g;6Vdl0p?sv zwg7kymgc#r8PWp@RH2xwzi4y%3EM*&6dElHRP?nVDv)(qlCd(h(x5Muqtr@`45&1> ze!@=q8IgF(COVf~iKE4FDnF-GxUp83zHyFzG5lYY?8HVpCD1;>?ZbH!+&;nWlg9Uj zz`6WXSz-aG37yU=lLQD78scj|Ercb``PkxN{88dxC$18;S5a1@0Q8j8^#z~9_O0>b zsQT?()NL`iqFT&15;Sg$`L@lO@7R8cd)!n|Du!=(Z{KmB1xb8-v7e@XM5xG`;l-lL s#E-ywRa1(yUbB6hmN}W!%ZxLNVRGa=e)8-kdzx8j z*mDEX3F7dOIYb-f_AnWDg%kD@5f6ozgi(LEQ*HZ!7j>i9X=h6(4h~#r_x^4sTfkoK z+Cl2UmQ?L`{tP&EG#t2I*bkhC5A=rB_IMkabDiUUn4oy?NO0Ao$oGdLSr!Otyh$l$ zJ(cex7vnQ-{gkNqQ8QR|zxal$&&7%J)E$eM1By^?l7yCX%u$sbW1;E_uC3tu3T~|6 zYrwphmJ$G;!J>zvpiF+6SvvuAm54P*xj*O3w`@15F*E35hKlAhq8X)>TB*5iER?4R zehI%(nku@I=43hjjtzK^aynOt(o(um(4b;!&{-B>QXws@;dAEev*!PTE^|W?Et6>3 z8D>mUfekA+3|dHVlFUuwOBSx&9B``biJasRi4$qbuOvAv`xSB8T;YkdVW?9LvZX!) z{gmiu7Nqbw<{al{fX@wT}<&X zrxw+wg}+dX6Yp_;i}PE^Zz2EEiu~3;$iGXb#L?*0?^=;RFaqWVui|jL#PJaQLE&E3 p_FeCF5J$(VqgT~QJxQpj^onbQ=rYmC(m+v~D^1vbgFmpUKLOHMd*1*6 literal 0 HcmV?d00001 diff --git a/doc/graphiques/path2383.png b/doc/graphiques/path2383.png new file mode 100644 index 0000000000000000000000000000000000000000..6313230ebb25b182b7a4d3718ca4d466bd0b6ec2 GIT binary patch literal 596 zcmV-a0;~OrP)2YWn5M;e{0>D? zD}r|W0wG!`ibA*h0#&Wa3`LQVBpca|>s~P!jOq6;D}s8xx&I)zzyHZ*J?Rc1!bB40XnuUOIWYx560Bphg4>A zoDq&Q-0#3ahn(ru!}qT#ie0iWoK-C(Nd!Qe#@KcjO*06?8CjM9C7=KbVK`&62^kDt zu~;&^=|p5Xlaxg1-X#(3U)g1|f0py2lQ8+o1(MN4jOKFGPl zZp^NGg|62rij2Fv?{cN(CPvYcAoxlguh@~6Ct?X<(`-J*_rDND3+Y#YEe{^kBPpb1 iJ^9-)B?|v}&c6URjlAc3WTZy`0000!e664I8Mx@5_cb?eq0g8{{Vg3d*$i6yBi3gww484B*6 zz5xu1KUo;L7}yzf7=Qp|00XmU#jZPE23|iK_THEj<;LdBn8VJ<=CH7<(S6pnU2*SK yS82u`-L&GZ&75^5oF@Y8A2DbOL_{g~xw8pf)nl4k%<(%RZnD7<$21iN25SJSOgDu9 literal 0 HcmV?d00001 diff --git a/img/kick.gif b/img/kick.gif new file mode 100644 index 0000000000000000000000000000000000000000..ce127dd3d145c51b85bc0429df1e6b71861da678 GIT binary patch literal 143 zcmZ?wbhEHb6krfwIK<3Q$;UT8D{I}lb;nSF;y*#>qSVBa)D(sC%#sWRcTe8{2F0H& zj9d&X3_1)z05X7q+1_K<9W4W|pACC&bj@2OqiyDuU#eag%MX8A;sVNHOnI#zt?w-B@42nNl z7`Yf&7<3qb0Av6IvxCR3J6Z-_KO6Sm=<0H3lU9t0>ug<|S|!f4=6&SUg8{1(Liug} jSFMX*zx?5wwlxbTM2M8mI6H-Fmay+Sf%a(=85pbqD^)q+ literal 0 HcmV?d00001 diff --git a/js/euphorik.js b/js/euphorik.js index 2d64fe2..6ed1963 100755 --- a/js/euphorik.js +++ b/js/euphorik.js @@ -673,10 +673,10 @@ Client.prototype.connexion = function(messageJson) Client.prototype.deconnexion = function() { - this.flush() + this.flush() + this.delCookie() this.setStatut(statutType.deconnected) // deconnexion this.resetDonneesPersonnelles() - this.delCookie () } Client.prototype.chargerDonnees = function(data) @@ -771,6 +771,39 @@ Client.prototype.majMenu = function() } } +Client.prototype.ban = function(userId, minutes) +{ + var thisClient = this + + // par défaut un ban correspond à 3 jours + if (typeof(minutes) == "undefined") + minutes = 60 * 24 * 3 + + jQuery.ajax({ + type: "POST", + url: "request", + dataType: "json", + data: this.util.jsonVersAction( + { + "action" : "ban", + "cookie" : thisClient.cookie, + "duration" : minutes, + "user_id" : userId + }), + success: + function(data) + { + if (data["reply"] == "error") + thisClient.util.messageDialogue(data["error_message"]) + } + }) +} + +Client.prototype.kick = function(userId) +{ + this.ban(userId, 15) +} + /////////////////////////////////////////////////////////////////////////////////////////////////// function initialiserListeStyles(client) diff --git a/js/pageMinichat.js b/js/pageMinichat.js index 2be7ca5..94f6e7c 100755 --- a/js/pageMinichat.js +++ b/js/pageMinichat.js @@ -40,6 +40,21 @@ PageMinichat.prototype.charger = function() this.util.setCaretToEnd(jQuery("form input.message")[0]) + // les outils de bannissement (uniquement pour les ekMaster) + if (this.client.ekMaster) + { + jQuery("body").append( + "
\"Ban\"Ban\"Avertissement\"
" + ) + jQuery("#outilsBan").hover( + function(){}, + function() + { + jQuery("#outilsBan").hide() + } + ) + } + // jQuery("body").append("
") // affichage des smiles @@ -134,6 +149,7 @@ PageMinichat.prototype.decharger = function() this.messages.stopAttenteCourante() jQuery("body #smiles").remove() + jQuery("body #outilsBan").remove() } PageMinichat.prototype.getJSONMessage = function(pseudo, message, repondA) @@ -209,7 +225,11 @@ PageMinichat.prototype.envoyerMessage = function(pseudo, message) thisPageMinichat.messages.conversations[c].messages[m].clientARepondu = true */ } - } + } + else if (data["reply"] == "error") + { + thisPageMinichat.util.messageDialogue(data["error_message"]) + } } } ) @@ -239,9 +259,10 @@ function Reponse(id, pseudo, login) * @param pseudo * @param contenu */ -function Message(id, date, pseudo, login, contenu) +function Message(id, auteurId, date, pseudo, login, contenu) { this.id = id + this.auteurId = auteurId this.date = date this.pseudo = pseudo this.login = login @@ -484,7 +505,7 @@ Conversation.prototype.flush = function(funClickOuvrirConv) "\">" + "
>
" + "[" + message.date + "]" + - "" + identifiant + ":" + + "" + identifiant + ":" + XHTMLrepondA + "" + (message.systeme ? this.formateur.remplacerBalisesHTML(message.contenu) : this.formateur.traitementComplet(message.contenu, message.pseudo)) + "" + "" @@ -518,13 +539,40 @@ Conversation.prototype.flush = function(funClickOuvrirConv) } ) + // les outils de bannissement (uniquement pour les ekMaster) + if (thisConversation.client.ekMaster) + jQuery(".pseudo", this).hover( + function(e) + { + var userId = parseInt(jQuery(this).attr("id").substr(4)) + var element = jQuery(e.target) + var h = element.height() + var offset = element.offset() + var outils = jQuery("#outilsBan").css("top", offset.top - 2).css("left", offset.left - 2).height(h < 16 ? 16 : h).width(element.width() + 16 * 3 + 4).show() + jQuery("img", outils).unbind() + jQuery("#kick", outils).click( + function(e) + { + thisConversation.client.kick(userId) + } + ) + jQuery("#ban", outils).click( + function(e) + { + thisConversation.client.ban(userId) + } + ) + }, + function(){} + ) + jQuery(this).click( function(event) { if (jQuery(event.target).is("a")) return // l'id du message - idMess = jQuery(this).attr("id") + var idMess = jQuery(this).attr("id") // extraction d'une conversation if (jQuery(event.target).is(".extraire")) @@ -611,9 +659,6 @@ function Messages(client, formateur, util) this.conversations = new Array() // les conversations, la première représente la conversation principale this.nouvelleConversation(0) - -// Obsolète - //this.idDernierMessage = null // l'id du dernier message connu // l'objet JSONHttpRequest représentant la connexion d'attente this.attenteCourante = null @@ -690,6 +735,7 @@ Messages.prototype.ajouterMessage = function(element, numConversation) var message = new Message( id, + element["user_id"], element["date"], element["nick"], element["login"], @@ -849,8 +895,6 @@ Messages.prototype.rafraichirMessages = function(vider) this.stopAttenteCourante() - /*if (vider) - this.idDernierMessage = null*/ if (vider) for (var i = 0; i < this.conversations.length; i++) this.conversations[i].idDernierMessageAffiche = 0 diff --git a/modules/erl/euphorik_bd.erl b/modules/erl/euphorik_bd.erl index 1b37b4c..87dab2f 100755 --- a/modules/erl/euphorik_bd.erl +++ b/modules/erl/euphorik_bd.erl @@ -29,6 +29,7 @@ % messages : nouveau_message/3, + nouveau_message_sys/1, messages/1, messages/2, messages/3, @@ -44,6 +45,8 @@ % ip : ip_table/0, ban/2, + deban/1, + est_banni/1, can_register/1, % versions : @@ -114,10 +117,11 @@ reset() -> mnesia:clear_table(counter), mnesia:clear_table(user), mnesia:clear_table(reponse_minichat), - mnesia:clear_table(minichat), + mnesia:clear_table(minichat), + mnesia:clear_table(ip_table), % crée l'utilisateur root mnesia:transaction(fun() -> - User = #user{id = 0, pseudo = "Sys", login = "Sys", date_creation = now(), date_derniere_connexion = now()}, + User = #user{id = 0, pseudo = "Sys", login = "Sys", date_creation = now(), date_derniere_connexion = now(), ek_master = true}, mnesia:write(User), User end). @@ -160,7 +164,7 @@ set_profile(Cookie, Login, Password, Pseudo, Email, Css, Nick_format, Page_princ % TODO : pourquoi ne pas tester avec la valeur "undefined" plutôt qu'avec "is_list" ? % TODO : validation plus strict des données (pas de page négative dans les conv par exemple) login = if is_list(Login) -> Login; true -> User#user.login end, - password = if is_list(Password) -> Password; true -> User#user.password end, + password = if is_list(Password) andalso Password =/= [] -> Password; true -> User#user.password end, pseudo = if is_list(Pseudo) -> Pseudo; true -> User#user.pseudo end, email = if is_list(Email) -> Email; true -> User#user.email end, css = if is_list(Css) -> Css; true -> User#user.css end, @@ -303,12 +307,6 @@ user_by_mess(Id) -> end )). - -% Renvoie l'utilisateur root -root() -> - {ok, User} = user_by_id(0), - User. - % Ajoute un message. Repond_A est une liste d'id auquel le message répond % retourne soit l'id du message soit erreur. @@ -324,7 +322,6 @@ nouveau_message(Mess, Auteur_id, Repond_A) -> if Nb_id_trouve =/= length(Repond_A) -> throw("Un ou plusieurs messages introuvable"); true -> ok end, - Id = nouvel_id(minichat), % compare les dernière Delta = delta_date_ms(Auteur#user.date_derniere_connexion, now()), Nouvel_indice_flood = Auteur#user.indice_flood + if Delta =< ?DUREE_SPAM -> 2; true -> -1 end, @@ -334,14 +331,13 @@ nouveau_message(Mess, Auteur_id, Repond_A) -> }, % est-ce que l'auteur à trop floodé ? if Auteur#user.indice_flood =/= ?INDICE_SPAM_MAX, Auteur_maj#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM -> - Root = root(), mnesia:write(Auteur#user{indice_flood = Auteur_maj#user.indice_flood}), - mnesia:write(#minichat{id=Id, auteur_id=Root#user.id, date=now(), pseudo=Root#user.pseudo, contenu=Auteur#user.pseudo ++ "(" ++ Auteur#user.login ++ ") est bloqué pour " ++ integer_to_list(trunc(?DUREE_BLOCAGE_SPAM / 1000)) ++ " secondes pour cause de flood.."}), - Id; + nouveau_message_sys(Auteur#user.pseudo ++ "(" ++ Auteur#user.login ++ ") est bloqué pour " ++ integer_to_list(trunc(?DUREE_BLOCAGE_SPAM / 1000)) ++ " secondes pour cause de flood.."); Auteur#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM -> erreur; true -> mnesia:write(Auteur_maj), + Id = nouvel_id(minichat), inserer_reponses(Id, Repond_A), mnesia:write(#minichat{id=Id, auteur_id=Auteur#user.id, date=now(), pseudo=Auteur#user.pseudo, contenu=Mess}), Id @@ -356,6 +352,18 @@ inserer_reponses(Id_repondant, [Id_mess | Reste]) -> inserer_reponses(_, []) -> ok. + +% Permet de créer un message système. +% Renvoie l'id du message système +nouveau_message_sys(Mess) -> + {ok, Root} = user_by_id(0), + mnesia:transaction( + fun() -> + Id = nouvel_id(minichat), + mnesia:write(#minichat{id=Id, auteur_id=0, date=now(), pseudo=Root#user.pseudo, contenu=Mess}) + end + ). + % Renvoie N messages se trouvant sur la première page messages(N) -> @@ -498,6 +506,47 @@ ban(IP, Duration) -> ). +% Débanni une ip +deban(IP) -> + ban(IP, 0). + + +% Renvoie soit {true, Temps} où Temps est le temps en minutes pendant lequel le user est encore banni +% ou false. +est_banni(User_id) -> + resultat_transaction(mnesia:transaction( + fun() -> + case qlc:e(qlc:q([ + {IP#ip_table.ban, IP#ip_table.ban_duration} || + U <- mnesia:table(user), + U#user.id =:= User_id, + IP <- mnesia:table(ip_table), + IP#ip_table.ip =:= U#user.last_ip + ])) of + [{Ban, Ban_duration}] -> + Echeance = date_plus_minutes(Ban, Ban_duration), + Now = now(), + if Echeance < Now -> % l'échéance est passée + false; + true -> + {true, trunc(delta_date_ms(Echeance, Now) / 1000 / 60)} + end; + _ -> + false + end + end + )). + + +% Ban est une date tel que retourner par now(). +% Ban_duration est un temps en minutes. +% retourne une date. +date_plus_minutes(Ban, Ban_duration) -> + Duration_sec = Ban_duration * 60, + {MegaSec, Sec, MicroSec} = Ban, + {MegaSec + if Sec + Duration_sec >= 1000000 -> 1; true -> 0 end,(Sec + Duration_sec) rem 1000000, MicroSec}. + + % Si deux enregistrements consequtifs de la même IP sont fait en moins d'une seconde alors % ip_table.nb_try_register est incrémenté de 1 sinon il est décrémenté de 1 (jusqu'a 0). % Si ip_table.nb_try_register vaut 5 alors l'ip ne peux plus s'enregistrer pour une heure. diff --git a/modules/erl/euphorik_protocole.erl b/modules/erl/euphorik_protocole.erl index 194961e..dc29bf5 100755 --- a/modules/erl/euphorik_protocole.erl +++ b/modules/erl/euphorik_protocole.erl @@ -150,22 +150,17 @@ wait_event(Data) -> Est_proprietaire = User =/= inconnu andalso User#user.id =:= Mess#minichat.auteur_id, A_repondu_a_message = User =/= inconnu andalso euphorik_bd:a_repondu_a_message(User#user.id, Mess#minichat.id), Est_une_reponse_a_user = User =/= inconnu andalso euphorik_bd:est_une_reponse_a_user(User#user.id, Mess#minichat.id), - User_mess = - if Mess#minichat.auteur_id =:= 0 -> - inconnu; - true -> - {ok, U2} = euphorik_bd:user_by_id(Mess#minichat.auteur_id), - U2 - end, + {ok, User_mess } = euphorik_bd:user_by_id(Mess#minichat.auteur_id), {struct, [ {id, Mess#minichat.id}, + {user_id, User_mess#user.id}, {date, format_date(Mess#minichat.date)}, {system, Mess#minichat.auteur_id =:= 0}, {owner, Est_proprietaire}, {answered, A_repondu_a_message}, {is_a_reply, Est_une_reponse_a_user}, {nick, Mess#minichat.pseudo}, - {login, if User_mess =:= inconnu -> Mess#minichat.pseudo; true -> User_mess#user.login end}, + {login, User_mess#user.login}, {content, Mess#minichat.contenu}, {answer_to, {array, lists:map( fun(Id_mess) -> @@ -205,21 +200,37 @@ put_message( ) -> case euphorik_bd:user_by_cookie(Cookie) of {ok, User} -> - Strip_content = string:strip(Content), - if (Strip_content =:= []) -> - erreur("Message vide"); - true -> - % TODO : non-atomique (update_pseudo+nouveau_message) - euphorik_bd:update_pseudo_user(User#user.id, Nick), - case euphorik_bd:nouveau_message(Strip_content, User#user.id, Answer_to) of - erreur -> erreur("Impossible d'ajouter un nouveau message"); - _ -> - json_reponse_ok() + case euphorik_bd:est_banni(User#user.id) of + {true, Temps_restant} -> + erreur("Vous êtes banni pour encore " ++ format_minutes(Temps_restant)); + _ -> + Strip_content = string:strip(Content), + if Strip_content =:= [] -> + erreur("Message vide"); + true -> + % TODO : non-atomique (update_pseudo+nouveau_message) + euphorik_bd:update_pseudo_user(User#user.id, Nick), + case euphorik_bd:nouveau_message(Strip_content, User#user.id, Answer_to) of + erreur -> erreur("Impossible d'ajouter un nouveau message"); + _ -> + json_reponse_ok() + end end end; - _ -> - erreur("Utilisateur inconnu") - end. + _ -> + erreur("Utilisateur inconnu") + end. + + +% Formatage de minutes. +% par exemple : "1min", "45min", "1h23min", "1jour 2h34min" +format_minutes(Min) -> + Jours = Min div (60 * 24), + Heures = Min rem (60 * 24) div 60, + Minutes = Min rem (60), + if Jours =/= 0 -> integer_to_list(Jours) ++ "Jour" ++ if Jours > 1 -> "s"; true -> "" end ++ " "; true -> "" end ++ + if Heures =/= 0 -> integer_to_list(Heures) ++ "h"; true -> "" end ++ + lists:flatten(io_lib:format(if Jours =:= 0, Heures =:= 0 -> "~w"; true -> "~2.2.0w" end, [Minutes])) ++ "min". % bannissement d'un utilisateur (son ip est bannie) @@ -229,18 +240,31 @@ ban( {duration, Duration}, {user_id, User_id} ]) -> - % controle que l'utilisateur est un admin - case euphorik_bd:user_by_cookie(Cookie) of - {ok, User = #user{ek_master = true}} -> - case euphorik_bd:user_by_id(User_id) of - {ok, User} -> - euphorik_bd:ban(User#user.last_ip, Duration); - _ -> - erreur("Utilisateur à bannir inconnu") - end; - _ -> - erreur("Utilisateur inconnu ou non ek master") - end. + % controle que l'utilisateur est un admin + case euphorik_bd:user_by_cookie(Cookie) of + {ok, User1 = #user{ek_master = true}} -> + case euphorik_bd:user_by_id(User_id) of + {ok, User1} -> + erreur("Il n'est pas possible de s'auto bannir"); + {ok, User2 = #user{ek_master = false}} -> + euphorik_bd:ban(User2#user.last_ip, Duration), + euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s ~s est ~s pour ~s", + [ + User2#user.pseudo, + if User2#user.login =:= [] -> ""; true -> "(" ++ User2#user.login ++ ")" end, + if Duration =< 15 -> "kické"; true -> "banni" end, + format_minutes(Duration) + ] + ))), + json_reponse_ok(); + {ok, _} -> + erreur("L'utilisateur est lui même un ekMaster"); + _ -> + erreur("Utilisateur à bannir inconnu") + end; + _ -> + erreur("Utilisateur inconnu ou non ek master") + end. % Construit une erreur @@ -273,7 +297,7 @@ format_date(Date) -> end ++ io_lib:format("~2.10.0B:~2.10.0B:~2.10.0B", [Heure, Minute, Seconde]) ). - + json_reponse_ok() -> {struct, [{reply, "ok"}]}. -- 2.43.0