1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
defmodule Irc.BaseClient do
@behaviour :gen_statem
require Logger
alias Irc.Parser.Line
alias Irc.Connection
require Irc.Parser.Numeric
import Irc.Parser.Numeric
@moduledoc """
Extensible fully-featured IRC client.
## Behaviour
The BaseClient requires a callback module. You can check out `Irc.Client`.
## Capabs
IRCv3 Capabs are handled by separates modules.
"""
@internal {:next_event, :internal, nil}
defstruct [:conn, :conn_mon, :module, :modstate, :args, :nick, :modes, :info, :error]
def callback_mode, do: [:state_functions, :state_enter]
@type event ::event_connected | event_nick | event_modes | event_line | event_down
@type event_connected :: {:connected, Connection.Info.t}
@type event_nick :: {:nick, prev_nick :: String.t, new_nick :: String.t}
@type event_modes :: {:modes, changes :: String.t, prev :: list, modes :: list}
@type event_line :: {:line, line :: Irc.Parser.Line.t}
@type event_down :: {:down, Connection.error, delay :: integer}
@type modstate :: any
@type handle_return :: {:ok, modstate()}
@type handle_call_return :: {:reply, any(), modstate()} | {:noreply, modstate()}
@callback init(Keyword.t) :: {:ok, modstate} | any
@callback handle_event(event(), modstate()) :: handle_return()
@callback handle_info(any(), modstate()) :: handle_return()
@callback handle_cast(any(), modstate()) :: handle_return()
@callback handle_call(any(), reference(), modstate()) :: handle_call_return()
@callback stop(Connection.error, modstate) :: any()
@type start_opt :: Connection.start_opt
| {:module, module()}
| {:conn_statem_opts, [:gen_statem.start_opt]}
@type start_ret :: {:ok, pid} | {:error, Connection.error} | :gen_statem.start_ret
@spec start(Connection.nick, Connection.host, [start_opt], [:gen_statem.start_opt]) :: start_ret
def start(nick, host, opts \\ [], start_opts \\ []) do
:gen_statem.start(__MODULE__, [nick, host, prepare_args(opts)], start_opts)
end
@spec start_link(Connection.nick, Connection.host, [start_opt], [:gen_statem.start_opt]) :: start_ret
def start_link(nick, host, opts \\ [], start_opts \\ []) do
:gen_statem.start_link(__MODULE__, [nick, host, prepare_args(opts)], start_opts)
end
@doc false
def init([args]) do
capabs = ["account-notify", "away-notify"]
args = args
|> Keyword.put(:capabs, capabs)
with \
{:ok, conn} <- Connection.start(Keyword.get(args, :nick), Keyword.get(args, :host), args, Keyword.get(args, :conn_statem_opts, [])),
conn_mon <- Process.monitor(conn),
{:ok, module} <- Keyword.fetch(args, :module),
{:ok, modstate} <- module.init(args)
do
data = %__MODULE__{
conn: conn, conn_mon: conn_mon,
module: module, modstate: modstate,
args: args
}
{:ok, :disconnected, data}
else
error -> {:stop, error}
end
end
def disconnected(:enter, :connected, data) do
Logger.debug "#{inspect data} disconnected: #{inspect data.error}"
:keep_state_and_data
end
def disconnected(:info, msg = {:irc_conn_up, _, info}, data) do
data = run_handler_event({:connected, info}, data)
{:next_state, :connected, %__MODULE__{data | nick: info.nick, info: info, modes: info.modes, error: nil}}
end
def disconnected(type, content, data) do
handle_common(:disconnected, type, content, data)
end
def connected(:enter, _, data) do
Logger.debug "#{inspect data} UP"
:keep_state_and_data
end
def connected(:info, msg = {:irc_conn_nick, _, prev_nick, new_nick}, data) do
data = run_handler_event({:nick, prev_nick, new_nick}, data)
{:keep_state, %__MODULE__{data | nick: new_nick}}
end
def connected(:info, msg = {:irc_conn_modes, _, changes, prev, modes}, data) do
data = run_handler_event({:modes, changes, prev, modes}, data)
{:keep_state, %__MODULE__{data | modes: modes}}
end
def connected(:info, {:irc_conn_down, _, reason, delay}, data) do
data = run_handler_event({:down, reason, delay}, data)
{:next_state, :disconnected, %__MODULE__{data | error: reason}}
end
# TODO: REMOVE?
def connected(:info, {:irc_conn_line, _, line}, data) do
data = run_handler_event({:line, line}, data)
:keep_state_and_data
end
def connected(type, content, data) do
handle_common(:connected, type, content, data)
end
# TODO: Callback stop?
def error(:internal, reason, data) do
Logger.error "#{inspect data}: #{inspect reason}"
data.module.stop(reason, data.modstate)
{:stop, :normal}
end
def handle_common(_, :info, {:irc_conn_error, _, reason}, data) do
{:next_state, :error, data, {:next_event, :internal, reason}}
end
def handle_common(_, :enter, _, _) do
:keep_state_and_data
end
def terminate(reason, state, data) do
Logger.error("#{inspect(data)} terminating in #{inspect(state)}: #{inspect reason}")
end
def code_change(_old_vsn, old_state, old_data, _extra) do
{:ok, old_state, old_data}
end
defp prepare_args(args) do
args
end
defp run_handler_event(event, data) do
case data.module.handle_event(event, data.modstate) do
{:ok, modstate} -> %__MODULE__{data | modstate: modstate}
:ok -> data
end
end
defimpl Inspect, for: __MODULE__ do
import Inspect.Algebra
def inspect(struct, _opts) do
concat(["#Irc.Client<", inspect(self()), ">"])
end
end
end
|