411 lines
15 KiB
Python
411 lines
15 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
import urllib.request, urllib.parse, urllib.error
|
|
import copy
|
|
|
|
from ..utils import etree
|
|
from .. import variables as v, utils
|
|
|
|
ICON_PATH = 'special://home/addons/plugin.video.plexkodiconnect/icon.png'
|
|
RECOMMENDED_SCORE_LOWER_BOUND = 7
|
|
|
|
# Logic of the following nodes:
|
|
# (node_type,
|
|
# label/node name,
|
|
# args for PKC add-on callback,
|
|
# Kodi "content",
|
|
# Bool: does this node's xml even point back to PKC add-on callback?
|
|
# )
|
|
NODE_TYPES = {
|
|
v.PLEX_TYPE_MOVIE: (
|
|
('plex_ondeck',
|
|
utils.lang(39500), # "On Deck"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/onDeck',
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_MOVIE,
|
|
True),
|
|
('ondeck',
|
|
utils.lang(39502), # "PKC On Deck (faster)"
|
|
{},
|
|
v.CONTENT_TYPE_MOVIE,
|
|
False),
|
|
('recent',
|
|
utils.lang(30174), # "Recently Added"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/recentlyAdded',
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_MOVIE,
|
|
False),
|
|
('all',
|
|
'{self.name}', # We're using this section's name
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/all',
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_MOVIE,
|
|
False),
|
|
('recommended',
|
|
utils.lang(30230), # "Recommended"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': ('/library/sections/{self.section_id}&%s'
|
|
% urllib.parse.urlencode({'sort': 'rating:desc'})),
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_MOVIE,
|
|
False),
|
|
('genres',
|
|
utils.lang(135), # "Genres"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/genre',
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_MOVIE,
|
|
False),
|
|
('sets',
|
|
utils.lang(39501), # "Collections"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/collection',
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_MOVIE,
|
|
False),
|
|
('random',
|
|
utils.lang(30227), # "Random"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': ('/library/sections/{self.section_id}&%s'
|
|
% urllib.parse.urlencode({'sort': 'random'})),
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_MOVIE,
|
|
False),
|
|
('lastplayed',
|
|
utils.lang(568), # "Last played"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/recentlyViewed',
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_MOVIE,
|
|
False),
|
|
('browse',
|
|
utils.lang(39702), # "Browse by folder"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/folder',
|
|
'plex_type': '{self.section_type}',
|
|
'section_id': '{self.section_id}',
|
|
'folder': True
|
|
},
|
|
v.CONTENT_TYPE_MOVIE,
|
|
True),
|
|
('more',
|
|
utils.lang(22082), # "More..."
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}',
|
|
'section_id': '{self.section_id}',
|
|
'folder': True
|
|
},
|
|
v.CONTENT_TYPE_FILE,
|
|
True),
|
|
),
|
|
###########################################################
|
|
v.PLEX_TYPE_SHOW: (
|
|
('ondeck',
|
|
utils.lang(39500), # "On Deck"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/onDeck',
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_EPISODE,
|
|
True),
|
|
('recent',
|
|
utils.lang(30174), # "Recently Added"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/recentlyAdded',
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_EPISODE,
|
|
False),
|
|
('all',
|
|
'{self.name}', # We're using this section's name
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/all',
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_SHOW,
|
|
False),
|
|
('recommended',
|
|
utils.lang(30230), # "Recommended"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': ('/library/sections/{self.section_id}&%s'
|
|
% urllib.parse.urlencode({'sort': 'rating:desc'})),
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_SHOW,
|
|
False),
|
|
('genres',
|
|
utils.lang(135), # "Genres"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/genre',
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_SHOW,
|
|
False),
|
|
('sets',
|
|
utils.lang(39501), # "Collections"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/collection',
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_SHOW,
|
|
True), # There are no sets/collections for shows with Kodi
|
|
('random',
|
|
utils.lang(30227), # "Random"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': ('/library/sections/{self.section_id}&%s'
|
|
% urllib.parse.urlencode({'sort': 'random'})),
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_SHOW,
|
|
False),
|
|
('lastplayed',
|
|
utils.lang(568), # "Last played"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': ('/library/sections/{self.section_id}/recentlyViewed&%s'
|
|
% urllib.parse.urlencode({'type': v.PLEX_TYPE_NUMBER_FROM_PLEX_TYPE[v.PLEX_TYPE_EPISODE]})),
|
|
'section_id': '{self.section_id}'
|
|
},
|
|
v.CONTENT_TYPE_EPISODE,
|
|
False),
|
|
('browse',
|
|
utils.lang(39702), # "Browse by folder"
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}/folder',
|
|
'section_id': '{self.section_id}',
|
|
'folder': True
|
|
},
|
|
v.CONTENT_TYPE_EPISODE,
|
|
True),
|
|
('more',
|
|
utils.lang(22082), # "More..."
|
|
{
|
|
'mode': 'browseplex',
|
|
'key': '/library/sections/{self.section_id}',
|
|
'section_id': '{self.section_id}',
|
|
'folder': True
|
|
},
|
|
v.CONTENT_TYPE_FILE,
|
|
True),
|
|
),
|
|
}
|
|
|
|
|
|
def node_pms(section, node_name, args):
|
|
"""
|
|
Nodes where the logic resides with the PMS - we're NOT building an
|
|
xml that filters and sorts, but point to PKC add-on path
|
|
|
|
Be sure to set args['folder'] = True if the listing is a folder and does
|
|
not contain playable elements like movies, episodes or tracks
|
|
"""
|
|
if 'folder' in args:
|
|
args = copy.deepcopy(args)
|
|
args.pop('folder')
|
|
folder = True
|
|
else:
|
|
folder = False
|
|
xml = etree.Element('node',
|
|
attrib={'order': str(section.order),
|
|
'type': 'folder' if folder else 'filter'})
|
|
etree.SubElement(xml, 'label').text = node_name
|
|
etree.SubElement(xml, 'icon').text = ICON_PATH
|
|
etree.SubElement(xml, 'content').text = section.content
|
|
etree.SubElement(xml, 'path').text = section.addon_path(args)
|
|
return xml
|
|
|
|
|
|
def node_ondeck(section, node_name):
|
|
"""
|
|
For movies only - returns in-progress movies sorted by last played
|
|
"""
|
|
xml = etree.Element('node', attrib={'order': str(section.order),
|
|
'type': 'filter'})
|
|
etree.SubElement(xml, 'match').text = 'all'
|
|
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
|
|
'operator': 'is'})
|
|
etree.SubElement(rule, 'value').text = section.name
|
|
etree.SubElement(xml, 'rule', attrib={'field': 'inprogress',
|
|
'operator': 'true'})
|
|
etree.SubElement(xml, 'label').text = node_name
|
|
etree.SubElement(xml, 'icon').text = ICON_PATH
|
|
etree.SubElement(xml, 'content').text = section.content
|
|
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
|
|
etree.SubElement(xml,
|
|
'order',
|
|
attrib={'direction':
|
|
'descending'}).text = 'lastplayed'
|
|
return xml
|
|
|
|
|
|
def node_recent(section, node_name):
|
|
xml = etree.Element('node',
|
|
attrib={'order': str(section.order),
|
|
'type': 'filter'})
|
|
etree.SubElement(xml, 'match').text = 'all'
|
|
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
|
|
'operator': 'is'})
|
|
etree.SubElement(rule, 'value').text = section.name
|
|
if ((section.section_type == v.PLEX_TYPE_SHOW and
|
|
utils.settings('TVShowWatched') == 'false') or
|
|
(section.section_type == v.PLEX_TYPE_MOVIE and
|
|
utils.settings('MovieShowWatched') == 'false')):
|
|
# Adds an additional rule if user deactivated the PKC setting
|
|
# "Recently Added: Also show already watched episodes"
|
|
# or
|
|
# "Recently Added: Also show already watched episodes"
|
|
rule = etree.SubElement(xml, 'rule', attrib={'field': 'playcount',
|
|
'operator': 'is'})
|
|
etree.SubElement(rule, 'value').text = '0'
|
|
etree.SubElement(xml, 'label').text = node_name
|
|
etree.SubElement(xml, 'icon').text = ICON_PATH
|
|
etree.SubElement(xml, 'content').text = section.content
|
|
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
|
|
etree.SubElement(xml,
|
|
'order',
|
|
attrib={'direction':
|
|
'descending'}).text = 'dateadded'
|
|
return xml
|
|
|
|
|
|
def node_all(section, node_name):
|
|
xml = etree.Element('node', attrib={'order': str(section.order),
|
|
'type': 'filter'})
|
|
etree.SubElement(xml, 'match').text = 'all'
|
|
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
|
|
'operator': 'is'})
|
|
etree.SubElement(rule, 'value').text = section.name
|
|
etree.SubElement(xml, 'label').text = node_name
|
|
etree.SubElement(xml, 'icon').text = ICON_PATH
|
|
etree.SubElement(xml, 'content').text = section.content
|
|
etree.SubElement(xml,
|
|
'order',
|
|
attrib={'direction':
|
|
'ascending'}).text = 'sorttitle'
|
|
return xml
|
|
|
|
|
|
def node_recommended(section, node_name):
|
|
xml = etree.Element('node', attrib={'order': str(section.order),
|
|
'type': 'filter'})
|
|
etree.SubElement(xml, 'match').text = 'all'
|
|
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
|
|
'operator': 'is'})
|
|
etree.SubElement(rule, 'value').text = section.name
|
|
# rule = etree.SubElement(xml, 'rule', attrib={'field': 'rating',
|
|
# 'operator': 'greaterthan'})
|
|
# etree.SubElement(rule, 'value').text = unicode(RECOMMENDED_SCORE_LOWER_BOUND)
|
|
etree.SubElement(xml, 'label').text = node_name
|
|
etree.SubElement(xml, 'icon').text = ICON_PATH
|
|
etree.SubElement(xml, 'content').text = section.content
|
|
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
|
|
etree.SubElement(xml,
|
|
'order',
|
|
attrib={'direction':
|
|
'descending'}).text = 'rating'
|
|
return xml
|
|
|
|
|
|
def node_genres(section, node_name):
|
|
xml = etree.Element('node', attrib={'order': str(section.order),
|
|
'type': 'filter'})
|
|
etree.SubElement(xml, 'match').text = 'all'
|
|
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
|
|
'operator': 'is'})
|
|
etree.SubElement(rule, 'value').text = section.name
|
|
etree.SubElement(xml, 'label').text = node_name
|
|
etree.SubElement(xml, 'icon').text = ICON_PATH
|
|
etree.SubElement(xml, 'content').text = section.content
|
|
etree.SubElement(xml,
|
|
'order',
|
|
attrib={'direction':
|
|
'ascending'}).text = 'sorttitle'
|
|
etree.SubElement(xml, 'group').text = 'genres'
|
|
return xml
|
|
|
|
|
|
def node_sets(section, node_name):
|
|
xml = etree.Element('node', attrib={'order': str(section.order),
|
|
'type': 'filter'})
|
|
etree.SubElement(xml, 'match').text = 'all'
|
|
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
|
|
'operator': 'is'})
|
|
etree.SubElement(rule, 'value').text = section.name
|
|
# "Collections"
|
|
etree.SubElement(xml, 'label').text = node_name
|
|
etree.SubElement(xml, 'icon').text = ICON_PATH
|
|
etree.SubElement(xml, 'content').text = section.content
|
|
etree.SubElement(xml,
|
|
'order',
|
|
attrib={'direction':
|
|
'ascending'}).text = 'sorttitle'
|
|
etree.SubElement(xml, 'group').text = 'sets'
|
|
return xml
|
|
|
|
|
|
def node_random(section, node_name):
|
|
xml = etree.Element('node', attrib={'order': str(section.order),
|
|
'type': 'filter'})
|
|
etree.SubElement(xml, 'match').text = 'all'
|
|
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
|
|
'operator': 'is'})
|
|
etree.SubElement(rule, 'value').text = section.name
|
|
etree.SubElement(xml, 'label').text = node_name
|
|
etree.SubElement(xml, 'icon').text = ICON_PATH
|
|
etree.SubElement(xml, 'content').text = section.content
|
|
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
|
|
etree.SubElement(xml,
|
|
'order',
|
|
attrib={'direction':
|
|
'ascending'}).text = 'random'
|
|
return xml
|
|
|
|
|
|
def node_lastplayed(section, node_name):
|
|
xml = etree.Element('node', attrib={'order': str(section.order),
|
|
'type': 'filter'})
|
|
etree.SubElement(xml, 'match').text = 'all'
|
|
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
|
|
'operator': 'is'})
|
|
etree.SubElement(rule, 'value').text = section.name
|
|
rule = etree.SubElement(xml, 'rule', attrib={'field': 'playcount',
|
|
'operator': 'greaterthan'})
|
|
etree.SubElement(rule, 'value').text = '0'
|
|
etree.SubElement(xml, 'label').text = node_name
|
|
etree.SubElement(xml, 'icon').text = ICON_PATH
|
|
etree.SubElement(xml, 'content').text = section.content
|
|
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
|
|
etree.SubElement(xml,
|
|
'order',
|
|
attrib={'direction':
|
|
'descending'}).text = 'lastplayed'
|
|
return xml
|