dc29bf5d2f8f3e53af8aae28050738c1c7df9021
[euphorik.git] / 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 erreur/1
16 ]).
17
18 -include_lib("xmerl/include/xmerl.hrl").
19 -include("../include/euphorik_bd.hrl").
20 -include("../include/euphorik_defines.hrl").
21
22
23 % Une utilisateur s'enregistre avec un tuple {Login, Password}.
24 register([{login, Login}, {password, Password}], IP) ->
25 Can_register = euphorik_bd:can_register(IP),
26 if Can_register ->
27 case euphorik_bd:user_by_login(Login) of
28 {ok, _} ->
29 erreur("Login déjà existant");
30 _ ->
31 User = euphorik_bd:nouveau_user(Login, Password, generer_cookie()),
32 euphorik_bd:update_ip(User#user.id, IP),
33 json_reponse_login_ok(User)
34 end;
35 true ->
36 erreur_register_flood()
37 end;
38 % Enregistrement sans {Login, Password}
39 register([], IP) ->
40 Can_register = euphorik_bd:can_register(IP),
41 if Can_register ->
42 User = euphorik_bd:nouveau_user("<nick>", generer_cookie()),
43 euphorik_bd:update_ip(User#user.id, IP),
44 json_reponse_login_ok(User);
45 true ->
46 erreur_register_flood()
47 end.
48
49 erreur_register_flood() ->
50 erreur("Trop de register (flood)").
51
52
53 % Un utilisateur se logge (avec un couple {login, mot de passe})
54 login([{login, Login}, {password, Password}], IP) ->
55 loginUser(euphorik_bd:user_by_login_password(Login, Password), IP);
56 % Un utilisateur se logge (avec un cookie)
57 login([{cookie, Cookie}], IP) ->
58 loginUser(euphorik_bd:user_by_cookie(Cookie), IP).
59
60 loginUser({ok, User}, IP) ->
61 euphorik_bd:update_ip(User#user.id, IP),
62 euphorik_bd:update_date_derniere_connexion(User#user.id),
63 json_reponse_login_ok(User);
64 loginUser(_, _) ->
65 % ajoute un délais d'attente (TODO : un autre moyen plus élégant ?)
66 receive after 1000 ->
67 erreur("Erreur login")
68 end.
69
70
71 % Renvoie un string() représentant un cookie en base 36. Il y a 10^32 possibillités.
72 generer_cookie() ->
73 {A1,A2,A3} = now(),
74 random:seed(A1, A2, A3),
75 erlang:integer_to_list(random:uniform(math:pow(10, 32)), 36).
76
77
78 % Un utilisateur se délogge.
79 logout(_) ->
80 do_nothing.
81
82
83 % Modification du profile.
84 profile(
85 [
86 {cookie, Cookie},
87 {login, Login},
88 {password, Password},
89 {nick, Pseudo},
90 {email, Email},
91 {css, Css},
92 {nick_format, Nick_format_str},
93 {main_page, Main_page},
94 {conversations, {array, Conversations_json}}
95 ]
96 ) ->
97 % est-ce que les messages auquel on répond existent ?
98 Conversations = lists:foldr(
99 fun({struct, [{root, Root}, {page, Page}]}, Acc) ->
100 Message_existe = euphorik_bd:message_existe(Root),
101 if Message_existe ->
102 [{Root, Page} | Acc];
103 true ->
104 Acc
105 end
106 end,
107 [],
108 Conversations_json
109 ),
110 case euphorik_bd:set_profile(Cookie, Login, Password, Pseudo, Email, Css, list_to_atom(Nick_format_str), Main_page, Conversations) of
111 ok ->
112 json_reponse_ok();
113 login_deja_pris ->
114 erreur("Login déjà pris");
115 _ ->
116 erreur("Impossible de mettre à jour le profile")
117 end.
118
119
120 % Renvoie les messages appropriés.
121 % last_message id et cookie sont facultatifs
122 wait_event(Data) ->
123 Cookie = case lists:keysearch(cookie, 1, Data) of {value, {_, C}} -> C; _ -> inconnu end,
124 Last_message_id = case lists:keysearch(last_message_id, 1, Data) of {value, {_, Id}} -> Id; _ -> 0 end,
125 {value, {_, Message_count}} = lists:keysearch(message_count, 1, Data),
126 Main_page = case lists:keysearch(main_page, 1, Data) of {value, {_, P}} -> P; _ -> 1 end,
127 {value, {_, {array, Conversations_json}}} = lists:keysearch(conversations, 1, Data),
128 Conversations = lists:map(
129 fun({struct, [{root, Racine}, {page, Page} | Reste]}) ->
130 Last_mess_conv = case Reste of [{last_message_id, L}] -> L; _ -> 0 end,
131 {Racine, Page, Last_mess_conv}
132 end,
133 Conversations_json
134 ),
135 User = case euphorik_bd:user_by_cookie(Cookie) of
136 {ok, U} -> U;
137 _ -> inconnu
138 end,
139 {struct, [
140 {reply, "new_message"},
141 {conversations, {array,
142 % accrochez-vous ca va siouxer ;)
143 lists:map(
144 fun({Conv, Plus}) ->
145 {struct, [
146 {last_page, not Plus},
147 {messages, {array,
148 lists:map(
149 fun({Mess, Repond_a}) ->
150 Est_proprietaire = User =/= inconnu andalso User#user.id =:= Mess#minichat.auteur_id,
151 A_repondu_a_message = User =/= inconnu andalso euphorik_bd:a_repondu_a_message(User#user.id, Mess#minichat.id),
152 Est_une_reponse_a_user = User =/= inconnu andalso euphorik_bd:est_une_reponse_a_user(User#user.id, Mess#minichat.id),
153 {ok, User_mess } = euphorik_bd:user_by_id(Mess#minichat.auteur_id),
154 {struct, [
155 {id, Mess#minichat.id},
156 {user_id, User_mess#user.id},
157 {date, format_date(Mess#minichat.date)},
158 {system, Mess#minichat.auteur_id =:= 0},
159 {owner, Est_proprietaire},
160 {answered, A_repondu_a_message},
161 {is_a_reply, Est_une_reponse_a_user},
162 {nick, Mess#minichat.pseudo},
163 {login, User_mess#user.login},
164 {content, Mess#minichat.contenu},
165 {answer_to, {array, lists:map(
166 fun(Id_mess) ->
167 {ok, M} = euphorik_bd:message_by_id(Id_mess),
168 {ok, User_reponse} = euphorik_bd:user_by_mess(M#minichat.id),
169 {struct, [{id, M#minichat.id}, {nick, M#minichat.pseudo}, {login, User_reponse#user.login}]}
170 end,
171 Repond_a
172 )}},
173 {ek_master, User_mess#user.ek_master}
174 ]}
175 end,
176 Conv
177 )
178 }}
179 ]}
180 end,
181 euphorik_minichat_conversation:conversations(
182 Conversations,
183 Message_count,
184 Last_message_id,
185 Main_page
186 )
187 )
188 }}
189 ]}.
190
191
192 % Un utilisateur envoie un message
193 put_message(
194 [
195 {cookie, Cookie},
196 {nick, Nick},
197 {content, Content},
198 {answer_to, {array, Answer_to}}
199 ]
200 ) ->
201 case euphorik_bd:user_by_cookie(Cookie) of
202 {ok, User} ->
203 case euphorik_bd:est_banni(User#user.id) of
204 {true, Temps_restant} ->
205 erreur("Vous êtes banni pour encore " ++ format_minutes(Temps_restant));
206 _ ->
207 Strip_content = string:strip(Content),
208 if Strip_content =:= [] ->
209 erreur("Message vide");
210 true ->
211 % TODO : non-atomique (update_pseudo+nouveau_message)
212 euphorik_bd:update_pseudo_user(User#user.id, Nick),
213 case euphorik_bd:nouveau_message(Strip_content, User#user.id, Answer_to) of
214 erreur -> erreur("Impossible d'ajouter un nouveau message");
215 _ ->
216 json_reponse_ok()
217 end
218 end
219 end;
220 _ ->
221 erreur("Utilisateur inconnu")
222 end.
223
224
225 % Formatage de minutes.
226 % par exemple : "1min", "45min", "1h23min", "1jour 2h34min"
227 format_minutes(Min) ->
228 Jours = Min div (60 * 24),
229 Heures = Min rem (60 * 24) div 60,
230 Minutes = Min rem (60),
231 if Jours =/= 0 -> integer_to_list(Jours) ++ "Jour" ++ if Jours > 1 -> "s"; true -> "" end ++ " "; true -> "" end ++
232 if Heures =/= 0 -> integer_to_list(Heures) ++ "h"; true -> "" end ++
233 lists:flatten(io_lib:format(if Jours =:= 0, Heures =:= 0 -> "~w"; true -> "~2.2.0w" end, [Minutes])) ++ "min".
234
235
236 % bannissement d'un utilisateur (son ip est bannie)
237 ban(
238 [
239 {cookie, Cookie},
240 {duration, Duration},
241 {user_id, User_id}
242 ]) ->
243 % controle que l'utilisateur est un admin
244 case euphorik_bd:user_by_cookie(Cookie) of
245 {ok, User1 = #user{ek_master = true}} ->
246 case euphorik_bd:user_by_id(User_id) of
247 {ok, User1} ->
248 erreur("Il n'est pas possible de s'auto bannir");
249 {ok, User2 = #user{ek_master = false}} ->
250 euphorik_bd:ban(User2#user.last_ip, Duration),
251 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s ~s est ~s pour ~s",
252 [
253 User2#user.pseudo,
254 if User2#user.login =:= [] -> ""; true -> "(" ++ User2#user.login ++ ")" end,
255 if Duration =< 15 -> "kické"; true -> "banni" end,
256 format_minutes(Duration)
257 ]
258 ))),
259 json_reponse_ok();
260 {ok, _} ->
261 erreur("L'utilisateur est lui même un ekMaster");
262 _ ->
263 erreur("Utilisateur à bannir inconnu")
264 end;
265 _ ->
266 erreur("Utilisateur inconnu ou non ek master")
267 end.
268
269
270 % Construit une erreur
271 erreur(Message) ->
272 {
273 struct, [
274 {reply, "error"},
275 {error_message, Message}
276 ]
277 }.
278
279
280 % Formatage d'une heure
281 % local_time() -> string
282 format_date(Date) ->
283 DateLocal = calendar:now_to_local_time(Date),
284 DateNowLocal = calendar:local_time(),
285 {{Annee, Mois, Jour}, {Heure, Minute, Seconde}} = DateLocal,
286 {{AnneeNow, _, _}, {_, _, _}} = DateNowLocal,
287 Hier = calendar:date_to_gregorian_days(element(1, DateLocal)) =:= calendar:date_to_gregorian_days(element(1, DateNowLocal)) - 1,
288 lists:flatten(
289 if element(1, DateLocal) =:= element(1, DateNowLocal) ->
290 "";
291 Hier ->
292 "Hier ";
293 Annee =:= AnneeNow ->
294 io_lib:format("~2.10.0B/~2.10.0B ", [Jour, Mois]);
295 true ->
296 io_lib:format("~2.10.0B/~2.10.0B/~B ", [Jour, Mois, Annee])
297 end ++
298 io_lib:format("~2.10.0B:~2.10.0B:~2.10.0B", [Heure, Minute, Seconde])
299 ).
300
301
302 json_reponse_ok() ->
303 {struct, [{reply, "ok"}]}.
304
305
306 json_reponse_login_ok(User) ->
307 {
308 struct, [
309 {reply, "login"},
310 {status, if (User#user.password =/= []) and (User#user.login =/= []) -> "auth_registered"; true -> "auth_not_registered" end},
311 {cookie, User#user.cookie},
312 {id, User#user.id},
313 {nick, User#user.pseudo},
314 {login, User#user.login},
315 {email, User#user.email},
316 {css, User#user.css},
317 {nick_format, atom_to_list(User#user.nick_format)},
318 {main_page, User#user.page_principale},
319 {conversations,
320 {array,
321 lists:map(
322 fun(C) ->
323 {struct,
324 [
325 {root, element(1, C)},
326 {page, element(2, C)}
327 ]
328 }
329 end,
330 User#user.conversations
331 )
332 }
333 },
334 {ek_master, User#user.ek_master}
335 ]
336 }.