HOW TO: Guide for Writing Pulsar Provider Add-ons for XBMC/KODI






Writing Plugins for XBMC/KODI can be a rewarding experience, you get to build something and you get to contribute back to an amazing community.


F0llow along below and learn how to write Pulsar Provider Plugins/Addons

For this guide, I am using the Steeve SDK, KODI/XBMC instruction about writing add-ons and some of my providers.   I will use as example to write a provider for thevirtualbay.net. There are several providers already working, but it is just for educational purpose. You can use the site that you need instead.

1. Editor

There are plenty editors for Python. But, I am going to use notepad++. It is an open source editor that is easy to use.

2. Have some basic knowledge about python

You can find in Google or YouTube really good tutorials, but I will be skipping that part. Anyways, with any basic Python for beginners’ tutorial you will know enough to write and understand the code.

3. Understanding the provider structure

You need to base your writing in the standard structure set up by Steeve in: https://github.com/steeve/script.pulsar.dummy
The provider is contained in a folder in C:\Users\{user}\AppData\Roaming\XBMC\addons
The name of the folder for pulsar provider needs to have this configuration:
1
script.pulsar.dummy
This folder name will be the id your provider for XBMC/KODI. It’s really important not to duplicate the name. First check the existent providers. If you are trying to write a provider for existent site. Please, communicate with the original author to know how to address.
For our case, we have several existing providers for thepiratebay.org then I will use Example1 as name.
Then we create a folder called:
1
script.pulsar.Example1
The basic provider is composed by these files:
addon.xml
This file contents the information that XBMC/KODI uses to link your provider to Pulsar
<pre><?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="script.pulsar.dummy" name="Pulsar Dummy Provider" version="0.0.1" provider-name="steeve">
    <requires>
        <import addon="xbmc.python" version="2.12.0"/>
        <import addon="plugin.video.pulsar" version="0.2"/>
    </requires>
    <extension point="xbmc.python.script" library="main.py">
        <provides>executable</provides>
    </extension>
    <extension point="xbmc.addon.metadata">
        <summary lang="en">Pulsar Dummy Sample Provider</summary>
        <language></language>
        <platform>all</platform>
    </extension>
</addon></pre>

Lines to modify:
<addon id="script.pulsar.dummy" name="Pulsar Dummy Provider" version="0.0.1" provider-name="steeve">


provider-name="steeve">
ID is the name of the folder where you are writing your provider.
Then the modification it will be:
<addon id="script.pulsar.Example1" name="Pulsar Example1 Provider" version="0.0.1" provider-name="your nickname or name">


Remember, ID is the name of your folder. Be careful to use the same combination of lowercase and uppercase. Otherwise, in a linux system there will be a problem.
<import addon="plugin.video.pulsar" version="0.3"/>
This will set up the Pulsar version that you will use for your provider. In this case, we are going to keep the Pulsar 0.3 version.
<summary lang="en">Pulsar Dummy Sample Provider</summary>

In this line you can make a summary about your provider:
<summary lang="en">Pulsar example for this how to guide</summary>
There is other information that you can add in to write a more complete provider add-on like forum, disclaimer. In this link you can find the explanation about each one. But, for a basic provider is not necessary.
http://kodi.wiki/view/Addon.xml
Changelog.txt
It is just plain text with the version and modifications.
We are going just to write
1
2
Version 1.0
Example
You can add more lines to explain the difference between versions.  In XBMC/KODI will be appear when you choose changelog for your provider.
Icon.png
It is the icon for your provider in format .png with size of 256 pixels by 256 pixels.  You can do a copy-paste or save it from print screen to paint.exe
It is not essential, though.  You can omit this file, if you want.
main.py
It is the code that Pulsar calls to find the torrents.

4. Explanation of main.py

This is the code
from pulsar import provider
def search(query):
    resp = provider.GET("http://foo.bar/search", params={
        "q": query,
    })
    return provider.extract_magnets(resp.data)
def search_episode(episode):
    return search("%(title)s S%(season)02dE%(episode)02d" % episode)
def search_movie(movie):
    return search("%(title)s %(year)d" % movie)
provider.register(search, search_movie, search_episode)

As you can see, the basic provider demands a few lines of code.
The first line imports the module provider from pulsar.  This makes it possible to use the Pulsar SDK tools.
def search()  is the function for general search. Usually, it is the function that Pulsar uses when you do a general search (last option in Pulsar menu)
def search_episode() is the function that Pulsar uses when you do a search for a Show or you select an episode in the list of TV shows.
def search_movie() does the same that def search_episode() does, but for movies.
Those three functions are the objective of our work.
The last line is just to register our functions to Pulsar.
Pulsar uses payloads to send and retrieve information to the providers, it is basically a dictionary type.
Episode Payload Sample
Episode Payload Sample
{"imdb_id": "tt0092400", "tvdb_id": "76385", "title": "married with children", "season": 1,"episode": 1"titles": null }, "season": 1,"episode": 1"titles": null }
{"imdb_id": "tt0092400", "tvdb_id": "76385", "title": "married with children", "season": 1,"episode": 1"titles": null }

In this case is the payload provided for Pulsar to the search_episode() function.
Then it means that inside of the search_episode(info) the variable info will be:
Episode Payload Sample
info = {"imdb_id": "tt0092400", "tvdb_id": "76385", "title": "married with children", "season": 1,"episode": 1"titles": null }

If you don’t understand dictionaries in Python it is really easy.
That means that query could have different values with each key.
Ex:
info["imdb_id"] will return "tt0092400"
info["season"] will return 1


The standard dictionaries or payloads for other functions are:

search_movie(info)
    {"imdb_id": "tt1254207", "title": "big buck bunny", "year": 2008"titles": { "es": "el gran conejo", "nl": "peach open movie project", "ru": "большои кролик", "us": "big buck bunny short 2008"
     }
 }

Note that “titles” keys are countries, not languages
The titles are also normalized (accents removed, lower case etc…)
For the function search(), we only deal with a string type.


search(info)
    info = "string to do the search"
As return, each function gives a Pulsar the next payload:
[{"name": string , "uri": string, "info_hash": string, "trackers": [string, ...], "size": int, "seeds": int, "peers": int, "resolution": int, "video_codec": int, "audio_codec": int, "rip_type": int, "scene_rating": int,     "language": string (ISO 639-1)}, {second file}, …]


But, it could be as simple like:
[{"uri": magnet or torrent link1}, {"uri": magnet or torrent link2}…]
In this easy provider we are going to use the search_movie() and search_episode() to define the query to the general search().  In other words, search() will find all the magnets for Pulsar and the other two function just fix the query adding more information.
We need to create the string for search()
def search_episode(info):
    return search("%(title)s S%(season)02dE%(episode)02d" % info)
def search_movie(query):
    return search('query': ""%(title)s %(year)d" % query)


Alternative writing (less elegant)
def search_episode(info):
    title = info['title']
    season = info['season']
    episode = info['episode']
    return search("%s S%02dE%02d" % (title, season, episode))
def search_movie(query):
    title = info['title']
    year = info['year']
    return search("%s %d" % (title, year))

Last not recommendable alternative, but easier to understand
1
2
3
4
5
6
7
8
9
10
11
12
def search_episode(info):
    title = info['title']
    season = info['season']
    episode = info['episode']
    query = title + ' S' + season  + 'E' + episode
    return search(query)
def search_movie(info):
    title = info('title')
    year = info['year']
    query = title + ' ' + year
    return search(query)
As you can see, the two function modify the query for the function search()

5. FUNCTION SEARCH()

The first step that we need to do it is open our favorite internet browser with this link:
http://thevirtualbay.net/
and we search for the film frozen.
thevirtualbay


As you can see the address bar return with the necessary code to write our provider.
Then we know that for virtualbay, we need to use thevirtualbay.net/search/***here what we want to search ***/0/7/0
We try now with something longer, like adding the year to emulate the query from search_movie() query.
The search will be frozen 2013.
If you don’t know, in internet address, you can’t add space.  Instead, you use the combination of %20.
Then, we see that our pattern is the same, but we need to replace the space for %20 or we can use the function urlib.quote.
Now, we see the code for the search() will be:
1
2
3
4
5
6
def search(info):
    import urllib
    query = urllib.quote(info)
    url ='http://thevirtualbay.net/search/' + query + '/0/7/0'
    resp = provider.GET(url)
    return provider.extract_magnets(resp.data)
As you can see, we use the function urllib.quote to convert our space to %20 and we’ve created the url address to the function provider.GET() for Pulsar.
Provider.Get() opens the url address and retrieves the source.  It is the same that in your browser with you right button mouse, you choose from the context menu the option view source or view source-code.  It will be the html file.
The function provider.extract_magnets() extracts all the magnets from the downloaded page and creates the payload to be sent to Pulsar.
And alternative less elegant solution will be:
1
2
3
4
5
6
7
8
9
10
11
12
from pulsar import provider
def search(info):
    query = info.replace(' ','%20')
    url ='http://thevirtualbay.net/search/' + query + '/0/7/0'
    resp = provider.GET(url)
    return provider.extract_magnets(resp.data)
def search_episode(info):
    return search("%(title)s S%(season)02dE%(episode)02d" % info)
def search_movie(info):
    return search("%(title)s %(year)d" % info)
Congratulations, you have written your first provider add-on!
Below are some examples you can download : Example1 + Example2