color: #76ff33;
}
+#page.minichat div.message.ekMaster .pseudo {
+ color: #ffffff;
+}
+
#page.minichat div.message .pseudo .login {
margin-left: 2px;
font-size: 8px;
* Traiter les tags TODO et FIXME dans le code
* Mettre les constantes au niveau du serveur dans euphorik_defines.hrl (par exemple les temps lié au flood)\r
* Cleaner le code (erl, js, xhtml, css) et eventuellement profiler un peu (le refresh est lent sous opera)\r
-* Restructurer le code Erlang : déplacer certaines fonctions d'un module à l'autre (ev. créer des modules)\r
* Choisir une licence et la mettre un peu partout dans les sources, voir : http://www.gnu.org/licenses/gpl-howto.fr.html\r
* Finir le script de mise en production\r
* Make des modules.\r
[ok] Tester avec des caractères exotiques (jap, coréen, etc..)
[ok] Modifier la syntaxe des smiles actuels (pour pas qu'ils entre en conflit avec totoz)
[ok] Trouver un moyen pour éviter la création à la suite de plusieurs comptes (via register).
+[ok] Restructurer le code Erlang : déplacer certaines fonctions d'un module à l'autre (ev. créer des modules)
=== Bugs ===
"answer_to" : [\r
{ "id" : 123, "nick" : "Pierre", "login" : "pierre_45" }\r
]
+ "ek_master" : true | false
}\r
]\r
}
{
"action" : "ban",
"cookie" : "LKJDLAKSJBFLKASN",
- "duration" : 3, // en heure
+ "duration" : 15, // en minute
"user_id" : 67
}
}
\r
/**\r
- * FIXME : Cette méthode est attrocement lourde ! A optimiser.\r
+ * FIXME : Cette méthode est attrocement lourde ! A optimiser.
+ * moyenne su échantillon : 234ms\r
*/
Formateur.prototype.traiterSmiles = function(M)
{
this.nickFormat = "nick"
this.pagePrincipale = 1
- this.ek_master = false
+ this.ekMaster = false
// les conversations, une conversation est un objet possédant les attributs suivants :
// - racine (entier)
// les conversations
thisClient.conversations = data["conversations"]
- thisClient.ek_master = data["ek_master"]
+ thisClient.ekMaster = data["ek_master"]
}
this.dernierMessageErreur = data["error_message"]
}
this.appartientAuClient = false
this.clientARepondu = false
this.estUneReponse = false
+ this.ekMaster = false
this.systeme = false // est-ce un message 'système' ?
}
/**
- * Défini les fonctions (callback) appelées lorsque l'on change de page.
+ * Défini la page courante et s'il l'on se trouve sur la dernière page.
* @pageCourante la page courante
* @dernierePage true si c'est la dernière page sinon false
*/
-Conversation.prototype.eventsPage = function(pageCourante, dernierePage)
+Conversation.prototype.setPage = function(pageCourante, dernierePage)
{
jQuery("#conversations #" + this.getId() + " .numPage").text(pageCourante)
jQuery("#conversations #" + this.getId() + " .next").css("display", pageCourante == 1 ? "none" : "inline")
delete this.messagesParId[this.messages.shift().id]
}
+/**
+ * FIXME : méthode très lourde. ne serait-ce pas mieux de virer d'un coup l'élément conversation et d'en remettre un vide ?
+ */
Conversation.prototype.viderMessages = function()
{
this.messages = new Array()
/**
* Après l'ajout d'un ou plusieurs message cette méthode est appelée afin
* d'afficher les messages non-affichés.
+ * FIXME : méthode super lourde, à optimiser.
* @param funClickExtract fonction (fun(numMess)) appellée lors du clic sur un bouton "extraire"
*/
Conversation.prototype.flush = function(funClickOuvrirConv)
(this.messages[i].clientARepondu ? " repondu" : "") +
(this.messages[i].estUneReponse ? " reponse" : "") +
(this.messages[i].systeme ? " systeme" : "") +
+ (this.messages[i].ekMaster ? " ekMaster" : "") +
"\">" +
"<div class=\"extraire\">></div>" +
"[<span class=\"date\">" + message.date + "</span>]" +
if (nbMessagesAffiche > this.nbMessageMax)
jQuery("#conversations #" + this.getId() + " .message").slice(this.nbMessageMax, nbMessagesAffiche).empty()
- // Ajoute les événements liés à chaque message
+ // ajoute les événements liés à chaque nouveau message
jQuery("#conversations #" + this.getId() + " .message").filter(function(){return parseInt(jQuery(this).attr("id"), 36) > thisConversation.idDernierMessageAffiche}).each(
function()
{
* Ajoute un ensemble de messages puis les affiches.
* @param elements un tableau d'éléments JSON représentant les messages, voir protocole.txt
* @param numConversation le numéro de la conversation auquel appartiennent les messages
+ * @return true si les messages on été ajoutés, false si la conversation n'existe pas et qu'il n'y a pas de message
*/
Messages.prototype.ajouterMessages = function(elements, numConversation)
{
+ if (elements["messages"].length == 0 && typeof(this.conversations[numConversation]) == "undefined")
+ return false
+
for (var i = 0; i < elements["messages"].length; i++)
this.ajouterMessage(elements["messages"][i], numConversation)
this.flush(numConversation)
+
+ // renseigne la conversation sur la page courante et si c'est la dernière
+ this.conversations[numConversation].setPage(
+ numConversation == 0 ? this.client.pagePrincipale : this.client.conversations[numConversation - 1].page,
+ elements["last_page"]
+ )
+
+ return true
}
/**
// pas d'utilisation de jquery pour des raisons de performance
var id = element["id"]
- // Obsolète
- /*if (this.idDernierMessage == null || id > this.idDernierMessage)
- this.idDernierMessage = id*/
-
var message = new Message(
id,
element["date"],
message.estUneReponse = element["is_a_reply"]
message.systeme = element["system"]
message.setRepondA(element["answer_to"])
+ message.ekMaster = element["ek_master"]
if (this.conversations[numConversation] == null)
{
{
thisPage.util.replaceSelection(
jQuery("form input.message")[0],
- "{" + thisMessages.client.conversations[num-1].root + "}"
+ "{" + thisMessages.client.conversations[num-1].root.toString(36) + "}"
)
}
)
// ajoute les messages reçus à leur conversation respective
for (var numConv = 0; numConv < data["conversations"].length; numConv++)
{
- // ya pas de nouveaux message -> on passe à la prochaine conversation
- if (data["conversations"][numConv]["messages"].length == 0) continue
-
- thisMessages.ajouterMessages(data["conversations"][numConv], numConv)
+ // ya pas de nouveaux message -> on passe à la prochaine conversation FIXME : marche pas
+ //if (data["conversations"][numConv]["messages"].length == 0) continue
- // définit les événements liés à la conversation
- thisMessages.conversations[numConv].eventsPage(
- numConv == 0 ? thisMessages.client.pagePrincipale : thisMessages.client.conversations[numConv - 1].page,
- data["conversations"][numConv]["last_page"]
- )
+ if (! thisMessages.ajouterMessages(data["conversations"][numConv], numConv))
+ {
+ thisMessages.util.messageDialogue("La conversation {" + thisMessages.client.conversations[numConv -1].root.toString(36) + "} n'existe pas")
+ thisMessages.client.supprimerConversation(numConv - 1)
+ }
}
// rappel de la fonction dans 100 ms
nouveau_user/3,
set_profile/9,
update_date_derniere_connexion/1,
+ update_ip/2,
update_pseudo_user/2,
users/0,
user_by_cookie/1,
user_by_login/1,
user_by_login_password/2,
user_by_mess/1,
+ toggle_ek_master/1,
% messages :
nouveau_message/3,
a_repondu_a_message/2,
possede_message/2,
+ % ip :
+ ip_table/0,
+ ban/2,
+
+ % versions :
+ update_version/1,
+
% utiles :
can_register/1,
resultat_transaction/1\r
% Met à jour la date de la dernière connexion d'un utilisateur à maintenant
-update_date_derniere_connexion(UserId) ->
+update_date_derniere_connexion(User_id) ->
mnesia:transaction(
fun() ->
- case mnesia:wread({user, UserId}) of
+ case mnesia:wread({user, User_id}) of
[User] ->
mnesia:write(User#user{date_derniere_connexion = now()});
_ ->
end
end
).
-
+
+
+% Met à jour l'ip d'un user
+update_ip(User_id, IP) ->
+ mnesia:transaction(
+ fun() ->
+ case mnesia:wread({user, User_id}) of
+ [User] ->
+ mnesia:write(User#user{last_ip = IP});
+ _ ->
+ mnesia:abort("update_ip: User inconnu")
+ end
+ end
+ ).
+
% Met à jour le pseudo du user
update_pseudo_user(UserId, Pseudo) ->
)).\r
+toggle_ek_master(User_id) ->
+ resultat_transaction(mnesia:transaction(
+ fun() ->
+ Users = qlc:e(qlc:q([E || E <- mnesia:table(user), E#user.id =:= User_id])),
+ case Users of
+ [User] ->
+ mnesia:write(User#user{ek_master = not User#user.ek_master});
+ _ -> erreur
+ end
+ end
+ )).
+
+
user_by_login_password(Login, Password) ->
resultat_transaction(mnesia:transaction(
fun() ->
_ -> false
end.
+
+ip_table() ->
+ resultat_transaction(mnesia:transaction(
+ fun() ->
+ qlc:e(qlc:q([IP || IP <- mnesia:table(ip_table)]))
+ end
+ )).
+
+
+% Bannie une ip pour un certain temps (en minute).
+ban(IP, Duration) ->
+ mnesia:transaction(
+ fun() ->
+ case mnesia:wread({ip_table, IP}) of
+ [IP_tuple] ->
+ mnesia:write(IP_tuple#ip_table{ban = now(), ban_duration = Duration});
+ _ ->
+ mnesia:write(#ip_table{ip = IP, ban = now(), ban_duration = Duration})
+ end
+ end
+ ).
+
+
+update_version(1) ->
+ mnesia:transform_table(
+ ip_table,
+ fun() -> null end,
+ record_info(fields, ip_table),
+ ip_table
+ ).
+
% 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).
-module(euphorik_protocole).
-export([
register/2,
- login/1,
+ login/2,
logout/1,
profile/1,
wait_event/1,
put_message/1,
+ ban/1,
erreur/1
]).\r
erreur("Login déjà existant");
_ ->
User = euphorik_bd:nouveau_user(Login, Password, generer_cookie()),
+ euphorik_bd:update_ip(User#user.id, IP),
json_reponse_login_ok(User)
end;
true ->
Can_register = euphorik_bd:can_register(IP),
if Can_register ->
User = euphorik_bd:nouveau_user("<nick>", generer_cookie()),
+ euphorik_bd:update_ip(User#user.id, IP),
json_reponse_login_ok(User);
true ->
erreur_register_flood()
\r
% Un utilisateur se logge (avec un couple {login, mot de passe})
-login([{login, Login}, {password, Password}]) ->
- loginUser(euphorik_bd:user_by_login_password(Login, Password));
+login([{login, Login}, {password, Password}], IP) ->
+ loginUser(euphorik_bd:user_by_login_password(Login, Password), IP);
% Un utilisateur se logge (avec un cookie)
-login([{cookie, Cookie}]) ->
- loginUser(euphorik_bd:user_by_cookie(Cookie)).
+login([{cookie, Cookie}], IP) ->
+ loginUser(euphorik_bd:user_by_cookie(Cookie), IP).
-loginUser({ok, User}) ->
+loginUser({ok, User}, IP) ->
+ euphorik_bd:update_ip(User#user.id, IP),
euphorik_bd:update_date_derniere_connexion(User#user.id),
json_reponse_login_ok(User);
-loginUser(_) ->
+loginUser(_, _) ->
% ajoute un délais d'attente (TODO : un autre moyen plus élégant ?)
receive after 1000 ->
erreur("Erreur login")
{ok, U2} = euphorik_bd:user_by_id(Mess#minichat.auteur_id),
U2
end,
- {struct, [
- {id, Mess#minichat.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},
- {content, Mess#minichat.contenu},
- {answer_to, {array, lists:map(
- fun(Id_mess) ->
- {ok, M} = euphorik_bd:message_by_id(Id_mess),
- {ok, User_reponse} = euphorik_bd:user_by_mess(M#minichat.id),
- {struct, [{id, M#minichat.id}, {nick, M#minichat.pseudo}, {login, User_reponse#user.login}]}
- end,
- Repond_a
- )}}
- ]}
+ {struct, [
+ {id, Mess#minichat.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},
+ {content, Mess#minichat.contenu},
+ {answer_to, {array, lists:map(
+ fun(Id_mess) ->
+ {ok, M} = euphorik_bd:message_by_id(Id_mess),
+ {ok, User_reponse} = euphorik_bd:user_by_mess(M#minichat.id),
+ {struct, [{id, M#minichat.id}, {nick, M#minichat.pseudo}, {login, User_reponse#user.login}]}
+ end,
+ Repond_a
+ )}},
+ {ek_master, User_mess#user.ek_master}
+ ]}
end,
Conv
)
end.
+% bannissement d'un utilisateur (son ip est bannie)
+ban(
+ [
+ {cookie, Cookie},
+ {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.
+
+
% Construit une erreur
erreur(Message) ->
{
User#user.conversations
)
}
- }
+ },
+ {ek_master, User#user.ek_master}
]
}.
\r
out(A) ->
- %io:format("~p~n~n", [A]),\r
+ %io:format("~p~n~n", [A]),
+ IP = case inet:peername(A#arg.clisock) of
+ {ok, {Adresse, _Port}} -> Adresse;
+ _ -> inconnue
+ end,\r
%inet:setopts(A#arg.clisock, inet:getopts(A#arg.clisock, [active])),\r
{value, {_, Contenu}} = lists:keysearch("action", 1, yaws_api:parse_post(A)),\r
- Ret = traiter_donnees(Contenu, 1),
+ Ret = traiter_donnees(Contenu, IP),
{content, "application/json", Ret}.\r
\r
% authentification d'un client
-traiter_action("authentification", JSON, _) ->
- euphorik_protocole:login(JSON);
+traiter_action("authentification", JSON, IP) ->
+ euphorik_protocole:login(JSON, IP);
% un client s'enregistre (pseudo + password)
traiter_action("register", JSON, IP) ->
euphorik_protocole:register(JSON, IP);
euphorik_protocole:wait_event(JSON);
% un utilisateur envoie un message
traiter_action("put_message", JSON, _) ->
- euphorik_protocole:put_message(JSON).
+ euphorik_protocole:put_message(JSON);
+traiter_action("ban", JSON, _) ->
+ euphorik_protocole:ban(JSON).
\ No newline at end of file
page_principale = 1, % la page de la conversation principale
conversations = [], % [{integer(), integer()}], la liste des messages correspondant au conversation ainsi que la page affichée
ek_master = false,
- last_ip % integer(), undefined si inconnu\r
+ last_ip = undefined % integer(), undefined si inconnu\r
}).
-record(ip_table,
{
ip, % integer()
- ban = false,
+ ban = undefined, % la date du dernier bannissement
+ ban_duration = 0, % le temps de ban en minute
nb_try_register = 0,
nb_try_login = 0, % pour l'instant pas utilisé
date_last_try_register,
# SciTE session file
-buffer.1.path=/home/gburri/projets/euphorik/modules/erl/euphorik_minichat.erl
-buffer.1.position=360
-buffer.1.current=1
+buffer.1.path=/home/gburri/projets/euphorik/modules/erl/euphorik_minichat_conversation.erl
+buffer.1.position=580
-buffer.2.path=/home/gburri/projets/euphorik/modules/erl/euphorik_minichat_conversation.erl
+buffer.2.path=/home/gburri/projets/euphorik/modules/erl/euphorik_protocole.erl
buffer.2.position=1
-buffer.3.path=/home/gburri/projets/euphorik/modules/erl/euphorik_protocole.erl
+buffer.3.path=/home/gburri/projets/euphorik/modules/erl/euphorik_requests.erl
buffer.3.position=1
-buffer.4.path=/home/gburri/projets/euphorik/modules/erl/euphorik_requests.erl
+buffer.4.path=/home/gburri/projets/euphorik/modules/include/euphorik_bd.hrl
buffer.4.position=1
-buffer.5.path=/home/gburri/projets/euphorik/modules/include/euphorik_bd.hrl
-buffer.5.position=1
+buffer.5.path=/home/gburri/projets/euphorik/modules/include/euphorik_defines.hrl
+buffer.5.position=336
-buffer.6.path=/home/gburri/projets/euphorik/modules/include/euphorik_defines.hrl
-buffer.6.position=336
+buffer.6.path=/home/gburri/projets/euphorik/modules/erl/euphorik_bd.erl
+buffer.6.position=1
+buffer.6.current=1
buffer.2.path=/home/gburri/projets/euphorik/js/pageMinichat.js
buffer.2.position=7496
+buffer.2.current=1
buffer.3.path=/home/gburri/projets/euphorik/js/pageProfile.js
buffer.3.position=1
buffer.5.path=/home/gburri/projets/euphorik/js/debug.js
buffer.5.position=1
-buffer.5.current=1
+
+buffer.6.path=/home/gburri/projets/euphorik/css/1/euphorik.css
+buffer.6.position=1
+
+buffer.7.path=/home/gburri/projets/euphorik/css/1/pageAbout.css
+buffer.7.position=1
+
+buffer.8.path=/home/gburri/projets/euphorik/css/1/pageMinichat.css
+buffer.8.position=1
+
+buffer.9.path=/home/gburri/projets/euphorik/css/1/pageProfileRegister.css
+buffer.9.position=164