# -*- coding: utf-8 -*-
"""
xml.py --- functions dealing with jen xml list format
Copyright (C) 2017, Jen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
import __builtin__
import datetime
import os
import re
import time
from threading import Thread
import koding
import requests
import xbmc
import xbmcaddon
import xbmcgui
import xbmcplugin
import sys
import sqlite3
import base64,pickle,koding
from language import get_string as _
from resources.lib.plugin import run_hook
from resources.lib.util.context import get_context_items
from resources.lib.util.info import get_info
from resources.lib.util.url import replace_url, get_addon_url
CACHE_TIME = 120 # change to wanted cache time in seconds
ADDON = xbmcaddon.Addon()
addon_id = xbmcaddon.Addon().getAddonInfo('id')
addon_data_folder = xbmc.translatePath('special://home/userdata/addon_data')
addon_data = os.path.join(addon_data_folder, addon_id)
database_loc = os.path.join(addon_data, 'database.db')
main_cache_time = int(ADDON.getSetting("main_cache_time"))*60
class JenList(object):
"""
wrapper class for jen list functions
"""
list_pattern = re.compile(
'((?:.+?|
.+?|.+?'
'|.+?|'
'[^<]+[^<]+[^<]+'
'[^<]+|'
'[^<]+[^<]+[^<]+'
'[^<]+))', re.MULTILINE | re.DOTALL)
def __init__(self, url, cached=True):
if cached:
xml = run_hook("get_xml", url)
else:
xml = run_hook("get_xml_uncached", url)
if not xml:
if url.endswith(".xml") or url.startswith("http"):
try:
xml = requests.get(url).content
except Exception as e:
xml = None
koding.dolog("error: " + repr(e))
koding.dolog("url: " + repr(url))
koding.dolog("#####################")
else:
xml = url
if xml:
self.xml = xml
self.content = ""
self.list = JenList.list_pattern.findall(xml)
info = JenItem(xml.split('')[0].split('')[0])
intro = info.get("intro", "")
if intro:
import resources.lib.sources as sources
sources.get_sources(intro)
self.playlister = info.get('poster', '0')
self.list_image = replace_url(info.get("animated_thumbnail", ""))
if not self.list_image:
self.list_image = replace_url(
info.get('thumbnail', ADDON.getAddonInfo('icon')))
self.list_fanart = replace_url(info.get("animated_fanart", ""))
if not self.list_fanart:
self.list_fanart = replace_url(info.get('fanart', "0"))
if self.list_fanart == "0":
theme = xbmcaddon.Addon().getSetting('theme')
if theme and theme != 'DEFAULT' and theme != 'none':
self.list_fanart = self.set_theme(theme)
else:
self.list_fanart = xbmcaddon.Addon().getAddonInfo('fanart')
self.metadata = {}
else:
self.list = []
@staticmethod
def set_theme(theme):
"""set jen theme"""
import random
theme_list = run_hook("get_theme_list")
if theme.lower() == "user":
user_theme_folder = os.path.join(
xbmc.translatePath(
xbmcaddon.Addon().getSetting("cache_folder")),
"theme")
if os.path.isdir(user_theme_folder):
user_files = []
for ufile in os.listdir(user_theme_folder):
if os.path.isfile(os.path.join(user_theme_folder, ufile)):
user_files.append(
os.path.join(user_theme_folder, ufile))
theme_list["user"] = user_files
else:
koding.dolog("huh?: " + repr(theme))
return xbmcaddon.Addon().getAddonInfo('fanart')
return replace_url(random.choice(theme_list[theme.lower()]))
def get_raw_list(self):
"""returns the raw xml list of items"""
return self.list
def get_list(self, input_list=None, skip_meta=False, skip_dialog=False):
"""
returns the list of items in a format suitable for kodiswift
Returns:
list of jen items
"""
items = []
meta = ADDON.getSetting("metadata") == "true"
try:
is_widget = __builtin__.JEN_WIDGET
except:
is_widget = False
if is_widget:
skip_dialog = True
if input_list is None:
input_list = self.list
xbmc.log("input_list: " + repr(input_list))
dialog = None
dialog2 = None
if not skip_dialog:
if ADDON.getSetting("disable_metadata_dialog") == "false":
dialog = xbmcgui.DialogProgress()
addon_name = ADDON.getAddonInfo("name")
dialog.create(addon_name, _("Loading items"))
if meta and not skip_meta:
info_thread = threadWithReturn(
target=get_info, args=(input_list, dialog))
info_thread.start()
else:
info_thread = None
num_items = len(input_list)
for index, item_xml in enumerate(input_list):
if dialog2:
percent = ((index + 1) * 100) / num_items
dialog2.update(percent, _("processing item"),
"%s of %s" % (index + 1, num_items))
result_item = self.process_item(item_xml)
if result_item:
items.append(result_item)
if dialog2:
dialog2.close()
if info_thread:
info = info_thread.join() # wait till for thread to finish
if info:
skip = 0
for index, item in enumerate(items):
if not item.get("summary", None) and not item.get("imdb", None):
continue
if index <= len(info) - 1:
for info_item in info[index + skip:]:
if not info_item:
break
if info_item.get("manual", ""):
item["info"]["plot"] = info_item["plot"]
break
if "imdb_id" in info_item:
if info_item["imdb_id"] == item["imdb"]:
item["info"].update(info_item)
break
else:
skip = skip + 1
else:
break
else:
break
for item in items:
if not item.get("imdb", ""):
continue
match = koding.Get_From_Table("watched", {
"identifier": item["imdb"],
"season": item["season"],
"episode": item["episode"]
})
if match:
match = match[0]
if match["watched"] and match["watched"] == "1":
item["info"]["playcount"] = 1
item["info"]["watched"] = 1
if ADDON.getSetting("trailer_context") == "true":
for item in items:
if "trailer" in item["info"]:
try:
item["context"].append((_("Trailer"), "PlayMedia({0})".format(
item["info"]["trailer"])))
except:
pass
return items
def process_item(self, item_xml):
hook_result = run_hook("process_item", item_xml)
if hook_result:
if hook_result.get("content", ""):
self.content = hook_result["content"]
return hook_result
try:
is_widget = __builtin__.JEN_WIDGET
except:
is_widget = False
item = JenItem(item_xml)
enable_gifs = xbmcaddon.Addon().getSetting('enable_gifs') == "true"
if item.item_string.startswith(""):
title = item["title"]
if title == "":
title = item["name"]
koding.dolog("depricated: ")
koding.dolog("name: " + repr(title))
koding.dolog("Use instead of in your xml")
koding.dolog("#####################")
try:
title = xbmcaddon.Addon().getLocalizedString(int(title))
except ValueError:
pass
is_playable = True
mode = "get_sources"
link = item_xml
elif item.item_string.startswith(""):
title = item["name"]
if title == "":
title = item["title"]
try:
title = xbmcaddon.Addon().getLocalizedString(int(title))
except ValueError:
pass
if item["link"].startswith("message"):
is_playable = True
mode = "message"
link = item["link"].replace("message(", "")[:-1]
if link.startswith("http"):
text = requests.request("GET", replace_url(link), verify=False).content
link = text
else:
is_playable = False
mode = "get_list"
link = item["link"]
elif item.item_string.startswith(""):
link = item["link"]
title = item["name"]
if title == "":
title = item["title"]
try:
title = xbmcaddon.Addon().getLocalizedString(int(title))
except ValueError:
pass
if link.endswith("openSettings"):
is_playable = True
link = ""
mode = "Settings"
elif link.endswith("developer"):
import os
is_playable = False
link = '{"file_name":"testings.xml"}'
mode = "Testings"
path = xbmcaddon.Addon().getAddonInfo('profile')
profile_path = xbmc.translatePath(path).decode('utf-8')
test_file = os.path.join(profile_path, "testings.xml")
if not os.path.exists(test_file):
return
elif link.endswith("search"):
is_playable = False
link = ""
mode = "Search"
else:
if "youtube" in link and ("user" in link or "playlist" in link):
is_playable = True
mode = 'get_sources'
link = item_xml
else:
is_playable = True
mode = 'get_sources'
link = item_xml
else:
koding.dolog("other: " + repr(item), xbmc.LOGDEBUG)
raise Exception()
# filter out "unreleased"
if title == "" or " /title" in title or "/ title" in title:
return
if is_widget:
if mode == "message":
return
context = get_context_items(item)
content = item["content"]
if content == "boxset":
content = "set"
if content != '':
self.content = content
imdb = item["imdb"]
season = item["season"] or '0'
episode = item["episode"] or '0'
year = item["year"] or '0'
fanart = None
if enable_gifs:
fan_url = item.get("animated_fanart", "")
if fan_url and fan_url != "0":
fanart = replace_url(fan_url)
if not fanart:
fanart = replace_url(
item.get("fanart", self.list_fanart), replace_gif=False)
thumbnail = None
if enable_gifs:
thumb_url = item.get("animated_thumbnail", "")
if thumb_url and thumb_url != "0":
thumbnail = replace_url(thumb_url)
if not thumbnail:
thumbnail = replace_url(
item.get("thumbnail", self.list_image), replace_gif=False)
premiered = item.get("premiered", "")
if premiered:
try:
today_tt = datetime.date.today().timetuple()
premiered_tt = time.strptime(premiered, "%Y-%m-%d")
if today_tt < premiered_tt:
title = "[COLORyellow]" + title + "[/COLOR]"
except:
koding.dolog("wrong premiered format")
pass
result_item = {
'label': title,
'icon': thumbnail,
'fanart': fanart,
'mode': mode,
'url': link,
'folder': not is_playable,
'imdb': imdb,
'content': content,
'season': season,
'episode': episode,
'info': {},
'year': year,
'context': context,
'plot': item.get("summary", None),
"summary": item.get("summary", None)
}
if fanart:
result_item["properties"] = {'fanart_image': fanart}
result_item['fanart_small'] = fanart
if content in ['movie', 'episode']:
# only add watched data for applicable items
result_item['info']['watched'] = 0
return result_item
def get_content_type(self):
"""return content type of list"""
try:
if self.content in ["movie", "set"]:
return "movies"
elif self.content == "tvshow":
return "tvshows"
elif self.content == "season":
return "seasons"
elif self.content == 'episode':
return "episodes"
elif self.content == 'artist':
return "artists"
elif self.content == 'album':
return "albums"
elif self.content == 'song':
return "songs"
else:
return "files"
except:
return "files"
class JenItem(object):
"""represents an item in a jen xml list"""
def __init__(self, item_xml):
self.item_string = item_xml
def get_tag_content(self, tag):
"""
parses xml string for the content of a tag
Args:
collection: xml to search through
tag: tag to find the content in
default: value to return if nothing found
Returns:
tag content or default value if content is not found
"""
return re.findall('<%s>(.+?)%s>' % (tag, tag), self.item_string,
re.MULTILINE | re.DOTALL)
def keys(self):
"""returns all keys in item"""
return re.findall("<([^/]+?)>", self.item_string)[1:]
def get(self, tag, default):
"""proxy for get_tag_content"""
try:
return self.get_tag_content(tag)[0]
except IndexError:
return default
def getAll(self, tag):
"get all tags contents" ""
return self.get_tag_content(tag)
def __getitem__(self, item):
return self.get(item, "")
def __eq__(self, other):
return bool(self.item_string == other.item_string)
def __repr__(self):
return self.item_string
def display_list(items, content_type, pins):
"display jen list in kodi"
if ADDON.getSetting("enable_cache") == "true":
Items = fetch_from_db(pins)
if Items:
import xbmcplugin
import sys
display_code(items, content_type)
elif not Items:
import xbmcplugin
import sys
hook_result = run_hook("display_list", items, content_type, pins)
if hook_result:
return
pins = pins.lower()
save_to_db(items, pins)
display_code(items, content_type)
else:
import xbmcplugin
import sys
hook_result = run_hook("display_list", items, content_type, pins)
if hook_result:
return
display_code(items, content_type)
def display_list2(items, content_type, pins):
save_to_db(items, pins)
display_data(Items)
def display_data(Items):
for item in Items:
context_items = []
if ADDON.getSetting("settings_context") == "true":
context_items.append((_("Settings"),
"RunPlugin({0})".format(
get_addon_url("Settings"))))
context_items.extend(item["context"])
koding.Add_Dir(
name=item["label"],
url=item["url"],
mode=item["mode"],
folder=item["folder"],
icon=item["icon"],
fanart=item["fanart"],
context_items=context_items,
content_type="video",
info_labels=item["info"],
set_property=item.get("properties", {}),
set_art={"poster": item["icon"]})
#xbmcplugin.setContent(int(sys.argv[1]), content_type)
def save_to_db(items, url):
if not items or not url:
return False
url2 = clean_url(url)
koding.reset_db()
test_spec = {
"columns": {
"value": "TEXT",
"created": "TEXT"}
}
koding.Create_Table(url2, test_spec)
set_val = base64.b64encode(pickle.dumps(items))
koding.Add_To_Table(url2,
{
"value": set_val,
"created": time.time()
})
table_name_spec = {
"columns": {
"name": "TEXT",}
}
koding.Create_Table("Table_names", table_name_spec)
plugin_table_name_spec = {
"columns": {
"name": "TEXT",}
}
koding.Create_Table("Plugin_table_names", plugin_table_name_spec)
if "PLugin" in url:
koding.Add_To_Table("Plugin_table_names",
{
"name": url2,
})
else:
koding.Add_To_Table("Table_names",
{
"name": url2,
})
def fetch_from_db(url):
koding.reset_db()
url2 = clean_url(url)
match = koding.Get_All_From_Table(url2)
if match:
match = match[0]
if not match["value"]:
return None
match_item = match["value"]
try:
result = pickle.loads(base64.b64decode(match_item))
except:
return None
created_time = match["created"]
if float(created_time) + int(main_cache_time) <= time.time():
koding.Remove_Table(url2)
db = sqlite3.connect('%s' % (database_loc))
cursor = db.cursor()
db.execute("vacuum")
db.commit()
db.close()
return result
else:
return result
return result
else:
return []
def clean_url(url):
url = re.sub('\\\|/|\(|\)|\[|\]|\{|\}|-|:|;|\*|\?|"|\'|<|>|\_|\.|\?|%20|_', '', url).replace("|","").replace(" ","")
return url
def display_code(Items, content_type):
for item in Items:
context_items = []
if ADDON.getSetting("settings_context") == "true":
context_items.append((_("Settings"),
"RunPlugin({0})".format(
get_addon_url("Settings"))))
context_items.extend(item["context"])
koding.Add_Dir(
name=item["label"],
url=item["url"],
mode=item["mode"],
folder=item["folder"],
icon=item["icon"],
fanart=item["fanart"],
context_items=context_items,
content_type="video",
info_labels=item["info"],
set_property=item.get("properties", {}),
set_art={"poster": item["icon"]})
xbmcplugin.setContent(int(sys.argv[1]), content_type)
class threadWithReturn(Thread):
def __init__(self, *args, **kwargs):
super(threadWithReturn, self).__init__(*args, **kwargs)
self._return = None
def run(self):
if self._Thread__target is not None:
self._return = self._Thread__target(*self._Thread__args,
**self._Thread__kwargs)
def join(self, *args, **kwargs):
super(threadWithReturn, self).join(*args, **kwargs)
return self._return