# -*- coding: UTF-8 -*- """ Kan Video plugin """ # Copyright (c) 2017 Tzafrir Cohen # SPDX-License-Identifier: GPL-2.0+ # # License-Filename: LICENSE from bs4 import BeautifulSoup import datetime import re import sys import urllib #import urllib2 import requests import simplecache import threading import urlparse import xbmc import xbmcgui import xbmcplugin import zlib KAN_URL = 'http://www.kan.org.il' USER_AGENT = 'Kodi/plugin.video.kan/1.0' # I'm optimistic PLUGIN_NAME = "plugin.video.kan" # FIXME: a better way to get this? class DummyCache: def __init__(self): pass def get(self, cache_id, checksum=''): return None def set(self, cache_id, data, checksum='', expiration=None): pass SiteCache = simplecache.SimpleCache() # FIXME: avoid this global #SiteCache = DummyCache() # Avoid caching for now def trace(msg): xbmc.log(msg="Tzafrir: " + msg, level=xbmc.LOGNOTICE) class Page: def __init__(self, argv): self.base_url = argv[0] self.addon_handle = int(argv[1]) self.args = urlparse.parse_qs(argv[2][1:]) self.mode = self.args.get('mode', None) self.content_type = self.args.get('content_type', None) def build_url(self, query): #query['addon_handle'] = self.addon_handle return self.base_url + '?' + urllib.urlencode(query) def add_directory_item(self, **kwargs): xbmcplugin.addDirectoryItem(handle=self.addon_handle, **kwargs) def end_directory(self): xbmcplugin.endOfDirectory(self.addon_handle) def placeholder_folder(self, foldername): """ A folder with a single dummy item """ url = 'http://localhost/some_video.mkv' li = xbmcgui.ListItem(foldername + 'Not Implemented', iconImage='DefaultVideo.png') self.add_directory_item(url=url, listitem=li) self.end_directory() def placeholder_item(self, name, label): """ An item that calls a placeholder folder """ url = self.build_url({'mode': 'placeholder', 'name': name}) li = xbmcgui.ListItem(label) self.add_directory_item(url=url, listitem=li, isFolder=True) def __str__(self): return "[base_url: {}, addon_handle: {}, mode: {}, content_type: {}]" \ .format(self.base_url, self.addon_handle, self.mode, self.content_type) def build_page(self, page_list, isFolder=False, isPlayable=False): """ Creates a complete page from a list of items (title, url) """ for item in page_list: title, url = item li = xbmcgui.ListItem(title) if isPlayable: li.setProperty('IsPlayable','true') self.add_directory_item(url=url, listitem=li, isFolder=isFolder) self.end_directory() def read_url(url): headers = {'user-agent': USER_AGENT} response = requests.get(url, headers=headers) if response.status_code != 200: raise IOError("Invalid URL {}".format(url)) return response.text def get_show_title(base_url, path): url = base_url + '/' + path page = read_url(url) parsed = BeautifulSoup(page, "html.parser") title = parsed.title.string title = re.sub('[|-].*', '', title) return title def title_checksum(title): """ A simple checksum to see that the title did not change Should only be good enough for the case that pages got renumbered and the title no longer matches page number. """ return zlib.adler32(title.encode('utf-8')) def get_program_item(res_array, page, a, i): """ A thread worker to get information about a program page input: a: the a element from the program's page. Writes results to the specified index in the results array. """ path = a.get('href') show_id = re.sub('.*=', '', path) title = get_show_title(KAN_URL, path) checksum = title_checksum(title) url = page.build_url({'mode': 'show', 'id': show_id, 'checksum': str(checksum)}) res_array[i] = (title, url) def video_top_menu(page, name): """ Display a menu of all the TV shows """ trace("Show top menu for " + name) cache_id = PLUGIN_NAME + '.toppage.' + name cached_items = SiteCache.get(cache_id) if cached_items: page.build_page(cached_items, isFolder=True) return trace("Show top menu for " + name + ": no cached copy") tvshows_url = KAN_URL + "/video/{}.aspx".format(name) main_page = read_url(tvshows_url) parsed = BeautifulSoup(main_page, "html.parser") anchors = parsed.find_all('a', class_="program_category_link w-inline-block") trace("got anchors: " + str(len(anchors))) page_items = [None for item in anchors] threads = [] for i in range(0, len(anchors)): t = threading.Thread(target=get_program_item, args=(page_items, page, anchors[i], i)) t.start() threads.append(t) for t in threads: t.join() # FIXME: check if result is still None. If so: handle error? page.build_page(page_items, isFolder=True) SiteCache.set(cache_id, page_items, expiration=datetime.timedelta(days=1)) def show_menu(page): """ Display a menu of items in a specific show """ show_id = page.args['id'][0] checksum = page.args['checksum'][0] cache_id = PLUGIN_NAME + '.progpage.' + show_id cached_items = SiteCache.get(cache_id, checksum=checksum) if cached_items: page.build_page(cached_items, isPlayable=True) return show_url = KAN_URL + '/Program/?catId=' + show_id trace("URL for show {}: {}".format(show_id, show_url)) show_page = read_url(show_url) parsed = BeautifulSoup(show_page, "html.parser") items = parsed.find_all('li', class_="program_list_item w-clearfix") trace("got items: " + str(len(items))) page_items = [] for item in items: titles = item.find_all('h2') title = titles[0].string iframe = item.find_all('iframe')[0] youtube_url = iframe['src'] youtube_id = re.sub('.*/embed/([0-9A-Za-z]+)(\?.*)?', r'\1', youtube_url) trace("Add link for ID {} ({}).".format(youtube_id, title.encode('utf-8'))) url = 'plugin://plugin.video.youtube/play/?video_id={}'.format(youtube_id) page_items.append((title, url)) page.build_page(page_items, isFolder=True) SiteCache.set(cache_id, page_items, checksum=checksum, expiration=datetime.timedelta(days=1)) def main_page(page): url = page.build_url({'mode': 'shows', 'name': 'programs'}) li = xbmcgui.ListItem(u'תוכניות טלוויזיה') page.add_directory_item(url=url, listitem=li, isFolder=True) url = page.build_url({'mode': 'shows', 'name': 'digital'}) li = xbmcgui.ListItem(u'תוכניות רשת') page.add_directory_item(url=url, listitem=li, isFolder=True) page.placeholder_item('new-items', u'קטעים חדשים') page.end_directory() def main(): page = Page(sys.argv) trace(' | '.join(sys.argv)) trace("{}".format(page)) if page.mode is not None: mode = page.mode[0] if page.mode is None: main_page(page) elif mode == 'shows': video_top_menu(page, page.args['name'][0]) elif mode == 'show': show_menu(page) elif mode == 'placeholder': name = page.args['name'][0] page.placeholder_folder(name) else: trace("No handler for mode '{}'".format(mode)) trace("Done showing page") if __name__ == '__main__': main()