Added script directory to Tautulli
This commit is contained in:
@@ -16,7 +16,8 @@
|
||||
- overseerr
|
||||
- ntfy
|
||||
- nextcloud
|
||||
- tautulli
|
||||
- name: tautulli
|
||||
tags: test
|
||||
- unifi-controller
|
||||
- navidrome
|
||||
- lidarr
|
||||
|
5
ansible/roles/authentik/handlers/main.yml
Normal file
5
ansible/roles/authentik/handlers/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
- name: restart searxng
|
||||
community.docker.docker_compose:
|
||||
project_src: "{{ install_directory }}/{{ role_name }}"
|
||||
restarted: true
|
||||
|
30
ansible/roles/authentik/tasks/main.yml
Normal file
30
ansible/roles/authentik/tasks/main.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
- name: Create install directory
|
||||
file:
|
||||
path: "{{ install_directory }}/{{ role_name }}"
|
||||
state: directory
|
||||
owner: "{{ docker_user }}"
|
||||
mode: "{{ docker_compose_directory_mask }}"
|
||||
become: true
|
||||
|
||||
- name: Copy docker-compose file to destination
|
||||
template:
|
||||
src: docker-compose.yml
|
||||
dest: "{{ install_directory }}/{{ role_name }}/docker-compose.yml"
|
||||
owner: "{{ docker_user }}"
|
||||
mode: "{{ docker_compose_file_mask }}"
|
||||
validate: docker-compose -f %s config
|
||||
become: true
|
||||
|
||||
- name: Copy settings file to destionation
|
||||
template:
|
||||
src: settings.yml
|
||||
dest: "{{ data_dir }}/{{ role_name }}/settings.yml"
|
||||
owner: "{{ docker_user }}"
|
||||
become: true
|
||||
notify: restart searxng
|
||||
|
||||
- name: Start docker container
|
||||
community.docker.docker_compose:
|
||||
project_src: "{{ install_directory }}/{{ role_name }}"
|
||||
pull: true
|
||||
remove_orphans: yes
|
38
ansible/roles/authentik/templates/docker-compose.yml
Normal file
38
ansible/roles/authentik/templates/docker-compose.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
version: "{{ docker_compose_version }}"
|
||||
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
|
||||
services:
|
||||
searxng:
|
||||
container_name: searxng
|
||||
image: searxng/searxng
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- traefik
|
||||
volumes:
|
||||
- "{{ data_dir }}/{{ role_name }}:/etc/searxng"
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.searxng.rule: "Host(`search.{{ personal_domain }}`)"
|
||||
traefik.http.routers.searxng.middlewares: lan-whitelist@file
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
restart: unless-stopped
|
||||
command: redis-server --save "" --appendonly "no"
|
||||
tmpfs:
|
||||
- /var/lib/redis
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- SETGID
|
||||
- SETUID
|
||||
- DAC_OVERRIDE
|
1890
ansible/roles/authentik/templates/settings.yml
Normal file
1890
ansible/roles/authentik/templates/settings.yml
Normal file
File diff suppressed because it is too large
Load Diff
10
ansible/roles/authentik/vars/main.yml
Normal file
10
ansible/roles/authentik/vars/main.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
searxng_secret_key: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
33656138666464373665663339363665346566613637626131363335336535313131333265646539
|
||||
3037373439643964343139383764386364623961383737610a313063613736316437366239663238
|
||||
65333735633661316463336665353138623264396534383865363134613165636164303765356265
|
||||
3865626366613966660a313738353339313133393765643136306361373061366132373130656531
|
||||
61396230346333346636356562353733623332333662653164373630626339376433353663313862
|
||||
61303230613135336662313531313836363466623162666233646231616333643536303233616231
|
||||
62353866333465646162633738383866363338383932623335353038393130323932343363653233
|
||||
62663465386661663262
|
@@ -13,8 +13,8 @@
|
||||
Match User git
|
||||
AuthorizedKeysCommandUser git
|
||||
AuthorizedKeysCommand /usr/bin/docker exec -i gitea /usr/local/bin/gitea keys -c /etc/gitea/app.ini -e git -u %u -t %t -k %k
|
||||
validate: /usr/sbin/sshd -T -f %s
|
||||
become: true
|
||||
validate: /usr/sbin/sshd -T -f %s
|
||||
notify: restart sshd
|
||||
#############################
|
||||
|
||||
|
639
ansible/roles/tautulli/files/kill_stream.py
Normal file
639
ansible/roles/tautulli/files/kill_stream.py
Normal file
@@ -0,0 +1,639 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Description: Use conditions to kill a stream
|
||||
Author: Blacktwin, Arcanemagus, Samwiseg0, JonnyWong16, DirtyCajunRice
|
||||
|
||||
Adding the script to Tautulli:
|
||||
Tautulli > Settings > Notification Agents > Add a new notification agent >
|
||||
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)
|
||||
Script Timeout: {timeout}
|
||||
Description: Kill stream(s)
|
||||
Save
|
||||
|
||||
Triggers:
|
||||
Tautulli > Settings > Notification Agents > New Script > Triggers:
|
||||
|
||||
Check: Playback Start and/or Playback Pause
|
||||
Save
|
||||
|
||||
Conditions:
|
||||
Tautulli > Settings > Notification Agents > New Script > Conditions:
|
||||
|
||||
Set Conditions: [{condition} | {operator} | {value} ]
|
||||
Save
|
||||
|
||||
Script Arguments:
|
||||
Tautulli > Settings > Notification Agents > New Script > Script Arguments:
|
||||
|
||||
Select: Playback Start, Playback Pause
|
||||
Arguments: --jbop SELECTOR --userId {user_id} --username {username}
|
||||
--sessionId {session_id} --notify notifierID
|
||||
--interval 30 --limit 1200
|
||||
--richMessage RICH_TYPE --serverName {server_name}
|
||||
--plexUrl {plex_url} --posterUrl {poster_url}
|
||||
--richColor '#E5A00D'
|
||||
--killMessage 'Your message here.'
|
||||
|
||||
Save
|
||||
Close
|
||||
"""
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
from builtins import object
|
||||
from builtins import str
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
from requests import Session
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
|
||||
TAUTULLI_URL = ''
|
||||
TAUTULLI_APIKEY = ''
|
||||
TAUTULLI_PUBLIC_URL = ''
|
||||
TAUTULLI_URL = os.getenv('TAUTULLI_URL', TAUTULLI_URL)
|
||||
TAUTULLI_PUBLIC_URL = os.getenv('TAUTULLI_PUBLIC_URL', TAUTULLI_PUBLIC_URL)
|
||||
TAUTULLI_APIKEY = os.getenv('TAUTULLI_APIKEY', TAUTULLI_APIKEY)
|
||||
TAUTULLI_ENCODING = os.getenv('TAUTULLI_ENCODING', 'UTF-8')
|
||||
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
|
||||
|
||||
SUBJECT_TEXT = "Tautulli has killed a stream."
|
||||
BODY_TEXT = "Killed session ID '{id}'. Reason: {message}"
|
||||
BODY_TEXT_USER = "Killed {user}'s stream. Reason: {message}."
|
||||
|
||||
|
||||
SELECTOR = ['stream', 'allStreams', 'paused']
|
||||
|
||||
RICH_TYPE = ['discord', 'slack']
|
||||
|
||||
TAUTULLI_ICON = 'https://github.com/Tautulli/Tautulli/raw/master/data/interfaces/default/images/logo-circle.png'
|
||||
|
||||
|
||||
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:
|
||||
return int(value, 16)
|
||||
except (ValueError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
def arg_decoding(arg):
|
||||
"""Decode args, encode UTF-8"""
|
||||
if sys.version_info[0] < 3:
|
||||
return arg.decode(TAUTULLI_ENCODING).encode('UTF-8')
|
||||
else:
|
||||
return arg
|
||||
|
||||
|
||||
def debug_dump_vars():
|
||||
"""Dump parameters for debug"""
|
||||
print('Tautulli URL - ' + TAUTULLI_URL)
|
||||
print('Tautulli Public URL - ' + TAUTULLI_PUBLIC_URL)
|
||||
print('Verify SSL - ' + str(VERIFY_SSL))
|
||||
print('Tautulli API key - ' + TAUTULLI_APIKEY[-4:]
|
||||
.rjust(len(TAUTULLI_APIKEY), "x"))
|
||||
|
||||
|
||||
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
|
||||
The of stream objects.
|
||||
"""
|
||||
sessions = tautulli.get_activity()['sessions']
|
||||
|
||||
if user_id:
|
||||
streams = [Stream(session=s) for s in sessions if s['user_id'] == user_id]
|
||||
else:
|
||||
streams = [Stream(session=s) for s in sessions]
|
||||
|
||||
return streams
|
||||
|
||||
|
||||
def notify(all_opts, message, kill_type=None, stream=None, tautulli=None):
|
||||
"""Decides which notifier type to use"""
|
||||
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, tautulli=None):
|
||||
"""Decides which rich notifier type to use. Set default values for empty variables
|
||||
|
||||
Parameters
|
||||
----------
|
||||
notifier_id : int
|
||||
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
|
||||
The name of the plex server.
|
||||
plex_url : str
|
||||
Plex media URL.
|
||||
poster_url : str
|
||||
The media poster URL.
|
||||
message : str
|
||||
Message sent to the client.
|
||||
stream : obj
|
||||
Stream object.
|
||||
tautulli : obj
|
||||
Tautulli object.
|
||||
"""
|
||||
notification = Notification(notifier_id, None, None, 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 default color if none is provided
|
||||
if color is None:
|
||||
color = '#E5A00D'
|
||||
|
||||
# Set a default plexUrl if none is provided
|
||||
if plex_url is None:
|
||||
plex_url = 'https://app.plex.tv'
|
||||
|
||||
# Set a default posterUrl if none is provided
|
||||
if poster_url is None:
|
||||
poster_url = TAUTULLI_ICON
|
||||
|
||||
# Set a default message if none is provided
|
||||
if message is None:
|
||||
message = 'The server owner has ended the stream.'
|
||||
|
||||
if kill_type == 'Stream':
|
||||
title = "Killed {}'s stream.".format(stream.friendly_name)
|
||||
footer = '{} | Kill {}'.format(server_name, kill_type)
|
||||
|
||||
elif kill_type == 'Paused':
|
||||
title = "Killed {}'s paused stream.".format(stream.friendly_name)
|
||||
footer = '{} | Kill {}'.format(server_name, kill_type)
|
||||
|
||||
elif kill_type == 'All Streams':
|
||||
title = "Killed {}'s stream.".format(stream.friendly_name)
|
||||
footer = '{} | Kill {}'.format(server_name, kill_type)
|
||||
poster_url = TAUTULLI_ICON
|
||||
plex_url = 'https://app.plex.tv'
|
||||
|
||||
if rich_type == 'discord':
|
||||
color = hex_to_int(color.lstrip('#'))
|
||||
notification.send_discord(title, color, poster_url, plex_url, message, footer)
|
||||
|
||||
elif rich_type == 'slack':
|
||||
notification.send_slack(title, color, poster_url, plex_url, message, footer)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if username:
|
||||
body = BODY_TEXT_USER.format(user=username,
|
||||
message=message)
|
||||
else:
|
||||
body = BODY_TEXT.format(id=session_id, message=message)
|
||||
notification.send(SUBJECT_TEXT, body)
|
||||
|
||||
|
||||
class Tautulli(object):
|
||||
def __init__(self, url, apikey, verify_ssl=False, debug=None):
|
||||
self.url = url
|
||||
self.apikey = apikey
|
||||
self.debug = debug
|
||||
|
||||
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)
|
||||
|
||||
# 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 _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))
|
||||
if self.debug:
|
||||
traceback.print_exc()
|
||||
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
|
||||
|
||||
def get_activity(self, session_key=None, session_id=None):
|
||||
"""Call Tautulli's get_activity api endpoint"""
|
||||
payload = {}
|
||||
|
||||
if session_key:
|
||||
payload['session_key'] = session_key
|
||||
elif session_id:
|
||||
payload['session_id'] = session_id
|
||||
|
||||
return self._call_api('get_activity', 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 terminate_session(self, session_key=None, session_id=None, message=''):
|
||||
"""Call Tautulli's terminate_session api endpoint"""
|
||||
payload = {}
|
||||
|
||||
if session_key:
|
||||
payload['session_key'] = session_key
|
||||
elif session_id:
|
||||
payload['session_id'] = session_id
|
||||
|
||||
if message:
|
||||
payload['message'] = message
|
||||
|
||||
return self._call_api('terminate_session', payload)
|
||||
|
||||
|
||||
class Stream(object):
|
||||
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:
|
||||
self._set_stream_attributes(session)
|
||||
|
||||
def _set_stream_attributes(self, session):
|
||||
for k, v in session.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def get_all_stream_info(self):
|
||||
"""Get all stream info from Tautulli."""
|
||||
session = self.tautulli.get_activity(session_id=self.session_id)
|
||||
if session:
|
||||
self._set_stream_attributes(session)
|
||||
self.session_exists = True
|
||||
else:
|
||||
self.session_exists = False
|
||||
|
||||
def terminate(self, message=''):
|
||||
"""Calls Tautulli to terminate the session.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
The message to use if the stream is terminated.
|
||||
"""
|
||||
self.tautulli.terminate_session(session_id=self.session_id, message=message)
|
||||
|
||||
def terminate_long_pause(self, message, limit, interval):
|
||||
"""Kills the session if it is paused for longer than <limit> seconds.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : str
|
||||
The message to use if the stream is terminated.
|
||||
limit : int
|
||||
The number of seconds the session is allowed to remain paused before it
|
||||
is terminated.
|
||||
interval : int
|
||||
The amount of time to wait between checks of the session state.
|
||||
"""
|
||||
start = datetime.now()
|
||||
checked_time = 0
|
||||
# Continue checking 2 intervals past the allowed limit in order to
|
||||
# account for system variances.
|
||||
check_limit = limit + (interval * 2)
|
||||
|
||||
while checked_time < check_limit:
|
||||
self.get_all_stream_info()
|
||||
|
||||
if self.session_exists is False:
|
||||
sys.stdout.write(
|
||||
"Session '{}' from user '{}' is no longer active "
|
||||
.format(self.session_id, self.username) +
|
||||
"on the server, stopping monitoring.\n")
|
||||
return False
|
||||
|
||||
now = datetime.now()
|
||||
checked_time = (now - start).total_seconds()
|
||||
|
||||
if self.state == 'paused':
|
||||
if checked_time >= limit:
|
||||
self.terminate(message)
|
||||
sys.stdout.write(
|
||||
"Session '{}' from user '{}' has been killed.\n"
|
||||
.format(self.session_id, self.username))
|
||||
return True
|
||||
else:
|
||||
time.sleep(interval)
|
||||
|
||||
elif self.state == 'playing' or self.state == 'buffering':
|
||||
sys.stdout.write(
|
||||
"Session '{}' from user '{}' has been resumed, "
|
||||
.format(self.session_id, self.username) +
|
||||
"stopping monitoring.\n")
|
||||
return False
|
||||
|
||||
|
||||
class Notification(object):
|
||||
def __init__(self, notifier_id, subject, body, tautulli, stream):
|
||||
self.notifier_id = notifier_id
|
||||
self.subject = subject
|
||||
self.body = body
|
||||
|
||||
self.tautulli = tautulli
|
||||
self.stream = stream
|
||||
|
||||
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.stream.friendly_name
|
||||
},
|
||||
{
|
||||
"inline": True,
|
||||
"name": "Session Key",
|
||||
"value": self.stream.session_key
|
||||
},
|
||||
{
|
||||
"inline": True,
|
||||
"name": "Watching",
|
||||
"value": self.stream.full_title
|
||||
},
|
||||
{
|
||||
"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.stream.friendly_name,
|
||||
"short": True
|
||||
},
|
||||
{
|
||||
"title": "Session Key",
|
||||
"value": self.stream.session_key,
|
||||
"short": True
|
||||
},
|
||||
{
|
||||
"title": "Watching",
|
||||
"value": self.stream.full_title,
|
||||
"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="Killing Plex streams from Tautulli.")
|
||||
parser.add_argument('--jbop', required=True, choices=SELECTOR,
|
||||
help='Kill selector.\nChoices: (%(choices)s)')
|
||||
parser.add_argument('--userId', type=int,
|
||||
help='The unique identifier for the user.')
|
||||
parser.add_argument('--username', type=arg_decoding,
|
||||
help='The username of the person streaming.')
|
||||
parser.add_argument('--sessionId',
|
||||
help='The unique identifier for the stream.')
|
||||
parser.add_argument('--notify', type=int,
|
||||
help='Notification Agent ID number to Agent to ' +
|
||||
'send notification.')
|
||||
parser.add_argument('--limit', type=int, default=(20 * 60), # 20 minutes
|
||||
help='The time session is allowed to remain paused.')
|
||||
parser.add_argument('--interval', type=int, default=30,
|
||||
help='The seconds between paused session checks.')
|
||||
parser.add_argument('--killMessage', nargs='+', type=arg_decoding,
|
||||
help='Message to send to user whose stream is killed.')
|
||||
parser.add_argument('--richMessage', type=arg_decoding, choices=RICH_TYPE,
|
||||
help='Rich message type selector.\nChoices: (%(choices)s)')
|
||||
parser.add_argument('--serverName', type=arg_decoding,
|
||||
help='Plex Server Name')
|
||||
parser.add_argument('--plexUrl', type=arg_decoding,
|
||||
help='URL to plex media')
|
||||
parser.add_argument('--posterUrl', type=arg_decoding,
|
||||
help='Poster URL of the media')
|
||||
parser.add_argument('--richColor', type=arg_decoding,
|
||||
help='Color of the rich message')
|
||||
parser.add_argument("--debug", action='store_true',
|
||||
help='Enable debug messages.')
|
||||
|
||||
opts = parser.parse_args()
|
||||
|
||||
if not opts.sessionId and opts.jbop != 'allStreams':
|
||||
sys.stderr.write("No sessionId provided! Is this synced content?\n")
|
||||
sys.exit(1)
|
||||
|
||||
if opts.debug:
|
||||
# Import traceback to get more detailed information
|
||||
import traceback
|
||||
# Dump the ENVs passed from tautulli
|
||||
debug_dump_vars()
|
||||
|
||||
# Create a Tautulli instance
|
||||
tautulli_server = Tautulli(TAUTULLI_URL.rstrip('/'), TAUTULLI_APIKEY, VERIFY_SSL, opts.debug)
|
||||
|
||||
# Create initial Stream object with basic info
|
||||
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:
|
||||
tautulli_stream.get_all_stream_info()
|
||||
|
||||
# Set a default message if none is provided
|
||||
if opts.killMessage:
|
||||
kill_message = ' '.join(opts.killMessage)
|
||||
else:
|
||||
kill_message = 'The server owner has ended the stream.'
|
||||
|
||||
if opts.jbop == 'stream':
|
||||
tautulli_stream.terminate(kill_message)
|
||||
notify(opts, kill_message, 'Stream', tautulli_stream, tautulli_server)
|
||||
|
||||
elif opts.jbop == 'allStreams':
|
||||
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 = tautulli_stream.terminate_long_pause(kill_message, opts.limit, opts.interval)
|
||||
if killed_stream:
|
||||
notify(opts, kill_message, 'Paused', tautulli_stream, tautulli_server)
|
@@ -22,6 +22,16 @@
|
||||
validate: docker-compose -f %s config
|
||||
become: true
|
||||
|
||||
- name: Install script files
|
||||
ansible.builtin.copy:
|
||||
src: "{{ item }}"
|
||||
dest: "{{ data_dir }}/tautulli/scripts"
|
||||
mode: "{{ docker_compose_file_mask }}"
|
||||
owner: "{{ primary_user }}"
|
||||
with_fileglob:
|
||||
- "files/*"
|
||||
become: true
|
||||
|
||||
- name: Start docker container
|
||||
community.docker.docker_compose:
|
||||
project_src: "{{ install_directory }}/{{ role_name }}"
|
||||
|
Reference in New Issue
Block a user