Skip to content

Commit

Permalink
Initial connection setup
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanwinchester committed Jul 28, 2020
1 parent aa96d3c commit 49f44b8
Show file tree
Hide file tree
Showing 11 changed files with 254 additions and 47 deletions.
13 changes: 13 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# editorconfig.org
root = true

[*]
indent_size = 2
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ erl_crash.dump
# Ignore package tarball (built via "mix hex.build").
tmi-*.tar

secret.exs
31 changes: 24 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
# tmi.ex

**TODO: Add description**
Connect to Twitch chat with Elixir.

Inspired by [tmi.js](https://github.com/tmijs/tmi.js).

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `tmi` to your list of dependencies in `mix.exs`:
The package can be installed by adding `tmi` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:tmi, "~> 0.1.0"}
{:tmi, "~> 0.1.0"},
]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/tmi](https://hexdocs.pm/tmi).
Documentation can be found at [https://hexdocs.pm/tmi/readme.html](https://hexdocs.pm/tmi/readme.html).

## Usage

You can use your own Twitch username, but it is recommended to make a new twitch account just for your bot.
You'll also need an OAuth token for the password.

The simplest method to get an OAuth token (while logged in to the account your bot will be) use the [Twitch Chat OAuth Password Generator](https://twitchapps.com/tmi/).

To connect, start the supervisor with:

```elixir
user = "mybotusername"
pass = "oauth:mybotoauthtoken"
chats = ["mychat", "myfriendschat"]

{:ok, _pid} = TMI.Supervisor.start_link(user, pass, chats)
```

#### TODO: All the typical bot stuff.
17 changes: 1 addition & 16 deletions lib/tmi.ex
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@
defmodule TMI do
@moduledoc """
Documentation for `TMI`.
"""

@doc """
Hello world.
## Examples
iex> TMI.hello()
:world
"""
def hello do
:world
end
@moduledoc false
end
19 changes: 0 additions & 19 deletions lib/tmi/application.ex

This file was deleted.

36 changes: 36 additions & 0 deletions lib/tmi/conn.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
defmodule TMI.Conn do
@moduledoc false

defstruct(
server: "irc.chat.twitch.tv",
port: 6697,
pass: nil,
nick: nil,
user: nil,
name: nil,
client: nil,
channels: []
)

@type t :: %__MODULE__{
server: String.t(),
port: integer,
pass: String.t(),
nick: String.t(),
user: String.t(),
name: String.t(),
client: pid,
channels: [String.t()]
}

def new(client, user, pass, channels) do
%__MODULE__{
client: client,
name: user,
nick: user,
user: user,
pass: pass,
channels: channels
}
end
end
51 changes: 51 additions & 0 deletions lib/tmi/handlers/connection_handler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
defmodule TMI.Handlers.ConnectionHandler do
@moduledoc false
use GenServer

require Logger

alias TMI.Conn

def start_link(%Conn{} = conn) do
GenServer.start_link(__MODULE__, conn, name: __MODULE__)
end

## Callbacks

@impl GenServer
def init(%Conn{} = conn) do
Logger.debug("Connecting to #{conn.server}:#{conn.port}")

ExIRC.Client.add_handler(conn.client, self())
ExIRC.Client.connect_ssl!(conn.client, conn.server, conn.port)

{:ok, conn}
end

@impl GenServer
def handle_info({:connected, server, port}, conn) do
Logger.debug("Connected to #{server}:#{port}. Logging in as #{conn.nick}")
Logger.debug("Logging in as #{conn.nick}..")
ExIRC.Client.logon(conn.client, conn.pass, conn.nick, conn.user, conn.name)
{:noreply, conn}
end

def handle_info(:disconnected, conn) do
Logger.debug("Disconnected from #{conn.server}:#{conn.port}")
{:stop, :normal, conn}
end

# Catch-all for messages you don't care about
def handle_info(_msg, conn) do
{:noreply, conn}
end

@impl GenServer
def terminate(_, conn) do
# Quit the channels and close the underlying client connection when the process is terminating
Logger.warn("Terminating...")
ExIRC.Client.quit(conn.client, "Goodbye, cruel world.")
ExIRC.Client.stop!(conn.client)
:ok
end
end
58 changes: 58 additions & 0 deletions lib/tmi/handlers/login_handler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
defmodule TMI.Handlers.LoginHandler do
@moduledoc false

use GenServer

require Logger

alias TMI.Conn

@tmi_capabilities ['membership', 'tags', 'commands']

def start_link(%Conn{} = conn) do
GenServer.start_link(__MODULE__, conn)
end

## Callbacks

@impl true
def init(%Conn{} = conn) do
ExIRC.Client.add_handler(conn.client, self())
{:ok, conn}
end

@impl true
def handle_info(:logged_in, conn) do
Logger.debug("Logged in to #{conn.server}:#{conn.port}")

Logger.debug("Requesting capabilities...")
Enum.each(@tmi_capabilities, &request_capabilities(conn.client, &1))

Logger.debug("Joining channels...")
Enum.map(conn.channels, &ExIRC.Client.join(conn.client, &1))

{:noreply, conn}
end

# Catch-all for messages you don't care about
def handle_info(msg, conn) do
Logger.debug("Unhandled msg: #{inspect(msg)}")
{:noreply, conn}
end

# Twitch Membership capability
# < CAP REQ :twitch.tv/membership
# > :tmi.twitch.tv CAP * ACK :twitch.tv/membership
#
# Twitch Tags capability
# < CAP REQ :twitch.tv/tags
# > :tmi.twitch.tv CAP * ACK :twitch.tv/tags
#
# Twitch Commands capability
# < CAP REQ :twitch.tv/commands
# > :tmi.twitch.tv CAP * ACK :twitch.tv/commands
defp request_capabilities(client, cap) do
cmd = ExIRC.Commands.command!('CAP REQ :twitch.tv/#{cap}')
:ok = ExIRC.Client.cmd(client, cmd)
end
end
27 changes: 27 additions & 0 deletions lib/tmi/supervisor.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
defmodule TMI.Supervisor do
@moduledoc false

use Supervisor

def start_link(config) do
Supervisor.start_link(__MODULE__, config, name: __MODULE__)
end

@impl true
def init(config) do
user = Keyword.fetch!(config, :user)
pass = Keyword.fetch!(config, :pass)
channels = Keyword.get(config, :channels, [])

{:ok, client} = ExIRC.start_link!()

conn = TMI.Conn.new(client, user, pass, channels)

children = [
{TMI.Handlers.ConnectionHandler, conn},
{TMI.Handlers.LoginHandler, conn}
]

Supervisor.init(children, strategy: :one_for_one)
end
end
40 changes: 35 additions & 5 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,53 @@ defmodule TMI.MixProject do
version: "0.1.0",
elixir: "~> 1.10",
start_permanent: Mix.env() == :prod,
deps: deps()
deps: deps(),

name: "tmi.ex",
source_url: "https://github.com/ryanwinchester/tmi.ex",
homepage_url: "https://github.com/ryanwinchester/tmi.ex",
description: description(),
docs: docs(),
package: package()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {TMI.Application, []}
extra_applications: [:logger]
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
{:exirc, "~> 2.0"},
{:ex_doc, "~> 0.22", only: :dev, runtime: false}
]
end

defp description do
"""
Connect to and interact with Twitch chat from Elixir.
"""
end

defp docs do
[
main: "README",
# logo: "path/to/logo.png",
extras: ["README.md"]
]
end

defp package do
[
name: "tmi",
licenses: ["Apache v2.0"],
links: %{
"GitHub" => "https://github.com/ryanwinchester/tmi.ex"
}
]
end
end
8 changes: 8 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
%{
"earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
"ex_doc": {:hex, :ex_doc, "0.22.2", "03a2a58bdd2ba0d83d004507c4ee113b9c521956938298eba16e55cc4aba4a6c", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "cf60e1b3e2efe317095b6bb79651f83a2c1b3edcb4d319c421d7fcda8b3aff26"},
"exirc": {:hex, :exirc, "2.0.0", "6b3b8da6d56096a2a613f3688ef05ed52d235a95566e889e885409c307b218b6", [:mix], [], "hexpm", "aba949c7e60ce4bbf1954d78ede33825ffe2fa01e840edd843ddc8f3775e7390"},
"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"},
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
}

0 comments on commit 49f44b8

Please sign in to comment.