def __init__(self):
        # Get the command line arguments
        # Get the plugin url in plugin:// notation
        self.plugin_url = sys.argv[0]
        # Get the plugin handle as an integer number
        self.plugin_handle = int(sys.argv[1])

        # Get plugin settings
        self.PREFERRED_QUALITY = SETTINGS.getSetting('quality')
        self.IS_FIRST_MEMBER = SETTINGS.getSetting('is_first_member')
        self.USE_ADAPTIVE_STREAM = SETTINGS.getSetting('use_adaptive_inputstream')

        # log("ARGV", repr(sys.argv))

        # Parse parameters...
        self.functional_url = urllib.parse.parse_qs(urllib.parse.urlparse(sys.argv[2]).query)['functional_url'][0]
        self.technical_url = urllib.parse.parse_qs(urllib.parse.urlparse(sys.argv[2]).query)['technical_url'][0]
        self.title = urllib.parse.parse_qs(urllib.parse.urlparse(sys.argv[2]).query)['title'][0]
        self.title = convertToUnicodeString(self.title)
        self.is_first_member_only = urllib.parse.parse_qs(urllib.parse.urlparse(sys.argv[2]).query)['is_first_member_only'][0]

        log("self.functional_url", self.functional_url)

        log("self.technical_url", self.technical_url)

        # let's use the technical url
        self.url = self.technical_url

        #
        # Play video...
        #
        self.playVideo()
    def find_video_quality_url(self, quality, response, video_url):

        # the video url will be something like this:
        # https://rtv3-roosterteeth.akamaized.net/store/24ee639e8baae80356d2b0c9cc367c75-f3abd10f/ts/1557430260_index.m3u8?Policy...
        # However, the direct link should be something like this:
        # https://rtv3-roosterteeth.akamaized.net/store/24ee639e8baae80356d2b0c9cc367c75-f3abd10f/ts/f3abd10f-hls_1080p-store-24ee639e8baae80356d2b0c9cc367c75.m3u8?Policy...
        # its composed of 3 parts:
        # part 1:
        # https://rtv3-roosterteeth.akamaized.net/store/24ee639e8baae80356d2b0c9cc367c75-f3abd10f/ts/
        # part 2:
        # the line found in the m3u8 of the corresponding quality (f.e. f3abd10f-hls_480p-store-24ee639e8baae80356d2b0c9cc367c75.m3u8)
        # part 3:
        # ?Policy...

        # lets determine part 1 and 3
        pos_of_index_dot_m3u8 = video_url.find(INDEX_DOT_M3U8)
        if pos_of_index_dot_m3u8 >= 0:
            pos_of_slash_before_dotm3u8 = video_url.rfind(
                "/", 0, pos_of_index_dot_m3u8)
            video_url_part1 = video_url[0:pos_of_slash_before_dotm3u8 + 1]
            video_url_part3 = video_url[pos_of_index_dot_m3u8 +
                                        len(INDEX_DOT_M3U8):]

        video_url_altered = ''
        # read the m3u8 file line for line and search for the quality. If found: alter the url of the m3u8 file.
        for line in response.iter_lines():
            if line:
                # let's convert the line to prevent python 2/3 troubles
                line = convertToUnicodeString(line)

                # log("line", line)

                if line.find(quality) >= 0:
                    video_url_part2 = line
                    video_url_altered = video_url_part1 + video_url_part2 + video_url_part3
                elif line.find(quality.upper()) >= 0:
                    video_url_part2 = line
                    video_url_altered = video_url_part1 + video_url_part2 + video_url_part3

        # log("returning video_url_altered", video_url_altered)

        return video_url_altered
    def getVideos(self):
        #
        # Init
        #
        # Create a list for our items.
        listing = []

        #
        # Get HTML page
        #
        response = requests.get(self.video_list_page_url, headers=HEADERS)

        html_source = response.text
        html_source = convertToUnicodeString(html_source)

        # log("html_source", html_source)

        try:
            json_data = json.loads(html_source)
        except (ValueError, KeyError, TypeError):
            xbmcgui.Dialog().ok(LANGUAGE(30000), LANGUAGE(30109))
            exit(1)

        for item in json_data['data']:
            serie_title = item['attributes']['title']

            summary = item['attributes']['summary']
            last_episode_golive_at = item['attributes'][
                'last_episode_golive_at']
            last_episode_golive_at = last_episode_golive_at[0:10]

            # this part looks like this f.e.: /series/nature-town
            serie_url_middle_part = item['canonical_links']['self']
            serie_name = serie_url_middle_part.replace('/series/', '')

            # log("serie_url_middle_part", serie_url_middle_part)
            # log("serie_name", serie_name)

            # the serie url should become something like this:
            # https://svod-be.roosterteeth.com/api/v1/shows/nature-town/seasons?order=desc
            serie_url = ROOSTERTEETH_SERIES_BASE_URL + '/' + serie_name + '/' + 'seasons' \
                        + ROOSTERTEETH_GET_EVERYTHING_IN_ONE_PAGE_URL_PART + ROOSTERTEETH_ORDER_URL_PART

            thumb = item['included']['images'][0]['attributes']['thumb']

            title = serie_title

            url = serie_url

            thumbnail_url = thumb

            # Add to list...
            list_item = xbmcgui.ListItem(title)
            list_item.setInfo(
                "video", {
                    "title":
                    title,
                    "mediatype":
                    "video",
                    "plot":
                    summary + '\n' + LANGUAGE(30319) + ' ' +
                    last_episode_golive_at
                })
            list_item.setArt({
                'thumb':
                thumbnail_url,
                'icon':
                thumbnail_url,
                'fanart':
                os.path.join(RESOURCES_PATH, 'fanart-blur.jpg')
            })
            list_item.setProperty('IsPlayable', 'false')

            # let's remove any non-ascii characters from the title, to prevent errors with urllib.parse.parse_qs
            # of the parameters
            title = title.encode('ascii', 'ignore')

            parameters = {
                "action": "list-serie-seasons",
                "url": url,
                "title": title,
                "thumbnail_url": thumbnail_url,
                "next_page_possible": "False"
            }
            plugin_url_with_parms = self.plugin_url + '?' + urllib.parse.urlencode(
                parameters)
            is_folder = True
            # Add refresh option to context menu
            list_item.addContextMenuItems([('Refresh', 'Container.Refresh')])
            # Add our item to the listing as a 3-element tuple.
            listing.append((plugin_url_with_parms, list_item, is_folder))

        # Add our listing to Kodi.
        # Large lists and/or slower systems benefit from adding all items at once via addDirectoryItems
        # instead of adding one by ove via addDirectoryItem.
        xbmcplugin.addDirectoryItems(self.plugin_handle, listing, len(listing))
        # Set initial sorting
        xbmcplugin.addSortMethod(handle=self.plugin_handle,
                                 sortMethod=xbmcplugin.SORT_METHOD_DATEADDED)
        # Finish creating a virtual folder.
        xbmcplugin.endOfDirectory(self.plugin_handle)
    def playVideo(self):
        #
        # Init
        #
        dialog_wait = xbmcgui.DialogProgress()

        #
        # Get current list item details...
        #
        title = xbmc.getInfoLabel("listitem.Title")
        # thumbnail_url = xbmc.getInfoImage("list_item.Thumb")
        # studio = xbmc.getInfoLabel("list_item.Studio")
        # mediatype = xbmc.getInfoLabel("list_item.Mediatype")
        # plot = xbmc.getInfoLabel("list_item.Plot")
        # genre = xbmc.getInfoLabel("list_item.Genre")

        session = ''
        try:
            # requests is sooooo nice, respect!
            session = requests.Session()

            if self.is_first_member_only == "True":
                must_login_first_member_user = True
                video_is_not_yet_available = False
            else:
                # try and get the non-first_member video without being logged in
                # get the page that contains the video
                response = session.get(self.url, headers=HEADERS)

                html_source = response.text
                html_source = convertToUnicodeString(html_source)

                # log("html_source without authorization", html_source)

                # sometimes a non-first_member video is not available. However this video will (!) be available for a first_member
                # after logging in. One of the perks of being a first_member, i reckon. This is what you get back in that
                # case: {"access":false,"message":"not yet available"}
                if html_source.find("not yet available") >= 0:
                    # let's try and get this non-first_member video after login in the first_member user then
                    must_login_first_member_user = True
                    video_is_not_yet_available = True
                else:
                    must_login_first_member_user = False
                    video_is_not_yet_available = False

            log("must_login_first_member_user", must_login_first_member_user)

            # login if needed
            if must_login_first_member_user:
                # is the first_member switch in the settings of this addon turned on?
                if self.IS_FIRST_MEMBER == 'true':
                    # is it a first_member video or not?
                    if self.is_first_member_only == "True":
                        log("logging in with user for this first member video", self.url)
                    else:
                        log("logging in with user for this non-first member video", self.url)

                    # let's try and get authorization
                    try:
                        # we need a NEW (!!!) session
                        session = requests.Session()

                        # set the needed authorization-data
                        payload = {"client_id": KODI_ROOSTERTEETH_ADDON_CLIENT_ID,
                                   "grant_type": "password", "password": SETTINGS.getSetting('password'),
                                   "scope": "user public", "username": SETTINGS.getSetting('username')}

                        # post the payload to the authorization url, to actually get an access token (oauth) back
                        response = session.post(ROOSTERTEETH_AUTHORIZATION_URL, data=payload)

                        log('post login page response, status_code:', response.status_code)

                        html_source = response.text
                        html_source = convertToUnicodeString(html_source)

                        # log("html_source getting authorization", html_source)

                        # check that the login was technically ok (status_code 200).
                        # This in itself does NOT mean that the username/password were correct.
                        if response.status_code == 200:
                            pass
                            # {"access_token":"eyJ0eXAiOiJKV1QiLCJ<SOMETHINGSOMETHING>","token_type":"bearer","expires_
                            # check that we get back an access_token (oauth)
                            # for some reason html_source can't be loaded in json, so we have to do it the hard way :(
                            start_pos_access_token_url = html_source.find('"access_token":"')
                            if start_pos_access_token_url >= 0:

                                log('login was successful!', 'login was successful!')

                                start_pos_access_token_url = start_pos_access_token_url + len('"access_token":"')
                                end_pos_access_token = html_source.find('"', start_pos_access_token_url)
                                access_token = html_source[start_pos_access_token_url:end_pos_access_token]

                                log("access_token", access_token)

                                # let's make a new header dictionary
                                headers_with_access_token = HEADERS
                                # add the access token to the dictionary
                                # see https://stackoverflow.com/questions/29931671/making-an-api-call-in-python-with-an-api-that-requires-a-bearer-token
                                # this is some specific magic for setting the authorization in the header
                                headers_with_access_token['Authorization'] = "Bearer " + access_token

                                # log("headers_with_access_token", headers_with_access_token)

                                # let's try getting the page with the received access_code
                                response = session.get(self.url, headers=headers_with_access_token)

                                html_source = response.text
                                html_source = convertToUnicodeString(html_source)

                                # log("html_source with authorization", html_source)

                                # Some videos are not available yet to non-First members
                                # {"access":false,"message":"not yet available"}
                                if html_source.find('not yet available') >= 0:
                                   xbmcgui.Dialog().ok(LANGUAGE(30000), LANGUAGE(30120))
                                   exit(1)

                                # Some videos are only available to First members (which for some reason is still called sponsor in the backend)
                                # {"access":false,"message":"This content is sponsor only. No access."}
                                if html_source.find('This content is') >= 0:
                                    if html_source.find('only') >= 0:
                                        xbmcgui.Dialog().ok(LANGUAGE(30000), LANGUAGE(30130))
                                        exit(1)
                            else:

                                log('login was NOT successful!', 'login was NOT successful!')

                                try:
                                    dialog_wait.close()
                                    del dialog_wait
                                except:
                                    pass
                                xbmcgui.Dialog().ok(LANGUAGE(30000), LANGUAGE(30101), LANGUAGE(30102),
                                                    LANGUAGE(30103))
                                exit(1)
                        else:
                            try:
                                dialog_wait.close()
                                del dialog_wait
                            except:
                                pass
                            xbmcgui.Dialog().ok(LANGUAGE(30000), LANGUAGE(30104), LANGUAGE(30111))

                            log('login was NOT successful!!, response.status_code: ', response.status_code)

                            exit(1)

                    except urllib.error.HTTPError as error:

                        log("HTTPError1", error)

                        try:
                            dialog_wait.close()
                            del dialog_wait
                        except:
                            pass
                        xbmcgui.Dialog().ok(LANGUAGE(30000), LANGUAGE(30106) % (convertToUnicodeString(error)))
                        exit(1)
                    except:
                        exception = sys.exc_info()[0]

                        log("ExceptionError1", exception)

                        try:
                            dialog_wait.close()
                            del dialog_wait
                        except:
                            pass
                        exit(1)

                else:
                    try:
                        dialog_wait.close()
                        del dialog_wait
                    except:
                        pass

                    if video_is_not_yet_available:
                        xbmcgui.Dialog().ok(LANGUAGE(30000), LANGUAGE(30110))
                    else:
                        xbmcgui.Dialog().ok(LANGUAGE(30000), LANGUAGE(30105))
                    exit(1)

        except urllib.error.HTTPError as error:

            log("HTTPError1", error)

            try:
                dialog_wait.close()
                del dialog_wait
            except:
                pass
            xbmcgui.Dialog().ok(LANGUAGE(30000), LANGUAGE(30106) % (convertToUnicodeString(error)))
            exit(1)
        except:
            exception = sys.exc_info()[0]

            log("ExceptionError2", exception)

            try:
                dialog_wait.close()
                del dialog_wait
            except:
                pass
            exit(1)

        video_url = ''
        no_url_found = True
        have_valid_url = False

        # for some reason html_source can't be loaded in json, so we have to do it the hard way :(
        start_pos_m3u8_url = html_source.find('"url":"')
        if start_pos_m3u8_url == -1:
            found_m3u8_url = False
        else:
            start_pos_m3u8_url = start_pos_m3u8_url + len('"url":"')
            end_pos_m3u8_url = html_source.find('"', start_pos_m3u8_url)
            if end_pos_m3u8_url == -1:
                found_m3u8_url = False
            else:
                m3u8_url = html_source[start_pos_m3u8_url:end_pos_m3u8_url]

                log("m3u8_url", m3u8_url)

                found_m3u8_url = True

        log("found_m3u8_url", found_m3u8_url)

        if found_m3u8_url:
            # for some reason u0026 is present in the url, it should have been an ampersand
            # let's correct that
            m3u8_url = m3u8_url.replace('u0026', '&')

            log("corrected m3u8_url", m3u8_url)

            # get the content of the m3u8 file
            response = session.get(m3u8_url, headers=HEADERS)
            if response.status_code == 200:
                have_valid_url = True
                video_url = m3u8_url

                log("video_url", video_url)

                if self.USE_ADAPTIVE_STREAM == 'true':
                    pass
                else:
                    html_source = response.text
                    html_source = convertToUnicodeString(html_source)

                    # log("html_source m3u8 file", html_source)

                    # determine the wanted video quality
                    if self.PREFERRED_QUALITY == '0':  # Very Low
                        quality = VQ240P
                    elif self.PREFERRED_QUALITY == '1':  # Low
                        quality = VQ360P
                    elif self.PREFERRED_QUALITY == '2':  # Medium
                        quality = VQ480P
                    elif self.PREFERRED_QUALITY == '3':  # High Quality
                        quality = VQ720P
                    elif self.PREFERRED_QUALITY == '4':  # Very High Quality
                        quality = VQ1080P
                    elif self.PREFERRED_QUALITY == '5':  # Ultra High Quality
                        quality = VQ4K
                    else:  # Default in case quality is not found
                        quality = VQ720P

                    # log("wanted quality", quality)

                    # an example of the content of a m3u8 file. Not all the resolutions will be there as most videos don't
                    # have an 4k option:
                    # #EXTM3U
                    # #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=21589000,RESOLUTION=1280x720,CODECS="avc1.4d001f,mp4a.40.2"
                    # aef4654c-hls_4k-rebuilds-4030.mp4.m3u8
                    # #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=21589000,RESOLUTION=1280x720,CODECS="avc1.4d001f,mp4a.40.2"
                    # aef4654c-hls_1080p-rebuilds-4030.mp4.m3u8
                    # #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=8281000,RESOLUTION=1280x720,CODECS="avc1.4d001f,mp4a.40.2"
                    # aef4654c-hls_720p-rebuilds-4030.mp4.m3u8
                    # #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=6370000,RESOLUTION=854x480,CODECS="avc1.4d001f,mp4a.40.2"
                    # aef4654c-hls_480p-rebuilds-4030.mp4.m3u8
                    # #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4093000,RESOLUTION=640x360,CODECS="avc1.4d001f,mp4a.40.2"
                    # aef4654c-hls_360p-rebuilds-4030.mp4.m3u8
                    # #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2941000,RESOLUTION=426x240,CODECS="avc1.4d001f,mp4a.40.2"
                    # aef4654c-hls_240p-rebuilds-4030.mp4.m3u8

                    # Let's try and find a video of the desired video quality.
                    # If that can't be found, try to find a video with less than the desired video quality
                    video_url_altered = ''
                    if video_url_altered == '':
                        if quality in [VQ4K]:
                            video_url_altered = self.find_video_quality_url(VQ4K, response, video_url)

                    if video_url_altered == '':
                        if quality in [VQ4K, VQ1080P]:
                            video_url_altered = self.find_video_quality_url(VQ1080P, response, video_url)

                    if video_url_altered == '':
                        if quality in [VQ4K, VQ1080P, VQ720P]:
                            video_url_altered = self.find_video_quality_url(VQ720P, response, video_url)

                    if video_url_altered == '':
                        if quality in [VQ4K, VQ1080P, VQ720P, VQ480P]:
                            video_url_altered = self.find_video_quality_url(VQ480P, response, video_url)

                    if video_url_altered == '':
                        if quality in [VQ4K, VQ1080P, VQ720P, VQ480P, VQ360P]:
                            video_url_altered = self.find_video_quality_url(VQ360P, response, video_url)

                    if video_url_altered == '':
                        if quality in [VQ4K, VQ1080P, VQ720P, VQ480P, VQ360P, VQ240P]:
                            video_url_altered = self.find_video_quality_url(VQ240P, response, video_url)

                    if video_url_altered == '':
                        pass
                    else:

                        log("video_url_altered", video_url_altered)

                        # Find out if the altered m3u8 url exists
                        response = session.get(video_url_altered)

                        log("response.status_code", response.status_code)

                        # if we find a m3u8 file with the altered url, let's use that.
                        # If it is not found, let's use the unaltered url.
                        if response.status_code in [200]:
                            video_url = video_url_altered

                    log("final video_url", video_url)
        else:
            have_valid_url = False

        # Play video...
        if have_valid_url:
            list_item = xbmcgui.ListItem(path=video_url)
            if self.USE_ADAPTIVE_STREAM == 'true':
                list_item.setProperty('inputstreamaddon', 'inputstream.adaptive')
                list_item.setProperty('inputstream.adaptive.manifest_type', 'hls')
            xbmcplugin.setResolvedUrl(self.plugin_handle, True, list_item)
        #
        # Alert user
        #
        elif no_url_found:
            xbmcgui.Dialog().ok(LANGUAGE(30000), LANGUAGE(30107))
    def getVideos(self):
        #
        # Init
        #
        # Create a list for our items.
        listing = []

        #
        # Get HTML page
        #
        response = requests.get(self.url, headers=HEADERS)

        html_source = response.text
        html_source = convertToUnicodeString(html_source)

        # log("html_source", html_source)

        try:
            json_data = json.loads(html_source)
        except (ValueError, KeyError, TypeError):
            xbmcgui.Dialog().ok(LANGUAGE(30000), LANGUAGE(30109))
            exit(1)

        for item in json_data['data']:

            # log("item", item)

            episode_title = item['attributes']['title']

            caption = item['attributes']['caption']

            length = item['attributes']['length']

            channel_slug = item['attributes']['channel_slug']

            # the url should be something like:
            # https://svod-be.roosterteeth.com/api/v1/episodes/ffc530d0-464d-11e7-a302-065410f210c4/videos"
            # or even
            # https://svod-be.roosterteeth.com/api/v1/episodes/lets-play-2011-2/videos
            technical_episode_url_last_part = item['links']['videos']
            technical_episode_url = ROOSTERTEETH_BASE_URL + technical_episode_url_last_part
            technical_url = technical_episode_url

            log("technical_url", technical_url)

            functional_episode_url_middle_part = item['links']['self']
            functional_url = ROOSTERTEETH_BASE_URL + functional_episode_url_middle_part + '/videos'

            log("functional_url", functional_url)

            thumb = item['included']['images'][0]['attributes']['thumb']

            serie_title = item['attributes']['show_title']

            original_air_date = item['attributes']['original_air_date']
            original_air_date = original_air_date[0:10]

            # The backend still calls it sponsor instead of first member
            is_first_member_only = item['attributes']['is_sponsors_only']

            # let's put some more info in the title of the episode
            if self.show_serie_name == "True":
                title = serie_title + ' - ' + episode_title
            else:
                title = episode_title

            if is_first_member_only:
                title = FIRST_MEMBER_ONLY_VIDEO_TITLE_PREFIX + ' ' + title

            title = convertToUnicodeString(title)

            thumbnail_url = thumb

            plot = caption

            duration_in_seconds = length

            studio = channel_slug
            studio = convertToUnicodeString(studio)
            studio = studio.replace("-", " ")
            studio = studio.capitalize()

            # Add to list...
            list_item = xbmcgui.ListItem(title)
            list_item.setInfo("video",
                             {"title": title, "studio": studio, "mediatype": "video", \
                              "plot": plot + '\n' + LANGUAGE(30318) + ' ' + original_air_date, \
                              "aired": original_air_date, "duration": duration_in_seconds})
            list_item.setArt({
                'thumb':
                thumbnail_url,
                'icon':
                thumbnail_url,
                'fanart':
                os.path.join(RESOURCES_PATH, 'fanart-blur.jpg')
            })
            list_item.setProperty('IsPlayable', 'true')

            # let's remove any non-ascii characters from the title, to prevent errors with urllib.parse.parse_qs
            # of the parameters
            title = title.encode('ascii', 'ignore')

            parameters = {
                "action": "play",
                "functional_url": functional_url,
                "technical_url": technical_url,
                "title": title,
                "is_first_member_only": is_first_member_only,
                "next_page_possible": "False"
            }

            plugin_url_with_parms = self.plugin_url + '?' + urllib.parse.urlencode(
                parameters)
            is_folder = False
            # Add refresh option to context menu
            list_item.addContextMenuItems([('Refresh', 'Container.Refresh')])
            # Add our item to the listing as a 3-element tuple.
            listing.append((plugin_url_with_parms, list_item, is_folder))

        # Make a next page item, if a next page is possible
        total_pages_str = json_data['total_pages']
        total_pages = int(total_pages_str)
        if self.page_number_next <= total_pages:
            # Next page entry
            if self.next_page_possible == 'True':
                list_item = xbmcgui.ListItem(LANGUAGE(30200))
                list_item.setArt({
                    'thumb':
                    os.path.join(RESOURCES_PATH, 'next-page.png'),
                    'fanart':
                    os.path.join(RESOURCES_PATH, 'fanart-blur.jpg')
                })
                list_item.setProperty('IsPlayable', 'false')
                parameters = {
                    "action": "list-episodes",
                    "url": str(self.next_url),
                    "next_page_possible": self.next_page_possible,
                    "show_serie_name": self.show_serie_name
                }
                url = self.plugin_url + '?' + urllib.parse.urlencode(
                    parameters)
                is_folder = True
                # Add refresh option to context menu
                list_item.addContextMenuItems([('Refresh', 'Container.Refresh')
                                               ])
                # Add our item to the listing as a 3-element tuple.
                listing.append((url, list_item, is_folder))

        # Add our listing to Kodi.
        # Large lists and/or slower systems benefit from adding all items at once via addDirectoryItems
        # instead of adding one by ove via addDirectoryItem.
        xbmcplugin.addDirectoryItems(self.plugin_handle, listing, len(listing))
        # Set initial sorting
        xbmcplugin.addSortMethod(handle=self.plugin_handle,
                                 sortMethod=xbmcplugin.SORT_METHOD_DATEADDED)
        # Finish creating a virtual folder.
        xbmcplugin.endOfDirectory(self.plugin_handle)
Example #6
0
    def __init__(self):
        # Get the command line arguments
        # Get the plugin url in plugin:// notation
        self.plugin_url = sys.argv[0]
        # Get the plugin handle as an integer number
        self.plugin_handle = int(sys.argv[1])

        #
        # Init
        #
        # Create a list for our items.
        listing = []

        #
        # Get HTML page
        #
        response = requests.get(ROOSTERTEETH_CHANNELS_URL, headers=HEADERS)

        html_source = response.text
        html_source = convertToUnicodeString(html_source)

        # log("html_source", html_source)

        try:
            json_data = json.loads(html_source)
        except (ValueError, KeyError, TypeError):
            xbmcgui.Dialog().ok(LANGUAGE(30000), LANGUAGE(30109))
            exit(1)

        for item in json_data['data']:
            thumb = item['included']['images'][0]['attributes']['large']
            channel_name = item['attributes']['name']
            channel_name_in_url = item['attributes']['slug']
            # channel_shows_link = item['links']['shows']
            channel_episodes_link = item['links']['episodes']
            title = channel_name.title() + ' ' + LANGUAGE(30321)

            # Add serie recently added episodes
            url_serie_recently_added_episodes = ROOSTERTEETH_BASE_URL + channel_episodes_link + \
                                                ROOSTERTEETH_PAGE_URL_PART + ROOSTERTEETH_ORDER_URL_PART + \
                                                ROOSTERTEETH_CHANNEL_URL_PART + channel_name_in_url

            # log("serie recently added episode url", url_serie_recently_added_episodes)

            thumbnail_url = thumb

            title = title.encode('ascii', 'ignore')

            parameters = {
                "action": "list-episodes",
                "plugin_category": title,
                "url": url_serie_recently_added_episodes,
                "show_serie_name": "True",
                "next_page_possible": "True"
            }
            list_item = xbmcgui.ListItem(title)
            list_item.setArt({
                'thumb':
                thumbnail_url,
                'icon':
                thumbnail_url,
                'fanart':
                os.path.join(RESOURCES_PATH, 'fanart-blur.jpg')
            })
            list_item.setProperty('IsPlayable', 'false')
            url = self.plugin_url + '?' + urllib.parse.urlencode(parameters)
            is_folder = True
            xbmcplugin.addDirectoryItem(handle=self.plugin_handle,
                                        url=url,
                                        listitem=list_item,
                                        isFolder=is_folder)

            # Add serie shows
            title = channel_name.title() + ' ' + LANGUAGE(30302)
            # let's remove any non-ascii characters from the title, to prevent errors with urllib.parse.parse_qs
            # of the parameters
            title = title.encode('ascii', 'ignore')

            url_serie_shows = ROOSTERTEETH_SERIES_BASE_URL + ROOSTERTEETH_GET_EVERYTHING_IN_ONE_PAGE_URL_PART + \
                              ROOSTERTEETH_ORDER_URL_PART + ROOSTERTEETH_CHANNEL_URL_PART + channel_name_in_url

            # log("serie shows url", url_serie_shows)

            parameters = {
                "action": "list-series",
                "plugin_category": title,
                "url": url_serie_shows,
                "show_serie_name": "True",
                "next_page_possible": "True"
            }
            list_item = xbmcgui.ListItem(title)
            list_item.setArt({
                'thumb':
                thumbnail_url,
                'icon':
                thumbnail_url,
                'fanart':
                os.path.join(RESOURCES_PATH, 'fanart-blur.jpg')
            })
            list_item.setProperty('IsPlayable', 'false')
            url = self.plugin_url + '?' + urllib.parse.urlencode(parameters)
            is_folder = True
            xbmcplugin.addDirectoryItem(handle=self.plugin_handle,
                                        url=url,
                                        listitem=list_item,
                                        isFolder=is_folder)

        # Add Series
        url_series = ROOSTERTEETH_SERIES_URL + ROOSTERTEETH_GET_EVERYTHING_IN_ONE_PAGE_URL_PART + \
                     ROOSTERTEETH_ORDER_URL_PART
        parameters = {
            "action": "list-series",
            "plugin_category": LANGUAGE(30302),
            "url": url_series,
            "show_serie_name": "True",
            "next_page_possible": "False"
        }
        url = self.plugin_url + '?' + urllib.parse.urlencode(parameters)
        list_item = xbmcgui.ListItem(LANGUAGE(30302))
        is_folder = True
        list_item.setArt(
            {'fanart': os.path.join(RESOURCES_PATH, 'fanart-blur.jpg')})
        list_item.setProperty('IsPlayable', 'false')
        xbmcplugin.addDirectoryItem(handle=self.plugin_handle,
                                    url=url,
                                    listitem=list_item,
                                    isFolder=is_folder)

        # Set initial sorting
        xbmcplugin.addSortMethod(handle=self.plugin_handle,
                                 sortMethod=xbmcplugin.SORT_METHOD_DATEADDED)
        # Finish creating a virtual folder.
        xbmcplugin.endOfDirectory(self.plugin_handle)