Jump to content

Plugin created not showing name


DarKni8

Recommended Posts

DarKni8

So after my last thread and going through logs , I was able to make sure my plugin solution is building properly and not giving error in emby.

But now problem is I am not getting my plugin name on emby plugin page and not even getting the configpage i created for plugin.

 

Also i wanted to ask , is this happening because I am on beta and the guide for plugin creation is in old .NET framework!>??

Here is the link to repo. 

DoucheHellSung/YAScrapper: Yet Aother Scrapper (github.com)

 

Here is screenshot of what i am getting

image.png.e5a3ace8773893f9549d1e4cb29f1735.png

Link to comment
Share on other sites

chef

Hey! Sorry I missed this.

If you have any other questions let me know.

More plugin developers, means more plugins... More plugins the better! 😃👍

 

DiskSpace on my GitHub uses a lot of the different plugin tricks. 

Recently, I posted an AI recommendations plugin on GitHub that does some cool things with .dll dependancies. 😃

  • Like 1
Link to comment
Share on other sites

chef

Okay! Cool! Your repo is looking good!

You'll probabaly want to remove the IHttpClient from the plugin class. Keep only the values that structure the plugin in that class. Example: Id, Name, Configuration Pages, etc.

 You could create a "PluginServerEntryPoint" class that implements the IServerEntryPoint interface.

Put any implementations for interacting with the server in there.

Then, you could create an API folder, and place a "Service" class file, which implements IService.

That file would handle any connection from the front end using Routes (like in ASP), and any connections to other services (like the Provider service, you are connecting too). 

 

Edited by chef
  • Like 1
Link to comment
Share on other sites

DarKni8
14 minutes ago, chef said:

Okay! Cool! Your repo is looking good!

You'll probabaly want to remove the IHttpClient from the plugin class. Keep only the values that structure the plugin in that class. Example: Id, Name, Configuration Pages, etc.

 You could create a "PluginServerEntryPoint" class that implements the IServerEntryPoint interface.

Put any implementations for interacting with the server in there.

Then, you could create an API folder, and place a "Service" class file, which implements IService.

That file would handle any connection from the front end using Routes (like in ASP), and any connections to other services (like the Provider service, you are connecting too). 

 

Thanks chef i will keep the points you highlighted in mind, Need more people like you who are cheerful in helping others. Thanks again

Also I am sulking through your repos to learn ways. 

:D thanks again.

 

@chef Also if you look at my repo i have defined config page for plugin just to show its name ! but still when i click on my plugin icon, it just doesnt load anything. How can i track and log these kind of things?

Edited by DarKni8
  • Haha 1
Link to comment
Share on other sites

chef
On 3/19/2022 at 10:16 AM, DarKni8 said:

Thanks chef i will keep the points you highlighted in mind, Need more people like you who are cheerful in helping others. Thanks again

Also I am sulking through your repos to learn ways. 

:D thanks again.

 

@chef Also if you look at my repo i have defined config page for plugin just to show its name ! but still when i click on my plugin icon, it just doesnt load anything. How can i track and log these kind of things?

Did you make sure your plugin html pages and js pages are set as embedded resources?

 

Link to comment
Share on other sites

chef

 

Also in your html, make sure you have  the data-controller attribute set to link the javascript.

 

 data-controller="__plugin/MyPluginConfigurationPageJS"

 

example:

<div data-role="page" style="align-items: center; margin: auto;" class="type-interior pluginConfigurationPage withTabs MyPluginConfigurationPage" data-require="emby-button,emby-input,emby-select" data-controller="__plugin/MyPluginConfigurationPageJS">
    <div class="content-primary">
        <div class="verticalSection verticalSection">
            <br />
            <div class="pagingContainer">
                <div class="tablesContainer">
                   
                </div>
            </div>
        </div>
    </div>

</div>

which was created in the Plugin.cs

 

                new PluginPageInfo()
                {
                    Name                 = "MyPluginConfigurationPageJS",
                    EmbeddedResourcePath = GetType().Namespace + ".Configuration.MyPluginConfigurationPage.js",
                },

 

which tells the plugin to look for the file "MyPluginConfiguration.js" page that has been set as an embedded resource.

 

Does that make sense to you?

😬

Edited by chef
  • Thanks 1
Link to comment
Share on other sites

DarKni8

@chef yup it makes sense to me , its like giving address to my controller to let know where exactly configpage is located.

But i have already given it on line 49 , and embedded script inside html under <script> tag, I will try restructuring how u said and see how things go.

image.png.12c303aeebcb69236b54c2a0fbd67fac.png

 

  • Like 1
Link to comment
Share on other sites

chef

Perhaps giving the name of the resource something other then "this.Name".

Use: "configPage", or something more specific "YaScrapperConfigurationPage"

I should just snag the repo... One moment 😆👍

Link to comment
Share on other sites

chef

Okay, a couple things to be aware about.

Unfortunately, you can't easily add references to dependancies that Emby doesn't ship with.

HtmlAgilityPack won't work when you load the plugin because it doesn't ship with emby.

If you want to use that library, I do have a way to ship your plugin with the dll as embedded resource, and reference it, to load using Reflection. I can share that with you if you want. It gets a little advanced, but it isn't impossible.

The other thing I wanted to mention, is that you don't have to add any JSON dependancies to your project because Emby has an Interface which can handle JSON serialization, and deserialization. It is called IJsonSerializer.

It will load through Emby's Dependancy Injection, so you only have to add it to a constructor to use the object. 

 

You do, however, want to include the "System.Memory" reference in your dependancies. 👍

Let me know if I can be of more help, if you want the code to side load dll's in your project.

 

Edited by chef
  • Like 1
Link to comment
Share on other sites

chef

Sorry, there is more...

You don't need the html, and body tags in your html documents because emby loads those pages inside it's html application.

Also, you may consider moving the JavaScript to its own file to keep things clean and organized 😎

And.. maybe consider.... moving away from jQuery...? 😬

That is a personal choice I suppose, but I'm not sure if emby even ships with jQuery libraries anymore... It might... 

 

Let me know if I'm just being annoying now 😆

Edited by chef
  • Like 1
Link to comment
Share on other sites

BillOatman
2 hours ago, chef said:

The other thing I wanted to mention, is that you don't have to add any JSON dependancies to your project because Emby ships with an Interface which can handle JSON serialization, and deserialization. It is called IJsonSerializer.

I suspect it just uses the Microsoft System.Json which is used almost identically to newtonsoft.

  • Like 1
Link to comment
Share on other sites

BillOatman
2 hours ago, chef said:

Also, maybe... consider.... moving away from jQuery...? 😬

That is a personal choice I suppose, but I'm not sure if emby even ships with jQuery libraries anymore... It might... 

I don't think it does.

  • Like 1
Link to comment
Share on other sites

For  now the web app does, but avoid writing any new code with jQuery. Eventually it will be removed and only pulled in via CDN url when requested, and some users will complain about that.

  • Like 1
  • Agree 1
  • Thanks 1
Link to comment
Share on other sites

BillOatman

@DarKni8  I just gutted that Comskipper plugin code to be just a generic plugin with a enable/disable config control.

I'll try it when I get home and when it works, I'll post a github link.

  • Like 1
Link to comment
Share on other sites

chef

I actually made a skeleton here too.

YAScrapper-master.zip

 

 

 

 

@BillOatman

That's what we should do, maybe create a github tutorial with a skeleton project to start people off with.

Just the barebones example of an  IService, IServerEntryPoint, and the Plugin class.

With a couple embedded web pages so they can see how to wire them up and get it working.

a quick write up about what is necessary, and what are the limitations as well.

If there is time LOL!

  • Agree 1
Link to comment
Share on other sites

DarKni8

Hi @chef, @BillOatman and @Luke

You guys are awesome

Oh lol about that HTML AND BODY TAG, i took it from roku bif plugin i guess....😅Even i dont like jquery and definitely will link a js file and write script there without jquery and in a proper structure.

 

Yes a skeleton project will be great to kick off things for newbies like me. Otherwise it becomes difficult. But now i have understood basic structure of it but could be awesome dor future.

 

Also i dont see documentation for metadat provider api which is used by luke and others in their plugin to provide metadata. I searched on tutorial repo, am i missing something here? Or its not made public for some reason?

 

Thanks again Chef, bill and luke for dealing patiently with this situation, have to learn lots from you guys😁

 

@chef i have seen plugins using HTML Agility...what do you suggest i should do , as its a must have thing for me. I will remove newtonsoft json though thanks for pointing it out.

 

Edited by DarKni8
Link to comment
Share on other sites

chef
5 hours ago, DarKni8 said:

@chef i have seen plugins using HTML Agility...what do you suggest i should do , as its a must have thing for me.

Ah yes, loading dependencies on command. This is a super cool trick.

To do this we are going to create an event handler on the AppDomain Assembly, so when the plugin loads and it tries to use a dependency that isn't there it will load it. Cool right?!?

 

  1. Add the Dependency to your project for the  Dynamic Link Library you want to use.

In your case you will add the HTMLAgilityPack dependency. You have already done this. 

We will need the library accessible while you are coding it. However, issues happen when you want to load your plugin into Emby.

Emby doesn't have this library, and your plugin can only use the libraries that Emby uses... not for long! 

 

      2. Create a folder in your project called "References"

 

     3. Click on the "Reference" folder, and "Add an Existing Item"

img_add_resource_1.png.b50e607cd699c491be043cdedd05fa2d.png

 

     4. Search for "HTMLAglilityPack.dll"

It is most likely saved in a folder on your computer called ".nuget/packages/HTMLAgilityPack". This is where Visual studio keeps all it's nuget libraries.

Be sure to Select "AllFiles (*.*)" to show the DLL in the open window

I'm on Windows, this is what I see below.

img_add_resource_2.thumb.png.3b155ba4709dd66dde0f015a39ef4553.png

   

 5. Add the DLL inside the "Reference" folder

img_add_resource_3.png.fbbb2914667928bc5752c5bfa3b10709.png

 

     6. Right Click the DLL => Properties => Build Action: Embedded Resource

Now, your plugin will ship with HTMLAgilityPack! And... It has references to it! Awesome! 

But, we are not done yet! Emby doesn't know what HTMLAgilityPack is. So, when Emby tries load you plugin it is going to error.

It will NOT load your plugin.

So we need to interrupt Emby's and tell it what to do when it comes across a reference to an Assembly it doesn't undertsand!

 

    7. The Magic!

Hopefully you understood what the IServerEntryPoint interface does from what I explained before. This will become important here.

Remember,  any class that implements the IServerEntryPoint will start when Emby loads. We'll use this to our advantage.

 

We are going to interrupt ("extended") Emby's loading, by attaching to the "AssembyResolve" event's in the Runtime. So Cool!

Now we can load our DLL before Emby has a chance to Error in the  callback of this event....

("callback"... can I use that terminology in c#.. or is that just a JS thing... maybe it is the "subscriber method"?... meh... you get it.)

 

 AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

It's magical! LOL!

Create this class:

using System;
using System.Linq;
using System.Reflection;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Logging;

namespace YAScrapper.References
{
    public class AssemblyResolve : IServerEntryPoint
    {
        private ILogger Log { get; }
    
        public AssemblyResolve(ILogManager logMan)
        {
           
            Log = logMan.GetLogger(Plugin.Instance.Name);
            //This event will fire when the plugin is loading dependencies
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
        }
        
        /// <summary>
        /// Load any required dependent libraries into the plugin.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            //Don't try and load items that are not theHTMLAgilityPack namespace
            if (!(args.Name.Contains("HTMLAgilityPack"))) return null;

            //Don't load the assembly twice
            var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
            if (assembly != null) return assembly;


            Log.Info($"Load Request {args.Name}");
            //r1 is that Assembly we were looking for. Naming? Meh.. fix it later.
            var r1 = Assembly.GetExecutingAssembly().GetManifestResourceNames().FirstOrDefault(s => s.Contains(args.Name.Split(',')[0]));
            Log.Info($"Loading Assembly {r1}");
            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(r1))
            {
                byte[] assemblyData = new byte[stream.Length];
                stream.Read(assemblyData, 0, assemblyData.Length);
                return Assembly.Load(assemblyData);
            }

        }

        public void Dispose()
        {
            
        }

        public void Run()
        {
            
        }
    }
}

 

Craziest thing is that you can load all your external DLL's here.

From this point forward, you can simply:

  1. add your DLL as an embedded resource in the "References" folder,
  2. Extended the condition:
 if (!(args.Name.Contains("HTMLAgilityPack")) || !(args.Name.Contains("MY_OTHER_DLL_NAME"))) return null;

 

Mind you this code is still somewhat in it's infancies... I only just learned how to do this myself, so there is some obvious refactoring that could be done.

Perhaps iterating  over a static List of DLL names during load.... or something like that. 

And... I know... I know... attaching events in constructors *might* (not be the right thing to do (I dunno)... But we need to get that event attached ASAP. so... we're doin' it! LOL!

 

Does this explain things in a good way. Do I need to make any steps clearer?

 

 

 

 

Edited by chef
  • Thanks 1
Link to comment
Share on other sites

DarKni8

@chefits lots for me to digest 😅😅...but i am gonna give it a shot and see how things goes. Thanks man this is gonna help me and many people yo come

 

Definitely i will let you know if i need any clarification in some steps, all this is alien to me but i will grasp it slowly 😁

Edited by DarKni8
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...