LeBazarDeBryan / XTVZ_

IPTV Streaming 🇫🇷
https://xtvz.vercel.app
GNU General Public License v3.0
25 stars 2 forks source link

TF1's workflow not working #4

Open azgaresncf opened 3 months ago

azgaresncf commented 3 months ago

I've tried the TF1's program, and the API throws an "Permission insuffisante" error.

The only way to fetch the TF1's stream is to provide an account.

It's not because of this. It now requires a JWT token in order to get access to their metadata. This token can be found by doing a single request to their API on post login, to this website : https://www.tf1.fr/token/gigya/web. It'll need a token based on the Gigya signature (UIDSignature on Gigya login -> https://compte.tf1.fr/accounts.login).

Do you think you would know how to update the current plugin ?

It would need a POST to https://compte.tf1.fr/accounts.login

loginID= email &password= password &sessionExpiration=31536000&targetEnv=jssdk&include=identities-all%2Cdata%2Cprofile%2Cpreferences%2C&includeUserInfo=true&loginMode=standard&lang=fr&APIKey=3_hWgJdARhz_7l1oOp3a8BDLoR9cuWZpUaKG4aqF7gum9_iK3uTZ2VlDBl8ANf8FVk&sdk=js_latest&authMode=cookie&pageURL=https%3A%2F%2Fwww.tf1.fr%2F&sdkBuild=13987&format=json You then grab the UIDSignature and the UID from the Gigya response (either from the accounts login, either from the userInfo, UID + UIDSignature).

You then POST to https://www.tf1.fr/token/gigya/web, in order for this request to work you will also need to create a timestamp in EPOCH/UNIX format (something like 1687780538)

{"uid":" UID ","signature":" UIDSignature ","timestamp":,"consent_ids":["1","2","3","4","10001","10003","10005","10007","10013","10015","10017","10019","10009","10011","13002","13001","10004","10014","10016","10018","10020","10010","10012","10006","10008"] You grab the "token" from the JSON answer.

After that, you can do a GET request to https://mediainfo.tf1.fr/mediainfocombo/L_TF1?pver=5010000&context=MYTF1&topDomain=unknown&platform=web&device=desktop&os=windows&osVersion=10.0&playerVersion=5.10.0&productName=mytf1&productVersion=2.59.2&browser=firefox&browserVersion=114 with a specific header Authorization : Bearer token.

Answer will be in "delivery" -> "url"

azgaresncf commented 3 months ago

I'm going to make a PR.

LeBazarDeBryan commented 3 months ago

Yes, I know that the workflow have problem. So what you're saying is that I need to add an account to have the permission?

azgaresncf commented 3 months ago

Yeah, but I can give a throwaway account (maybe on Discord if you want to).

LeBazarDeBryan commented 3 months ago

Yeah, but I can give a throwaway account (maybe on Discord if you want to).

Yes, please. You can send it to me on Discord if you want.

azgaresncf commented 3 months ago
{
    'callId': '<callid>',
    'errorCode': '<error>',
    'errorDetails': 'Your request is blocked because of security issues.',
    'errorMessage': 'Invalid parameter value',
    'apiVersion': 2,
    'statusCode': 400,
    'statusReason': 'Bad Request',
    'time': '<time>',
    'errorFlags': 'missingKey'
}
rickeymandraque commented 1 month ago

Bonjour, c'est le docteur Bash ! À prendre 6 fois par jour en alternant les comptes yopmail !

#!/bin/bash

# Informations d'authentification
USERNAME="fauxcompte@yopmail.com"
PASSWORD="password12345"
OUTPUT="$3"
USER_AGENT="Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like MacOS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"

# Nom du groupe de la playlist.
# possibilité d'utiliser $thematic_label au lieu d'une chaîne de caractères.
# ATTENTION Si `$thematic_label` est utilisé alors la variable doit être définie dans la boucle while de la fonction build_playlist
# la variable sera à décommenter
group_name="\"TF1 Plus\""

# Proxy ne fonctionne pas.
# PROXY="94.23.252.168:9180"

# Page d'accueil de TF1
TF1_DIRECT_URL="https://www.tf1.fr/direct"

# URL de l'API Gigya
AUTH_URL="https://compte.tf1.fr/accounts.login"
SESSION_URL="https://www.tf1.fr/token/gigya/web"
#GIGYA_API_KEY="$(curl -sL "$TF1_DIRECT_URL" | grep -oP 'apiKey=\K[^&]+')"
GIGYA_API_KEY="$(curl -sL "$TF1_DIRECT_URL" | grep -oP '(?<=gigya":{"apiKey":")[^",]+')"

# URL GraphQL pour obtenir la liste des chaînes
GRAPHQL_URL="https://www.tf1.fr/graphql/web"

# Fonction d'authentification
login() {
    local username="$1"
    local password="$2"

    # Authentification avec TF1 en utilisant le proxy
    res=$(curl -sL -X POST "$AUTH_URL" -d "loginID=$username&password=$password&APIKey=$GIGYA_API_KEY&includeUserInfo=true" -H "Referer: $TF1_DIRECT_URL" -A "$USER_AGENT" --compressed)

    # Si l'authentification réussit, obtenir le token Gigya
    if [ "$(echo "$res" | jq -r '.statusCode')" == "200" ]; then
        uid=$(echo "$res" | jq -r '.userInfo.UID')
        signature=$(echo "$res" | jq -r '.userInfo.UIDSignature')
        timestamp=$(echo "$res" | jq -r '.userInfo.signatureTimestamp')

        session_res=$(curl -sL -X POST "$SESSION_URL" -d "{\"uid\": \"$uid\", \"signature\": \"$signature\", \"timestamp\": $timestamp, \"consent_ids\": [\"1\", \"2\", \"3\", \"4\", \"10001\", \"10003\", \"10005\", \"10007\", \"10013\", \"10015\", \"10017\", \"10019\", \"10009\", \"10011\", \"13002\", \"13001\", \"10004\", \"10014\", \"10016\", \"10018\", \"10020\", \"10010\", \"10012\", \"10006\", \"10008\"]}" --compressed)

        if [ "$(echo "$session_res" | jq -r '.right')" == "BASIC" ]; then
            user_token=$(echo "$session_res" | jq -r '.token')
            return 0 # Authentification réussie
        else
            return 1 # Échec de l'authentification
        fi
    else
        return 1 # Échec de l'authentification
    fi
}

# Fonction pour encoder la requête JSON dans l'URL
json_encode() {
    echo -n "$1" | jq -s -R -r @uri
}

# Fonction pour obtenir la liste des chaînes avec certains détails
get_channel_list() {
    # Identifiant GraphQL, sujet à modification
    GRAPHQL_ID="ecf53113697d74c6a7771ad8ed018b5a22ae7a06"
    # Requête GraphQL
    graphql_query=$(json_encode '{"ofChannelTypes":["DIGITAL","TV","EVENT"],"limit":500}')
    encoded_url="$GRAPHQL_URL?id=${GRAPHQL_ID}&variables=$graphql_query"

    # Utiliser curl avec l'URL encodée et le proxy
    response=$(curl -sL --globoff "$encoded_url" --compressed)

    # Analyse de la réponse JSON et formatage en CSV
    channel_list=$(echo "$response" | jq -r '.data.lives.items[] | [.channel.live.streamId, .channel.slug, .channel.thematic.slug, .channel.thematic.label, .channel.id, .channel.type, .channel.decoration.logo.sourcesWithScales[0].url, .channel.label] | @csv' | sed 's/null//g')
    # echo "streamId, slug, label, thematic_slug, thematic_label, id, type, logo"
    echo "$channel_list"
}

# Fonction pour obtenir les URLs HLS
function get_hls_urls() {

    # URL de l'API de TF1
    URL_API="https://mediainfo.tf1.fr/mediainfocombo"
    CHANNELID="$1"

    # echo "Authentification réussie"
    if [ -n "$CHANNELID" ]; then
        URL_API="${URL_API}/${CHANNELID}"
        API_RESPONSE=$(curl -s "${URL_API}?pver=5015000&format=hls" -A "$USER_AGENT" --header "authorization: Bearer $user_token" --compressed)
        # Analyser la réponse JSON
        status_code=$(echo "$API_RESPONSE" | jq -r '.delivery.code')
        if [ "$status_code" == "200" ]; then

            # Extraction de l'URL du flux HLS
            hls_url=$(echo "$API_RESPONSE" | jq -r '.delivery.url')

            # Si l'URL se termine par hls-sd.m3u8, changer pour hls-hd.m3u8
            if [[ $hls_url == *hls-sd.m3u8 ]]; then
                hls_url="${hls_url/hls-sd.m3u8/hls-hd.m3u8}"
            fi

            echo "$hls_url"
        else
            error_message=$(echo "$API_RESPONSE" | jq -r '.delivery.code')
            echo "Erreur API : $error_message"
        fi
    else
        echo "Chaîne non trouvée dans la liste."
    fi

}

# Fonction pour construire la playlist
function build_playlist() {

  group_option="$1"
    # Obtenir la liste des chaînes
    CHANNELS_LIST=$(get_channel_list)

    # Authentification
    if login "$USERNAME" "$PASSWORD"; then
        # echo "$user_token"
        # Analyser la sortie CSV
        while IFS="," read -r streamId slug thematic_slug thematic_label id type logo label; do
            CHANNELID="$(echo "$streamId" | tr -d \")"
            name="$label"
            clean_name="$(echo "$name" | tr -d \, | tr -d \")"
            if [[ "$group_option" == "thematic" ]]; then
            m3u_group_name="$thematic_label"
            else
            m3u_group_name="$group_name"
            fi
            echo "#EXTINF:-1 tvg-name=\"$clean_name\" tvg-logo=$logo group-title=${m3u_group_name},$clean_name"
            get_hls_urls "$CHANNELID"

        done <<<"$CHANNELS_LIST"

    else
        echo "Échec de l'authentification. Vérifiez votre nom d'utilisateur et votre mot de passe."
    fi
}

build_playlist

Bon, c'est encore un peu expérimental mais cela fonctionne en 720p. Bonne journée.

LeBazarDeBryan commented 1 month ago

Bonjour, c'est le docteur Bash ! À prendre 6 fois par jour en alternant les comptes yopmail !

Bon, c'est encore un peu expérimental mais cela fonctionne en 720p. Bonne journée.

Bonjour, est-ce possible d'avoir le script en python s'il vous plaît ?

azgaresncf commented 1 month ago

Bonjour, c'est le docteur Bash ! À prendre 6 fois par jour en alternant les comptes yopmail ! Bon, c'est encore un peu expérimental mais cela fonctionne en 720p. Bonne journée.

Bonjour, est-ce possible d'avoir le script en python s'il vous plaît ?

Je vais te le convertir.

azgaresncf commented 1 month ago
import requests
import json
import urllib.parse

# Informations d'authentification
USERNAME = "fauxcompte@yopmail.com"
PASSWORD = "password12345"
USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like MacOS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
group_name = "TF1 Plus"

# URL et clés d'API
TF1_DIRECT_URL = "https://www.tf1.fr/direct"
AUTH_URL = "https://compte.tf1.fr/accounts.login"
SESSION_URL = "https://www.tf1.fr/token/gigya/web"
GIGYA_API_KEY = requests.get(TF1_DIRECT_URL).text.split('gigya":{"apiKey":"')[1].split('"')[0]
GRAPHQL_URL = "https://www.tf1.fr/graphql/web"
GRAPHQL_ID = "ecf53113697d74c6a7771ad8ed018b5a22ae7a06"

def login(username, password):
    payload = {
        "loginID": username,
        "password": password,
        "APIKey": GIGYA_API_KEY,
        "includeUserInfo": "true"
    }
    headers = {
        "Referer": TF1_DIRECT_URL,
        "User-Agent": USER_AGENT
    }
    res = requests.post(AUTH_URL, data=payload, headers=headers)
    res_json = res.json()

    if res_json.get('statusCode') == 200:
        uid = res_json['userInfo']['UID']
        signature = res_json['userInfo']['UIDSignature']
        timestamp = res_json['userInfo']['signatureTimestamp']
        session_payload = {
            "uid": uid,
            "signature": signature,
            "timestamp": timestamp,
            "consent_ids": ["1", "2", "3", "4", "10001", "10003", "10005", "10007", "10013", "10015", "10017", "10019", "10009", "10011", "13002", "13001", "10004", "10014", "10016", "10018", "10020", "10010", "10012", "10006", "10008"]
        }
        session_res = requests.post(SESSION_URL, json=session_payload)
        session_res_json = session_res.json()
        if session_res_json.get('right') == "BASIC":
            user_token = session_res_json['token']
            return user_token
    return None

def json_encode(data):
    return urllib.parse.quote(json.dumps(data))

def get_channel_list():
    graphql_query = json_encode({"ofChannelTypes": ["DIGITAL", "TV", "EVENT"], "limit": 500})
    encoded_url = f"{GRAPHQL_URL}?id={GRAPHQL_ID}&variables={graphql_query}"
    response = requests.get(encoded_url)
    channel_list = response.json()['data']['lives']['items']
    return [
        (
            item['channel']['live']['streamId'], 
            item['channel']['slug'], 
            item['channel']['thematic']['slug'], 
            item['channel']['thematic']['label'], 
            item['channel']['id'], 
            item['channel']['type'], 
            item['channel']['decoration']['logo']['sourcesWithScales'][0]['url'], 
            item['channel']['label']
        ) for item in channel_list
    ]

def get_hls_urls(channel_id, user_token):
    URL_API = f"https://mediainfo.tf1.fr/mediainfocombo/{channel_id}?pver=5015000&format=hls"
    headers = {
        "User-Agent": USER_AGENT,
        "authorization": f"Bearer {user_token}"
    }
    api_response = requests.get(URL_API, headers=headers)
    api_response_json = api_response.json()
    if api_response_json.get('delivery', {}).get('code') == "200":
        hls_url = api_response_json['delivery']['url']
        if hls_url.endswith("hls-sd.m3u8"):
            hls_url = hls_url.replace("hls-sd.m3u8", "hls-hd.m3u8")
        return hls_url
    return None

def build_playlist(group_option="thematic"):
    user_token = login(USERNAME, PASSWORD)
    if not user_token:
        print("Échec de l'authentification. Vérifiez votre nom d'utilisateur et votre mot de passe.")
        return

    channel_list = get_channel_list()

    for stream_id, slug, thematic_slug, thematic_label, id_, type_, logo, label in channel_list:
        channel_id = stream_id.strip('"')
        clean_name = label.replace(',', '').replace('"', '')
        if group_option == "thematic":
            m3u_group_name = thematic_label
        else:
            m3u_group_name = group_name
        print(f'#EXTINF:-1 tvg-name="{clean_name}" tvg-logo={logo} group-title={m3u_group_name},{clean_name}')
        hls_url = get_hls_urls(channel_id, user_token)
        if hls_url:
            print(hls_url)
        else:
            print("Chaîne non trouvée dans la liste.")

if __name__ == "__main__":
    build_playlist()
LeBazarDeBryan commented 1 month ago

Bonjour, c'est le docteur Bash ! À prendre 6 fois par jour en alternant les comptes yopmail ! Bon, c'est encore un peu expérimental mais cela fonctionne en 720p. Bonne journée.

Bonjour, est-ce possible d'avoir le script en python s'il vous plaît ?

Je vais te le convertir.

Merci Azgar ! Je suis entrain de tester le script avec un autre compte temporaire, car celui-ci a été bloqué. Pour moi, je n'ai pas de lien dans la playlist donc j'ai debug pour voir ce qui n'allait pas. Le script arrive bien à tout avoir, mais il n'arrive pas à le mettre dans la playlist. J'essaye de trouver un moyen pour régler ce problème.

rickeymandraque commented 1 month ago

Bonjour, c'est le docteur Bash ! À prendre 6 fois par jour en alternant les comptes yopmail ! Bon, c'est encore un peu expérimental mais cela fonctionne en 720p. Bonne journée.

Bonjour, est-ce possible d'avoir le script en python s'il vous plaît ?

Je vais te le convertir.

Merci Azgar ! Je suis entrain de tester le script avec un autre compte temporaire, car celui-ci a été bloqué. Pour moi, je n'ai pas de lien dans la playlist donc j'ai debug pour voir ce qui n'allait pas. Le script arrive bien à tout avoir, mais il n'arrive pas à le mettre dans la playlist. J'essaye de trouver un moyen pour régler ce problème.

Attention, sur les workflows cela ne fonctionne pas comme ça, il faut passer par un vpn... Oui c'est con mais j'ai perdu plus de 8h avant de tilter "merde... Github c'est américain !" ...

LeBazarDeBryan commented 1 month ago

Bonjour, c'est le docteur Bash ! À prendre 6 fois par jour en alternant les comptes yopmail ! Bon, c'est encore un peu expérimental mais cela fonctionne en 720p. Bonne journée.

Bonjour, est-ce possible d'avoir le script en python s'il vous plaît ?

Je vais te le convertir.

Merci Azgar ! Je suis entrain de tester le script avec un autre compte temporaire, car celui-ci a été bloqué. Pour moi, je n'ai pas de lien dans la playlist donc j'ai debug pour voir ce qui n'allait pas. Le script arrive bien à tout avoir, mais il n'arrive pas à le mettre dans la playlist. J'essaye de trouver un moyen pour régler ce problème.

Attention, sur les workflows cela ne fonctionne pas comme ça, il faut passer par un vpn... Oui c'est con mais j'ai perdu plus de 8h avant de tilter "merde... Github c'est américain !" ...

Je n'essayais pas sur un workflow, mais merci pour l'info ! Donc la on a un autre problème...

rickeymandraque commented 1 month ago

À chaque problème sa solution... Solution bretonne à tester :

VPN gratuit sans inscription

Par contre je n'ai jamais essayé, je me sert de mon smartphone avec termux pour renouveler les URL toutes les 4h. Les connexions 4/5G ont une IP dynamique si on redémarre la connexion (activer /désactiver dans les paramètres de la sim) donc difficile de Blacklister les IP de SFR/free/orange.

Autre chose concernant le script, enfin plutôt l'API de TF1, c'est une merde pas croyable, il suffit d'oublier d'activer 1 cookie (1008 par exemple) pour vous donner la sensation que le compte est bloqué.

Comme il faut envoyer les requêtes en json (nique la logique... ) c'est un enfer à scripter correctement, une virgule oublié et hop, pas de réponse ou un beau 403 dans ta tronche...

Si cela peut vous aider, regardez du côté de https://github.com/Catch-up-TV-and-More/plugin.video.catchuptvandmore , en fouillant dans les modules vous trouverez votre bonheur.

Quand au vpn, il est possible de mettre un VPN sur un workflow si c'est le but.

Bon, c'est un peu des conseils basique mais j'ai bloqué réellement 8h à cause de tout ça donc je partage. 😉

Courage.