Twitch¶
A connector for Twitch.
Requirements¶
A Twitch Account
A Twitch App obtained from Twitch developers page
The
code
obtained from the first step of OAuth
Configuration¶
connectors:
twitch:
# required
code: "hfu923hfks02nd2821jfislf" # Code obtained from the first OAuth step
client-id: "e0asdj48jfkspod0284"
client-secret: "kdksd0458j93847j"
channel: theflyingdev # Broadcaster channel
redirect: http://localhost # Url to be passed to get oath token - defaults to localhost
forward-url: 'http://94jfsd9ff.ngrok.io' # Either an URL provided by a forwarding service or an exposed ip address
# optional
webhook-lease-seconds: 86400 # how long for webhooks to expire
always-listening: false # Turn on to connect to the chat server even if stream is offline.
Setup Twitch App¶
You need to create a Twitch App to use the Twitch Connector. Click the + Register Your Application
button, give this app a name and a redirect url - using http://localhost
is fine. Once created, you can click the Manage
button and get your client-id
, you can then get a client-secret
by pressing the New Secret
button (keep this secret safe as it won’t be shown to you again).
Getting OAuth code¶
Twitch OAuth has two steps, first you need to make a GET
request to a specific URL to obtain a code
. After you’ve received the code, you need to make a POST
request to the same URL and Twitch will send you an access_token
and refresh_token
.
After a certain period, the access_token
will expire and you have to make a new request with your refresh_token
to re-authenticate yourself.
NOTE: The Twitch Connector will handle token expiration and re-authentication for you.
Step 1 - Getting Code¶
To get your code, you need to make a request to https://id.twitch.tv/oauth2/authorize
with the following parameters:
client_id
redirect_uri
response_type
scope
Both the client_id
and redirect_uri
can be obtained when you click the Manage
button on your app. The response_type
that we want will be code
and we will ask for a lot of scopes. You can check the API Scopes and the IRC Scopes to read more about what we are asking and why.
The Twitch Connector interacts with a wide range of services - IRC server, New API, V5 API - so we need to pass a big number of scopes to make sure everything works as expected.
Recommended scopes¶
channel:read:subscriptions+channel_subscriptions+analytics:read:games+chat:read+chat:edit+viewing_activity_read+channel_feed_read+channel_editor+channel:read:subscriptions+user:read:broadcast+user:edit:broadcast+user:edit:follows+channel:moderate+channel_read
Example: OAuth URL¶
You can use this example url to make your request - make sure you add your client_id
before making the request. After adding your client id, you can open the url on a browser window, accept the request and Twitch will send you back to your redirect_url
. Look at the address bar and you will see that it contains a code=jfsd98hjh8d7da983j
this is what you need to add to your opsdroid config.
https://id.twitch.tv/oauth2/authorize?client_id=<your client id>&redirect_uri=http://localhost&response_type=code&scope=channel:read:subscriptions+channel_subscriptions+analytics:read:games+chat:read+chat:edit+viewing_activity_read+channel_feed_read+channel_editor+channel:read:subscriptions+user:read:broadcast+user:edit:broadcast+user:edit:follows+channel:moderate+channel_read
Usage¶
The connector will subscribe to followers alerts, stream status (live/offline) and subscriber alerts, it will also connect to the chat service whenever the stream status notification is triggered and the StreamStarted
event is triggered by opsdroid. If you wish you can set the optional config parameter always-listening: True
to connect to the chat whenever opsdroid is started.
Events Available¶
The Twitch Connector contains 10 events that you can use on your custom made skill. Some of these events are triggered automatically whenever an action happens on twitch - for example when a user follows your channel. Others you will have to trigger on a skill - for example, to delete a specific message.
Automatic Events¶
These events are triggered by opsdroid whenever something happens on twitch.
- class opsdroid.events.JoinRoom(user_id=None, user=None, target=None, connector=None, raw_event=None, raw_parses=None, event_id=None, linked_event=None)¶
Event class to represent a user joining a room.
- class opsdroid.events.LeaveRoom(user_id=None, user=None, target=None, connector=None, raw_event=None, raw_parses=None, event_id=None, linked_event=None)¶
Event class to represent a user leaving a room.
- class opsdroid.connector.twitch.events.UserFollowed(follower, followed_at, *args, **kwargs)¶
Event class to trigger when a user follows the streamer.
- class opsdroid.connector.twitch.events.UserSubscribed(user, message, *args, **kwargs)¶
Event class that triggers whenever a user subscribes to the channel.
- class opsdroid.connector.twitch.events.UserGiftedSubscription(gifter_name, gifted_named, *args, **kwargs)¶
Event class that triggers when a user gifts a subscription to someone.
- class opsdroid.connector.twitch.events.StreamStarted(title, viewers, started_at, *args, **kwargs)¶
Event class that triggers when streamer started broadcasting.
- class opsdroid.connector.twitch.events.StreamEnded(user_id=None, user=None, target=None, connector=None, raw_event=None, raw_parses=None, event_id=None, linked_event=None)¶
Event class that triggers when streamer stoppped broadcasting.
Manual Events¶
These events will have to be triggered by you with an opsdroid skill.
- class opsdroid.connector.twitch.events.UpdateTitle(status, *args, **kwargs)¶
Event class that updates channel title.
- class opsdroid.connector.twitch.events.CreateClip(id, *args, **kwargs)¶
Event class that creates a clip once triggered.
- class opsdroid.events.DeleteMessage(user_id=None, user=None, target=None, connector=None, raw_event=None, raw_parses=None, event_id=None, linked_event=None)¶
Event to represent deleting a message or other event.
- class opsdroid.events.BanUser(user_id=None, user=None, target=None, connector=None, raw_event=None, raw_parses=None, event_id=None, linked_event=None)¶
Event to represent the banning of a user from a room.
Examples¶
You can write your custom skills to interact with the Twitch connector, here are a few examples of what you can do. You can also use the Twitch Skill to interact with the connector.
StreamStarted event¶
Let’s say that you want to send a message to another connector whenever you go live, you can achieve this by writing that will be triggered when the StreamStarted event is triggered.
from opsdroid.skill import Skill
from opsdroid.matchers import match_event
from opsdroid.connector.twitch.events import StreamStarted
class TwitchSkill(Skill):
"""opsdroid skill for Twitch."""
def __init__(self, opsdroid, config, *args, **kwargs):
super().__init__(opsdroid, config, *args, **kwargs)
self.rocketchat_connector = self.opsdroid.get_connector('rocketchat')
@match_event(StreamStarted)
async def stream_started_skill(event):
"""Send message to rocketchat channel."""
await self.rocketchat_connector.send(Message(f"I'm live on twitch, come see me work on {event.title}"))
UserFollowed event¶
Some bots will send a thank you message to the chat whenever a user follows your channel. You can do the same with opsdroid by using the UserFollowed event.
from opsdroid.skill import Skill
from opsdroid.matchers import match_event
from opsdroid.connector.twitch.events import UserFollowed
class TwitchSkill(Skill):
"""opsdroid skill for Twitch."""
def __init__(self, opsdroid, config, *args, **kwargs):
super().__init__(opsdroid, config, *args, **kwargs)
self.connector = self.opsdroid.get_connector('twitch')
@match_event(UserFollowed)
async def say_thank_you(event):
"""Send message to rocketchat channel."""
await self.connector.send(Message(f"Thank you so much for the follow {event.follower}, you are awesome!"))
BanUser event¶
We have seen how to send messages to the chat, how about we remove a spam message and ban bots from trying to sell you followers, subs and viewers?
from opsdroid.skill import Skill
from opsdroid.matchers import match_regex
from opsdroid.connector.twitch.events import BanUser, DeleteMessage
class TwitchSkill(Skill):
"""opsdroid skill for Twitch."""
def __init__(self, opsdroid, config, *args, **kwargs):
super().__init__(opsdroid, config, *args, **kwargs)
self.connector = self.opsdroid.get_connector('twitch')
@match_regex(r'famous\? Buy followers', case_sensitive=False)
async def goodbye_spam_bot(self, message):
await self.connector.send(BanUser(user=message.user))
deletion = DeleteMessage(id=message.event_id)
await self.connector.send(deletion)
UpdateTitle event¶
You need to be careful on how you set this skill, you should have a list of users that are allowed to change your broadcast title otherwise it can be abused while you are streaming.
from opsdroid.skill import Skill
from opsdroid.constraints import constrain_users
from opsdroid.matchers import match_regex
from opsdroid.connector.twitch.events import UpdateTitle
class TwitchSkill(Skill):
"""opsdroid skill for Twitch."""
def __init__(self, opsdroid, config, *args, **kwargs):
super().__init__(opsdroid, config, *args, **kwargs)
self.connector = self.opsdroid.get_connector('twitch')
@match_regex(r'\!title (.*)')
@constrain_users(your_awesome_twitch_username)
async def change_title(self, message):
_LOGGER.info("Attempt to change title")
await self.connector.send(UpdateTitle(status=message.regex.group(1)))
You could also add a whitelisted
config param to your skill and then read the configuration to check if the user that tried to change the title is in that list.
skills:
- twitch:
whitelisted:
- your_username_on_twitch
- your_username_on_another_connector
from opsdroid.skill import Skill
from opsdroid.constraints import constrain_users
from opsdroid.matchers import match_regex
from opsdroid.connector.twitch.events import UpdateTitle
class TwitchSkill(Skill):
"""opsdroid skill for Twitch."""
def __init__(self, opsdroid, config, *args, **kwargs):
super().__init__(opsdroid, config, *args, **kwargs)
self.connector = self.opsdroid.get_connector('twitch')
@match_regex(r'\!title (.*)')
async def change_title(self, message):
if message.user in self.config.get('whitelisted', []):
await self.connector.send(UpdateTitle(status=message.regex.group(1)))
Reference¶
- class opsdroid.connector.twitch.ConnectorTwitch(*args, **kwargs)¶
A connector for Twitch.
- async ban_user(event)¶
Ban user from the channel.
This event will be used when we need to ban a specific user from the chat channel. Banning a user will also remove all the messages sent by that user, so we don’t need to worry about removing a lot of mensages.
- async connect()¶
Connect to Twitch services.
Within our connect method we do a quick check to see if the file
twitch.json
exists in the application folder, if this file doesn’t exist we assume that it’s the first time the user is running opsdroid and we do the first request for the oauth token.If this file exists then we just need to read from the file, get the token in the file and attempt to connect to the websockets and subscribe to the Twitch events webhook.
- async connect_websocket()¶
Connect to the irc chat through websockets.
Our connect method will attempt to make a connection to Twitch through the websockets server. If the connection is made, any sort of failure received from the websocket will be in the form of a
NOTICE
, unless Twitch closes the websocket connection.In this method we attempt to connect to the websocket and use the previously saved oauth token to join a twitch channel.
Once we are logged in and on a Twitch channel, we will request access to special features from Twitch.
The
commands
request is used to allow us to send special commands to the Twitch IRC server.The
tags
request is used to receive more information with each message received from twitch. Tags enable us to get metadata such as message ids.The
membership
request is used to get notifications when an user enters the chat server (it doesn’t mean that the user is watching the streamer) and also when a user leaves the chat channel.
- async create_clip()¶
Create clip from broadcast.
We send a post request to twitch to create a clip from the broadcast, Twitch will return a response containing a clip
id
andedit_url
. TWitch mentions that the way to check if the clip was created successfully is by making aget
request to theclips
API enpoint and query by theid
obtained from the previous request.
- async disconnect()¶
Disconnect from twitch.
Before opsdroid exists we will want to disconnect the Twitch connector, we need to do some clean up. We first set the while loop flag to False to stop the loop and then try to unsubscribe from all the webhooks that we subscribed to on connect - we want to do that because when we start opsdroid and the
connect
method is called we will send another subscribe request to Twitch. After we will send aPART
command to leave the channel that we joined on connect.Finally we try to close the websocket connection.
- async disconnect_websockets()¶
Disconnect from the websocket.
- get_authorization_data()¶
Open file containing authentication data.
- async get_messages_loop()¶
Listen for and parse messages.
Since we are using aiohttp websockets support we need to manually send a pong response every time Twitch asks for it. We also need to handle if the connection was closed and if it was closed but we are still live, then a ConnectionError exception is raised so we can attempt to reconnect to the chat server again.
- async get_user_id(channel, token, client_id)¶
Call twitch api to get broadcaster user id.
A lot of webhooks expect you to pass your user id in order to get the notification when a user subscribes or folllows the broadcaster channel.
Since we are calling the Twitch API to get our
self.user_id
on connect, we will use this method to handle when a token has expired, so if we get a 401 status back from Twitch we will raise a ClientResponseError and send back the status and the message Unauthorized, that way we can refresh the oauth token on connect if the exception is raised.- Parameters
channel (string) – Channel that we wish to get the broadcaster id from.
token (string) – OAuth token obtained from previous authentication.
client_id (string) – Client ID obtained from creating a Twitch App to iteract with opsdroid.
- Returns
Broadcaster/user id received from Twitch
- Return type
string
- Raises
ConnectionError – Raised exception if we got an unauthorized code from twitch. Our
oauth token probably expired. –
- async handle_challenge(request)¶
Challenge handler for get request made by Twitch.
Upon subscription to a Twitch webhook, Twitch will do a get request to the
callback
url provided to check if the url exists. Twitch will do a get request with a challenge and expects thecallback
url to return that challenge in plain-text back to Twitch.This is what we are doing here, we are getting
hub.challenge
from the request and return it in plain-text, if we can’t find that challenge we will return a status code 500.- Parameters
request (aiohttp.web.Request) – Request made to the get route created for webhook subscription.
- Returns
if request contains
hub.challenge
we return it, otherwise return status 500.- Return type
- async listen()¶
Listen method of the connector.
Every connector has to implement the listen method. When an infinite loop is running, it becomes hard to cancel this task. So we are creating a task and set it on a variable so we can cancel the task.
If we need to reconnect to Twitch, Twitch will allow us to reconnect immediatly on the first reconnect and then expects us to wait exponentially to reconnect to the websocket.
- async refresh_token()¶
Attempt to refresh the oauth token.
Twitch oauth tokens expire after a day, so we need to do a post request to twitch to get a new token when ours expires. The refresh token is already saved on the
twitch.json
file so we can just open that file, get the appropriate token and then update the file with the new received data.
- async remove_message(event)¶
Remove message from the chat.
This event is used when we need to remove a specific message from the chat service. We need to pass the message id to remove a specific message. So this method is calling the
/delete
method together with the message id to remove that message.
- async request_oauth_token()¶
Call Twitch and requests new oauth token.
This method assumes that the user already has the code obtained from following the first oauth step which is making a get request to the twitch api endpoint:
https://id.twitch.tv/oauth2/authorize
and passing the needed client id, redirect uri and needed scopes to work with the bot.This method is the second - and final step - when trying to get the oauth token. We use the code that the user obtained on step one - check documentation - and make a post request to Twitch to get the
access_token
andrefresh_token
so we can refresh the access_token when needed. Note that the refresh_token doesn’t change with each refresh.
- save_authentication_data(data)¶
Save data obtained from requesting authentication token.
- async send_handshake()¶
Send needed data to the websockets to be able to make a connection.
If we try to connect to Twitch with an expired oauth token, we need to request a new token. The problem is that Twitch doesn’t close the websocket and will only notify the user that the login authentication failed after we sent the
PASS
,NICK
andJOIN
command to the websocket.So we need to send the initial commands to Twitch, await for a status with
await self.websockets.recv()
and there will be our notification that the authentication failed in the form of:tmi.twitch.tv NOTICE * :Login authentication failed
This method was created to prevent us from having to copy the same commands and send them to the websocket. If there is an authentication issue, then we will have to send the same commands again - just with a new token.
- async send_message(message)¶
Send message throught websocket.
To send a message to the Twitch IRC server through websocket we need to use the same style, we will always send the command
PRIVMSG
and the channel we want to send the message to. The message also comes after :.- Parameters
message (string) – Text message that should be sent to Twitch chat.
- async twitch_webhook_handler(request)¶
Handle event from Twitch webhooks.
This method will handle events when they are pushed to the webhook post route. Each webhook will send a different kind of payload so we can handle each event and trigger the right opsdroid event for the received payload.
For follow events the payload will contain
from_id
(broadcaster id),from_username
(broadcaster username)to_id
(follower id),to_name
(follower name) andfollowed_at
(timestamp).For stream changes a lot more things are returned but we only really care about
type
(if live/offline)title
(stream title).For subscriptions events we will want to know
event_type
,timestamp
,event_data.plan_name
,event_data.is_gift
,event_data.tier
,event_data.username
andevent_data.gifter_name
.- Parameters
request (aiohttp.web.Request) – Request made to the post route created for webhook subscription.
- Returns
Send a
received
message and status 200 - Twitch will keep sending the event if it doesn’t get the 200 status code.- Return type
- async update_stream_title(event)¶
Update Twitch title.
To update your channel details you need to use Twitch API V5(kraken). The so called “New Twitch API” doesn’t have an enpoint to update the channel. To update your channel details you need to do a put request and pass your title into the url.
- Parameters
event (twitch.events.UpdateTitle) – opsdroid event containing
status
(your title).
- async validate_request(request, secret)¶
Compute sha256 hash of request and secret.
Twitch suggests that we should always validate the requests made to our webhook callback url, that way we protect ourselves from received an event that wasn’t sent by Twitch. After sending
hub.secret
on our webhook subscribe, Twitch will use that secret to send thex-hub-signature
header, that is the hash that we should compare with our own computed one, if they don’t match then the request is not valid and shouldn’t be parsed.
- async webhook(topic, mode)¶
Subscribe to a specific webhook.
Twitch has different webhooks that you can subscribe to, when you subscribe to a particular webhook, a
post
request needs to be made containing aJSON
payload, that tells Twitch what subscription you are attempting to do.When you submit the
post
request toTWITCH_WEBHOOK_ENDPOINT
, twitch will send back aget
request to yourcallback
url (hub.callback
) with a challenge. Twitch will then await for a response containing only the challenge in plain text.With this in mind, that is the reason why we open two routes (
get
andpost
) that link to/connector/<connector name>
.The
hub.topic
represents the webhook that we want to suscribe from twitch. Thehub.lease_seconds
defines the number of seconds until the subscription expires, maximum is 864000 seconds (10 days), but we will set up a day as our expiration since our app oauth tokens seem to expire after a day.- Parameters
topic (string) – Twitch webhook url to subscribe/unsubscribe to.
mode (string) – subscribe or unsuscribe to the webhook.