├── .gitignore ├── LICENSE.md ├── README.md ├── config └── config.exs ├── lib └── hedwig_irc.ex ├── mix.exs ├── mix.lock └── test ├── hedwig_irc_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /doc 3 | /cover 4 | /deps 5 | erl_crash.dump 6 | *.ez 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jeff Weiss 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hedwig IRC Adapter 2 | > An IRC adapter for [Hedwig](https://github.com/hedwig-im/hedwig), based 3 | > on [ExIrc](https://github.com/bitwalker/exirc) 4 | 5 | ## Getting started 6 | 7 | Let's generate a new Elixir application with a supervision tree: 8 | 9 | ``` 10 | λ mix new alfred --sup 11 | * creating README.md 12 | * creating .gitignore 13 | * creating mix.exs 14 | * creating config 15 | * creating config/config.exs 16 | * creating lib 17 | * creating lib/alfred.ex 18 | * creating test 19 | * creating test/test_helper.exs 20 | * creating test/alfred_test.exs 21 | 22 | Your Mix project was created successfully. 23 | You can use "mix" to compile it, test it, and more: 24 | 25 | cd alfred 26 | mix test 27 | 28 | Run "mix help" for more commands. 29 | ``` 30 | 31 | Change into our new application directory: 32 | 33 | ``` 34 | λ cd alfred 35 | ``` 36 | 37 | Add `hedwig_irc` to your list of dependencies in `mix.exs`: 38 | 39 | ```elixir 40 | def deps do 41 | [ 42 | {:hedwig_irc, "~> 0.1.0"} 43 | ] 44 | end 45 | ``` 46 | 47 | Ensure `hedwig_irc` is started before your application: 48 | 49 | ```elixir 50 | def application do 51 | [applications: [:hedwig_irc]] 52 | end 53 | ``` 54 | 55 | ### Generate our robot 56 | 57 | ``` 58 | λ mix deps.get 59 | λ mix hedwig.gen.robot 60 | 61 | Welcome to the Hedwig Robot Generator! 62 | 63 | Let's get started. 64 | 65 | What would you like to name your bot?: alfred 66 | 67 | Available adapters 68 | 69 | 1. Hedwig.Adapters.IRC 70 | 2. Hedwig.Adapters.Console 71 | 3. Hedwig.Adapters.Test 72 | 73 | Please select an adapter: 1 74 | 75 | * creating lib/alfred 76 | * creating lib/alfred/robot.ex 77 | * updating config/config.exs 78 | 79 | Don't forget to add your new robot to your supervision tree 80 | (typically in lib/alfred.ex): 81 | 82 | worker(Alfred.Robot, []) 83 | ``` 84 | 85 | ### Supervise our robot 86 | 87 | We'll want Alfred to be supervised and started when we start our application. 88 | Let's add it to our supervision tree. Open up `lib/alfred.ex` and add the 89 | following to the `children` list: 90 | 91 | ```elixir 92 | worker(Alfred.Robot, []) 93 | ``` 94 | 95 | ### Configuration 96 | 97 | The next thing we need to do is configure our bot for our IRC server. Open 98 | up `config/config.exs` and let's take a look at what was generated for us: 99 | 100 | ```elixir 101 | use Mix.Config 102 | 103 | config :alfred, Alfred.Robot, 104 | adapter: Hedwig.Adapters.IRC, 105 | name: "alfred", 106 | aka: "/", 107 | responders: [ 108 | {Hedwig.Responders.Help, []}, 109 | {Hedwig.Responders.GreatSuccess, []}, 110 | {Hedwig.Responders.ShipIt, []}, 111 | ] 112 | ``` 113 | 114 | So we have the `adapter`, `name`, `aka`, and `responders` set. The `adapter` is 115 | the module responsible for handling all of the IRC details like connecting 116 | and sending and receiving messages over the network. The `name` is the name 117 | that our bot will respond to, and _must not be used by any other account registered 118 | or active on IRC_. The `aka` (also known as) field is optional, but it 119 | allows us to address our bot with an alias. By default, this alias is set to 120 | `/`; _we'll need to change that (since `/` is used by the IRC client), so 121 | we'll use `!` instead_. 122 | 123 | Finally we have `responders`. Responders are modules that provide functions that 124 | match on the messages that get sent to our bot. We'll discuss this further in 125 | a bit. 126 | 127 | We'll need to provide a few more things in order for us to connect to our 128 | IRC server. We'll need to provide our bot's `password`, the IRC `server` as well as 129 | a list of rooms/channels we want our bot to join once connected. 130 | 131 | Let's see what this could look like: 132 | 133 | ```elixir 134 | use Mix.Config 135 | 136 | config :alfred, Alfred.Robot, 137 | adapter: Hedwig.Adapters.IRC, 138 | name: "alfred", 139 | user: "alfred", # optional, defaults to `:name` 140 | full_name: "Alfred Bot", # optional, defaults to `:name` 141 | # we needed to change this, remember? 142 | aka: "!", 143 | # fill in the appropriate password for your bot 144 | password: "password", 145 | server: "chat.freenode.net", 146 | port: 6697, # optional, defaults to `6667` 147 | ssl?: true, # optional, defaults to `false` 148 | rooms: [ 149 | # fill in the appropriate channels for your IRC server 150 | {"#elixir-lang", ""} 151 | {"#private-channel", "password-for-private-channel"} 152 | ], 153 | responders: [ 154 | {Hedwig.Responders.Help, []}, 155 | {Hedwig.Responders.GreatSuccess, []}, 156 | {Hedwig.Responders.ShipIt, []} 157 | ] 158 | ``` 159 | 160 | Great! We're ready to start our bot. From the root of our application, let's run 161 | the following: 162 | 163 | ``` 164 | λ mix run --no-halt 165 | ``` 166 | 167 | This will start our application along with our bot. Our bot should connect to 168 | the server and join the configured room(s). From there, we can connect with our 169 | favourite IRC client and begin sending messages to our bot. 170 | 171 | Since we have the `Help` responder installed, we can say `alfred help` (or the 172 | shorter version using our `aka`, `!help`) and we should see a list of usage for 173 | all of the installed responders. 174 | 175 | ## What's next? 176 | 177 | Well, that's it for now. Make sure to read the [Hedwig Documentation](http://hexdocs.pm/hedwig) for more 178 | details on writing responders and other exciting things! 179 | 180 | ## LICENSE 181 | 182 | Copyright (c) 2016 Jeff Weiss 183 | 184 | Hedwig IRC source code is licensed under the [MIT License](https://github.com/jeffweiss/hedwig_irc/blob/master/LICENSE.md). 185 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :hedwig_irc, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:hedwig_irc, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /lib/hedwig_irc.ex: -------------------------------------------------------------------------------- 1 | defmodule Hedwig.Adapters.IRC do 2 | @moduledoc false 3 | 4 | use Hedwig.Adapter 5 | 6 | require Logger 7 | 8 | alias ExIrc.Client 9 | alias ExIrc.SenderInfo 10 | alias Hedwig.User 11 | alias Hedwig.Message 12 | alias Hedwig.Robot 13 | 14 | def init({robot, opts}) do 15 | Logger.debug "#{inspect(opts)}" 16 | {:ok, client} = ExIrc.start_client! 17 | ExIrc.Client.add_handler client, self() 18 | Kernel.send(self(), :connect) 19 | {:ok, {robot, opts, client}} 20 | end 21 | 22 | def handle_cast({:send, %{text: text, room: channel}}, state = {_robot, _opts, client}) do 23 | for line <- String.split(text, "\n") do 24 | Client.msg client, :privmsg, channel, line 25 | end 26 | {:noreply, state} 27 | end 28 | 29 | def handle_cast({:reply, %{text: text, user: user, room: channel}}, state = {_robot, _opts, client}) do 30 | Client.msg client, :privmsg, channel, user.name <> ": " <> text 31 | {:noreply, state} 32 | end 33 | 34 | def handle_cast({:emote, %{text: text, room: channel}}, state = {_robot, _opts, client}) do 35 | Client.me client, channel, text 36 | {:noreply, state} 37 | end 38 | 39 | def handle_info(:connect, state = {_robot, opts, client}) do 40 | host = Keyword.fetch!(opts, :server) 41 | port = Keyword.get(opts, :port, 6667) 42 | ssl? = Keyword.get(opts, :ssl?, false) 43 | if ssl? do 44 | Client.connect_ssl! client, host, port 45 | else 46 | Client.connect! client, host, port 47 | end 48 | {:noreply, state} 49 | end 50 | 51 | def handle_info({:connected, server, port}, state = {robot, opts, client}) do 52 | Logger.info "Connected to #{server}:#{port}" 53 | pass = Keyword.fetch!(opts, :password) 54 | nick = Keyword.fetch!(opts, :name) 55 | user = Keyword.get(opts, :irc_user, nick) 56 | name = Keyword.get(opts, :full_name, nick) 57 | Client.logon client, pass, nick, user, name 58 | :ok = Robot.handle_connect(robot) 59 | {:noreply, state} 60 | end 61 | 62 | def handle_info(:logged_in, state = {_robot, opts, client}) do 63 | Logger.info "Logged in" 64 | rooms = Keyword.fetch!(opts, :rooms) 65 | for {channel, password} <- rooms do 66 | Client.join client, channel, password 67 | end 68 | {:noreply, state} 69 | end 70 | 71 | def handle_info({:mentioned, _msg, _user, _channel}, state) do 72 | {:noreply, state} 73 | end 74 | 75 | def handle_info({:received, msg, %SenderInfo{} = user, channel}, state = {robot, _opts, _client}) do 76 | incoming_message = %Message{ 77 | ref: make_ref(), 78 | robot: robot, 79 | room: channel, 80 | text: msg, 81 | user: %User{id: "#{user.user}@#{user.host}", name: user.nick}, 82 | type: "groupchat" 83 | } 84 | 85 | Robot.handle_in(robot, incoming_message) 86 | 87 | {:noreply, state} 88 | end 89 | 90 | def handle_info({:quit, message, %{nick: user}}, state) do 91 | Logger.info "#{user} left with message: #{inspect message}" 92 | {:noreply, state} 93 | end 94 | 95 | def handle_info(:disconnected, state = {robot, _opts, _client}) do 96 | Robot.handle_disconnect(robot, nil) 97 | {:noreply, state} 98 | end 99 | 100 | def handle_info(msg, state) do 101 | Logger.debug "Unknown message: #{inspect msg}" 102 | {:noreply, state} 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule HedwigIrc.Mixfile do 2 | use Mix.Project 3 | @description """ 4 | An IRC adapter for Hedwig 5 | """ 6 | 7 | def project do 8 | [ 9 | app: :hedwig_irc, 10 | version: "0.1.4", 11 | elixir: "~> 1.2", 12 | name: "hedwig_irc", 13 | description: @description, 14 | package: package(), 15 | build_embedded: Mix.env == :prod, 16 | start_permanent: Mix.env == :prod, 17 | deps: deps(), 18 | ] 19 | end 20 | 21 | # Configuration for the OTP application 22 | # 23 | # Type "mix help compile.app" for more information 24 | def application do 25 | [applications: [:logger, :hedwig, :exirc]] 26 | end 27 | 28 | # Dependencies can be Hex packages: 29 | # 30 | # {:mydep, "~> 0.3.0"} 31 | # 32 | # Or git/path repositories: 33 | # 34 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 35 | # 36 | # Type "mix help deps" for more examples and options 37 | defp deps do 38 | [ 39 | {:hedwig, "~> 1.0"}, 40 | {:exirc, "~> 1.0"}, 41 | {:ex_doc, "~> 0.18", only: :dev}, 42 | ] 43 | end 44 | 45 | defp package do 46 | [ 47 | maintainers: ["Jeff Weiss"], 48 | licenses: ["MIT"], 49 | links: %{"Github" => "https://github.com/jeffweiss/hedwig_irc"}, 50 | ] 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [], [], "hexpm"}, 2 | "ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "exirc": {:hex, :exirc, "1.0.1", "a1eccd89eea12eb928b7142dba7b6b7766ebca74b92405c37af9a8e7d3bd39a4", [:mix], []}, 4 | "gproc": {:hex, :gproc, "0.5.0", "2df2d886f8f8a7b81a4b04aa17972b5965bbc5bf0100ea6d8e8ac6a0e7389afe", [:rebar], []}, 5 | "hedwig": {:hex, :hedwig, "1.0.0", "effbb160b9b270cb62ac1b2d6041b4ac731a21b0fc048dd2c35840245fdc0f22", [:mix], []}} 6 | -------------------------------------------------------------------------------- /test/hedwig_irc_test.exs: -------------------------------------------------------------------------------- 1 | defmodule HedwigIrcTest do 2 | use ExUnit.Case 3 | doctest Hedwig.Adapters.IRC 4 | 5 | test "the truth" do 6 | assert 1 + 1 == 2 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------