diff --git a/Tested.bundle/Contents/Code/__init__.py b/Tested.bundle/Contents/Code/__init__.py
index 1028e7b..071799a 100644
--- a/Tested.bundle/Contents/Code/__init__.py
+++ b/Tested.bundle/Contents/Code/__init__.py
@@ -1,70 +1,75 @@
-API_PATH = 'http://api.tested.com'
-API_KEY = '060b7347a9f8e317a1a9efceea0f8b70bf7018f6'
-
ART = 'art-default.png'
ICON = 'icon-default.png'
-def ValidatePrefs():
- link_code = Prefs['link_code'].upper()
- if link_code and len(link_code) == 6:
- response = JSON.ObjectFromURL(API_PATH + '/validate?link_code=' + link_code + '&format=json')
- if 'api_key' in response:
- Dict['api_key'] = response['api_key']
- Dict.Save()
-
+# Category Icons
+LIVE_ICON = 'live.png'
+LATEST_ICON = 'latest.png'
+SCIENCE_ICON = 'science.png'
+REVIEW_ICON = 'review.png'
+APP_OF_THE_DAY_ICON = 'app-of-the-day.png'
+DIY_ICON = 'diy.png'
+COFFEE_ICON = 'coffee.png'
+HOWTO_ICON = 'howto.png'
+MAKERBOT_ICON = 'makerbot.png'
+SEARCH_ICON = 'search.png'
+
+# Creates the main menu (the first menu that pops up when opening the app)
@handler('/video/tested', 'Tested')
def MainMenu():
- if 'api_key' in Dict:
- global API_KEY
- API_KEY = Dict['api_key']
-
oc = ObjectContainer()
- # Live stream
- response = JSON.ObjectFromURL(API_PATH + '/chats/?api_key=' + API_KEY + '&format=json')
-
- if response['status_code'] == 100:
- # Revert to the default key
- Dict.Reset()
- Dict.Save()
-
- chats = response['results']
- for chat in chats:
- url = 'http://www.justin.tv/widgets/live_embed_player.swf?channel=' + chat['channel_name'] + '&auto_play=true&start_volume=25'
- if chat['password']:
- url += '&publisherGuard=' + chat['password']
-
+ # Checks to see if a livestream is going on, since the Tested API is no longer around it checks the Justin TV API
+ # Assumes live streams are on the "tested" channel
+ LIVE_CHANNEL = 'tested'
+ response = JSON.ObjectFromURL('http://api.justin.tv/api/stream/list.json?channel=' + LIVE_CHANNEL)
+
+ if( len(response) > 0):
+
+ stream = response[0] #This should be fine as there can only ever be one live stream going per channel
+
+ url = 'http://www.justin.tv/widgets/live_embed_player.swf?channel=' + LIVE_CHANNEL + '&auto_play=true&start_volume=25'
+ title = stream['title']
+
oc.add(
VideoClipObject(
key=WebVideoURL(url),
- title='LIVE: ' + chat['title'],
- summary=chat['deck'],
+ title='LIVE: ' + title,
source_title='Justin.tv',
- thumb=chat['image']['super_url'],
+ thumb=R(LIVE_ICON),
art=R(ART),
- rating_key=chat['channel_name']
+ rating_key=LIVE_CHANNEL
)
)
+
oc.add(
DirectoryObject(
key='/video/tested/videos',
title='Latest',
- summary='Watch the newest stuff.',
- thumb=R(ICON),
+ summary='',
+ thumb=R(LATEST_ICON),
art=R(ART)
)
)
-
- categories = JSON.ObjectFromURL(API_PATH + '/video_types/?api_key=' + API_KEY + '&format=json')['results']
-
+
+ # The categories were chosen using a script to see what the most popular tags were on tested youtube videos
+ # Script can be found here: https://gist.github.com/2940575
+ categories = [
+ ('Science & Technology', 'Tech', '', R(SCIENCE_ICON)),
+ ('Reviews', 'review', '', R(REVIEW_ICON)),
+ ('App Of The Day', 'app,of,the,day', '', R(APP_OF_THE_DAY_ICON)),
+ ('DIY', 'diy', '', R(DIY_ICON)),
+ ('Coffee', 'coffee', '', R(COFFEE_ICON)),
+ ('Howtos', 'howto', '', R(HOWTO_ICON)),
+ ('Makerbot', 'makerbot', '', R(MAKERBOT_ICON))
+ ]
for cat in categories:
oc.add(
DirectoryObject(
- key='/video/tested/videos/?cat_id=' + str(cat['id']),
- title=cat['name'],
- summary=cat['deck'],
- thumb=R(ICON),
+ key='/video/tested/videos/?cat_id=' + str(cat[1]),
+ title=cat[0],
+ summary='',
+ thumb=cat[3],
art=R(ART)
)
)
@@ -74,64 +79,133 @@ def MainMenu():
key=Callback(Videos),
title='Search',
prompt='Search',
- thumb=R(ICON),
- art=R(ART)
- )
- )
-
- oc.add(
- PrefsObject(
- title='Preferences',
- thumb=R(ICON),
+ thumb=R(SEARCH_ICON),
art=R(ART)
)
)
return oc
-
+
+# Handles when a category is selected from the main menu
@route('/video/tested/videos')
-def Videos(cat_id=None, query=None):
- if 'api_key' in Dict:
- global API_KEY
- API_KEY = Dict['api_key']
+def Videos(cat_id=None, query=None, page = 1):
+
+ # Apparently plex *always* passes parameters to these functions as strings
+ # So have to 'unconvert' the parameters
+ page = int(page)
+
+ if query == '':
+ query = None
+
+ if cat_id == '':
+ cat_id = None
+
+ # Change this to change how many results are displayed per page, maximum is 50 according to youtube API
+ MAXRESULTS = 20
oc = ObjectContainer()
+ Log(str(page))
+ url_suffix = '&start-index=' + str((page - 1) * MAXRESULTS + 1)
+ url_suffix += '&max-results=' + str(MAXRESULTS)
+
if query:
- videos = JSON.ObjectFromURL(API_PATH + '/search/?api_key=' + API_KEY + '&resources=video&query=' + query + '&format=json')['results']
+ videos = JSON.ObjectFromURL('http://gdata.youtube.com/feeds/api/videos?alt=json&author=testedcom&prettyprint=true&v=2' + url_suffix + '&q=' + query.replace(' ', '%20'))
elif cat_id:
- videos = JSON.ObjectFromURL(API_PATH + '/videos/?api_key=' + API_KEY + '&video_type=' + cat_id + '&sort=-publish_date&format=json')['results']
+ videos = JSON.ObjectFromURL('http://gdata.youtube.com/feeds/api/videos?alt=json&author=testedcom&prettyprint=true&v=2' + url_suffix + '&orderby=published&category=' + cat_id)
else:
- videos = JSON.ObjectFromURL(API_PATH + '/videos/?api_key=' + API_KEY + '&sort=-publish_date&format=json')['results']
-
- if Prefs['quality'] == 'Auto':
- if 'hd_url' in videos[0]:
- quality = 'hd_url'
- else:
- quality = 'high_url'
- else:
- quality = Prefs['quality'].lower() + '_url'
-
- for vid in videos:
- if 'wallpaper_image' not in vid or not vid['wallpaper_image']: # or whatever it gets called
- vid_art = R(ART)
- else:
- vid_art = vid['wallpaper_image']
-
- if quality == 'hd_url':
- url = vid[quality] + '&api_key=' + API_KEY
- else:
- url = vid[quality]
-
- oc.add(
- VideoClipObject(
- key=url,
- title=vid['name'],
- summary=vid['deck'],
- thumb=vid['image']['super_url'],
- art=vid_art,
- rating_key=vid['id']
- )
- )
+ videos = JSON.ObjectFromURL('https://gdata.youtube.com/feeds/api/users/testedcom/uploads?alt=json&prettyprint=true&v=2' + url_suffix )
+
+
+ # The following tidbit was inspired by the youtube plugin that plex uses
+ if videos['feed'].has_key('entry'):
+
+ for video in videos['feed']['entry']:
+
+ if CheckRejectedEntry(video):
+ continue
+
+ video_url = None
+ for video_links in video['link']:
+ if video_links['type'] == 'text/html':
+ video_url = video_links['href']
+ break
+
+ if video_url is None:
+ Log('Found video that had no URL')
+ continue
+
+ video_title = video['media$group']['media$title']['$t']
+ thumb = video['media$group']['media$thumbnail'][2]['url']
+ duration = int(video['media$group']['yt$duration']['seconds']) * 1000
+
+ summary = None
+ try: summary = video['media$group']['media$description']['$t']
+ except: pass
+
+ rating = None
+ try: rating = float(video['gd$rating']['average']) * 2
+ except: pass
+
+ date = None
+ try: date = Datetime.ParseDate(video['published']['$t'].split('T')[0])
+ except:
+ try: date = Datetime.ParseDate(video['updated']['$t'].split('T')[0])
+ except: pass
+
+ oc.add(
+ VideoClipObject(
+ url=video_url + "&hd=1",
+ title=video_title,
+ summary=summary,
+ thumb=Callback(GetThumb, url = thumb),
+ art=R(ART),
+ rating = rating,
+ originally_available_at = date
+ )
+ )
+
+ # Handles pagination if there are more results that were not shown
+ if videos['feed'].has_key('openSearch$totalResults'):
+ total_results = int(videos['feed']['openSearch$totalResults']['$t'])
+ items_per_page = int(videos['feed']['openSearch$itemsPerPage']['$t'])
+ start_index = int(videos['feed']['openSearch$startIndex']['$t'])
+
+ if not query:
+ query = ''
+
+ if not cat_id:
+ cat_id = ''
+
+ if (start_index + items_per_page) < total_results:
+ oc.add(DirectoryObject(key = Callback(Videos, cat_id = cat_id, query = query, page = page + 1), title = 'Next'))
return oc
+
+# Retrives the thumbnail of a video from the given url, caching it
+def GetThumb(url):
+ try:
+ data = HTTP.Request(url, cacheTime = CACHE_1WEEK).content
+ return DataObject(data, 'image/jpeg')
+ except:
+ Log.Exception("Error when attempting to get the associated thumb")
+ return Redirect(R(ICON))
+
+# Checks to see if the video passed is a valid video (not deleted, rejected, restricted, etc
+def CheckRejectedEntry(entry):
+ try:
+ status_name = entry['app$control']['yt$state']['name']
+
+ if status_name in ['deleted', 'rejected', 'failed']:
+ return True
+
+ if status_name == 'restricted':
+ status_reason = entry['app$control']['yt$state']['reasonCode']
+
+ if status_reason in ['private', 'requesterRegion']:
+ return True
+
+ except:
+ pass
+
+ return False
\ No newline at end of file
diff --git a/Tested.bundle/Contents/DefaultPrefs.json b/Tested.bundle/Contents/DefaultPrefs.json
index b366385..32960f8 100644
--- a/Tested.bundle/Contents/DefaultPrefs.json
+++ b/Tested.bundle/Contents/DefaultPrefs.json
@@ -1,15 +1,2 @@
[
- {
- "id": "link_code",
- "label": "Link Code",
- "type": "text",
- "default": ""
- },
- {
- "id": "quality",
- "type": "enum",
- "label": "Quality",
- "values": ["Auto", "Low", "High", "HD"],
- "default": "Auto"
- }
]
\ No newline at end of file
diff --git a/Tested.bundle/Contents/Info.plist b/Tested.bundle/Contents/Info.plist
index c7dde26..254e3d9 100644
--- a/Tested.bundle/Contents/Info.plist
+++ b/Tested.bundle/Contents/Info.plist
@@ -5,7 +5,7 @@
CFBundleIdentifier
com.plexapp.plugins.tested
PlexClientPlatforms
- MacOSX,iOS,Android,LGTV
+ MacOSX,iOS,Android,LGTV,Windows
PlexFrameworkVersion
2
PlexPluginClass
diff --git a/Tested.bundle/Contents/Resources/app-of-the-day.png b/Tested.bundle/Contents/Resources/app-of-the-day.png
new file mode 100644
index 0000000..1951d1d
Binary files /dev/null and b/Tested.bundle/Contents/Resources/app-of-the-day.png differ
diff --git a/Tested.bundle/Contents/Resources/art-default.png b/Tested.bundle/Contents/Resources/art-default.png
index 1df9dca..e0c4bb8 100644
Binary files a/Tested.bundle/Contents/Resources/art-default.png and b/Tested.bundle/Contents/Resources/art-default.png differ
diff --git a/Tested.bundle/Contents/Resources/coffee.png b/Tested.bundle/Contents/Resources/coffee.png
new file mode 100644
index 0000000..68d3aa1
Binary files /dev/null and b/Tested.bundle/Contents/Resources/coffee.png differ
diff --git a/Tested.bundle/Contents/Resources/diy.png b/Tested.bundle/Contents/Resources/diy.png
new file mode 100644
index 0000000..9a2e613
Binary files /dev/null and b/Tested.bundle/Contents/Resources/diy.png differ
diff --git a/Tested.bundle/Contents/Resources/howto.png b/Tested.bundle/Contents/Resources/howto.png
new file mode 100644
index 0000000..313db7f
Binary files /dev/null and b/Tested.bundle/Contents/Resources/howto.png differ
diff --git a/Tested.bundle/Contents/Resources/icon-default.png b/Tested.bundle/Contents/Resources/icon-default.png
index c531c7e..246c7aa 100644
Binary files a/Tested.bundle/Contents/Resources/icon-default.png and b/Tested.bundle/Contents/Resources/icon-default.png differ
diff --git a/Tested.bundle/Contents/Resources/latest.png b/Tested.bundle/Contents/Resources/latest.png
new file mode 100644
index 0000000..ae9b80a
Binary files /dev/null and b/Tested.bundle/Contents/Resources/latest.png differ
diff --git a/Tested.bundle/Contents/Resources/live.png b/Tested.bundle/Contents/Resources/live.png
new file mode 100644
index 0000000..35b5ea6
Binary files /dev/null and b/Tested.bundle/Contents/Resources/live.png differ
diff --git a/Tested.bundle/Contents/Resources/makerbot.png b/Tested.bundle/Contents/Resources/makerbot.png
new file mode 100644
index 0000000..a27395b
Binary files /dev/null and b/Tested.bundle/Contents/Resources/makerbot.png differ
diff --git a/Tested.bundle/Contents/Resources/review.png b/Tested.bundle/Contents/Resources/review.png
new file mode 100644
index 0000000..2d4f2ce
Binary files /dev/null and b/Tested.bundle/Contents/Resources/review.png differ
diff --git a/Tested.bundle/Contents/Resources/science.png b/Tested.bundle/Contents/Resources/science.png
new file mode 100644
index 0000000..3593b23
Binary files /dev/null and b/Tested.bundle/Contents/Resources/science.png differ
diff --git a/Tested.bundle/Contents/Resources/search.png b/Tested.bundle/Contents/Resources/search.png
new file mode 100644
index 0000000..de3bbdf
Binary files /dev/null and b/Tested.bundle/Contents/Resources/search.png differ