dave-p / TVH-API-docs

User-created documentation for the TVHeadend HTTP API
Other
64 stars 6 forks source link

No access to TVH api through python's requests #6

Closed TheUnityGuy closed 1 year ago

TheUnityGuy commented 1 year ago

Every request made with python ends Unauthorized 401:

query='%s/%s' % ("http://192.168.0.150:9981", "api/status/subscriptions")
req = requests.get(query, auth=("XXXXXXXXX", "XXXXXXXX"))
print("TVH:", req.content)

which returns:

[DEBUG  ] [Starting new HTTP connection (1)] 192.168.0.150:9981
[DEBUG  ] [http          ]//192.168.0.150:9981 "GET /api/status/subscriptions HTTP/1.1" 401 250
TVH: b'<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">\r\n<HTML><HEAD>\r\n<TITLE>401 Unauthorized</TITLE>\r\n</HEAD><BODY>\r\n<H1>401 Unauthorized</H1>\r\n<P STYLE="text-align: center; margin: 2em"><A HREF="/" STYLE="border: 1px solid; border-radius: 4px; padding: .6em">Default login</A></P><P STYLE="text-align: center; margin: 2em"><A HREF="/login" STYLE="border: 1px solid; border-radius: 4px; padding: .6em">New login</A></P></BODY></HTML>\r\n'

Using default requests way to auth, exactly the same as in https://github.com/dave-p/TVH-API-docs/wiki/Examples. And for sure, I am putting correct credentials 😅. Requests made using browser work flawlessly.

Version of TVH: {"sw_version":"4.3-1994~gc7b713edb","api_version":19,"name":"Tvheadend","capabilities":["caclient","tvadapters","satip_client","satip_server","trace"]}

dave-p commented 1 year ago

Have you tried running the script in the Examples, just changing the credentials and IP address? That script works for me.

The API function 'api/status/subscriptions' requires ADMIN privilege to work.

Actually I think there may be a mistake in the description of the error messages. If I try to run the example Python script with a deliberately wrong username or password I get a 403 error, not 401. I'll check the code later.

TheUnityGuy commented 1 year ago

Just tested that script, and unfortunately i'm still getting 401 Unauthorized. Full output:

<!DOCTYPE html>
<html>
 <head>
  <title>TVH Python test</title>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <style>
    table, th, td {
      border: 1px solid black;
    }
  </style>
 </head>
 <body>
  <table>
   <tr><th>Date</th><th>Start</th><th>End</th><th>Title</th></tr>

<pre>Error code 401
b'<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">\r\n<HTML><HEAD>\r\n<TITLE>401 Unauthorized</TITLE>\r\n</HEAD><BODY>\r\n<H1>401 Unauthorized</H1>\r\n<P STYLE="text-align: center; margin: 2em"><A HREF="/" STYLE="border: 1px solid; border-radius: 4px; padding: .6em">Default login</A></P><P STYLE="text-align: center; margin: 2em"><A HREF="/login" STYLE="border: 1px solid; border-radius: 4px; padding: .6em">New login</A></P></BODY></HTML>\r\n'</pre>

  </table>
 </body>
</html>

That script works for me.

Something must be wrong with my TVH then...

TheUnityGuy commented 1 year ago

While looking for fix, I tried connecting with curl, it also doesn't work... so right now only the browser is working. Maybe there's a problem with http headers...

TheUnityGuy commented 1 year ago

Working browser's headers: sample

Well, digest authorization might be a problem.

TheUnityGuy commented 1 year ago

Alright, it's not (at least for python). Add "auth=requests.auth.HTTPDigestAuth(ts_user, ts_pass)" to an example script and everything work as intended. Updated python example should look like:

import json
import time
import requests

def get_timers():
    ts_server = 'http://192.168.0.1:9981'
    ts_url = 'api/dvr/entry/grid_upcoming?sort=start'
    ts_user = 'admin'
    ts_pass = 'admin'
    ts_query = '%s/%s' % (
        ts_server,
        ts_url,
    )
    ts_response = requests.get(ts_query, auth=requests.auth.HTTPDigestAuth(ts_user, ts_pass))
    if ts_response.status_code != 200:
        print('<pre>Error code %d\n%s</pre>' % (ts_response.status_code, ts_response.content, ))
        return {}

    ts_json = json.loads(ts_response.text, strict=False)
    return ts_json['entries']

def main():
    print('''
<!DOCTYPE html>
<html>
 <head>
  <title>TVH Python test</title>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <style>
    table, th, td {
      border: 1px solid black;
    }
  </style>
 </head>
 <body>
  <table>
   <tr><th>Date</th><th>Start</th><th>End</th><th>Title</th></tr>
''')

    timers = get_timers()
    if len(timers):
        for timer in timers:
            start = time.localtime(timer['start'])
            print('<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>'
                % (time.strftime("%a %e/%m",start),
                   time.strftime("%H:%M",start),
                   time.strftime("%H:%M",time.localtime(timer['stop'])),
                   timer['disp_title'],
                ))

    print('''
  </table>
 </body>
</html>
''')

main()
dave-p commented 1 year ago

Well spotted.

At the top of the Examples page is a note: "These examples (and any other use of the API which passes username and password in the URL) will only work if TVheadend is configured to use 'basic' or 'basic and digest' authentication". Which I'd forgotten about.

I'll add your digest auth version to the examples page and check whether my description of the HTTP error codes is correct.

speculatrix commented 1 year ago

here's a python example, showing that you have to decide whether you're using basic or digest auth

https://github.com/speculatrix/tvh_radio/blob/62d381c1af12152cee8b81a048871d49facfc49d/tvh_radio.py#L466