ADD avancement sur les trolls, partie d'administration presque terminée
[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 {ok, User} = euphorik_bd:user_by_id(T#troll.id_user),
212 {struct,
213 [
214 {troll_id, T#troll.id},
215 {content, T#troll.content},
216 {author, User#user.pseudo},
217 {author_id, User#user.id}
218 ]
219 }
220 end,
221 Trolls
222 )
223 }}
224 ]
225 };
226 {del, Troll_id} ->
227 {struct,
228 [
229 {reply, "troll_deleted"},
230 {troll_id, Troll_id}
231 ]
232 };
233 _ ->
234 erreur("timeout")
235 end;
236 wait_event(_) ->
237 erreur("Page inconnue").
238
239
240 % Un utilisateur envoie un message
241 put_message(
242 [
243 {cookie, Cookie},
244 {nick, Nick},
245 {content, Content},
246 {answer_to, {array, Answer_to}}
247 ]
248 ) ->
249 case euphorik_bd:user_by_cookie(Cookie) of
250 {ok, User} ->
251 case euphorik_bd:est_banni(User#user.id) of
252 {true, Temps_restant} ->
253 erreur("Vous êtes banni pour encore " ++ format_minutes(Temps_restant));
254 _ ->
255 Strip_content = string:strip(Content),
256 if Strip_content =:= [] ->
257 erreur("Message vide");
258 true ->
259 % TODO : non-atomique (update_pseudo+nouveau_message)
260 euphorik_bd:update_pseudo_user(User#user.id, Nick),
261 case euphorik_bd:nouveau_message(Strip_content, User#user.id, Answer_to) of
262 erreur -> erreur("Impossible d'ajouter un nouveau message");
263 _ ->
264 json_reponse_ok()
265 end
266 end
267 end;
268 _ ->
269 erreur("Utilisateur inconnu")
270 end.
271
272
273 % Formatage de minutes.
274 % par exemple : "1min", "45min", "1h23min", "1jour 2h34min"
275 format_minutes(Min) ->
276 Jours = Min div (60 * 24),
277 Heures = Min rem (60 * 24) div 60,
278 Minutes = Min rem (60),
279 if Jours =/= 0 -> integer_to_list(Jours) ++ "Jour" ++ if Jours > 1 -> "s"; true -> "" end ++ " "; true -> "" end ++
280 if Heures =/= 0 -> integer_to_list(Heures) ++ "h"; true -> "" end ++
281 if Minutes == 0 ->
282 "";
283 true ->
284 lists:flatten(io_lib:format(if Jours =:= 0, Heures =:= 0 -> "~w"; true -> "~2.2.0w" end, [Minutes])) ++ "min"
285 end.
286
287
288 % bannissement d'un utilisateur (son ip est bannie)
289 ban(
290 [
291 {cookie, Cookie},
292 {duration, Duration},
293 {user_id, User_id},
294 {reason, Reason}
295 ]) ->
296 % controle que l'utilisateur est un admin
297 case euphorik_bd:user_by_cookie(Cookie) of
298 {ok, User1 = #user{ek_master = true}} ->
299 case euphorik_bd:user_by_id(User_id) of
300 {ok, User1} ->
301 erreur("Il n'est pas possible de s'auto bannir");
302 {ok, User2 = #user{ek_master = false}} ->
303 euphorik_bd:ban(User2#user.last_ip, Duration),
304 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s ~s est ~s pour ~s.~s",
305 [
306 User2#user.pseudo,
307 if User2#user.login =:= [] -> ""; true -> "(" ++ User2#user.login ++ ")" end,
308 if Duration =< 15 -> "kické"; true -> "banni" end,
309 format_minutes(Duration),
310 if Reason =/= [] -> " - Raison: " ++ Reason; true -> "" end ++ "."
311 ]
312 ))),
313 json_reponse_ok();
314 {ok, _} ->
315 erreur("L'utilisateur est lui même un ekMaster");
316 _ ->
317 erreur("Utilisateur à bannir inconnu")
318 end;
319 _ ->
320 erreur("Utilisateur inconnu ou non ek master")
321 end.
322
323
324 % slapage d'un user (avertissement)
325 slap(
326 [
327 {cookie, Cookie},
328 {user_id, User_id},
329 {reason, Reason}
330 ]) ->
331 % controle que l'utilisateur est un admin
332 case euphorik_bd:user_by_cookie(Cookie) of
333 {ok, User1 = #user{ek_master = true}} ->
334 case euphorik_bd:user_by_id(User_id) of
335 {ok, User1} ->
336 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s s'auto slap~s.",
337 [
338 User1#user.pseudo,
339 if Reason =/= [] -> " - Raison: " ++ Reason; true -> "" end
340 ]
341 )));
342 {ok, User2 = #user{ek_master = false}} ->
343 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s se fait slaper par ~s.~s",
344 [
345 User2#user.pseudo,
346 User1#user.pseudo,
347 if Reason =/= [] -> " - Raison: " ++ Reason; true -> "" end ++ "."
348 ]
349 ))),
350 json_reponse_ok();
351 {ok, _} ->
352 erreur("L'utilisateur est lui même un ekMaster");
353 _ ->
354 erreur("Utilisateur à slaper inconnu")
355 end;
356 _ ->
357 erreur("Utilisateur inconnu ou non ek master")
358 end.
359
360
361 put_troll(
362 [
363 {cookie, Cookie},
364 {content, Content}
365 ]
366 ) ->
367 % controle que l'utilisateur est un admin
368 case euphorik_bd:user_by_cookie(Cookie) of
369 {ok, User = #user{ek_master = true}} ->
370 case euphorik_bd:put_troll(User#user.id, Content) of
371 max_troll_reached_per_user ->
372 erreur(lists:flatten(io_lib:format("Le nombre de troll maximum par utilisateur est atteint : ~w ", [?NB_MAX_TROLL_WAITING_BY_USER])));
373 max_troll_reached ->
374 erreur(lists:flatten(io_lib:format("Le nombre de troll maximum en attente est atteint : ~w ", [?NB_MAX_TROLL_WAITING])));
375 _Id ->
376 json_reponse_ok()
377 end;
378 _ ->
379 erreur("Seul les ekMaster peuvent proposer des trolls")
380 end.
381
382
383 mod_troll(
384 [
385 {cookie, Cookie},
386 {troll_id, Troll_id},
387 {content, Content}
388 ]
389 ) ->
390 % controle que l'utilisateur est un admin
391 case euphorik_bd:user_by_cookie(Cookie) of
392 {ok, User = #user{ek_master = true}} ->
393 User_id = User#user.id,
394 case euphorik_bd:troll_by_id(Troll_id) of
395 {ok, #troll{id_user = User_id}} ->
396 euphorik_bd:mod_troll(Troll_id, Content),
397 json_reponse_ok();
398 _ ->
399 erreur("Vous ne posséder pas ce troll")
400 end;
401 _ ->
402 erreur("Seul les ekMaster peuvent proposer des trolls")
403 end.
404
405
406 del_troll(
407 [
408 {cookie, Cookie},
409 {troll_id, Troll_id}
410 ]
411 ) ->
412 % controle que l'utilisateur est un admin
413 case euphorik_bd:user_by_cookie(Cookie) of
414 {ok, User = #user{ek_master = true}} ->
415 User_id = User#user.id,
416 case euphorik_bd:troll_by_id(Troll_id) of
417 {ok, #troll{id_user = User_id}} ->
418 euphorik_bd:del_troll(Troll_id),
419 json_reponse_ok();
420 _ ->
421 erreur("Vous ne posséder pas ce troll")
422 end;
423 _ ->
424 erreur("Seul les ekMaster peuvent proposer des trolls")
425 end.
426
427
428 % Construit une erreur
429 erreur(Message) ->
430 {
431 struct, [
432 {reply, "error"},
433 {error_message, Message}
434 ]
435 }.
436
437
438 % Formatage d'une heure
439 % local_time() -> string
440 format_date(Date) ->
441 DateLocal = calendar:now_to_local_time(Date),
442 DateNowLocal = calendar:local_time(),
443 {{Annee, Mois, Jour}, {Heure, Minute, Seconde}} = DateLocal,
444 {{AnneeNow, _, _}, {_, _, _}} = DateNowLocal,
445 Hier = calendar:date_to_gregorian_days(element(1, DateLocal)) =:= calendar:date_to_gregorian_days(element(1, DateNowLocal)) - 1,
446 lists:flatten(
447 if element(1, DateLocal) =:= element(1, DateNowLocal) ->
448 "";
449 Hier ->
450 "Hier ";
451 Annee =:= AnneeNow ->
452 io_lib:format("~2.10.0B/~2.10.0B ", [Jour, Mois]);
453 true ->
454 io_lib:format("~2.10.0B/~2.10.0B/~B ", [Jour, Mois, Annee])
455 end ++
456 io_lib:format("~2.10.0B:~2.10.0B:~2.10.0B", [Heure, Minute, Seconde])
457 ).
458
459
460 json_reponse_ok() ->
461 {struct, [{reply, "ok"}]}.
462
463
464 json_reponse_login_ok(User) ->
465 {
466 struct, [
467 {reply, "login"},
468 {status, if (User#user.password =/= []) and (User#user.login =/= []) -> "auth_registered"; true -> "auth_not_registered" end},
469 {cookie, User#user.cookie},
470 {id, User#user.id},
471 {nick, User#user.pseudo},
472 {login, User#user.login},
473 {email, User#user.email},
474 {css, User#user.css},
475 {nick_format, atom_to_list(User#user.nick_format)},
476 {main_page, User#user.page_principale},
477 {conversations,
478 {array,
479 lists:map(
480 fun(C) ->
481 {struct,
482 [
483 {root, element(1, C)},
484 {page, element(2, C)}
485 ]
486 }
487 end,
488 User#user.conversations
489 )
490 }
491 },
492 {ek_master, User#user.ek_master}
493 ]
494 }.