matthuisman / i.mjh.nz

Mirror of https://i.mjh.nz + issues / feature requests
252 stars 57 forks source link

Australia 9 Channels Issues #116

Closed matthuisman closed 6 days ago

matthuisman commented 1 month ago

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: https://github.com/matthuisman/slyguy.addons/issues/825

trevor68 commented 1 month ago

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 commented 1 month ago

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 https://github.com/matthuisman/slyguy.addons/issues/825

trevor68 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

I suspect it's simply seeing multiple ips using the same token. I've heard users using a dontbugme login creds having that account suspended too.

On Sun, 4 Aug 2024, 16:29 paul19920801, @.***> wrote:

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.

— Reply to this email directly, view it on GitHub https://github.com/matthuisman/i.mjh.nz/issues/116#issuecomment-2267299884, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABPQAKLO5GAWGZ22TKOYMSTZPWU4HAVCNFSM6AAAAABL3YHZCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENRXGI4TSOBYGQ . You are receiving this because you authored the thread.Message ID: @.***>

oismacca commented 1 month ago

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 commented 1 month ago

That's the exact same as we used to use. They will get blocked when you have as many users using it.

On Sun, 4 Aug 2024, 17:50 oismacca, @.***> 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.

— Reply to this email directly, view it on GitHub https://github.com/matthuisman/i.mjh.nz/issues/116#issuecomment-2267354311, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABPQAKK2MD2YG5QV2AUT2BLZPW6MBAVCNFSM6AAAAABL3YHZCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENRXGM2TIMZRGE . You are receiving this because you authored the thread.Message ID: @.***>

oismacca commented 1 month ago

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

paul19920801 commented 1 month ago

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 commented 1 month ago

This is what my server does. It uses a constant url that redirects to latest streams. There code floating around that's copied the auth etc from my plugin. It just gets the url and then 302 redirect to the latest.

There is even my kodi.proxy which can run my addon's outside of Kodi to get the links etc. ppl use that with tvheadend.

It's still not an easy solution. But if anyone has a server running, not too hard to setup your own redirect service using your own auth

On Sun, 4 Aug 2024, 18:42 paul19920801, @.***> wrote:

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.

— Reply to this email directly, view it on GitHub https://github.com/matthuisman/i.mjh.nz/issues/116#issuecomment-2267370841, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABPQAKPMFTQQM3FUNMIDVILZPXENFAVCNFSM6AAAAABL3YHZCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENRXGM3TAOBUGE . You are receiving this because you authored the thread.Message ID: @.***>

trevor68 commented 1 month ago

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

gongoner commented 1 month ago

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

oismacca commented 1 month ago

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 commented 1 month ago

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

GS1812 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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 https://github.com/matthuisman/i.mjh.nz/issues/116#issuecomment-2270679157, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADRL4OBBJRVDXLEYCJXNAILZQCB3RAVCNFSM6AAAAABL3YHZCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENZQGY3TSMJVG4 . You are receiving this because you commented.Message ID: <matthuisman/i. @.***>

GS1812 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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

trevor68 commented 1 month ago

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

jamesgallagher commented 1 month ago

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 commented 1 month ago

That's the way. For tivimate just slap on the |user-agent= and profit :)

On Fri, 9 Aug 2024, 18:42 The James, @.***> wrote:

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 https://9now-livestreams-fhd-t.akamaized.net/VeryLongURLWithLotsInIt%7Cuser-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

— Reply to this email directly, view it on GitHub https://github.com/matthuisman/i.mjh.nz/issues/116#issuecomment-2277252552, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABPQAKJFTMLPUE76GUE63XTZQRQEXAVCNFSM6AAAAABL3YHZCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENZXGI2TENJVGI . You are receiving this because you authored the thread.Message ID: @.***>

trevor68 commented 1 month ago

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

[link removed]

matthuisman commented 1 month ago

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

jamesgallagher commented 1 month ago

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 commented 1 month ago

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.

trevor68 commented 1 month ago

ahhhhh frustrating, I'm watching the olympics live in Chrome, working fine, then, as soon as I add the pipe on the end thats it, 400 bad request error.

[link removed]

jamesgallagher commented 1 month ago

@matthuisman not sure if you know the answer, but figure it might be worth asking. The links contain 2 parameters 'st' and 'exp' which contain timestamps, 'st' looks like start and 'exp' looks like expiry and is exactly 48 hours 'st'. Any chance you happen to know if I can just populate those on a nightly job to keep the links active as I'm sure the token id doesn't change?

jamesgallagher commented 1 month ago

ahhhhh frustrating, I'm watching the olympics live in Chrome, working fine, then, as soon as I add the pipe on the end thats it, 400 bad request error.

I know you are just testing, but if you are, why not ignore Chrome and see if it works on TiVi mate? I wouldn't be phased about Chrome as it will already add your useragent. If you still have issues with the pipe, drop the pipe and just add the user agent through TiVi ,ate playlist options?

matthuisman commented 1 month ago

@trevor68 no. only kodi + tivimate understand the pipe. Itll only work directly into them.... Why overcomplicate everything copy the url from chrome. Add on the |user-agent=.... on the end, put it in a m3u8 file and point tivimate to that file. Done

@jamesgallagher not that easy. there will be a checksum in the url. you can confirm this by attempting to change them If generate checksum over query params doesnt = the checksum on their end then denied

trevor68 commented 1 month ago

another good call, the media player on W11 plays this link fine, no sound, but I suspect thats a codec issue, as it never did have sound. Have added them all to the m3u file and wandering off to test tivimate now, wish me luck.

matthuisman commented 1 month ago

if anyone thinks "ill create a short static url that redirects to the long url and share it with everyone" Well done - you've just thought of the i.mjh.nz service :)

The account you used to create the url will most likely suspended and the stream stop working. So, keep your own links just for yourself and they should keep working :)

trevor68 commented 1 month ago

Totally get it matt, small problem is my links work fine in everything except tivimate, the intended recipient!

[M3U Removed]

matthuisman commented 1 month ago

why do your links contain: chrome-extension://akkncdpkjlfanomlnpmmolafofpnpjgn/player/player.html?site=https://links.cannardy.com/admin/#

Tivimate wont understand that at all....

For you and everyone else, your playlist should be as simple as

#EXTM3U
#EXTINF:-1 tvg-id="mjh-channel-9-nsw",Channel 9
https://csm-e-nineau1-eb.bln1.yospace.com/csm/ext..............|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
#EXTINF:-1 tvg-id="mjh-gem-nsw",9 Gem
https://csm-e-nineau1-eb.bln1.yospace.com/csm/ext..............|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
trevor68 commented 1 month ago

Ah ok, they are the links that the m3u sniffer gave me, I'll try again later.

trevor68 commented 1 month ago

All sorted, just sharing the M3U via SMB now. Worth noting things get interesting if you have 3 instances of Tivimate.

Once another instance uses the link, they all stop working. You then need to force stop them all, then the first one to grab the link works, otherwise 403 errors for all.

LGSAM59 commented 1 month ago

Haha , I just spent an hour or so trying to setup , kept getting 403 errors on Tivimate , I then realized I still had the stream running on my laptop . All good now

ballfam commented 1 month ago

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.

@paul19920801 I'm going to try to fix myu NextPVR over the weekend, but essentially, my plan is to simply post the link into an Extra. I already have extras for Channel 7 and Channel 9 so I can use Streamlink rather than ffmpeg, which seems to work better, so, it's simply a case of replacing Matt's links with the direct link.

It's also easy to test with streamlink, you just need to paster the link into the streamlink command-line and channel 9 should pop up on the screen. If this works, then NextPVR is golden.

There are a bunch of instructions on the NextPVR forums on how to set up extras is you haven't done this before.

keehan-git commented 1 month ago

Hey - question for the experts... I'm successfully using the tokenized urls from m3u sniffer. Thank you to the folks that figured that out! My VPS is in Sydney and I'm directed to the Sydney streams. Looking at the URL there's a "simulcast-syd-ch9-hls" that I thought I might just switch to "bri" for the Brisbane streams but no luck. By chance anyone know how/if I can get streams for the other regions? Thanks!

matthuisman commented 1 month ago

My kodi plugin has the code you can use to request different regions

On Sat, 10 Aug 2024, 15:15 keehan-git, @.***> wrote:

Hey - question for the experts... I'm successfully using the tokenized urls from m3u sniffer. Thank you to the folks that figured that out! My VPS is in Sydney and I'm directed to the Sydney streams. Looking at the URL there's a "simulcast-syd-ch9-hls" that I thought I might just switch to "bri" for the Brisbane streams but no luck. By chance anyone know how/if I can get streams for the other regions? Thanks!

— Reply to this email directly, view it on GitHub https://github.com/matthuisman/i.mjh.nz/issues/116#issuecomment-2278957381, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABPQAKJ36DNHJUP5ZOIEVJLZQWAVXAVCNFSM6AAAAABL3YHZCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENZYHE2TOMZYGE . You are receiving this because you were mentioned.Message ID: @.***>

paul19920801 commented 1 month ago

What's the latest on getting Nine channels back in NextPVR? I wish that NextPVR had the ability to FTA but encrypted channels using either cleartype or Widevine - they can be played back with FFplay with appropriate keys - should be fairly easy to pipe the output in FFplay, to ffmpeg, and then onto NextPVR, but some things are more complicated than they appear - but if encrypted links were supported, it would make my life, so much easier...

matthuisman commented 1 month ago

The 9 channels are not encrypted. The issue is their tokens. Once things cool down, I'll try get a docker container up for a redirect service that can just hook into my kodi plugins (without Kodi) so no need to duplicate all the python

On Sun, 11 Aug 2024, 11:25 paul19920801, @.***> wrote:

What's the latest on getting Nine channels back in NextPVR? I wish that NextPVR had the ability to FTA but encrypted channels using either cleartype or Widevine - they can be played back with FFplay with appropriate keys - should be fairly easy to pipe the output in FFplay, to ffmpeg, and then onto NextPVR, but some things are more complicated than they appear - but if encrypted links were supported, it would make my life, so much easier...

— Reply to this email directly, view it on GitHub https://github.com/matthuisman/i.mjh.nz/issues/116#issuecomment-2282311358, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABPQAKIUFP2VXM7KHSRM2O3ZQ2OP3AVCNFSM6AAAAABL3YHZCGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEOBSGMYTCMZVHA . You are receiving this because you were mentioned.Message ID: @.***>

ballfam commented 1 month ago

What's the latest on getting Nine channels back in NextPVR? I wish that NextPVR had the ability to FTA but encrypted channels using either cleartype or Widevine - they can be played back with FFplay with appropriate keys - should be fairly easy to pipe the output in FFplay, to ffmpeg, and then onto NextPVR, but some things are more complicated than they appear - but if encrypted links were supported, it would make my life, so much easier...

In theory it all works fine with an extra. I got myself a link using 9now.com.au and M3U sniffer, then I ran streamlink with the link and it worked perfectly and popped up VLC to play channel 9. The problem comes when I add it to the extra XML. When I try to rescan the channel, I'm getting a syntax error on the ampersand (&) in the link. I tried quoting it and dereferencing with a backslash, but it doesn't help. I'm going to post something to the NextPVR forums to see if they can help, otherwise I need some way to alias and forward the link so I can type in some short form link in the NextPVR extra and have it forward to the long link with all the special characters (pretty much exactly what Matt's service does). I don't know if there is any way to "alias" links without an HTML server, but if there is, can someone post how to do it.

If I work it out, I'll post.

drspy00 commented 1 month ago

Does anyone know the likelihood this being permanent with 9? The app is very slow on TV, and the kodi addon while better it still takes a while to switch channels. I was hoping its just for the olympics and possibly paralympics. Seems to slow their app down even, it always just switched perfectly. Always prefer using an iptv client and these lists were amazing.

trevor68 commented 1 month ago

All shall be revealed soon I guess, with the olympics finished now.

matthuisman commented 1 month ago

someone said the old urls are back working

Can someone see if this plays: https://9now-livestreams-v2.akamaized.net/prod/simulcast/adl/ch9/hls/r1/index.m3u8

jamesgallagher commented 1 month ago

Doesn't work for me using VLC. I get a 403.

-- logger module started -- main: Running vlc with the default interface. Use 'cvlc' to use vlc without interface. main: playlist is empty access error: HTTP 403 error http error: local stream 1 error: Cancellation (0x8) access error: HTTP 403 error http error: local stream 1 error: Cancellation (0x8) -- logger module stopped --