Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Twitch Connector #1497

Merged
merged 56 commits into from Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
9953339
Initial work on twitch connector
FabioRosado May 20, 2020
11823bf
Add logic to handle oauth token request/refresh. Update events
FabioRosado May 21, 2020
7096b8a
Improve logic to handle oauth/token refresh
FabioRosado May 22, 2020
ae20219
Add docstrings, fix bug with refresh method, handle authentication fa…
FabioRosado May 22, 2020
53b90c1
Initial work on adding webhook support
FabioRosado May 23, 2020
b703a9f
Update webhook code and refactor webhook method.
FabioRosado May 24, 2020
9cdc99f
Add events for updating channel title and create clip
FabioRosado May 24, 2020
ff640c9
Add subscribe events, add config schema
FabioRosado May 25, 2020
6992ecf
Initial refactor, add is_live flag, connect/disconnect websocket on g…
FabioRosado May 27, 2020
eb504be
Remove helper function to build url - use params instead. Remove TODO…
FabioRosado May 27, 2020
538de6f
Add more Twitch const variables for API endpoint and regex match, unp…
FabioRosado May 27, 2020
72ad671
move routes to connect method
FabioRosado May 27, 2020
597d7dc
Fix issue with going live url, refactor code
FabioRosado May 27, 2020
84c4331
Add subscriptions.notification to event handler, add flag to check if…
FabioRosado May 27, 2020
c94e1bf
Handle status code != 200
FabioRosado May 27, 2020
3e82763
Add _() to loggers
FabioRosado May 27, 2020
0517f4f
Refactor code - handle token expiration on receiving 401 from function
FabioRosado May 29, 2020
f06f8fb
Add handshake again since websockets can be run on/off and oauth can …
FabioRosado May 29, 2020
300990b
Run black to fix linting issues
FabioRosado May 29, 2020
9261d6b
Fix linting, refactor code, fix typos that were generating bugs
FabioRosado May 30, 2020
896b038
Add use web address as default for forwarding
FabioRosado Jun 1, 2020
3668303
Fix bugs found on stream
FabioRosado Jun 1, 2020
b15b1af
Inital work on docs
FabioRosado Jun 4, 2020
4f909b4
Refactor websockets logic and use aiohttp for handling websockets
FabioRosado Jun 11, 2020
2563df2
Uncomment webhooks sub/unsub
FabioRosado Jun 11, 2020
7243ad1
Refactor websockets logic - add heartbeat and handle reconnect
FabioRosado Jun 11, 2020
c1be0b8
Handle dropped connection - reconnect to websocket whilst is_live
FabioRosado Jun 11, 2020
ab135ec
Update lease time for 9 days
FabioRosado Jun 12, 2020
1ad2127
Refactor code, raise ConnectionError exception on token expired, rena…
FabioRosado Jun 13, 2020
5c40c90
Remove check for 'close' on msg.data it will never be sent by Twitch
FabioRosado Jun 13, 2020
075d25e
Await exponentially to reconnect, handle 'stream offline' event better
FabioRosado Jun 13, 2020
ac9d9d2
Move log to try section otherwise it will raise exception
FabioRosado Jun 19, 2020
ed8c21c
Fix bug with stream started - should call listen method and not conne…
FabioRosado Jun 19, 2020
b809e6e
Move TWITCH_JSON const to const file, remove whitespaces
FabioRosado Jun 20, 2020
f765f1c
Replace api urls with const var
FabioRosado Jun 20, 2020
7e01e57
Update UpdateTitle event to use new api endpoint
FabioRosado Jun 20, 2020
66117f8
Refactor some code and write ALL the tests
FabioRosado Jun 25, 2020
d61d310
Move get_user_id inside connector
FabioRosado Jun 25, 2020
b60de10
Refactor connector and events, add TWITCH_JSON const path to class, u…
FabioRosado Jun 26, 2020
1813c07
Update documentation
FabioRosado Jun 26, 2020
8cf82ea
add twitch.json to test folder
FabioRosado Jun 26, 2020
ee063cf
Update docs, update __init__ of events, lint all things
FabioRosado Jun 26, 2020
2f31667
Merge branch 'master' into twitch
FabioRosado Jul 31, 2020
4d1a50f
Add 'userleftchannel' event
FabioRosado Jul 7, 2020
faf9748
Fix linting issues, fix bug with the user left event
FabioRosado Jul 31, 2020
7455761
Yet another linting fix and bump coverage
FabioRosado Jul 31, 2020
7452019
Fix docs problem
FabioRosado Jul 31, 2020
4f6b1fa
Remove comma?
FabioRosado Jul 31, 2020
ad755ae
Update opsdroid/connector/twitch/__init__.py
FabioRosado Aug 3, 2020
a954420
backtick ALL the things
FabioRosado Aug 3, 2020
549ca71
Merge branch 'master' into twitch
FabioRosado Aug 3, 2020
d6aaa3b
Move events to core, update tests
FabioRosado Aug 4, 2020
fb5c66f
Fix lint and refactor DeleteMessage event
FabioRosado Aug 7, 2020
eea958d
Update events in docs
FabioRosado Aug 7, 2020
b6f86b2
Forgot about the other events...
FabioRosado Aug 7, 2020
6aa2364
Merge branch 'master' into twitch
FabioRosado Aug 7, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/labeler.yml
Expand Up @@ -14,6 +14,8 @@ connector/slack:
- opsdroid/connector/slack/**/*
connector/telegram:
- opsdroid/connector/telegram/**/*
connector/twitch:
- opsdroid/connector/twitch/**/*
connector/webexteams:
- opsdroid/connector/webexteams/**/*
connector/websocket:
Expand Down
1 change: 1 addition & 0 deletions docs/connectors/index.md
Expand Up @@ -15,6 +15,7 @@ Connectors are modules which connect opsdroid to an external event source. This
shell
slack
telegram
twitch
webexteams
websocket
custom
Expand Down
270 changes: 270 additions & 0 deletions docs/connectors/twitch.md
@@ -0,0 +1,270 @@
# Twitch

A connector for [Twitch](https://twitch.tv/).

## Requirements

- A Twitch Account
- A Twitch App obtained from [Twitch developers page](https://dev.twitch.tv/console/apps)
- The `code` obtained from the first step of OAuth

## Configuration

```yaml
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
foward-url: 'http://94jfsd9ff.ngrok.io' # Either an URL provided by a forwarding service or an exposed ip address
FabioRosado marked this conversation as resolved.
Show resolved Hide resolved
# optional
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](https://dev.twitch.tv/console/apps) 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.
FabioRosado marked this conversation as resolved.
Show resolved Hide resolved

_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](https://dev.twitch.tv/docs/v5/guides/migration#scopes) and the [IRC Scopes](https://dev.twitch.tv/docs/irc/guide#scopes-for-irc-commands) 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.

```eval_rst
.. autoclass:: opsdroid.events.JoinRoom
:members:
```

```eval_rst
.. autoclass:: opsdroid.events.LeaveRoom
:members:
```

```eval_rst
.. autoclass:: opsdroid.connector.twitch.events.UserFollowed
:members:
```

```eval_rst
.. autoclass:: opsdroid.connector.twitch.events.UserSubscribed
:members:
```

```eval_rst
.. autoclass:: opsdroid.connector.twitch.events.UserGiftedSubscription
:members:
```

```eval_rst
.. autoclass:: opsdroid.connector.twitch.events.StreamStarted
:members:
```

```eval_rst
.. autoclass:: opsdroid.connector.twitch.events.StreamEnded
:members:
```

#### Manual Events

These events will have to be triggered by you with an opsdroid skill.

```eval_rst
.. autoclass:: opsdroid.connector.twitch.events.UpdateTitle
:members:
```

```eval_rst
.. autoclass:: opsdroid.connector.twitch.events.CreateClip
:members:
```

```eval_rst
.. autoclass:: opsdroid.events.DeleteMessage
:members:
```

```eval_rst
.. autoclass:: opsdroid.events.BanUser
:members:
```

## 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](https://github.com/FabioRosado/skill-twitch) 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.

```python
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.

```python
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?

```python
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.

```python
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.

```yaml
skills:
- twitch:
whitelisted:
- your_username_on_twitch
- your_username_on_another_connector
```

```python
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

```eval_rst
.. autoclass:: opsdroid.connector.twitch.ConnectorTwitch
:members:
```