Merge pull request #130 from DirtyCajunRice/master

Lots of Updates
This commit is contained in:
blacktwin 2019-01-02 21:39:39 -05:00 committed by GitHub
commit 472e438967
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 174 additions and 70 deletions

View File

@ -1,13 +1,13 @@
"""
Description: Use conditions to kill a stream
Author: Blacktwin, Arcanemagus, Samwiseg00, JonnyWong16
Author: Blacktwin, Arcanemagus, Samwiseg0, JonnyWong16, DirtyCajunRice
Adding the script to Tautulli:
Taultulli > Settings > Notification Agents > Add a new notification agent >
Tautulli > Settings > Notification Agents > Add a new notification agent >
Script
Configuration:
Taultulli > Settings > Notification Agents > New Script > Configuration:
Tautulli > Settings > Notification Agents > New Script > Configuration:
Script Folder: /path/to/your/scripts
Script File: ./kill_stream.py (Should be selectable in a dropdown list)
@ -16,19 +16,19 @@ Taultulli > Settings > Notification Agents > New Script > Configuration:
Save
Triggers:
Taultulli > Settings > Notification Agents > New Script > Triggers:
Tautulli > Settings > Notification Agents > New Script > Triggers:
Check: Playback Start and/or Playback Pause
Save
Conditions:
Taultulli > Settings > Notification Agents > New Script > Conditions:
Tautulli > Settings > Notification Agents > New Script > Conditions:
Set Conditions: [{condition} | {operator} | {value} ]
Save
Script Arguments:
Taultulli > Settings > Notification Agents > New Script > Script Arguments:
Tautulli > Settings > Notification Agents > New Script > Script Arguments:
Select: Playback Start, Playback Pause
Arguments: --jbop SELECTOR --userId {user_id} --username {username}
@ -46,11 +46,13 @@ Taultulli > Settings > Notification Agents > New Script > Script Arguments:
import os
import sys
import argparse
import json
import time
import argparse
from datetime import datetime
import requests
from requests import Session
from requests.adapters import HTTPAdapter
from requests.exceptions import RequestException
TAUTULLI_URL = ''
@ -109,13 +111,15 @@ def debug_dump_vars():
.rjust(len(TAUTULLI_APIKEY), "x"))
def get_all_streams(user_id=None):
def get_all_streams(tautulli, user_id=None):
"""Get a list of all current streams.
Parameters
----------
user_id : int
The ID of the user to grab sessions for.
tautulli : obj
Tautulli object.
Returns
-------
objects
@ -131,17 +135,17 @@ def get_all_streams(user_id=None):
return streams
def notify(opts, message, kill_type=None, stream=None):
def notify(all_opts, message, kill_type=None, stream=None, tautulli=None):
"""Decides which notifier type to use"""
if opts.notify and opts.richMessage:
rich_notify(opts.notify, opts.richMessage, opts.richColor, kill_type,
opts.serverName, opts.plexUrl, opts.posterUrl, message, stream)
elif opts.notify:
basic_notify(opts.notify, opts.sessionId, opts.username, message)
if all_opts.notify and all_opts.richMessage:
rich_notify(all_opts.notify, all_opts.richMessage, all_opts.richColor, kill_type,
all_opts.serverName, all_opts.plexUrl, all_opts.posterUrl, message, stream, tautulli)
elif all_opts.notify:
basic_notify(all_opts.notify, all_opts.sessionId, all_opts.username, message, stream, tautulli)
def rich_notify(notifier_id, rich_type, color=None, kill_type=None, server_name=None,
plex_url=None, poster_url=None, message=None, stream=None):
plex_url=None, poster_url=None, message=None, stream=None, tautulli=None):
"""Decides which rich notifier type to use. Set default values for empty variables
Parameters
@ -150,6 +154,8 @@ def rich_notify(notifier_id, rich_type, color=None, kill_type=None, server_name=
The ID of the user to grab sessions for.
rich_type : str
Contains 'discord' or 'slack'.
color : Union[int, str]
Hex string or integer representation of color.
kill_type : str
The kill type used.
server_name : str
@ -162,22 +168,26 @@ def rich_notify(notifier_id, rich_type, color=None, kill_type=None, server_name=
Message sent to the client.
stream : obj
Stream object.
tautulli : obj
Tautulli object.
"""
notification = Notification(notifier_id, SUBJECT_TEXT, BODY_TEXT, tautulli, stream)
# Initialize Variables
title = ''
footer = ''
# Set a default server_name if none is provided
if server_name is None:
server_name = 'Plex Server'
# Set a defult color if none is provided
# Set a default color if none is provided
if color is None:
color = '#E5A00D'
# Set a defult plexUrl if none is provided
# Set a default plexUrl if none is provided
if plex_url is None:
plex_url = 'https://app.plex.tv'
# Set a defult posterUrl if none is provided
# Set a default posterUrl if none is provided
if poster_url is None:
poster_url = TAUTULLI_ICON
@ -207,7 +217,7 @@ def rich_notify(notifier_id, rich_type, color=None, kill_type=None, server_name=
notification.send_slack(title, color, poster_url, plex_url, message, footer)
def basic_notify(notifier_id, session_id, username=None, message=None):
def basic_notify(notifier_id, session_id, username=None, message=None, stream=None, tautulli=None):
"""Basic notifier"""
notification = Notification(notifier_id, SUBJECT_TEXT, BODY_TEXT, tautulli, stream)
@ -225,11 +235,11 @@ class Tautulli:
self.apikey = apikey
self.debug = debug
self.session = requests.Session()
self.adapters = requests.adapters.HTTPAdapter(max_retries=3,
pool_connections=1,
pool_maxsize=1,
pool_block=True)
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)
@ -240,14 +250,14 @@ class Tautulli:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def _call_api(self, cmd, payload, method='GET', debug=None):
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:
print("Tautulli request failed for cmd '{}'. Invalid Tautulli URL?".format(cmd))
except RequestException as e:
print("Tautulli request failed for cmd '{}'. Invalid Tautulli URL? Error: {}".format(cmd, e))
if self.debug:
traceback.print_exc()
return
@ -303,11 +313,12 @@ class Tautulli:
class Stream:
def __init__(self, session_id=None, user_id=None, username=None, tautulli=None, session=None):
self.state = None
self.ip_address = None
self.session_id = session_id
self.user_id = user_id
self.username = username
self.session_exists = False
self.tautulli = tautulli
if session is not None:
@ -579,38 +590,38 @@ if __name__ == "__main__":
sys.exit(1)
if opts.debug:
# Import traceback to get more deatiled information
# Import traceback to get more detailed information
import traceback
# Dump the ENVs passed from tatutulli
# Dump the ENVs passed from tautulli
debug_dump_vars()
# Create a Tautulli instance
tautulli = Tautulli(TAUTULLI_URL.rstrip('/'), TAUTULLI_APIKEY, VERIFY_SSL, opts.debug)
tautulli_server = Tautulli(TAUTULLI_URL.rstrip('/'), TAUTULLI_APIKEY, VERIFY_SSL, opts.debug)
# Create initial Stream object with basic info
stream = Stream(opts.sessionId, opts.userId, opts.username, tautulli)
tautulli_stream = Stream(opts.sessionId, opts.userId, opts.username, tautulli_server)
# Only pull all stream info if using richMessage
if opts.notify and opts.richMessage:
stream.get_all_stream_info()
tautulli_stream.get_all_stream_info()
# Set a defult message if none is provided
# Set a default message if none is provided
if opts.killMessage:
message = ' '.join(opts.killMessage)
kill_message = ' '.join(opts.killMessage)
else:
message = 'The server owner has ended the stream.'
kill_message = 'The server owner has ended the stream.'
if opts.jbop == 'stream':
stream.terminate(message)
notify(opts, message, 'Stream', stream)
tautulli_stream.terminate(kill_message)
notify(opts, kill_message, 'Stream', tautulli_stream, tautulli_server)
elif opts.jbop == 'allStreams':
streams = get_all_streams(opts.userId)
for stream in streams:
tautulli.terminate_session(session_id=stream.session_id, message=message)
notify(opts, message, 'All Streams', stream)
all_streams = get_all_streams(tautulli_server, opts.userId)
for a_stream in all_streams:
tautulli_server.terminate_session(session_id=a_stream.session_id, message=kill_message)
notify(opts, kill_message, 'All Streams', a_stream, tautulli_server)
elif opts.jbop == 'paused':
killed_stream = stream.terminate_long_pause(message, opts.limit, opts.interval)
killed_stream = tautulli_stream.terminate_long_pause(kill_message, opts.limit, opts.interval)
if killed_stream:
notify(opts, message, 'Paused', stream)
notify(opts, kill_message, 'Paused', tautulli_stream, tautulli_server)

View File

@ -6,7 +6,10 @@ Killing streams is a Plex Pass only feature. So these scripts will **only** work
### Kill transcodes
Triggers: Playback Start
Triggers:
* Playback Start
* Transcode Decision Change
Conditions: \[ `Transcode Decision` | `is` | `transcode` \]
Arguments:
@ -59,6 +62,18 @@ Arguments:
--jbop stream --username {username} --sessionId {session_id} --killMessage 'You are only allowed 3 streams.'
```
### Limit User streams to one unique IP
Triggers: Playback Start
Settings:
* Notifications & Newsletters > Show Advanced > `User Concurrent Streams Notifications by IP Address` | `Checked`
* Notifications & Newsletters > `User Concurrent Stream Threshold` | `2`
Arguments:
```
--jbop stream --username {username} --sessionId {session_id} --killMessage 'You are only allowed to stream from one location at a time.'
```
### IP Whitelist
Triggers: Playback Start
@ -81,7 +96,10 @@ Arguments:
### Kill transcode by library
Triggers: Playback Start
Triggers:
* Playback Start
* Transcode Decision Change
Conditions:
* \[ `Transcode Decision` | `is` | `transcode` \]
* \[ `Library Name` | `is` | `4K Movies` \]
@ -93,7 +111,10 @@ Arguments:
### Kill transcode by original resolution
Triggers: Playback Start
Triggers:
* Playback Start
* Transcode Decision Change
Conditions:
* \[ `Transcode Decision` | `is` | `transcode` \]
* \[ `Video Resolution` | `is` | `1080 or 720`\]
@ -105,7 +126,10 @@ Arguments:
### Kill transcode by bitrate
Triggers: Playback Start
Triggers:
* Playback Start
* Transcode Decision Change
Conditions:
* \[ `Transcode Decision` | `is` | `transcode` \]
* \[ `Bitrate` | `is greater than` | `4000` \]
@ -137,7 +161,10 @@ Arguments:
### Kill transcodes and send a notification to agent 1
Triggers: Playback Start
Triggers:
* Playback Start
* Transcode Decision Change
Conditions: \[ `Transcode Decision` | `is` | `transcode` \]
Arguments:
@ -147,7 +174,10 @@ Arguments:
### Kill transcodes using the default message
Triggers: Playback Start
Triggers:
* Playback Start
* Transcode Decision Change
Conditions: \[ `Transcode Decision` | `is` | `transcode` \]
Arguments:
@ -211,3 +241,11 @@ Tautulli > Script Agent > Script > Tautulli > Webhook Agent > Discord/Slack
### Debug
Add `--debug` to enable debug logging.
### Conditions considerations
#### Kill transcode variants
All examples use \[ `Transcode Decision` | `is` | `transcode` \] which will kill any variant of transcoding.
If you want to allow audio or container transcoding and only drop video transcodes, your condition would change to
\[ `Video Decision` | `is` | `transcode` \]

View File

@ -3,4 +3,5 @@
# pip install -r requirements.txt
#---------------------------------------------------------
requests
plexapi
plexapi
urllib3

View File

@ -0,0 +1,48 @@
#!/usr/bin/env python
"""
Description: Enable or disable all users remote access to Tautulli
Author: DirtyCajunRice
Requires: requests, python3.6+
"""
from requests import Session
from json.decoder import JSONDecodeError
ENABLE_REMOTE_ACCESS = True
TAUTULLI_URL = ''
TAUTULLI_API_KEY = ''
# Do not edit past this line #
session = Session()
session.params = {'apikey': TAUTULLI_API_KEY}
formatted_url = f'{TAUTULLI_URL}/api/v2'
request = session.get(formatted_url, params={'cmd': 'get_users'})
tautulli_users = None
try:
tautulli_users = request.json()['response']['data']
except JSONDecodeError:
exit("Error talking to Tautulli API, please check your TAUTULLI_URL")
allow_guest = 1 if ENABLE_REMOTE_ACCESS else 0
string_representation = 'Enabled' if ENABLE_REMOTE_ACCESS else 'Disabled'
users_to_change = [user for user in tautulli_users if user['allow_guest'] != allow_guest]
if users_to_change:
for user in users_to_change:
# Redefine ALL params because of Tautulli edit_user API bug
params = {
'cmd': 'edit_user',
'user_id': user['user_id'],
'friendly_name': user['friendly_name'],
'custom_thumb': user['custom_thumb'],
'keep_history': user['keep_history'],
'allow_guest': allow_guest
}
changed_user = session.get(formatted_url, params=params)
print(f"{string_representation} guest access for {user['friendly_name']}")
else:
print(f'No users to {string_representation.lower()[:-1]}')

View File

@ -2,13 +2,14 @@
"""
Description: Purge Tautulli users that no longer exist as a friend in Plex
Author: DirtyCajunRice
Requires: requests, plexapi
Requires: requests, plexapi, python3.6+
"""
import requests
from requests import Session
from json.decoder import JSONDecodeError
from plexapi.myplex import MyPlexAccount
TAUTULLI_BASE_URL = ''
TAUTULLI_URL = ''
TAUTULLI_API_KEY = ''
PLEX_USERNAME = ''
@ -20,22 +21,27 @@ BACKUP_DB = True
# Do not edit past this line #
account = MyPlexAccount(PLEX_USERNAME, PLEX_PASSWORD)
payload = {'apikey': TAUTULLI_API_KEY, 'cmd': 'get_user_names'}
tautulli_users = requests.get('http://{}/api/v2'
.format(TAUTULLI_BASE_URL), params=payload).json()['response']['data']
session = Session()
session.params = {'apikey': TAUTULLI_API_KEY}
formatted_url = f'{TAUTULLI_URL}/api/v2'
request = session.get(formatted_url, params={'cmd': 'get_user_names'})
tautulli_users = None
try:
tautulli_users = request.json()['response']['data']
except JSONDecodeError:
exit("Error talking to Tautulli API, please check your TAUTULLI_URL")
plex_friend_ids = [friend.id for friend in account.users()]
tautulli_user_ids = [user['user_id'] for user in tautulli_users]
removed_user_ids = [user_id for user_id in tautulli_user_ids if user_id not in plex_friend_ids]
removed_users = [user for user in tautulli_users if user['user_id'] not in plex_friend_ids]
if BACKUP_DB:
payload['cmd'] = 'backup_db'
backup = requests.get('http://{}/api/v2'.format(TAUTULLI_BASE_URL), params=payload)
backup = session.get(formatted_url, params={'cmd': 'backup_db'})
if removed_user_ids:
payload['cmd'] = 'delete_user'
for user_id in removed_user_ids:
payload['user_id'] = user_id
remove_user = requests.get('http://{}/api/v2'.format(TAUTULLI_BASE_URL), params=payload)
if removed_users:
for user in removed_users:
removed_user = session.get(formatted_url, params={'cmd': 'delete_user', 'user_id': user['user_id']})
print(f"Removed {user['friendly_name']} from Tautulli")
else:
print('No users to remove')