Overhaul
reuse classes and methods from kill_stream still using old methods for calculations frameworks for using discord or slack from kill_stream using plexapi CONFIG file for url and apikey
This commit is contained in:
parent
7b0befad71
commit
ff0e834b9c
@ -11,23 +11,45 @@ User stats display username and hour, minutes, and seconds of view time
|
||||
Tautulli Settings > Extra Settings > Check - Calculate Total File Sizes [experimental] ...... wait
|
||||
|
||||
"""
|
||||
|
||||
import requests
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
# import json
|
||||
from plexapi.server import CONFIG
|
||||
from datetime import datetime, timedelta, date
|
||||
from requests import Session
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.exceptions import RequestException
|
||||
from operator import itemgetter
|
||||
import time
|
||||
import json
|
||||
import argparse
|
||||
|
||||
|
||||
# EDIT THESE SETTINGS #
|
||||
TAUTULLI_APIKEY = 'xxxxx' # Your Tautulli API key
|
||||
TAUTULLI_URL = 'http://localhost:8181/' # Your Tautulli URL
|
||||
TAUTULLI_URL = ''
|
||||
TAUTULLI_APIKEY = ''
|
||||
TAUTULLI_PUBLIC_URL = '0'
|
||||
|
||||
if not TAUTULLI_URL:
|
||||
TAUTULLI_URL = CONFIG.data['auth'].get('tautulli_baseurl')
|
||||
if not TAUTULLI_APIKEY:
|
||||
TAUTULLI_APIKEY = CONFIG.data['auth'].get('tautulli_apikey')
|
||||
if not TAUTULLI_PUBLIC_URL:
|
||||
TAUTULLI_PUBLIC_URL = CONFIG.data['auth'].get('tautulli_public_url')
|
||||
|
||||
VERIFY_SSL = False
|
||||
|
||||
if TAUTULLI_PUBLIC_URL != '/':
|
||||
# Check to see if there is a public URL set in Tautulli
|
||||
TAUTULLI_LINK = TAUTULLI_PUBLIC_URL
|
||||
else:
|
||||
TAUTULLI_LINK = TAUTULLI_URL
|
||||
|
||||
RICH_TYPE = ['discord', 'slack']
|
||||
|
||||
TAUTULLI_ICON = 'https://github.com/Tautulli/Tautulli/raw/master/data/interfaces/default/images/logo-circle.png'
|
||||
|
||||
SUBJECT_TEXT = "Tautulli Weekly Server, Library, and User Statistics"
|
||||
|
||||
# Notification notifier ID: https://github.com/JonnyWong16/plexpy/blob/master/API.md#notify
|
||||
NOTIFIER_ID = 10 # The email notification notifier ID for Tautulli
|
||||
NOTIFIER_ID = 12 # The email notification notifier ID for Tautulli
|
||||
|
||||
# Remove library element you do not want shown. Logging before exclusion.
|
||||
# SHOW_STAT = 'Shows: {0}, Episodes: {2}'
|
||||
@ -76,86 +98,21 @@ BODY_TEXT = """\
|
||||
# /EDIT THESE SETTINGS #
|
||||
|
||||
|
||||
def get_history(section_id, check_date):
|
||||
# Get the Tautulli history.
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'get_history',
|
||||
'section_id': section_id,
|
||||
'start_date': check_date}
|
||||
|
||||
def utc_now_iso():
|
||||
"""Get current time in ISO format"""
|
||||
utcnow = datetime.utcnow()
|
||||
|
||||
return utcnow.isoformat()
|
||||
|
||||
|
||||
def hex_to_int(value):
|
||||
"""Convert hex value to integer"""
|
||||
try:
|
||||
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
|
||||
response = r.json()
|
||||
# print(json.dumps(response['response']['data'], indent=4, sort_keys=True))
|
||||
res_data = response['response']['data']
|
||||
if res_data['filter_duration'] != '0':
|
||||
return res_data['data']
|
||||
else:
|
||||
pass
|
||||
return int(value, 16)
|
||||
except (ValueError, TypeError):
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_history' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def get_libraries():
|
||||
# Get a list of all libraries on your server.
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'get_libraries'}
|
||||
|
||||
try:
|
||||
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
|
||||
response = r.json()
|
||||
# print(json.dumps(response['response']['data'], indent=4, sort_keys=True))
|
||||
res_data = response['response']['data']
|
||||
return res_data
|
||||
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_libraries' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def get_library_media_info(section_id):
|
||||
# Get a list of all libraries on your server.
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'get_library_media_info',
|
||||
'section_id': section_id}
|
||||
|
||||
try:
|
||||
r = requests.get(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
|
||||
response = r.json()
|
||||
# print(json.dumps(response['response']['data'], indent=4, sort_keys=True))
|
||||
res_data = response['response']['data']
|
||||
return res_data['total_file_size']
|
||||
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'get_library_media_info' request failed: {0}.".format(e))
|
||||
|
||||
|
||||
def send_notification(body_text):
|
||||
# Format notification text
|
||||
try:
|
||||
subject = SUBJECT_TEXT
|
||||
body = body_text
|
||||
except LookupError as e:
|
||||
sys.stderr.write("Unable to substitute '{0}' in the notification subject or body".format(e))
|
||||
return None
|
||||
# Send the notification through Tautulli
|
||||
payload = {'apikey': TAUTULLI_APIKEY,
|
||||
'cmd': 'notify',
|
||||
'notifier_id': NOTIFIER_ID,
|
||||
'subject': subject,
|
||||
'body': body}
|
||||
|
||||
try:
|
||||
r = requests.post(TAUTULLI_URL.rstrip('/') + '/api/v2', params=payload)
|
||||
response = r.json()
|
||||
|
||||
if response['response']['result'] == 'success':
|
||||
sys.stdout.write("Successfully sent Tautulli notification.")
|
||||
else:
|
||||
raise Exception(response['response']['message'])
|
||||
except Exception as e:
|
||||
sys.stderr.write("Tautulli API 'notify' request failed: {0}.".format(e))
|
||||
return None
|
||||
|
||||
|
||||
def sizeof_fmt(num, suffix='B'):
|
||||
@ -175,7 +132,6 @@ def date_split(to_split):
|
||||
|
||||
|
||||
def add_to_dictval(d, key, val):
|
||||
# print(d, key, val)
|
||||
if key not in d:
|
||||
d[key] = val
|
||||
else:
|
||||
@ -184,7 +140,7 @@ def add_to_dictval(d, key, val):
|
||||
|
||||
def daterange(start_date, end_date):
|
||||
for n in range(int((end_date - start_date).days) + 1):
|
||||
yield start_date + datetime.timedelta(n)
|
||||
yield start_date + timedelta(n)
|
||||
|
||||
|
||||
def get_server_stats(date_ranges):
|
||||
@ -196,10 +152,11 @@ def get_server_stats(date_ranges):
|
||||
user_stats_dict = {}
|
||||
|
||||
print('Checking library stats.')
|
||||
for sections in get_libraries():
|
||||
tautulli_server = Tautulli(TAUTULLI_URL.rstrip('/'), TAUTULLI_APIKEY, VERIFY_SSL)
|
||||
for sections in tautulli_server.get_libraries():
|
||||
|
||||
lib_size = get_library_media_info(sections['section_id'])
|
||||
total_size += lib_size
|
||||
library = tautulli_server.get_library_media_info(sections['section_id'])
|
||||
total_size += library['total_file_size']
|
||||
sections_id_lst += [sections['section_id']]
|
||||
|
||||
if sections['section_type'] == 'artist':
|
||||
@ -219,19 +176,14 @@ def get_server_stats(date_ranges):
|
||||
sections_stats_lst += ['<li>{}: {}</li>'.format(sections['section_name'], section_count)]
|
||||
|
||||
print('Checking users stats.')
|
||||
# print(sections_id_lst)
|
||||
for check_date in date_ranges:
|
||||
for section_id in sections_id_lst:
|
||||
# print(check_date, section_id)
|
||||
history = get_history(section_id, check_date)
|
||||
history = tautulli_server.get_history(section_id, check_date)
|
||||
if history:
|
||||
# print(json.dumps(history, indent=4, sort_keys=True))
|
||||
for data in history:
|
||||
# print(data)
|
||||
for data in history['data']:
|
||||
add_to_dictval(user_stats_dict, data['friendly_name'], data['duration'])
|
||||
|
||||
print('{} watched something on {}'.format(' & '.join(set(user_stats_dict.keys())), check_date))
|
||||
# print(json.dumps(user_stats_dict, indent=4, sort_keys=True))
|
||||
for user, duration in sorted(user_stats_dict.items(), key=itemgetter(1), reverse=True):
|
||||
if user not in USER_IGNORE:
|
||||
m, s = divmod(duration, 60)
|
||||
@ -244,13 +196,232 @@ def get_server_stats(date_ranges):
|
||||
# Html formatting. Adding the Capacity to bottom of list.
|
||||
sections_stats_lst += ['<li>Capacity: {}</li>'.format(sizeof_fmt(total_size))]
|
||||
|
||||
# print(sections_stats_lst, user_stats_lst)
|
||||
return (sections_stats_lst, user_stats_lst)
|
||||
|
||||
class Tautulli:
|
||||
def __init__(self, url, apikey, verify_ssl=False, debug=None):
|
||||
self.url = url
|
||||
self.apikey = apikey
|
||||
self.debug = debug
|
||||
|
||||
def main():
|
||||
self.session = Session()
|
||||
self.adapters = HTTPAdapter(max_retries=3,
|
||||
pool_connections=1,
|
||||
pool_maxsize=1,
|
||||
pool_block=True)
|
||||
self.session.mount('http://', self.adapters)
|
||||
self.session.mount('https://', self.adapters)
|
||||
|
||||
global BODY_TEXT
|
||||
# Ignore verifying the SSL certificate
|
||||
if verify_ssl is False:
|
||||
self.session.verify = False
|
||||
# Disable the warning that the request is insecure, we know that...
|
||||
import urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
|
||||
def get_library_media_info(self, section_id):
|
||||
"""Call Tautulli's get_activity api endpoint"""
|
||||
payload = {}
|
||||
payload['section_id'] = section_id
|
||||
|
||||
return self._call_api('get_library_media_info', payload)
|
||||
|
||||
def get_libraries(self):
|
||||
"""Call Tautulli's get_activity api endpoint"""
|
||||
payload = {}
|
||||
|
||||
return self._call_api('get_libraries', payload)
|
||||
|
||||
def get_history(self, section_id, check_date):
|
||||
"""Call Tautulli's get_activity api endpoint"""
|
||||
payload = {}
|
||||
payload['section_id'] = int(section_id)
|
||||
payload['start_date'] = check_date
|
||||
|
||||
return self._call_api('get_history', payload)
|
||||
|
||||
def notify(self, notifier_id, subject, body):
|
||||
"""Call Tautulli's notify api endpoint"""
|
||||
payload = {'notifier_id': notifier_id,
|
||||
'subject': subject,
|
||||
'body': body}
|
||||
|
||||
return self._call_api('notify', payload)
|
||||
|
||||
def _call_api(self, cmd, payload, method='GET'):
|
||||
payload['cmd'] = cmd
|
||||
payload['apikey'] = self.apikey
|
||||
|
||||
try:
|
||||
response = self.session.request(method, self.url + '/api/v2', params=payload)
|
||||
except RequestException as e:
|
||||
print("Tautulli request failed for cmd '{}'. Invalid Tautulli URL? Error: {}".format(cmd, e))
|
||||
return
|
||||
|
||||
try:
|
||||
response_json = response.json()
|
||||
except ValueError:
|
||||
print(
|
||||
"Failed to parse json response for Tautulli API cmd '{}': {}"
|
||||
.format(cmd, response.content))
|
||||
return
|
||||
|
||||
if response_json['response']['result'] == 'success':
|
||||
if self.debug:
|
||||
print("Successfully called Tautulli API cmd '{}'".format(cmd))
|
||||
return response_json['response']['data']
|
||||
else:
|
||||
error_msg = response_json['response']['message']
|
||||
print("Tautulli API cmd '{}' failed: {}".format(cmd, error_msg))
|
||||
return
|
||||
|
||||
|
||||
class Notification:
|
||||
def __init__(self, notifier_id, subject, body, tautulli, user=None):
|
||||
self.notifier_id = notifier_id
|
||||
self.subject = subject
|
||||
self.body = body
|
||||
|
||||
self.tautulli = tautulli
|
||||
if user:
|
||||
self.user = user
|
||||
|
||||
def send(self, subject='', body=''):
|
||||
"""Send to Tautulli notifier.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
subject : str
|
||||
Subject of the message.
|
||||
body : str
|
||||
Body of the message.
|
||||
"""
|
||||
subject = subject or self.subject
|
||||
body = body or self.body
|
||||
self.tautulli.notify(notifier_id=self.notifier_id, subject=subject, body=body)
|
||||
|
||||
def send_discord(self, title, color, poster_url, plex_url, message, footer):
|
||||
"""Build the Discord message.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
title : str
|
||||
The title of the message.
|
||||
color : int
|
||||
The color of the message
|
||||
poster_url : str
|
||||
The media poster URL.
|
||||
plex_url : str
|
||||
Plex media URL.
|
||||
message : str
|
||||
Message sent to the player.
|
||||
footer : str
|
||||
Footer of the message.
|
||||
"""
|
||||
discord_message = {
|
||||
"embeds": [
|
||||
{
|
||||
"author": {
|
||||
"icon_url": TAUTULLI_ICON,
|
||||
"name": "Tautulli",
|
||||
"url": TAUTULLI_LINK.rstrip('/')
|
||||
},
|
||||
"color": color,
|
||||
"fields": [
|
||||
{
|
||||
"inline": True,
|
||||
"name": "User",
|
||||
"value": self.user.friendly_name
|
||||
},
|
||||
{
|
||||
"inline": True,
|
||||
"name": "Watched",
|
||||
"value": self.user.stats
|
||||
},
|
||||
{
|
||||
"inline": False,
|
||||
"name": "Message Sent",
|
||||
"value": message
|
||||
}
|
||||
],
|
||||
"thumbnail": {
|
||||
"url": poster_url
|
||||
},
|
||||
"title": title,
|
||||
"timestamp": utc_now_iso(),
|
||||
"url": plex_url,
|
||||
"footer": {
|
||||
"text": footer
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
}
|
||||
|
||||
discord_message = json.dumps(discord_message, sort_keys=True,
|
||||
separators=(',', ': '))
|
||||
self.send(body=discord_message)
|
||||
|
||||
def send_slack(self, title, color, poster_url, plex_url, message, footer):
|
||||
"""Build the Slack message.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
title : str
|
||||
The title of the message.
|
||||
color : int
|
||||
The color of the message
|
||||
poster_url : str
|
||||
The media poster URL.
|
||||
plex_url : str
|
||||
Plex media URL.
|
||||
message : str
|
||||
Message sent to the player.
|
||||
footer : str
|
||||
Footer of the message.
|
||||
"""
|
||||
slack_message = {
|
||||
"attachments": [
|
||||
{
|
||||
"title": title,
|
||||
"title_link": plex_url,
|
||||
"author_icon": TAUTULLI_ICON,
|
||||
"author_name": "Tautulli",
|
||||
"author_link": TAUTULLI_LINK.rstrip('/'),
|
||||
"color": color,
|
||||
"fields": [
|
||||
{
|
||||
"title": "User",
|
||||
"value": self.user.friendly_name,
|
||||
"short": True
|
||||
},
|
||||
{
|
||||
"title": "Watched",
|
||||
"value": self.user.stats,
|
||||
"short": True
|
||||
},
|
||||
{
|
||||
"title": "Message Sent",
|
||||
"value": message,
|
||||
"short": False
|
||||
}
|
||||
],
|
||||
"thumb_url": poster_url,
|
||||
"footer": footer,
|
||||
"ts": time.time()
|
||||
}
|
||||
|
||||
],
|
||||
}
|
||||
|
||||
slack_message = json.dumps(slack_message, sort_keys=True,
|
||||
separators=(',', ': '))
|
||||
self.send(body=slack_message)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser(description="Use Tautulli to pull library and user statistics for date range.",
|
||||
formatter_class=argparse.RawTextHelpFormatter)
|
||||
@ -262,23 +433,21 @@ def main():
|
||||
TODAY = int(time.time())
|
||||
DAYS = opts.days
|
||||
DAYS_AGO = int(TODAY - DAYS * 24 * 60 * 60)
|
||||
START_DATE = (datetime.datetime.utcfromtimestamp(DAYS_AGO).strftime("%Y-%m-%d")) # DAYS_AGO as YYYY-MM-DD
|
||||
END_DATE = (datetime.datetime.utcfromtimestamp(TODAY).strftime("%Y-%m-%d")) # TODAY as YYYY-MM-DD
|
||||
START_DATE = (datetime.utcfromtimestamp(DAYS_AGO).strftime("%Y-%m-%d")) # DAYS_AGO as YYYY-MM-DD
|
||||
END_DATE = (datetime.utcfromtimestamp(TODAY).strftime("%Y-%m-%d")) # TODAY as YYYY-MM-DD
|
||||
|
||||
start_date = datetime.date(date_split(START_DATE)[0], date_split(START_DATE)[1], date_split(START_DATE)[2])
|
||||
end_date = datetime.date(date_split(END_DATE)[0], date_split(END_DATE)[1], date_split(END_DATE)[2])
|
||||
start_date = date(date_split(START_DATE)[0], date_split(START_DATE)[1], date_split(START_DATE)[2])
|
||||
end_date = date(date_split(END_DATE)[0], date_split(END_DATE)[1], date_split(END_DATE)[2])
|
||||
|
||||
dates_range_lst = []
|
||||
for single_date in daterange(start_date, end_date):
|
||||
dates_range_lst += [single_date.strftime("%Y-%m-%d")]
|
||||
|
||||
print('Checking user stats from {:02d} days ago.'.format(opts.days))
|
||||
|
||||
lib_stats, user_stats_lst = get_server_stats(dates_range_lst)
|
||||
# print(lib_stats)
|
||||
|
||||
end = datetime.datetime.strptime(time.ctime(float(TODAY)), "%a %b %d %H:%M:%S %Y").strftime("%a %b %d %Y")
|
||||
start = datetime.datetime.strptime(time.ctime(float(DAYS_AGO)), "%a %b %d %H:%M:%S %Y").strftime("%a %b %d %Y")
|
||||
end = datetime.strptime(time.ctime(float(TODAY)), "%a %b %d %H:%M:%S %Y").strftime("%a %b %d %Y")
|
||||
start = datetime.strptime(time.ctime(float(DAYS_AGO)), "%a %b %d %H:%M:%S %Y").strftime("%a %b %d %Y")
|
||||
|
||||
sections_stats = "\n".join(lib_stats)
|
||||
user_stats = "\n".join(user_stats_lst)
|
||||
@ -286,8 +455,6 @@ def main():
|
||||
BODY_TEXT = BODY_TEXT.format(end=end, start=start, sections_stats=sections_stats, user_stats=user_stats)
|
||||
|
||||
print('Sending message.')
|
||||
send_notification(BODY_TEXT)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
tautulli_server = Tautulli(TAUTULLI_URL.rstrip('/'), TAUTULLI_APIKEY, VERIFY_SSL)
|
||||
notify = Notification(NOTIFIER_ID, SUBJECT_TEXT, BODY_TEXT, tautulli_server)
|
||||
notify.send()
|
||||
|
Loading…
Reference in New Issue
Block a user