Compare commits

..

4 commits

Author SHA1 Message Date
Jens Timmerman e2ad6c0610 fixed login and better video url selection 2021-09-14 23:26:12 +02:00
Jens Timmerman 1a37e51dd4 document updated login procedure 2021-09-14 23:02:41 +02:00
Jens Timmerman c25869428f added searchable for show list, fixed website parsing, TODO: login is broken 2021-09-12 20:06:31 +02:00
Jens Timmerman 1a431c1fb7 started working on searchbar, works but cannot be focussed after focus us lost 2020-11-01 01:58:36 +01:00
6 changed files with 188 additions and 59 deletions

View file

@ -58,9 +58,51 @@ session.post('https://token.vrt.be',
'ts': auth_info['signatureTimestamp'],
'email': auth_info['profile']['email'],
}).encode('utf-8'))
token = session.post('https://media-services-public.vrt.be/vualto-video-aggregator-web/rest/external/v1/tokens', headers={'Content-Type': 'application/json'}, data=b'').json()['vrtPlayerToken']
```
### update sept 2021
```
session = requests.session()
username = urllib.parse.quote('<vrtnu username here>', safe='')
password = urllib.parse.quote('<vrtnu password here>', safe='')
data={
'loginID': username,
'password': password,
'sessionExpiration': '-2',
'APIKey': '3_qhEcPa5JGFROVwu5SWKqJ4mVOIkwlFNMSKwzPDAh8QZOtHqu6L4nD5Q7lk0eXOOG',
'targetEnv': 'jssdk',
}
auth_info = requests.post('https://accounts.vrt.be/accounts.login', data=data).json()
# get crsrf cookie
session.get('https://token.vrt.be/vrtnuinitlogin?provider=site&destination=https://www.vrt.be/vrtnu/')
# do login
session.post('https://login.vrt.be/perform_login',
data={
'UID': auth_info['UID'],
'UIDSignature': auth_info['UIDSignature'],
'signatureTimestamp': auth_info['signatureTimestamp'],
'client_id': 'vrtnu-site',
'_csrf': session.cookies['OIDCXSRF'],
#'email': auth_info['profile']['email'],
})
token = session.post('https://media-services-public.vrt.be/vualto-video-aggregator-web/rest/external/v1/tokens', headers={'Content-Type': 'application/json'}, data=b'').json()['vrtPlayerToken']
```
vrtnu-site_profile_vt contains a token:
data = {"identityToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2xvZ2luLnZydC5iZSIsImF1ZCI6InZydG51LXNpdGUiLCJpYXQiOjE2MzE2NDkwNjAsImV4cCI6MTYzMTY1MjY2MCwic3ViIjoiOThmODNkNzMtNzlmNi00YTY4LWEzMmYtODBkMzlmMGRkZmE5IiwiYWMiOiIxNisiLCJ1c2VyX3N0YXR1cyI6IlZFUklGSUVEX0JFX1JFU0lERU5UIn0.NM4Fqg-6C_v5PMfizd-_5ZUfGD40D3S7wvG8WPpFJu8"}
get video data

View file

@ -1,13 +1,11 @@
#!/usr/bin/env ruby
print "What is the URL of your Apple Downloads resource?\nYou should find this on https://developer.apple.com/download/ \nURL:"
print "What is the URL of your Apple Downloads resource?\nURL:"
url = gets.strip
print "What is the ADCDownloadAuth cookie token:\nADCDownloadAuth: "
token = gets.strip
print "Make sure you have aria2 installed (brew install aria2) "
command = "aria2c --header \"Host: adcdownload.apple.com\" --header \"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\" --header \"Upgrade-Insecure-Requests: 1\" --header \"Cookie: ADCDownloadAuth=#{token}\" --header \"User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 10_1 like Mac OS X) AppleWebKit/602.2.14 (KHTML, like Gecko) Version/10.0 Mobile/14B72 Safari/602.1\" --header \"Accept-Language: en-us\" -x 16 -s 16 #{url} -d ~/Downloads"
exec(command)

View file

@ -7,10 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
30A7823B26EE41EF00DAC1FB /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4F0CC7C251BE62B00E9EA74 /* ContentView.swift */; };
B408F0DD251F6D180043E3A4 /* AsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B408F0DC251F6D180043E3A4 /* AsyncImage.swift */; };
B408F0E5251F7AC50043E3A4 /* Just.swift in Sources */ = {isa = PBXBuildFile; fileRef = B408F0E4251F7AC50043E3A4 /* Just.swift */; };
B4F0CC7B251BE62B00E9EA74 /* vrtnuApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4F0CC7A251BE62B00E9EA74 /* vrtnuApp.swift */; };
B4F0CC7D251BE62B00E9EA74 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4F0CC7C251BE62B00E9EA74 /* ContentView.swift */; };
B4F0CC7F251BE62F00E9EA74 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B4F0CC7E251BE62F00E9EA74 /* Assets.xcassets */; };
B4F0CC82251BE62F00E9EA74 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B4F0CC81251BE62F00E9EA74 /* Preview Assets.xcassets */; };
B4F0CC8D251BE62F00E9EA74 /* vrtnuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4F0CC8C251BE62F00E9EA74 /* vrtnuTests.swift */; };
@ -280,8 +280,8 @@
buildActionMask = 2147483647;
files = (
B408F0E5251F7AC50043E3A4 /* Just.swift in Sources */,
B4F0CC7D251BE62B00E9EA74 /* ContentView.swift in Sources */,
B4F0CCB7251D6B5400E9EA74 /* VrtNuLayout.swift in Sources */,
30A7823B26EE41EF00DAC1FB /* ContentView.swift in Sources */,
B4F0CC7B251BE62B00E9EA74 /* vrtnuApp.swift in Sources */,
B4F0CCAA251BFD6F00E9EA74 /* video.swift in Sources */,
B408F0DD251F6D180043E3A4 /* AsyncImage.swift in Sources */,

View file

@ -15,7 +15,7 @@ import TVUIKit
struct ContentView: View {
@ViewBuilder
var body: some View {
VRTNuView(vrtNu: VRTNu())
VRTNuView(shows: VRTNu().getShows())
}
}
@ -51,30 +51,55 @@ struct LoginView: View{
})
}
}
struct VRTListView: View{
var shows: [Show]
var text: String = ""
var body: some View{
List(shows, id: \.title){ show in
NavigationLink(destination: ShowView(show: show)){
HStack{
AsyncImage(url: show.imageURL,placeholder: {
//Image(name: "loading")
Text("Loading...")
}, image:{
Image(uiImage:$0)
.resizable()
}).aspectRatio(contentMode: .fit).frame(width: 480, height:300)
VStack(alignment: .leading){
Text(show.title)
.padding()
Text(show.showURL.absoluteString)
}
}
}
}
}
}
struct VRTNuView: View{
var vrtNu: VRTNu
var shows: [Show]
@State private var searchText = ""
var body: some View {
NavigationView(){
List(vrtNu.getShows(), id: \.title){ show in
NavigationLink(destination: ShowView(show: show)){
HStack{
AsyncImage(url: show.imageURL,placeholder: {
//Image(name: "loading")
Text("Loading...")
}, image:{
Image(uiImage:$0)
.resizable()
}).aspectRatio(contentMode: .fit).frame(width: 480, height:300)
VStack(alignment: .leading){
Text(show.title)
.padding()
Text(show.showURL.absoluteString)
if #available(tvOS 15.0, *) {
VRTListView(shows: shows.filter({ searchText.isEmpty ? true : $0.showName.contains(searchText) }))
.searchable(text: $searchText)
{
ForEach(shows.filter({ searchText.isEmpty ? true : $0.showName.contains(searchText) })) { show in
Text(show.title).searchCompletion(show.title)
}
}
} else {
// Fallback on earlier versions
VRTListView(shows: shows)
}
}.navigationBarTitle("Browse VRT Nu")
}
}.navigationTitle("Browse VRT Nu")//.listStyle(DefaultListStyle())
}
}
struct ShowView: View {

View file

@ -52,15 +52,14 @@ struct Episode: Hashable, Comparable{
let title = videojson.value(forKey: "title") as! String
let targetURLs = videojson.value(forKey: "targetUrls") as! [NSDictionary]
var videourl = targetURLs[0].value(forKey: "url") as! String
// it seams that the hls_aes stream has more changes of playing
// TODO: pass all streams and switch stream if one fails?
for i in 0 ..< targetURLs.count{
if targetURLs[i].value(forKey: "type") as! String == "hls_aes"{
videourl = targetURLs[i].value(forKey: "url") as! String
var temptargeturl: String
for targeturl in targetURLs{
temptargeturl = (targeturl["url"] as! String)
if temptargeturl.contains("aes"){
videourl = temptargeturl
}
}
print(videourl)
//session.get('https://media-services-public.vrt.be/vualto-video-aggregator-web/rest/external/v1/videos/%s?vrtPlayerToken=%s&client=%s@PROD' %(video_id, token, clientid)).json()
var video = Video(hlsUrl: URL(string: videourl)!,
@ -106,12 +105,17 @@ struct Episode: Hashable, Comparable{
}
func parseData(data: String, regexPattern: String) -> [String]{
print("Parsing Data")
print("regex")
print(regexPattern)
let range = NSRange(location: 0, length: data.count)
let regex = try! NSRegularExpression(pattern: regexPattern)
let matches = regex.matches(in: data, range: range)
let nsString = data as NSString
//let output = Array(Set(matches.map {nsString.substring(with: $0.range)}))
let output = matches.map {nsString.substring(with: $0.range)}
print("parsedata output:")
print(output)
return output
}
@ -146,14 +150,20 @@ struct Season: Hashable, Comparable{
print("getting episodes for " + show.showName + " " + seasonName)
let regexPattern = "vrtnu/a-z/" + show.showName + "/" + seasonName + "/([^\"]*)/"
let imageregexPattern = "data-responsive-image=\".*(jpg|png)"
let titleregexPattern = "\">(.*)(</a>|<br />)"
let imageregexPattern = "images.vrt.be/.*(jpg|png)"
let titleregexPattern = "\">(.*)(</a>|<br />|</h3>)"
let data = Just.get("https://www.vrt.be/vrtnu/a-z/" + show.showName + "/" + seasonName + ".lists.all-episodes/").text!
print("getting url")
let url = "https://www.vrt.be/vrtnu/a-z/" + show.showName + "/" + seasonName + ".episodes-list/"
print(url)
let data = Just.get(url).text!
let output = parseData(data: data, regexPattern: regexPattern)
let imageoutput = parseData(data: data, regexPattern: imageregexPattern)
let titleoutput = parseData(data: data, regexPattern: titleregexPattern)
print(data)
print(output)
print(imageoutput)
print(titleoutput)
var episode: String
var myepisodes: [Episode]
@ -163,8 +173,8 @@ struct Season: Hashable, Comparable{
episode = output[i * 2].replacingOccurrences(of: "vrtnu/a-z/" + show.showName + "/" + seasonName + "/", with: "").replacingOccurrences(of: "/", with: "")
print(episode)
print(seasonName)
let image = URL(string: imageoutput[i].replacingOccurrences(of: "https:", with: "").replacingOccurrences(of: "http:", with: "").replacingOccurrences(of: "data-responsive-image=\"", with: "https:"))!
let title = titleoutput[i].replacingOccurrences(of: "\">", with: "").replacingOccurrences(of: "<br />", with: "").replacingOccurrences(of: "</a>", with: "")
let image = URL(string: imageoutput[i].replacingOccurrences(of: "https:", with: "").replacingOccurrences(of: "http:", with: "").replacingOccurrences(of: "images.vrt.be/", with: "https://images.vrt.be/"))!
let title = titleoutput[i].replacingOccurrences(of: "\">", with: "").replacingOccurrences(of: "<br />", with: "").replacingOccurrences(of: "</a>", with: "").replacingOccurrences(of: "</h3>", with: "")
myepisodes.append(Episode(season: self, episodeName: episode, title: title, imageURL: image))
}
@ -174,7 +184,9 @@ struct Season: Hashable, Comparable{
}
struct Show: Hashable, Comparable{
struct Show: Hashable, Comparable, Identifiable{
let id = UUID()
static func < (lhs: Show, rhs: Show) -> Bool {
//return lhs.showName < rhs.showName
@ -239,23 +251,31 @@ struct Show: Hashable, Comparable{
func getSeasons() -> [Season]{
//`re.findall('value="#parsys_container_banner_%s_(.*)">' % show, requests.get('https://www.vrt.be/vrtnu/a-z/%s/' % show).text)`
let regexPattern = "value=\"#parsys_container_banner_(" + showName + "_)?(.*)\">"
var regexPattern = "id=\"parsys_container_banner_(.*)_title\">"
print("getting seasons from " + showURL.absoluteString)
print("filtering with " + regexPattern)
let output = parseData(data: Just.get(showURL).text!, regexPattern: regexPattern)
let data = Just.get(showURL).text!
var output = parseData(data: data, regexPattern: regexPattern)
if output.count == 0 {
regexPattern = "id=\"parsys_container_episodes-list_(.*)_title\">"
output = parseData(data: data, regexPattern: regexPattern)
}
if output.count == 0 {
regexPattern = "value=\"#parsys_container_banner_(.*)\">"
output = parseData(data: data, regexPattern: regexPattern)
}
var season: String
var myseasons: [Season]
myseasons = []
for i in 0 ..< output.count{
print(output[i])
season = output[i].replacingOccurrences(of: "value=\"#parsys_container_banner_", with: "")
.replacingOccurrences(of: showName + "_", with: "").replacingOccurrences(of: "\">", with: "")
season = output[i].replacingOccurrences(of: "id=\"parsys_container_episodes-list_", with: "").replacingOccurrences(of: "_title", with: "").replacingOccurrences(of: "id=\"parsys_container_banner_", with: "").replacingOccurrences(of: "value=\"#parsys_container_banner_", with: "").replacingOccurrences(of: "\">", with: "")
print(season)
print(showName)
myseasons.append(Season(show: self, seasonName: season, title: season))
}
myseasons.sort()
return myseasons
}
@ -275,9 +295,10 @@ extension NSTextCheckingResult {
struct VRTNu: Hashable {
let regexPattern = "a href=\"/vrtnu/a-z/(.*).relevant"
let regexPattern = "a href=\"/vrtnu/a-z/(.*)/\">"
let imageregexPattern = "data-responsive-image=\".*(jpg|png)"
let titleregexPattern = ".relevant/\">(.*)</a>"
let titleregexPattern = "a href=\"/vrtnu/a-z/.*/\">(.*)</a>"
func getToken() -> String {
///token = session.post('https://media-services-public.vrt.be/vualto-video-aggregator-web/rest/external/v1/tokens', headers={'Content-Type': 'application/json'}, data=b'').json()['vrtPlayerToken']
@ -292,14 +313,15 @@ struct VRTNu: Hashable {
func login(username: String, password:String) -> Bool {
let auth_data = [
"ApiKey": "3_0Z2HujMtiWq_pkAjgnS2Md2E11a1AwZjYiBETtwNE-EoEHDINgtnvcAOpNgmrVGy",
"ApiKey": "3_qhEcPa5JGFROVwu5SWKqJ4mVOIkwlFNMSKwzPDAh8QZOtHqu6L4nD5Q7lk0eXOOG",
"targetEnv": "jssdk",
"loginID": username,
"password": password,
"authMode": "cookie",
"sessionExpiration": "-2",
]
let auth_json = just.post("https://accounts.eu1.gigya.com/accounts.login", data: auth_data).json!
let auth_json = just.post("https://accounts.vrt.be/accounts.login", data: auth_data).json!
let auth_info = auth_json as! NSDictionary
print("auth_info")
print(auth_info)
if auth_info.object(forKey: "statusCode") != nil{
if auth_info.value(forKey: "statusCode") as! Int == 403
@ -308,19 +330,19 @@ struct VRTNu: Hashable {
return false
}
}
// no token is returnd but necessary cookies are set
just.post("https://token.vrt.be", json:[
"uid": auth_info.value(forKey: "UID"),
"uidsig": auth_info.value(forKey: "UIDSignature"),
"ts": auth_info.value(forKey: "signatureTimestamp"),
// get csrf
// tokensession.get('https://token.vrt.be/vrtnuinitlogin?provider=site&destination=https://www.vrt.be/vrtnu/')
let csrf = just.get("https://token.vrt.be/vrtnuinitlogin?provider=site&destination=https://www.vrt.be/vrtnu/").cookies["OIDCXSRF"]
just.post("https://login.vrt.be/perform_login", data :[
"UID": auth_info.value(forKey: "UID")!,
"UIDSignature": auth_info.value(forKey: "UIDSignature")!,
"signatureTimestamp": auth_info.value(forKey: "signatureTimestamp")!,
"client_id": "vrtnu-site",
"_csrf": csrf!.value,
//"email": auth_info.value(forKey: "profile"['email'],
"email": username
],
headers: [
"Conetnt-Type": "application/json",
"Referer": "https://www.vrt.be/vrtnu/",
//"email": username
]
)
print("authenticated")
return true
@ -335,6 +357,7 @@ struct VRTNu: Hashable {
print("getting shows")
let data = Just.get("https://www.vrt.be/vrtnu/a-z/").text!
print("show data")
print(data)
let output = parseData(data: data, regexPattern: regexPattern)
@ -347,9 +370,12 @@ struct VRTNu: Hashable {
var myshows: [Show]
myshows = []
for i in 0 ..< output.count{
show = output[i].replacingOccurrences(of: ".relevant", with: "").replacingOccurrences(of: "a href=\"/vrtnu/a-z/", with: "")
show = output[i].replacingOccurrences(of: "/\">", with: "").replacingOccurrences(of: "a href=\"/vrtnu/a-z/", with: "")
image = imageoutput[i].replacingOccurrences(of: "https:", with: "").replacingOccurrences(of: "http:", with: "").replacingOccurrences(of: "data-responsive-image=\"", with: "https:")
title = titleoutput[i].replacingOccurrences(of: ".relevant/\">", with: "").replacingOccurrences(of: "</a>", with: "")
title = titleoutput[i].replacingOccurrences(of: "a href=\"/vrtnu/a-z/" + show + "/\">", with: "").replacingOccurrences(of: "</a>", with: "")
print(show)
print(image)
print(title)
myshows.append(Show(vrtNu: self, showName: show, title: title, imageURL: URL(string: image)!))
}
myshows.sort()

View file

@ -0,0 +1,38 @@
//
// SearchBar.swift
// vrtnu
//
// Created by Jens Timmerman on 29/10/2020.
//
import SwiftUI
struct SearchBar: View {
@Binding var text: String
@State private var isEditing = false
var body: some View {
HStack {
TextField("Search ...", text: $text)
.padding(7)
.padding(.horizontal, 25)
.cornerRadius(8).overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 8)
}
)
.padding(.horizontal, 10)
}
}
}
struct SearchBar_Previews: PreviewProvider {
static var previews: some View {
SearchBar(text: .constant(""))
}
}