Jump to content

Flexible, customisable webhooks with Emby Scripter-X


Anthony Musgrove

Recommended Posts

Anthony Musgrove

Hi guys, I just thought I'd introduce the latest feature of Emby Scripter-X (v2.3.5+) out now on the catalog.

 

Emby Scripter-X Webhooks

 

Why? I notice that a lot of Scripter-X users use the plugin to perform some type of webhook(s) based on certain events, so why not natively support it?

 

So how does it work?  Pretty much as-per-usual with the Actions interface.

 

5ec3b83e1a7c5_onAuthenticationFailed_Web

 

The interface will eventually change input field labels based on the selection in the interpreter box, but it is as above:

 

Run [script], where [script] is the URL of your webhook script (webhook endpoint/api endpoint, etc), in my example above, I have a php script running on my local webserver at http://192.168.1.10/hook.php.

 

[interpreter] should be set to web:post (to post data), web:get is also available, however it does exactly that - doesn't post data.

 

[Parameters] instead of listing parameters to send over the command-line, this is the path to your json content template - where your data is formatted from.   Here I specify d:\embyscripts\hooktemplates\authenticationfailed.json, and in this file is the following content:

{
	"user": {
		"username": "%username%",
		"password": "%password%"
	},

	"device": {
		"id": "%device.id%",
		"name": "%device.name%",
		"ip_address": "%device.remote.ipaddress%"
	},

	"scripterx_version": "%scripterx.version%",

	"emby_server_version": "%server.version%"
}

It should be self explanatory from here.  So ScripterX will read in your template, substitute for the token values, and post it to the specified URL.

 

So my PHP script simply takes $_POST['data'], casts it as a JSON object, and outputs to a file on my webserver:

Array
(
    [user] => Array
        (
            [username] => Anthony
            [password] => asdfasdfasdffasgfsasgasdfasdf
        )

    [device] => Array
        (
            [id] => 427a7309-5fc2-489d-9a83-0664c619c779
            [name] => Firefox
            [ip_address] => 192.168.1.124
        )

    [scripterx_version] => 2.3.4.0
    [emby_server_version] => 4.4.2.0

But as you can see --- these templates are entirely customisable, so you can adjust these templates to suit whatever you're posting your data to.

 

The only thing currently that I haven't made customisable is the POST variable that contains the json data, which is set to 'data'.  I will eventually extend the functionality such that the JSON data can be submitted without the need for POST variables.

 

Any comments/feedback/suggestions are greatly appreciated as always.

Edited by Anthony.Musgrove
  • Like 2
Link to comment
Share on other sites

  • 2 weeks later...
spaceman07

hello.. can you provide the php script you are using? i will try this out and would like to explore the options more.

 

thanks

Link to comment
Share on other sites

Anthony Musgrove

hello.. can you provide the php script you are using? i will try this out and would like to explore the options more.

 

thanks

 

Hi mate, that is so much appreciated.   This hook.php file was very simple - was just designed to take in some JSON data and basically spit it back out to the requesting connection.  It's:

<?php

    $post_json_data = $_POST["data"];
    $json_data_object = json_decode($post_json_data,true);

	file_put_contents("/home/medius/public_html/auth_hook.txt", "Hook Called!:\r\n", FILE_APPEND);
    file_put_contents("/home/medius/public_html/auth_hook.txt", print_r($json_data_object,true), FILE_APPEND);


header('Content-type: application/json');
echo json_encode($json_data_object);


Link to comment
Share on other sites

  • 1 year later...

Hello,

I have a question:
I have done this with a workaround under linux. ScripterX call a shell script via /bin/bash interpreter to post webhook using curl.

script:

curl -X POST https://webhook.example.com/mywebhookurl -H "Content-Type: application/json" -d '{"movie":"$1","year":"$2","library":"$3"}'

ScripterX config:

image.thumb.png.6f9940def85f4273be2f806e96e014ca.png

I have tested it via web:post interpreter but the post is empty.

image.thumb.png.17ad5b19ee41ef96914817231f0a9960.png

Would be nice to have a leaner solution in the futur

 

 

Edited by dual-o
Link to comment
Share on other sites

Hi Anthony, this was just what I was looking for to control my lighting during movie night. Thanks!

Quick question. is it also possible to add OnPlaybackPause as a trigger?

This way I turn on the lights at a low level of brightness if somebody needs a toilet break ;)

Again, thanks a lot for your work!

Link to comment
Share on other sites

  • 1 month later...
horstepipe
On 9/24/2021 at 7:26 PM, dual-o said:

Hello,

I have a question:
I have done this with a workaround under linux. ScripterX call a shell script via /bin/bash interpreter to post webhook using curl.

script:

curl -X POST https://webhook.example.com/mywebhookurl -H "Content-Type: application/json" -d '{"movie":"$1","year":"$2","library":"$3"}'

ScripterX config:

image.thumb.png.6f9940def85f4273be2f806e96e014ca.png

I have tested it via web:post interpreter but the post is empty.

image.thumb.png.17ad5b19ee41ef96914817231f0a9960.png

Would be nice to have a leaner solution in the futur

 

 

 

Hello folks,

I'd like to setup the same but I'm struggling with it. It is for a new content notification for a Synapse/Matrix homeserver. I setup the webhook gateway and it basically works (I can receive test messages on a channel). But I'm struggling setting this up with ScripterX and I'm unsure whether @dual-os instructions are complete.

So on the Webhook's side, the developer gave me a plugin - which I guess is the same as he also gave to @dual-o

https://github.com/geluk/matrix-webhook-gateway/issues/25#issuecomment-955253060

But now I'm unsure how to setup ScripterX. If I'm doing it like above it does not work, I'm getting lots of errors by the webhook gateway.

2021-10-30 18:13:08.268.000      ERROR  [webhook-srv]           Failed to handle webhook invocation:
 Error  Cannot read properties of undefined (reading 'formatPlain')
error stack:
• formatting.ts:26 toPlain
    src/formatting/formatting.ts:26:24

• formatting.ts:231 formatPlain
    src/formatting/formatting.ts:231:23

• formatting.ts:26 toPlain
    src/formatting/formatting.ts:26:25

• formatting.ts:36 formatPlain
    src/formatting/formatting.ts:36:30

• formatting.ts:26 toPlain
    src/formatting/formatting.ts:26:25

• MatrixBridge.ts:53 <anonymous>
    src/bridge/MatrixBridge.ts:53:19

• MatrixBridge.js:27 <anonymous>
    src/bridge/MatrixBridge.js:27:71

 

But are the instructions from @dual-o above really valid or did he probably made changes and didn't post them?

Maybe I'm currently a little slow lol.

hopefully somebody is willing to help me out here.

 

Best regards

 

Link to comment
Share on other sites

Hey guys,
here is my little quick-and-drity how-to... hope it helps 😃

The idea behind the scenes is that ScripterX passes all the needed informations to the webhook. Unfortunately this does not work direct. So I write this little shell script as transmitter.

First of all we need to configure ScripterX to pass the needed parameters when "OnMediaItemAddedComplete" is triggered.
For movies we need Name, Year, IMDB ID and the Library. The IMDB ID is required for later link-creation and that false scraped elements without IMDB ID wouldn't sent to the webhook.
image.png.84a75aff1d415380b1a71192b0541819.png

for series...
image.png.3f009f5dfb8ce2ceaec252f2b138064d.png

for seasons...
image.png.8c56ef7cded99987cb183ef180b8cb53.png
note: "%item.isvirtual% Equals FALSE" prevent notifications for upcoming or missing seasons

webhook.sh shell script:

#!/bin/bash

# Variables
imdb_pattern='^tt[0-9]*$'
webhook_url='https://webhook.domain.com/hook/pvp***f53'

webhook() {
    curl -X POST "$webhook_url/emby" -H "Content-Type: application/json" -d "$1"
}

if [[ $1 == Movie ]] && [[ "$4" =~ $imdb_pattern ]]; then
    # Check if item.type is Movie and item.meta.imdb is set
    webhook "{\"movie\":\"$2\",\"year\":\"$3\",\"imdb\":\"$4\",\"library\":\"$5\"}"
elif [[ $1 == Series ]] && [[ "$3" =~ $imdb_pattern ]]; then
    # Check if item.type is Series and item.meta.imdb is set
    webhook "{\"series\":\"$2\",\"imdb\":\"$3\",\"library\":\"$4\"}"
elif [[ $1 == Season ]] && [[ "$4" =~ $imdb_pattern ]]; then
    # Check if item.type is Season and item.meta.imdb is set
    webhook "{\"season\":\"$2\",\"series\":\"$3\",\"imdb\":\"$4\"}"
fi

note:
The if statement checks the first parameter ($1), we remember it was "%item.type%", for movie, series or season that was added to the library. The second parameter ($2) is the IMDB ID "%xxx.meta.imdb%". We remember false scraped elements without IMDB ID souldn't sent to the webhook. The IMDB ID will be checked via regex (imdb_pattern).
After that, we send a different hook for movies series and seasons. I have made this separation so that the emby-plugin from the matrix-webhook-gateway can differentiate between the messages to be sent. 

emby.ts plugin:

import { is } from 'typescript-is';
import { PluginBase, WebhookMessage } from '../../src/pluginApi/v2';
import {
    a,
    strong,
    fmt,
} from '../../src/formatting/formatting';

type movie = {
    movie: string;
    year: string;
    imdb: string;
    library: string;
};
type series = {
    series: string;
    imdb: string;
    library: string;
};
type season = {
    season: string;
    series: string;
    imdb: string;
};

export const format = 'emby';

export default class EmbyPlugin extends PluginBase {
  // This function will be executed once, on startup.
  async init(): Promise<void> {
    this.logger.info('emby plugin starting up');
  }

  // This function will be executed every time a webhook with a matching
  // format is posted. It should either return a `WebhookMessage`, if the
  // webhook is to be executed, or `undefined`, if the webhook is to be
  // rejected.
  async transform(body: unknown): Promise<WebhookMessage | undefined> {
    // You can make use of 'typescript-is' to perform runtime type checks on
    // input data. This makes it easy to reject invalid webhooks.
    if (is<movie>(body)) {
        var link = 'https://www.imdb.com/title/' + body.imdb;
        var titel = body.movie + ' (' + body.year + ')';
        return {
            // username: 'Emby Bot',
            text: fmt(
                'The Movie ',
                strong(a(link,titel)),
                ' was added to the ',
                body.library,
                ' library',
            ),
          };
    }
    if (is<series>(body)) {
        var link = 'https://www.imdb.com/title/' + body.imdb;
        return {
            // username: 'Emby Bot',
            text: fmt(
                'The Series ',
                strong(a(link,body.series)),
                ' was added to the ',
                body.library,
                ' library',
            ),
          };
    }
    if ((is<season>(body) {
        var link = 'https://www.imdb.com/title/' + body.imdb;
        return {
            // username: 'Emby Bot',
            text: fmt(
                'Season ',
                body.season,
                ' was added to ',
                strong(a(link,body.series)),
            ),
          };

    } else {
        this.logger.warn('Invalid webhook');
        this.logger.warn(body);
        return undefined;
    }
  }
}

note: as you can see the plugin also differentiate between the type movie, series and seasons. Additionally we use the IMDB ID to generate a IMDB link inside the message.

When all is configured right you get the messages from the bot. (and click on the blue link in the message to open the IMDB page of the movie/series)

movie example:
image.png.2da1c87c2aa9557d2dc21939f1a4edf4.png

season example:
image.png.cddc173d9fc99c77e06cf1fa3ef001ff.png

series example:
This doesn't work. Seems like ScripterX doesn't check series "OnMediaItemAddedComplete"... @Anthony Musgrove can you help with this behavior?

Finally it would be nice to have some feedback to make emby notifications via matrix cleaner, leaner or more efficiant 🙂

regards Dual-O

 

Edited by dual-o
  • Thanks 1
Link to comment
Share on other sites

horstepipe

@dual-o
so you didn't set it up to send a message if you add a single episode?

Just wondering whether you didn't have the need for it or whether there are other reasons

Edited by horstepipe
Link to comment
Share on other sites

3 minutes ago, horstepipe said:

@dual-o
so you didn't set it up to send a message if you add a single episode?

Just wondering whether you didn't have the need for it or whether there are other reasons

I've done this in the past, but it was a little bit like a spam bot. 😅
Example: add the whole Simpson Series... and you will receive 716 messages... 

but yes we can add this to a updated version of the shell script and make it optional 

  • Thanks 1
Link to comment
Share on other sites

horstepipe
56 minutes ago, dual-o said:

I've done this in the past, but it was a little bit like a spam bot. 😅
Example: add the whole Simpson Series... and you will receive 716 messages... 

but yes we can add this to a updated version of the shell script and make it optional 

yeah okay, but without that you won't be notified if a single new episode comes up I guess?
That's quite important here, will see if I can figure it out.

Link to comment
Share on other sites

horstepipe

so here are my edits.

webhook.sh:
 

#!/bin/bash

# Variables
imdb_pattern='^tt[0-9]*$'
webhook_url='127.0.0.1:8020/hook/xxxxx'

webhook() {
    curl -X POST "$webhook_url/emby" -H "Content-Type: application/json" -d "$1"
}

if [[ $1 == Movie ]] && [[ "$4" =~ $imdb_pattern ]]; then
    # Check if item.type is Movie and item.meta.imdb is set
    webhook "{\"movie\":\"$2\",\"year\":\"$3\",\"imdb\":\"$4\",\"library\":\"$5\"}"
elif [[ $1 == Series ]] && [[ "$3" =~ $imdb_pattern ]]; then
    # Check if item.type is Series and item.meta.imdb is set
    webhook "{\"series\":\"$2\",\"imdb\":\"$3\",\"library\":\"$4\"}"
elif [[ $1 == Season ]] && [[ "$4" =~ $imdb_pattern ]]; then
    # Check if item.type is Season and item.meta.imdb is set
    webhook "{\"season\":\"$2\",\"series\":\"$3\",\"imdb\":\"$4\"}"
elif [[ $1 == Episode ]] && [[ "$5" =~ $imdb_pattern ]]; then
    # Check if item.type is Season and item.meta.imdb is set
    webhook "{\"episode\":\"$2\",\"season\":\"$3\",\"series\":\"$4\",\"imdb\":\"$5\"}"

fi

EmbyWebhook.ts
 

import { is } from 'typescript-is';
import { PluginBase, WebhookMessage } from '../../src/pluginApi/v2';
import {
    a,
    strong,
    fmt,
} from '../../src/formatting/formatting';

type movie = {
    movie: string;
    year: string;
    imdb: string;
    library: string;
};
type series = {
    series: string;
    imdb: string;
    library: string;
};
type season = {
    season: string;
    series: string;
    imdb: string;
}
type episode = {
    episode: string;
    season: string;
    series: string;
    imdb: string;
}
;
export const format = 'emby';

export default class EmbyPlugin extends PluginBase {
  // This function will be executed once, on startup.
  async init(): Promise<void> {
    this.logger.info('emby plugin starting up');
  }

  // This function will be executed every time a webhook with a matching
  // format is posted. It should either return a `WebhookMessage`, if the
  // webhook is to be executed, or `undefined`, if the webhook is to be
  // rejected.
  async transform(body: unknown): Promise<WebhookMessage | undefined> {
    // You can make use of 'typescript-is' to perform runtime type checks on
    // input data. This makes it easy to reject invalid webhooks.
    if (is<movie>(body)) {
        var link = 'https://www.imdb.com/title/' + body.imdb;
        var titel = body.movie + ' (' + body.year + ')';
        return {
            // username: 'Emby Bot',
            text: fmt(
                'Der Film ',
                strong(a(link,titel)),
                ' wurde hinzugefügt zu ',
                body.library,
                ' library',
            ),
          };
    }
    if (is<series>(body)) {
        var link = 'https://www.imdb.com/title/' + body.imdb;
        return {
            // username: 'Emby Bot',
            text: fmt(
                'The Series ',
                strong(a(link,body.series)),
                ' was added to the ',
                body.library,
                ' library',
            ),
          };
    }
    if ((is<season>(body) {
        var link = 'https://www.imdb.com/title/' + body.imdb;
        return {
            // username: 'Emby Bot',
            text: fmt(
                'Season ',
                body.season,
                ' was added to ',
                strong(a(link,body.series)),
            ),
          };

    }
    if ((is<episode>(body) {
        var link = 'https://www.imdb.com/title/' + body.imdb;
        return {
            // username: 'Emby Bot',
            text: fmt(
                'Season''Episode ',
                body.season,
                ' was added to  ',
                strong(a(link,body.series)),
            ),
          };

    } else {
        this.logger.warn('Invalid webhook');
        this.logger.warn(body);
        return undefined;
    }
  }
}

 

Link to comment
Share on other sites

horstepipe

any idea what's going wrong here?
 

Info Emby ScripterX: onMediaItemAddedComplete: "Movie" "Beckett" "2021" "tt10230994" "error: System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at EmbyScripterX.Core.ScripterXContextFactory.<>c__DisplayClass14_0.<Item>b__0(VirtualFolderInfo x)
   at System.Collections.Generic.List`1.Find(Predicate`1 match)
   at EmbyScripterX.Core.ScripterXContextFactory.Item(BaseItem item, BaseItem parent)"

parameter are:
 

"%item.type%" "item.name%" "%item.productionyear%" "%item.meta.imdb%" "%item.library.name%"

 

Edited by horstepipe
Link to comment
Share on other sites

horstepipe
12 hours ago, dual-o said:

Have you already tried removing each element one by one to see which is causing the error?

soo it is

"%item.library.name%" which is causing the error.

Do you or anyone else have multiple movie libraries and this setup working?

But it is no dealbreaker

Edited by horstepipe
Link to comment
Share on other sites

horstepipe

anybody has an idea for a workaround?

Looks like there is no option to add a "contains" statement? Then I could check for 4k folder / no 4k folder.

Link to comment
Share on other sites

  • 3 months later...
vincentli806
On 5/19/2020 at 6:49 PM, Anthony Musgrove said:

Hi guys, I just thought I'd introduce the latest feature of Emby Scripter-X (v2.3.5+) out now on the catalog.

 

Emby Scripter-X Webhooks

 

Why? I notice that a lot of Scripter-X users use the plugin to perform some type of webhook(s) based on certain events, so why not natively support it?

 

So how does it work?  Pretty much as-per-usual with the Actions interface.

 

5ec3b83e1a7c5_onAuthenticationFailed_Web

 

The interface will eventually change input field labels based on the selection in the interpreter box, but it is as above:

 

Run [script], where [script] is the URL of your webhook script (webhook endpoint/api endpoint, etc), in my example above, I have a php script running on my local webserver at http://192.168.1.10/hook.php.

 

[interpreter] should be set to web:post (to post data), web:get is also available, however it does exactly that - doesn't post data.

 

[Parameters] instead of listing parameters to send over the command-line, this is the path to your json content template - where your data is formatted from.   Here I specify d:\embyscripts\hooktemplates\authenticationfailed.json, and in this file is the following content:

{
	"user": {
		"username": "%username%",
		"password": "%password%"
	},

	"device": {
		"id": "%device.id%",
		"name": "%device.name%",
		"ip_address": "%device.remote.ipaddress%"
	},

	"scripterx_version": "%scripterx.version%",

	"emby_server_version": "%server.version%"
}

It should be self explanatory from here.  So ScripterX will read in your template, substitute for the token values, and post it to the specified URL.

 

So my PHP script simply takes $_POST['data'], casts it as a JSON object, and outputs to a file on my webserver:

Array
(
    [user] => Array
        (
            [username] => Anthony
            [password] => asdfasdfasdffasgfsasgasdfasdf
        )

    [device] => Array
        (
            [id] => 427a7309-5fc2-489d-9a83-0664c619c779
            [name] => Firefox
            [ip_address] => 192.168.1.124
        )

    [scripterx_version] => 2.3.4.0
    [emby_server_version] => 4.4.2.0

But as you can see --- these templates are entirely customisable, so you can adjust these templates to suit whatever you're posting your data to.

 

The only thing currently that I haven't made customisable is the POST variable that contains the json data, which is set to 'data'.  I will eventually extend the functionality such that the JSON data can be submitted without the need for POST variables.

 

Any comments/feedback/suggestions are greatly appreciated as always.

Hi Anthony!

Hope you are doing well and thanks for building this plugin which is great!

I just learned from this thread that eventually you will 'extend the functionality such that the JSON data can be submitted without the need for POST variables.'

May I know the progress about this?  I am asking this because the http endpoint of my use case is not accepting 'data' variable and the endpoint not owned by me so I cannot change the parameter setting from server side to align with 'data' variable.

Alternatively I have just created batch/shell script and called external libraries to resolve this.  Although I can manage to implement what I want, it would be much appreciated if you can complete the next version with this feature - 'json submission without the need for POST variables.' 

Thank you.

Edited by vincentli806
Link to comment
Share on other sites

  • 2 weeks later...

First of all, thank you for developing such a good plug-in,

Now I can get the event of emby use the web:POST(use node-red to parse msg.data)

Some questions about JSON templates:

Is there any way to add the current state triggered by which event in the JSON template?

Because I want to use the same JSON template  to send web:POST

For example:

{
	"user": {
		"username": "%username%",
		"password": "%password%"
	},

	"device": {
		"id": "%device.id%",
		"name": "%device.name%",
		"ip_address": "%device.remote.ipaddress%"
	},
	"scripterx_version": "%scripterx.version%",
	"emby_server_version": "%server.version%",
	"action": "onAuthenticationSuccess"
}

  in the last line  "action": "onAuthenticationSuccess",I hope that “onAuthenticationSuccess” is a variable.

Use Google translation, Sorry for my poor English,Thank you again.

 

Edited by suwill
Link to comment
Share on other sites

4 hours ago, suwill said:

First of all, thank you for developing such a good plug-in,

Now I can get the event of emby use the web:POST(use node-red to parse msg.data)

Some questions about JSON templates:

Is there any way to add the current state triggered by which event in the JSON template?

Because I want to use the same JSON template  to send web:POST

For example:

{
	"user": {
		"username": "%username%",
		"password": "%password%"
	},

	"device": {
		"id": "%device.id%",
		"name": "%device.name%",
		"ip_address": "%device.remote.ipaddress%"
	},
	"scripterx_version": "%scripterx.version%",
	"emby_server_version": "%server.version%",
	"action": "onAuthenticationSuccess"
}

  in the last line  "action": "onAuthenticationSuccess",I hope that “onAuthenticationSuccess” is a variable.

Use Google translation, Sorry for my poor English,Thank you again.

 

Now, This problem has been solved. Write it to the tutorial when I'm not busy

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...