Skip to content

Commit faec21b

Browse files
Update event_sub opts and README
1 parent 0812df0 commit faec21b

3 files changed

Lines changed: 182 additions & 94 deletions

File tree

README.md

Lines changed: 162 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![Hex.pm](https://img.shields.io/hexpm/dt/tmi)](https://hex.pm/packages/tmi)
55
[![Hex.pm](https://img.shields.io/hexpm/l/tmi)](https://github.com/ryanwinchester/tmi.ex/blob/main/LICENSE)
66

7-
Connect to Twitch chat with Elixir.
7+
Connect to Twitch chat and EventSub with Elixir.
88

99
## Installation
1010

@@ -22,133 +22,210 @@ Documentation can be found at [https://hexdocs.pm/tmi/readme.html](https://hexdo
2222

2323
## Usage
2424

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

28-
The simplest method to get an OAuth token (while logged in to the account your bot will use), use the [Twitch Chat OAuth Password Generator](https://twitchapps.com/tmi/).
29+
* The simplest method to get an OAuth token (while logged in to the account your bot will use), use
30+
the [Twitch Chat OAuth Password Generator](https://twitchapps.com/tmi/).
2931

30-
Create a bot module to deal with chat messages or events:
31-
32-
```elixir
33-
defmodule MyBot do
34-
use TMI
35-
36-
alias TMI.Events.Message
37-
38-
@impl true
39-
def handle_event(%Message{message: "!" <> cmd} = event) do
40-
cmd(cmd, event)
41-
end
42-
43-
# ... TODO: match on other you're interested in ...
32+
#### Config options (Chat/TMI)
4433

45-
## Helpers
46-
47-
defp cmd("roll", %Message{channel: channel, display_name: user}),
48-
do: say(channel, "@#{user} rolled a #{Enum.random(1..6)}!")
49-
50-
defp cmd("echo " <> rest, %Message{channel: channel}),
51-
do: say(channel, rest)
34+
* `:bot` - A module that `use`s `TMI` and implements the `TMI.Handler` behaviour.
35+
* `:user` - Twitch username of your bot user (lowercase).
36+
* `:pass` - OAuth token to use as a password, prefixed with `oauth:`.
37+
* `:channels` - The list of channels to join (lowercase).
38+
* `:mod_channels` - The list of channels where your bot is a moderator
39+
(this effects the message and command rate limits).
5240

53-
defp cmd("dance", %Message{channel: channel, display_name: user}),
54-
do: me(channel, "dances for @#{user}")
41+
```elixir
42+
# config/runtime.exs
5543

56-
defp cmd(_command, msg),
57-
do: say(msg.channel, "unrecognized command")
58-
end
44+
config :my_app,
45+
bots: [
46+
[
47+
bot: MyApp.MyBot,
48+
user: "myappbot",
49+
pass: System.fetch_env!("TWITCH_OATH_TOKEN"), # "oauth:myappbotpassword"
50+
channels: ["mychannel", "foo"],
51+
mod_channels: ["mychannel"],
52+
debug: false # defaults to false
53+
]
54+
]
5955
```
6056

61-
## EventSub
57+
### EventSub
6258

63-
https://twitchapps.com/tokengen
59+
* You need to create an app on the [Twitch Developer Console](https://dev.twitch.tv/console/apps/create)
60+
to get the `client_id`. Also, add the redirect URL from the instructions in the token generator
61+
linked below if you use that.
62+
* To get an OAuth token for EventSub, it's easiest of you are logged in as the broadcaster of the
63+
channel you want to use the bot for and then you can use the [Twitch OAuth Token Generator](https://twitchapps.com/tokengen/)
64+
with the `client_id` of the app you created.
6465

65-
#### Available handler callbacks:
66+
For scopes, I just use all the `read` scopes except for `whisper` and `stream_key`. If you want to
67+
do the same, just paste the below into the `scopes` field on the token generator page:
6668

67-
handle_connected(server, port)
68-
handle_logged_in()
69-
handle_login_failed(reason)
70-
handle_disconnected()
71-
handle_join(chat)
72-
handle_join(chat, user)
73-
handle_part(chat)
74-
handle_part(chat, user)
75-
handle_kick(chat, kicker)
76-
handle_kick(chat, user, kicker)
77-
handle_whisper(message, sender)
78-
handle_whisper(message, sender, tags)
79-
handle_message(message, sender, chat)
80-
handle_message(message, sender, chat, tags)
81-
handle_mention(message, sender, chat)
82-
handle_action(message, sender, chat)
83-
handle_unrecognized(msg)
84-
handle_unrecognized(msg, tags)
69+
```
70+
analytics:read:extensions analytics:read:games bits:read channel:read:ads channel:read:charity channel:read:goals channel:read:guest_star channel:read:hype_train channel:read:polls channel:read:predictions channel:read:redemptions channel:read:subscriptions channel:read:vips moderation:read moderator:read:automod_settings moderator:read:blocked_terms moderator:read:chat_settings moderator:read:chatters moderator:read:followers moderator:read:guest_star moderator:read:shield_mode moderator:read:shoutouts user:read:blocked_users user:read:broadcast user:read:email user:read:follows user:read:subscriptions channel:bot chat:read user:bot user:read:chat
71+
```
8572

86-
### Starting
73+
If you want to do moderation things with this token, then you can add the required scopes for
74+
your actions found here [https://dev.twitch.tv/docs/authentication/scopes](https://dev.twitch.tv/docs/authentication/scopes/).
8775

88-
First we need to go over the config options.
76+
#### Config options (EventSub)
8977

90-
#### Config options
78+
* `:user_id` - The twitch user ID of the broadcaster.
79+
* `:handler` - A module that `use`s `TMI` and implements the `TMI.Handler` behaviour.
80+
* `:client_id` - The client ID of the application you used for the token.
81+
* `:access_token` - The OAuth token you generated with the correct scopes for your subscriptions.
82+
* `:keepalive_timeout` - Optional. The keepalive timeout in seconds. Specifying an invalid,
83+
but numeric value will return the nearest acceptable value. Defaults to `10`.
84+
* `:start?` - Optional. A boolean value of whether or not to start the eventsub socket.
85+
Defaults to `false` if there are no `event_sub` config options.
86+
* `:subscriptions` - Optional. The list of subscriptions to create. See below for more info.
87+
Defaults to:
9188

92-
* `:bot` - The module that `use`s `TMI` and implements the `TMI.Handler` behaviour.
93-
* `:user` - Twitch username of your bot user (lowercase).
94-
* `:pass` - OAuth token to use as a password, prefixed with `oauth:`.
95-
* `:channels` - The list of channels to join (lowercase).
96-
* `:mod_channels` - The list of channels where your bot is a moderator
97-
(this effects the message and command rate limits).
98-
99-
#### Example config
89+
```elixir
90+
# Default subscriptions.
91+
~w[
92+
channel.ad_break.begin channel.cheer channel.follow channel.subscription.end
93+
channel.channel_points_custom_reward_redemption.add
94+
channel.channel_points_custom_reward_redemption.update
95+
channel.charity_campaign.donate channel.charity_campaign.progress
96+
channel.goal.begin channel.goal.progress channel.goal.end
97+
channel.hype_train.begin channel.hype_train.progress channel.hype_train.end
98+
channel.shoutout.create channel.shoutout.receive
99+
stream.online stream.offline
100+
]
101+
```
100102

101103
```elixir
102104
# config/runtime.exs
103105

106+
# Add to the existing bot config.
104107
config :my_app,
105108
bots: [
106109
[
107-
bot: MyApp.Bot,
110+
# Example existing Bot config.
111+
bot: MyApp.MyBot,
108112
user: "myappbot",
109113
pass: System.fetch_env!("TWITCH_OATH_TOKEN"), # "oauth:myappbotpassword"
110114
channels: ["mychannel", "foo"],
111115
mod_channels: ["mychannel"],
112-
debug: false # defaults to false
116+
debug: false, # defaults to false
117+
# Adding here ===>:
118+
# Adding event_sub config options will start the eventsub socket.
119+
event_sub: [
120+
user_id: "123456",
121+
handler: MyApp.MyBot,
122+
client_id: System.get_env("TWITCH_CLIENT_ID"),
123+
access_token: System.get_env("TWITCH_ACCESS_TOKEN")
124+
]
113125
]
114126
]
115127
```
116128

117-
### Add to your supervision tree
129+
### Bot module
118130

119-
##### Single bot example:
131+
Create a bot module to deal with chat messages or events:
120132

121133
```elixir
122-
# lib/my_app/application.ex
134+
defmodule MyBot do
135+
use TMI
123136

124-
[bot_config] = Application.fetch_env!(:my_app, :bots)
137+
alias TMI.Events.Follow
138+
alias TMI.Events.Message
125139

126-
children = [
127-
# If you have existing children, e.g.:
128-
Existing.Worker,
129-
{Another.Existing.Supervisor, []},
130-
# Add the bot.
131-
{TMI.Supervisor, bot_config}
132-
]
140+
@impl true
141+
def handle_event(%Message{message: "!" <> cmd} = event) do
142+
dispatch(cmd, event)
143+
end
133144

134-
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
145+
def handle_event(%Follow{} = event) do
146+
say(event.broadcaster_user_login, "Thanks for the follow, @#{event.user_name}")
147+
end
148+
149+
## Helpers
150+
151+
defp dispatch("roll", %{channel: channel, display_name: user}),
152+
do: say(channel, "@#{user} rolled a #{Enum.random(1..6)}!")
153+
154+
defp dispatch(_command, _msg),
155+
do: :noop
156+
end
157+
```
158+
159+
#### Available handler callbacks:
160+
161+
```elixir
162+
## Receives `Event` structs
163+
handle_event(event)
164+
165+
## IRC-related callbacks
166+
handle_connected(server, port)
167+
handle_disconnected()
168+
handle_join(channel)
169+
handle_join(channel, user)
170+
handle_kick(channel, kicker)
171+
handle_kick(channel, user, kicker)
172+
handle_logged_in()
173+
handle_login_failed(reason)
174+
handle_part(channel)
175+
handle_part(channel, user)
176+
handle_unrecognized(msg)
177+
handle_unrecognized(msg, tags)
178+
```
179+
180+
### Starting
181+
182+
Examples of adding it to your application's supervision tree below.
183+
184+
##### Single bot example:
185+
186+
```elixir
187+
# lib/my_app/application.ex in `start/2` function:
188+
defmodule MyApp.Application do
189+
# ...
190+
@impl true
191+
def start(_type, _args) do
192+
[bot_config] = Application.fetch_env!(:my_app, :bots)
193+
194+
children = [
195+
# ... existing stuff ...
196+
# Add the bot.
197+
{TMI.Supervisor, bot_config}
198+
]
199+
200+
# ...
201+
end
202+
# ...
203+
end
135204
```
136205

137206
##### Multiple bots example:
138207

139208
```elixir
140-
bots = Application.fetch_env!(:my_app, :bots)
141-
bot_children = for bot_config <- bots, do: {TMI.Supervisor, bot_config}
142-
143-
children = [
144-
# If you have existing children, e.g.:
145-
Existing.Worker,
146-
{Another.Existing.Supervisor, []}
147-
# Add the bot children.
148-
| bot_children
149-
]
209+
# lib/my_app/application.ex in `start/2` function:
210+
defmodule MyApp.Application do
211+
# ...
212+
@impl true
213+
def start(_type, _args) do
214+
bots = Application.fetch_env!(:my_app, :bots)
215+
bot_children = for bot_config <- bots, do: {TMI.Supervisor, bot_config}
216+
217+
children = [
218+
# If you have existing children, e.g.:
219+
Existing.Worker,
220+
{Another.Existing.Supervisor, []}
221+
# Add the bot children.
222+
| bot_children
223+
]
150224

151-
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
225+
# ...
226+
end
227+
# ...
228+
end
152229
```
153230

154231
### To get your bot verified:
@@ -170,7 +247,3 @@ distributed under the License is distributed on an "AS IS" BASIS,
170247
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
171248
See the License for the specific language governing permissions and
172249
limitations under the License.
173-
174-
## For the memes
175-
176-
![roxcar76](https://user-images.githubusercontent.com/2897340/163022131-e86af85b-6b2f-4ee8-b44b-486f267ac7bd.png)

lib/tmi/event_sub/socket.ex

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,19 @@ defmodule TMI.EventSub.Socket do
3838
* `:url` - A websocket URL to connect to. `Defaults to "wss://eventsub.wss.twitch.tv/ws"`.
3939
* `:keepalive_timeout` - The keepalive timeout in seconds. Specifying an invalid,
4040
but numeric value will return the nearest acceptable value. Defaults to `10`.
41+
* `:start?` - A boolean value of whether or not to start the eventsub socket.
4142
4243
"""
44+
@spec start_link(keyword()) :: GenServer.on_start()
4345
def start_link(opts) do
46+
if Keyword.get(opts, :start?, true) do
47+
do_start(opts)
48+
else
49+
:ignore
50+
end
51+
end
52+
53+
defp do_start(opts) do
4454
Logger.info("[TMI.EventSub.Socket] connecting...")
4555

4656
if not Enum.all?(@required_opts, &Keyword.has_key?(opts, &1)) do
@@ -212,7 +222,11 @@ defmodule TMI.EventSub.Socket do
212222
# }
213223
# }
214224
#
215-
defp handle_message(%{"message_type" => "notification", "subscription_type" => type}, %{"event" => payload}, state) do
225+
defp handle_message(
226+
%{"message_type" => "notification", "subscription_type" => type},
227+
%{"event" => payload},
228+
state
229+
) do
216230
Logger.debug("[TMI.EventSub.Socket] got notification: " <> inspect(payload, pretty: true))
217231

218232
type

lib/tmi/supervisor.ex

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,24 @@ defmodule TMI.Supervisor do
1818

1919
@impl true
2020
def init({bot, opts}) do
21+
{eventsub_opts, opts} = Keyword.pop(opts, :event_sub, start?: false)
22+
2123
# IRC Bot config.
2224
{is_verified, opts} = Keyword.pop(opts, :is_verified, false)
2325
{mod_channels, opts} = Keyword.pop(opts, :mod_channels, [])
26+
2427
{:ok, client} = TMI.IRC.Client.start_link(Keyword.take(opts, [:debug]))
28+
2529
conn = build_irc_conn(client, opts)
2630
dynamic_supervisor = TMI.IRC.MessageServer.supervisor_name(bot)
2731

28-
# EventSub config.
29-
eventsub_config = Application.fetch_env!(:abesai_bot, TMI.EventSub.Socket)
30-
3132
children = [
3233
{DynamicSupervisor, strategy: :one_for_one, name: dynamic_supervisor},
3334
{TMI.IRC.ChannelServer, {bot, conn, is_verified, mod_channels}},
3435
{TMI.IRC.ConnectionServer, {bot, conn}},
3536
{TMI.IRC.WhisperServer, {bot, conn}},
3637
{bot, conn},
37-
{TMI.EventSub.Socket, eventsub_config}
38+
{TMI.EventSub.Socket, eventsub_opts}
3839
]
3940

4041
Supervisor.init(children, strategy: :one_for_one)

0 commit comments

Comments
 (0)