add part; part on quit; broadcast messages

This commit is contained in:
tali 2024-01-09 22:01:21 -05:00
parent 17e3739cfe
commit d17e573e41
2 changed files with 101 additions and 40 deletions

View File

@ -21,8 +21,15 @@ let make ~(router : Router.t) ~(addr : sockaddr) : t =
let outbox t = t.outbox let outbox t = t.outbox
let shutdown t = let shutdown ?reason t =
User.quit t.user ~router:t.router; if User.is_registered t.user then begin
(* TODO: relay to everyone interested *)
let reason = Option.value reason ~default:"Goot bye" in
Router.relay (Irc.Msg.make "QUIT" [reason])
~from:t.user (`to_interested t.user);
User.part_all t.user;
User.unregister t.user ~router:t.router;
end;
Outbox.close t.outbox Outbox.close t.outbox
(* message handling *) (* message handling *)
@ -53,11 +60,13 @@ let on_msg_nick t nick =
if Irc.name_type nick <> `nick then if Irc.name_type nick <> `nick then
(if nick = "" then `nonicknamegiven else `erroneusnickname nick) (if nick = "" then `nonicknamegiven else `erroneusnickname nick)
else if User.is_registered t.user then else if User.is_registered t.user then
match User.set_nick t.user nick ~router:t.router with let success_callback () =
let msg = Irc.Msg.make "NICK" [nick] in
Router.relay msg ~from:t.user (`to_interested t.user);
in
match User.set_nick t.user nick ~router:t.router ~success_callback with
| `nick_in_use -> `nicknameinuse nick | `nick_in_use -> `nicknameinuse nick
| `nick_set -> | `nick_set -> `ok
((* TODO: relay NICK message *));
`ok
else begin else begin
t.pending_nick <- Some nick; t.pending_nick <- Some nick;
attempt_to_register t attempt_to_register t
@ -77,7 +86,7 @@ let on_msg_user t username modestr realname =
(* > messages and channels *) (* > messages and channels *)
let on_msg_privmsg t tgt txt _ = let on_msg_privmsg t tgt txt _ =
let msg = Irc.Msg.make "PRIVMSG" [tgt; txt] in let msg = Irc.Msg.make "PRIVMSG" [tgt; txt] ~always_trailing:true in
let dst = let dst =
try try
match Irc.name_type tgt with match Irc.name_type tgt with
@ -89,6 +98,7 @@ let on_msg_privmsg t tgt txt _ =
match dst with match dst with
| `not_found -> `nosuchnick tgt | `not_found -> `nosuchnick tgt
| (`to_user _ | `to_chan _) as dst -> | (`to_user _ | `to_chan _) as dst ->
(* TODO: check if allowed to send to channel *)
Router.relay msg ~from:t.user dst; Router.relay msg ~from:t.user dst;
`ok `ok
@ -100,6 +110,19 @@ let list_names chan =
in in
`names ("@", Chan.name chan, names) `names ("@", Chan.name chan, names)
let on_msg_names t name _ =
match Irc.name_type name with
| `nick | `invalid ->
if name = "" then `needmoreparams else `nosuchchannel name
| `chan ->
let chan = try Some (Router.find_chan t.router name)
with Not_found -> None
in
(* TODO: check if allowed to list names *)
match chan with
| None -> `nosuchchannel name
| Some chan -> list_names chan
let on_msg_join t name _ = let on_msg_join t name _ =
match Irc.name_type name with match Irc.name_type name with
| `nick | `invalid -> | `nick | `invalid ->
@ -113,15 +136,17 @@ let on_msg_join t name _ =
Chan.register chan ~router:t.router; Chan.register chan ~router:t.router;
chan chan
in in
if not (Chan.is_member chan t.user) then begin if Chan.is_member chan t.user then
`ok
else begin
Chan.join chan t.user; Chan.join chan t.user;
let join_msg = Irc.Msg.make "JOIN" [name] in let msg = Irc.Msg.make "JOIN" [name] in
Router.relay join_msg ~from:t.user `to_self; Router.relay msg ~from:t.user `to_self;
Router.relay join_msg ~from:t.user (`to_chan chan); Router.relay msg ~from:t.user (`to_chan chan);
end; list_names chan
list_names chan end
let on_msg_names t name _ = let on_msg_part t name _ =
match Irc.name_type name with match Irc.name_type name with
| `nick | `invalid -> | `nick | `invalid ->
if name = "" then `needmoreparams else `nosuchchannel name if name = "" then `needmoreparams else `nosuchchannel name
@ -129,20 +154,32 @@ let on_msg_names t name _ =
let chan = try Some (Router.find_chan t.router name) let chan = try Some (Router.find_chan t.router name)
with Not_found -> None with Not_found -> None
in in
(* TODO: check if allowed to list names *)
match chan with match chan with
| None -> `nosuchchannel name | None -> `nosuchchannel name
| Some chan -> list_names chan | Some chan ->
if not (Chan.is_member chan t.user) then `notonchannel name
else begin
let msg = Irc.Msg.make "PART" [name] in
Router.relay msg ~from:t.user `to_self;
Router.relay msg ~from:t.user (`to_chan chan);
Chan.part chan t.user;
if Chan.no_members chan then
Chan.unregister chan ~router:t.router;
`ok
end
let on_msg_privmsg t tgt msg = require_registered t (on_msg_privmsg t tgt msg) let on_msg_privmsg t tgt msg = require_registered t (on_msg_privmsg t tgt msg)
let on_msg_join t name = require_registered t (on_msg_join t name)
let on_msg_names t name = require_registered t (on_msg_names t name) let on_msg_names t name = require_registered t (on_msg_names t name)
let on_msg_join t name = require_registered t (on_msg_join t name)
let on_msg_part t name = require_registered t (on_msg_part t name)
(* > misc *) (* > misc *)
let on_msg_quit t why = let on_msg_quit t reason =
let why = String.concat " " why in let reason = String.concat " " reason in
Logs.debug (fun m -> m "%a: quit: %S" pp_sockaddr t.addr why); shutdown t ~reason;
`quit `ok
(* message sending *) (* message sending *)
@ -199,6 +236,7 @@ let err_unknowncommand t cmd = rpl t "421" [cmd; "Unknown command"]
let err_nonicknamegiven t = rpl t "431" ["No nickname given"] let err_nonicknamegiven t = rpl t "431" ["No nickname given"]
let err_erroneousnickname t nick = rpl t "432" [nick; "Erroneus nickname"] let err_erroneousnickname t nick = rpl t "432" [nick; "Erroneus nickname"]
let err_nicknameinuse t nick = rpl t "433" [nick; "Nickname is already in use"] let err_nicknameinuse t nick = rpl t "433" [nick; "Nickname is already in use"]
let err_notonchannel t chan = rpl t "442" [chan; "You're not on that channel"]
let err_notregistered t = rpl t "451" ["You have not registered"] let err_notregistered t = rpl t "451" ["You have not registered"]
let err_needmoreparams t cmd = rpl t "461" [cmd; "Not enough parameters"] let err_needmoreparams t cmd = rpl t "461" [cmd; "Not enough parameters"]
let err_alreadyregistered t = rpl t "462" ["Unauthorized command (already registered)"] let err_alreadyregistered t = rpl t "462" ["Unauthorized command (already registered)"]
@ -221,23 +259,25 @@ let on_msg t (msg : Irc.Msg.t) : unit =
| "JOIN", _ -> `needmoreparams | "JOIN", _ -> `needmoreparams
| "NAMES", tgt :: _ -> on_msg_names t tgt | "NAMES", tgt :: _ -> on_msg_names t tgt
| "NAMES", _ -> `needmoreparams | "NAMES", _ -> `needmoreparams
| "PART", tgt :: _ -> on_msg_part t tgt
| "PART", _ -> `needmoreparams
| _, _ -> `unknowncommand | _, _ -> `unknowncommand
in in
match result with match result with
| `ok -> () | `ok -> ()
| `quit -> shutdown t
| `welcome -> rpl_welcome t; rpl_motd t | `welcome -> rpl_welcome t; rpl_motd t
| `motd -> rpl_motd t | `motd -> rpl_motd t
| `names (cp, ch, us) -> rpl_names t cp ch us | `names (cp, ch, us) -> rpl_names t cp ch us
| `tryagain -> rpl_tryagain t msg.command | `tryagain -> rpl_tryagain t msg.command
| `alreadyregistered -> err_alreadyregistered t | `alreadyregistered -> err_alreadyregistered t
| `erroneusnickname n -> err_erroneousnickname t n
| `needmoreparams -> err_needmoreparams t msg.command | `needmoreparams -> err_needmoreparams t msg.command
| `nicknameinuse n -> err_nicknameinuse t n | `nicknameinuse n -> err_nicknameinuse t n
| `nonicknamegiven -> err_nonicknamegiven t
| `norecipient -> err_norecipient t msg.command | `norecipient -> err_norecipient t msg.command
| `nosuchnick n -> err_nosuchnick t n
| `nosuchchannel c -> err_nosuchchannel t c | `nosuchchannel c -> err_nosuchchannel t c
| `nosuchnick n -> err_nosuchnick t n
| `notexttosend -> err_notexttosend t | `notexttosend -> err_notexttosend t
| `notonchannel c -> err_notonchannel t c
| `notregistered -> err_notregistered t | `notregistered -> err_notregistered t
| `unknowncommand -> err_unknowncommand t msg.command | `unknowncommand -> err_unknowncommand t msg.command
| `nonicknamegiven -> err_nonicknamegiven t
| `erroneusnickname n -> err_erroneousnickname t n

View File

@ -25,8 +25,8 @@ and chan = {
and membership = { and membership = {
mem_user : user; mem_user : user;
mem_chan : chan; mem_chan : chan;
mutable mem_of_chan : membership Dllist.node option; mutable mem_in_chan : membership Dllist.node option;
mutable mem_of_user : membership Dllist.node option; (* mutable mem_of_user : membership Dllist.node option; *)
} }
type router = t type router = t
@ -56,6 +56,15 @@ let relay ~from msg target =
Dllist.iter_l (fun m -> Outbox.incl bcc m.mem_user.outbox) dst.members; Dllist.iter_l (fun m -> Outbox.incl bcc m.mem_user.outbox) dst.members;
Outbox.excl from.outbox; Outbox.excl from.outbox;
Outbox.send_all bcc msg Outbox.send_all bcc msg
| `to_interested user ->
let bcc = Outbox.make_bcc () in
Dllist.iter_l
(fun m ->
Dllist.iter_l
(fun m -> Outbox.incl bcc m.mem_user.outbox)
m.mem_chan.members)
user.membership;
Outbox.send_all bcc msg
module User = struct module User = struct
type t = user type t = user
@ -80,11 +89,13 @@ module User = struct
Hashtbl.remove router.users t.nick_key; Hashtbl.remove router.users t.nick_key;
t.nick_key <- empty_string_ci t.nick_key <- empty_string_ci
let set_nick t new_nick ~router = let set_nick ?(success_callback = ignore) t new_nick ~router =
let key = string_ci new_nick in let key = string_ci new_nick in
if Hashtbl.mem router.users key then if Hashtbl.mem router.users key then
`nick_in_use `nick_in_use
else begin else begin
(* hack to allow broadcasting a NICK message before nick is actually changed *)
success_callback ();
unregister t ~router; unregister t ~router;
Hashtbl.add router.users key t; Hashtbl.add router.users key t;
t.nick <- new_nick; t.nick <- new_nick;
@ -92,13 +103,13 @@ module User = struct
`nick_set `nick_set
end end
let quit t ~router = let rec part_all t =
if is_registered t then begin match Dllist.take_l t.membership with
let reason = "Goot bye" in | m ->
(* TODO: relay to everyone interested *) Option.iter Dllist.remove m.mem_in_chan;
relay ~from:t (Irc.Msg.make "QUIT" [reason]) `to_self; part_all t
unregister t ~router | exception Dllist.Empty ->
end ()
end end
module Chan = struct module Chan = struct
@ -114,6 +125,7 @@ module Chan = struct
let name t = t.name let name t = t.name
let topic t = t.topic let topic t = t.topic
let no_members t = Dllist.is_empty t.members
let register t ~router = let register t ~router =
Hashtbl.replace router.channels t.name_key t Hashtbl.replace router.channels t.name_key t
@ -125,18 +137,27 @@ module Chan = struct
Dllist.fold_r (fun m xs -> m.mem_user :: xs) t.members [] Dllist.fold_r (fun m xs -> m.mem_user :: xs) t.members []
let is_member t user = let is_member t user =
let is_mem m = m.mem_user == user in let is_mem m = m.mem_chan == t in
Option.is_some (Dllist.find_node_opt_l is_mem t.members) try
ignore (Dllist.find_node_l is_mem user.membership);
true
with Not_found ->
false
let join t user = let join t user =
let m = { let m = {
mem_chan = t; mem_chan = t;
mem_user = user; mem_user = user;
mem_of_chan = None; mem_in_chan = None;
mem_of_user = None;
} in } in
begin begin
m.mem_of_chan <- Some (Dllist.add_r m t.members); m.mem_in_chan <- Some (Dllist.add_r m t.members);
m.mem_of_user <- Some (Dllist.add_r m user.membership); ignore (Dllist.add_r m user.membership);
end end
let part t user =
let is_mem m = m.mem_chan == t in
let mem = Dllist.find_node_l is_mem user.membership in
Option.iter Dllist.remove (Dllist.get mem).mem_in_chan;
Dllist.remove mem
end end