FIX correction d'un petit bug au niveau de la liste des personnes bannies
[euphorik.git] / modules / erl / euphorik_protocole.erl
1 % coding: utf-8
2 % Ce module gére les différents messages envoyés par le client (javascript) via AJAX.
3 % Les messages donnés ainsi que les réponses sont au format JSON.
4 % @author G.Burri
5
6 -module(euphorik_protocole).
7 -export([
8 register/2,
9 login/2,
10 logout/1,
11 profile/1,
12 wait_event/1,
13 put_message/1,
14 ban/1,
15 slap/1,
16 put_troll/1,
17 mod_troll/1,
18 del_troll/1,
19 unban_ip/1,
20 list_banned_ips/1,
21 erreur/1
22 ]).
23 -include_lib("xmerl/include/xmerl.hrl").
24 -include("../include/euphorik_bd.hrl").
25 -include("../include/euphorik_defines.hrl").
26
27
28 % Une utilisateur s'enregistre avec un tuple {Login, Password}.
29 register([{login, Login}, {password, Password}], IP) ->
30 Can_register = euphorik_bd:can_register(IP),
31 if Can_register ->
32 case euphorik_bd:user_by_login(Login) of
33 {ok, _} ->
34 erreur("Login déjà existant");
35 _ ->
36 User = euphorik_bd:nouveau_user(Login, Password, generer_cookie()),
37 euphorik_bd:update_ip(User#user.id, IP),
38 json_reponse_login_ok(User)
39 end;
40 true ->
41 erreur_register_flood()
42 end;
43 % Enregistrement sans {Login, Password}
44 register([], IP) ->
45 Can_register = euphorik_bd:can_register(IP),
46 if Can_register ->
47 User = euphorik_bd:nouveau_user("<nick>", generer_cookie()),
48 euphorik_bd:update_ip(User#user.id, IP),
49 json_reponse_login_ok(User);
50 true ->
51 erreur_register_flood()
52 end.
53
54 erreur_register_flood() ->
55 erreur("Trop de register (flood)").
56
57
58 % Un utilisateur se logge (avec un couple {login, mot de passe})
59 login([{login, Login}, {password, Password}], IP) ->
60 loginUser(euphorik_bd:user_by_login_password(Login, Password), IP);
61 % Un utilisateur se logge (avec un cookie)
62 login([{cookie, Cookie}], IP) ->
63 loginUser(euphorik_bd:user_by_cookie(Cookie), IP).
64
65 loginUser({ok, User}, IP) ->
66 euphorik_bd:update_ip(User#user.id, IP),
67 euphorik_bd:update_date_derniere_connexion(User#user.id),
68 json_reponse_login_ok(User);
69 loginUser(_, _) ->
70 % ajoute un délais d'attente
71 timer:sleep(1000),
72 erreur("Erreur login").
73
74
75 % Renvoie un string() représentant un cookie en base 36. Il y a 10^32 possibillités.
76 generer_cookie() ->
77 {A1,A2,A3} = now(),
78 random:seed(A1, A2, A3),
79 erlang:integer_to_list(random:uniform(math:pow(10, 32)), 36).
80
81
82 % Un utilisateur se délogge.
83 logout(_) ->
84 do_nothing.
85
86
87 % Modification du profile.
88 profile(
89 [
90 {cookie, Cookie},
91 {login, Login},
92 {password, Password},
93 {nick, Pseudo},
94 {email, Email},
95 {css, Css},
96 {nick_format, Nick_format_str},
97 {main_page, Main_page},
98 {conversations, {array, Conversations_json}}
99 ]
100 ) ->
101 % est-ce que les messages auquel on répond existent ?
102 Conversations = lists:foldr(
103 fun({struct, [{root, Root}, {page, Page}]}, Acc) ->
104 Message_existe = euphorik_bd:message_existe(Root),
105 if Message_existe ->
106 [{Root, Page} | Acc];
107 true ->
108 Acc
109 end
110 end,
111 [],
112 Conversations_json
113 ),
114 case euphorik_bd:set_profile(Cookie, Login, Password, Pseudo, Email, Css, list_to_atom(Nick_format_str), Main_page, Conversations) of
115 ok ->
116 json_reponse_ok();
117 login_deja_pris ->
118 erreur("Login déjà pris");
119 _ ->
120 erreur("Impossible de mettre à jour le profile")
121 end.
122
123
124 % Renvoie les messages appropriés.
125 % last_message id et cookie sont facultatifs
126 wait_event([{page, "chat"} | Data]) ->
127 % traitement des inputs
128 Cookie = case lists:keysearch(cookie, 1, Data) of {value, {_, C}} -> C; _ -> inconnu end,
129 Last_message_id = case lists:keysearch(last_message_id, 1, Data) of {value, {_, Id}} -> Id; _ -> 0 end,
130 {value, {_, Message_count}} = lists:keysearch(message_count, 1, Data),
131 Main_page = case lists:keysearch(main_page, 1, Data) of {value, {_, P}} -> P; _ -> 1 end,
132 Troll_id = case lists:keysearch(troll_id, 1, Data) of {value, {_, T}} -> T; _ -> 0 end,
133 {value, {_, {array, Conversations_json}}} = lists:keysearch(conversations, 1, Data),
134 Racines_conversations = lists:map(
135 fun({struct, [{root, Racine}, {page, Page} | Reste]}) ->
136 Last_mess_conv = case Reste of [{last_message_id, L}] -> L; _ -> 0 end,
137 {Racine, Page, Last_mess_conv}
138 end,
139 Conversations_json
140 ),
141 User = case euphorik_bd:user_by_cookie(Cookie) of
142 {ok, U} -> U;
143 _ -> inconnu
144 end,
145 case {mnesia:subscribe({table, minichat, detailed}), mnesia:subscribe({table, troll, detailed})} of
146 {{error, E}, _} -> E;
147 {_, {error, E}} -> E;
148 _ ->
149 % attente d'événements
150 R = wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id, Main_page, Troll_id),
151 mnesia:unsubscribe({table, minichat, detailed}),
152 mnesia:unsubscribe({table, troll, detailed}),
153 R
154 end;
155 wait_event([{page, "admin"}, {last_troll, Last_troll}]) ->
156 case euphorik_bd:trolls_attente(Last_troll) of
157 {mod, Troll} ->
158 {struct,
159 [
160 {reply, "troll_modified"},
161 {troll_id, Troll#troll.id},
162 {content, Troll#troll.content}
163 ]
164 };
165 {add, Trolls} ->
166 {struct,
167 [
168 {reply, "troll_added"},
169 {trolls, {array,
170 lists:map(
171 fun(T) ->
172 {ok, User} = euphorik_bd:user_by_id(T#troll.id_user),
173 {struct,
174 [
175 {troll_id, T#troll.id},
176 {content, T#troll.content},
177 {author, User#user.pseudo},
178 {author_id, User#user.id}
179 ]
180 }
181 end,
182 Trolls
183 )
184 }}
185 ]
186 };
187 {del, Troll_id} ->
188 {struct,
189 [
190 {reply, "troll_deleted"},
191 {troll_id, Troll_id}
192 ]
193 };
194 _ ->
195 erreur("timeout")
196 end;
197 wait_event(_) ->
198 erreur("Page inconnue").
199
200
201 wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id, Main_page, Troll_id) ->
202 % est-ce que le troll est à jour ?
203 case euphorik_bd:current_troll() of
204 Current when is_record(Current, troll), Current#troll.id =/= Troll_id ->
205 {struct, [
206 {reply, "new_troll"},
207 {troll_id, Current#troll.id},
208 {content, Current#troll.content}
209 ]};
210 _ ->
211 % est-ce qu'il y a des nouveaux messages ?
212 case euphorik_minichat_conversation:conversations(Racines_conversations, Message_count, Last_message_id, Main_page) of
213 vide ->
214 wait_event_bd_page_chat(),
215 % TODO : l'appel est-il bien tail-recursive ?
216 wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id, Main_page, Troll_id);
217 Conversations ->
218 % accrochez-vous ca va siouxer ;)
219 {struct, [
220 {reply, "new_messages"},
221 {conversations, {array,
222 lists:map(
223 fun({Conv, Plus}) ->
224 {struct, [
225 {last_page, not Plus},
226 {messages, {array,
227 lists:map(
228 fun({Mess, Repond_a}) ->
229 Est_proprietaire = User =/= inconnu andalso User#user.id =:= Mess#minichat.auteur_id,
230 A_repondu_a_message = User =/= inconnu andalso euphorik_bd:a_repondu_a_message(User#user.id, Mess#minichat.id),
231 Est_une_reponse_a_user = User =/= inconnu andalso euphorik_bd:est_une_reponse_a_user(User#user.id, Mess#minichat.id),
232 {ok, User_mess } = euphorik_bd:user_by_id(Mess#minichat.auteur_id),
233 {struct, [
234 {id, Mess#minichat.id},
235 {user_id, User_mess#user.id},
236 {date, format_date(Mess#minichat.date)},
237 {system, Mess#minichat.auteur_id =:= 0},
238 {owner, Est_proprietaire},
239 {answered, A_repondu_a_message},
240 {is_a_reply, Est_une_reponse_a_user},
241 {nick, Mess#minichat.pseudo},
242 {login, User_mess#user.login},
243 {content, Mess#minichat.contenu},
244 {answer_to, {array, lists:map(
245 fun(Id_mess) ->
246 {ok, M} = euphorik_bd:message_by_id(Id_mess),
247 {ok, User_reponse} = euphorik_bd:user_by_mess(M#minichat.id),
248 {struct, [{id, M#minichat.id}, {nick, M#minichat.pseudo}, {login, User_reponse#user.login}]}
249 end,
250 Repond_a
251 )}},
252 {ek_master, User_mess#user.ek_master}
253 ]}
254 end,
255 Conv
256 )
257 }}
258 ]}
259 end,
260 Conversations
261 )
262 }}
263 ]}
264 end
265 end.
266
267
268 % Attend un événement lié à la page 'chat'.
269 wait_event_bd_page_chat() ->
270 receive % attente d'un post
271 {mnesia_table_event, {write, minichat, _Message, [], _}} ->
272 ok;
273 {mnesia_table_event, {write, troll, Troll, [Old_troll | _], _}} when Troll#troll.date_post =/= undefined, Old_troll#troll.date_post == undefined ->
274 ok;
275 {tcp_closed, _} ->
276 exit(normal);
277 _ ->
278 wait_event_bd_page_chat()
279 % 60 minutes de timeout (on ne sais jamais)
280 % Après 60 minutes de connexion, le client doit donc reétablir une connexion
281 after 1000 * 60 * 60 ->
282 timeout
283 end.
284
285
286 % Un utilisateur envoie un message
287 put_message(
288 [
289 {cookie, Cookie},
290 {nick, Nick},
291 {content, Content},
292 {answer_to, {array, Answer_to}}
293 ]
294 ) ->
295 case euphorik_bd:user_by_cookie(Cookie) of
296 {ok, User} ->
297 case euphorik_bd:est_banni(User#user.id) of
298 {true, Temps_restant} ->
299 erreur("Vous êtes banni pour encore " ++ format_minutes(Temps_restant));
300 _ ->
301 Strip_content = string:strip(Content),
302 if Strip_content =:= [] ->
303 erreur("Message vide");
304 true ->
305 % TODO : non-atomique (update_pseudo+nouveau_message)
306 euphorik_bd:update_pseudo_user(User#user.id, Nick),
307 case euphorik_bd:nouveau_message(Strip_content, User#user.id, Answer_to) of
308 {erreur, R} -> erreur("Impossible d'ajouter un nouveau message. Raison : " ++ R);
309 _ ->
310 json_reponse_ok()
311 end
312 end
313 end;
314 _ ->
315 erreur("Utilisateur inconnu")
316 end.
317
318
319 % bannissement d'un utilisateur (son ip est bannie)
320 ban(
321 [
322 {cookie, Cookie},
323 {duration, Duration},
324 {user_id, User_id},
325 {reason, Reason}
326 ]) ->
327 % controle que l'utilisateur est un admin
328 case euphorik_bd:user_by_cookie(Cookie) of
329 {ok, User1 = #user{ek_master = true}} ->
330 case euphorik_bd:user_by_id(User_id) of
331 {ok, User1} ->
332 erreur("Il n'est pas possible de s'auto bannir");
333 {ok, User2 = #user{ek_master = false}} ->
334 euphorik_bd:ban(User2#user.last_ip, Duration),
335 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s ~s est ~s pour ~s.~s",
336 [
337 User2#user.pseudo,
338 if User2#user.login =:= [] -> ""; true -> "(" ++ User2#user.login ++ ")" end,
339 if Duration =< 15 -> "kické"; true -> "banni" end,
340 format_minutes(Duration),
341 if Reason =/= [] -> " - Raison: " ++ Reason; true -> "" end ++ "."
342 ]
343 ))),
344 json_reponse_ok();
345 {ok, _} ->
346 erreur("L'utilisateur est lui même un ekMaster");
347 _ ->
348 erreur("Utilisateur à bannir inconnu")
349 end;
350 _ ->
351 erreur("Utilisateur inconnu ou non ek master")
352 end.
353
354
355 % slapage d'un user (avertissement)
356 slap(
357 [
358 {cookie, Cookie},
359 {user_id, User_id},
360 {reason, Reason}
361 ]) ->
362 % controle que l'utilisateur est un admin
363 case euphorik_bd:user_by_cookie(Cookie) of
364 {ok, User1 = #user{ek_master = true}} ->
365 case euphorik_bd:user_by_id(User_id) of
366 {ok, User1} ->
367 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s s'auto slap~s.",
368 [
369 User1#user.pseudo,
370 if Reason =/= [] -> " - Raison: " ++ Reason; true -> "" end
371 ]
372 ))),
373 json_reponse_ok();
374 {ok, User2 = #user{ek_master = false}} ->
375 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s se fait slaper par ~s.~s",
376 [
377 User2#user.pseudo,
378 User1#user.pseudo,
379 if Reason =/= [] -> " - Raison: " ++ Reason; true -> "" end ++ "."
380 ]
381 ))),
382 json_reponse_ok();
383 {ok, _} ->
384 erreur("L'utilisateur est lui même un ekMaster");
385 _ ->
386 erreur("Utilisateur à slaper inconnu")
387 end;
388 _ ->
389 erreur("Utilisateur inconnu ou non ek master")
390 end.
391
392
393 put_troll(
394 [
395 {cookie, Cookie},
396 {content, Content}
397 ]
398 ) ->
399 % controle que l'utilisateur est un admin
400 case euphorik_bd:user_by_cookie(Cookie) of
401 {ok, User = #user{ek_master = true}} ->
402 case euphorik_bd:put_troll(User#user.id, Content) of
403 max_troll_reached_per_user ->
404 erreur(lists:flatten(io_lib:format("Le nombre de troll maximum par utilisateur est atteint : ~w ", [?NB_MAX_TROLL_WAITING_BY_USER])));
405 max_troll_reached ->
406 erreur(lists:flatten(io_lib:format("Le nombre de troll maximum en attente est atteint : ~w ", [?NB_MAX_TROLL_WAITING])));
407 _Id ->
408 json_reponse_ok()
409 end;
410 _ ->
411 erreur("Seul les ekMaster peuvent proposer des trolls")
412 end.
413
414
415 mod_troll(
416 [
417 {cookie, Cookie},
418 {troll_id, Troll_id},
419 {content, Content}
420 ]
421 ) ->
422 % controle que l'utilisateur est un admin
423 case euphorik_bd:user_by_cookie(Cookie) of
424 {ok, User = #user{ek_master = true}} ->
425 User_id = User#user.id,
426 case euphorik_bd:troll_by_id(Troll_id) of
427 {ok, #troll{id_user = User_id}} ->
428 euphorik_bd:mod_troll(Troll_id, Content),
429 json_reponse_ok();
430 _ ->
431 erreur("Vous ne posséder pas ce troll")
432 end;
433 _ ->
434 erreur("Seul les ekMaster peuvent proposer des trolls")
435 end.
436
437
438 del_troll(
439 [
440 {cookie, Cookie},
441 {troll_id, Troll_id}
442 ]
443 ) ->
444 % controle que l'utilisateur est un admin
445 case euphorik_bd:user_by_cookie(Cookie) of
446 {ok, User = #user{ek_master = true}} ->
447 User_id = User#user.id,
448 case euphorik_bd:troll_by_id(Troll_id) of
449 {ok, #troll{id_user = User_id}} ->
450 euphorik_bd:del_troll(Troll_id),
451 json_reponse_ok();
452 _ ->
453 erreur("Vous ne posséder pas ce troll")
454 end;
455 _ ->
456 erreur("Seul les ekMaster peuvent proposer des trolls")
457 end.
458
459
460 unban_ip(
461 [
462 {cookie, Cookie},
463 {ip, IP}
464 ]
465 ) ->
466 case euphorik_bd:user_by_cookie(Cookie) of
467 {ok, #user{ek_master = true}} ->
468 euphorik_bd:deban(unserialize_ip(IP)),
469 json_reponse_ok;
470 _ ->
471 erreur("Seul les ekMaster peuvent connaitre la liste des ips bannies")
472 end.
473
474
475 list_banned_ips(
476 [
477 {cookie, Cookie}
478 ]
479 ) ->
480 case euphorik_bd:user_by_cookie(Cookie) of
481 {ok, #user{ek_master = true}} ->
482 {
483 struct,
484 [
485 {reply, "list_banned_ips"},
486 {list, {array, lists:map(
487 fun({IP, T, Users}) ->
488 {struct,
489 [
490 {ip, serialize_ip(IP)},
491 {remaining_time, format_minutes(T)},
492 {users, {array, lists:map(
493 fun({Pseudo, Login}) ->
494 {struct,
495 [
496 {nick, Pseudo},
497 {login, Login}
498 ]
499 }
500 end,
501 Users
502 )}}
503 ]
504 }
505 end,
506 euphorik_bd:list_ban()
507 )}}
508 ]
509 };
510 _ ->
511 erreur("Seul les ekMaster peuvent connaitre la liste des ips bannies")
512 end.
513
514
515 % Construit une erreur
516 erreur(Message) ->
517 {
518 struct, [
519 {reply, "error"},
520 {error_message, Message}
521 ]
522 }.
523
524
525 serialize_ip(IP) ->
526 lists:flatten(io_lib:format("~w.~w.~w.~w", tuple_to_list(IP))).
527
528
529 unserialize_ip(IP) ->
530 case io_lib:fread("~d.~d.~d.~d", IP) of
531 {ok, [A, B, C, D], []} -> {A, B, C, D};
532 _ -> erreur
533 end.
534
535
536 % Formatage de minutes.
537 % par exemple : "1min", "45min", "1h23min", "1jour 2h34min"
538 format_minutes(Min) ->
539 Jours = Min div (60 * 24),
540 Heures = Min rem (60 * 24) div 60,
541 Minutes = Min rem (60),
542 if Jours =/= 0 -> integer_to_list(Jours) ++ " Jour" ++ if Jours > 1 -> "s"; true -> "" end ++ " "; true -> "" end ++
543 if Heures =/= 0 -> integer_to_list(Heures) ++ " heure" ++ if Heures > 1 -> "s"; true -> "" end; true -> "" end ++
544 if Minutes == 0 ->
545 "";
546 true ->
547 integer_to_list(Minutes) ++ " minute" ++ if Minutes > 1 -> "s"; true -> "" end
548 end.
549
550
551 % Formatage d'une heure
552 % local_time() -> string
553 format_date(Date) ->
554 DateLocal = calendar:now_to_local_time(Date),
555 DateNowLocal = calendar:local_time(),
556 {{Annee, Mois, Jour}, {Heure, Minute, Seconde}} = DateLocal,
557 {{AnneeNow, _, _}, {_, _, _}} = DateNowLocal,
558 Hier = calendar:date_to_gregorian_days(element(1, DateLocal)) =:= calendar:date_to_gregorian_days(element(1, DateNowLocal)) - 1,
559 lists:flatten(
560 if element(1, DateLocal) =:= element(1, DateNowLocal) ->
561 "";
562 Hier ->
563 "Hier ";
564 Annee =:= AnneeNow ->
565 io_lib:format("~2.10.0B/~2.10.0B ", [Jour, Mois]);
566 true ->
567 io_lib:format("~2.10.0B/~2.10.0B/~B ", [Jour, Mois, Annee])
568 end ++
569 io_lib:format("~2.10.0B:~2.10.0B:~2.10.0B", [Heure, Minute, Seconde])
570 ).
571
572
573 json_reponse_ok() ->
574 {struct, [{reply, "ok"}]}.
575
576
577 json_reponse_login_ok(User) ->
578 {
579 struct, [
580 {reply, "login"},
581 {status, if (User#user.password =/= []) and (User#user.login =/= []) -> "auth_registered"; true -> "auth_not_registered" end},
582 {cookie, User#user.cookie},
583 {id, User#user.id},
584 {nick, User#user.pseudo},
585 {login, User#user.login},
586 {email, User#user.email},
587 {css, User#user.css},
588 {nick_format, atom_to_list(User#user.nick_format)},
589 {main_page, User#user.page_principale},
590 {conversations,
591 {array,
592 lists:map(
593 fun(C) ->
594 {struct,
595 [
596 {root, element(1, C)},
597 {page, element(2, C)}
598 ]
599 }
600 end,
601 User#user.conversations
602 )
603 }
604 },
605 {ek_master, User#user.ek_master}
606 ]
607 }.