This app triggers API functions when Google assistant is given custom commands (created through IFTTT's voice command applet) in conjunction with Plex webhooks like media.play
, media.pause
, and media.stop
.
This is a barebones and personal app, but you may find it a useful starting point for your own needs.
- Create an Apache or Nginx host file for the app (important: the document root should be
/public
) - Clone or download the repo to the appropriate directory you setup in the previous step (again, ensure the
/public
directory is your document root) - Install dependencies:
composer install --no-dev
- Create your
settings.ini
file:cp settings.ini.example settings.ini
and edit the contents with your desired configuration. - Now generate your unique configuration file by executing
php slack config:generate
from a terminal. Note: The config file (config/slack-house.json
) will be regenerated every time theslack config:generate
command is called, so make sure yoursettings.ini
file is up-to-date before you run the command and never edit the generated JSON config file directly.
This app interacts primarily with LIFX and Plex, but in my configuration, through IFTTT webhooks it also sends commands to Harmony, Shield TV, and TP Link smart plugs. It should be trivial to integrate any other products so long as they provide an HTTP API or IFTTT can interact with them.
Monolog messages are sent to my Discord server, but there are many other ways to catch them.
There are only two endpoints: /webhook/plex
and /webhook/ifttt
, see the routes/routes.php
file. Both endpoints have their own middleware to handle requests, see src/Middleware
.
Plex players that are allowed to trigger events are verified by UUID
(obtained from your Plex server's settings page) in the players[]
ini option (see the VerifyPlexWebhook
middleware file for an example of how this is used). Plex media types allowed to trigger events are listed in the allowed_media[]
setting. Both settings can be a single value or a comma separated list of values (e.g., allowed_media[]=movie
)
You can hack up the IFTTT and LIFX API wrappers as needed, they're located in the src/Services
directory and use the Guzzle client for requests, though this too can easily be swapped out if you prefer another client.
$app->group('/webhook', function () use ($app) {
// IFTTT
$app->post('/ifttt', 'app.controller.ifttt')
->add('app.middleware.verify_ifttt_webhook');
// Plex
$app->post('/plex', 'app.controller.plex')
->add('app.middleware.verify_plex_webhook');
});
Only two routes are needed to handle the commands sent by Plex webhooks or IFTTT applets. The IFTTT commands must have a valid payload in order to pass schema validation (see src/Middleware/VerifyIftttWebhook.php
):
{
"key": "ifttt_maker_key_here",
"event": "event_name_here",
"command": "command_name_here"
}
So, for example, if I want to activate movie time, I tell my Google Assistant: "It's movie time.", and it triggers the IFTTT webhook for movie time, handled in src/Controllers/IftttController.php
:
// Handle home events
if ($payload->event === 'home_command') {
// Movie time!
// 1. Re-enable Plex webhooks so lights respond (in case they are disabled)
// 2. Activate the LIFX Movie Time scene over 5 seconds
// 3. Turn on the Kasa smart plug for the TV, receiver, and speakers
// 4. Tell Harmony to activate the Shield TV activity
if ($payload->command === 'activate_movie_time') {
$this->appCommand->changeSetting('plex.webhooks', 'enabled');
$this->lifx->activateScene('movie_time', 5);
$this->ifttt->trigger('turn_tv_plug_on');
$this->ifttt->trigger('start_shield_activity');
return $response->withJson($payload->command . ' webhook fired.');
}
}
Plex sends its payloads as JSON in a URL encoded POST request, so we need to run json_decode()
on the payload
after we receive it. The Plex middleware (see src/Middleware/VerifyPlexWebhook.php
)) validates the Plex payloads, which look like this:
{
"event": "media.play",
"user": true,
"owner": true,
"Account": {
"id": 1,
"thumb": "https://plex.tv/users/1022b120ffbaa/avatar?c=1465525047",
"title": "elan"
},
"Server": {
"title": "Office",
"uuid": "54664a3d8acc39983675640ec9ce00b70af9cc36"
},
"Player": {
"local": true,
"publicAddress": "200.200.200.200",
"title": "Plex Web (Safari)",
"uuid": "r6yfkdnfggbh2bdnvkffwbms"
},
"Metadata": {
"librarySectionType": "artist",
"ratingKey": "1936545",
"key": "/library/metadata/1936545",
"parentRatingKey": "1936544",
"grandparentRatingKey": "1936543",
"guid": "com.plexapp.agents.plexmusic://gracenote/track/7572499-91016293BE6BF7F1AB2F848F736E74E5/7572500-3CBAE310D4F3E66C285E104A1458B272?lang=en",
"librarySectionID": 1224,
"type": "track",
"title": "Love The One You're With",
"grandparentKey": "/library/metadata/1936543",
"parentKey": "/library/metadata/1936544",
"grandparentTitle": "Stephen Stills",
"parentTitle": "Stephen Stills",
"summary": "",
"index": 1,
"parentIndex": 1,
"ratingCount": 6794,
"thumb": "/library/metadata/1936544/thumb/1432897518",
"art": "/library/metadata/1936543/art/1485951497",
"parentThumb": "/library/metadata/1936544/thumb/1432897518",
"grandparentThumb": "/library/metadata/1936543/thumb/1485951497",
"grandparentArt": "/library/metadata/1936543/art/1485951497",
"addedAt": 1000396126,
"updatedAt": 1432897518
}
}
Pressing play on a movie or show in Plex sends a webhook to our /webhook/plex
endpoint which, if it passed validation, is handled in src/Controllers/PlexController.php
:
// Handle the Play event
if ($payload->event === 'media.play') {
// Power off all the lights in the LIFX Warm Night scene over 30 seconds
$lifx->activateScene('warm_night', 30, ['power' => 'off']);
return $response->withJson('Play event handled.');
}
As you can see this is pretty straightforward to use and is easily extensible, especially if you're familiar with Slim's container and basic PHP.
Pull requests and suggestions are welcome.