ADD changement dans le protocole, le premier message de chaque conversation est maint...
[euphorik.git] / modules / erl / euphorik_protocole.erl
1 % coding: utf-8
2 % Copyright 2008 Grégory Burri
3 %
4 % This file is part of Euphorik.
5 %
6 % Euphorik is free software: you can redistribute it and/or modify
7 % it under the terms of the GNU General Public License as published by
8 % the Free Software Foundation, either version 3 of the License, or
9 % (at your option) any later version.
10 %
11 % Euphorik is distributed in the hope that it will be useful,
12 % but WITHOUT ANY WARRANTY; without even the implied warranty of
13 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 % GNU General Public License for more details.
15 %
16 % You should have received a copy of the GNU General Public License
17 % along with Euphorik. If not, see <http://www.gnu.org/licenses/>.
18 %
19 % Ce module gére les différents messages envoyés par le client (javascript) via AJAX.
20 % Les messages donnés ainsi que les réponses sont au format JSON.
21 % @author G.Burri
22
23
24 -module(euphorik_protocole).
25 -export([
26 register/2,
27 login/2,
28 logout/1,
29 profile/1,
30 wait_event/1,
31 put_message/1,
32 ban/1,
33 slap/1,
34 put_troll/1,
35 mod_troll/1,
36 del_troll/1,
37 unban_ip/1,
38 list_banned_ips/1,
39 erreur/1
40 ]).
41 -include("../include/euphorik_bd.hrl").
42 -include("../include/euphorik_defines.hrl").
43
44
45 % Une utilisateur s'enregistre avec un tuple {Login, Password}.
46 register([{login, Login}, {password, Password}], IP) ->
47 Can_register = euphorik_bd:can_register(IP),
48 if Can_register ->
49 case euphorik_bd:user_by_login(Login) of
50 {ok, _} ->
51 erreur("Login déjà existant");
52 _ ->
53 User = euphorik_bd:nouveau_user(Login, Password, generer_cookie()),
54 euphorik_bd:update_ip(User#user.id, IP),
55 json_reponse_login_ok(User)
56 end;
57 true ->
58 erreur_register_flood()
59 end;
60 % Enregistrement sans {Login, Password}
61 register([], IP) ->
62 Can_register = euphorik_bd:can_register(IP),
63 if Can_register ->
64 User = euphorik_bd:nouveau_user("<nick>", generer_cookie()),
65 euphorik_bd:update_ip(User#user.id, IP),
66 json_reponse_login_ok(User);
67 true ->
68 erreur_register_flood()
69 end.
70
71 erreur_register_flood() ->
72 erreur("Trop de register (flood)").
73
74
75 % Un utilisateur se logge (avec un couple {login, mot de passe})
76 login([{login, Login}, {password, Password}], IP) ->
77 loginUser(euphorik_bd:user_by_login_password(Login, Password), IP);
78 % Un utilisateur se logge (avec un cookie)
79 login([{cookie, Cookie}], IP) ->
80 loginUser(euphorik_bd:user_by_cookie(Cookie), IP).
81
82 loginUser({ok, User}, IP) ->
83 euphorik_bd:update_ip(User#user.id, IP),
84 euphorik_bd:update_date_derniere_connexion(User#user.id),
85 json_reponse_login_ok(User);
86 loginUser(_, _) ->
87 % ajoute un délais d'attente
88 timer:sleep(?TEMPS_ATTENTE_ERREUR_LOGIN),
89 erreur("Couple login/pass introuvable").
90
91
92 % Renvoie un string() représentant un cookie en base 36. Il y a 10^32 possibillités.
93 generer_cookie() ->
94 {A1, A2, A3} = now(),
95 random:seed(A1, A2, A3),
96 erlang:integer_to_list(random:uniform(math:pow(10, 32)), 36).
97
98
99 % Un utilisateur se délogge.
100 logout(_) ->
101 do_nothing.
102
103
104 % Modification du profile.
105 profile(
106 [
107 {cookie, Cookie},
108 {login, Login},
109 {password, Password},
110 {nick, Pseudo},
111 {email, Email},
112 {css, Css},
113 {nick_format, Nick_format_str},
114 {view_times, View_times},
115 {view_tooltips, View_tooltips},
116 {conversations, {array, Conversations_json}}
117 ]
118 ) ->
119 % virage des messages qui n'existent pas
120 Conversations = lists:dropwhile(
121 fun(Message_id) ->
122 not euphorik_bd:message_existe(Message_id)
123 end,
124 Conversations_json
125 ),
126 % TODO : pas très beau, mieux vaut construire un #user
127 case euphorik_bd:set_profile(
128 Cookie,
129 Login,
130 Password,
131 Pseudo,
132 Email,
133 Css,
134 list_to_atom(Nick_format_str),
135 View_times,
136 View_tooltips,
137 Conversations) of
138 ok ->
139 json_reponse_ok();
140 login_deja_pris ->
141 erreur("Login déjà pris");
142 _ ->
143 erreur("Impossible de mettre à jour le profile")
144 end.
145
146
147 % Renvoie les messages appropriés.
148 % last_message id et cookie sont facultatifs
149 wait_event([{page, "chat"} | Data]) ->
150 % traitement des inputs
151 Cookie = case lists:keysearch(cookie, 1, Data) of {value, {_, C}} -> C; _ -> inconnu end,
152 Last_message_id = case lists:keysearch(last_message_id, 1, Data) of {value, {_, Id}} -> Id; _ -> 0 end,
153 {value, {_, Message_count}} = lists:keysearch(message_count, 1, Data),
154 Main_page = case lists:keysearch(main_page, 1, Data) of {value, {_, P}} -> P; _ -> 1 end,
155 Troll_id = case lists:keysearch(troll_id, 1, Data) of {value, {_, T}} -> T; _ -> 0 end,
156 {value, {_, {array, Conversations_json}}} = lists:keysearch(conversations, 1, Data),
157 Racines_conversations = lists:map(
158 fun({struct, [{root, Racine}, {page, Page} | Reste]}) ->
159 Last_mess_conv = case Reste of [{last_message_id, L}] -> L; _ -> 0 end,
160 {Racine, Page, Last_mess_conv}
161 end,
162 Conversations_json
163 ),
164 User = case euphorik_bd:user_by_cookie(Cookie) of
165 {ok, U} -> U;
166 _ -> inconnu
167 end,
168 case {mnesia:subscribe({table, minichat, detailed}), mnesia:subscribe({table, troll, detailed})} of
169 {{error, E}, _} -> E;
170 {_, {error, E}} -> E;
171 _ ->
172 % attente d'événements
173 R = wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id, Main_page, Troll_id),
174 mnesia:unsubscribe({table, minichat, detailed}),
175 mnesia:unsubscribe({table, troll, detailed}),
176 R
177 end;
178 wait_event([{page, "admin"}, {last_troll, Last_troll}]) ->
179 case wait_event_page_admin(Last_troll) of
180 banned_ips_refresh ->
181 {struct,
182 [
183 {reply, "banned_ips_refresh"}
184 ]
185 };
186 {mod, Troll} ->
187 {struct,
188 [
189 {reply, "troll_modified"},
190 {troll_id, Troll#troll.id},
191 {content, Troll#troll.content}
192 ]
193 };
194 {add, Trolls} ->
195 {struct,
196 [
197 {reply, "troll_added"},
198 {trolls, {array,
199 lists:map(
200 fun(T) ->
201 {ok, User} = euphorik_bd:user_by_id(T#troll.id_user),
202 {struct,
203 [
204 {troll_id, T#troll.id},
205 {content, T#troll.content},
206 {author, User#user.pseudo},
207 {author_id, User#user.id}
208 ]
209 }
210 end,
211 Trolls
212 )
213 }}
214 ]
215 };
216 {del, Troll_id} ->
217 {struct,
218 [
219 {reply, "troll_deleted"},
220 {troll_id, Troll_id}
221 ]
222 };
223 _ ->
224 erreur("timeout")
225 end;
226 wait_event(_) ->
227 erreur("Page inconnue").
228
229
230 wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id, Main_page, Troll_id) ->
231 % est-ce que le troll est à jour ?
232 case euphorik_bd:current_troll() of
233 Current when is_record(Current, troll), Current#troll.id =/= Troll_id ->
234 {struct, [
235 {reply, "new_troll"},
236 {troll_id, Current#troll.id},
237 {message_id, euphorik_bd:message_id_associe(Current#troll.id)},
238 {content, Current#troll.content}
239 ]};
240 _ ->
241 % est-ce qu'il y a des nouveaux messages ?
242 case euphorik_minichat_conversation:conversations(Racines_conversations, Message_count, Last_message_id, Main_page) of
243 vide ->
244 wait_event_bd_page_chat(),
245 wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id, Main_page, Troll_id);
246 Conversations ->
247 % accrochez-vous ca va siouxer ;)
248 {struct, [
249 {reply, "new_messages"},
250 {conversations, {array,
251 lists:map(
252 fun({Racine, {Conv, Plus}}) ->
253 {struct, [
254 {last_page, not Plus},
255 {first,
256 if Racine =:= undefined orelse Conv =:= [] ->
257 "undefined";
258 true ->
259 {Racine_id, _, _} = Racine,
260 case euphorik_bd:message_by_id(Racine_id) of
261 {ok, Mess} ->
262 json_message(Mess, euphorik_bd:parents(Racine), User);
263 _ ->
264 "undefined"
265 end
266 end
267 }, % le premier message de la conversation, peut correspondre
268 {messages, {array,
269 lists:map(
270 fun({Mess, Repond_a}) ->
271 json_message(Mess, Repond_a, User)
272 end,
273 Conv
274 )
275 }}
276 ]}
277 end,
278 % on ajoute un 'undefined' correspondant à la premier conversation qui ne possède pas de racine
279 % TODO : peut être à revoir car un peu lourd est compliqué
280 aggregation_racines_conversations([undefined | Racines_conversations], Conversations)
281 )
282 }}
283 ]}
284 end
285 end.
286
287
288 aggregation_racines_conversations(L1, L2) ->
289 aggregation_racines_conversations(L1, L2, []).
290 aggregation_racines_conversations([], [], L) -> lists:reverse(L);
291 aggregation_racines_conversations([E1|R1], [E2|R2], L) ->
292 aggregation_racines_conversations(R1, R2, [{E1, E2} | L]).
293
294
295
296 % Attend un événement lié à la page 'chat'.
297 wait_event_bd_page_chat() ->
298 receive % attente d'un post
299 {mnesia_table_event, {write, minichat, _Message, [], _}} ->
300 ok;
301 {mnesia_table_event, {write, troll, Troll, [Old_troll | _], _}} when Troll#troll.date_post =/= undefined, Old_troll#troll.date_post == undefined ->
302 ok;
303 {tcp_closed, _} ->
304 exit(normal);
305 _ ->
306 wait_event_bd_page_chat()
307 % 60 minutes de timeout (on ne sais jamais)
308 % Après 60 minutes de connexion, le client doit donc reétablir une connexion
309 after 1000 * 60 * 60 ->
310 timeout
311 end.
312
313
314 % Attent un événement concernant la page admin
315 % Renvoie les trolls manquants posté après Last_id ou banned_ips_refresh.
316 % Si pas de trolls alors attend un événement tel qu'un ajout, une modification ou une suppression.
317 % renvoie :
318 % {mod, Troll}
319 % ou {add, [Trolls]}
320 % ou {del, Troll_id}
321 % ou banned_ips_refresh
322 % ou timeout
323 wait_event_page_admin(Last_id) ->
324 case {mnesia:subscribe({table, troll, detailed}), mnesia:subscribe({table, ip_table, detailed})} of
325 {{error, E}, _ } -> E;
326 {_, {error, E}} -> E;
327 _ ->
328 R = case euphorik_bd:trolls(Last_id) of
329 [] -> % pas de trolls
330 wait_event_page_admin();
331 Trolls ->
332 {add, Trolls}
333 end,
334 mnesia:unsubscribe({table, troll, detailed}),
335 mnesia:unsubscribe({table, ip_table, detailed}),
336 R
337 end.
338
339 wait_event_page_admin() ->
340 % s'il n'y a pas de trolls que l'utilisateur n'a pas connaissance alors on attend un événement
341 receive
342 % cas où un troll est choisit comme courant
343 {mnesia_table_event, {write, troll, Troll, [Old_troll | _], _}}
344 when Old_troll#troll.date_post =:= undefined, Troll#troll.date_post =/= undefined ->
345 {del, Troll#troll.id};
346 {mnesia_table_event, {write, troll, Troll, [_Old_troll | _], _}} ->
347 {mod, Troll};
348 {mnesia_table_event, {write, troll, Troll, [], _}} ->
349 {add, [Troll]};
350 {mnesia_table_event, {delete, troll, {troll, Id}, _, _}} ->
351 {del, Id};
352 {mnesia_table_event, {write, ip_table, IP, [Old_IP | _], _}}
353 when Old_IP#ip_table.ban =/= IP#ip_table.ban; Old_IP#ip_table.ban_duration =/= IP#ip_table.ban_duration ->
354 banned_ips_refresh;
355 {tcp_closed, _} ->
356 exit(normal);
357 _ ->
358 wait_event_page_admin()
359 % 60 minutes de timeout (on ne sais jamais)
360 % Après 60 minutes de connexion, le client doit donc reétablir une connexion
361 after 1000 * 60 * 60 ->
362 timeout
363 end.
364
365
366 % Un utilisateur envoie un message
367 % Answer_to est une liste d'id (int)
368 put_message(
369 [
370 {cookie, Cookie},
371 {nick, Nick},
372 {content, Content},
373 {answer_to, {array, Answer_to}}
374 ]
375 ) ->
376 case euphorik_bd:user_by_cookie(Cookie) of
377 {ok, User} ->
378 case euphorik_bd:est_banni(User#user.id) of
379 {true, Temps_restant} ->
380 erreur("Vous êtes banni pour encore " ++ format_minutes(Temps_restant));
381 _ ->
382 Strip_content = string:strip(Content),
383 if Strip_content =:= [] ->
384 erreur("Message vide");
385 true ->
386 % attention : non-atomique (update_pseudo+nouveau_message)
387 euphorik_bd:update_pseudo_user(User#user.id, Nick),
388 case euphorik_bd:nouveau_message(Strip_content, User#user.id, Answer_to) of
389 {erreur, R} -> erreur("Impossible d'ajouter un nouveau message. Raison : " ++ R);
390 _ ->
391 json_reponse_ok()
392 end
393 end
394 end;
395 _ ->
396 erreur("Utilisateur inconnu")
397 end.
398
399
400 % bannissement d'un utilisateur (son ip est bannie)
401 ban(
402 [
403 {cookie, Cookie},
404 {duration, Duration},
405 {user_id, User_id},
406 {reason, Reason}
407 ]) ->
408 % controle que l'utilisateur est un admin
409 case euphorik_bd:user_by_cookie(Cookie) of
410 {ok, User1 = #user{ek_master = true}} ->
411 case euphorik_bd:user_by_id(User_id) of
412 {ok, User1} ->
413 erreur("Il n'est pas possible de s'auto bannir");
414 {ok, User2 = #user{ek_master = false}} ->
415 euphorik_bd:ban(User2#user.last_ip, Duration),
416 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("''~s~s'' est ~s pour ~s.~s",
417 [
418 User2#user.pseudo,
419 if User2#user.login =:= [] -> ""; true -> " (" ++ User2#user.login ++ ")" end,
420 if Duration =< 15 -> "kické"; true -> "banni" end,
421 format_minutes(Duration),
422 if Reason =/= [] -> " - Raison: " ++ Reason; true -> "" end ++ "."
423 ]
424 ))),
425 json_reponse_ok();
426 {ok, _} ->
427 erreur("L'utilisateur est lui même un ekMaster");
428 _ ->
429 erreur("Utilisateur à bannir inconnu")
430 end;
431 _ ->
432 erreur("Utilisateur inconnu ou non ek master")
433 end.
434
435
436 % slapage d'un user (avertissement)
437 slap(
438 [
439 {cookie, Cookie},
440 {user_id, User_id},
441 {reason, Reason}
442 ]) ->
443 % controle que l'utilisateur est un admin
444 case euphorik_bd:user_by_cookie(Cookie) of
445 {ok, User1 = #user{ek_master = true}} ->
446 case euphorik_bd:user_by_id(User_id) of
447 {ok, User1} ->
448 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s s'auto slap~s.",
449 [
450 User1#user.pseudo,
451 if Reason =/= [] -> " - Raison: " ++ Reason; true -> "" end
452 ]
453 ))),
454 json_reponse_ok();
455 {ok, User2 = #user{ek_master = false}} ->
456 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s se fait slaper par ~s.~s",
457 [
458 User2#user.pseudo,
459 User1#user.pseudo,
460 if Reason =/= [] -> " - Raison: " ++ Reason; true -> "" end ++ "."
461 ]
462 ))),
463 json_reponse_ok();
464 {ok, _} ->
465 erreur("L'utilisateur est lui même un ekMaster");
466 _ ->
467 erreur("Utilisateur à slaper inconnu")
468 end;
469 _ ->
470 erreur("Utilisateur inconnu ou non ek master")
471 end.
472
473
474 put_troll(
475 [
476 {cookie, Cookie},
477 {content, Content}
478 ]
479 ) ->
480 % controle que l'utilisateur est un admin
481 case euphorik_bd:user_by_cookie(Cookie) of
482 {ok, User = #user{ek_master = true}} ->
483 case euphorik_bd:put_troll(User#user.id, Content) of
484 max_troll_reached_per_user ->
485 erreur(lists:flatten(io_lib:format("Le nombre de troll maximum par utilisateur est atteint : ~w ", [?NB_MAX_TROLL_WAITING_BY_USER])));
486 max_troll_reached ->
487 erreur(lists:flatten(io_lib:format("Le nombre de troll maximum en attente est atteint : ~w ", [?NB_MAX_TROLL_WAITING])));
488 _Id ->
489 json_reponse_ok()
490 end;
491 _ ->
492 erreur("Seul les ekMaster peuvent proposer des trolls")
493 end.
494
495
496 mod_troll(
497 [
498 {cookie, Cookie},
499 {troll_id, Troll_id},
500 {content, Content}
501 ]
502 ) ->
503 % controle que l'utilisateur est un admin
504 case euphorik_bd:user_by_cookie(Cookie) of
505 {ok, User = #user{ek_master = true}} ->
506 User_id = User#user.id,
507 case euphorik_bd:troll_by_id(Troll_id) of
508 {ok, #troll{id_user = User_id}} ->
509 euphorik_bd:mod_troll(Troll_id, Content),
510 json_reponse_ok();
511 _ ->
512 erreur("Vous ne posséder pas ce troll")
513 end;
514 _ ->
515 erreur("Seul les ekMaster peuvent proposer des trolls")
516 end.
517
518
519 del_troll(
520 [
521 {cookie, Cookie},
522 {troll_id, Troll_id}
523 ]
524 ) ->
525 % controle que l'utilisateur est un admin
526 case euphorik_bd:user_by_cookie(Cookie) of
527 {ok, User = #user{ek_master = true}} ->
528 User_id = User#user.id,
529 case euphorik_bd:troll_by_id(Troll_id) of
530 {ok, #troll{id_user = User_id}} ->
531 euphorik_bd:del_troll(Troll_id),
532 json_reponse_ok();
533 _ ->
534 erreur("Vous ne posséder pas ce troll")
535 end;
536 _ ->
537 erreur("Seul les ekMaster peuvent proposer des trolls")
538 end.
539
540
541 unban_ip(
542 [
543 {cookie, Cookie},
544 {ip, IP}
545 ]
546 ) ->
547 case euphorik_bd:user_by_cookie(Cookie) of
548 {ok, #user{ek_master = true}} ->
549 euphorik_bd:deban(euphorik_common:unserialize_ip(IP)),
550 json_reponse_ok();
551 _ ->
552 erreur("Seul les ekMaster peuvent connaitre la liste des ips bannies")
553 end.
554
555
556 list_banned_ips(
557 [
558 {cookie, Cookie}
559 ]
560 ) ->
561 case euphorik_bd:user_by_cookie(Cookie) of
562 {ok, #user{ek_master = true}} ->
563 {
564 struct,
565 [
566 {reply, "list_banned_ips"},
567 {list, {array, lists:map(
568 fun({IP, T, Users}) ->
569 {struct,
570 [
571 {ip, euphorik_common:serialize_ip(IP)},
572 {remaining_time, format_minutes(T)},
573 {users, {array, lists:map(
574 fun({Pseudo, Login}) ->
575 {struct,
576 [
577 {nick, Pseudo},
578 {login, Login}
579 ]
580 }
581 end,
582 Users
583 )}}
584 ]
585 }
586 end,
587 euphorik_bd:list_ban()
588 )}}
589 ]
590 };
591 _ ->
592 erreur("Seul les ekMaster peuvent connaitre la liste des ips bannies")
593 end.
594
595
596 % Construit une erreur
597 erreur(Message) ->
598 {
599 struct, [
600 {reply, "error"},
601 {error_message, Message}
602 ]
603 }.
604
605
606 % Formatage de minutes.
607 % par exemple : "1min", "45min", "1h23min", "1jour 2h34min"
608 format_minutes(Min) ->
609 Jours = Min div (60 * 24),
610 Heures = Min rem (60 * 24) div 60,
611 Minutes = Min rem (60),
612 if Jours =/= 0 -> integer_to_list(Jours) ++ " Jour" ++ if Jours > 1 -> "s"; true -> "" end ++ " "; true -> "" end ++
613 if Heures =/= 0 -> integer_to_list(Heures) ++ " heure" ++ if Heures > 1 -> "s"; true -> "" end; true -> "" end ++
614 if Minutes == 0 ->
615 "";
616 true ->
617 " " ++ integer_to_list(Minutes) ++ " minute" ++ if Minutes > 1 -> "s"; true -> "" end
618 end.
619
620
621 % Formatage d'une heure
622 % local_time() -> string
623 format_date(Date) ->
624 DateLocal = calendar:now_to_local_time(Date),
625 DateNowLocal = calendar:local_time(),
626 {{Annee, Mois, Jour}, {Heure, Minute, Seconde}} = DateLocal,
627 {{AnneeNow, _, _}, {_, _, _}} = DateNowLocal,
628 Hier = calendar:date_to_gregorian_days(element(1, DateLocal)) =:= calendar:date_to_gregorian_days(element(1, DateNowLocal)) - 1,
629 lists:flatten(
630 if element(1, DateLocal) =:= element(1, DateNowLocal) ->
631 "";
632 Hier ->
633 "Hier ";
634 Annee =:= AnneeNow ->
635 io_lib:format("~2.10.0B/~2.10.0B ", [Jour, Mois]);
636 true ->
637 io_lib:format("~2.10.0B/~2.10.0B/~B ", [Jour, Mois, Annee])
638 end ++
639 io_lib:format("~2.10.0B:~2.10.0B:~2.10.0B", [Heure, Minute, Seconde])
640 ).
641
642
643 json_reponse_ok() ->
644 {struct, [{reply, "ok"}]}.
645
646
647 json_reponse_login_ok(User) ->
648 {
649 struct, [
650 {reply, "login"},
651 {status, if (User#user.password =/= []) and (User#user.login =/= []) -> "auth_registered"; true -> "auth_not_registered" end},
652 {cookie, User#user.cookie},
653 {id, User#user.id},
654 {nick, User#user.pseudo},
655 {login, User#user.login},
656 {email, User#user.email},
657 {css, User#user.css},
658 {nick_format, atom_to_list(User#user.nick_format)},
659 {view_times, User#user.view_times},
660 {view_tooltips, User#user.view_tooltips},
661 {conversations, {array, User#user.conversations } },
662 {ek_master, User#user.ek_master}
663 ]
664 }.
665
666 % Renvoie le message formaté en JSON.
667 % Mess est de type #minichat
668 % Repond_a est une liste d'id des messages auquel répond Mess
669 % User est l'utilisateur courant de type #user
670 json_message(Mess, Repond_a, User) ->
671 Est_proprietaire = User =/= inconnu andalso User#user.id =:= Mess#minichat.auteur_id,
672 A_repondu_a_message = User =/= inconnu andalso euphorik_bd:a_repondu_a_message(User#user.id, Mess#minichat.id),
673 Est_une_reponse_a_user = User =/= inconnu andalso euphorik_bd:est_une_reponse_a_user(User#user.id, Mess#minichat.id),
674 {ok, User_mess } = euphorik_bd:user_by_id(Mess#minichat.auteur_id),
675 {struct, [
676 {id, Mess#minichat.id},
677 {user_id, User_mess#user.id},
678 {date, format_date(Mess#minichat.date)},
679 {system, Mess#minichat.auteur_id =:= 0},
680 {owner, Est_proprietaire},
681 {answered, A_repondu_a_message},
682 {is_a_reply, Est_une_reponse_a_user},
683 {nick, Mess#minichat.pseudo},
684 {login, User_mess#user.login},
685 {content, Mess#minichat.contenu},
686 {root, Mess#minichat.racine_id},
687 {answer_to, {array, lists:map(
688 fun(Id_mess) ->
689 {ok, M} = euphorik_bd:message_by_id(Id_mess),
690 {ok, User_reponse} = euphorik_bd:user_by_mess(M#minichat.id),
691 {struct, [{id, M#minichat.id}, {nick, M#minichat.pseudo}, {login, User_reponse#user.login}]}
692 end,
693 Repond_a
694 )}},
695 {ek_master, User_mess#user.ek_master}
696 ]}.