yet another user register flow refactor
This commit is contained in:
parent
ae8b837c56
commit
7a101e0620
|
@ -3,22 +3,13 @@ open Types
|
||||||
type prefix =
|
type prefix =
|
||||||
| No_prefix
|
| No_prefix
|
||||||
| Server_prefix of string
|
| Server_prefix of string
|
||||||
| User_prefix of name * userinfo option * string option
|
| User_prefix of name * userinfo option
|
||||||
|
|
||||||
let pp_userinfo_opt ppf = function
|
|
||||||
| None -> ()
|
|
||||||
| Some { username; _ } -> Format.fprintf ppf "!%s" username
|
|
||||||
|
|
||||||
let pp_hostname_opt ppf = function
|
|
||||||
| None -> ()
|
|
||||||
| Some hostname -> Format.fprintf ppf "@%s" hostname
|
|
||||||
|
|
||||||
let prefix_string = function
|
let prefix_string = function
|
||||||
| No_prefix -> ""
|
| No_prefix -> ""
|
||||||
| Server_prefix s -> s
|
| Server_prefix servername -> servername
|
||||||
| User_prefix (nick, uinfo, host) ->
|
| User_prefix (nick, None) -> nick
|
||||||
Format.asprintf "%s%a%a"
|
| User_prefix (nick, Some uinfo) -> Format.asprintf "%s%a" nick pp_userinfo uinfo
|
||||||
nick pp_userinfo_opt uinfo pp_hostname_opt host
|
|
||||||
|
|
||||||
let pp_prefix ppf p =
|
let pp_prefix ppf p =
|
||||||
Format.fprintf ppf "%S" (prefix_string p)
|
Format.fprintf ppf "%S" (prefix_string p)
|
||||||
|
@ -158,14 +149,15 @@ let%expect_test _ =
|
||||||
Format.printf "%a" pp_prefix (Server_prefix "localhost");
|
Format.printf "%a" pp_prefix (Server_prefix "localhost");
|
||||||
[%expect {| "localhost" |}];
|
[%expect {| "localhost" |}];
|
||||||
|
|
||||||
Format.printf "%a" pp_prefix (User_prefix ("tali", None, None));
|
Format.printf "%a" pp_prefix (User_prefix ("tali", None));
|
||||||
[%expect {| "tali" |}];
|
[%expect {| "tali" |}];
|
||||||
|
|
||||||
Format.printf "%a" pp_prefix (User_prefix ("tali", None, Some "elsewhere"));
|
let milo = {
|
||||||
[%expect {| "tali@elsewhere" |}];
|
username = "milo";
|
||||||
|
realname = "Milo";
|
||||||
let milo = { username = "milo"; realname = "Milo" } in
|
hostname = "elsewhere";
|
||||||
Format.printf "%a" pp_prefix (User_prefix ("tali", Some milo, Some "elsewhere"));
|
} in
|
||||||
|
Format.printf "%a" pp_prefix (User_prefix ("tali", Some milo));
|
||||||
[%expect {| "tali!milo@elsewhere" |}];
|
[%expect {| "tali!milo@elsewhere" |}];
|
||||||
|
|
||||||
make "NICK" ["tali"] |> print_msg_nl;
|
make "NICK" ["tali"] |> print_msg_nl;
|
||||||
|
|
|
@ -3,7 +3,7 @@ open Types
|
||||||
type prefix =
|
type prefix =
|
||||||
| No_prefix
|
| No_prefix
|
||||||
| Server_prefix of string
|
| Server_prefix of string
|
||||||
| User_prefix of name * userinfo option * string option
|
| User_prefix of name * userinfo option
|
||||||
|
|
||||||
val prefix_string : prefix -> string
|
val prefix_string : prefix -> string
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
type name = string
|
type name = string
|
||||||
type userinfo = { username : string; realname : string }
|
|
||||||
|
type userinfo = {
|
||||||
|
username : string;
|
||||||
|
realname : string;
|
||||||
|
hostname : string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pp_userinfo ppf { username; realname; hostname } =
|
||||||
|
ignore realname;
|
||||||
|
Format.fprintf ppf "!%s@%s" username hostname
|
||||||
|
|
||||||
let name_type s =
|
let name_type s =
|
||||||
let rec valid i =
|
let rec valid i =
|
||||||
|
|
|
@ -3,91 +3,89 @@ module User = Router.User
|
||||||
module Chan = Router.Chan
|
module Chan = Router.Chan
|
||||||
|
|
||||||
type t = {
|
type t = {
|
||||||
addr : sockaddr;
|
|
||||||
router : Router.t;
|
router : Router.t;
|
||||||
user : User.t;
|
addr : sockaddr;
|
||||||
outbox : Outbox.t;
|
outbox : Outbox.t;
|
||||||
|
mutable user : User.t option;
|
||||||
mutable pending_nick : string option;
|
mutable pending_nick : string option;
|
||||||
|
mutable pending_userinfo : Irc.userinfo option;
|
||||||
}
|
}
|
||||||
|
|
||||||
let make ~(router : Router.t) ~(addr : sockaddr) : t =
|
let make ~(router : Router.t) ~(addr : sockaddr) : t =
|
||||||
let hostname = match addr with
|
|
||||||
| ADDR_INET (ia, _) -> Unix.string_of_inet_addr ia
|
|
||||||
| ADDR_UNIX path -> path
|
|
||||||
in
|
|
||||||
let outbox = Outbox.make () in
|
let outbox = Outbox.make () in
|
||||||
let user = User.make ~hostname ~outbox in
|
{ router; addr; outbox; user = None; pending_nick = None; pending_userinfo = None }
|
||||||
{ addr; router; user; outbox; pending_nick = None }
|
|
||||||
|
|
||||||
let outbox t = t.outbox
|
let outbox t = t.outbox
|
||||||
|
|
||||||
let shutdown ?reason t =
|
let shutdown ?reason t =
|
||||||
if User.is_registered t.user then begin
|
Option.iter
|
||||||
(* TODO: relay to everyone interested *)
|
(fun me ->
|
||||||
let reason = Option.value reason ~default:"Goot bye" in
|
let reason = Option.value reason ~default:"Goot bye" in
|
||||||
Router.relay (Irc.Msg.make "QUIT" [reason])
|
Router.relay (Irc.Msg.make "QUIT" [reason]) ~from:me (`to_interested me);
|
||||||
~from:t.user (`to_interested t.user);
|
User.part_all me;
|
||||||
User.part_all t.user;
|
User.unregister me ~router:t.router)
|
||||||
User.unregister t.user ~router:t.router;
|
t.user;
|
||||||
end;
|
|
||||||
Outbox.close t.outbox
|
Outbox.close t.outbox
|
||||||
|
|
||||||
(* message handling *)
|
|
||||||
|
|
||||||
let require_registered t f =
|
|
||||||
if User.is_registered t.user then
|
|
||||||
f (Option.get t.user.userinfo)
|
|
||||||
else
|
|
||||||
`notregistered
|
|
||||||
|
|
||||||
(* > user registration *)
|
(* > user registration *)
|
||||||
|
|
||||||
|
let require_registered t f =
|
||||||
|
match t.user with
|
||||||
|
| Some me -> f me
|
||||||
|
| None -> `notregistered
|
||||||
|
|
||||||
let attempt_to_register t =
|
let attempt_to_register t =
|
||||||
match t.pending_nick, t.user.userinfo with
|
match t.pending_nick, t.pending_userinfo with
|
||||||
| Some nick, Some _userinfo ->
|
| Some nick, Some userinfo ->
|
||||||
t.pending_nick <- None;
|
t.pending_nick <- None;
|
||||||
begin match User.set_nick t.user nick ~router:t.router with
|
let me = User.make ~userinfo ~outbox:t.outbox in
|
||||||
|
begin match User.set_nick me nick ~router:t.router with
|
||||||
| `nick_in_use -> `nicknameinuse nick
|
| `nick_in_use -> `nicknameinuse nick
|
||||||
| `nick_set ->
|
| `nick_set ->
|
||||||
|
t.user <- Some me;
|
||||||
|
|
||||||
(* TODO: this sucks, fix it *)
|
(* TODO: this sucks, fix it *)
|
||||||
let mode_msg = Irc.Msg.make "MODE" [nick; "+iw"] in
|
let mode_msg = Irc.Msg.make "MODE" [nick; "+iw"] in
|
||||||
defer (fun () -> Router.relay mode_msg ~from:t.user `to_self);
|
defer (fun () -> Router.relay mode_msg ~from:me `to_self);
|
||||||
|
|
||||||
`welcome
|
`welcome me
|
||||||
end
|
end
|
||||||
| _, _ -> `ok
|
| _, _ -> `ok
|
||||||
|
|
||||||
let on_msg_nick t nick =
|
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 match t.user with
|
||||||
let success_callback () =
|
| Some me ->
|
||||||
let msg = Irc.Msg.make "NICK" [nick] in
|
begin
|
||||||
Router.relay msg ~from:t.user (`to_interested t.user);
|
let msg = Irc.Msg.make "NICK" [nick] in
|
||||||
in
|
match
|
||||||
match User.set_nick t.user nick ~router:t.router ~success_callback with
|
User.set_nick me nick
|
||||||
| `nick_in_use -> `nicknameinuse nick
|
~router:t.router
|
||||||
| `nick_set -> `ok
|
~success_callback:(fun () -> Router.relay msg ~from:me (`to_interested me))
|
||||||
else begin
|
with
|
||||||
t.pending_nick <- Some nick;
|
| `nick_in_use -> `nicknameinuse nick
|
||||||
attempt_to_register t
|
| `nick_set -> `ok
|
||||||
end
|
end
|
||||||
|
| None ->
|
||||||
|
t.pending_nick <- Some nick;
|
||||||
|
attempt_to_register t
|
||||||
|
|
||||||
let on_msg_user t username modestr realname =
|
let on_msg_user t username realname =
|
||||||
if User.is_registered t.user then
|
match t.user with
|
||||||
`alreadyregistered
|
| Some _me -> `alreadyregistered
|
||||||
else begin
|
| None ->
|
||||||
(* NB: "+iw" is automatically set, so it's impossible to actually affect the initial
|
(* TODO: configure hiding hostnames *)
|
||||||
mode with the parameter to USER *)
|
let hostname = match t.addr with
|
||||||
ignore modestr;
|
| ADDR_INET (ia, _) -> Unix.string_of_inet_addr ia
|
||||||
t.user.userinfo <- Some { username; realname };
|
| ADDR_UNIX path -> path
|
||||||
|
in
|
||||||
|
t.pending_userinfo <- Some { username; realname; hostname };
|
||||||
attempt_to_register t
|
attempt_to_register t
|
||||||
end
|
|
||||||
|
|
||||||
(* > messages and channels *)
|
(* > messages and channels *)
|
||||||
|
|
||||||
let on_msg_privmsg t tgt txt _ =
|
let on_msg_privmsg t tgt txt me =
|
||||||
(* TODO: comma-separated list of targets *)
|
(* TODO: comma-separated list of targets *)
|
||||||
let msg = Irc.Msg.make "PRIVMSG" [tgt; txt] ~always_trailing:true in
|
let msg = Irc.Msg.make "PRIVMSG" [tgt; txt] ~always_trailing:true in
|
||||||
let dst =
|
let dst =
|
||||||
|
@ -105,7 +103,7 @@ let on_msg_privmsg t tgt txt _ =
|
||||||
(* TODO: check if channel is +n and user is not a member *)
|
(* TODO: check if channel is +n and user is not a member *)
|
||||||
(* TODO: check if channel is +m and user is not priviledged *)
|
(* TODO: check if channel is +m and user is not priviledged *)
|
||||||
(* TODO: check if channel is +b <user> *)
|
(* TODO: check if channel is +b <user> *)
|
||||||
Router.relay msg ~from:t.user dst;
|
Router.relay msg ~from:me dst;
|
||||||
`ok
|
`ok
|
||||||
|
|
||||||
let list_names chan =
|
let list_names chan =
|
||||||
|
@ -116,7 +114,7 @@ let list_names chan =
|
||||||
in
|
in
|
||||||
`names ("@", Chan.name chan, names)
|
`names ("@", Chan.name chan, names)
|
||||||
|
|
||||||
let on_msg_names t name _ =
|
let on_msg_names t name _me =
|
||||||
(* TODO: comma-separated list of channels *)
|
(* TODO: comma-separated list of channels *)
|
||||||
match Irc.name_type name with
|
match Irc.name_type name with
|
||||||
| `nick | `invalid ->
|
| `nick | `invalid ->
|
||||||
|
@ -131,7 +129,7 @@ let on_msg_names t name _ =
|
||||||
| None -> `nosuchchannel name
|
| None -> `nosuchchannel name
|
||||||
| Some chan -> list_names chan
|
| Some chan -> list_names chan
|
||||||
|
|
||||||
let on_msg_join t name _ =
|
let on_msg_join t name me =
|
||||||
(* TODO: comma-separated list of channels *)
|
(* TODO: comma-separated list of channels *)
|
||||||
(* TODO: "0" parameter means part from all channels *)
|
(* TODO: "0" parameter means part from all channels *)
|
||||||
match Irc.name_type name with
|
match Irc.name_type name with
|
||||||
|
@ -146,18 +144,18 @@ let on_msg_join t name _ =
|
||||||
Chan.register chan ~router:t.router;
|
Chan.register chan ~router:t.router;
|
||||||
chan
|
chan
|
||||||
in
|
in
|
||||||
if Chan.is_member chan t.user then
|
if Chan.is_member chan me then
|
||||||
`ok
|
`ok
|
||||||
else begin
|
else begin
|
||||||
(* TODO: check if channel is +k, get associated key from parameters *)
|
(* TODO: check if channel is +k, get associated key from parameters *)
|
||||||
Chan.join chan t.user;
|
Chan.join chan me;
|
||||||
let msg = Irc.Msg.make "JOIN" [name] in
|
let msg = Irc.Msg.make "JOIN" [name] in
|
||||||
Router.relay msg ~from:t.user `to_self;
|
Router.relay msg ~from:me `to_self;
|
||||||
Router.relay msg ~from:t.user (`to_chan chan);
|
Router.relay msg ~from:me (`to_chan chan);
|
||||||
list_names chan
|
list_names chan
|
||||||
end
|
end
|
||||||
|
|
||||||
let on_msg_part t name _ =
|
let on_msg_part t name me =
|
||||||
(* TODO: comma-separated list of channels *)
|
(* TODO: comma-separated list of channels *)
|
||||||
(* TODO: part reason *)
|
(* TODO: part reason *)
|
||||||
match Irc.name_type name with
|
match Irc.name_type name with
|
||||||
|
@ -171,12 +169,12 @@ let on_msg_part t name _ =
|
||||||
match chan with
|
match chan with
|
||||||
| None -> `nosuchchannel name
|
| None -> `nosuchchannel name
|
||||||
| Some chan ->
|
| Some chan ->
|
||||||
if not (Chan.is_member chan t.user) then `notonchannel name
|
if not (Chan.is_member chan me) then `notonchannel name
|
||||||
else begin
|
else begin
|
||||||
let msg = Irc.Msg.make "PART" [name] in
|
let msg = Irc.Msg.make "PART" [name] in
|
||||||
Router.relay msg ~from:t.user `to_self;
|
Router.relay msg ~from:me `to_self;
|
||||||
Router.relay msg ~from:t.user (`to_chan chan);
|
Router.relay msg ~from:me (`to_chan chan);
|
||||||
Chan.part chan t.user;
|
Chan.part chan me;
|
||||||
if Chan.no_members chan then
|
if Chan.no_members chan then
|
||||||
Chan.unregister chan ~router:t.router;
|
Chan.unregister chan ~router:t.router;
|
||||||
`ok
|
`ok
|
||||||
|
@ -211,13 +209,15 @@ let srv_motd_lines = [
|
||||||
|
|
||||||
let rpl t cmd params =
|
let rpl t cmd params =
|
||||||
let prefix = Irc.Msg.Server_prefix srv_host in
|
let prefix = Irc.Msg.Server_prefix srv_host in
|
||||||
let target = User.nick t.user in
|
let target = match t.user with
|
||||||
|
| Some me -> User.nick me
|
||||||
|
| None -> "*"
|
||||||
|
in
|
||||||
Outbox.send t.outbox (Irc.Msg.make ~prefix cmd (target :: params))
|
Outbox.send t.outbox (Irc.Msg.make ~prefix cmd (target :: params))
|
||||||
|
|
||||||
let rpl_tryagain t cmd = rpl t "263" [cmd; "Please wait a while and try again."]
|
let rpl_tryagain t cmd = rpl t "263" [cmd; "Please wait a while and try again."]
|
||||||
|
|
||||||
let rpl_welcome t =
|
let rpl_welcome t me =
|
||||||
let who = Irc.Msg.prefix_string (User.prefix t.user) in
|
|
||||||
let isupport = [
|
let isupport = [
|
||||||
"CASEMAPPING=ascii";
|
"CASEMAPPING=ascii";
|
||||||
"CHANTYPES=#";
|
"CHANTYPES=#";
|
||||||
|
@ -225,7 +225,8 @@ let rpl_welcome t =
|
||||||
"PREFIX=(ov)@+";
|
"PREFIX=(ov)@+";
|
||||||
] in
|
] in
|
||||||
begin
|
begin
|
||||||
rpl t "001" [Fmt.str "Welcome to the tali IRC network %s" who];
|
rpl t "001" ["Welcome to the tali IRC network %s"
|
||||||
|
^ Irc.Msg.prefix_string (User.prefix me)];
|
||||||
rpl t "002" [Fmt.str "Your host is %s, running version %s" srv_host srv_ver];
|
rpl t "002" [Fmt.str "Your host is %s, running version %s" srv_host srv_ver];
|
||||||
rpl t "003" [Fmt.str "This server was created %s" srv_created];
|
rpl t "003" [Fmt.str "This server was created %s" srv_created];
|
||||||
rpl t "004" [srv_host; srv_ver; "iow"; "imnst"; "bklov"];
|
rpl t "004" [srv_host; srv_ver; "iow"; "imnst"; "bklov"];
|
||||||
|
@ -266,7 +267,7 @@ let on_msg t (msg : Irc.Msg.t) : unit =
|
||||||
match msg.command, msg.params with
|
match msg.command, msg.params with
|
||||||
| "NICK", new_nick :: _ -> on_msg_nick t new_nick
|
| "NICK", new_nick :: _ -> on_msg_nick t new_nick
|
||||||
| "NICK", [] -> `nonicknamegiven
|
| "NICK", [] -> `nonicknamegiven
|
||||||
| "USER", u :: m :: _h :: r :: _ -> on_msg_user t u m r
|
| "USER", unm :: _ :: _ :: rnm :: _ -> on_msg_user t unm rnm
|
||||||
| "USER", _ -> `needmoreparams
|
| "USER", _ -> `needmoreparams
|
||||||
| "QUIT", why -> on_msg_quit t why
|
| "QUIT", why -> on_msg_quit t why
|
||||||
| "MOTD", _ -> `motd
|
| "MOTD", _ -> `motd
|
||||||
|
@ -282,7 +283,7 @@ let on_msg t (msg : Irc.Msg.t) : unit =
|
||||||
in
|
in
|
||||||
match result with
|
match result with
|
||||||
| `ok -> ()
|
| `ok -> ()
|
||||||
| `welcome -> rpl_welcome t; rpl_motd t
|
| `welcome usr -> rpl_welcome t usr; 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
|
||||||
|
|
|
@ -7,10 +7,9 @@ type t = {
|
||||||
|
|
||||||
and user = {
|
and user = {
|
||||||
outbox : Outbox.t;
|
outbox : Outbox.t;
|
||||||
hostname : string;
|
userinfo : Irc.userinfo;
|
||||||
mutable nick : Irc.name;
|
mutable nick : Irc.name;
|
||||||
mutable nick_key : string_ci;
|
mutable nick_key : string_ci;
|
||||||
mutable userinfo : Irc.userinfo option;
|
|
||||||
mutable membership : membership Dllist.t;
|
mutable membership : membership Dllist.t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,11 +39,11 @@ let find_user t nick =
|
||||||
let find_chan t name =
|
let find_chan t name =
|
||||||
Hashtbl.find t.channels (string_ci name)
|
Hashtbl.find t.channels (string_ci name)
|
||||||
|
|
||||||
let user_prefix u =
|
let user_prefix user =
|
||||||
Irc.Msg.User_prefix (u.nick, u.userinfo, Some u.hostname)
|
Irc.Msg.User_prefix (user.nick, Some user.userinfo)
|
||||||
|
|
||||||
let relay ~from msg target =
|
let relay ~(from : user) (msg : Irc.Msg.t) target =
|
||||||
let msg = { msg with Irc.Msg.prefix = user_prefix from } in
|
let msg = { msg with prefix = user_prefix from } in
|
||||||
match target with
|
match target with
|
||||||
| `to_self ->
|
| `to_self ->
|
||||||
Outbox.send from.outbox msg
|
Outbox.send from.outbox msg
|
||||||
|
@ -68,13 +67,12 @@ let relay ~from msg target =
|
||||||
module User = struct
|
module User = struct
|
||||||
type t = user
|
type t = user
|
||||||
|
|
||||||
let make ~hostname ~outbox =
|
let make ~userinfo ~outbox =
|
||||||
{
|
{
|
||||||
hostname;
|
|
||||||
outbox;
|
outbox;
|
||||||
|
userinfo;
|
||||||
nick = "*";
|
nick = "*";
|
||||||
nick_key = empty_string_ci;
|
nick_key = empty_string_ci;
|
||||||
userinfo = None;
|
|
||||||
(* mode = "+iw"; *)
|
(* mode = "+iw"; *)
|
||||||
membership = Dllist.create ();
|
membership = Dllist.create ();
|
||||||
}
|
}
|
||||||
|
@ -82,7 +80,7 @@ module User = struct
|
||||||
let outbox t = t.outbox
|
let outbox t = t.outbox
|
||||||
let nick t = t.nick
|
let nick t = t.nick
|
||||||
let prefix = user_prefix
|
let prefix = user_prefix
|
||||||
let is_registered t = t.nick_key <> empty_string_ci
|
(* let is_registered t = t.nick_key <> empty_string_ci *)
|
||||||
|
|
||||||
let unregister t ~router =
|
let unregister t ~router =
|
||||||
Hashtbl.remove router.users t.nick_key;
|
Hashtbl.remove router.users t.nick_key;
|
||||||
|
|
Loading…
Reference in New Issue