[Not working anymore] Videoland - Sign In Fix
Not working anymore See https://github.com/dut-iptv/dut-iptv/issues/144#issuecomment-1352449715
I was unable to sign-in to Videoland. It apparently has something to do with the cookies. To temp fix it, just download the following fixed version of Videoland: plugin.video.videoland.zip
If you are not familiar with Python, dont bother reading the below info.
So I fixed it by adding the gmid + ucid + hasGmid cookies :)
Working example of api.py below [using static/contant cookies]
[might work for others?]
import base64, json, os, random, re, string, time, xbmc
from collections import OrderedDict
from resources.lib.base.l1.constants import ADDON_ID, ADDON_PROFILE
from resources.lib.base.l2 import settings
from resources.lib.base.l2.log import log
from resources.lib.base.l3.language import _
from resources.lib.base.l3.util import check_key, get_credentials, encode32, is_file_older_than_x_days, is_file_older_than_x_minutes, load_file, load_profile, load_prefs, remove_dir, remove_file, save_profile, save_prefs, set_credentials, write_file
from resources.lib.base.l4.exceptions import Error
from resources.lib.base.l4.
session import Session
from resources.lib.base.l5.api import api_download, api_get_channels
from resources.lib.constants import CONST_BASE_HEADERS, CONST_URLS, CONST_IMAGES
from urllib.parse import parse_qs, urlparse, quote_plus
#Included from base.l7.plugin
#api_clean_after_playback
#api_get_info
#Included from base.l8.menu
#api_add_to_watchlist
#api_get_profiles
#api_list_watchlist
#api_login
#api_play_url
#api_remove_from_watchlist
#api_search
#api_set_profile
#api_vod_download
#api_vod_season
#api_vod_seasons
#api_watchlist_listing
def api_add_to_watchlist(id, series='', season='', program_type='', type='watchlist'):
if not api_get_session():
return None
if series and len(str(series)) > 0:
id = series
if type == 'watchlist':
watchlist_url = '{base_url}/api/v3/watchlist/{id}'.format(base_url=CONST_URLS['base'], id=id)
else:
return False
headers = api_get_headers(personal=False)
download = api_download(url=watchlist_url, type='post', headers=headers, data=None, json_data=False, return_json=False)
code = download['code']
if not code or not code == 200:
return False
return True
def api_clean_after_playback(stoptime):
profile_settings = load_profile(profile_id=1)
if len(str(profile_settings['ticket_id'])) > 0:
offset = "00:00:00"
if stoptime > 0:
m, s = divmod(stoptime, 60)
h, m = divmod(m, 60)
offset = '{:02d}:{:02d}:{:02d}'.format(h, m, s)
session_post_data = {
'action': "stop",
'buffer_state': 0,
'buffer_total': 1,
'offset': offset,
'token': profile_settings['token']
}
headers = api_get_headers(personal=False)
stop_url = '{base_url}/api/v3/heartbeat/{ticket_id}?action=stop&offset={offset}'.format(base_url=CONST_URLS['base'], ticket_id=profile_settings['ticket_id'], offset=offset)
download = api_download(url=stop_url, type='post', headers=headers, data=session_post_data, json_data=True, return_json=True)
session_post_data['action'] = 'exit'
exit_url = '{base_url}/api/v3/heartbeat/{ticket_id}?action=exit&offset={offset}'.format(base_url=CONST_URLS['base'], ticket_id=profile_settings['ticket_id'], offset=offset)
download = api_download(url=exit_url, type='post', headers=headers, data=session_post_data, json_data=True, return_json=True)
def api_get_headers(personal=False):
headers = { 'videoland-platform': 'videoland' }
if personal == True:
pass
return headers
def api_get_info(id, channel=''):
#profile_settings = load_profile(profile_id=1)
info = {}
return info
def api_get_session(force=0, return_data=False):
force = int(force)
profile_url = '{base_url}/api/v3/profiles'.format(base_url=CONST_URLS['base'])
headers = api_get_headers(personal=False)
headers["Referer"] = "{base_url}/profielkeuze".format(base_url=CONST_URLS['base'])
download = api_download(url=profile_url, type='get', headers=headers, data=None, json_data=False, return_json=True)
data = download['data']
code = download['code']
if not code or not code == 200 or not data or not check_key(data[0], 'id'):
login_result = api_login()
if not login_result['result']:
if return_data == True:
return {'result': False, 'data': login_result['data'], 'code': login_result['code']}
return False
else:
download = api_download(url=profile_url, type='get', headers=headers, data=None, json_data=False, return_json=True)
data = download['data']
code = download['code']
profile_settings = load_profile(profile_id=1)
profile_settings['last_login_success'] = 1
profile_settings['last_login_time'] = int(time.time())
save_profile(profile_id=1, profile=profile_settings)
if return_data == True:
return {'result': True, 'data': data, 'code': code}
return True
def api_get_profiles():
profiles = api_get_session(force=0, return_data=True)
return_profiles = {}
if profiles['result'] == True:
for result in profiles['data']:
if result['is_active'] == True:
return_profiles[result['id']] = {}
return_profiles[result['id']]['id'] = result['id']
return_profiles[result['id']]['name'] = result['profilename']
return return_profiles
def api_list_watchlist(type='watchlist'):
if not api_get_session():
return None
profile_settings = load_profile(profile_id=1)
headers = api_get_headers(personal=False)
if type == 'continuewatch':
watchlist_url = '{base_url}/api/v3/progress/{id}'.format(base_url=CONST_URLS['base'], id=profile_settings['profile_id'])
elif type == 'watchlist':
watchlist_url = '{base_url}/api/v3/watchlist/{id}'.format(base_url=CONST_URLS['base'], id=profile_settings['profile_id'])
download = api_download(url=watchlist_url, type='get', headers=headers, data=None, json_data=False, return_json=True)
data = download['data']
code = download['code']
if code and code == 200 and data and check_key(data, 'count'):
return data
return None
def api_login():
creds = get_credentials()
username = creds['username']
password = creds['password']
remove_file(file='stream_cookies', ext=False)
profile_settings = load_profile(profile_id=1)
profile_settings['vlid'] = ''
profile_settings['profile_id'] = ''
save_profile(profile_id=1, profile=profile_settings)
headers = {
"Origin": CONST_URLS['gigya'],
"Referer": "{gigya_url}/".format(gigya_url=CONST_URLS['gigya']),
"Cookie": "gmid=gmid.ver4.AcbH8QbssA.v4cJjgoAsSCf9Zdj_avWgcJ9HV2e6AWBNGkcWUjHS0xdUzsDk76vyzDMvFQ23hNh.bUx-tQpqtFBZFdJpjLC6K7xRVnLBDdtOhQEDNyidI_qDKlktYOciFkL_3T3OWYqaHNtNvQXbBcZ9bZppDcXd3g.sc3; ucid=pwJ04qPJUZvOT0Y2bmqTmQ; hasGmid=ver4"
}
session_post_data = {
'loginID': username,
'password': password,
'sessionExpiration': '0',
'targetEnv': 'jssdk',
'include': 'profile,data',
'includeUserInfo': 'true',
'lang': 'en',
'APIKey': '3_t2Z1dFrbWR-IjcC-Bod1kei6W91UKmeiu3dETVG5iKaY4ILBRzVsmgRHWWo0fqqd',
'sdk': 'js_latest',
'authMode': 'cookie',
'pageURL': "{base_url}/".format(base_url=CONST_URLS['base']),
'format': 'json',
}
login_url = '{gigya_url}/accounts.login'.format(gigya_url=CONST_URLS['gigya'])
download = api_download(url=login_url, type='post', headers=headers, data=session_post_data, json_data=False, return_json=True)
data = download['data']
code = download['code']
if not code or not code == 200 or not data or not check_key(data, 'signatureTimestamp') or not check_key(data, 'UID') or not check_key(data, 'UIDSignature'):
return { 'code': code, 'data': data, 'result': False }
hash_url = '{base_url}/api/v3/login/hash'.format(base_url=CONST_URLS['base'])
headers = api_get_headers(personal=False)
headers["Referer"] = "{base_url}/inloggen".format(base_url=CONST_URLS['base'])
headers["Origin"] = CONST_URLS['base']
session_post_data = {
'UID': data['UID'],
'UIDSignature': data['UIDSignature'],
'signatureTimestamp': data['signatureTimestamp'],
}
download = api_download(url=hash_url, type='post', headers=headers, data=session_post_data, json_data=True, return_json=True)
data = download['data']
code = download['code']
if not code or not code == 200 or not data or not check_key(data, 'vlHash'):
return { 'code': code, 'data': data, 'result': False }
profile_settings['vlid'] = data['vlHash']
cookies = load_file(file='stream_cookies', isJSON=True)
if not cookies:
cookies = {}
cookies['vlId'] = data['vlHash']
write_file(file='stream_cookies', data=cookies, isJSON=True)
profile_url = '{base_url}/api/v3/profiles'.format(base_url=CONST_URLS['base'])
headers = api_get_headers(personal=False)
headers["Referer"] = "{base_url}/profielkeuze".format(base_url=CONST_URLS['base'])
download = api_download(url=profile_url, type='get', headers=headers, data=None, json_data=False, return_json=True)
data = download['data']
code = download['code']
if not code or not code == 200 or not data or not check_key(data[0], 'id'):
return { 'code': code, 'data': data, 'result': False }
save_profile(profile_id=1, profile=profile_settings)
api_set_profile()
return { 'code': code, 'data': data, 'result': True }
def api_play_url(type, channel=None, id=None, video_data=None, from_beginning=0, pvr=0, change_audio=0):
playdata = {'path': '', 'license': '', 'info': '', 'properties': {}}
if not api_get_session():
return playdata
from_beginning = int(from_beginning)
pvr = int(pvr)
change_audio = int(change_audio)
profile_settings = load_profile(profile_id=1)
info = {}
properties = {}
if not type or not len(str(type)) > 0:
return playdata
headers = api_get_headers(personal=False)
if type == 'channel':
play_url_path = id
download = api_download(url=play_url_path, type='get', headers=headers, data=None, json_data=False, return_json=True)
data = download['data']
code = download['code']
if not code or not code == 200 or not data or not check_key(data, 'manifest') or not check_key(data, 'token'):
return playdata
path = data['manifest']
if check_key(data, 'licenseUrl'):
license = data['licenseUrl']
profile_settings['ticket_id'] = ''
profile_settings['token'] = data['token']
save_profile(profile_id=1, profile=profile_settings)
mpd = ''
playdata = {'path': path, 'mpd': mpd, 'license': license, 'info': info, 'properties': properties}
return playdata
if id.startswith('E'):
typestr = 'episode'
id = id[1:]
id_ar = id.split('###')
series = id_ar[0]
season = id_ar[1]
id = id_ar[2]
info_url = '{base_url}/api/v3/episodes/{series}/{season}'.format(base_url=CONST_URLS['base'], series=series, season=season)
elif id.startswith('M'):
typestr = 'movie'
id = id[1:]
info_url = '{base_url}/api/v3/movies/{id}'.format(base_url=CONST_URLS['base'], id=id)
else:
return playdata
download = api_download(url=info_url, type='get', headers=headers, data=None, json_data=False, return_json=True)
data = download['data']
code = download['code']
if code and code == 200 and data:
if typestr == 'episode':
if check_key(data, 'details') and check_key(data['details'], 'E' + str(id)):
info = data['details']['E' + str(id)]
else:
info = data
play_url_path = '{base_url}/api/v3/stream/{id}/widevine?edition=&profile_id={profile_id}'.format(base_url=CONST_URLS['base'], id=id, profile_id=profile_settings['profile_id'])
download = api_download(url=play_url_path, type='get', headers=headers, data=None, json_data=False, return_json=True)
data = download['data']
code = download['code']
if not code or not code == 200 or not data or not check_key(data, 'id') or not check_key(data, 'ticket_id') or not check_key(data, 'stream') or not check_key(data['stream'], 'dash'):
return playdata
path = data['stream']['dash']
if check_key(data, 'drm'):
license = data['drm']
profile_settings['ticket_id'] = data['ticket_id']
profile_settings['token'] = data['stream']['token']
save_profile(profile_id=1, profile=profile_settings)
mpd = ''
if change_audio == 1:
download = api_download(url=path, type='get', headers=headers, data=None, json_data=False, return_json=False)
data = download['data']
code = download['code']
if code and code == 200:
mpd = data
playdata = {'path': path, 'mpd': mpd, 'license': license, 'info': info, 'properties': properties}
return playdata
def api_remove_from_watchlist(id, type='watchlist'):
if not api_get_session():
return None
headers = api_get_headers(personal=False)
if type == 'continuewatch':
remove_url = '{base_url}/api/v3/progress/{id}'.format(base_url=CONST_URLS['base'], id=id)
elif type == 'watchlist':
remove_url = '{base_url}/api/v3/watchlist/{id}'.format(base_url=CONST_URLS['base'], id=id)
download = api_download(url=remove_url, type='delete', headers=headers, data=None, json_data=False, return_json=False)
code = download['code']
if not code or (not code == 200 and not code == 204):
return False
return True
def api_search(query):
return None
def api_set_profile(id=''):
profile_settings = load_profile(profile_id=1)
profiles = api_get_session(force=0, return_data=True)
name = ''
owner_id = ''
owner_name = ''
saved_id = ''
saved_name = ''
if not profiles or profiles['result'] == False:
return False
for result in profiles['data']:
if result['is_account_owner'] == True:
owner_id = result['id']
owner_name = result['profilename']
if result['is_active'] == True and result['id'] == id:
name = result['profilename']
if check_key(profile_settings, 'profile_id'):
if result['is_active'] == True and result['id'] == profile_settings['profile_id']:
saved_id = result['id']
saved_name = result['profilename']
if len(str(name)) == 0:
if len(str(saved_name)) > 0:
id = saved_id
name = saved_name
else:
id = owner_id
name = owner_name
switch_url = '{base_url}/api/v3/profiles/{id}/switch'.format(base_url=CONST_URLS['base'], id=id)
headers = api_get_headers(personal=False)
headers["Referer"] = "{base_url}/profielkeuze".format(base_url=CONST_URLS['base'])
session_post_data = {
'customer_id': id
}
download = api_download(url=switch_url, type='post', headers=headers, data=session_post_data, json_data=False, return_json=False)
code = download['code']
if not code or not code == 204:
return False
profile_settings = load_profile(profile_id=1)
profile_settings['profile_name'] = name
profile_settings['profile_id'] = id
save_profile(profile_id=1, profile=profile_settings)
return True
def api_vod_download():
return None
def api_vod_season(series, id, use_cache=True):
type = "vod_season_{id}".format(id=id)
type = encode32(type)
file = os.path.join("cache", "{type}.json".format(type=type))
id_ar = id.split('###')
series = id_ar[0]
seasonstr = id_ar[1]
cache = 0
if not is_file_older_than_x_days(file=os.path.join(ADDON_PROFILE, file), days=0.5) and use_cache == True:
data = load_file(file=file, isJSON=True)
cache = 1
else:
headers = api_get_headers(personal=False)
seasons_url = '{base_url}/api/v3/series/{series}/seasons/{season}'.format(base_url=CONST_URLS['base'], series=series, season=seasonstr)
download = api_download(url=seasons_url, type='get', headers=headers, data=None, json_data=False, return_json=True)
data = download['data']
code = download['code']
if code and code == 200 and data and check_key(data, 'title'):
write_file(file=file, data=data, isJSON=True)
return {'data': data, 'cache': cache}
def api_vod_seasons(type, id, use_cache=True):
type = "vod_seasons_{id}".format(id=id)
type = encode32(type)
file = os.path.join("cache", "{type}.json".format(type=type))
id = id[1:]
cache = 0
if not is_file_older_than_x_days(file=os.path.join(ADDON_PROFILE, file), days=0.5) and use_cache == True:
data = load_file(file=file, isJSON=True)
cache = 1
else:
headers = api_get_headers(personal=False)
seasons_url = '{base_url}/api/v3/series/{series}'.format(base_url=CONST_URLS['base'], series=id)
download = api_download(url=seasons_url, type='get', headers=headers, data=None, json_data=False, return_json=True)
data = download['data']
code = download['code']
if code and code == 200 and data and check_key(data, 'title'):
write_file(file=file, data=data, isJSON=True)
return {'data': data, 'cache': cache}
def api_vod_subscription():
return None
def api_watchlist_listing():
return None
NOTE: I am not a programmer, but I am familiar with HTTP. To "hopefully" fix this permanently, you'll need to parse the cookies.
These cookies can be parsed from response headers after first making a GET request to:
https://accounts.eu1.gigya.com/accounts.webSdkBootstrap?apiKey=3_t2Z1dFrbWR-IjcC-Bod1kei6W91UKmeiu3dETVG5iKaY4ILBRzVsmgRHWWo0fqqd&pageURL=https%3A%2F%2Fwww.videoland.com%2F&sdk=js_latest&sdkBuild=13455&format=json
With the following headers.
Host: accounts.eu1.gigya.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0
Accept: */*
Accept-Language: nl,en-US;q=0.7,en;q=0.3
Origin: https://cdns.eu1.gigya.com
DNT: 1
Connection: keep-alive
Referer: https://cdns.eu1.gigya.com/
Cookie: gig3pctest=true
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Accept-Encoding: gzip, deflate
The response should contain the required cookies for login.
set-cookie: gmid=gmid.ver4.AcbHF_bQLg.qEdRRBNoJRoIWKw42-12Ms4pmka-Q0EV5eFZEsntAROdB0_gTfTEJN87Oy5S9CrH.5zK-lVztBN1Arvn7imJRW1aJbaitQKAQzYZ9JAeCwevj3vwvptLm47W3Z-58FX_z7_glMOCdmS_A85rnGUqKSA.sc3; expires=Sat, 18 Nov 2023 15:35:48 GMT; domain=.gigya.com; path=/; secure; samesite=none; httponly
set-cookie: ucid=Kx4yQiSc1ImzRcpmS59XKw; expires=Sat, 18 Nov 2023 15:35:48 GMT; domain=.gigya.com; path=/; secure; samesite=none
set-cookie: hasGmid=ver4; expires=Thu, 18 May 2023 14:35:48 GMT; domain=.gigya.com; path=/; secure; samesite=none
If someone is familair with Python, please edit the api.py so it parses the required cookies :)
Is this still an issue and does this fix still work? If others experience the same issues, I could implement this in my fork so everything could be in one place again. @Nigel1992 Static cookies doesn't seem the best idea as it could stop working any moment, response headers are easily obtained by, assume the response is called download: download.headers['the-required-header-name'] Ziggo uses response headers for tokens, too.
@3DSBricker
This fix is not working anymore as the entire login procedure changed. See https://github.com/dut-iptv/dut-iptv/issues/144#issuecomment-1352449715 Sidenote: I did say to parse the cookies as static was only a temp fix 😉
I can't test anything since I don't have Videoland. I'm not sure whether the "PRIVATE" in the links represent a token, your UID (user ID), name or something else. I assumed it was the UID (of which I don't know the layout). I doubt it'll be as simple as I did, and it probably won't work (yet), but I did try to apply the V2 logic. Also, if your ID looks something like this: _puid_0dba....dbd18, delete the HTTP file. In one response you edited the token, but didn't in a later (renew) request. That token isn't important in terms of hacking as far as I know. Could you test this? If it doesn't work (at all), please make a new, full, dump, blurring all private information like passwords and addresses (I saw they even include bank details, fortunately they're incomplete). But do include the requests from clicking on the login button and all those things. I noticed the Widevine license mentioned in the request is only valid for up to 540P, did you watch "John Wick 2" in 540P?
@3DSBricker Thanks for your help. Unfortunately, it didn't work :( I'll make a new dump soon :)
@3DSBricker Contact me on [decode as base64] dGhlZGpza3l3YWxrZXJAZ21haWwuY29t and I will provide you with a valid account to get this problem sorted.