nulform/lib/connection.ex
2013-08-03 00:45:10 +03:00

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