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:
blacktwin 2020-02-13 09:30:57 -05:00
parent 7b0befad71
commit ff0e834b9c

View File

@ -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()