diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index ac918c8..0056319 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -10,15 +10,23 @@ jobs: build: runs-on: ubuntu-latest - + name: OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}} + strategy: + matrix: + otp: [22.1.7] + elixir: [1.10.4] + env: + MIX_ENV: test + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v2 - name: Setup elixir uses: actions/setup-elixir@v1 with: - elixir-version: '1.9.4' # Define the elixir version [required] - otp-version: '22.2' # Define the OTP version [required] + elixir-version: ${{matrix.elixir}} # Define the elixir version [required] + otp-version: ${{matrix.otp}} # Define the OTP version [required] - name: Install Dependencies run: mix deps.get - name: Run Tests run: mix test + - run: mix coveralls.github diff --git a/README.md b/README.md index c4d6cda..20ab361 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,36 @@ Blur ==== -Chat Bot for Streamers. Built to scale. Fast, efficient processing. +Twitch Chat Bot + +![Elixir CI](https://github.com/rockerBOO/blur/workflows/Elixir%20CI/badge.svg) +[![Coverage Status](https://coveralls.io/repos/github/rockerBOO/blur/badge.svg)](https://coveralls.io/github/rockerBOO/blur) ## Install -To setup, the configuration options are in `.env`. `.env.example` is setup to show how to configure, and just rename to `.env`. +First, add Blur to your mix.exs dependencies: ```elixir -# The key generated from twitchtmi.com/chat -export TWITCH_CHAT_KEY=oauth: - -# Username on Twitch for the Bot. -# It needs to match to the user of the access token/chat key. -export TWITCH_USERNAME= - -# OPTIONAL. This takes precidence over the chat key for more intergrated options (authenticated calls to twitch) -# export TWITCH_ACCESS_TOKEN= +def deps do + [{:blur, "~> 0.2"}] +end ``` -## To Run - -`source .env && iex -S mix` - -## Configuration for Channels +Then, update your dependencies: -Configuration for channels are stored in `data/#channel/` +```sh-session +$ mix deps.get +``` -* `data/rockerboo/commands.json` - * Commands list -* `data/rockerboo/config.json` - * Configuration options for the channel -* `data/rockerboo/aliases.json` - * Stores aliases for commmands +To setup, the configuration options are in `.env`. `.env.example` is setup to show how to configure, and just rename to `.env`. -### `data/#channel/config.json` -```js -{ - "messages": "on" -} -``` +```elixir +# The key generated from https://twitchapps.com/tmi/. +export TWITCH_CHAT_KEY=oauth: -### `data/#channel/commands.json` -```js -{ - "hello": ["say", "Hello"], - "help": ["say", "You need help"], - "follower": ["cmd", "follower"], - "followed": ["cmd", "followed"], - "flip": ["say", "(╯°□°)╯︵┻━┻"], - "song": ["cmd", "song"], - "highlight": ["cmd", "highlight"] -} +# Username on Twitch for the Bot. +# It needs to match to the user of the access token/chat key. +export TWITCH_USERNAME= ``` -### `data/#channel/aliases.json` -```js -{ - "bealight": "bealright", - "bot": "elirc", - "glacier": "theme", - "xfile": "xfiles", - "x-file": "xfiles", - "x-files": "xfiles", - "h": "help", - "playlist": "lastfm", - "coming": "getsmeeverytime", - "63": "speedlimit", - "soundslist": "soundlist", - "sounds": "soundlist", - "table": "flip", - "tableflip": "flip" -} -``` diff --git a/config/config.exs b/config/config.exs deleted file mode 100644 index 68f80dd..0000000 --- a/config/config.exs +++ /dev/null @@ -1,28 +0,0 @@ -# This file is responsible for configuring your application -# and its dependencies with the aid of the Mix.Config module. -use Mix.Config - -# This configuration is loaded before any dependency and is restricted -# to this project. If another project depends on this project, this -# file won't be loaded nor affect the parent project. For this reason, -# if you want to provide default values for your application for third- -# party users, it should be done in your mix.exs file. - -# Sample configuration: -# -# config :logger, :console, -# level: :info, -# format: "$date $time [$level] $metadata$message\n", -# metadata: [:user_id] - -# It is also possible to import configuration files, relative to this -# directory. For example, you can emulate configuration per environment -# by uncommenting the line below and defining dev.exs, test.exs and such. -# Configuration from the imported file will override the ones defined -# here (which is why it is important to import them last). -# -# import_config "#{Mix.env}.exs" - -# config :logger, handle_sasl_reports: true - -config :blur, :autojoin, ["#rockerboo"] diff --git a/lib/app.ex b/lib/app.ex new file mode 100644 index 0000000..a325246 --- /dev/null +++ b/lib/app.ex @@ -0,0 +1,29 @@ +defmodule Blur.App do + use Supervisor + + def start! do + Supervisor.start_link(__MODULE__, [], name: :blur) + end + + @spec start(atom, list) :: GenServer.on_start() + def start(_type, [user, channels]) do + Supervisor.start_link(__MODULE__, [user, channels], name: :blur) + end + + @impl Supervisor + def init([user, channels]) do + {:ok, client} = ExIRC.start_link!() + + children = [ + {Blur.Channels, []}, + {Blur.Users, []}, + # {Blur.IRC.Client, [client]}, + {Blur.IRC.Connection, [client, user]}, + {Blur.IRC.Login, [client, channels]}, + {Blur.IRC.Channel, [client]}, + {Blur.IRC.Message, [client]} + ] + + Supervisor.init(children, strategy: :one_for_one) + end +end diff --git a/lib/blur.ex b/lib/blur.ex index fb2bfd0..8ad81a9 100644 --- a/lib/blur.ex +++ b/lib/blur.ex @@ -1,67 +1,66 @@ defmodule Blur do @moduledoc """ Access your bot through Blur - """ - - require Logger - @doc """ - Join a channel. + Client - ## Examples - iex> Blur.join "#channel" - :ok + Handlers + - Login + on_logon -> join_channels + - Connection + %ConnState{} + on_connection -> login + on_disconnection -> logoff + - Message + pool + - Channels + on_join -> add_channel + [Channel, Channel] + - Users + on_names -> add_users + on_user_join -> add_user + on_part -> remove_user + [User, User] """ - @spec join(channel :: binary) :: :ok | {:error, atom} - def join(channel), - do: join(:irc_client, channel) + + require Logger @doc """ Join a channel on the client IRC connection. ## Examples - iex> Blur.join client, "#channel" - :ok + + Blur.join :twitch, "#channel" + :ok """ - @spec join(pid | atom, binary) :: :ok | {:error, atom} + @spec join(client :: GenServer.server(), channel :: binary) :: :ok | {:error, atom} def join(client, "#" <> _ = channel), - do: Blur.IRC.join(client, channel) + do: join(client, channel) def join(client, channel), - do: join(client, "#" <> channel) + do: Blur.IRC.join(client, channel) @doc """ Leave a channel on the client IRC connection. ## Examples - iex> Blur.leave client, "#channel" - :ok + + Blur.leave :twitch, "#channel" + {:error, :not_logged_in} """ - @spec leave(client :: pid, channel :: binary) :: :ok | {:error, atom} + @spec leave(client :: GenServer.server(), channel :: binary) :: :ok | {:error, atom} def leave(client, channel), do: Blur.IRC.part(client, channel) - @doc """ - Leave a channel. - - ## Examples - iex> Blur.leave "#channel" - :ok - """ - @spec leave(channel :: binary) :: :ok | {:error, atom} - def leave(channel), - do: leave(:irc_client, channel) - @doc """ Say a message to the channel. ## Examples - iex> Blur.say "#channel", "a message to the channel" - :ok + + Blur.say :twitch, "adattape" """ - @spec say(channel :: charlist, msg :: charlist) :: :ok | {:error, atom} - def say(channel, msg), - do: Blur.IRC.say(:irc_client, channel, msg) + def say(client, channel, message), + do: Blur.IRC.say(client, channel, message) @doc """ Get token from the environmental variables. diff --git a/lib/channel.ex b/lib/channel.ex deleted file mode 100644 index 7a2bf50..0000000 --- a/lib/channel.ex +++ /dev/null @@ -1,160 +0,0 @@ -defmodule Blur.Channel do - @moduledoc """ - Channel module - """ - - use GenServer - require Logger - alias Blur.Channel.Config - - defmodule State do - defstruct channel: "", - client: nil, - config?: nil, - users: [] - end - - defmodule Error do - @moduledoc """ - Channel errors - """ - defexception [:message] - end - - @spec to_atom(channel :: binary) :: atom - def to_atom("#" <> channel), do: String.to_atom(channel) - def to_atom(channel), do: to_atom("#" <> channel) - - @doc """ - Start channel store - """ - @spec start_link(client :: pid, channel :: binary) :: GenServer.on_start() - def start_link(client, "#" <> _ = channel) do - GenServer.start_link( - __MODULE__, - [client, channel] - ) - end - - def start_link(client, channel), do: start_link(client, "#" <> channel) - - @impl true - @spec init(list) :: {:ok, %State{}} - def init([client, channel]) do - config? = channel |> Config.load_channel_config() - - {:ok, users} = Blur.Channel.Users.start_link(channel) - - {:ok, %State{channel: channel, client: client, config?: config?, users: users}} - end - - @doc """ - Add user to channel - - ## Examples - iex> Blur.Channel.add_user "#rockerboo", "rockerBOO" - :ok - """ - @spec add_user(channel :: binary, user :: binary) :: :ok - def add_user(channel, user) do - to_atom(channel) |> GenServer.cast({:add_user, user}) - end - - @doc """ - Remove user from the channel - - ## Examples - iex> Blur.Channel.remove_user "#rockerboo", "rockerBOO" - :ok - """ - @spec remove_user(channel :: binary, user :: binary) :: :ok - def remove_user(channel, user) do - to_atom(channel) |> GenServer.cast({:remove_user, user}) - end - - @doc """ - Get users in a channel - - ## Examples - iex> Blur.Channel.users "#rockerboo" - ["rockerBOO"] - """ - @spec users(channel :: binary) :: :ok - def users(channel) do - to_atom(channel) |> GenServer.call(:users) - end - - @doc """ - Get config for a channel - - ## Examples - iex> Blur.Channel.config? "#rockerboo" - %{} - """ - @spec config?(channel :: binary) :: nil | map - def config?(channel) do - to_atom(channel) |> GenServer.call(:config?) - end - - @doc """ - Get commands for a channel - - ## Examples - iex> Blur.Channel.commands "#rockerboo" - %{} - """ - @spec commands(channel :: binary) :: list - def commands(channel) do - to_atom(channel) |> GenServer.call(:commands) - end - - @doc """ - Get aliases for a channel - - ## Examples - iex> Blur.Channel.aliases "#rockerboo" - %{} - """ - @spec aliases(channel :: binary) :: list - def aliases(channel) do - to_atom(channel) |> GenServer.call(:aliases) - end - - @doc """ - Handle channel info - """ - @impl true - @spec handle_call(:aliases, pid, %State{}) :: {:reply, map, %State{}} - def handle_call(:aliases, _from, state), - do: {:reply, Config.get("#{state.channel}-aliases"), state} - - @spec handle_call(:commands, pid, %State{}) :: {:reply, map, %State{}} - def handle_call(:commands, _from, state), - do: {:reply, Config.get("#{state.channel}-commands"), state} - - @spec handle_call(:config?, pid, %State{}) :: {:reply, nil | map, %State{}} - def handle_call(:config?, _from, state), - do: {:reply, state.config?, state} - - @spec handle_call(:users, pid, %State{}) :: {:reply, list, %State{}} - def handle_call(:users, _from, state) do - {:reply, Blur.Channel.Users.list(state.channel), state} - end - - @doc """ - Handle channel users - """ - @impl true - @spec handle_cast({:add_user, user :: binary}, %State{}) :: {:reply, list, %State{}} - def handle_cast({:add_user, user}, state) do - Blur.Channel.Users.add(state.channel, user) - - {:noreply, state} - end - - def handle_cast({:remove_user, user}, state) do - Blur.Channel.Users.remove(state.channel, user) - - {:noreply, state} - end -end diff --git a/lib/channel/channel.ex b/lib/channel/channel.ex new file mode 100644 index 0000000..e95b6fe --- /dev/null +++ b/lib/channel/channel.ex @@ -0,0 +1,69 @@ +defmodule Blur.Channel do + @moduledoc """ + Channel module + """ + defmodule State do + defstruct channel: "", client: nil, config?: nil, users: nil + end + + use GenServer + require Logger + + alias Blur.Users + alias Blur.User + alias Blur.Channel.Config + + @spec start_link(channel :: binary, users :: list) :: GenServer.on_start() + def start_link(channel, users \\ []) do + GenServer.start_link(__MODULE__, {channel, users}) + end + + @impl GenServer + def init({channel, users}) do + config? = Config.load_from_file(channel, "config") + + {:ok, users} = Blur.Users.start_link(users) + + {:ok, %State{channel: channel, users: users, config?: config?}} + end + + @doc """ + Get users process is from channel process + """ + @spec get_users_pid(pid :: GenServer.server()) :: GenServer.server() + def get_users_pid(pid), + do: GenServer.call(pid, :users) + + @doc """ + Add user to channel + """ + @spec add_user(pid :: GenServer.server(), user :: %User{}) :: :ok + def add_user(pid, user), + do: get_users_pid(pid) |> Users.add_user(user) + + @doc """ + Remove user from channel + """ + @spec remove_user(pid :: GenServer.server(), user :: %User{}) :: :ok + def remove_user(pid, user), + do: get_users_pid(pid) |> Users.remove_user(user) + + @doc """ + Get users in channel + """ + @spec users(pid :: GenServer.server()) :: list | {:error, atom} + def users(pid), do: get_users_pid(pid) |> Users.users() + + @doc """ + Find user in channel + """ + @spec find_user(pid :: GenServer.server(), name :: %User{}) :: %User{} + def find_user(pid, name), do: get_users_pid(pid) |> Users.find_user(name) + + # Handlers + # -=-=-=-=-=-=-=-=-= + + @impl GenServer + def handle_call(:users, _from, state), + do: {:reply, state.users, state} +end diff --git a/lib/channel/channels.ex b/lib/channel/channels.ex new file mode 100644 index 0000000..e7005fa --- /dev/null +++ b/lib/channel/channels.ex @@ -0,0 +1,68 @@ +defmodule Blur.Channels do + use GenServer + + def start_link([]) do + GenServer.start_link(__MODULE__, :ok) + end + + @impl GenServer + def init(:ok) do + {:ok, []} + end + + @doc """ + Add channel + """ + @spec add(pid, channel :: binary) :: :ok + def add(pid, channel) do + GenServer.cast(pid, {:add, channel}) + end + + @doc """ + Delete channel + """ + @spec delete(pid, channel :: binary) :: :ok + def delete(pid, channel) do + GenServer.cast(pid, {:delete, channel}) + end + + @doc """ + Find channel + """ + @spec find(pid, channel :: binary) :: [{binary, GenServer.server()}] + def find(pid, channel) do + GenServer.call(pid, {:find, channel}) + end + + @doc """ + Get the list of channels, with their process + """ + @spec channels(pid :: GenServer.server()) :: [{binary, GenServer.server()}] + def channels(pid), do: GenServer.call(pid, {:channels}) + + @impl GenServer + @spec handle_cast( + {:add, channel :: binary} + | {:delete, channel :: binary}, + [{binary, GenServer.server()}] + ) :: + {:noreply, [{binary, GenServer.server()}]} + def handle_cast({:add, channel}, state) do + {:ok, pid} = Blur.Channel.start_link(channel) + {:noreply, state ++ [{channel, pid}]} + end + + def handle_cast({:delete, channel}, state) do + {:noreply, Enum.filter(state, fn {chan, _} -> chan !== channel end)} + end + + @impl GenServer + @spec handle_call({:find, channel :: charlist} | {:channels}, from :: tuple, state :: list) :: + {:reply, [{binary, GenServer.server()}] | {binary, GenServer.server()}, + :error | {:ok, pid}} + def handle_call({:find, channel}, _from, state) do + {:reply, Enum.find(state, fn {chan, _} -> chan === channel end), state} + end + + def handle_call({:channels}, _from, state), do: {:reply, state, state} +end diff --git a/lib/channel/config.ex b/lib/channel/config.ex new file mode 100644 index 0000000..6bb041f --- /dev/null +++ b/lib/channel/config.ex @@ -0,0 +1,24 @@ +defmodule Blur.Channel.Config do + @moduledoc """ + + Config for the channel + - Loads from a data file in data/#channel/x.json + """ + + require Logger + + @doc """ + Load config from a json file. + """ + @spec load_from_file(channel :: binary, type :: binary) :: Poison.Parser.t() | no_return | nil + def load_from_file("#" <> channel, type), + do: load_from_file(channel, type) + + def load_from_file(channel, type) do + case File.read("data/#{channel}/#{type}.json") do + {:ok, body} -> body |> Poison.decode!() + {:error, :enoent} -> %{} + {:error, error} -> IO.inspect(error) + end + end +end diff --git a/lib/channel_config.ex b/lib/channel_config.ex deleted file mode 100644 index a8b6f07..0000000 --- a/lib/channel_config.ex +++ /dev/null @@ -1,57 +0,0 @@ -defmodule Blur.Channel.Config do - @moduledoc """ - - Config for the channel - - Loads from a data file in blur/data/#channel/x.json - """ - - require Logger - - def load_channel_config(channel) do - load(channel, ["config", "commands", "aliases"]) - - # check if commands were loaded for channel - if config_loaded?(channel) do - Logger.info("Loaded config for #{channel}") - true - else - false - end - end - - def load(_channel, []), do: :ok - - def load(channel, [type | remaining]) do - channel |> load_from_json(type) |> save(channel, type) - - load(channel, remaining) - end - - def load_from_json("#" <> channel, type) do - case File.read("data/#{channel}/#{type}.json") do - {:ok, body} -> body |> Poison.decode!() - {:error, :enoent} -> nil - end - end - - def get(key) do - case ConCache.get(:channels_config, key) do - nil -> nil - value -> value |> Poison.decode!() - end - end - - def save(data, channel, type) do - if data != nil do - put(:channels_config, "#{channel}-#{type}", data |> Poison.encode!()) - end - end - - def put(table, key, value) do - ConCache.put(table, key, value) - end - - def config_loaded?(channel) do - ConCache.ets(:channels_config) |> :ets.member("#{channel}-config") - end -end diff --git a/lib/command.ex b/lib/command.ex deleted file mode 100644 index 9c485c2..0000000 --- a/lib/command.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule Blur.Command do - @moduledoc """ - - Processor of commands (right now) - """ - - require Logger - - def run(_msg, _user, _channel) do - :ok - end -end diff --git a/lib/handlers/IRC/app.ex b/lib/handlers/IRC/app.ex deleted file mode 100644 index 6a63d75..0000000 --- a/lib/handlers/IRC/app.ex +++ /dev/null @@ -1,47 +0,0 @@ -defmodule Blur.IRC.App do - @moduledoc """ - Blur IRC App Supervisor - - Children - * ConCache - * Blur.IRC.Connection - * Blur.IRC.Login - """ - - use Supervisor - require Logger - alias ExIRC.Client - - @spec start(atom, list) :: GenServer.on_start - def start(_type, opts) do - Supervisor.start_link(__MODULE__, :ok, opts) - end - - @impl true - @spec init(:ok) :: {:ok, tuple} - def init(:ok) do - {:ok, irc_client} = Client.start_link() - - # Register :irc_client for easy access for commands. Better idea? - Process.register(irc_client, :irc_client) - - children = [ - worker(ConCache, [[], [name: :channels_config]]), - {Blur.IRC.Connection, [irc_client]}, - {Blur.IRC.Login, [irc_client, ["#rockerboo", "#firstcrimson"]]} - ] - - Supervisor.init(children, strategy: :one_for_one) - end - - def terminate(_reason, irc_client) do - # Quit the channel and close the underlying client - # connection when the process is terminating - Blur.IRC.quit(irc_client) - Blur.IRC.stop!(irc_client) - - Logger.info("Closed IRC connection.") - - :ok - end -end diff --git a/lib/handlers/IRC/channel.ex b/lib/handlers/IRC/channel.ex index 5e680f6..418fb60 100644 --- a/lib/handlers/IRC/channel.ex +++ b/lib/handlers/IRC/channel.ex @@ -6,12 +6,12 @@ defmodule Blur.IRC.Channel do require Logger alias ExIRC.Client - @spec start_link(client :: pid) :: GenServer.on_start() - def start_link(client) do + @spec start_link([client :: pid]) :: GenServer.on_start() + def start_link([client]) do GenServer.start_link(__MODULE__, client) end - @impl true + @impl GenServer @spec init(client :: pid) :: {:ok, pid} def init(client) do Client.add_handler(client, self()) @@ -21,40 +21,31 @@ defmodule Blur.IRC.Channel do @doc """ Handle channel messages """ - @impl true - @spec handle_info({:joined, channel :: charlist}, client :: pid) :: {:noreply, pid} + @impl GenServer + @spec handle_info( + {:joined, channel :: charlist} + | {:joined, channel :: charlist, sender :: %ExIRC.SenderInfo{}} + | {:logon, charlist, nick :: charlist, charlist, charlist} + | {:kicked, sender :: %ExIRC.SenderInfo{}, channel :: charlist}, + client :: pid + ) :: {:noreply, pid} def handle_info({:joined, channel}, client) do Logger.debug("Joined #{channel}") - {:ok, _} = Blur.Channel.start_link(client, channel) - {:noreply, client} end - @impl true - @spec handle_info({:joined, channel :: charlist, sender :: %ExIRC.SenderInfo{}}, pid) :: - {:noreply, pid} def handle_info({:joined, _channel, _sender}, client) do {:noreply, client} end - @spec handle_info({:logon, charlist, nick :: charlist, charlist, charlist}, state :: pid) :: - {:noreply, pid} - def handle_info({:logon, _, _nick, _, _}, state) do - Logger.debug("Drop logon message") - {:noreply, state} - end - - @spec handle_info({:kicked, sender :: %ExIRC.SenderInfo{}, channel :: charlist}, state :: pid) :: - {:noreply, pid} - def handle_info({:kicked, sender, channel}, state) do - by = sender.nick - Logger.debug("We were kicked from #{channel} by #{by}") - {:noreply, state} - end - # Drops unknown messages def handle_info(_msg, state) do {:noreply, state} end + + @impl true + def terminate(_reason, _state) do + :ok + end end diff --git a/lib/handlers/IRC/connection.ex b/lib/handlers/IRC/connection.ex index 340fd6d..de1b11f 100644 --- a/lib/handlers/IRC/connection.ex +++ b/lib/handlers/IRC/connection.ex @@ -7,9 +7,22 @@ defmodule Blur.IRC.Connection do use GenServer alias Blur.IRC.Connection.State - @spec start_link([client :: pid]) :: GenServer.on_start() - def start_link([client]) do - GenServer.start_link(__MODULE__, %State{client: client}) + defmodule State do + @moduledoc """ + IRC connection state + """ + defstruct host: "irc.twitch.tv", + port: 6667, + nick: "", + user: "", + name: "", + debug?: true, + client: nil + end + + @spec start_link(list) :: GenServer.on_start() + def start_link([client, user]) do + GenServer.start_link(__MODULE__, %State{client: client, user: user, nick: user, name: user}) end @impl true @@ -21,33 +34,41 @@ defmodule Blur.IRC.Connection do {:ok, state} end + def login(client, nick, token) do + # Login to IRC + case Blur.IRC.login(client, nick, token) do + {:error, :not_logged_in} -> + Logger.info("Logging in with #{nick} failed.") + + :ok -> + :ok + end + end + @impl true @spec handle_info( - {:connected, server :: binary, port :: non_neg_integer()}, + {:connected, server :: binary, port :: non_neg_integer()} + | {:disconnected} + | {:disconnected, cmd :: binary, msg :: binary}, %State{} ) :: {:noreply, %State{}} def handle_info({:connected, server, port}, state) do Logger.debug("Connected to #{server}:#{port}") - nick = Blur.Env.fetch!(:username) - - # Login to IRC - :ok = Blur.IRC.login(state.client, nick, Blur.token()) - + :ok = login(state.client, state.user, Blur.token()) {:noreply, state} end - @spec handle_info({:disconnected}, %State{}) :: - {:noreply, %State{}} + # Handle disconnection def handle_info({:disconnected}, state) do Logger.debug(":disconnected") {:noreply, state} end - @spec handle_info({:disconnected, cmd :: binary, msg :: binary}, %State{}) :: - {:noreply, %State{}} + # Handle tagged message disconnect def handle_info({:disconnected, "@" <> _cmd, _msg}, state) do + Logger.debug(":disconnected") {:noreply, state} end diff --git a/lib/handlers/IRC/connection_state.ex b/lib/handlers/IRC/connection_state.ex deleted file mode 100644 index 69119d4..0000000 --- a/lib/handlers/IRC/connection_state.ex +++ /dev/null @@ -1,13 +0,0 @@ -defmodule Blur.IRC.Connection.State do - @moduledoc """ - IRC connection state - """ - - defstruct host: "irc.twitch.tv", - port: 6667, - nick: "", - user: "", - name: "", - debug?: true, - client: nil -end diff --git a/lib/handlers/IRC/irc.ex b/lib/handlers/IRC/irc.ex index 7ba6090..53ec0b0 100644 --- a/lib/handlers/IRC/irc.ex +++ b/lib/handlers/IRC/irc.ex @@ -11,10 +11,10 @@ defmodule Blur.IRC do Request the CAP (capability) on the server ## Example - iex> Blur.IRC.cap_request client, ':twitch.tv/membership' + Blur.IRC.cap_request client, ':twitch.tv/membership' :ok """ - @spec cap_request(client :: pid, cap :: binary) :: :ok | {:error, atom} + @spec cap_request(client :: pid | atom, cap :: binary) :: :ok | {:error, atom} def cap_request(client, cap) do ExIRC.Client.cmd(client, "CAP REQ #{cap}") end @@ -23,10 +23,10 @@ defmodule Blur.IRC do Request twitch for capabilities ## Example - iex> Blur.IRC.request_twitch_capabilities client + Blur.IRC.request_twitch_capabilities client :ok """ - @spec request_twitch_capabilities(client :: pid) :: :ok | {:error, atom} + @spec request_twitch_capabilities(client :: pid | atom) :: :ok | {:error, atom} def request_twitch_capabilities(client) do # Request capabilities before joining the channel cap_request(client, ":twitch.tv/membership") @@ -51,45 +51,32 @@ defmodule Blur.IRC do Connect to the IRC server. ## Example - iex> Blur.IRC.connect client, "irc.twitch.tv", 6667 + Blur.IRC.connect client, "irc.twitch.tv", 6667 :ok """ - @spec connect!(client :: pid, host :: binary, port :: non_neg_integer) :: :ok + @spec connect!(client :: pid | atom, host :: binary, port :: non_neg_integer) :: :ok def connect!(client, host, port), do: ExIRC.Client.connect!(client, host, port) - @doc """ - Connect to the default IRC server. - - ## Example - iex> Blur.IRC.connect client - :ok - """ - @spec connect!(client :: pid) :: :ok - def connect!(client) do - %{host: host, port: port} = %Blur.IRC.Connection.State{} - - ExIRC.Client.connect!(client, host, port) - end - @doc """ Login to the server. ## Example - iex> Blur.IRC.login client, "rockerBOO", "oauth:oauthhashherewithlettersandnumbers" + Blur.IRC.login client, "rockerBOO", "oauth:oauthhashherewithlettersandnumbers" :ok """ - @spec login(client :: pid, nick :: binary, pass :: binary) :: :ok | {:error, :not_connected} + @spec login(client :: pid | atom, nick :: binary, pass :: binary) :: + :ok | {:error, :not_connected} def login(client, nick, pass), do: ExIRC.Client.logon(client, pass, nick, nick, nick) @doc """ Join many channels. ## Example - iex> Blur.IRC.join_many client, ["#rockerboo", "#adattape"] + Blur.IRC.join_many client, ["#rockerboo", "#adattape"] :ok """ - @spec join_many(client :: pid, list) :: :ok | {:error, atom} + @spec join_many(client :: pid | atom | atom, list) :: :ok | {:error, atom} def join_many(client, channels) do channels |> Enum.each(&join(client, &1)) end @@ -98,10 +85,10 @@ defmodule Blur.IRC do Join an IRC channel. ## Example - iex> Blur.IRC.join client, "#rockerboo" + Blur.IRC.join client, "#rockerboo" :ok """ - @spec join(client :: pid, channel :: binary) :: :ok | {:error, atom} + @spec join(client :: pid | atom, channel :: binary) :: :ok | {:error, atom} def join(client, "#" <> _ = channel) do Logger.debug("Join #{channel}") ExIRC.Client.join(client, channel) @@ -114,10 +101,10 @@ defmodule Blur.IRC do Part from IRC channel. ## Example - iex> Blur.IRC.part client, "#rockerboo" + Blur.IRC.part client, "#rockerboo" :ok """ - @spec part(client :: pid, channel :: binary) :: :ok | {:error, atom} + @spec part(client :: pid | atom, channel :: binary) :: :ok | {:error, atom} def part(client, "#" <> _ = channel) do Logger.debug("Part #{channel}") ExIRC.Client.part(client, channel) @@ -130,10 +117,10 @@ defmodule Blur.IRC do Send a message to the channel ## Example - iex> Blur.IRC.say client, "#rockerboo", "Hello" + Blur.IRC.say client, "#rockerboo", "Hello" :ok """ - @spec say(client :: pid, channel :: binary, msg :: binary) :: :ok | {:error, atom} + @spec say(client :: pid | atom, channel :: binary, msg :: binary) :: :ok | {:error, atom} def say(client, "#" <> _ = channel, msg) do ExIRC.Client.msg(client, :privmsg, channel, msg) end @@ -142,10 +129,10 @@ defmodule Blur.IRC do Quit the IRC server. ## Example - iex> Blur.IRC.quit client, "Goodbye!" + Blur.IRC.quit client, "Goodbye!" :ok """ - @spec quit(client :: pid, msg :: nil | binary) :: :ok | {:error, atom} + @spec quit(client :: pid | atom, msg :: nil | binary) :: :ok | {:error, atom} def quit(client, msg) do ExIRC.Client.quit(client, msg) end @@ -154,10 +141,10 @@ defmodule Blur.IRC do Quit the IRC server with no message. ## Example - iex> Blur.IRC.quit client + Blur.IRC.quit client :ok """ - @spec quit(client :: pid) :: :ok | {:error, atom} + @spec quit(client :: pid | atom) :: :ok | {:error, atom} def quit(client) do quit(client, nil) end @@ -166,9 +153,10 @@ defmodule Blur.IRC do Stop the IRC client process ## Example - iex> Blur.IRC.stop! client + Blur.IRC.stop! client {:stop, :normal, :ok, %ExIRC.Client{}} """ + @spec stop!(client :: pid | atom) :: {:stop, :normal, :ok, %ExIRC.Client.ClientState{}} def stop!(client) do ExIRC.Client.stop!(client) end diff --git a/lib/handlers/IRC/login.ex b/lib/handlers/IRC/login.ex index 978c071..1a8f2ce 100644 --- a/lib/handlers/IRC/login.ex +++ b/lib/handlers/IRC/login.ex @@ -16,32 +16,39 @@ defmodule Blur.IRC.Login do end @impl true - @spec init(list) :: {:ok, list} + # @spec init([pid, list]) :: {:ok, GenServer.server()} def init([client, channels]) do Client.add_handler(client, self()) - {:ok, [client, channels]} + {:ok, %{client: client, autojoin: channels}} + end + + @spec autojoin(client :: GenServer.server(), channels :: list) :: :ok | {:error, atom} + def autojoin(client, channels) do + client |> Blur.IRC.join_many(channels) end @doc """ - Handle user login + Handle login messages """ @impl true - @spec handle_info(:logged_in, list) :: {:noreply, list} - def handle_info(:logged_in, [client, channels]) do + @spec handle_info(:logged_in, map) :: {:noreply, list} + def handle_info(:logged_in, state) do Logger.debug("Logged in as #{Blur.Env.fetch(:username)}") - Blur.IRC.request_twitch_capabilities(client) + # Request Twitch Capabilities (tags) + case Blur.IRC.request_twitch_capabilities(state.client) do + {:error, _} -> Logger.error("Not connected to Twitch") + :ok -> :ok + end - Logger.debug("Joining channels #{Enum.join(channels, ", ")}") - Blur.IRC.join_many(client, channels) + Logger.debug("Joining channels [#{Enum.join(state.autojoin, ", ")}]") - Logger.debug("Start channels ...") - Blur.IRC.Channel.start_link(client) + case autojoin(state.client, state.autojoin) do + {:error, :not_connected} -> Logger.error("Not connected to Twitch IRC.") + :ok -> :ok + end - Logger.debug("Start messages ...") - Blur.IRC.Message.start_link(client) - - {:noreply, [client, channels]} + {:noreply, state} end # Drops unknown messages diff --git a/lib/handlers/IRC/message.ex b/lib/handlers/IRC/message.ex index d6b19b1..8e47074 100644 --- a/lib/handlers/IRC/message.ex +++ b/lib/handlers/IRC/message.ex @@ -6,112 +6,76 @@ defmodule Blur.IRC.Message do require Logger use GenServer - alias Blur.Command - alias Blur.Parser.Message - alias Blur.Channel alias ExIRC.Client @doc """ Start message handler. """ - @spec start_link(client :: pid) :: GenServer.on_start() - def start_link(client) do + @spec start_link([client :: GenServer.server()]) :: GenServer.on_start() + def start_link([client]) do GenServer.start_link(__MODULE__, client) end - @impl true - @spec init(client :: pid) :: {:ok, pid} + @impl GenServer + @spec init(client :: GenServer.server()) :: {:ok, pid | atom} def init(client) do Client.add_handler(client, self()) - Logger.debug("Receiving messages") {:ok, client} end - @spec parse_message(channel :: binary, user :: charlist, msg :: charlist) :: nil | :ok + @spec parse_message(channel :: binary, user :: binary, msg :: binary) :: :ok def parse_message(channel, user, msg) do - # Logger.debug "#{channel} #{user}: #{msg}" - - if Channel.config?(channel) do - Message.parse(msg, user, channel) - |> Command.run(user, channel) - |> case do - :ok -> Logger.debug("Command send properly") - end - end - end - - @doc """ - Parse out message from tagged message. - """ - @spec parse(%ExIRC.Message{}) :: %ExIRC.Message{} - def parse(irc_message) do - [_server, cmd, channel | msg] = Enum.at(irc_message.args, 0) |> String.split(" ") - - message = Enum.join(msg, " ") - - %ExIRC.Message{ - irc_message - | args: [channel, String.slice(message, 1, String.length(message))], - cmd: cmd - } + Logger.debug("Parsing message #{channel} #{user}: #{msg}") + :ok end @doc """ Handle messages from IRC connection. """ - @impl true - @spec handle_info({:received, message :: charlist, %ExIRC.SenderInfo{}}, state :: pid) :: + @impl GenServer + @spec handle_info( + {:received, message :: charlist, sender :: %ExIRC.SenderInfo{}} + | {:received, message :: charlist, sender :: %ExIRC.SenderInfo{}, channel :: charlist} + | {:mentioned, message :: charlist, sender :: %ExIRC.SenderInfo{}, channel :: charlist} + | {:unrecognized, code :: charlist, message :: %ExIRC.Message{}}, + state :: pid + ) :: {:noreply, pid} + + # Private messages. (Unsure if used by Twitch) def handle_info({:received, message, sender}, state) do from = sender.nick Logger.debug("#{from} sent us a private message: #{message}") {:noreply, state} end - @spec handle_info( - {:received, message :: charlist, %ExIRC.SenderInfo{}, channel :: charlist}, - pid - ) :: {:noreply, pid} + # Handle received messages. Tagged messages don't land here current. def handle_info({:received, message, sender, channel}, state) do from = sender.nick Logger.debug("#{channel} #{from}: #{message}") {:noreply, state} end - @spec handle_info( - {:mentioned, message :: charlist, sender :: %ExIRC.SenderInfo{}, channel :: charlist}, - pid - ) :: {:noreply, pid} - def handle_info({:mentioned, message, sender, channel}, state) do - from = sender.nick - Logger.debug("#{from} mentioned us in #{channel}: #{message}") - {:noreply, state} - end - # Uncaught end names list # {:unrecognized, "366", %ExIRC.Message{args: ["800807", "#rockerboo", "End of /NAMES list"], cmd: "366", ctcp: false, host: [], nick: [], server: "800807.tmi.twitch.tv", user: []}} - @spec handle_info({:unrecognized, code :: charlist, %ExIRC.Message{}}, pid) :: {:noreply, pid} def handle_info({:unrecognized, "366", _}, state) do {:noreply, state} end # CAP reply - @spec handle_info({:unrecognized, code :: charlist, %ExIRC.Message{}}, pid) :: {:noreply, pid} def handle_info({:unrecognized, "CAP", _}, state) do {:noreply, state} end # Tagged message - @spec handle_info({:unrecognized, code :: charlist, msg :: %ExIRC.Message{}}, pid) :: - {:noreply, pid} - def handle_info({:unrecognized, "@" <> cmd, msg}, state) do - opts = - Blur.Parser.Twitch.parse(cmd) - |> Enum.reduce(%{}, fn [k, v], acc -> Map.put(acc, k, v) end) - - %{args: [channel, message]} = parse(msg) + def handle_info({:unrecognized, "@" <> _, msg}, state) do + case Blur.IRC.TwitchTag.parse_tagged_message(msg) do + %{args: [channel, message], cmd: "PRIVMSG", user: user} -> + parse_message(channel, user, message) - Logger.debug("#{channel} #{opts["display-name"]}: #{message}") + _ -> + nil + end {:noreply, state} end @@ -120,4 +84,11 @@ defmodule Blur.IRC.Message do def handle_info(_info, state) do {:noreply, state} end + + @impl GenServer + def terminate(reason, _state) do + IO.inspect("terminate #{__MODULE__}") + IO.inspect(reason) + :ok + end end diff --git a/lib/handlers/IRC/twitch_tag.ex b/lib/handlers/IRC/twitch_tag.ex new file mode 100644 index 0000000..81e0d39 --- /dev/null +++ b/lib/handlers/IRC/twitch_tag.ex @@ -0,0 +1,97 @@ +defmodule Blur.IRC.TwitchTag do + @moduledoc """ + Handle all the following tags. + https://dev.twitch.tv/docs/irc/tags/ + + # User + display-name: The user’s display name + badge-info: indicate the exact number of months the user has been a subscriber. + badges: Comma-separated list of chat badges and the version of each badge + color: Hexadecimal RGB color code; the empty string if it is never set. + user-id: The user's ID. + + # Messages + bits: (Sent only for Bits messages) The amount of cheer/Bits employed by the user. + emote-sets: A comma-separated list of emotes, belonging to one or more emote sets. + emotes: Information to replace text in the message with emote images. This can be empty. + mod: 1 if the user has a moderator badge; otherwise, 0. + room-id: The channel ID. + tmi-sent-ts: Timestamp when the server received the message. + + # Channel + followers-only: Followers-only mode. If enabled, controls which followers can chat. Valid values: -1 (disabled), 0 (all followers can chat), or a non-negative integer (only users following for at least the specified number of minutes can chat). + r9k: R9K mode. If enabled, messages with more than 9 characters must be unique. Valid values: 0 (disabled) or 1 (enabled). + slow: The number of seconds a chatter without moderator privileges must wait between sending messages. + subs-only: Subscribers-only mode. If enabled, only subscribers and moderators can chat. Valid values: 0 (disabled) or 1 (enabled). + + # User Notice + msg-id: The type of notice (not the ID). Valid values: sub, resub, subgift, anonsubgift, submysterygift, giftpaidupgrade, rewardgift, anongiftpaidupgrade, raid, unraid, ritual, bitsbadgetier. + system-msg: The message printed in chat along with this notice. + + # Message Params + msg-param-cumulative-months (Sent only on sub, resub) The total number of months the user has subscribed. This is the same as msg-param-months but sent for different types of user notices. + msg-param-displayName (Sent only on raid) The display name of the source user raiding this channel. + msg-param-login (Sent on only raid) The name of the source user raiding this channel. + msg-param-months (Sent only on subgift, anonsubgift) The total number of months the user has subscribed. This is the same as msg-param-cumulative-months but sent for different types of user notices. + msg-param-promo-gift-total (Sent only on anongiftpaidupgrade, giftpaidupgrade) The number of gifts the gifter has given during the promo indicated by msg-param-promo-name. + msg-param-promo-name (Sent only on anongiftpaidupgrade, giftpaidupgrade) The subscriptions promo, if any, that is ongoing; e.g. Subtember 2018. + msg-param-recipient-display-name (Sent only on subgift, anonsubgift) The display name of the subscription gift recipient. + msg-param-recipient-id (Sent only on subgift, anonsubgift) The user ID of the subscription gift recipient. + msg-param-recipient-user-name (Sent only on subgift, anonsubgift) The user name of the subscription gift recipient. + msg-param-sender-login (Sent only on giftpaidupgrade) The login of the user who gifted the subscription. + msg-param-sender-name (Sent only on giftpaidupgrade) The display name of the user who gifted the subscription. + msg-param-should-share-streak (Sent only on sub, resub) Boolean indicating whether users want their streaks to be shared. + msg-param-streak-months (Sent only on sub, resub) The number of consecutive months the user has subscribed. This is 0 if msg-param-should-share-streak is 0. + msg-param-sub-plan (Sent only on sub, resub, subgift, anonsubgift) The type of subscription plan being used. Valid values: Prime, 1000, 2000, 3000. 1000, 2000, and 3000 refer to the first, second, and third levels of paid subscriptions, respectively (currently $4.99, $9.99, and $24.99). + msg-param-sub-plan-name (Sent only on sub, resub, subgift, anonsubgift) The display name of the subscription plan. This may be a default name or one created by the channel owner. + msg-param-viewerCount (Sent only on raid) The number of viewers watching the source channel raiding this channel. + msg-param-ritual-name (Sent only on ritual) The name of the ritual this notice is for. Valid value: new_chatter. + msg-param-threshold (Sent only on bitsbadgetier) The tier of the bits badge the user just earned; e.g. 100, 1000, 10000. + """ + + @doc """ + Convert twitch tags to a map. + """ + @spec to_map(cmd :: binary) :: map + def to_map(cmd) do + Blur.Parser.Twitch.parse(cmd) + |> Enum.reduce(%{}, fn [k, v], acc -> Map.put(acc, k, v) end) + end + + @doc """ + Parse server string into parts + + ## Example + iex> Blur.IRC.TwitchTag.parse_server("red_stone_dragon!red_stone_dragon@red_stone_dragon.tmi.twitch.tv") + {"red_stone_dragon","red_stone_dragon","red_stone_dragon.tmi.twitch.tv"} + """ + @spec parse_server(connection_string :: binary) :: {binary, binary, binary} + def parse_server(connection_string) do + case String.split(connection_string, ["!", "@"]) do + [user, nick, server] -> {user, nick, server} + [server] -> {"", "", server} + end + end + + @doc """ + Parse out message from tagged message. + """ + @spec parse_tagged_message(%ExIRC.Message{}) :: %ExIRC.Message{} | nil + def parse_tagged_message(irc_message) do + [connection, cmd, channel | msg] = Enum.at(irc_message.args, 0) |> String.split(" ") + + message = Enum.join(msg, " ") + + case parse_server(connection) do + {user, nick, server} -> + %ExIRC.Message{ + irc_message + | args: [channel, String.slice(message, 1, String.length(message))], + cmd: cmd, + nick: nick, + user: user, + server: server + } + end + end +end diff --git a/lib/handlers/IRC/names.ex b/lib/handlers/IRC/users.ex similarity index 51% rename from lib/handlers/IRC/names.ex rename to lib/handlers/IRC/users.ex index 2556710..deca91d 100644 --- a/lib/handlers/IRC/names.ex +++ b/lib/handlers/IRC/users.ex @@ -16,15 +16,21 @@ defmodule Blur.IRC.Names do GenServer.start_link(__MODULE__, client) end - @impl true + @impl GenServer @spec init(client :: pid) :: {:ok, pid} def init(client) do ExIRC.Client.add_handler(client, self()) {:ok, client} end - @impl true - @spec handle_info({:names_list, channel :: charlist, names :: list}, client :: pid) :: + @impl GenServer + @spec handle_info( + {:names_list, channel :: charlist, names :: charlist} + | {:joined, channel :: charlist, user :: %ExIRC.SenderInfo{}} + | {:parted, channel :: charlist, user :: %ExIRC.SenderInfo{}} + | {:mode, charlist, charlist, user :: %ExIRC.SenderInfo{}}, + client :: pid + ) :: {:noreply, client :: pid} def handle_info({:names_list, channel, names}, state) do String.split(names, " ") @@ -35,29 +41,24 @@ defmodule Blur.IRC.Names do {:noreply, state} end - @spec handle_info({:joined, channel :: charlist, name :: charlist}, client :: pid) :: - {:noreply, client :: pid} - def handle_info({:joined, channel, name}, state) do - Logger.debug("Joined #{channel} #{name}") + def handle_info({:joined, channel, user}, state) do + Logger.debug("#{user.nick} joined #{channel} ") - Channel.add_user(channel, name) + Channel.add_user(channel, user.nick) {:noreply, state} end - @spec handle_info({:parted, channel :: charlist, name :: charlist}, client :: pid) :: - {:noreply, client :: pid} - def handle_info({:parted, channel, name}, state) do - Logger.debug("Parted #{channel} #{name}") + def handle_info({:parted, channel, user}, state) do + Logger.debug("#{user.nick} parted #{channel}") - Channel.remove_user(channel, name) + Channel.remove_user(channel, user.nick) {:noreply, state} end - @spec handle_info({:mode, charlist, charlist}, client :: pid) :: {:noreply, client :: pid} - def handle_info({:mode, channel, op, name}, state) do - Logger.debug("#{channel} #{op} #{name}") + def handle_info({:mode, channel, op, user}, state) do + Logger.debug("#{channel} #{op} #{user.nick}") {:noreply, state} end diff --git a/lib/handlers/WS.7z b/lib/handlers/WS.7z new file mode 100644 index 0000000..e24cfbb Binary files /dev/null and b/lib/handlers/WS.7z differ diff --git a/lib/helpers/env.ex b/lib/helpers/env.ex index 652f9e5..00e1eb4 100644 --- a/lib/helpers/env.ex +++ b/lib/helpers/env.ex @@ -1,22 +1,25 @@ defmodule Blur.Env do @moduledoc """ Helper for getting the environmental variable - """ @doc """ Fetches the key - Blur.Env.fetch!(:chat_key) + + ## Example + + Blur.Env.fetch!(:username) + "800807" """ def fetch!(key) when is_atom(key) do case fetch(key) do - "" -> raise "Environmetal variable not found" + "" -> raise "Environmetal variable not found." value -> value end end @doc """ - DO NOT USE :D, yet. + Get the keys from the system variables """ def fetch(key) when is_atom(key) do case key do @@ -26,16 +29,4 @@ defmodule Blur.Env do _ -> "" end end - - @doc """ - Used on startup to check if twitch environmental - variables are setup. - """ - def validate! do - IO.inspect(System.get_env("TWITCH_USERNAME")) - - if System.get_env("TWITCH_USERNAME") == nil do - raise "Environmetal variables not found" - end - end end diff --git a/lib/parsers/command.ex b/lib/parsers/command.ex index d31522d..73a36c6 100644 --- a/lib/parsers/command.ex +++ b/lib/parsers/command.ex @@ -3,41 +3,6 @@ defmodule Blur.Parser.Command do Parses messages for commands """ - require Logger - - def parse("!" <> command), do: command - def parse(_message), do: nil - - def find(message, _user, channel) do - parse(message) |> translate(channel) - end - - def translate(message, channel) do - message - |> translate_alias(Blur.Channel.aliases(channel)) - |> translate_command(Blur.Channel.commands(channel)) - end - - def translate_alias(message, aliases) do - case alias?(message, aliases) do - true -> alias_to_command(message, aliases) - false -> message - end - end - - def alias?(message, aliases) do - if aliases[message] do - true - else - false - end - end - - def alias_to_command(message, aliases) do - aliases[message] - end - - def translate_command(command, commands) do - commands[command] - end + def command("!" <> command), do: command + def command(_message), do: nil end diff --git a/lib/parsers/message.ex b/lib/parsers/message.ex index 619605a..8278920 100644 --- a/lib/parsers/message.ex +++ b/lib/parsers/message.ex @@ -4,12 +4,4 @@ defmodule Blur.Parser.Message do * Fix flow """ - - alias Blur.Parser.Command - require Logger - - def parse(message, user, channel) do - message - |> Command.find(user, channel) - end end diff --git a/lib/parsers/twitch.ex b/lib/parsers/twitch.ex index 82969e5..f0151be 100644 --- a/lib/parsers/twitch.ex +++ b/lib/parsers/twitch.ex @@ -1,4 +1,14 @@ defmodule Blur.Parser.Twitch do + @moduledoc """ + Parse Twitch messages + """ + + @doc """ + Parse tag messages into parts. + + ## Examples + """ + @spec parse(msg :: binary) :: list def parse(msg) do String.split(msg, ";") |> Enum.map(fn s -> String.split(s, "=") end) end diff --git a/lib/users.ex b/lib/users.ex deleted file mode 100644 index 2c0dbce..0000000 --- a/lib/users.ex +++ /dev/null @@ -1,57 +0,0 @@ -defmodule Blur.Channel.Users do - @moduledoc """ - Manages the lists of users in each channel. - """ - - require Logger - use GenServer - - @spec start_link(channel :: binary) :: GenServer.on_start() - def start_link("#" <> _ = channel) do - GenServer.start_link(__MODULE__, [channel]) - end - - @impl true - @spec init(list) :: {:ok, binary} - def init([channel]) do - table = ets_table(channel) - - table |> :ets.new([:ordered_set, :named_table, :public]) - - {:ok, channel} - end - - @spec ets_table(channel :: binary) :: atom - def ets_table(channel) do - String.to_atom("users" <> channel) - end - - @spec list(channel :: binary) :: list - def list("#" <> _ = channel) do - table = ets_table(channel) - - table - |> :ets.tab2list() - |> Enum.map(fn {name} -> - name - end) - end - - @spec add(channel :: binary, user :: binary) :: boolean | [tuple] - def add("#" <> _ = channel, user) do - Logger.debug("Adding #{channel} #{user}") - - table = ets_table(channel) - - table |> :ets.insert_new({user}) - end - - @spec remove(channel :: binary, user :: binary) :: boolean | term - def remove("#" <> _ = channel, user) do - Logger.debug("Removing #{channel} #{user}") - - table = ets_table(channel) - - table |> :ets.delete(user) - end -end diff --git a/lib/users/user.ex b/lib/users/user.ex new file mode 100644 index 0000000..f3cbf2c --- /dev/null +++ b/lib/users/user.ex @@ -0,0 +1,4 @@ +defmodule Blur.User do + defstruct name: "", + opts: %{} +end diff --git a/lib/users/users.ex b/lib/users/users.ex new file mode 100644 index 0000000..5f1fe46 --- /dev/null +++ b/lib/users/users.ex @@ -0,0 +1,78 @@ +defmodule Blur.Users do + @moduledoc """ + Manages the lists of users in each channel. + """ + + require Logger + use GenServer + alias Blur.User + + @spec start_link(users :: list) :: GenServer.on_start() + def start_link(users \\ []) do + GenServer.start_link(__MODULE__, users) + end + + @impl GenServer + @spec init({GenServer.server(), list}) :: {:ok, list} + def init(users) do + {:ok, users} + end + + @doc """ + Add user + """ + @spec(add_user(pid :: GenServer.server(), user :: %User{}) :: :ok, {:error, atom}) + def add_user(pid, user), + do: GenServer.cast(pid, {:add_user, user}) + + @doc """ + Remove user + """ + @spec(remove_user(pid :: GenServer.server(), user :: %User{}) :: :ok, {:error, atom}) + def remove_user(pid, user), + do: GenServer.cast(pid, {:remove_user, user}) + + @doc """ + Get user + """ + @spec find_user(pid :: GenServer.server(), user :: %User{}) :: %User{} + def find_user(pid, user), + do: GenServer.call(pid, {:find_user, user}) + + @doc """ + Get users + """ + @spec users(pid :: GenServer.server()) :: list | {:error, atom} + def users(pid), + do: GenServer.call(pid, {:users}) + + # Handlers + # -=-=-=-=-=-=-=-=-= + + @impl GenServer + @spec handle_cast( + {:add_user, user :: %User{}} | {:remove_user, user :: %User{}}, + state :: list + ) :: {:noreply, list} + + # Add user to list of users + def handle_cast({:add_user, user}, state), + do: {:noreply, state ++ [user]} + + # Remove user from list of users + def handle_cast({:remove_user, user}, state), + do: {:noreply, Enum.filter(state, fn u -> u !== user end)} + + @impl GenServer + @spec handle_call( + {:find_user, user :: %User{}} | {:users}, + from :: {pid, tag :: term}, + state :: list + ) :: + {:reply, reply :: %User{} | list, new_state :: list} + def handle_call({:find_user, user}, _from, state), + do: {:reply, Enum.find(state, %User{}, fn u -> u.name === user.name end), state} + + def handle_call({:users}, _from, state), + do: {:reply, state, state} +end diff --git a/mix.exs b/mix.exs index 18d9fef..304cb0f 100644 --- a/mix.exs +++ b/mix.exs @@ -1,16 +1,25 @@ defmodule Blur.Mixfile do use Mix.Project + @version "0.2.1" + def project do [ app: :blur, - version: "0.2.1-beta1", + version: @version, elixir: "~> 1.0", build_embedded: Mix.env() == :prod, start_permanent: Mix.env() == :prod, description: description(), package: package(), deps: deps(), + test_coverage: [tool: ExCoveralls], + preferred_cli_env: [ + coveralls: :test, + "coveralls.detail": :test, + "coveralls.post": :test, + "coveralls.html": :test + ], name: "Blur", source_url: "https://github.com/rockerboo/blur", docs: [ @@ -24,18 +33,20 @@ defmodule Blur.Mixfile do end def application do - [applications: [:logger, :exirc, :con_cache, :httpoison], mod: {Blur.IRC.App, []}] + [ + applications: [:logger, :exirc], + mod: {Blur.App, ["800807", ["harbleu"]]} + ] end defp deps do [ {:exirc, "~> 1.1"}, - {:con_cache, "~> 0.9.0"}, - {:amnesia, "~> 0.2.0"}, - {:poison, "~> 1.5"}, - {:httpoison, "~> 0.7.2"}, + {:poison, "~> 3.1"}, + {:excoveralls, "~> 0.13", only: :test}, + {:mix_test_watch, "~> 1.0", only: :dev, runtime: false}, {:ex_doc, "~> 0.22", only: :dev, runtime: false}, - {:dialyxir, "~> 1.0", only: [:dev], runtime: false} + {:dialyxir, "~> 1.0", only: :dev, runtime: false} ] end diff --git a/mix.lock b/mix.lock index 90cb938..20b466f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,26 +1,38 @@ %{ "amnesia": {:hex, :amnesia, "0.2.8", "81199a1c4c8db886cfb8ea159f746d5ffdb188bee96cb944f63bdb4465b09fa0", [:mix], [{:exquisite, "~> 0.1.7", [hex: :exquisite, repo: "hexpm", optional: false]}], "hexpm", "6037898c974457809ffa1f9a74cccab8f48c99b206aee9b3fc7bb3af73b06b14"}, - "con_cache": {:hex, :con_cache, "0.9.0", "a0b8240e1c0b18ac084bf6d4e2ed2e351a8eff5c65e3118e9d3da0cbb4995def", [:mix], [{:exactor, "~> 2.2.0", [hex: :exactor, repo: "hexpm", optional: false]}], "hexpm", "600b122653d7e5f6414bb0728fa6133c656e2d24fad7f0a31bb89c1c70ec68bb"}, + "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, + "con_cache": {:hex, :con_cache, "0.14.0", "863acb90fa08017be3129074993af944cf7a4b6c3ee7c06c5cd0ed6b94fbc223", [:mix], [], "hexpm", "50887a8949377d0b707a3c6653b7610de06074751b52d0f267f52135f391aece"}, "dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"}, "dogma": {:hex, :dogma, "0.1.8", "1ee856ed64f3ad635a4b2127beea427ea1b9bc271b8731679a97938e1827730f", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "3b145724262b12e3d9599c1901cbdf9833f353c03d662db212f15a8698ba5f3c"}, - "earmark": {:hex, :earmark, "1.4.6", "f14260802d8998f30e1654189a0d1699c4aa7d099eb4dad904eb5f41283a70b2", [:mix], [], "hexpm", "c1653a90f63400d029b783a098f4d70a40418ddff14da4cc156a0fee9e72e8d6"}, + "earmark": {:hex, :earmark, "1.4.9", "837e4c1c5302b3135e9955f2bbf52c6c52e950c383983942b68b03909356c0d9", [:mix], [{:earmark_parser, ">= 1.4.9", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "0d72df7d13a3dc8422882bed5263fdec5a773f56f7baeb02379361cb9e5b0d8e"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.9", "819bda2049e6ee1365424e4ced1ba65806eacf0d2867415f19f3f80047f8037b", [:mix], [], "hexpm", "8bf54fddabf2d7e137a0c22660e71b49d5a0a82d1fb05b5af62f2761cd6485c4"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_doc": {:hex, :ex_doc, "0.22.1", "9bb6d51508778193a4ea90fa16eac47f8b67934f33f8271d5e1edec2dc0eee4c", [:mix], [{:earmark, "~> 1.4.0", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "d957de1b75cb9f78d3ee17820733dc4460114d8b1e11f7ee4fd6546e69b1db60"}, "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm", "1222419f706e01bfa1095aec9acf6421367dcfab798a6f67c54cf784733cd6b5"}, + "excoveralls": {:hex, :excoveralls, "0.13.0", "4e1b7cc4e0351d8d16e9be21b0345a7e165798ee5319c7800b9138ce17e0b38e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "fe2a56c8909564e2e6764765878d7d5e141f2af3bc8ff3b018a68ee2a218fced"}, "exirc": {:hex, :exirc, "1.1.0", "212a86124a113d87752ce528d52f8e2e2dab3accada66e5330591b9de2c628f9", [:mix], [], "hexpm", "71d01fd9c3378e3f13acd855188ec2b4f225884d2d33fabdb77b77e34b7f5dc7"}, "exprintf": {:git, "git://github.com/parroty/exprintf.git", "5e71122ca40b0a9e0c8d4f57d64a8004b9d007ef", []}, "exquisite": {:hex, :exquisite, "0.1.10", "e3ca4f8b812696a40a6da3bcd4e9861cef879ee2eb239d5485b0f96885fc9fba", [:mix], [], "hexpm", "0af9319851f1e21dd4adab812c82247f05e11cc19820fa17cc74afd696e2313c"}, - "hackney": {:hex, :hackney, "1.3.2", "43bd07ab88753f5e136e38fddd2a09124bee25733b03361eeb459d0173fc17ab", [:make, :rebar], [{:idna, "~> 1.0.2", [hex: :idna, repo: "hexpm", optional: false]}, {:ssl_verify_hostname, "~> 1.0.5", [hex: :ssl_verify_hostname, repo: "hexpm", optional: false]}], "hexpm", "9b811cff637b29f9c7e2c61abf01986c85cd4f64a9422315fd803993b4e82615"}, - "httpoison": {:hex, :httpoison, "0.7.5", "7f4a1dc1245f6a4a7d944786b75a44c94056bd54830299229e4b57fd75cb9daa", [:mix], [{:hackney, "~> 1.3.1", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a9b32452df3c4671c012953d6bb15e3a52bbb41b618f72cbd464e8c9320847c9"}, - "idna": {:hex, :idna, "1.0.3", "d456a8761cad91c97e9788c27002eb3b773adaf5c893275fc35ba4e3434bbd9b", [:rebar3], [], "hexpm", "357d489a51112db4f216034406834f9172b3c0ff5a12f83fb28b25ca271541d1"}, + "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, + "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, + "httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"}, + "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, + "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, "lastfm": {:git, "https://github.com/rockerBOO/lastfm.git", "7ab13c74f7dc1cc3707cb6127e3096c18ba31f8b", []}, + "logger_file_backend": {:hex, :logger_file_backend, "0.0.11", "3bbc5f31d3669e8d09d7a9443e86056fae7fc18e45c6f748c33b8c79a7e147a1", [:mix], [], "hexpm", "62be826f04644c62b0a2bc98a13e2e7ae52c0a4eda020f4c59d7287356d5e445"}, "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mix_test_watch": {:hex, :mix_test_watch, "1.0.2", "34900184cbbbc6b6ed616ed3a8ea9b791f9fd2088419352a6d3200525637f785", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "47ac558d8b06f684773972c6d04fcc15590abdb97aeb7666da19fcbfdc441a07"}, "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"}, "oauth2": {:hex, :oauth2, "0.1.1"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "plug": {:hex, :plug, "0.13.0"}, - "poison": {:hex, :poison, "1.5.2", "560bdfb7449e3ddd23a096929fb9fc2122f709bcc758b2d5d5a5c7d0ea848910", [:mix], [], "hexpm", "4afc59dcadf71be7edc8b934b39f554ec7b31e2b1b1a4767383a663f86958ce3"}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "rest_twitch": {:git, "git://github.com/rockerBOO/rest_twitch.git", "232b1d00eb74552d0e03d6b497fe86e084739403", []}, "socket": {:hex, :socket, "0.3.13", "98a2ab20ce17f95fb512c5cadddba32b57273e0d2dba2d2e5f976c5969d0c632", [:mix], [], "hexpm", "f82ea9833ef49dde272e6568ab8aac657a636acb4cf44a7de8a935acb8957c2e"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.6", "45866d958d9ae51cfe8fef0050ab8054d25cba23ace43b88046092aa2c714645", [:make], [], "hexpm", "72b2fc8a8e23d77eed4441137fefa491bbf4a6dc52e9c0045f3f8e92e66243b5"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"}, } diff --git a/test/app_test.exs b/test/app_test.exs new file mode 100644 index 0000000..2ef903b --- /dev/null +++ b/test/app_test.exs @@ -0,0 +1,7 @@ +defmodule Blur.AppTest do + # test "that the blur supervisor starts" do + # {:ok, pid} = Blur.App.start(:normal, ["", []]) + + # assert pid + # end +end diff --git a/test/blur_test.exs b/test/blur_test.exs index 4211b37..3525d6e 100644 --- a/test/blur_test.exs +++ b/test/blur_test.exs @@ -1,7 +1,4 @@ -defmodule BlurTest do +defmodule Blur.Test do use ExUnit.Case - - test "the truth" do - assert 1 + 1 == 2 - end + doctest Blur end diff --git a/test/channel/channel_test.exs b/test/channel/channel_test.exs new file mode 100644 index 0000000..de56824 --- /dev/null +++ b/test/channel/channel_test.exs @@ -0,0 +1,5 @@ +defmodule Blur.ChannelTest do + use ExUnit.Case, async: true + + doctest Blur.Channel +end diff --git a/test/channel/channels_test.exs b/test/channel/channels_test.exs new file mode 100644 index 0000000..46bd2d6 --- /dev/null +++ b/test/channel/channels_test.exs @@ -0,0 +1,39 @@ +defmodule Blur.ChannelsTest do + use ExUnit.Case, async: true + alias Blur.Channels + + doctest Blur.Channels + + test "to add channel to channels" do + pid = start_supervised!({Blur.Channels, []}) + + assert :ok === Channels.add(pid, "rockerboo") + end + + test "to delete channel from channels" do + pid = start_supervised!({Blur.Channels, []}) + + assert :ok = Channels.add(pid, "rockerboo") + + assert :ok === Channels.delete(pid, "rockerboo") + end + + test "to find channel in channels" do + pid = start_supervised!({Blur.Channels, []}) + + assert :ok = Channels.add(pid, "rockerboo") + + assert {"rockerboo", _} = Channels.find(pid, "rockerboo") + + assert nil === Channels.find(pid, "adattape") + end + + test "to list all channels" do + pid = start_supervised!({Blur.Channels, []}) + + assert :ok = Channels.add(pid, "rockerboo") + assert :ok = Channels.add(pid, "adattape") + + assert [{"rockerboo", _}, {"adattape", _}] = Channels.channels(pid) + end +end diff --git a/test/channel/config_test.exs b/test/channel/config_test.exs new file mode 100644 index 0000000..f119755 --- /dev/null +++ b/test/channel/config_test.exs @@ -0,0 +1,10 @@ +defmodule Blur.Channel.ConfigTest do + use ExUnit.Case, async: true + + doctest Blur.Channel.Config + alias Blur.Channel.Config + + test "load config from file" do + %{} = Config.load_from_file("#someonewhodoesntexist", "config") + end +end diff --git a/test/irc/channel_test.exs b/test/irc/channel_test.exs new file mode 100644 index 0000000..997009a --- /dev/null +++ b/test/irc/channel_test.exs @@ -0,0 +1,33 @@ +defmodule Blur.IRC.ChannelTest do + use ExUnit.Case, async: true + doctest Blur.IRC.Channel + + test "on joining the IRC channel" do + irc_client = start_supervised!({ExIRC.Client, []}) + + {:noreply, state} = Blur.IRC.Channel.handle_info({:joined, "rockerboo"}, irc_client) + + assert irc_client === state + end + + test "on someone joining the IRC channel" do + irc_client = start_supervised!({ExIRC.Client, []}) + + {:noreply, state} = + Blur.IRC.Channel.handle_info({:joined, "rockerboo", %ExIRC.SenderInfo{}}, irc_client) + + assert irc_client === state + end + + test "to start and exit IRC channel process" do + irc_client = start_supervised!({ExIRC.Client, []}) + + pid = start_supervised!({Blur.IRC.Channel, [irc_client]}) + + Process.exit(pid, :kill) + + Process.sleep(1) + + refute Process.alive?(pid) + end +end diff --git a/test/irc/connection_test.exs b/test/irc/connection_test.exs new file mode 100644 index 0000000..ff44a46 --- /dev/null +++ b/test/irc/connection_test.exs @@ -0,0 +1,32 @@ +defmodule Blur.IRC.ConnectionTest do + use ExUnit.Case, async: true + doctest Blur.IRC.Connection + + alias Blur.IRC.Connection.State + alias Blur.IRC.Connection + + # test "on connecting to the IRC server" do + # {:noreply, state} = + # Connection.handle_info( + # {:connected, "localhost", 6667}, + # %State{} + # ) + + # assert state === %State{} + # end + + describe "on disconnecting from the channel" do + test "regular IRC" do + {:noreply, state} = Connection.handle_info({:disconnected}, %State{}) + + assert state === %State{} + end + + test "Twitch Tags" do + {:noreply, state} = + Connection.handle_info({:disconnected, "@display-name=rockerBOO"}, %State{}) + + assert state === %State{} + end + end +end diff --git a/test/irc/irc_test.exs b/test/irc/irc_test.exs new file mode 100644 index 0000000..28f0cb6 --- /dev/null +++ b/test/irc/irc_test.exs @@ -0,0 +1,4 @@ +defmodule Blur.IRCTest do + use ExUnit.Case, async: true + doctest Blur.IRC +end diff --git a/test/irc/login_test.exs b/test/irc/login_test.exs new file mode 100644 index 0000000..dc059bc --- /dev/null +++ b/test/irc/login_test.exs @@ -0,0 +1,16 @@ +defmodule Blur.IRC.LoginTest do + use ExUnit.Case, async: true + doctest Blur.IRC.Login + + test "on logged in" do + irc_client = start_supervised!({ExIRC.Client, []}) + + {:noreply, _} = Blur.IRC.Login.handle_info(:logged_in, %{client: irc_client, autojoin: []}) + end + + test "to autojoin channels in the config" do + irc_client = start_supervised!({ExIRC.Client, []}) + + :ok = Blur.IRC.Login.autojoin(irc_client, ["rockerboo"]) + end +end diff --git a/test/irc/message_test.exs b/test/irc/message_test.exs new file mode 100644 index 0000000..1e84bd2 --- /dev/null +++ b/test/irc/message_test.exs @@ -0,0 +1,73 @@ +defmodule Blur.IRC.MessageTest do + use ExUnit.Case, async: true + doctest Blur.IRC.Message + + alias Blur.IRC.Message + + describe "on receiving an IRC message" do + test "for private message" do + irc_client = start_supervised({ExIRC.Client, []}) + + {:noreply, state} = + Message.handle_info( + {:received, "Private message", %ExIRC.SenderInfo{nick: "rockerboo"}}, + irc_client + ) + + assert state === irc_client + end + + test "for message to channel" do + irc_client = start_supervised({ExIRC.Client, []}) + + {:noreply, state} = + Message.handle_info( + {:received, "Private message", %ExIRC.SenderInfo{}, "rockerboo"}, + irc_client + ) + + assert state === irc_client + end + end + + describe "on unrecognized " do + test "for code 366" do + irc_client = start_supervised({ExIRC.Client, []}) + + {:noreply, state} = + Message.handle_info( + {:unrecognized, "366", %ExIRC.SenderInfo{}}, + irc_client + ) + + assert state === irc_client + end + + test "for code CAP" do + irc_client = start_supervised({ExIRC.Client, []}) + + {:noreply, state} = + Message.handle_info( + {:unrecognized, "CAP", %ExIRC.SenderInfo{}}, + irc_client + ) + + assert state === irc_client + end + + test "for Twitch tags" do + irc_client = start_supervised({ExIRC.Client, []}) + + {:noreply, state} = + Message.handle_info( + {:unrecognized, "display-name=rockerBOO connection cmd channel msg", + %ExIRC.Message{ + args: ["@display-name=rockerBOO;color=434554; connection cmd channel msg"] + }}, + irc_client + ) + + assert state === irc_client + end + end +end diff --git a/test/irc/twitch_tag_test.exs b/test/irc/twitch_tag_test.exs new file mode 100644 index 0000000..168d01b --- /dev/null +++ b/test/irc/twitch_tag_test.exs @@ -0,0 +1,4 @@ +defmodule Blur.IRC.TwitchTagTest do + use ExUnit.Case + doctest Blur.IRC.TwitchTag +end diff --git a/test/parsers/command_test.exs b/test/parsers/command_test.exs new file mode 100644 index 0000000..d12ff2a --- /dev/null +++ b/test/parsers/command_test.exs @@ -0,0 +1,9 @@ +defmodule Blur.Parser.CommandTest do + use ExUnit.Case + doctest Blur.Parser.Command + + test "find command" do + assert Blur.Parser.Command.command("!command") == "command" + assert Blur.Parser.Command.command("command") == nil + end +end diff --git a/test/parsers/message_test.exs b/test/parsers/message_test.exs new file mode 100644 index 0000000..aecea60 --- /dev/null +++ b/test/parsers/message_test.exs @@ -0,0 +1,5 @@ +defmodule Blur.Parser.MessageTest do + use ExUnit.Case + + doctest Blur.Parser.Message +end diff --git a/test/parsers/twitch_test.exs b/test/parsers/twitch_test.exs new file mode 100644 index 0000000..9010b65 --- /dev/null +++ b/test/parsers/twitch_test.exs @@ -0,0 +1,16 @@ +defmodule Blur.Parser.TwitchTest do + use ExUnit.Case + + doctest Blur.Parser.Twitch + alias Blur.Parser.Twitch + + describe "parse twitch tags into parts" do + test "demo data" do + assert [["x", "y"], ["a", "b"]] === Twitch.parse("x=y;a=b") + end + + test "display-name" do + assert [["display-name", "rockerBOO"]] === Twitch.parse("display-name=rockerBOO") + end + end +end diff --git a/test/users/users_test.exs b/test/users/users_test.exs new file mode 100644 index 0000000..f749876 --- /dev/null +++ b/test/users/users_test.exs @@ -0,0 +1,37 @@ +defmodule Blur.UsersTest do + use ExUnit.Case + + doctest Blur.Users + alias Blur.User + + test "Add user" do + pid = start_supervised!({Blur.Channel, []}) + + assert :ok === Blur.Channel.add_user(pid, %User{name: "rockerboo"}) + end + + test "Remove user" do + pid = start_supervised!({Blur.Channel, []}) + + assert :ok === Blur.Channel.remove_user(pid, %User{name: "rockerboo"}) + + assert Blur.Channel.find_user(pid, %User{name: "rockerboo"}) === %User{} + end + + test "find_user" do + pid = start_supervised!({Blur.Channel, []}) + + Blur.Channel.add_user(pid, %User{name: "rockerboo"}) + + assert %User{name: "rockerboo"} === Blur.Channel.find_user(pid, %User{name: "rockerboo"}) + end + + test "users" do + pid = start_supervised!({Blur.Channel, []}) + + Blur.Channel.add_user(pid, %User{name: "rockerboo"}) + Blur.Channel.add_user(pid, %User{name: "adattape"}) + + assert [%User{name: "rockerboo"}, %User{name: "adattape"}] === Blur.Channel.users(pid) + end +end