Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Australia 9 Channels Issues #116

Closed
matthuisman opened this issue Aug 2, 2024 · 104 comments
Closed

Australia 9 Channels Issues #116

matthuisman opened this issue Aug 2, 2024 · 104 comments

Comments

@matthuisman
Copy link
Owner

matthuisman commented Aug 2, 2024

Currently the 9 streams are down.

Around the start of olympics they removed the old linear streams that were used
We needed to switch to streams that require a TOKEN
These tokens need to be generated by a 9now user account.

Added automation to do this, however the user accounts keep getting suspended.
Maybe they are detected as bots? or maybe its human's doing it?
Or, could be automation if a token is used by multiple different IPs then kill the account.

Anyway - to get new user accounts - it takes a bit of time to create them and then get the refresh token required
to create access tokens that then are used to get the streams.

Im currently a bit "sick of the back and forth" so not jumping to try to keep fixing it.

For now, I'd recommend using the 9now Kodi addon which generates the token for just your instance (not shared).

Another option is to spin up a server to generate the tokens and then points other IPTV players at that server.
There is abit about that on this ticket: matthuisman/slyguy.addons#825

@matthuisman matthuisman pinned this issue Aug 2, 2024
@trevor68
Copy link

trevor68 commented Aug 2, 2024

Hi,
Would it be feasible for supporters to share the 9now addon URL with Tivimate? I'm ok using Kodi, but the wife approval factor of now using 2 apps to watch TV is quite low!

@matthuisman
Copy link
Owner Author

getting tivimate to get a url from kodi isnt really possible.
youd need to create your own HTTP app somewhere on the network somewhere to fetch and return the url
Someone did that on matthuisman/slyguy.addons#825

@trevor68
Copy link

trevor68 commented Aug 2, 2024

Ah ok thanks anyhow, I'm only a homelabber running truenas scale, some VM's and Docker Desktop, coding is out of my reach.

@paul19920801
Copy link

Perhaps automation requests are being sent to frequently, and too predictably. Maybe implement it do be done randomly, perhaps once every few hours, then once between 30 and 45 minutes in between.

@matthuisman
Copy link
Owner Author

matthuisman commented Aug 4, 2024 via email

@oismacca
Copy link

oismacca commented Aug 4, 2024

I've managed to successfully extract a tokenized url for each channel manually from 9now.com.au, and the streams still work after 48 hours. Tested in VLC, Chrome browser and Tivimate. They also work with and without VPN so at least 2 IPs. I don't want to share them incase more than 2 IPs triggers an account suspension.

Requires the M3U Sniffer extension for Chrome. Navigate to each channel on 9now and grab the url that begins with either of the two syntaxes below:

https://csm-e-nineau1-eb.bln1.yospace.com/csm/extlive/nnaprd01,prod-simulcast-....

https://9now-livestreams-fhd-t.akamaized.net/t/prod/simulcast/......

It's a bloody big url with over 1500 characters. But they work so far.

@matthuisman
Copy link
Owner Author

matthuisman commented Aug 4, 2024 via email

@oismacca
Copy link

oismacca commented Aug 4, 2024

Do you know if these urls eventually expire? I'm surprised I'm getting 48+ hours out of them so far.

@paul19920801
Copy link

What would it require to import the tokenized urls into nextpvr?

I'm thinking along the lines of an app/webserver which automatically do it - this would require each user to use their own token, which should automatically get generated, then the url that's exported from the app is what one would import into nextpvr.

@matthuisman
Copy link
Owner Author

matthuisman commented Aug 4, 2024 via email

@trevor68
Copy link

trevor68 commented Aug 4, 2024

If anybody gets said redirect server working as a docker container that would be brilliant!

@gongoner
Copy link

gongoner commented Aug 4, 2024

I hope there is a fix. wife is on my case.

@oismacca
Copy link

oismacca commented Aug 4, 2024

But if anyone has a server running, not too hard to setup your own redirect service using your own auth

I've been trying to work out how they generate the token. I get tokenized urls for the SBS streams which is an easy php redirect that calls an api via curl with the right auth headers. The results in a json with the stream url. But it looks like 9now uses some on-the-fly base64 stuff across multiple js scripts to generate the token instead of an api call. It's like they're hiding the token other than in the url? Bit above my head I think, my skills go as far as php/curl/java.

@matthuisman
Copy link
Owner Author

everything you need is in my slyguy 9now plugin or that other code above

@GS1812
Copy link

GS1812 commented Aug 5, 2024

Just want to share these flask apps that have been tested on Ubuntu 24.04 server. All codes here are based entirely on Matt's 9now addon so all credits go to him.

flask app to activate device and acquire private refresh_token:

`
from flask import Flask, render_template_string, jsonify, redirect, url_for
import time
import json
import os
import requests
from urllib.parse import urlencode
from requests.exceptions import RequestException, Timeout, ConnectionError
import threading

app = Flask(name)

HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36',
}
AUTH_URL = 'https://login.nine.com.au/api/device{}'
REFRESH_TOKEN_PATH = 'your_local_path/refresh_token.json'
ACTIVATION_URL = 'https://login.nine.com.au/device?client_id=9now-web'

device_code_data = None
device_code_lock = threading.Lock()

class API:
def init(self):
self.session = requests.Session()
self.session.headers.update(HEADERS)

def _make_request(self, method, url, **kwargs):
    max_retries = 3
    for attempt in range(max_retries):
        try:
            response = method(url, **kwargs, timeout=60)
            response.raise_for_status()
            return response
        except (Timeout, ConnectionError) as e:
            if attempt == max_retries - 1:
                raise
            time.sleep(2 ** attempt)  # Exponential backoff
        except RequestException as e:
            raise

def device_code(self):
    params = {
        'client_id': '9nowdevice',
    }
    response = self._make_request(self.session.post, AUTH_URL.format('/code'), params=params, data={})
    return response.json()

def device_login(self, auth_code, device_code):
    params = {
        'auth_code': auth_code,
        'device_code': device_code,
        'client_id': '9nowdevice',
        'response_types': 'id_token',
    }
    response = self._make_request(self.session.get, AUTH_URL.format('/token'), params=params)
    data = response.json()
    
    if 'accessToken' not in data:
        return False

    # Store the refresh token in the session cookies
    self.session.cookies.set('refresh_token', data['refresh_token'])
    return True

api = API()

def get_new_device_code():
global device_code_data
with device_code_lock:
try:
device_code_data = api.device_code()
print("\nNew Device Code Information:")
print(f"Your device code is: {device_code_data['auth_code']}")
print(f"Please visit: {ACTIVATION_URL}")
print("Enter the device code on that page to activate your device.")
except RequestException as e:
print(f"Error getting device code: {str(e)}")
device_code_data = None

@app.route('/')
def index():
get_new_device_code() # Always get a new device code when the page is loaded
if device_code_data is None:
return "Error getting device code. Please refresh the page to try again.", 500

html = """
<!DOCTYPE html>
<html>
<head>
    <title>9Now Device Activation</title>
    <script>
        function checkStatus() {
            fetch('/check_status', {
                method: 'POST',
                headers: {
                    'User-Agent': '{{ user_agent }}'
                }
            })
                .then(response => response.json())
                .then(data => {
                    if (data.status === 'success') {
                        document.getElementById('status').innerHTML = `
                            Device activated! Refresh token saved.<br>
                            Token: ${data.refresh_token}<br>
                            Saved to: ${data.token_path}<br>
                            <a href="/">Click here to activate another device</a>
                        `;
                    } else if (data.status === 'pending') {
                        setTimeout(checkStatus, 5000);
                    } else if (data.status === 'reset') {
                        window.location.reload();
                    } else {
                        document.getElementById('status').innerHTML = 'Error: ' + data.message + ' <a href="/">Click here to try again</a>';
                    }
                })
                .catch(error => {
                    console.error('Error:', error);
                    setTimeout(checkStatus, 5000);
                });
        }
    </script>
</head>
<body onload="checkStatus()">
    <h1>9Now Device Activation</h1>
    <p>Your device code is: <strong>{{ device_code }}</strong></p>
    <p>Please follow these steps:</p>
    <ol>
        <li>Visit <a href="{{ activation_url }}" target="_blank">{{ activation_url }}</a> to activate your device.</li>
        <li>If you're not already logged in, you'll be asked to log in to your 9Now account.</li>
        <li>After logging in (if necessary), enter the code above on that page.</li>
    </ol>
    <p id="status">Waiting for activation...</p>
</body>
</html>
"""

return render_template_string(html, 
                              device_code=device_code_data['auth_code'],
                              activation_url=ACTIVATION_URL,
                              user_agent=HEADERS['User-Agent'])

@app.route('/check_status', methods=['POST'])
def check_status():
global device_code_data
if device_code_data is None:
return jsonify({"status": "reset", "message": "Device code not initialized"})

try:
    if api.device_login(device_code_data['auth_code'], device_code_data['device_code']):
        refresh_token = api.session.cookies.get('refresh_token')
        if refresh_token:
            save_refresh_token(refresh_token)
            print("Device activated! Refresh token saved.")
            return jsonify({
                "status": "success", 
                "message": "Device activated and refresh token saved.",
                "refresh_token": refresh_token,
                "token_path": REFRESH_TOKEN_PATH
            })
        else:
            return jsonify({"status": "error", "message": "Refresh token not found in cookies."})
    else:
        return jsonify({"status": "pending", "message": "Waiting for device activation."})
except RequestException as e:
    if '422' in str(e):
        print("Received 422 error. Device code may be expired or invalid.")
        return jsonify({"status": "reset", "message": "Device code expired or invalid. Please refresh the page."})
    return jsonify({"status": "error", "message": f"Network error: {str(e)}"})

def save_refresh_token(refresh_token):
save_path = os.path.dirname(REFRESH_TOKEN_PATH)
if not os.path.exists(save_path):
os.makedirs(save_path)

with open(REFRESH_TOKEN_PATH, 'w') as f:
    json.dump({'refresh_token': refresh_token}, f)
print(f"Refresh token saved to {REFRESH_TOKEN_PATH}")

if name == 'main':
print("Starting 9Now Device Activation server...")
print("Please open a web browser and navigate to http://localhost:9000")
print(f"Using User-Agent: {HEADERS['User-Agent']}")

app.run(host='0.0.0.0', port=9000)

`

flask app to get Channel 9 NSW live stream:

`
import os
import json
import time
import requests
from flask import Flask, Response, redirect
from urllib.parse import urlencode, parse_qsl
import threading
import sys
from datetime import datetime

HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36',
}
AUTH_URL = 'https://login.nine.com.au/api/device{}'
LIVESTREAM_URL = 'https://api.9now.com.au/ctv/livestreams'

REFRESH_TOKEN_PATH = '/your_local_path/refresh_token.json'
CACHE_FILE_PATH = '/your_local_path/channel9_url_cache.json'
DEAD_SCRIPT_PATH = '/your_local_path/dead_channel_script.txt'

class Settings:
REGION = 'nsw'
CHANNEL_REFERENCE = 'live-ch9-syd-ssai' # Reference for Channel 9 Sydney
MAX_RETRIES = 5
INITIAL_RETRY_DELAY = 1 # seconds

settings = Settings()

def get_refresh_token():
with open(REFRESH_TOKEN_PATH, 'r') as f:
data = json.load(f)
return data.get('refresh_token')

def print_with_timestamp(message):
print(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {message}")

class API:
def init(self):
self.session = requests.Session()
self.session.verify = True
self.session.headers.update(HEADERS)
self._access_token = None
self._token_expires = 0
self.retry_count = 0
self.url_cache = {}
self.is_initial_fetch = True

def _refresh_token(self, force=False):
    if not force and self._token_expires > time.time():
        return True

    refresh_token = get_refresh_token()
    params = {
        'refresh_token': refresh_token,
        'client_id': '9nowdevice',
        'response_types': 'id_token',
    }

    refresh_url = AUTH_URL.format('/refresh-token')
    full_url = f"{refresh_url}?{urlencode(params)}"
    print_with_timestamp(f"Refreshing Access token: {full_url}")

    try:
        response = self.session.post(refresh_url, params=params, timeout=30)
        response.raise_for_status()
        data = response.json()
        
        if 'error' in data:
            raise Exception(data['error'])

        self._access_token = data['accessToken']
        self._token_expires = int(time.time()) + data['expiresIn'] - 30
        self.session.headers.update({'Authorization': f'Bearer {self._access_token}'})
        print_with_timestamp(f"Access token refreshed successfully. Expires at: {time.ctime(self._token_expires)}")
        self.retry_count = 0
        return True
    except requests.exceptions.RequestException as e:
        print_with_timestamp(f"Error refreshing token: {str(e)}")
        self.handle_retry()
        return False

def handle_retry(self):
    self.retry_count += 1
    if self.retry_count >= settings.MAX_RETRIES:
        print_with_timestamp(f"Max retries ({settings.MAX_RETRIES}) reached. Exiting...")
        self.exit_gracefully()
    else:
        retry_delay = settings.INITIAL_RETRY_DELAY * (2 ** (self.retry_count - 1))
        print_with_timestamp(f"Retry {self.retry_count}/{settings.MAX_RETRIES}. Waiting for {retry_delay} seconds before next attempt.")
        time.sleep(retry_delay)

def exit_gracefully(self):
    with open(DEAD_SCRIPT_PATH, 'w') as f:
        f.write(os.path.abspath(__file__))
    print_with_timestamp(f"Written to {DEAD_SCRIPT_PATH}. Exiting...")
    sys.exit(1)

def channels(self, region='nsw'):
    self._refresh_token()
    params = {
        'device': 'web',
        'streamParams': 'web,chrome,windows',
        'region': region,
        'offset': 0,
    }
    try:
        response = self.session.get(LIVESTREAM_URL, params=params, timeout=60)
        response.raise_for_status()
        data = response.json()
        self.retry_count = 0  # Reset retry count on successful request
        return data['data']['getLivestream']
    except requests.RequestException as e:
        print_with_timestamp(f"Error fetching channels: {str(e)}")
        self.handle_retry()
        return None

def get_channel9_url(self):
    current_time = time.time()
    
    if 'url' in self.url_cache and current_time < self.url_cache['expiry']:
        return self.url_cache['url']

    if not self._refresh_token():
        print_with_timestamp("Failed to refresh token")
        return None

    try:
        data = self.channels(region=settings.REGION)
        if data:
            for row in data['channels']:
                if row['referenceId'] == settings.CHANNEL_REFERENCE:
                    url = row['stream']['url']
                    if '?' in url:
                        url = url.split('?')[0] + '?' + urlencode(parse_qsl(url.split('?')[1]))
                    
                    self.url_cache = {
                        'url': url,
                        'expiry': self._token_expires
                    }

                    with open(CACHE_FILE_PATH, 'w') as f:
                        json.dump(self.url_cache, f)

                    if self.is_initial_fetch:
                        print_with_timestamp("Successfully acquired initial URL:")
                        print(url)
                        print_with_timestamp(f"Next refresh in {self.format_next_refresh()}")
                        self.is_initial_fetch = False
                    else:
                        print_with_timestamp("Successfully updated URL upon expiry:")
                        print(url)
                        print_with_timestamp(f"Next refresh in {self.format_next_refresh()}")

                    return url
            print_with_timestamp("Channel not found in API response.")
            return None
        else:
            print_with_timestamp("No data returned from channels()")
            return None
    except Exception as e:
        print_with_timestamp(f"Error fetching Channel 9 URL: {str(e)}")
        return None

def format_next_refresh(self):
    sleep_time = max(self._token_expires - time.time(), 60)
    hours, remainder = divmod(sleep_time, 3600)
    minutes, _ = divmod(remainder, 60)
    next_refresh_time = time.localtime(self._token_expires)
    return f"{int(hours)} hrs {int(minutes):02d} mins at {time.strftime('%Y-%m-%d %H:%M:%S', next_refresh_time)}"

def initialize_api():
global api
api = API()
max_attempts = 10
for attempt in range(max_attempts):
print_with_timestamp(f"Attempt {attempt + 1}/{max_attempts} to initialize API and fetch initial URL")
url = api.get_channel9_url()
if url:
return True
else:
print_with_timestamp(f"Failed to acquire initial URL on attempt {attempt + 1}")
time.sleep(5) # Wait 5 seconds before retrying

print_with_timestamp(f"Failed to acquire initial URL after {max_attempts} attempts")
return False

def update_url_periodically():
while True:
try:
url = api.get_channel9_url()
if not url:
print_with_timestamp("Failed to update URL")
time.sleep(max(api._token_expires - time.time(), 60))
except Exception as e:
print_with_timestamp(f"Error in periodic update: {str(e)}")
time.sleep(60)

app = Flask(name)

@app.route('/9')
def get_channel9_live():
url = api.get_channel9_url()
if url:
print_with_timestamp("Serving cached URL")
return redirect(url)
else:
return Response("Unable to fetch Channel 9 URL", status=500, mimetype='text/plain')

if name == 'main':
if initialize_api():
# Start the background thread to update the URL periodically
threading.Thread(target=update_url_periodically, daemon=True).start()

    # Start the Flask app in production mode with threading
    print_with_timestamp("Starting Flask app")
    app.run(host='0.0.0.0', port=9001, threaded=True)
else:
    print_with_timestamp("Failed to initialize API. Exiting...")
    sys.exit(1)

`

@LGSAM59
Copy link

LGSAM59 commented Aug 6, 2024

I've managed to successfully extract a tokenized url for each channel manually from 9now.com.au, and the streams still work after 48 hours. Tested in VLC, Chrome browser and Tivimate. They also work with and without VPN so at least 2 IPs. I don't want to share them incase more than 2 IPs triggers an account suspension.

Requires the M3U Sniffer extension for Chrome. Navigate to each channel on 9now and grab the url that begins with either of the two syntaxes below:

https://csm-e-nineau1-eb.bln1.yospace.com/csm/extlive/nnaprd01,prod-simulcast-....

https://9now-livestreams-fhd-t.akamaized.net/t/prod/simulcast/......

It's a bloody big url with over 1500 characters. But they work so far.

I have managed to use the sniffer and play on VLC and Chrome , how do you play this in Tivimate ?

@oismacca
Copy link

oismacca commented Aug 6, 2024 via email

@GS1812
Copy link

GS1812 commented Aug 6, 2024

This is a very good extension for Chrome/Edge.

https://chromewebstore.google.com/detail/videoplayer-mpdm3u8m3uepg/opmeopcambhfimffbomjgemehjkbbmji

It autoplays and is capable of decrypting WV encrypted videos on the fly if WV keys are provided.

@LGSAM59
Copy link

LGSAM59 commented Aug 8, 2024

Try matching the User-Agent setting in Tivimate (either under Settings > General or Settings > Playlists > [playlist name]) with the user agent in the URL.

On Tue, 6 Aug 2024, 4:24 pm LGSAM59, @.> wrote: I've managed to successfully extract a tokenized url for each channel manually from 9now.com.au, and the streams still work after 48 hours. Tested in VLC, Chrome browser and Tivimate. They also work with and without VPN so at least 2 IPs. I don't want to share them incase more than 2 IPs triggers an account suspension. Requires the M3U Sniffer https://chromewebstore.google.com/detail/m3u8-sniffer-tv-find-and/akkncdpkjlfanomlnpmmolafofpnpjgn extension for Chrome. Navigate to each channel on 9now and grab the url that begins with either of the two syntaxes below: https://csm-e-nineau1-eb.bln1.yospace.com/csm/extlive/nnaprd01,prod-simulcast-.. .. https://9now-livestreams-fhd-t.akamaized.net/t/prod/simulcast/...... It's a bloody big url with over 1500 characters. But they work so far. I have managed to use the sniffer and play on VLC and Chrome , how do you play this in Tivimate ? — Reply to this email directly, view it on GitHub <#116 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADRL4OBBJRVDXLEYCJXNAILZQCB3RAVCNFSM6AAAAABL3YHZCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENZQGY3TSMJVG4 . You are receiving this because you commented.Message ID: <matthuisman/i. @.>

Thanks , ended up being a poor copy and paste on my part . Now I just need to find a way to link it to a guide and hope it stays live

@jamesgallagher
Copy link

jamesgallagher commented Aug 8, 2024

Thanks , ended up being a poor copy and paste on my part . Now I just need to find a way to link it to a guide and hope it stays live

If you keep the IDs the same as Matt's, you can just use his EPG. No reason why it wouldn't work (unless they have been removed from the EPG).

Edit: Probably should have elaborated. Add those links to a new m3u playlist and make sure you keep the IDs the same as Matt's. Then it should work using his EPG.

@trevor68
Copy link

trevor68 commented Aug 8, 2024

I'm at the same position, used the sniffer to get a working url, self hosted YOURLS to get a nice short url, but now can't add it to Tivimate without a matching epg? Matt's is still live, but of course the ID's now do not match.

Update:

I already pipe Xteve to all my Tivimate instances, I will see if I can manually write a m3u file using my links, but with matts ID's.

@trevor68
Copy link

trevor68 commented Aug 8, 2024

I got the EPG working nicely, unfortunately the links do not work in Tivimate, even though they are fine in Chrome?

@trevor68
Copy link

trevor68 commented Aug 9, 2024

Could someone please post the useragent for Tivimate, pretty sure I have messed it up

@jamesgallagher
Copy link

I use the Kodi format of Matt's with the pipe, but I have not tested if it is even needed as the M3U has the useragent in it. This is mine

https://9now-livestreams-fhd-t.akamaized.net/VeryLongURLWithLotsInIt|user-agent=Mozilla/5.0%20%28Windows%20NT%2010.0%3B%20Win64%3B%20x64%29%20AppleWebKit/537.36%20%28KHTML%2C%20like%20Gecko%29%20Chrome/98.0.4758.102%20Safari/537.36&seekable=0&referer=%20

@matthuisman
Copy link
Owner Author

matthuisman commented Aug 9, 2024 via email

@trevor68
Copy link

trevor68 commented Aug 9, 2024

Mine works in Chrome and my media player, tivimate gives me a 302 error.

[link removed]

@matthuisman
Copy link
Owner Author

302 isnt an error. 302 = redirect
tivimate needs to follow that redirect

@jamesgallagher
Copy link

jamesgallagher commented Aug 9, 2024

Looks like your URL shortener is mundging it. I am anything but an expert, but if I was you and you are worrying about long URLs, why don't you just make a playlist with a short name and host that. It makes no difference how long the URLs inside that list are. So make a playlist called 9.m3u8 and add all 5 Nine networks channels in that. It will even be shorter than your 'shortened' URL. eg cannardy.com/9.m3u8

@trevor68
Copy link

trevor68 commented Aug 9, 2024

Ah ok, that's precisely what i had done, I have a m3u file called channels 9, it contains all five long url's. Tivimate use a short url from Xteve.

I posted the short one for convenience. Though i was testing from it too. I will go add the |useragent to the end of the long urls and test properly thanks.

@NormBurns
Copy link

NormBurns commented Sep 7, 2024 via email

@ballfam
Copy link

ballfam commented Sep 7, 2024

don't know if it works for what you are using, but I switched to streamlink a while back. It actually just uses ffmpeg under the hood, but it can handle those ad insertions. It can even handle Pluto

@RedRubble
Copy link

RedRubble commented Sep 8, 2024

For those who might find this useful, here's a version of the script that dispenses with flask and just prints the url for the m3u8. I use this to pipe into streamlink and in turn into tvh. I've not changed the logic so all credits go to Matt and original person who created the modified script, i just made some changes to suit my needs. What this version will do is save copies of the access code and expiry, as well as cache all the URLs into json files, and read them each time the script is called. The script was tested under python 3.8.10 only.

9geturl.txt

I'm trying to get this to work, but getting a fetching error. I'm guessing i've got my token formatting wrong. How do you get this token?
I've also changed to region to 'vic' and all 'syd' to 'mel'

@smalldog666
Copy link

For those who might find this useful, here's a version of the script that dispenses with flask and just prints the url for the m3u8. I use this to pipe into streamlink and in turn into tvh. I've not changed the logic so all credits go to Matt and original person who created the modified script, i just made some changes to suit my needs. What this version will do is save copies of the access code and expiry, as well as cache all the URLs into json files, and read them each time the script is called. The script was tested under python 3.8.10 only.
9geturl.txt

I'm trying to get this to work, but getting a fetching error. I'm guessing i've got my token formatting wrong. How do you get this token? I've also changed to region to 'vic' and all 'syd' to 'mel'

I used the associated script that was posted earlier in this thread without changes to generate the token.

9acquire_refresh_token.txt

@smalldog666
Copy link

smalldog666 commented Sep 9, 2024

For those who might find this useful, here's a version of the script that dispenses with flask and just prints the url for the m3u8. I use this to pipe into streamlink and in turn into tvh. I've not changed the logic so all credits go to Matt and original person who created the modified script, i just made some changes to suit my needs. What this version will do is save copies of the access code and expiry, as well as cache all the URLs into json files, and read them each time the script is called. The script was tested under python 3.8.10 only.
9geturl.txt

I'm trying to get this to work, but getting a fetching error. I'm guessing i've got my token formatting wrong. How do you get this token? I've also changed to region to 'vic' and all 'syd' to 'mel'

Also, you might want to leave the region and city as nsw and syd, to make sure you get a working token first, then change to get the vic and mel equiv streams.

i had not looked into the codes (nsw vs vic etc).. having said this, i just changed my script to have region as vic and channel 9 as live-ch9-mel-ssai, and i was able to get a valid stream back..

@RedRubble
Copy link

RedRubble commented Sep 9, 2024

Also, you might want to leave the region and city as nsw and syd, to make sure you get a working token first, then change to get the vic and mel equiv streams.

i had not looked into the codes (nsw vs vic etc).. having said this, i just changed my script to have region as vic and channel 9 as live-ch9-mel-ssai, and i was able to get a valid stream back..

OK so I got this to work with the refresh token, with nsw syd, tried vic and mel and got no URL. I think I have different goals to most of you all here, as I'm using StreamMaster (basically tvheadend but only for iptv to add into Plex). I got a URL and added it in, but its reporting no streams avaliable (can download and watch via VLC though, so not sure if this is a nsw issue or something else). So one at another brick wall unfortunately, but I feel I'm close...

@smalldog666
Copy link

Also, you might want to leave the region and city as nsw and syd, to make sure you get a working token first, then change to get the vic and mel equiv streams.
i had not looked into the codes (nsw vs vic etc).. having said this, i just changed my script to have region as vic and channel 9 as live-ch9-mel-ssai, and i was able to get a valid stream back..

OK so I got this to work with the refresh token, with nsw syd, tried vic and mel and got no URL. I think I have different goals to most of you all here, as I'm using StreamMaster (basically tvheadend but only for iptv to add into Plex). I got a URL and added it in, but its reporting no streams avaliable (can download and watch via VLC though). So one at another brick wall unfortunately, but I feel I'm close...

If you changed the region, remove the cached_urls.json and rerun the script..

@RedRubble
Copy link

If you changed the region, remove the cached_urls.json and rerun the script..

Oh sweet that worked :P

@smalldog666
Copy link

smalldog666 commented Sep 9, 2024

Also, you might want to leave the region and city as nsw and syd, to make sure you get a working token first, then change to get the vic and mel equiv streams.
i had not looked into the codes (nsw vs vic etc).. having said this, i just changed my script to have region as vic and channel 9 as live-ch9-mel-ssai, and i was able to get a valid stream back..

OK so I got this to work with the refresh token, with nsw syd, tried vic and mel and got no URL. I think I have different goals to most of you all here, as I'm using StreamMaster (basically tvheadend but only for iptv to add into Plex). I got a URL and added it in, but its reporting no streams avaliable (can download and watch via VLC though, so not sure if this is a nsw issue or something else). So one at another brick wall unfortunately, but I feel I'm close...

I use tvh, and feed tvh links into plex.. reason i do this is i use pvrlive on android tv, which can pull the channels (and epg) from tvh.. so i get a native interface on the tv with all the channels and guide.. i only do plex live tv when i am not at home..

@RedRubble
Copy link

RedRubble commented Sep 9, 2024

I use tvh, and feed tvh links into plex.. reason i do this is i use pvrlive on android tv, which can pull the channels (and epg) from tvh.. so i get a native interface on the tv with all the channels and guide.. i only do plex live tv when i am not at home..

So I tried adding that URL to the 'Networks' section in tvh, and reporting 0 muxes/services/mapped. Am I missing a step here? Thanks for your help!

@smalldog666
Copy link

I use tvh, and feed tvh links into plex.. reason i do this is i use pvrlive on android tv, which can pull the channels (and epg) from tvh.. so i get a native interface on the tv with all the channels and guide.. i only do plex live tv when i am not at home..

So I tried adding that URL to the 'Networks' section in tvh, and reporting 0 muxes/services/mapped. Am I missing a step here? Thanks for your help!

I don't feed any m3u's directly into tvh.. i normally wash it with streamlink first... below is the shell script that i use.. i have a simple m3u (that's added to tvh's network tab) that basically call pipe://home/hts/nine/nine.sh with an argument of 9/9gem/9go/9life etc for each stream..

#!/bin/sh
STREAMLINK=/usr/local/bin/streamlink
PYTHON3=/usr/bin/python3
NINE=/home/hts/nine/9geturl.py
URL=$PYTHON3 $NINE $1
$STREAMLINK --stdout --default-stream best --hls-audio-select "*" --ringbuffer-size 128M --stream-segment-threads 1 --stream-segment-attempts 2 --stream-segment-timeout 5 --hls-live-edge 6 --hls-segment-stream-data --url $URL

@matthuisman matthuisman unpinned this issue Sep 9, 2024
@matthuisman
Copy link
Owner Author

matthuisman commented Sep 9, 2024

9 streams are now back in all playlists as they have removed the tokens.
Must have been olympics only.

Closing this issue.
See you all in 4 years! hehe

Remember to support me:
https://www.matthuisman.nz/support-me

@camkerr81
Copy link

No way!. well thats a win. I literally sent them a support request to remove their tokens stuff about an hour ago. Hahaha. what a coincidence.

@nickw444
Copy link

nickw444 commented Sep 9, 2024

Wow, thanks for looping back on this. I guess I'm surprised that they didn't keep it as an overall improvement as piracy protection for their regular content 🤷

@phoenpc
Copy link

phoenpc commented Sep 9, 2024

Considering people had issues on 9now, I guess they had to rollback for the sake of their viewers instead of keeping it for good.

In either case this is so good to hear, love it. Thanks for the update.

@NormBurns
Copy link

Thanks so much for all your advice and support throughout all this Matt. I have certainly learned a lot about streams....!

@LegendMASTER-2024
Copy link

LegendMASTER-2024 commented Sep 10, 2024

Awesome stuff. Was tuning into the Newcastle and Northern Rivers streams, but just before the Olympics started, bye bye direct links, was sad. Now I am happy again, and can finally go back to these streams. NBN News and the regional advertising for both areas are the main key takeaways of these streams, otherwise programming is the same as the capital cities.

For anyone who's new here and are looking for the direct links to the (UPDATED) regional streams...
https://9now-livestreams-fhd-t.akamaized.net/u/prod/simulcast/new/ch9/hls/r1/index.m3u8
https://9now-livestreams-fhd-t.akamaized.net/u/prod/simulcast/nlm/ch9/hls/r1/index.m3u8

Will these work with the capital cities too? Yes, of course. Just change the city name to "syd" for Sydney, "mel" for Melbourne, "bne" for Brisbane, "adl" for Adelaide, or "per" for Perth. And for the Gold Coast QTQ relay, "gcq" is the one for you.

@nihonjin98
Copy link

Is there anything thst needs to be done for the urls to work? I an no longer getting a 403 error but now get a IllegalStateException error. I have updated the playlists; cleared the cache for the app. What else is needed to get then to work?

@matthuisman
Copy link
Owner Author

matthuisman commented Sep 12, 2024 via email

@coxy86
Copy link

coxy86 commented Sep 12, 2024

I've been using sparkle and have had no issues since channel 9 streams have been updated by Matt. Using sparkle on nvidia shield fwiw

@trevor68
Copy link

Yep, tivimate 5.1 problem, roll back to 5.04

@psychocircus98
Copy link

Is there anything thst needs to be done for the urls to work? I an no longer getting a 403 error but now get a IllegalStateException error. I have updated the playlists; cleared the cache for the app. What else is needed to get then to work?

Do you find the error is consistent? I ask because if I first go to the new stream in Tivimate I get the error, but if I flick to another channel and back it typically works. Made me wonder if I needed to specify something further in the URL (browser type?) to avoid the issue but the above suggests a Tivimate 5.1 issue.

@trevor68
Copy link

yup that's the workaround for the error in tivimate,

@psychocircus98
Copy link

I just saw the thread posted above on whirlpool....and did the look at me being late to the party! haha

@nihonjin98
Copy link

Do you find the error is consistent?

Yes it would always appear if i went first to a channel 9 link but if i went to another station first and then to channel 9 it would not give the error.

@matthuisman
Copy link
Owner Author

locking this convo for the sake of of my email inbox

Repository owner locked and limited conversation to collaborators Sep 13, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests