Jump to content

Tutorial/exemple on plugins/channel creation


AMenard
Go to solution Solved by Luke,

Recommended Posts

AMenard

Hi,

 

Is there some material on how to develop plugins/channel for emby? Or some exemple code that someone can study and/or adapt?

An API function list/call?

 

Maybe I'm not looking at the right place... For exemple, I've found a link to the wiki but the plugin and channel entry is more about how to install them than how to create them.

Link to comment
Share on other sites

Anthony Musgrove

Im in the process of writing a book on it :)

Link to comment
Share on other sites

  • 3 months later...
bakes82

@Luke
This is way I prefer a nice api instead lol less issues about how to do things. As long and I can interface the api I can use whatever front end I want 😛.  When you guys moving to blazor ;) 

Link to comment
Share on other sites

chef
On 10/2/2020 at 8:29 PM, bakes82 said:

@Luke
This is way I prefer a nice api instead lol less issues about how to do things. As long and I can interface the api I can use whatever front end I want 😛.  When you guys moving to blazor ;) 

I have some plugin examples for channels on my github.

https://github.com/chefbennyj1/Emby.NewReleasesChannel

 

 

Link to comment
Share on other sites

bakes82
6 hours ago, chef said:

I have some plugin examples for channels on my github.

https://github.com/chefbennyj1/Emby.NewReleasesChannel

 

 

You got a screen shot of what that does?  Does it show New movies/tv shows for that week/month I assume?  Idk what a channel is lol.  Why are these not in the global plugin place, or a way to add repos so we dont need to download and add to plugins folder.

Link to comment
Share on other sites

chef
3 hours ago, bakes82 said:

You got a screen shot of what that does?  Does it show New movies/tv shows for that week/month I assume?  Idk what a channel is lol.  Why are these not in the global plugin place, or a way to add repos so we dont need to download and add to plugins folder.

Some of those plugins (in the repo) are in the catalog, but some others would... 'over-extended'... the criteria to be put in there (if you know what I mean, ex. u#orren# plugin doesn't really have a place in the catalog). They are just some personal ones I made for my setup to fulfill a need.

When you mentioned plugin/channels, I thought you meant: how do you build a channel plugin. :)

I really enjoy writing code for emby plugins, so if you have any questions I can help with, lemme know :)

 

Edited by chef
Link to comment
Share on other sites

bakes82
44 minutes ago, chef said:

Some of those plugins (in the repo) are in the catalog, but some others would... 'over-extended'... the criteria to be put in there (if you know what I mean, ex. u#orren# plugin doesn't really have a place in the catalog). They are just some personal ones I made for my setup to fulfill a need.

When you mentioned plugin/channels, I thought you meant: how do you build a channel plugin. :)

I really enjoy writing code for emby plugins, so if you have any questions I can help with, lemme know :)

 

Well you want to just write this :P  My stuff is all remote hosted so its a PITA to put dlls on to docker, especially if I cant test via a console app or something first.

Its pretty simple I think, I did it in plex but as blazor app.

You just need 1 object to store a Collection Name, a list of trakt URLs, library to create collection in.

You parse the URLS.

Then you just send those to the trak.net api and you can get back the IMDB/TMDB/TVDB to check if its in the Emby library.
https://github.com/henrikfroehling/Trakt.NET

In trakt you setup your own API client/secret

public static class TraktHelpers
    {
        public static TraktListFields TraktListUriParse(string uri)
        {
            uri = uri.ToLower();
            if (uri.Contains("/users/") && uri.Contains("/lists/"))
            {
                var questionMarkIndex = uri.IndexOf('?');
                if (questionMarkIndex > 0)
                {
                    uri = uri.Remove(questionMarkIndex);
                }

                var splitString = uri.Split("/users/")[1].Split("/lists/");
                var userName = splitString[0];
                var listName = splitString[1];
                
                return new TraktListFields{Username = userName, ListName = listName};
            }

            return null;
        }
    }

    public class TraktListFields
    {
        public string Username { get; set; }
        public string ListName { get; set; }
    }
if (urls != null)
            foreach (var url in urls)
            {
                var userLists = TraktHelpers.TraktListUriParse(url);

                if (userLists == null || userLists.Username.IsNullOrEmpty() || userLists.ListName.IsNullOrEmpty())
                {
                    MoviesListCompare = new List<MovieDto>();

                    await _toastObj.Show(new ToastModel
                    {
                        Title = "Error!",
                        Content = $"Can not parse user name or list from uri {url}",
                        CssClass = "e-toast-danger",
                        Icon = "e-danger toast-icons",
                        TimeOut = 15000
                    });
                }

                if (userLists != null) traktListFields.Add(new TraktListFields {Username = userLists.Username, ListName = userLists.ListName});
            }

        var clientId = Configuration["Trakt:ClientId"];
        var clientSecret = Configuration["Trakt:ClientSecret"];
        var traktClient = new TraktClient(clientId, clientSecret)
        {
            Authorization = TraktAuthorization.CreateWith(TraktKey)
        };

        var items = new List<TraktPagedResponse<ITraktListItem>>();
        foreach (var list in traktListFields)
        {
            items.AddRange(await traktClient.Users.GetCustomListItemsAsync(list.Username, list.ListName));
        }

        MoviesListCompare = new List<MovieDto>();

        var plexMovies = PlexLibraryInfo.MediaContainer.Metadata;

        foreach (var traktPagedResponse in items)
        {
            foreach (var traktListItem in traktPagedResponse.Value)
            {
                if (traktListItem.Type == TraktListItemType.Movie)
                {
                    var plexHas = PlexHelpers.PlexHasItem(plexMovies, traktListItem.Movie.Title, traktListItem.Movie.Ids.Imdb, traktListItem.Movie.Year ?? 0);
                    var plexWatched = PlexHelpers.PlexWatchedItem(plexMovies, traktListItem.Movie.Title, traktListItem.Movie.Ids.Imdb, traktListItem.Movie.Year ?? 0);
                    MoviesListCompare.Add(new MovieDto
                    {
                        Title = traktListItem.Movie.Title,
                        Description = plexHas != null ? plexHas.Summary : traktListItem.Movie.Overview,
                        TraktHas = true,
                        Year = traktListItem.Movie.Year,
                        IMDB = traktListItem.Movie.Ids.Imdb,
                        PlexHas = plexHas != null,
                        PlexWatched = plexWatched != null,
                        Duration = plexHas != null
                            ? (plexHas.Duration / 1000).ToString()
                            : (traktListItem.Movie.Runtime ?? 0 * 60).ToString(),
                        Rating = plexHas != null ? plexHas.ContentRating : traktListItem.Movie.Certification,
                        PlexRatingKey = plexHas != null ? plexHas.RatingKey : string.Empty,
                        Art = plexHas != null ? ServerUrl + plexHas.Thumb + "?X-Plex-Token=" + PlexKey : string.Empty
                    });
                }
            }
        }

 

Trakt Login

var clientId = Configuration["Trakt:ClientId"];
        var traktRedirectUrl = NavigationManager.BaseUri + "TraktReturn";
        var redirectUrl =
            $"https://trakt.tv/oauth/authorize?client_id={clientId}&redirect_uri={traktRedirectUrl}&response_type=code";
        NavigationManager.NavigateTo(redirectUrl);

Return From trakt

 var clientId = Configuration["Trakt:ClientId"];
        var clientSecret = Configuration["Trakt:ClientSecret"];

        var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
        var code = string.Empty;
        if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("code", out var codeOut))
        {
            code = codeOut;
        }

        if (code.IsNotNullOrEmpty())
        {
            var traktClient = new TraktClient(clientId, clientSecret);
            traktClient.Authentication.RedirectUri = NavigationManager.BaseUri + "TraktReturn";
            var authResp = await traktClient.Authentication.GetAuthorizationAsync(code);

            await ProtectedLocalStore.SetAsync("TraktKey", authResp.Value.AccessToken);
            TraktLoginState.LoggedIn = true;
            NavigationManager.NavigateTo("/trakt");
        }

 

 

Link to comment
Share on other sites

bakes82

@chefI just got your NewReleases installed, if you could do the exact same things put using a trakt list I think thats exactly what most people want ;)

 

Link to comment
Share on other sites

chef
Just now, bakes82 said:

@chefI just got your NewReleases installed, if you could do the exact same things put using a trakt list I think thats exactly what most people want ;)

 

This is interesting :)

You'll have to forgive me, I'm not a trakt user (yet...), I have couple questions, so I can get an idea of the scope of the project. 

 

The trakt plugin that emby already has, will sync watched states between trakt  and emby, but this plugin is different.

The idea is to create a "Trakt" library collection option (appearing on the emby home screen), which will show media items from Trakt (that are not already in the Emby database).

 

What happens when we view the item details page (the Hero page of the Movie/Episode) and we press the play button? 

 

 

Link to comment
Share on other sites

bakes82

@chef No this plugin is the reverse of what you are saying, It will SHOW what movies or tv shows MATCH on the list.  (If you want to show the "missing" things also would be wonderful but I think thats not priority)

So many people like to have a collection that shows you know the top 250 Imdb movies, or all the James Bond Movies.  I usually say "Collections" but I like this library format also, less clicking.  But I guess you could make that a toggle.

So there are many automated trakt lists or people can make their own, then the plugin will get that list every night and if emby has it, put it in that collection/libary whatever we are calling them :).

Heres some ref:

https://trakt.tv/users/nielsz/lists/active-imdb-top-250?sort=rank,asc

https://trakt.tv/users/giladg/lists/top-10-movies-of-the-week?sort=added,asc

 

Just  FYI this is a highly requested item in Plex and Emby

  • Like 1
Link to comment
Share on other sites

bakes82

So UI:

  • Name of Library to make
    • URL1
    • URL2
    • URLN
  • Create Library (Like NewReleases Does) or Create Collection
    • If Collection -> What Libraries do you want the collections in

Backend:
Make sure to removed items no longer on list when comparing.

Link to comment
Share on other sites

  • 1 year later...
On 08/10/2020 at 02:11, chef said:

This is interesting :)

You'll have to forgive me, I'm not a trakt user (yet...), I have couple questions, so I can get an idea of the scope of the project. 

 

The trakt plugin that emby already has, will sync watched states between trakt  and emby, but this plugin is different.

The idea is to create a "Trakt" library collection option (appearing on the emby home screen), which will show media items from Trakt (that are not already in the Emby database).

 

What happens when we view the item details page (the Hero page of the Movie/Episode) and we press the play button? 

 

 

Hey @chef did you ever get anywhere with making a plugin to use trakt lists as 'collections' inside of Emby?

Link to comment
Share on other sites

1 hour ago, smernt said:

Hey @chef did you ever get anywhere with making a plugin to use trakt lists as 'collections' inside of Emby?

@bakes82 made an awesome one.

Link to comment
Share on other sites

43 minutes ago, chef said:

@bakes82 made an awesome one.

Thanks, I've just tried it but couldn't get it to work, it created a 'channel' but didn't pull anything into it, even though I did the trakt pin & have apple tv content on my server etc.

Also, how do I now remove the 'AppleTV' channel that it created? there's no delete button & I can't see it referenced as a library or collection...

Link to comment
Share on other sites

bakes82

I don't use emby, wasnt worth the effort. I also dont think this dude is a dev and wants one pre built which doesnt exist.

Code is here youll need to update the server api Im sure.  I recall there being some random issues but maybe they been fixed in the past year but doubtful ;)

https://github.com/bakes82/TraktLists

  • Like 1
Link to comment
Share on other sites

  • 4 months later...
bakes82

@chef Whats this doing in your "NewReleases" channel code?  I have a channel but if I update something in the config it doesnt seem to take until server restart its like channels only load the config 1x?  Does this get around that?
 

private Task<ChannelItemResult> GetChannelItemsInternal()
        {
            Plugin.Instance.UpdateConfiguration(Plugin.Instance.Configuration);
            var config = Plugin.Instance.Configuration;

 

Link to comment
Share on other sites

chef
32 minutes ago, bakes82 said:

@chef Whats this doing in your "NewReleases" channel code?  I have a channel but if I update something in the config it doesnt seem to take until server restart its like channels only load the config 1x?  Does this get around that?
 

private Task<ChannelItemResult> GetChannelItemsInternal()
        {
            Plugin.Instance.UpdateConfiguration(Plugin.Instance.Configuration);
            var config = Plugin.Instance.Configuration;

 

Hmm... Might have been early code that didn't make sense.

 

However, on first run of a plugin,  if the plugin configuration class doesn't have a constructor, calling update will create the config, and the xml with default values, or null.

 

I think it is proper to have a constructor in the plugin configuration class.

 

That is a cheesy hack to force the plugin configuration to initialize the first time the plugin runs in the server. 

 

Link to comment
Share on other sites

To get your channel content to change immediately, have the channel implement the interface IHasChangeEvent, and then fire the ContentChanged event.

To do that from some other class such as your plugin, you could do:

        public override void UpdateConfiguration(BasePluginConfiguration configuration)
        {
            base.UpdateConfiguration(configuration);

            _channelManager.GetChannel<Channel>().OnContentChanged();
        }

 

Link to comment
Share on other sites

  • 4 weeks later...
DragonSkills99

@Luke

How can I debug my plugin? I'm quite confused with the "https://github.com/MediaBrowser/Emby" Repo, as it has many projects and I don't know what to start and where to put/include the plugin.

 

Alternatively is there any lightweight example for adding a TV Provider (more the where to put and how to save data in the normal nfo files than how to request/parse remote data, as I've already completed writing the API/Website-Wrapper)

But even with that example I would like it very much to be able to debug my Plugin...

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