ADD trolls, fin coté serveur et début coté client
[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 erreur/1
20 ]).
21
22 -include_lib("xmerl/include/xmerl.hrl").
23 -include("../include/euphorik_bd.hrl").
24 -include("../include/euphorik_defines.hrl").
25
26
27 % Une utilisateur s'enregistre avec un tuple {Login, Password}.
28 register([{login, Login}, {password, Password}], IP) ->
29 Can_register = euphorik_bd:can_register(IP),
30 if Can_register ->
31 case euphorik_bd:user_by_login(Login) of
32 {ok, _} ->
33 erreur("Login déjà existant");
34 _ ->
35 User = euphorik_bd:nouveau_user(Login, Password, generer_cookie()),
36 euphorik_bd:update_ip(User#user.id, IP),
37 json_reponse_login_ok(User)
38 end;
39 true ->
40 erreur_register_flood()
41 end;
42 % Enregistrement sans {Login, Password}
43 register([], IP) ->
44 Can_register = euphorik_bd:can_register(IP),
45 if Can_register ->
46 User = euphorik_bd:nouveau_user("<nick>", generer_cookie()),
47 euphorik_bd:update_ip(User#user.id, IP),
48 json_reponse_login_ok(User);
49 true ->
50 erreur_register_flood()
51 end.
52
53 erreur_register_flood() ->
54 erreur("Trop de register (flood)").
55
56
57 % Un utilisateur se logge (avec un couple {login, mot de passe})
58 login([{login, Login}, {password, Password}], IP) ->
59 loginUser(euphorik_bd:user_by_login_password(Login, Password), IP);
60 % Un utilisateur se logge (avec un cookie)
61 login([{cookie, Cookie}], IP) ->
62 loginUser(euphorik_bd:user_by_cookie(Cookie), IP).
63
64 loginUser({ok, User}, IP) ->
65 euphorik_bd:update_ip(User#user.id, IP),
66 euphorik_bd:update_date_derniere_connexion(User#user.id),
67 json_reponse_login_ok(User);
68 loginUser(_, _) ->
69 % ajoute un délais d'attente (TODO : un autre moyen plus élégant ?)
70 receive after 1000 ->
71 erreur("Erreur login")
72 end.
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 Cookie = case lists:keysearch(cookie, 1, Data) of {value, {_, C}} -> C; _ -> inconnu end,
128 Last_message_id = case lists:keysearch(last_message_id, 1, Data) of {value, {_, Id}} -> Id; _ -> 0 end,
129 {value, {_, Message_count}} = lists:keysearch(message_count, 1, Data),
130 Main_page = case lists:keysearch(main_page, 1, Data) of {value, {_, P}} -> P; _ -> 1 end,
131 {value, {_, {array, Conversations_json}}} = lists:keysearch(conversations, 1, Data),
132 Conversations = lists:map(
133 fun({struct, [{root, Racine}, {page, Page} | Reste]}) ->
134 Last_mess_conv = case Reste of [{last_message_id, L}] -> L; _ -> 0 end,
135 {Racine, Page, Last_mess_conv}
136 end,
137 Conversations_json
138 ),
139 User = case euphorik_bd:user_by_cookie(Cookie) of
140 {ok, U} -> U;
141 _ -> inconnu
142 end,
143 {struct, [
144 {reply, "new_message"},
145 {conversations, {array,
146 % accrochez-vous ca va siouxer ;)
147 lists:map(
148 fun({Conv, Plus}) ->
149 {struct, [
150 {last_page, not Plus},
151 {messages, {array,
152 lists:map(
153 fun({Mess, Repond_a}) ->
154 Est_proprietaire = User =/= inconnu andalso User#user.id =:= Mess#minichat.auteur_id,
155 A_repondu_a_message = User =/= inconnu andalso euphorik_bd:a_repondu_a_message(User#user.id, Mess#minichat.id),
156 Est_une_reponse_a_user = User =/= inconnu andalso euphorik_bd:est_une_reponse_a_user(User#user.id, Mess#minichat.id),
157 {ok, User_mess } = euphorik_bd:user_by_id(Mess#minichat.auteur_id),
158 {struct, [
159 {id, Mess#minichat.id},
160 {user_id, User_mess#user.id},
161 {date, format_date(Mess#minichat.date)},
162 {system, Mess#minichat.auteur_id =:= 0},
163 {owner, Est_proprietaire},
164 {answered, A_repondu_a_message},
165 {is_a_reply, Est_une_reponse_a_user},
166 {nick, Mess#minichat.pseudo},
167 {login, User_mess#user.login},
168 {content, Mess#minichat.contenu},
169 {answer_to, {array, lists:map(
170 fun(Id_mess) ->
171 {ok, M} = euphorik_bd:message_by_id(Id_mess),
172 {ok, User_reponse} = euphorik_bd:user_by_mess(M#minichat.id),
173 {struct, [{id, M#minichat.id}, {nick, M#minichat.pseudo}, {login, User_reponse#user.login}]}
174 end,
175 Repond_a
176 )}},
177 {ek_master, User_mess#user.ek_master}
178 ]}
179 end,
180 Conv
181 )
182 }}
183 ]}
184 end,
185 euphorik_minichat_conversation:conversations(
186 Conversations,
187 Message_count,
188 Last_message_id,
189 Main_page
190 )
191 )
192 }}
193 ]};
194 wait_event([{page, "admin"}, {last_troll, Last_troll}]) ->
195 case euphorik_bd:trolls(Last_troll) of
196 {mod, Troll} ->
197 {struct,
198 [
199 {reply, "troll_modified"},
200 {troll_id, Troll#troll.id},
201 {content, Troll#troll.content}
202 ]
203 };
204 {add, Trolls} ->
205 {struct,
206 [
207 {reply, "troll_added"},
208 {trolls, {array,
209 lists:map(
210 fun(T) ->
211 {struct,
212 [
213 {troll_id, T#troll.id},
214 {content, T#troll.content}
215 ]
216 }
217 end,
218 Trolls
219 )
220 }}
221 ]
222 };
223 {del, Troll_id} ->
224 {struct,
225 [
226 {reply, "troll_deleted"},
227 {troll_id, Troll_id}
228 ]
229 };
230 _ ->
231 erreur("timeout")
232 end;
233 wait_event(_) ->
234 erreur("Page inconnue").
235
236
237 % Un utilisateur envoie un message
238 put_message(
239 [
240 {cookie, Cookie},
241 {nick, Nick},
242 {content, Content},
243 {answer_to, {array, Answer_to}}
244 ]
245 ) ->
246 case euphorik_bd:user_by_cookie(Cookie) of
247 {ok, User} ->
248 case euphorik_bd:est_banni(User#user.id) of
249 {true, Temps_restant} ->
250 erreur("Vous êtes banni pour encore " ++ format_minutes(Temps_restant));
251 _ ->
252 Strip_content = string:strip(Content),
253 if Strip_content =:= [] ->
254 erreur("Message vide");
255 true ->
256 % TODO : non-atomique (update_pseudo+nouveau_message)
257 euphorik_bd:update_pseudo_user(User#user.id, Nick),
258 case euphorik_bd:nouveau_message(Strip_content, User#user.id, Answer_to) of
259 erreur -> erreur("Impossible d'ajouter un nouveau message");
260 _ ->
261 json_reponse_ok()
262 end
263 end
264 end;
265 _ ->
266 erreur("Utilisateur inconnu")
267 end.
268
269
270 % Formatage de minutes.
271 % par exemple : "1min", "45min", "1h23min", "1jour 2h34min"
272 format_minutes(Min) ->
273 Jours = Min div (60 * 24),
274 Heures = Min rem (60 * 24) div 60,
275 Minutes = Min rem (60),
276 if Jours =/= 0 -> integer_to_list(Jours) ++ "Jour" ++ if Jours > 1 -> "s"; true -> "" end ++ " "; true -> "" end ++
277 if Heures =/= 0 -> integer_to_list(Heures) ++ "h"; true -> "" end ++
278 if Minutes == 0 ->
279 "";
280 true ->
281 lists:flatten(io_lib:format(if Jours =:= 0, Heures =:= 0 -> "~w"; true -> "~2.2.0w" end, [Minutes])) ++ "min"
282 end.
283
284
285 % bannissement d'un utilisateur (son ip est bannie)
286 ban(
287 [
288 {cookie, Cookie},
289 {duration, Duration},
290 {user_id, User_id},
291 {reason, Reason}
292 ]) ->
293 % controle que l'utilisateur est un admin
294 case euphorik_bd:user_by_cookie(Cookie) of
295 {ok, User1 = #user{ek_master = true}} ->
296 case euphorik_bd:user_by_id(User_id) of
297 {ok, User1} ->
298 erreur("Il n'est pas possible de s'auto bannir");
299 {ok, User2 = #user{ek_master = false}} ->
300 euphorik_bd:ban(User2#user.last_ip, Duration),
301 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s ~s est ~s pour ~s.~s",
302 [
303 User2#user.pseudo,
304 if User2#user.login =:= [] -> ""; true -> "(" ++ User2#user.login ++ ")" end,
305 if Duration =< 15 -> "kické"; true -> "banni" end,
306 format_minutes(Duration),
307 if Reason =/= [] -> " Raison: " ++ Reason; true -> "" end ++ "."
308 ]
309 ))),
310 json_reponse_ok();
311 {ok, _} ->
312 erreur("L'utilisateur est lui même un ekMaster");
313 _ ->
314 erreur("Utilisateur à bannir inconnu")
315 end;
316 _ ->
317 erreur("Utilisateur inconnu ou non ek master")
318 end.
319
320
321 % slapage d'un user (avertissement)
322 slap(
323 [
324 {cookie, Cookie},
325 {user_id, User_id},
326 {reason, Reason}
327 ]) ->
328 % controle que l'utilisateur est un admin
329 case euphorik_bd:user_by_cookie(Cookie) of
330 {ok, User1 = #user{ek_master = true}} ->
331 case euphorik_bd:user_by_id(User_id) of
332 {ok, User1} ->
333 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s s'auto slap~s.",
334 [
335 User1#user.pseudo,
336 if Reason =/= [] -> " Raison: " ++ Reason; true -> "" end
337 ]
338 )));
339 {ok, User2 = #user{ek_master = false}} ->
340 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s se fait slaper par ~s.~s",
341 [
342 User2#user.pseudo,
343 User1#user.pseudo,
344 if Reason =/= [] -> " Raison: " ++ Reason; true -> "" end ++ "."
345 ]
346 ))),
347 json_reponse_ok();
348 {ok, _} ->
349 erreur("L'utilisateur est lui même un ekMaster");
350 _ ->
351 erreur("Utilisateur à slaper inconnu")
352 end;
353 _ ->
354 erreur("Utilisateur inconnu ou non ek master")
355 end.
356
357
358 put_troll(
359 [
360 {cookie, Cookie},
361 {content, Content}
362 ]
363 ) ->
364 % controle que l'utilisateur est un admin
365 case euphorik_bd:user_by_cookie(Cookie) of
366 {ok, User = #user{ek_master = true}} ->
367 case euphorik_bd:put_troll(User#user.id, Content) of
368 max_troll_reached_per_user ->
369 erreur(lists:flatten(io_lib:format("Le nombre de troll maximum par utilisateur est atteint : ~w ", [?NB_MAX_TROLL_WAITING_BY_USER])));
370 max_troll_reached ->
371 erreur(lists:flatten(io_lib:format("Le nombre de troll maximum en attente est atteint : ~w ", [?NB_MAX_TROLL_WAITING])));
372 _Id ->
373 json_reponse_ok()
374 end;
375 _ ->
376 erreur("Seul les ekMaster peuvent proposer des trolls")
377 end.
378
379
380 mod_troll(
381 [
382 {cookie, Cookie},
383 {troll_id, Troll_id},
384 {content, Content}
385 ]
386 ) ->
387 % controle que l'utilisateur est un admin
388 case euphorik_bd:user_by_cookie(Cookie) of
389 {ok, User = #user{ek_master = true}} ->
390 User_id = User#user.id,
391 case euphorik_bd:troll_by_id(Troll_id) of
392 {ok, #troll{id_user = User_id}} ->
393 euphorik_bd:mod_troll(User#user.id, Content),
394 json_reponse_ok();
395 _ ->
396 erreur("Vous ne posséder pas ce troll")
397 end;
398 _ ->
399 erreur("Seul les ekMaster peuvent proposer des trolls")
400 end.
401
402
403 del_troll(
404 [
405 {cookie, Cookie},
406 {troll_id, Troll_id}
407 ]
408 ) ->
409 % controle que l'utilisateur est un admin
410 case euphorik_bd:user_by_cookie(Cookie) of
411 {ok, User = #user{ek_master = true}} ->
412 User_id = User#user.id,
413 case euphorik_bd:troll_by_id(Troll_id) of
414 {ok, #troll{id_user = User_id}} ->
415 euphorik_bd:del_troll(User#user.id),
416 json_reponse_ok();
417 _ ->
418 erreur("Vous ne posséder pas ce troll")
419 end;
420 _ ->
421 erreur("Seul les ekMaster peuvent proposer des trolls")
422 end.
423
424
425 % Construit une erreur
426 erreur(Message) ->
427 {
428 struct, [
429 {reply, "error"},
430 {error_message, Message}
431 ]
432 }.
433
434
435 % Formatage d'une heure
436 % local_time() -> string
437 format_date(Date) ->
438 DateLocal = calendar:now_to_local_time(Date),
439 DateNowLocal = calendar:local_time(),
440 {{Annee, Mois, Jour}, {Heure, Minute, Seconde}} = DateLocal,
441 {{AnneeNow, _, _}, {_, _, _}} = DateNowLocal,
442 Hier = calendar:date_to_gregorian_days(element(1, DateLocal)) =:= calendar:date_to_gregorian_days(element(1, DateNowLocal)) - 1,
443 lists:flatten(
444 if element(1, DateLocal) =:= element(1, DateNowLocal) ->
445 "";
446 Hier ->
447 "Hier ";
448 Annee =:= AnneeNow ->
449 io_lib:format("~2.10.0B/~2.10.0B ", [Jour, Mois]);
450 true ->
451 io_lib:format("~2.10.0B/~2.10.0B/~B ", [Jour, Mois, Annee])
452 end ++
453 io_lib:format("~2.10.0B:~2.10.0B:~2.10.0B", [Heure, Minute, Seconde])
454 ).
455
456
457 json_reponse_ok() ->
458 {struct, [{reply, "ok"}]}.
459
460
461 json_reponse_login_ok(User) ->
462 {
463 struct, [
464 {reply, "login"},
465 {status, if (User#user.password =/= []) and (User#user.login =/= []) -> "auth_registered"; true -> "auth_not_registered" end},
466 {cookie, User#user.cookie},
467 {id, User#user.id},
468 {nick, User#user.pseudo},
469 {login, User#user.login},
470 {email, User#user.email},
471 {css, User#user.css},
472 {nick_format, atom_to_list(User#user.nick_format)},
473 {main_page, User#user.page_principale},
474 {conversations,
475 {array,
476 lists:map(
477 fun(C) ->
478 {struct,
479 [
480 {root, element(1, C)},
481 {page, element(2, C)}
482 ]
483 }
484 end,
485 User#user.conversations
486 )
487 }
488 },
489 {ek_master, User#user.ek_master}
490 ]
491 }.