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.
6 -module(euphorik_protocole
).
22 -include_lib("xmerl/include/xmerl.hrl").
23 -include("../include/euphorik_bd.hrl").
24 -include("../include/euphorik_defines.hrl").
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
),
31 case euphorik_bd:user_by_login(Login
) of
33 erreur("Login déjà existant");
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
)
40 erreur_register_flood()
42 % Enregistrement sans {Login, Password}
44 Can_register
= euphorik_bd:can_register(IP
),
46 User
= euphorik_bd:nouveau_user("<nick>", generer_cookie()),
47 euphorik_bd:update_ip(User#user
.id
, IP
),
48 json_reponse_login_ok(User
);
50 erreur_register_flood()
53 erreur_register_flood() ->
54 erreur("Trop de register (flood)").
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
).
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
);
69 % ajoute un délais d'attente
71 erreur("Erreur login").
74 % Renvoie un string() représentant un cookie en base 36. Il y a 10^32 possibillités.
77 random:seed(A1
, A2
, A3
),
78 erlang:integer_to_list(random:uniform(math:pow(10, 32)), 36).
81 % Un utilisateur se délogge.
86 % Modification du profile.
95 {nick_format
, Nick_format_str
},
96 {main_page
, Main_page
},
97 {conversations
, {array
, Conversations_json
}}
100 % est-ce que les messages auquel on répond existent ?
101 Conversations
= lists:foldr(
102 fun({struct
, [{root
, Root
}, {page
, Page
}]}, Acc
) ->
103 Message_existe
= euphorik_bd:message_existe(Root
),
105 [{Root
, Page
} | Acc
];
113 case euphorik_bd:set_profile(Cookie
, Login
, Password
, Pseudo
, Email
, Css
, list_to_atom(Nick_format_str
), Main_page
, Conversations
) of
117 erreur("Login déjà pris");
119 erreur("Impossible de mettre à jour le profile")
123 % Renvoie les messages appropriés.
124 % last_message id et cookie sont facultatifs
125 wait_event([{page
, "chat"} | Data
]) ->
126 % traitement des inputs
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 Troll_id
= case lists:keysearch(troll_id
, 1, Data
) of {value
, {_
, T
}} -> T
; _
-> 0 end,
132 {value
, {_
, {array
, Conversations_json
}}} = lists:keysearch(conversations
, 1, Data
),
133 Racines_conversations
= lists:map(
134 fun({struct
, [{root
, Racine
}, {page
, Page
} | Reste
]}) ->
135 Last_mess_conv
= case Reste
of [{last_message_id
, L
}] -> L
; _
-> 0 end,
136 {Racine
, Page
, Last_mess_conv
}
140 User
= case euphorik_bd:user_by_cookie(Cookie
) of
144 case {mnesia:subscribe({table
, minichat
, detailed
}), mnesia:subscribe({table
, troll
, detailed
})} of
145 {{error
, E
}, _
} -> E
;
146 {_
, {error
, E
}} -> E
;
148 % attente d'événements
149 R
= wait_event_page_chat(User
, Racines_conversations
, Message_count
, Last_message_id
, Main_page
, Troll_id
),
150 mnesia:unsubscribe({table
, minichat
, detailed
}),
151 mnesia:unsubscribe({table
, troll
, detailed
}),
154 wait_event([{page
, "admin"}, {last_troll
, Last_troll
}]) ->
155 case euphorik_bd:trolls_attente(Last_troll
) of
159 {reply
, "troll_modified"},
160 {troll_id
, Troll#troll
.id
},
161 {content
, Troll#troll
.content
}
167 {reply
, "troll_added"},
171 {ok
, User
} = euphorik_bd:user_by_id(T#troll
.id_user
),
174 {troll_id
, T#troll
.id
},
175 {content
, T#troll
.content
},
176 {author
, User#user
.pseudo
},
177 {author_id
, User#user
.id
}
189 {reply
, "troll_deleted"},
197 erreur("Page inconnue").
200 wait_event_page_chat(User
, Racines_conversations
, Message_count
, Last_message_id
, Main_page
, Troll_id
) ->
201 % est-ce qu'il y a des nouveaux messages ?
202 case euphorik_minichat_conversation:conversations(Racines_conversations
, Message_count
, Last_message_id
, Main_page
) of
204 % est-ce que le troll est à jour ?
205 case euphorik_bd:current_troll() of
206 Current
when is_record(Current
, troll
), Current#troll
.id
=/= Troll_id
->
208 {reply
, "new_troll"},
209 {troll_id
, Current#troll
.id
},
210 {content
, Current#troll
.content
}
213 wait_event_bd_page_chat(),
214 % TODO : l'appel est-il bien tail-recursive ?
215 wait_event_page_chat(User
, Racines_conversations
, Message_count
, Last_message_id
, Main_page
, Troll_id
)
218 % accrochez-vous ca va siouxer ;)
220 {reply
, "new_messages"},
221 {conversations
, {array
,
225 {last_page
, not Plus
},
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
),
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(
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
}]}
252 {ek_master
, User_mess#user
.ek_master
}
267 % Attend un événement lié à la page 'chat'.
268 wait_event_bd_page_chat() ->
269 receive % attente d'un post
270 {mnesia_table_event
, {write
, minichat
, _Message
, [], _
}} ->
272 {mnesia_table_event
, {write
, troll
, Troll
, [Old_troll
| _
], _
}} when Troll#troll
.date_post
=/= undefined
, Old_troll#troll
.date_post
== undefined
->
275 io:format("M : ~p~n", [M
]), %TODO : a virer
276 wait_event_bd_page_chat()
277 % 60 minutes de timeout (le cas ou il n'y a que des consultations et jamais de post)
278 % Après 60 minutes de connexion, le client doit donc reétablir une connexion
279 % TODO : pour être mieux : quand le socket se ferme alors un message devrait être envoyé et débloquer ce receive (demande en cours sur la mailing list de yaws)
280 after 1000 * 60 * 60 ->
285 % Un utilisateur envoie un message
291 {answer_to
, {array
, Answer_to
}}
294 case euphorik_bd:user_by_cookie(Cookie
) of
296 case euphorik_bd:est_banni(User#user
.id
) of
297 {true
, Temps_restant
} ->
298 erreur("Vous êtes banni pour encore " ++ format_minutes(Temps_restant
));
300 Strip_content
= string:strip(Content
),
301 if Strip_content
=:= [] ->
302 erreur("Message vide");
304 % TODO : non-atomique (update_pseudo+nouveau_message)
305 euphorik_bd:update_pseudo_user(User#user
.id
, Nick
),
306 case euphorik_bd:nouveau_message(Strip_content
, User#user
.id
, Answer_to
) of
307 erreur
-> erreur("Impossible d'ajouter un nouveau message");
314 erreur("Utilisateur inconnu")
318 % Formatage de minutes.
319 % par exemple : "1min", "45min", "1h23min", "1jour 2h34min"
320 format_minutes(Min
) ->
321 Jours
= Min
div (60 * 24),
322 Heures
= Min
rem (60 * 24) div
60,
323 Minutes
= Min
rem (60),
324 if Jours
=/= 0 -> integer_to_list(Jours
) ++ "Jour" ++ if Jours
> 1 -> "s"; true
-> "" end ++ " "; true
-> "" end ++
325 if Heures
=/= 0 -> integer_to_list(Heures
) ++ "h"; true
-> "" end ++
329 lists:flatten(io_lib:format(if Jours
=:= 0, Heures
=:= 0 -> "~w"; true
-> "~2.2.0w" end, [Minutes
])) ++ "min"
333 % bannissement d'un utilisateur (son ip est bannie)
337 {duration
, Duration
},
341 % controle que l'utilisateur est un admin
342 case euphorik_bd:user_by_cookie(Cookie
) of
343 {ok
, User1
= #user
{ek_master
= true
}} ->
344 case euphorik_bd:user_by_id(User_id
) of
346 erreur("Il n'est pas possible de s'auto bannir");
347 {ok
, User2
= #user
{ek_master
= false
}} ->
348 euphorik_bd:ban(User2#user
.last_ip
, Duration
),
349 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s ~s est ~s pour ~s.~s",
352 if User2#user
.login
=:= [] -> ""; true
-> "(" ++ User2#user
.login
++ ")" end,
353 if Duration
=< 15 -> "kické"; true
-> "banni" end,
354 format_minutes(Duration
),
355 if Reason
=/= [] -> " - Raison: " ++ Reason
; true
-> "" end ++ "."
360 erreur("L'utilisateur est lui même un ekMaster");
362 erreur("Utilisateur à bannir inconnu")
365 erreur("Utilisateur inconnu ou non ek master")
369 % slapage d'un user (avertissement)
376 % controle que l'utilisateur est un admin
377 case euphorik_bd:user_by_cookie(Cookie
) of
378 {ok
, User1
= #user
{ek_master
= true
}} ->
379 case euphorik_bd:user_by_id(User_id
) of
381 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s s'auto slap~s.",
384 if Reason
=/= [] -> " - Raison: " ++ Reason
; true
-> "" end
387 {ok
, User2
= #user
{ek_master
= false
}} ->
388 euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s se fait slaper par ~s.~s",
392 if Reason
=/= [] -> " - Raison: " ++ Reason
; true
-> "" end ++ "."
397 erreur("L'utilisateur est lui même un ekMaster");
399 erreur("Utilisateur à slaper inconnu")
402 erreur("Utilisateur inconnu ou non ek master")
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 case euphorik_bd:put_troll(User#user
.id
, Content
) of
416 max_troll_reached_per_user
->
417 erreur(lists:flatten(io_lib:format("Le nombre de troll maximum par utilisateur est atteint : ~w ", [?NB_MAX_TROLL_WAITING_BY_USER
])));
419 erreur(lists:flatten(io_lib:format("Le nombre de troll maximum en attente est atteint : ~w ", [?NB_MAX_TROLL_WAITING
])));
424 erreur("Seul les ekMaster peuvent proposer des trolls")
431 {troll_id
, Troll_id
},
435 % controle que l'utilisateur est un admin
436 case euphorik_bd:user_by_cookie(Cookie
) of
437 {ok
, User
= #user
{ek_master
= true
}} ->
438 User_id
= User#user
.id
,
439 case euphorik_bd:troll_by_id(Troll_id
) of
440 {ok
, #troll
{id_user
= User_id
}} ->
441 euphorik_bd:mod_troll(Troll_id
, Content
),
444 erreur("Vous ne posséder pas ce troll")
447 erreur("Seul les ekMaster peuvent proposer des trolls")
457 % controle que l'utilisateur est un admin
458 case euphorik_bd:user_by_cookie(Cookie
) of
459 {ok
, User
= #user
{ek_master
= true
}} ->
460 User_id
= User#user
.id
,
461 case euphorik_bd:troll_by_id(Troll_id
) of
462 {ok
, #troll
{id_user
= User_id
}} ->
463 euphorik_bd:del_troll(Troll_id
),
466 erreur("Vous ne posséder pas ce troll")
469 erreur("Seul les ekMaster peuvent proposer des trolls")
473 % Construit une erreur
478 {error_message
, Message
}
483 % Formatage d'une heure
484 % local_time() -> string
486 DateLocal
= calendar:now_to_local_time(Date
),
487 DateNowLocal
= calendar:local_time(),
488 {{Annee
, Mois
, Jour
}, {Heure
, Minute
, Seconde
}} = DateLocal
,
489 {{AnneeNow
, _
, _
}, {_
, _
, _
}} = DateNowLocal
,
490 Hier
= calendar:date_to_gregorian_days(element(1, DateLocal
)) =:= calendar:date_to_gregorian_days(element(1, DateNowLocal
)) - 1,
492 if element(1, DateLocal
) =:= element(1, DateNowLocal
) ->
496 Annee
=:= AnneeNow
->
497 io_lib:format("~2.10.0B/~2.10.0B ", [Jour
, Mois
]);
499 io_lib:format("~2.10.0B/~2.10.0B/~B ", [Jour
, Mois
, Annee
])
501 io_lib:format("~2.10.0B:~2.10.0B:~2.10.0B", [Heure
, Minute
, Seconde
])
506 {struct
, [{reply
, "ok"}]}.
509 json_reponse_login_ok(User
) ->
513 {status
, if (User#user
.password
=/= []) and (User#user
.login
=/= []) -> "auth_registered"; true
-> "auth_not_registered" end},
514 {cookie
, User#user
.cookie
},
516 {nick
, User#user
.pseudo
},
517 {login
, User#user
.login
},
518 {email
, User#user
.email
},
519 {css
, User#user
.css
},
520 {nick_format
, atom_to_list(User#user
.nick_format
)},
521 {main_page
, User#user
.page_principale
},
528 {root
, element(1, C
)},
529 {page
, element(2, C
)}
533 User#user
.conversations
537 {ek_master
, User#user
.ek_master
}