652 lines
25 KiB
Python
652 lines
25 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Module: BRAVIA-CORE
|
|
# Created on: 01-12-2021
|
|
# Authors: -∞WKS∞-
|
|
# Version: 1.0
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import hashlib
|
|
import json
|
|
import sys
|
|
from enum import Enum
|
|
from typing import Any, Optional, Union
|
|
|
|
import click
|
|
import jsonpickle
|
|
import requests
|
|
import braviacoreConfig
|
|
from click import Context
|
|
|
|
|
|
class BraviaCORE(BaseService):
|
|
"""
|
|
Service code for Sony's Bravia CORE streaming service (https://electronics.sony.com/bravia-core).
|
|
|
|
\b
|
|
Authorization: Credentials
|
|
Security: UHD@L3 HD@L3
|
|
|
|
\b
|
|
Tip: It's currently using unintentionally open internal API endpoints, use while you can!
|
|
"""
|
|
|
|
ALIASES = ["CORE", "braviacore"]
|
|
|
|
@staticmethod
|
|
@click.command(name="BraviaCORE", short_help="https://electronics.sony.com/bravia-core")
|
|
@click.argument("title", type=str)
|
|
@click.option("-x", "--internal", is_flag=True, default=False,
|
|
help="Use the weird unintentionally open API endpoint with unrestricted title access.")
|
|
@click.option("-vp", "--vprofile", default=None,
|
|
type=click.Choice(["h264", "sdr", "hdr", "imax"], case_sensitive=False),
|
|
help="Video Profile. Default will be highest quality/best compression.")
|
|
@click.pass_context
|
|
def cli(ctx: Context, **kwargs: Any) -> BraviaCORE:
|
|
return BraviaCORE(ctx, **kwargs)
|
|
|
|
def __init__(self, ctx: Context, title: str, internal: bool, vprofile: Optional[str]):
|
|
self.title = int(title) if title != "list" else title
|
|
self.internal = internal
|
|
self.vprofile = vprofile
|
|
super().__init__(ctx)
|
|
|
|
self.session_id: str
|
|
self.credits: int
|
|
|
|
self.configure()
|
|
|
|
def get_titles(self) -> Union[Title, list[Title]]:
|
|
if self.title == "list":
|
|
if self.internal:
|
|
pages = self.get_cache("manifest_internal")
|
|
if not pages.is_dir() or len(list(pages.iterdir())) == 0:
|
|
self.log.exit(" - Endpoint was patched. Can only search cached pages, which you have none of.")
|
|
raise
|
|
samples = [
|
|
sample
|
|
for page in pages.iterdir()
|
|
for sample in jsonpickle.decode(page.read_text("utf8"))
|
|
]
|
|
samples = sorted(samples, key=lambda s: int(s["ppId"]))
|
|
for sample in samples:
|
|
self.log.info(
|
|
"{} | {} [{}] [{}]".format(
|
|
sample["ppId"],
|
|
sample["parent_product_name"],
|
|
sample["alpha"],
|
|
sample["quality"]
|
|
)
|
|
)
|
|
else:
|
|
self.list_playlist(self.config["playlists"]["unlimited"], "Unlimited Streaming")
|
|
self.list_playlist(self.config["playlists"]["library"], "Library")
|
|
sys.exit(0)
|
|
|
|
res = self.session.get(
|
|
url=f"https://service.privilegemovies.com/content/v6/metadata/{self.title}",
|
|
params={"width": "300"}
|
|
)
|
|
title = res.json()
|
|
if title["responseCode"] >= 19999:
|
|
raise ValueError(
|
|
f"Could not get metadata for {self.title}. "
|
|
f"Error: {repr(ResponseCode(title['responseCode']))}. "
|
|
f"URL: {res.request.url}"
|
|
)
|
|
|
|
title["id"] = self.title
|
|
|
|
search = next(filter(lambda x: x["parentProductId"] == title["id"], self.search(title["title"])), None)
|
|
if not search:
|
|
self.log.exit(f"Could not get search result for {self.title}.")
|
|
raise
|
|
title["transactionTypes"] = sorted(search["transactionTypes"])
|
|
|
|
return Title(
|
|
id_=title["id"],
|
|
type_=Title.Types.MOVIE,
|
|
name=title["title"],
|
|
year=title["year"],
|
|
original_lang=title["language"],
|
|
source=self.ALIASES[0],
|
|
service_data=title
|
|
)
|
|
|
|
def get_tracks(self, title: Title) -> Tracks:
|
|
profiles = sorted(
|
|
[x.lower() for x in title.service_data['availableProfiles']],
|
|
key=["imax", "hdr", "sdr", "h264"].index
|
|
)
|
|
profile = None
|
|
if self.vprofile and self.vprofile in profiles:
|
|
profile = self.vprofile.lower()
|
|
elif profiles:
|
|
profile = profiles[0]
|
|
self.log.debug(f"Available Profiles: {profiles}")
|
|
|
|
if self.internal:
|
|
res = self.search_manifest_internal(pp_id=title.service_data["id"])
|
|
# disabled for now until true full format is discovered, specifically "s" (signature).
|
|
# res["uri"] = self.prepare_manifest_url(res["uri"], res["movieId"])
|
|
else:
|
|
res = self.get_video(
|
|
title.service_data["id"],
|
|
transaction_type=title.service_data["transactionTypes"][-1],
|
|
profile=profile
|
|
)
|
|
|
|
tracks = Tracks.from_mpds(
|
|
data=requests.get(res["uri"]).text,
|
|
url=res["uri"].replace("service.privilegemovies.com/mg/drm", "cf.privilegemovies.com/drm"),
|
|
lang=title.original_lang,
|
|
source=self.ALIASES[0]
|
|
)
|
|
|
|
for sub in res.get("subtitles") or []:
|
|
if sub["extension"] == "vtt":
|
|
continue # SRT should be available for the exact same sub
|
|
if sub["languageCode"].lower() == "pp":
|
|
sub["languageCode"] = "pt-BR"
|
|
if sub["languageCode"].lower() == "cn":
|
|
sub["languageCode"] = "zh-Hant"
|
|
if sub["languageCode"].lower() == "zh":
|
|
sub["languageCode"] = "zh-Hans"
|
|
if self.session.head(sub["subtitleUrl"]).status_code == 404:
|
|
self.log.warning(f" - Subtitle returned 404, skipping: {sub['subtitleUrl']}")
|
|
continue
|
|
tracks.add(TextTrack(
|
|
id_="{}_{}_{}_sub".format(
|
|
self.title,
|
|
sub["languageCode"],
|
|
hashlib.md5(sub["subtitleUrl"].encode()).hexdigest()[0:6]
|
|
),
|
|
source=self.ALIASES[0],
|
|
url=sub["subtitleUrl"],
|
|
# metadata
|
|
codec=sub["extension"],
|
|
language=sub["languageCode"],
|
|
is_original_lang=title.original_lang and is_close_match(sub["languageCode"], [title.original_lang]),
|
|
forced=sub["forced"],
|
|
sdh="_CC_" in sub["subtitleUrl"]
|
|
))
|
|
|
|
for track in tracks:
|
|
if not track.language and title.original_lang:
|
|
track.language = title.original_lang
|
|
if isinstance(track, VideoTrack):
|
|
track.hdr10 = profile in ("hdr", "imax") # TODO: What about DV? Could it be DV?
|
|
track.extra = {"license_url": res["widevineLicenseServer"]}
|
|
|
|
return tracks
|
|
|
|
def get_chapters(self, title: Title) -> list[MenuTrack]:
|
|
return []
|
|
|
|
def certificate(self, **kwargs: Any) -> bytes:
|
|
# TODO: Hardcode the certificate
|
|
return self.license(**kwargs)
|
|
|
|
def license(self, challenge: bytes, track: Track, **_: Any) -> bytes:
|
|
for n in range(5):
|
|
# even the official APK seems to need to retry at least twice
|
|
res = self.session.post(
|
|
url=track.extra["license_url"],
|
|
data=challenge, # expects bytes
|
|
# TODO: Need session ID? headers={"Session": self.session_id}
|
|
).content
|
|
if res and res != b"Unauthorized":
|
|
print(res)
|
|
return res
|
|
self.log.exit(" - License api call failed, unable to get certificate or license.")
|
|
raise
|
|
|
|
# Service specific functions
|
|
|
|
def configure(self) -> None:
|
|
self.session.headers.update({
|
|
"ApiKey": self.config["api_key"],
|
|
"AppLanguage": "EN"
|
|
})
|
|
self.session_id = self.login()
|
|
self.credits = self.get_available_credits()
|
|
self.log.info(f" - Credits available: {self.credits}.")
|
|
|
|
def login(self) -> str:
|
|
"""
|
|
Log in to BraviaCORE and return a Session ID.
|
|
:returns: Session ID.
|
|
"""
|
|
if not self.credentials:
|
|
self.log.exit(" - No credentials provided, unable to log in.")
|
|
raise
|
|
res = self.session.post(
|
|
url=self.config["endpoints"]["login"],
|
|
json={
|
|
"deviceIdentifier": self.config["device_id"],
|
|
"deviceModel": self.config["device_model"],
|
|
"softwareVersion": self.config["software_version"],
|
|
"email": self.credentials.username,
|
|
"password": self.credentials.password,
|
|
}
|
|
)
|
|
try:
|
|
data = res.json()
|
|
except json.JSONDecodeError:
|
|
self.log.exit(f" - Failed to get Session ID, response was not JSON: {res.text}")
|
|
raise
|
|
if data["responseCode"] >= 19999:
|
|
self.log.exit(f" - Failed to log in. Error: {repr(ResponseCode(data['responseCode']))}.")
|
|
raise
|
|
return data["session"]
|
|
|
|
def get_available_credits(self) -> int:
|
|
"""Get the amount of available credits in the account."""
|
|
if not self.session_id:
|
|
self.log.exit(" - Cannot get available credits, you must log in first")
|
|
raise
|
|
res = self.session.get(
|
|
url=self.config["endpoints"]["credits"],
|
|
headers={"Session": self.session_id}
|
|
)
|
|
try:
|
|
data = res.json()
|
|
except json.JSONDecodeError:
|
|
self.log.debug(res.text)
|
|
self.log.exit(" - Failed to get available credits, response was not JSON")
|
|
raise
|
|
return data["creditsAvailable"]
|
|
|
|
def redeem(self, pp_id: int) -> None:
|
|
"""Redeem title by Parent Product ID using available credits."""
|
|
if not self.session_id:
|
|
self.log.exit(f" - Cannot redeem title {pp_id}, you must log in first")
|
|
raise
|
|
definition = "-6" # TODO: what's the -6? api refers to it as a "definition", seen -2 in v1.1.0 apk
|
|
res = self.session.post(
|
|
url=self.config["endpoints"]["redeem"],
|
|
json={"parentProductIds": f"{pp_id}{definition}"}, # can be multiple values separated by ','.
|
|
headers={"Session": self.session_id}
|
|
)
|
|
res_code = ResponseCode(res.json()["productResponseCodes"][0]["responseCode"])
|
|
if res_code not in [ResponseCode.SUCCESS_REDEEMED, ResponseCode.SUCCESS_ALREAD_REDEEMED]:
|
|
self.log.exit(f" - Failed to redeem title {pp_id}{definition}. Error: {repr(res_code)}")
|
|
raise
|
|
self.log.info(f" - Redeemed title {pp_id}{definition} [{repr(res_code)}]")
|
|
|
|
def get_video(self, pp_id: int, profile: Optional[str] = None, quality: int = 3000, sub_type: str = "srt",
|
|
is_3d: bool = False, is_4k: bool = True, stream_type: int = 2, transaction_type: int = 1,
|
|
restrictions_enabled: bool = True) -> dict:
|
|
"""
|
|
Get Video Manifest Information.
|
|
|
|
Parameters:
|
|
pp_id: Parent Product ID.
|
|
profile: Profile. imax, hdr, h264, None (best?)
|
|
quality: Quality. 3000, 2160, 1080
|
|
sub_type: Subtitle Format. vtt, srt
|
|
is_3d: Request 3D Video.
|
|
is_4k: Request UHD Video.
|
|
stream_type: ? 2 Seems to be hardcoded.
|
|
transaction_type: ? 1 is typical default.
|
|
restrictions_enabled: ? True Seems to be hardcoded.
|
|
"""
|
|
res = self.session.get(
|
|
url=f"https://service.privilegemovies.com/content/v6/video/{pp_id}",
|
|
params={
|
|
"profile": profile,
|
|
"quality": quality,
|
|
"subType": sub_type,
|
|
"is3D": is_3d,
|
|
"is4K": is_4k,
|
|
"streamType": stream_type,
|
|
"transactionType": transaction_type,
|
|
"restrictionsEnabled": restrictions_enabled
|
|
},
|
|
headers={"Session": self.session_id}
|
|
)
|
|
res.raise_for_status()
|
|
video = res.json()
|
|
if video["responseCode"] > 19999:
|
|
raise ValueError(
|
|
f"Could not get manifest for {pp_id}. "
|
|
f"Error: {repr(ResponseCode(video['responseCode']))}. "
|
|
f"URL: {res.request.url}"
|
|
)
|
|
return dict(
|
|
alpha=video["alpha"],
|
|
audioLanguages=video["audioLanguages"].split(","),
|
|
downloadable=video["downloadable"],
|
|
expiryDate=video["linkExpiry"],
|
|
fairPlayLicenseServer=video["fairPlayLicenseServer"],
|
|
playReadyLicenseServer=video["playReadyLicenseServer"],
|
|
widevineLicenseServer=video["widevineLicenseServer"],
|
|
movieId=video["movieId"],
|
|
trackingId=video["trackingId"],
|
|
tracks=video["productTracks"],
|
|
uri=next((x["url"] for x in video["productTracks"] if x["fileType"] in (20, 11)), None)
|
|
)
|
|
|
|
def search_manifest_internal(self, pp_id: int, movie_id: Optional[int] = None) -> dict:
|
|
"""
|
|
Gets all manifest pages from the internal endpoint that was briefly open and returns only wanted title.
|
|
Since it was closed/fixed, only the cached pages are searchable.
|
|
It intentionally gets all manifests before checking for a match to have a safe sorted() check.
|
|
"""
|
|
samples = []
|
|
pages = self.get_cache("manifest_internal")
|
|
if pages.is_dir():
|
|
samples = [
|
|
sample
|
|
for page in pages.iterdir()
|
|
for sample in jsonpickle.decode(page.read_text("utf8"))
|
|
if sample["ppId"] == pp_id
|
|
]
|
|
if samples:
|
|
alphas = list(set(x["alpha"].upper() for x in samples))
|
|
if len(alphas) > 1:
|
|
print("Alpha List:")
|
|
for i, a in enumerate(alphas):
|
|
sub_count = sum(len(x.get('subtitles') or []) for x in samples if x['alpha'].upper() == a)
|
|
print(f"{i + 1:02}: {a} (Has up to {sub_count} Subtitles)")
|
|
alpha = input("Which alpha (version) do you wish to get? (#): ")
|
|
alpha = alphas[int(alpha or 1) - 1]
|
|
else:
|
|
alpha = alphas[0]
|
|
samples = [x for x in samples if x["alpha"].upper() == alpha.upper()]
|
|
samples = sorted(samples, key=lambda t: int(t["job_number"] or 0))
|
|
samples = sorted(samples, key=lambda t: "SDR" in t["quality"])
|
|
samples = sorted(samples, key=lambda t: "HDR" in t["quality"])
|
|
samples = sorted(samples, key=lambda t: "4K" in t["quality"])
|
|
samples = sorted(samples, key=lambda t: "IMAX" in t["quality"])
|
|
if movie_id:
|
|
samples = sorted(samples, key=lambda t: int(t["movieId"]) == movie_id)
|
|
chosen = samples[-1]
|
|
if not chosen.get("subtitles"):
|
|
chosen["subtitles"] = []
|
|
for sample in samples:
|
|
if sample["alpha"] != chosen["alpha"]:
|
|
continue
|
|
for subtitle in (sample.get("subtitles") or []):
|
|
subtitle_data = "".join(reversed(subtitle["subtitleUrl"])).split("_", 1)[-1]
|
|
if not any([
|
|
"".join(reversed(x["subtitleUrl"])).split("_", 1)[-1] == subtitle_data
|
|
for x in chosen["subtitles"]
|
|
]):
|
|
chosen["subtitles"].append(subtitle)
|
|
chosen["widevineLicenseServer"] = chosen["drm_license_url"]
|
|
return chosen
|
|
self.log.exit(" - Title was not found in the internal endpoint, possibly in broken pages :/")
|
|
raise
|
|
|
|
def get_playlist(self, playlist: str) -> list[Title]:
|
|
titles = []
|
|
page = 0
|
|
while True:
|
|
page += 1
|
|
res = self.session.get(
|
|
url=f"https://service.privilegemovies.com/content/v6/playlist/{playlist}/content",
|
|
params={
|
|
"kids": "false",
|
|
"width": "300",
|
|
"PageSize": "48",
|
|
"PageNumber": str(page)
|
|
}
|
|
).json()
|
|
res = res["products"]
|
|
titles.extend([Title(
|
|
id_=x["parentProductId"],
|
|
type_=Title.Types.MOVIE if x["contentType"] == 1 else Title.Types.TV,
|
|
name=x["title"],
|
|
year=x["year"],
|
|
season=x.get("season"),
|
|
episode=x.get("episode"),
|
|
episode_name=None, # TODO: Implement episode_name
|
|
original_lang=x["language"],
|
|
source=self.ALIASES[0],
|
|
service_data=x
|
|
) for x in res])
|
|
if len(res) < 48:
|
|
break
|
|
return titles
|
|
|
|
def list_playlist(self, playlist: str, name: str) -> None:
|
|
titles = self.get_playlist(playlist)
|
|
self.log.info(f" > {name} ({len(titles)}):")
|
|
for title in titles:
|
|
self.log.info(
|
|
"{} | {} ({}) [{}]".format(
|
|
title.id,
|
|
title.name,
|
|
title.year or "???",
|
|
",".join(map(str, title.service_data["transactionTypes"]))
|
|
)
|
|
)
|
|
|
|
def search(self, query: str) -> list[dict]:
|
|
res = self.session.get(
|
|
url=f"https://service.privilegemovies.com/content/v6/search/{query}",
|
|
params={
|
|
"kids": "false",
|
|
"width": "0"
|
|
}
|
|
).json()
|
|
return res["results"]
|
|
|
|
@staticmethod
|
|
def prepare_manifest_url(url: str, movie_id: int) -> str:
|
|
if "cf.privilegemovies.com/drm" in url:
|
|
mr = base64.b64encode(json.dumps({
|
|
"v": "7", # version
|
|
"m": movie_id, # movie id, title.service_data["parentId"] maybe?
|
|
"u": url, # original uri
|
|
"minB": "0", # min bitrate
|
|
"e": "Production", # environment
|
|
"maxB": "2147483647", # max bitrate
|
|
"mvas": "false", # ?
|
|
"al": ["EN", "en-US", "ENG", "UKE", "UKH", "ENH", "ENA", "en-EN"], # audio languages, what purpose?
|
|
"up": "2021-04-19T16:03:10.373", # when the file was uploaded
|
|
"o": "cf", # output, CDN maybe?
|
|
"f": base64.b64encode("-".join([
|
|
# string format of above?
|
|
str(movie_id),
|
|
"manifest.mpd",
|
|
"0-2147483647",
|
|
"False",
|
|
"cf",
|
|
"637544449903730000",
|
|
"Production",
|
|
"7",
|
|
"EN-en-US-ENG-UKE-UKH-ENH-ENA-en-EN"
|
|
]).encode()).decode() + ".mpd",
|
|
"s": "CRhH/PTdzH6zzowZu2k3jnRh7zw=" # hmac signature of f?
|
|
}).encode()).decode()
|
|
url = url.replace("cf.privilegemovies.com/drm", "service.privilegemovies.com/mg/drm")
|
|
url += f"?mr={mr}"
|
|
return url
|
|
|
|
|
|
class ResponseCode(Enum):
|
|
ACCEPTANCE_REQUIRED = 40070
|
|
ACCOUNT_EXISTS = 40016
|
|
AGE_NOT_CHECKED = 40015
|
|
AUTO_REDEMPTION_UNAVAILABLE = 40027
|
|
CANNOT_SET_EMPTY_WEBHOOK_URL = 40115
|
|
CANT_DELETE_LAST_CONSUMER_PROFILE = 40092
|
|
CANT_EXPIRE_LAST_PROFILE = 40108
|
|
CANT_MAKE_LAST_PROFILE_KIDS = 40107
|
|
CATEGORY_DEFINITION_ALREADY_EXISTS = 40135
|
|
CATEGORY_DEFINITION_NOT_FOUND = 40134
|
|
CHILD_PRODUCT_NOT_ACTIVE = 40031
|
|
CODE_GENERATION_ERROR = 20006
|
|
CONCURRENT_STREAM_LIMIT_REACHED = 40079
|
|
CONSUMER_BLACK_LISTED = 40047
|
|
CONSUMER_DEVICE_NOT_AUTHENTICATED = 40082
|
|
CONSUMER_NOT_FOUND = 40103
|
|
CONTENT_NOT_RENTED = 40128
|
|
CREDIT_BUNDLE_NOT_FOUND = 40111
|
|
CREDIT_BUNDLE_PRICE_NOT_FOUND = 40113
|
|
DECLINED_PRIVACY_POLICY = 40013
|
|
DECLINED_TERMS_AND_CONDITIONS = 40014
|
|
DEFINITION_REQUIRED = 40065
|
|
DELIVERY_TYPE_NOT_ALLOWED = 40028
|
|
DEVICE_ACTIVATED_TOO_SOON = 40083
|
|
DEVICE_BLACK_LISTED = 40049
|
|
DEVICE_ID_REQUIRED = 40012
|
|
DEVICE_LIMIT_REACHED = 40081
|
|
DEVICE_MEMBERSHIP_NOT_FOUND = 20020
|
|
DEVICE_MODEL_NOT_FOUND = 40140
|
|
DEVICE_MODEL_REQUIRED = 40022
|
|
DEVICE_NO_LONGER_ACTIVE = 40045
|
|
DOWNLOAD_LIMIT_EXCEEDED = 40032
|
|
DOWNLOAD_UNAVAILABLE = 40037
|
|
EMAIL_ALREADY_IN_USE = 40102
|
|
EMAIL_BLACK_LISTED = 40048
|
|
FACEBOOK_LOGIN_DISABLED = 30002
|
|
FACEBOOK_LOGIN_REQUIRED = 40072
|
|
FAILED_TO_BLOCK = 40094
|
|
FAILED_TO_DELETE_BLOCKED = 40095
|
|
FAILED_TO_REDEEM_PRODUCT = 40023
|
|
FAILED_TO_REDEEM_PRODUCT_DEFINITION = 40059
|
|
FAILED_TO_REDEEM_VOUCHER = 20013
|
|
FAILED_TO_REGISTER_DEVICE = 20010
|
|
FAILED_TO_SEND_EMAIL = 20012
|
|
FAILED_TO_UPDATE_DOWNLOAD_STATE = 20011
|
|
FORCED_REDEMPTION_DOES_NOT_EXIST = 40064
|
|
GCM_FAILED_TO_UPDATE_SERVICE = 40074
|
|
GCM_INVALID_INSTANCE_ID = 40073
|
|
GENERIC_NETWORK_ERROR = -1
|
|
INCORRECT_PAYMENT_STATE = 40124
|
|
INSUFFICIENT_PERMISSIONS = 40101
|
|
INVALID_ACCEPTANCE_FORMAT = 40071
|
|
INVALID_ACCESS_TOKEN = 40090
|
|
INVALID_API_KEY = 30001
|
|
INVALID_CHARACTERS_DETECTED = 40093
|
|
INVALID_CONCURRENT_STREAM_EVENT = 40091
|
|
INVALID_CONSUMER_DEVICE = 40080
|
|
INVALID_CONSUMER_PROFILE = 40089
|
|
INVALID_CONTENT_SELECTION_TYPE = 40053
|
|
INVALID_COUNTRY_CODE = 40010
|
|
INVALID_DOWNLOAD_REQUEST_CODE = 40006
|
|
INVALID_EMAIL_FORMAT = 40008
|
|
INVALID_FACEBOOK_TOKEN = 40051
|
|
INVALID_IP_COUNTRY = 40038
|
|
INVALID_IP_FORMAT = 40084
|
|
INVALID_LICENSE_REQUEST_CODE = 40007
|
|
INVALID_NONCE = 40000
|
|
INVALID_PASSWORD = 40003
|
|
INVALID_PASSWORD_FORMAT = 40009
|
|
INVALID_PIN = 40096
|
|
INVALID_PIN_FORMAT = 40097
|
|
INVALID_PLAYSTATION_AUTH_CODE = 40139
|
|
INVALID_PURCHASE_OPTION = 40109
|
|
INVALID_PUSH_NOTIFICATION_DEVICE = 40086
|
|
INVALID_QUALITY = 40060
|
|
INVALID_REDEEM_DEVICE = 40138
|
|
INVALID_REDEMPTION = 40026
|
|
INVALID_SESSION_ID = 40001
|
|
INVALID_SOFTWARE_VERSION = 40046
|
|
INVALID_SPDID = 40041
|
|
INVALID_TEMPORARY_PASSWORD = 40002
|
|
INVALID_URL_PROVIDED = 40117
|
|
INVALID_USERNAME = 40137
|
|
INVALID_USERNAME_OR_PASSWORD = 40005
|
|
INVALID_VOUCHER_CODE = 40004
|
|
IP_BLACK_LISTED = 40050
|
|
MOVIE_CREDIT_REDEMPTION_UNAVAILABLE = 40036
|
|
MOVIE_NOT_FOUND = 40119
|
|
MOVIE_TRACK_NOT_FOUND = 40118
|
|
NOT_ENOUGH_CREDITS = 40021
|
|
NOT_PRIMARY_DEVICE = 40033
|
|
NO_CONSUMER_PREFERENCE_FOUND = 40078
|
|
NO_CONTENT_FOUND = 40025
|
|
NO_CONTENT_PATH = 40039
|
|
NO_EMAIL_ADDRESS_RETRIEVED_FROM_FACEBOOK = 40069
|
|
NO_MOVIES_OF_THE_MONTH_DEFINED = 40133
|
|
NO_PIN_SET = 40098
|
|
NO_STATIC_BANNER_FOUND = 40077
|
|
OUT_DATED_SOFTWARE_VERSION = 40062
|
|
PARENT_PRODUCT_DEFINITION_NOT_REDEEMED = 40061
|
|
PARENT_PRODUCT_DEFINITION_NOT_RENTED = 40131
|
|
PARENT_PRODUCT_DOES_NOT_EXIST = 40058
|
|
PARENT_PRODUCT_EXISTS_IN_PLAYLIST = 40106
|
|
PARENT_PRODUCT_IDS_MISSING = 40063
|
|
PARENT_PRODUCT_ID_REQUIRED = 40068
|
|
PARENT_PRODUCT_NOT_ACTIVE = 40056
|
|
PARENT_PRODUCT_NOT_FOUND = 40110
|
|
PARENT_PRODUCT_NOT_FOUND_IN_PLAYLIST = 40105
|
|
PARENT_PRODUCT_NOT_REDEEMED = 40029
|
|
PARENT_PRODUCT_UNAVAILABLE = 40044
|
|
PASSWORDS_DO_NOT_MATCH = 40024
|
|
PLAYLIST_CUSTOM_LIST_TYPE_NOT_SET = 40132
|
|
PLAYLIST_NOT_FOUND = 40088
|
|
PRODUCT_UNKNOWN_ERROR = 20007
|
|
PROFILE_EXPIRED = 40099
|
|
PROFILE_NAME_EXISTS = 40104
|
|
PROMOTION_UNAVAILABLE = 40035
|
|
PROMO_STATE_NOT_FOUND = 20018
|
|
PURCHASE_LOCATION_REQUIRED = 40030
|
|
REGISTRATION_FAILED = 20009
|
|
RENTAL_PERIOD_ALREADY_EXISTS = 40126
|
|
RENTAL_PERIOD_NOT_FOUND = 40125
|
|
RENTING_DEVICE_LIMIT_REACHED = 40130
|
|
RENTING_NOT_SUPPORTED_BY_WHITE_LABEL_CAMPAIGN = 40129
|
|
REQUIREMENTS_NOT_FOUND = 40085
|
|
SECONDARY_EMAIL_EXISTS = 40100
|
|
SERIES_NOT_FOUND = 40136
|
|
SERVER_ERROR = 20000
|
|
SESSION_ERROR = 20008
|
|
SESSION_EXPIRED = 40011
|
|
SOFTWARE_VERSION_NOT_FOUND = 20019
|
|
SPDID_EXPIRED = 40042
|
|
SPDID_NO_SETUP = 20015
|
|
SPDID_RAW_DEVICE_INVALID = 40043
|
|
SPDID_REQUIRED = 40040
|
|
SUBSCRIPTION_ALREADY_CANCELLED = 40127
|
|
SUBSCRIPTION_INVOICE_NOT_FOUND = 40123
|
|
SUBSCRIPTION_NOT_FOUND = 40122
|
|
SUBSCRIPTION_PLAN_NOT_FOUND = 40121
|
|
SUBTITLE_NOT_FOUND = 40120
|
|
SUCCESS = 10000
|
|
SUCCESS_ACCEPTANCE_REQUIRED = 10008
|
|
SUCCESS_ALREAD_REDEEMED = 10002
|
|
SUCCESS_END = 19999
|
|
SUCCESS_FAILED_EMAIL = 10005
|
|
SUCCESS_FAILED_REDEEMED = 10007
|
|
SUCCESS_INVALID_LANGUAGE = 10001
|
|
SUCCESS_INVALID_VOUCHER = 10004
|
|
SUCCESS_OK_ALREADY_REDEEMED_VOUCHER_CODE = 10011
|
|
SUCCESS_OK_ALREADY_RENTING = 10013
|
|
SUCCESS_OK_INVALID_NOTIFICATION_MESSAGE = 10010
|
|
SUCCESS_OK_SOME_FAILED = 10012
|
|
SUCCESS_REDEEMED = 10006
|
|
SUCCESS_TEMPORARY_PASSWORD_USED = 10003
|
|
TEAM_NOT_FOUND = 40114
|
|
THIRD_PARTY_INACTIVE = 30000
|
|
TIER_PRICE_NOT_FOUND = 40112
|
|
UNKNOWN_CAMPAIN_GROUP_ERROR = 20002
|
|
UNKNOWN_CONSUMER_ERROR = 20004
|
|
UNKNOWN_DEVICE_ERROR = 20003
|
|
UNKNOWN_SESSION_ERROR = 20005
|
|
UNKNOWN_SESSION_ERROR_2 = 20014
|
|
UNKNOWN_THEME_ERROR = 20016
|
|
UNKNOWN_WHITE_LABEL_CAMPAIGN_ERROR = 20017
|
|
UNKNOWN_WHITE_LABEL_ERROR = 20001
|
|
VIDEO_UNLIMITED_NOT_SUPPORTED = 30003
|
|
VOUCHER_BUNDLE_NOT_ACTIVE = 40057
|
|
VOUCHER_BUNDLE_NOT_STARTED = 40075
|
|
VOUCHER_CODE_EXPIRED = 40019
|
|
VOUCHER_CODE_HASNT_STARTED = 40055
|
|
VOUCHER_CODE_INVALID = 40018
|
|
VOUCHER_CODE_INVALID_COUNTRY = 40054
|
|
VOUCHER_CODE_INVALID_USER_TYPE = 40052
|
|
VOUCHER_CODE_REQUIRED = 40017
|
|
VOUCHER_CODE_USED = 40020
|
|
VOUCHER_CODE_WRONG_PROMO = 40076
|
|
VOUCHER_REDEMPTION_UNAVAILABLE = 40034
|
|
VOUCHER_RULE_INVALID_DEVICE = 40067
|
|
WEBHOOK_EVENT_NOT_FOUND = 40116
|
|
WHITE_LABEL_CAMPAIGN_DOWNLOAD_DISABLED = 40066
|
|
WHITE_LABEL_CAMPAIGN_NOT_FOUND = 40087
|