aboutsummaryrefslogtreecommitdiff
path: root/jellystack_lib.py
blob: 5c5c132ac857752ff8dd144fbf1c37e4cb08fdc3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/usr/bin/env python3
# File: jellystack_lib.py
# Location: /mnt/public/Support/Programs/jellyfin/scripts/
# Author: bgstack15
# Startdate: 2024-01-20-7 13:59
# SPDX-License-Identifier: GPL-3.0-only
# Title: jellyfin stackrpms library
# Project: jellystack
# Purpose: Useful functions that use python3 jellyfin apiclient
# History:
#    2024-01 started for listing/rescanning libraries
# Usage:
#    in jellystack.py
#    client.auth.login("vm4:8096","admin","0EXAMPLE0f93EXAMPLEXAMPLE534e0f6")
# Reference:
#    from firefox devtools:
#    curl 'https://jellyfin.example.com:500/Items/14895ee3d991844aee62d94dd46dfb7a/Refresh?Recursive=true&ImageRefreshMode=Default&MetadataRefreshMode=Default&ReplaceAllImages=false&ReplaceAllMetadata=false' -X POST -H 'X-Emby-Authorization: MediaBrowser Client="Jellyfin Web", Device="Firefox", DeviceId="TW96aWxsYS81LjAgKFgxMTsgTGlEXAMPLEg2XzY0OyBydjo5MS4wKSBHZWNEXAMPLEEwMDEwMSBGaXJlZm94LzkxLjB8MTYzODk3NjExMzkwMA11", Version="10.8.10", Token="0f6f671c4eEXAMPLEc219aEXAMPLE7b3" -H 'Content-Length: 0'
#    https://jmshrv.com/posts/jellyfin-api/
#    swagger interface at https://jellyfin.example.com:500/api-docs/swagger/
# Improve:
# Dependencies:
#    req-devuan: jellyfin-apiclient-python

from jellyfin_apiclient_python import JellyfinClient
import json, os, sys

def get_authenticated_client(url = "http://vm4:8096", username = "admin", password = "None"):
   client = JellyfinClient()
   # this one works basically, but somehow my previous attempt did not.
   client.config.app('cli','0.0.1','any-ssh-node','a56decad32c0aefd696a7d3565ac1d0')
   client.config.data["auth.ssl"] = False
   client.auth.connect_to_address(url)
   client.auth.login(url,username,password)
   credentials = client.auth.credentials.get_credentials()
   server = credentials["Servers"][0]
   server["username"] = 'username'
   json.dumps(server)
   #json.loads(credentials)
   #client.authenticate({"Servers": [credentials]}, discover=False)
   return client

def get_media_folders(client):
   results = client.jellyfin.get_media_folders()["Items"]
   libraries = [{"Name":i["Name"],"Id":i["Id"]} for i in results]
   return libraries

def libraries_to_csv(libraries):
   for i in libraries:
      print(f"\"{i['Name']}\" {i['Id']}")

def get_library_names_only(libraries):
   return [i["Name"] for i in libraries]

def refresh_library(library, libraries, client):
   # find id of given library
   _use_id = ""
   for i in libraries:
      if i["Id"] == library:
         _use_id = library
         break
      elif i["Name"] == library:
         _use_id = i["Id"]
         break
   # if not found, fail out
   if _use_id == "":
      raise f"Unable to find which library, given {library}"
   print(f"Will try to refresh library {_use_id}")
   client.http.request(
      {
         "type": "POST",
         "url": f"{client.config.data['auth.server']}/Items/{_use_id}/Refresh?Recursive=true&ImageRefreshMode=Default&MetadataRefreshMode=Default&ReplaceAllImages=false&ReplaceAllMetadata=false"
      }
   )

def is_like_id(input_id):
   """ Given input_id, if it is a 32-char hexadecimal value, return true, else false. """
   try:
      int(input_id, 16)
   except ValueError as e:
      return False
   return len(input_id) == 32

def watched_episodes_for_show(client, library_id_or_name, show_id_or_name, season_id = -1, verbose = False):
   """
   Given the show_id_or_name (show_id from the user's view in jellyfin, or exact name), return a list of the episode count watched and total episode count. If season_id is not defined (-1), then do it for all seasons. The episode-watched number is dependent on who logged in to jellyfin.
   Season_id may also be string "sum" which will just produce a single 100/110 fraction in the output string.
   If you cannot find the exact name to use, pass verbose=True to see what it is comparing against.
   WARNING: Seasons are zero-indexed, but specials (if present in your library) are number zero!
   Improve: research if admin user can look at other users' views.
   Examples:

      a = jellystack_lib.get_authenticated_client(url="http://vm4:8096",username="jellyfin",password="KEEPASS")
      jellystack_lib.watched_episodes_for_show(a,"TV","Star Trek: Strange New Worlds",-1)
      ['0/17', '2/10', '0/10']

   """
   view_library_id = None
   view_series_id = None
   # validate library_id_or_name
   if library_id_or_name.isdigit():
      view_library_id = library_id_or_name
   else:
      views = client.jellyfin.get_views()
      for i in views["Items"]:
         if i["Name"] == library_id_or_name:
            view_library_id = i["Id"]
            if verbose:
               print(f"verbose: {library_id_or_name} matches {i['Id']}", file=sys.stderr)
            break
         elif verbose:
            print(f"verbose: {library_id_or_name} does not match {i['Name']}", file=sys.stderr)
   if view_library_id is None:
      return "Failed to find library matching {library_id_or_name}."
   # validate show_id_or_name
   if is_like_id(show_id_or_name):
      view_series_id = show_id_or_name
   else:
      library_items = client.http.request({"type":"GET","url":f"{client.config.data['auth.server']}/Users/{client.jellyfin.users()['Id']}/Items?ParentId={view_library_id}"})
      for i in library_items["Items"]:
         if i["Name"] == show_id_or_name:
            view_series_id = i["Id"]
            if verbose:
               print(f"verbose: {show_id_or_name} matches {i['Id']}", file=sys.stderr)
            break
         elif verbose:
            print(f"verbose: {show_id_or_name} does not match {i['Name']}", file=sys.stderr)
   if view_series_id is None:
      return f"Failed to find show matching {show_id_or_name}."
   # get season count
   seasons = client.jellyfin.get_seasons(view_series_id)
   season_count = seasons["TotalRecordCount"]
   if season_id != -1 and season_id.isdigit():
      # a specific season
      return _watched_episodes_for_season(client, seasons["Items"][season_id]["Id"], season_id)
   else:
      x = 0
      # Improvement: are lists ordered in python? Is .append() good enough for order of seasons?
      response = []
      while x < season_count:
         response.append(_watched_episodes_for_season(client, seasons["Items"][x]["Id"], x))
         x += 1
   if season_id == "sum":
      sumx = 0
      sumy = 0
      for i in response:
         x, y = i.split("/")
         sumx += int(x)
         sumy += int(y)
      return f"{sumx}/{sumy}"
   else:
      return response

def _watched_episodes_for_season(client, view_season_id, season_index):
   y = 0
   watched = 0
   season_info = client.http.request({"type":"GET","url":f"{client.config.data['auth.server']}/Users/{client.jellyfin.users()['Id']}/Items?ParentId={view_season_id}"})
   while y < season_info["TotalRecordCount"]:
      watched += season_info["Items"][y]["UserData"]["Played"]
      y += 1
   return f"{watched}/{y}"
bgstack15