Improved next page feature
Improved next page feature

# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Crunchyroll # Crunchyroll
# Copyright (C) 2018 MrKrabat # Copyright (C) 2018 MrKrabat
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the # published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version. # License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details. # GNU Affero General Public License for more details.
# #
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
   
import sys import sys
import json  
import time import time
   
import xbmc import xbmc
import xbmcgui import xbmcgui
import xbmcplugin import xbmcplugin
   
from . import api from . import api
from . import view from . import view
   
   
def showQueue(args): def showQueue(args):
""" shows anime queue/playlist """ shows anime queue/playlist
""" """
# api request # api request
payload = {"media_types": "anime|drama", payload = {"media_types": "anime|drama",
"fields": "media.name,media.media_id,media.collection_id,media.collection_name,media.description,media.episode_number,media.created, \ "fields": "media.name,media.media_id,media.collection_id,media.collection_name,media.description,media.episode_number,media.created, \
media.screenshot_image,media.premium_only,media.premium_available,media.available,media.premium_available,media.duration, \ media.screenshot_image,media.premium_only,media.premium_available,media.available,media.premium_available,media.duration, \
series.series_id,series.year,series.publisher_name,series.rating,series.genres,series.landscape_image"} series.series_id,series.year,series.publisher_name,series.rating,series.genres,series.landscape_image"}
req = api.request(args, "queue", payload) req = api.request(args, "queue", payload)
   
# check for error # check for error
if req["error"]: if req["error"]:
view.add_item(args, {"title": args._addon.getLocalizedString(30061)}) view.add_item(args, {"title": args._addon.getLocalizedString(30061)})
view.endofdirectory() view.endofdirectory()
return False return False
   
# display media # display media
for item in req["data"]: for item in req["data"]:
# video no longer available # video no longer available
if not ("most_likely_media" in item and "series" in item and item["most_likely_media"]["available"] and item["most_likely_media"]["premium_available"]): if not ("most_likely_media" in item and "series" in item and item["most_likely_media"]["available"] and item["most_likely_media"]["premium_available"]):
continue continue
   
# add to view # add to view
view.add_item(args, view.add_item(args,
{"title": item["most_likely_media"]["collection_name"] + " #" + item["most_likely_media"]["episode_number"] + " - " + item["most_likely_media"]["name"], {"title": item["most_likely_media"]["collection_name"] + " #" + item["most_likely_media"]["episode_number"] + " - " + item["most_likely_media"]["name"],
"tvshowtitle": item["most_likely_media"]["collection_name"], "tvshowtitle": item["most_likely_media"]["collection_name"],
"duration": item["most_likely_media"]["duration"], "duration": item["most_likely_media"]["duration"],
"playcount": 1 if (100/float(item["most_likely_media"]["duration"]))*int(item["playhead"]) > 90 else 0, "playcount": 1 if (100/float(item["most_likely_media"]["duration"]))*int(item["playhead"]) > 90 else 0,
"episode": item["most_likely_media"]["episode_number"], "episode": item["most_likely_media"]["episode_number"],
"episode_id": item["most_likely_media"]["media_id"], "episode_id": item["most_likely_media"]["media_id"],
"collection_id": item["most_likely_media"]["collection_id"], "collection_id": item["most_likely_media"]["collection_id"],
"series_id": item["series"]["series_id"], "series_id": item["series"]["series_id"],
"plot": item["most_likely_media"]["description"], "plot": item["most_likely_media"]["description"],
"plotoutline": item["most_likely_media"]["description"], "plotoutline": item["most_likely_media"]["description"],
"genre": ", ".join(item["series"]["genres"]), "genre": ", ".join(item["series"]["genres"]),
"year": item["series"]["year"], "year": item["series"]["year"],
"aired": item["most_likely_media"]["created"][:10], "aired": item["most_likely_media"]["created"][:10],
"premiered": item["most_likely_media"]["created"][:10], "premiered": item["most_likely_media"]["created"][:10],
"studio": item["series"]["publisher_name"], "studio": item["series"]["publisher_name"],
"rating": int(item["series"]["rating"])/10.0, "rating": int(item["series"]["rating"])/10.0,
"thumb": item["most_likely_media"]["screenshot_image"]["fwidestar_url"] if item["most_likely_media"]["premium_only"] else item["most_likely_media"]["screenshot_image"]["full_url"], "thumb": item["most_likely_media"]["screenshot_image"]["fwidestar_url"] if item["most_likely_media"]["premium_only"] else item["most_likely_media"]["screenshot_image"]["full_url"],
"fanart": item["series"]["landscape_image"]["full_url"], "fanart": item["series"]["landscape_image"]["full_url"],
"mode": "videoplay"}, "mode": "videoplay"},
isFolder=False) isFolder=False)
   
view.endofdirectory() view.endofdirectory()
return True return True
   
   
def searchAnime(args): def searchAnime(args):
"""Search for anime """Search for anime
""" """
# ask for search string # ask for search string
d = xbmcgui.Dialog().input(args._addon.getLocalizedString(30021), type=xbmcgui.INPUT_ALPHANUM) if not hasattr(args, "search"):
if not d: d = xbmcgui.Dialog().input(args._addon.getLocalizedString(30021), type=xbmcgui.INPUT_ALPHANUM)
return if not d:
  return
  else:
  d = args.search
   
# api request # api request
payload = {"media_types": "anime|drama", payload = {"media_types": "anime|drama",
"q": d, "q": d,
  "limit": 30,
  "offset": int(getattr(args, "offset", 0)),
"fields": "series.name,series.series_id,series.description,series.year,series.publisher_name, \ "fields": "series.name,series.series_id,series.description,series.year,series.publisher_name, \
series.genres,series.portrait_image,series.landscape_image"} series.genres,series.portrait_image,series.landscape_image"}
req = api.request(args, "autocomplete", payload) req = api.request(args, "autocomplete", payload)
   
# check for error # check for error
if req["error"]: if req["error"]:
view.add_item(args, {"title": args._addon.getLocalizedString(30061)}) view.add_item(args, {"title": args._addon.getLocalizedString(30061)})
view.endofdirectory() view.endofdirectory()
return False return False
   
# display media # display media
for item in req["data"]: for item in req["data"]:
# add to view # add to view
view.add_item(args, view.add_item(args,
{"title": item["name"], {"title": item["name"],
"tvshowtitle": item["name"], "tvshowtitle": item["name"],
"series_id": item["series_id"], "series_id": item["series_id"],
"plot": item["description"], "plot": item["description"],
"plotoutline": item["description"], "plotoutline": item["description"],
"genre": ", ".join(item["genres"]), "genre": ", ".join(item["genres"]),
"year": item["year"], "year": item["year"],
"studio": item["publisher_name"], "studio": item["publisher_name"],
"thumb": item["portrait_image"]["full_url"], "thumb": item["portrait_image"]["full_url"],
"fanart": item["landscape_image"]["full_url"], "fanart": item["landscape_image"]["full_url"],
"mode": "series"}, "mode": "series"},
isFolder=True) isFolder=True)
   
  # show next page button
  if len(req["data"]) >= 30:
  view.add_item(args,
  {"title": args._addon.getLocalizedString(30044),
  "offset": int(getattr(args, "offset", 0)) + 30,
  "search": d,
  "mode": args.mode},
  isFolder=True)
   
view.endofdirectory() view.endofdirectory()
return True return True
   
   
def showHistory(args): def showHistory(args):
""" shows history of watched anime """ shows history of watched anime
""" """
# api request # api request
payload = {"media_types": "anime|drama", payload = {"media_types": "anime|drama",
  "limit": 30,
  "offset": int(getattr(args, "offset", 0)),
"fields": "media.name,media.media_id,media.collection_id,media.collection_name,media.description,media.episode_number,media.created, \ "fields": "media.name,media.media_id,media.collection_id,media.collection_name,media.description,media.episode_number,media.created, \
media.screenshot_image,media.premium_only,media.premium_available,media.available,media.premium_available,media.duration,media.playhead, \ media.screenshot_image,media.premium_only,media.premium_available,media.available,media.premium_available,media.duration,media.playhead, \
series.series_id,series.year,series.publisher_name,series.rating,series.genres,series.landscape_image"} series.series_id,series.year,series.publisher_name,series.rating,series.genres,series.landscape_image"}
req = api.request(args, "recently_watched", payload) req = api.request(args, "recently_watched", payload)
   
# check for error # check for error
if req["error"]: if req["error"]:
view.add_item(args, {"title": args._addon.getLocalizedString(30061)}) view.add_item(args, {"title": args._addon.getLocalizedString(30061)})
view.endofdirectory() view.endofdirectory()
return False return False
   
# display media # display media
for item in req["data"]: for item in req["data"]:
# video no longer available # video no longer available
if not ("media" in item and "series" in item and item["media"]["available"] and item["media"]["premium_available"]): if not ("media" in item and "series" in item and item["media"]["available"] and item["media"]["premium_available"]):
continue continue
   
# add to view # add to view
view.add_item(args, view.add_item(args,
{"title": item["media"]["collection_name"] + " #" + item["media"]["episode_number"] + " - " + item["media"]["name"], {"title": item["media"]["collection_name"] + " #" + item["media"]["episode_number"] + " - " + item["media"]["name"],
"tvshowtitle": item["media"]["collection_name"], "tvshowtitle": item["media"]["collection_name"],
"duration": item["media"]["duration"], "duration": item["media"]["duration"],
"playcount": 1 if (100/float(item["media"]["duration"]))*int(item["media"]["playhead"]) > 90 else 0, "playcount": 1 if (100/float(item["media"]["duration"]))*int(item["media"]["playhead"]) > 90 else 0,
"episode": item["media"]["episode_number"], "episode": item["media"]["episode_number"],
"episode_id": item["media"]["media_id"], "episode_id": item["media"]["media_id"],
"collection_id": item["media"]["collection_id"], "collection_id": item["media"]["collection_id"],
"series_id": item["series"]["series_id"], "series_id": item["series"]["series_id"],
"plot": item["media"]["description"], "plot": item["media"]["description"],
"plotoutline": item["media"]["description"], "plotoutline": item["media"]["description"],
"genre": ", ".join(item["series"]["genres"]), "genre": ", ".join(item["series"]["genres"]),
"year": item["series"]["year"], "year": item["series"]["year"],
"aired": item["media"]["created"][:10], "aired": item["media"]["created"][:10],
"premiered": item["media"]["created"][:10], "premiered": item["media"]["created"][:10],
"studio": item["series"]["publisher_name"], "studio": item["series"]["publisher_name"],
"rating": int(item["series"]["rating"])/10.0, "rating": int(item["series"]["rating"])/10.0,
"thumb": item["media"]["screenshot_image"]["fwidestar_url"] if item["media"]["premium_only"] else item["media"]["screenshot_image"]["full_url"], "thumb": item["media"]["screenshot_image"]["fwidestar_url"] if item["media"]["premium_only"] else item["media"]["screenshot_image"]["full_url"],
"fanart": item["series"]["landscape_image"]["full_url"], "fanart": item["series"]["landscape_image"]["full_url"],
"mode": "videoplay"}, "mode": "videoplay"},
isFolder=False) isFolder=False)
   
  # show next page button
  if len(req["data"]) >= 30:
  view.add_item(args,
  {"title": args._addon.getLocalizedString(30044),
  "offset": int(getattr(args, "offset", 0)) + 30,
  "mode": args.mode},
  isFolder=True)
   
view.endofdirectory() view.endofdirectory()
return True return True
   
   
def listSeries(args, mode): def listSeries(args, mode):
""" view all anime from selected mode """ view all anime from selected mode
""" """
# api request # api request
payload = {"media_type": args.genre, payload = {"media_type": args.genre,
"filter": mode, "filter": mode,
"limit": 30, "limit": 30,
"offset": int(getattr(args, "offset", 0)), "offset": int(getattr(args, "offset", 0)),
"fields": "series.name,series.series_id,series.description,series.year,series.publisher_name, \ "fields": "series.name,series.series_id,series.description,series.year,series.publisher_name, \
series.genres,series.portrait_image,series.landscape_image"} series.genres,series.portrait_image,series.landscape_image"}
req = api.request(args, "list_series", payload) req = api.request(args, "list_series", payload)
   
# check for error # check for error
if req["error"]: if req["error"]:
view.add_item(args, {"title": args._addon.getLocalizedString(30061)}) view.add_item(args, {"title": args._addon.getLocalizedString(30061)})
view.endofdirectory() view.endofdirectory()
return False return False
   
# display media # display media
for item in req["data"]: for item in req["data"]:
# add to view # add to view
view.add_item(args, view.add_item(args,
{"title": item["name"], {"title": item["name"],
"tvshowtitle": item["name"], "tvshowtitle": item["name"],
"series_id": item["series_id"], "series_id": item["series_id"],
"plot": item["description"], "plot": item["description"],
"plotoutline": item["description"], "plotoutline": item["description"],
"genre": ", ".join(item["genres"]), "genre": ", ".join(item["genres"]),
"year": item["year"], "year": item["year"],
"studio": item["publisher_name"], "studio": item["publisher_name"],
"thumb": item["portrait_image"]["full_url"], "thumb": item["portrait_image"]["full_url"],
"fanart": item["landscape_image"]["full_url"], "fanart": item["landscape_image"]["full_url"],
"mode": "series"}, "mode": "series"},
isFolder=True) isFolder=True)
   
# show next page button # show next page button
if len(req["data"]) >= 30: if len(req["data"]) >= 30:
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30044), {"title": args._addon.getLocalizedString(30044),
"offset": int(getattr(args, "offset", 0)) + 30, "offset": int(getattr(args, "offset", 0)) + 30,
  "search": getattr(args, "search", ""),
"mode": args.mode}, "mode": args.mode},
isFolder=True) isFolder=True)
   
view.endofdirectory() view.endofdirectory()
return True return True
   
   
def listFilter(args, mode): def listFilter(args, mode):
""" view all anime from selected mode """ view all anime from selected mode
""" """
# test if filter is selected # test if filter is selected
if hasattr(args, "search"): if hasattr(args, "search"):
return listSeries(args, "tag:" + args.search) return listSeries(args, "tag:" + args.search)
   
# api request # api request
payload = {"media_type": args.genre} payload = {"media_type": args.genre}
req = api.request(args, "categories", payload) req = api.request(args, "categories", payload)
   
# check for error # check for error
if req["error"]: if req["error"]:
view.add_item(args, {"title": args._addon.getLocalizedString(30061)}) view.add_item(args, {"title": args._addon.getLocalizedString(30061)})
view.endofdirectory() view.endofdirectory()
return False return False
   
# display media # display media
for item in req["data"][mode]: for item in req["data"][mode]:
# add to view # add to view
view.add_item(args, view.add_item(args,
{"title": item["label"], {"title": item["label"],
"search": item["tag"], "search": item["tag"],
"mode": args.mode}, "mode": args.mode},
isFolder=True) isFolder=True)
   
view.endofdirectory() view.endofdirectory()
return True return True
   
   
def viewSeries(args): def viewSeries(args):
""" view all seasons/arcs of an anime """ view all seasons/arcs of an anime
""" """
# api request # api request
payload = {"series_id": args.series_id, payload = {"series_id": args.series_id,
"fields": "collection.name,collection.collection_id,collection.description,collection.media_type,collection.created, \ "fields": "collection.name,collection.collection_id,collection.description,collection.media_type,collection.created, \
collection.season,collection.complete,collection.portrait_image,collection.landscape_image"} collection.season,collection.complete,collection.portrait_image,collection.landscape_image"}
req = api.request(args, "list_collections", payload) req = api.request(args, "list_collections", payload)
   
# check for error # check for error
if req["error"]: if req["error"]:
view.add_item(args, {"title": args._addon.getLocalizedString(30061)}) view.add_item(args, {"title": args._addon.getLocalizedString(30061)})
view.endofdirectory() view.endofdirectory()
return False return False
   
# display media # display media
for item in req["data"]: for item in req["data"]:
# add to view # add to view
view.add_item(args, view.add_item(args,
{"title": item["name"], {"title": item["name"],
"tvshowtitle": item["name"], "tvshowtitle": item["name"],
"season": item["season"], "season": item["season"],
"collection_id": item["collection_id"], "collection_id": item["collection_id"],
"series_id": args.series_id, "series_id": args.series_id,
"plot": item["description"], "plot": item["description"],
"plotoutline": item["description"], "plotoutline": item["description"],
"genre": item["media_type"], "genre": item["media_type"],
"aired": item["created"][:10], "aired": item["created"][:10],
"premiered": item["created"][:10], "premiered": item["created"][:10],
"status": u"Completed" if item["complete"] else u"Continuing", "status": u"Completed" if item["complete"] else u"Continuing",
"thumb": item["portrait_image"]["full_url"] if item["portrait_image"] else args.thumb, "thumb": item["portrait_image"]["full_url"] if item["portrait_image"] else args.thumb,
"fanart": item["landscape_image"]["full_url"] if item["landscape_image"] else args.fanart, "fanart": item["landscape_image"]["full_url"] if item["landscape_image"] else args.fanart,
"mode": "episodes"}, "mode": "episodes"},
isFolder=True) isFolder=True)
   
view.endofdirectory() view.endofdirectory()
return True return True
   
   
def viewEpisodes(args): def viewEpisodes(args):
""" view all episodes of season """ view all episodes of season
""" """
# api request # api request
payload = {"collection_id": args.collection_id, payload = {"collection_id": args.collection_id,
  "limit": 30,
  "offset": int(getattr(args, "offset", 0)),
"fields": "media.name,media.media_id,media.collection_id,media.collection_name,media.description,media.episode_number,media.created,media.series_id, \ "fields": "media.name,media.media_id,media.collection_id,media.collection_name,media.description,media.episode_number,media.created,media.series_id, \
media.screenshot_image,media.premium_only,media.premium_available,media.available,media.premium_available,media.duration,media.playhead"} media.screenshot_image,media.premium_only,media.premium_available,media.available,media.premium_available,media.duration,media.playhead"}
req = api.request(args, "list_media", payload) req = api.request(args, "list_media", payload)
   
# check for error # check for error
if req["error"]: if req["error"]:
view.add_item(args, {"title": args._addon.getLocalizedString(30061)}) view.add_item(args, {"title": args._addon.getLocalizedString(30061)})
view.endofdirectory() view.endofdirectory()
return False return False
   
# display media # display media
for item in req["data"]: for item in req["data"]:
# add to view # add to view
view.add_item(args, view.add_item(args,
{"title": item["collection_name"] + " #" + item["episode_number"] + " - " + item["name"], {"title": item["collection_name"] + " #" + item["episode_number"] + " - " + item["name"],
"tvshowtitle": item["collection_name"], "tvshowtitle": item["collection_name"],
"duration": item["duration"], "duration": item["duration"],
"playcount": 1 if (100/float(item["duration"]))*int(item["playhead"]) > 90 else 0, "playcount": 1 if (100/float(item["duration"]))*int(item["playhead"]) > 90 else 0,
"episode": item["episode_number"], "episode": item["episode_number"],
"episode_id": item["media_id"], "episode_id": item["media_id"],
"collection_id": args.collection_id, "collection_id": args.collection_id,
"series_id": item["series_id"], "series_id": item["series_id"],
"plot": item["description"], "plot": item["description"],
"plotoutline": item["description"], "plotoutline": item["description"],
"aired": item["created"][:10], "aired": item["created"][:10],
"premiered": item["created"][:10], "premiered": item["created"][:10],
"thumb": item["screenshot_image"]["fwidestar_url"] if item["premium_only"] else item["screenshot_image"]["full_url"], "thumb": item["screenshot_image"]["fwidestar_url"] if item["premium_only"] else item["screenshot_image"]["full_url"],
"fanart": args.fanart, "fanart": args.fanart,
"mode": "videoplay"}, "mode": "videoplay"},
isFolder=False) isFolder=False)
   
  # show next page button
  if len(req["data"]) >= 30:
  view.add_item(args,
  {"title": args._addon.getLocalizedString(30044),
  "collection_id": args.collection_id,
  "offset": int(getattr(args, "offset", 0)) + 30,
  "thumb": args.thumb,
  "fanart": args.fanart,
  "mode": args.mode},
  isFolder=True)
   
view.endofdirectory() view.endofdirectory()
return True return True
   
   
def startplayback(args): def startplayback(args):
""" plays an episode """ plays an episode
""" """
# api request # api request
payload = {"media_id": args.episode_id, payload = {"media_id": args.episode_id,
"fields": "media.duration,media.playhead,media.stream_data"} "fields": "media.duration,media.playhead,media.stream_data"}
req = api.request(args, "info", payload) req = api.request(args, "info", payload)
   
# check for error # check for error
if req["error"]: if req["error"]:
item = xbmcgui.ListItem(getattr(args, "title", "Title not provided")) item = xbmcgui.ListItem(getattr(args, "title", "Title not provided"))
xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, item) xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, item)
return False return False
   
# get stream url # get stream url
url = req["data"]["stream_data"]["streams"][0]["url"] url = req["data"]["stream_data"]["streams"][0]["url"]
for stream in req["data"]["stream_data"]["streams"]: for stream in req["data"]["stream_data"]["streams"]:
# TODO: get user selected quality # TODO: get user selected quality
url = stream["url"] url = stream["url"]
break break
   
# start playback # start playback
item = xbmcgui.ListItem(getattr(args, "title", "Title not provided"), path=url) item = xbmcgui.ListItem(getattr(args, "title", "Title not provided"), path=url)
item.setMimeType("application/vnd.apple.mpegurl") item.setMimeType("application/vnd.apple.mpegurl")
item.setContentLookup(False) item.setContentLookup(False)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, item) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, item)
   
if args._addon.getSetting("sync_playtime") == "true": if args._addon.getSetting("sync_playtime") == "true":
# wait for video to begin # wait for video to begin
player = xbmc.Player() player = xbmc.Player()
timeout = time.time() + 20 timeout = time.time() + 20
while not xbmc.getCondVisibility("Player.IsInternetStream"): while not xbmc.getCondVisibility("Player.IsInternetStream"):
xbmc.sleep(50) xbmc.sleep(50)
# timeout to prevent infinite loop # timeout to prevent infinite loop
if time.time() > timeout: if time.time() > timeout:
xbmc.log("[PLUGIN] %s: Timeout reached, video did not start in 20 seconds" % args._addonname, xbmc.LOGERROR) xbmc.log("[PLUGIN] %s: Timeout reached, video did not start in 20 seconds" % args._addonname, xbmc.LOGERROR)
return return
   
# ask if user want to continue playback # ask if user want to continue playback
resume = (100/float(req["data"]["duration"])) * int(req["data"]["playhead"]) resume = (100/float(req["data"]["duration"])) * int(req["data"]["playhead"])
if resume >= 5 and resume <= 90: if resume >= 5 and resume <= 90:
player.pause() player.pause()
if xbmcgui.Dialog().yesno(args._addonname, args._addon.getLocalizedString(30065) % resume): if xbmcgui.Dialog().yesno(args._addonname, args._addon.getLocalizedString(30065) % resume):
player.seekTime(int(req["data"]["playhead"]) - 5) player.seekTime(int(req["data"]["playhead"]) - 5)
player.pause() player.pause()
   
# update playtime at crunchyroll # update playtime at crunchyroll
try: try:
while url == player.getPlayingFile(): while url == player.getPlayingFile():
# wait 10 seconds # wait 10 seconds
xbmc.sleep(10000) xbmc.sleep(10000)
   
if url == player.getPlayingFile(): if url == player.getPlayingFile():
# api request # api request
payload = {"event": "playback_status", payload = {"event": "playback_status",
"media_id": args.episode_id, "media_id": args.episode_id,
"playhead": int(player.getTime())} "playhead": int(player.getTime())}
api.request(args, "log", payload) api.request(args, "log", payload)
except RuntimeError: except RuntimeError:
xbmc.log("[PLUGIN] %s: Playback aborted" % args._addonname, xbmc.LOGDEBUG) xbmc.log("[PLUGIN] %s: Playback aborted" % args._addonname, xbmc.LOGDEBUG)
   
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Crunchyroll # Crunchyroll
# Copyright (C) 2018 MrKrabat # Copyright (C) 2018 MrKrabat
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the # published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version. # License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details. # GNU Affero General Public License for more details.
# #
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
   
import sys import sys
import random import random
   
import xbmc import xbmc
import xbmcgui import xbmcgui
import xbmcplugin import xbmcplugin
   
from . import api from . import api
from . import view from . import view
from . import model from . import model
from . import controller from . import controller
   
   
def main(): def main():
"""Main function for the addon """Main function for the addon
""" """
args = model.parse() args = model.parse()
   
# get account informations # get account informations
username = args._addon.getSetting("crunchyroll_username") username = args._addon.getSetting("crunchyroll_username")
password = args._addon.getSetting("crunchyroll_password") password = args._addon.getSetting("crunchyroll_password")
args._session_id = args._addon.getSetting("session_id") args._session_id = args._addon.getSetting("session_id")
args._auth_token = args._addon.getSetting("auth_token") args._auth_token = args._addon.getSetting("auth_token")
args._device_id = args._addon.getSetting("device_id") args._device_id = args._addon.getSetting("device_id")
if not args._device_id: if not args._device_id:
char_set = "0123456789abcdefghijklmnopqrstuvwxyz0123456789" char_set = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
args._device_id = "".join(random.sample(char_set, 8)) + "-KODI-" + "".join(random.sample(char_set, 4)) + "-" + "".join(random.sample(char_set, 4)) + "-" + "".join(random.sample(char_set, 12)) args._device_id = "".join(random.sample(char_set, 8)) + "-KODI-" + "".join(random.sample(char_set, 4)) + "-" + "".join(random.sample(char_set, 4)) + "-" + "".join(random.sample(char_set, 12))
args._addon.setSetting("device_id", args._device_id) args._addon.setSetting("device_id", args._device_id)
   
# get subtitle language # get subtitle language
args._subtitle = args._addon.getSetting("subtitle_language") args._subtitle = args._addon.getSetting("subtitle_language")
if args._subtitle == "0": if args._subtitle == "0":
args._subtitle = "enUS" args._subtitle = "enUS"
elif args._subtitle == "1": elif args._subtitle == "1":
args._subtitle = "enGB" args._subtitle = "enGB"
elif args._subtitle == "2": elif args._subtitle == "2":
args._subtitle = "esLA" args._subtitle = "esLA"
elif args._subtitle == "3": elif args._subtitle == "3":
args._subtitle = "esES" args._subtitle = "esES"
elif args._subtitle == "4": elif args._subtitle == "4":
args._subtitle = "ptBR" args._subtitle = "ptBR"
elif args._subtitle == "5": elif args._subtitle == "5":
args._subtitle = "ptPT" args._subtitle = "ptPT"
elif args._subtitle == "6": elif args._subtitle == "6":
args._subtitle = "frFR" args._subtitle = "frFR"
elif args._subtitle == "7": elif args._subtitle == "7":
args._subtitle = "deDE" args._subtitle = "deDE"
elif args._subtitle == "8": elif args._subtitle == "8":
args._subtitle = "arME" args._subtitle = "arME"
elif args._subtitle == "9": elif args._subtitle == "9":
args._subtitle = "itIT" args._subtitle = "itIT"
elif args._subtitle == "10": elif args._subtitle == "10":
args._subtitle = "ruRU" args._subtitle = "ruRU"
else: else:
args._subtitle = "enUS" args._subtitle = "enUS"
   
# get video quality # get video quality
args._quality = args._addon.getSetting("video_quality") args._quality = args._addon.getSetting("video_quality")
if args._quality == "0": if args._quality == "0":
args._quality = "80" args._quality = "80"
elif args._quality == "1": elif args._quality == "1":
args._quality = "62" args._quality = "62"
elif args._quality == "2": elif args._quality == "2":
args._quality = "61" args._quality = "61"
elif args._quality == "3": elif args._quality == "3":
args._quality = "60" args._quality = "60"
else: else:
args._quality = "80" args._quality = "80"
   
if not (username and password): if not (username and password):
# open addon settings # open addon settings
view.add_item(args, {"title": args._addon.getLocalizedString(30062)}) view.add_item(args, {"title": args._addon.getLocalizedString(30062)})
view.endofdirectory() view.endofdirectory()
args._addon.openSettings() args._addon.openSettings()
return False return False
else: else:
# login # login
if api.start(args): if api.start(args):
# list menue # list menue
xbmcplugin.setContent(int(sys.argv[1]), "tvshows") xbmcplugin.setContent(int(sys.argv[1]), "tvshows")
check_mode(args) check_mode(args)
api.close(args) api.close(args)
else: else:
# login failed # login failed
xbmc.log("[PLUGIN] %s: Login failed" % args._addonname, xbmc.LOGERROR) xbmc.log("[PLUGIN] %s: Login failed" % args._addonname, xbmc.LOGERROR)
view.add_item(args, {"title": args._addon.getLocalizedString(30060)}) view.add_item(args, {"title": args._addon.getLocalizedString(30060)})
view.endofdirectory() view.endofdirectory()
xbmcgui.Dialog().ok(args._addonname, args._addon.getLocalizedString(30060)) xbmcgui.Dialog().ok(args._addonname, args._addon.getLocalizedString(30060))
return False return False
   
   
def check_mode(args): def check_mode(args):
"""Run mode-specific functions """Run mode-specific functions
""" """
if hasattr(args, "mode"): if hasattr(args, "mode"):
mode = args.mode mode = args.mode
elif hasattr(args, "id"): elif hasattr(args, "id"):
# call from other plugin # call from other plugin
mode = "videoplay" mode = "videoplay"
args.url = "/media-" + args.id args.url = "/media-" + args.id
elif hasattr(args, "url"): elif hasattr(args, "url"):
# call from other plugin # call from other plugin
mode = "videoplay" mode = "videoplay"
args.url = args.url[26:] args.url = args.url[26:]
else: else:
mode = None mode = None
   
if not mode: if not mode:
showMainMenue(args) showMainMenue(args)
   
elif mode == "queue": elif mode == "queue":
controller.showQueue(args) controller.showQueue(args)
elif mode == "search": elif mode == "search":
controller.searchAnime(args) controller.searchAnime(args)
elif mode == "history": elif mode == "history":
controller.showHistory(args) controller.showHistory(args)
elif mode == "random": elif mode == "random":
controller.showRandom(args) controller.showRandom(args)
   
elif mode == "anime": elif mode == "anime":
showMainCategory(args, "anime") showMainCategory(args, "anime")
elif mode == "drama": elif mode == "drama":
showMainCategory(args, "drama") showMainCategory(args, "drama")
   
elif mode == "featured": elif mode == "featured":
controller.listSeries(args, "featured") controller.listSeries(args, "featured")
elif mode == "popular": elif mode == "popular":
controller.listSeries(args, "popular") controller.listSeries(args, "popular")
elif mode == "simulcast": elif mode == "simulcast":
controller.listSeries(args, "simulcast") controller.listSeries(args, "simulcast")
elif mode == "updated": elif mode == "updated":
controller.listSeries(args, "updated") controller.listSeries(args, "updated")
elif mode == "newest": elif mode == "newest":
controller.listSeries(args, "newest") controller.listSeries(args, "newest")
elif mode == "alpha": elif mode == "alpha":
controller.listSeries(args, "alpha") controller.listSeries(args, "alpha")
elif mode == "season": elif mode == "season":
controller.listFilter(args, "season") controller.listFilter(args, "season")
elif mode == "genre": elif mode == "genre":
controller.listFilter(args, "genre") controller.listFilter(args, "genre")
   
elif mode == "series": elif mode == "series":
controller.viewSeries(args) controller.viewSeries(args)
elif mode == "episodes": elif mode == "episodes":
controller.viewEpisodes(args) controller.viewEpisodes(args)
elif mode == "videoplay": elif mode == "videoplay":
controller.startplayback(args) controller.startplayback(args)
else: else:
# unkown mode # unkown mode
xbmc.log("[PLUGIN] %s: Failed in check_mode '%s'" % (args._addonname, str(mode)), xbmc.LOGERROR) xbmc.log("[PLUGIN] %s: Failed in check_mode '%s'" % (args._addonname, str(mode)), xbmc.LOGERROR)
xbmcgui.Dialog().notification(args._addonname, args._addon.getLocalizedString(30061), xbmcgui.NOTIFICATION_ERROR) xbmcgui.Dialog().notification(args._addonname, args._addon.getLocalizedString(30061), xbmcgui.NOTIFICATION_ERROR)
showMainMenue(args) showMainMenue(args)
   
   
def showMainMenue(args): def showMainMenue(args):
"""Show main menu """Show main menu
""" """
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30040), {"title": args._addon.getLocalizedString(30040),
"mode": "queue"}) "mode": "queue"})
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30041), {"title": args._addon.getLocalizedString(30041),
"mode": "search"}) "mode": "search"})
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30042), {"title": args._addon.getLocalizedString(30042),
"mode": "history"}) "mode": "history"})
view.add_item(args, #view.add_item(args,
{"title": args._addon.getLocalizedString(30043), # {"title": args._addon.getLocalizedString(30043),
"mode": "random"}) # "mode": "random"})
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30050), {"title": args._addon.getLocalizedString(30050),
"mode": "anime"}) "mode": "anime"})
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30051), {"title": args._addon.getLocalizedString(30051),
"mode": "drama"}) "mode": "drama"})
view.endofdirectory() view.endofdirectory()
   
   
def showMainCategory(args, genre): def showMainCategory(args, genre):
"""Show main category """Show main category
""" """
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30058), {"title": args._addon.getLocalizedString(30058),
"mode": "featured", "mode": "featured",
"genre": genre}) "genre": genre})
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30052), {"title": args._addon.getLocalizedString(30052),
"mode": "popular", "mode": "popular",
"genre": genre}) "genre": genre})
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30053), {"title": args._addon.getLocalizedString(30053),
"mode": "simulcast", "mode": "simulcast",
"genre": genre}) "genre": genre})
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30054), {"title": args._addon.getLocalizedString(30054),
"mode": "updated", "mode": "updated",
"genre": genre}) "genre": genre})
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30059), {"title": args._addon.getLocalizedString(30059),
"mode": "newest", "mode": "newest",
"genre": genre}) "genre": genre})
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30055), {"title": args._addon.getLocalizedString(30055),
"mode": "alpha", "mode": "alpha",
"genre": genre}) "genre": genre})
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30057), {"title": args._addon.getLocalizedString(30057),
"mode": "season", "mode": "season",
"genre": genre}) "genre": genre})
view.add_item(args, view.add_item(args,
{"title": args._addon.getLocalizedString(30056), {"title": args._addon.getLocalizedString(30056),
"mode": "genre", "mode": "genre",
"genre": genre}) "genre": genre})
view.endofdirectory() view.endofdirectory()