124 lines
3.8 KiB
Elixir
124 lines
3.8 KiB
Elixir
defmodule Nulform.Connection do
|
|
@moduledoc """
|
|
Connection is a module to handle a single IRC connection to an IRC
|
|
server.
|
|
|
|
Connection automatically handles using an altnick (but only one) and
|
|
responding to pings.
|
|
"""
|
|
defrecord Data,
|
|
handler: nil,
|
|
buffer: nil,
|
|
id: 0,
|
|
host: "",
|
|
port: 0,
|
|
nick: "",
|
|
altnick: "",
|
|
username: "",
|
|
realname: "",
|
|
sock: nil
|
|
|
|
|
|
def init([handler, buffer, id, host, port, nick, altnick, username, realname]) do
|
|
data = Data.new handler: handler, buffer: buffer, id: id, host: host,
|
|
port: port, nick: nick, altnick: altnick,
|
|
username: username, realname: realname
|
|
|
|
{:ok, data}
|
|
end
|
|
|
|
def handle_cast(:connect, data) do
|
|
{:ok, sock} = connect binary_to_list(data.host), data.port
|
|
data = data.sock sock
|
|
|
|
send_connect_info data
|
|
{:noreply, data}
|
|
end
|
|
|
|
def handle_cast({:connect, sock}, data) do
|
|
data = data.sock sock
|
|
send_connect_info data
|
|
|
|
{:noreply, data}
|
|
end
|
|
|
|
def handle_cast(msg, data) when is_binary msg do
|
|
send_raw data, msg
|
|
{:noreply, data}
|
|
end
|
|
|
|
def handle_info(msg, data) do
|
|
case msg do
|
|
{:nulform, :connection, :reset_buffer, pid} ->
|
|
data = data.buffer pid
|
|
|
|
{:tcp, _, msg} ->
|
|
stripped = String.rstrip msg
|
|
tell_handler data, stripped
|
|
|
|
# Handle PING responses
|
|
case String.split stripped, ":" do
|
|
["PING " | rest] when is_list rest ->
|
|
concat_list = fn
|
|
f, list, sep when length(list) > 1 ->
|
|
[now | rest] = list
|
|
now <> sep <> f.(rest, sep)
|
|
_, list, _ ->
|
|
[last] = list
|
|
last
|
|
end
|
|
|
|
send data.buffer, "PONG :" <> concat_list.(concat_list, rest, ":")
|
|
|
|
_ ->
|
|
end
|
|
|
|
# Change to altnick when nick is already in use
|
|
# TODO: May not work on all networks
|
|
# We change the altnick to avoid an infinite loop
|
|
# TODO: New altnick is predictable, no seeding
|
|
case String.split stripped do
|
|
[_, "433" | _] ->
|
|
send data.buffer, "NICK " <> data.altnick
|
|
uniqid = to_binary :random.uniform(9999)
|
|
data = data.altnick String.slice(data.altnick, 0, 10) <> "-" <> uniqid
|
|
_ ->
|
|
end
|
|
end
|
|
|
|
{:noreply, data}
|
|
end
|
|
|
|
defp connect(host, port) do
|
|
:gen_tcp.connect host, port, [
|
|
{:active, :true}, :binary, :inet, {:packet, :line}
|
|
]
|
|
end
|
|
|
|
defp disconnect(sock) do
|
|
:gen_tcp.close sock
|
|
end
|
|
|
|
defp send_connect_info(data) do
|
|
# TODO: Do we need to delay here on some networks?
|
|
send data.buffer, "NICK " <> data.nick
|
|
send data.buffer, "USER " <> data.username <> " 8 * :" <> data.realname
|
|
end
|
|
|
|
# Send message through buffer to avoid flooding
|
|
defp send(buffer, msg) do
|
|
:gen_server.cast buffer, msg
|
|
end
|
|
|
|
defp send_raw(data, msg) do
|
|
:ok = :gen_tcp.send data.sock, msg <> "\r\n"
|
|
IO.puts(to_binary(data.id) <> " <- " <> msg)
|
|
end
|
|
|
|
defp tell_handler(data, msg) do
|
|
msg_record = Nulform.IRC.Message.new connection_id: data.id,
|
|
buffer: data.buffer,
|
|
raw_msg: msg
|
|
data.handler <- {:nulform, :msg_in, msg_record}
|
|
end
|
|
end
|