diff --git a/CHANGELOG.md b/CHANGELOG.md
index eda655d..6ad9ec5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,25 @@
# Change Log
-## [v1.4](https://github.com/Boerderij/Varken/tree/v1.4) (2018-12-18)
+## [v1.5](https://github.com/Boerderij/Varken/tree/v1.5) (2018-12-30)
+[Full Changelog](https://github.com/Boerderij/Varken/compare/v1.4...v1.5)
+
+**Implemented enhancements:**
+
+- \[Feature Request\] Add issues from Ombi [\#70](https://github.com/Boerderij/Varken/issues/70)
+- \[Feature Request\] Allow DNS Hostnames [\#66](https://github.com/Boerderij/Varken/issues/66)
+- Replace static grafana configs with a Public Example [\#32](https://github.com/Boerderij/Varken/issues/32)
+
+**Fixed bugs:**
+
+- \[BUG\] unexpected keyword argument 'channel\_icon' [\#73](https://github.com/Boerderij/Varken/issues/73)
+- \[BUG\] Unexpected keyword argument 'addOptions' [\#68](https://github.com/Boerderij/Varken/issues/68)
+
+**Merged pull requests:**
+
+- v1.5 Merge [\#75](https://github.com/Boerderij/Varken/pull/75) ([DirtyCajunRice](https://github.com/DirtyCajunRice))
+- Add Ombi Issues [\#74](https://github.com/Boerderij/Varken/pull/74) ([anderssonoscar0](https://github.com/anderssonoscar0))
+
+## [v1.4](https://github.com/Boerderij/Varken/tree/v1.4) (2018-12-19)
[Full Changelog](https://github.com/Boerderij/Varken/compare/v1.3-nightly...v1.4)
**Implemented enhancements:**
@@ -92,10 +111,6 @@
## [v0.2-nightly](https://github.com/Boerderij/Varken/tree/v0.2-nightly) (2018-12-06)
[Full Changelog](https://github.com/Boerderij/Varken/compare/v0.1...v0.2-nightly)
-**Implemented enhancements:**
-
-- Tautulli - multiple server support? [\#25](https://github.com/Boerderij/Varken/issues/25)
-
**Closed issues:**
- Create the DB if it does not exist. [\#38](https://github.com/Boerderij/Varken/issues/38)
@@ -105,7 +120,12 @@
- use a config.ini instead of command-line flags [\#33](https://github.com/Boerderij/Varken/issues/33)
- Migrate crontab to python schedule package [\#31](https://github.com/Boerderij/Varken/issues/31)
- Consolidate missing and missing\_days in sonarr.py [\#30](https://github.com/Boerderij/Varken/issues/30)
+- Database Withou any scripts [\#29](https://github.com/Boerderij/Varken/issues/29)
+- Grafana dashboard json doesn't match format of readme screenshot? [\#28](https://github.com/Boerderij/Varken/issues/28)
- Ombi something new \[Request\] [\#26](https://github.com/Boerderij/Varken/issues/26)
+- Users Online not populating [\#24](https://github.com/Boerderij/Varken/issues/24)
+- Missing dashboard [\#23](https://github.com/Boerderij/Varken/issues/23)
+- Is there a Docker Image available for these scripts? [\#22](https://github.com/Boerderij/Varken/issues/22)
- Support for Linux without ASA [\#21](https://github.com/Boerderij/Varken/issues/21)
**Merged pull requests:**
diff --git a/README.md b/README.md
index fe68aa7..544f703 100644
--- a/README.md
+++ b/README.md
@@ -10,14 +10,14 @@ from the Plex ecosystem into InfluxDB. Examples use Grafana for a
frontend
Requirements:
-* Python3.6+
+* Python3.6.7+
* Python3-pip
* [InfluxDB](https://www.influxdata.com/)
Example Dashboard
-
+
Supported Modules:
@@ -103,9 +103,16 @@ do not include database creation, please ensure you create an influx database
named `varken`
### Grafana
-[Grafana Installation Documentation](http://docs.grafana.org/installation/)
+[Grafana Installation Documentation](http://docs.grafana.org/installation/)
+[Official Example Dashboards](https://grafana.com/dashboards?search=Varken%20%5BOfficial%5D)
Grafana is used in our examples but not required, nor packaged as part of
-Varken. Panel example pictures are pinned in the grafana-panels channel of
-discord. Future releases may contain a json-generator, but it does not exist
-as varken stands today.
+Varken. Panel examples now exist in both nightly and tagged releases hosted
+on grafana.com (link above).
+
+1. Use the link above, then click on your desired dashboard version
+2. Click `Copy ID to Clipboard`
+3. In grafana, click your dashboards menu dropdown, and then click `Import dashboard`
+4. Paste the ID into the `Grafana.com Dashboard` field and then click into empty space on the screen. (This should change the screen to show `Importing Dashboard from Grafana.com`
+5. Select your varken datasource name in the dropdown labeled `Varken`
+6. Click Import!
\ No newline at end of file
diff --git a/Varken.py b/Varken.py
index 20aea2b..c157646 100644
--- a/Varken.py
+++ b/Varken.py
@@ -121,6 +121,8 @@ if __name__ == "__main__":
schedule.every(server.request_type_run_seconds).seconds.do(threaded, OMBI.get_request_counts)
if server.request_total_counts:
schedule.every(server.request_total_run_seconds).seconds.do(threaded, OMBI.get_all_requests)
+ if server.issue_status_counts:
+ schedule.every(server.issue_status_run_seconds).seconds.do(threaded, OMBI.get_issue_counts)
if CONFIG.sickchill_enabled:
for server in CONFIG.sickchill_servers:
diff --git a/data/varken.example.ini b/data/varken.example.ini
index b061984..bbb2ceb 100644
--- a/data/varken.example.ini
+++ b/data/varken.example.ini
@@ -2,26 +2,28 @@
# - Sonarr + Radarr scripts support multiple servers. You can remove the second
# server by putting a # in front of the lines and section name, and removing
# that number from your server_ids list
-# - fallback_ip, This is used when there is no IP listed in tautulli.
-# This can happen when you are streaming locally. This is usually your public IP.
+# - fallback_ip, This is used when there is no IP listed in Tautulli.
+# This can happen when you are streaming locally. Set this to your public IP.
+# You do not need to change this value if your IP changes. This is only for
+# location lookups when there is a failure.
[global]
sonarr_server_ids = 1,2
radarr_server_ids = 1,2
tautulli_server_ids = 1
ombi_server_ids = 1
-ciscoasa_firewall_ids = false
+ciscoasa_server_ids = false
sickchill_server_ids = false
[influxdb]
url = influxdb.domain.tld
port = 8086
-username =
-password =
+username = root
+password = root
[tautulli-1]
url = tautulli.domain.tld:8181
-fallback_ip = 0.0.0.0
+fallback_ip = 1.1.1.1
apikey = xxxxxxxxxxxxxxxx
ssl = false
verify_ssl = false
@@ -83,6 +85,8 @@ get_request_type_counts = true
request_type_run_seconds = 300
get_request_total_counts = true
request_total_run_seconds = 300
+get_issue_status_counts = true
+issue_status_run_seconds = 300
[sickchill-1]
url = sickchill.domain.tld:8081
@@ -92,8 +96,6 @@ verify_ssl = false
get_missing = true
get_missing_run_seconds = 300
-
-
[ciscoasa-1]
url = firewall.domain.tld
username = cisco
diff --git a/varken/__init__.py b/varken/__init__.py
index e19b0bd..fba0206 100644
--- a/varken/__init__.py
+++ b/varken/__init__.py
@@ -1,2 +1,2 @@
-VERSION = 1.4
+VERSION = 1.5
BRANCH = 'master'
diff --git a/varken/helpers.py b/varken/helpers.py
index fd13385..648eb5a 100644
--- a/varken/helpers.py
+++ b/varken/helpers.py
@@ -27,17 +27,20 @@ class GeoIPHandler(object):
def lookup(self, ipaddress):
ip = ipaddress
- self.logger.debug('Getting lat/long for Tautulli stream')
+ self.logger.debug('Getting lat/long for Tautulli stream using ip with last octet ending in %s',
+ ip.split('.')[-1:])
return self.reader.city(ip)
def update(self):
today = date.today()
- dbdate = None
+
try:
dbdate = date.fromtimestamp(stat(self.dbfile).st_ctime)
except FileNotFoundError:
self.logger.error("Could not find GeoLite2 DB as: %s", self.dbfile)
self.download()
+ dbdate = date.fromtimestamp(stat(self.dbfile).st_ctime)
+
first_wednesday_day = [week[2:3][0] for week in monthcalendar(today.year, today.month) if week[2:3][0] != 0][0]
first_wednesday_date = date(today.year, today.month, first_wednesday_day)
@@ -52,7 +55,6 @@ class GeoIPHandler(object):
else:
self.logger.debug('Geolite2 DB will update in %s days', abs(td.days))
-
def download(self):
tar_dbfile = abspath(join(self.data_folder, 'GeoLite2-City.tar.gz'))
url = 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz'
diff --git a/varken/iniparser.py b/varken/iniparser.py
index dcd2e44..3569503 100644
--- a/varken/iniparser.py
+++ b/varken/iniparser.py
@@ -59,15 +59,17 @@ class INIParser(object):
self.logger.error('Config file missing (varken.ini) in %s', self.data_folder)
exit(1)
- def url_check(self, url=None, include_port=True):
+ def url_check(self, url=None, include_port=True, section=None):
url_check = url
+ module = section
inc_port = include_port
- search = (r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
+ search = (r'(?:([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}|' # domain...
r'localhost|' # localhost...
+ r'^[a-zA-Z0-9_-]*|' # hostname only. My soul dies a little every time this is used...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
)
-
+ # Include search for port if it is needed.
if inc_port:
search = (search + r'(?::\d+)?' + r'(?:/?|[/?]\S+)$')
else:
@@ -78,28 +80,28 @@ class INIParser(object):
valid = match(regex, url_check) is not None
if not valid:
if inc_port:
- self.logger.error('%s is invalid! URL must host/IP and port if not 80 or 443. ie. localhost:8080',
- url_check)
+ self.logger.error('%s is invalid in module [%s]! URL must host/IP and '
+ 'port if not 80 or 443. ie. localhost:8080',
+ url_check, module)
exit(1)
else:
- self.logger.error('%s is invalid! URL must host/IP. ie. localhost', url_check)
+ self.logger.error('%s is invalid in module [%s]! URL must host/IP. ie. localhost', url_check, module)
exit(1)
else:
- self.logger.debug('%s is a valid URL in the config.', url_check)
+ self.logger.debug('%s is a valid URL in module [%s].', url_check, module)
return url_check
def parse_opts(self):
self.read_file()
# Parse InfluxDB options
- url = self.url_check(self.config.get('influxdb', 'url'), include_port=False)
+ url = self.url_check(self.config.get('influxdb', 'url'), include_port=False, section='influxdb')
port = self.config.getint('influxdb', 'port')
username = self.config.get('influxdb', 'username')
password = self.config.get('influxdb', 'password')
-
- self.influx_server = InfluxServer(url, port, username, password)
+ self.influx_server = InfluxServer(url=url, port=port, username=username, password=password)
# Check for all enabled services
for service in self.services:
@@ -111,7 +113,7 @@ class INIParser(object):
server = None
section = f"{service}-{server_id}"
try:
- url = self.url_check(self.config.get(section, 'url'))
+ url = self.url_check(self.config.get(section, 'url'), section=section)
apikey = None
if service != 'ciscoasa':
@@ -137,9 +139,11 @@ class INIParser(object):
queue_run_seconds = self.config.getint(section, 'queue_run_seconds')
- server = SonarrServer(server_id, scheme + url, apikey, verify_ssl, missing_days,
- missing_days_run_seconds, future_days, future_days_run_seconds,
- queue, queue_run_seconds)
+ server = SonarrServer(id=server_id, url=scheme + url, api_key=apikey, verify_ssl=verify_ssl,
+ missing_days=missing_days, future_days=future_days,
+ missing_days_run_seconds=missing_days_run_seconds,
+ future_days_run_seconds=future_days_run_seconds,
+ queue=queue, queue_run_seconds=queue_run_seconds)
if service == 'radarr':
queue = self.config.getboolean(section, 'queue')
@@ -150,8 +154,9 @@ class INIParser(object):
get_missing_run_seconds = self.config.getint(section, 'get_missing_run_seconds')
- server = RadarrServer(server_id, scheme + url, apikey, verify_ssl, queue, queue_run_seconds,
- get_missing, get_missing_run_seconds)
+ server = RadarrServer(id=server_id, url=scheme + url, api_key=apikey, verify_ssl=verify_ssl,
+ queue_run_seconds=queue_run_seconds, get_missing=get_missing,
+ queue=queue, get_missing_run_seconds=get_missing_run_seconds)
if service == 'tautulli':
fallback_ip = self.config.get(section, 'fallback_ip')
@@ -164,9 +169,11 @@ class INIParser(object):
get_stats_run_seconds = self.config.getint(section, 'get_stats_run_seconds')
- server = TautulliServer(server_id, scheme + url, fallback_ip, apikey, verify_ssl,
- get_activity, get_activity_run_seconds, get_stats,
- get_stats_run_seconds)
+ server = TautulliServer(id=server_id, url=scheme + url, api_key=apikey,
+ verify_ssl=verify_ssl, get_activity=get_activity,
+ fallback_ip=fallback_ip, get_stats=get_stats,
+ get_activity_run_seconds=get_activity_run_seconds,
+ get_stats_run_seconds=get_stats_run_seconds)
if service == 'ombi':
request_type_counts = self.config.getboolean(section, 'get_request_type_counts')
@@ -177,17 +184,26 @@ class INIParser(object):
request_total_run_seconds = self.config.getint(section, 'request_total_run_seconds')
- server = OmbiServer(server_id, scheme + url, apikey, verify_ssl, request_type_counts,
- request_type_run_seconds, request_total_counts,
- request_total_run_seconds)
+ issue_status_counts = self.config.getboolean(section, 'get_issue_status_counts')
+
+ issue_status_run_seconds = self.config.getint(section, 'issue_status_run_seconds')
+
+ server = OmbiServer(id=server_id, url=scheme + url, api_key=apikey, verify_ssl=verify_ssl,
+ request_type_counts=request_type_counts,
+ request_type_run_seconds=request_type_run_seconds,
+ request_total_counts=request_total_counts,
+ request_total_run_seconds=request_total_run_seconds,
+ issue_status_counts=issue_status_counts,
+ issue_status_run_seconds=issue_status_run_seconds)
if service == 'sickchill':
get_missing = self.config.getboolean(section, 'get_missing')
get_missing_run_seconds = self.config.getint(section, 'get_missing_run_seconds')
- server = SickChillServer(server_id, scheme + url, apikey, verify_ssl,
- get_missing, get_missing_run_seconds)
+ server = SickChillServer(id=server_id, url=scheme + url, api_key=apikey,
+ verify_ssl=verify_ssl, get_missing=get_missing,
+ get_missing_run_seconds=get_missing_run_seconds)
if service == 'ciscoasa':
username = self.config.get(section, 'username')
@@ -198,8 +214,10 @@ class INIParser(object):
get_bandwidth_run_seconds = self.config.getint(section, 'get_bandwidth_run_seconds')
- server = CiscoASAFirewall(server_id, scheme + url, username, password, outside_interface,
- verify_ssl, get_bandwidth_run_seconds)
+ server = CiscoASAFirewall(id=server_id, url=scheme + url, verify_ssl=verify_ssl,
+ username=username, password=password,
+ outside_interface=outside_interface,
+ get_bandwidth_run_seconds=get_bandwidth_run_seconds)
getattr(self, f'{service}_servers').append(server)
diff --git a/varken/ombi.py b/varken/ombi.py
index f9a4830..86f8e33 100644
--- a/varken/ombi.py
+++ b/varken/ombi.py
@@ -3,7 +3,7 @@ from requests import Session, Request
from datetime import datetime, timezone
from varken.helpers import connection_handler, hashit
-from varken.structures import OmbiRequestCounts, OmbiMovieRequest, OmbiTVRequest
+from varken.structures import OmbiRequestCounts, OmbiIssuesCounts, OmbiMovieRequest, OmbiTVRequest
class OmbiAPI(object):
@@ -65,14 +65,17 @@ class OmbiAPI(object):
# Request Type: Movie = 1, TV Show = 0
for movie in movie_requests:
hash_id = hashit(f'{movie.id}{movie.theMovieDbId}{movie.title}')
- status = None
+
# Denied = 0, Approved = 1, Completed = 2, Pending = 3
if movie.denied:
status = 0
+
elif movie.approved and movie.available:
status = 2
+
elif movie.approved:
status = 1
+
else:
status = 3
@@ -101,10 +104,13 @@ class OmbiAPI(object):
# Denied = 0, Approved = 1, Completed = 2, Pending = 3
if show.childRequests[0]['denied']:
status = 0
+
elif show.childRequests[0]['approved'] and show.childRequests[0]['available']:
status = 2
+
elif show.childRequests[0]['approved']:
status = 1
+
else:
status = 3
@@ -156,3 +162,31 @@ class OmbiAPI(object):
]
self.dbmanager.write_points(influx_payload)
+
+ def get_issue_counts(self):
+ now = datetime.now(timezone.utc).astimezone().isoformat()
+ endpoint = '/api/v1/Issues/count'
+
+ req = self.session.prepare_request(Request('GET', self.server.url + endpoint))
+ get = connection_handler(self.session, req, self.server.verify_ssl)
+
+ if not get:
+ return
+
+ requests = OmbiIssuesCounts(**get)
+ influx_payload = [
+ {
+ "measurement": "Ombi",
+ "tags": {
+ "type": "Issues_Counts"
+ },
+ "time": now,
+ "fields": {
+ "pending": requests.pending,
+ "in_progress": requests.inProgress,
+ "resolved": requests.resolved
+ }
+ }
+ ]
+
+ self.dbmanager.write_points(influx_payload)
diff --git a/varken/radarr.py b/varken/radarr.py
index d5ad514..e8a15d0 100644
--- a/varken/radarr.py
+++ b/varken/radarr.py
@@ -2,7 +2,7 @@ from logging import getLogger
from requests import Session, Request
from datetime import datetime, timezone
-from varken.structures import Movie, Queue
+from varken.structures import RadarrMovie, Queue
from varken.helpers import hashit, connection_handler
@@ -31,9 +31,9 @@ class RadarrAPI(object):
return
try:
- movies = [Movie(**movie) for movie in get]
+ movies = [RadarrMovie(**movie) for movie in get]
except TypeError as e:
- self.logger.error('TypeError has occurred : %s while creating Movie structure', e)
+ self.logger.error('TypeError has occurred : %s while creating RadarrMovie structure', e)
return
for movie in movies:
@@ -82,9 +82,9 @@ class RadarrAPI(object):
for movie in get:
try:
- movie['movie'] = Movie(**movie['movie'])
+ movie['movie'] = RadarrMovie(**movie['movie'])
except TypeError as e:
- self.logger.error('TypeError has occurred : %s while creating Movie structure', e)
+ self.logger.error('TypeError has occurred : %s while creating RadarrMovie structure', e)
return
try:
diff --git a/varken/sonarr.py b/varken/sonarr.py
index 19a36c9..b90dad9 100644
--- a/varken/sonarr.py
+++ b/varken/sonarr.py
@@ -2,7 +2,7 @@ from logging import getLogger
from requests import Session, Request
from datetime import datetime, timezone, date, timedelta
-from varken.structures import Queue, TVShow
+from varken.structures import Queue, SonarrTVShow
from varken.helpers import hashit, connection_handler
@@ -34,11 +34,11 @@ class SonarrAPI(object):
if not get:
return
- # Iteratively create a list of TVShow Objects from response json
+ # Iteratively create a list of SonarrTVShow Objects from response json
try:
- tv_shows = [TVShow(**show) for show in get]
+ tv_shows = [SonarrTVShow(**show) for show in get]
except TypeError as e:
- self.logger.error('TypeError has occurred : %s while creating TVShow structure', e)
+ self.logger.error('TypeError has occurred : %s while creating SonarrTVShow structure', e)
return
# Add show to missing list if file does not exist
@@ -87,9 +87,9 @@ class SonarrAPI(object):
return
try:
- tv_shows = [TVShow(**show) for show in get]
+ tv_shows = [SonarrTVShow(**show) for show in get]
except TypeError as e:
- self.logger.error('TypeError has occurred : %s while creating TVShow structure', e)
+ self.logger.error('TypeError has occurred : %s while creating SonarrTVShow structure', e)
return
for show in tv_shows:
@@ -150,9 +150,9 @@ class SonarrAPI(object):
protocol_id = 0
queue.append((show.series['title'], show.episode['title'], show.protocol.upper(),
- protocol_id, sxe, show.id))
+ protocol_id, sxe, show.id, show.quality['quality']['name']))
- for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id in queue:
+ for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id, quality in queue:
hash_id = hashit(f'{self.server.id}{series_title}{sxe}')
influx_payload.append(
{
@@ -165,7 +165,8 @@ class SonarrAPI(object):
"epname": episode_title,
"sxe": sxe,
"protocol": protocol,
- "protocol_id": protocol_id
+ "protocol_id": protocol_id,
+ "quality": quality
},
"time": now,
"fields": {
@@ -173,5 +174,4 @@ class SonarrAPI(object):
}
}
)
-
self.dbmanager.write_points(influx_payload)
diff --git a/varken/structures.py b/varken/structures.py
index accd4fe..0672f50 100644
--- a/varken/structures.py
+++ b/varken/structures.py
@@ -4,418 +4,246 @@ from logging import getLogger
logger = getLogger('temp')
# Check for python3.6 or newer to resolve erroneous typing.NamedTuple issues
-if version_info < (3, 6):
- logger.error('Varken requires python3.6 or newer. You are on python%s.%s - Exiting...',
- version_info.major, version_info.minor)
+if version_info < (3, 6, 2):
+ logger.error('Varken requires python3.6.2 or newer. You are on python%s.%s.%s - Exiting...',
+ version_info.major, version_info.minor, version_info.micro)
exit(1)
-class Queue(NamedTuple):
- movie: dict = None
- series: dict = None
- episode: dict = None
- quality: dict = None
- size: float = None
- title: str = None
- sizeleft: float = None
- timeleft: str = None
- estimatedCompletionTime: str = None
- status: str = None
- trackedDownloadStatus: str = None
- statusMessages: list = None
- downloadId: str = None
- protocol: str = None
- id: int = None
+# Server Structures
+class InfluxServer(NamedTuple):
+ password: str = 'root'
+ port: int = 8086
+ url: str = 'localhost'
+ username: str = 'root'
class SonarrServer(NamedTuple):
- id: int = None
- url: str = None
api_key: str = None
- verify_ssl: bool = False
- missing_days: int = 0
- missing_days_run_seconds: int = 30
future_days: int = 0
future_days_run_seconds: int = 30
+ id: int = None
+ missing_days: int = 0
+ missing_days_run_seconds: int = 30
queue: bool = False
queue_run_seconds: int = 30
+ url: str = None
+ verify_ssl: bool = False
class RadarrServer(NamedTuple):
- id: int = None
- url: str = None
api_key: str = None
- verify_ssl: bool = False
- queue: bool = False
- queue_run_seconds: int = 30
get_missing: bool = False
get_missing_run_seconds: int = 30
+ id: int = None
+ queue: bool = False
+ queue_run_seconds: int = 30
+ url: str = None
+ verify_ssl: bool = False
class OmbiServer(NamedTuple):
- id: int = None
- url: str = None
api_key: str = None
- verify_ssl: bool = False
- request_type_counts: bool = False
- request_type_run_seconds: int = 30
+ id: int = None
+ issue_status_counts: bool = False
+ issue_status_run_seconds: int = 30
request_total_counts: bool = False
request_total_run_seconds: int = 30
+ request_type_counts: bool = False
+ request_type_run_seconds: int = 30
+ url: str = None
+ verify_ssl: bool = False
class TautulliServer(NamedTuple):
- id: int = None
- url: str = None
- fallback_ip: str = None
api_key: str = None
- verify_ssl: bool = None
+ fallback_ip: str = None
get_activity: bool = False
get_activity_run_seconds: int = 30
get_stats: bool = False
get_stats_run_seconds: int = 30
-
-
-class InfluxServer(NamedTuple):
- url: str = 'localhost'
- port: int = 8086
- username: str = 'root'
- password: str = 'root'
+ id: int = None
+ url: str = None
+ verify_ssl: bool = None
class SickChillServer(NamedTuple):
- id: int = None
- url: str = None
api_key: str = None
- verify_ssl: bool = False
get_missing: bool = False
get_missing_run_seconds: int = 30
+ id: int = None
+ url: str = None
+ verify_ssl: bool = False
class CiscoASAFirewall(NamedTuple):
+ get_bandwidth_run_seconds: int = 30
id: int = None
+ outside_interface: str = None
+ password: str = 'cisco'
url: str = '192.168.1.1'
username: str = 'cisco'
- password: str = 'cisco'
- outside_interface: str = None
verify_ssl: bool = False
- get_bandwidth_run_seconds: int = 30
+# Shared
+class Queue(NamedTuple):
+ downloadId: str = None
+ episode: dict = None
+ estimatedCompletionTime: str = None
+ id: int = None
+ movie: dict = None
+ protocol: str = None
+ quality: dict = None
+ series: dict = None
+ size: float = None
+ sizeleft: float = None
+ status: str = None
+ statusMessages: list = None
+ timeleft: str = None
+ title: str = None
+ trackedDownloadStatus: str = None
+
+
+# Ombi Structures
class OmbiRequestCounts(NamedTuple):
- pending: int = 0
approved: int = 0
available: int = 0
+ pending: int = 0
-class TautulliStream(NamedTuple):
- rating: str = None
- transcode_width: str = None
- labels: list = None
- stream_bitrate: str = None
- bandwidth: str = None
- optimized_version: int = None
- video_language: str = None
- parent_rating_key: str = None
- rating_key: str = None
- platform_version: str = None
- transcode_hw_decoding: int = None
- thumb: str = None
- title: str = None
- video_codec_level: str = None
- tagline: str = None
- last_viewed_at: str = None
- audio_sample_rate: str = None
- user_rating: str = None
- platform: str = None
- collections: list = None
- location: str = None
- transcode_container: str = None
- audio_channel_layout: str = None
- local: str = None
- stream_subtitle_format: str = None
- stream_video_ref_frames: str = None
- transcode_hw_encode_title: str = None
- stream_container_decision: str = None
- audience_rating: str = None
- full_title: str = None
- ip_address: str = None
- subtitles: int = None
- stream_subtitle_language: str = None
- channel_stream: int = None
- video_bitrate: str = None
- is_allow_sync: int = None
- stream_video_bitrate: str = None
- summary: str = None
- stream_audio_decision: str = None
- aspect_ratio: str = None
- audio_bitrate_mode: str = None
- transcode_hw_decode_title: str = None
- stream_audio_channel_layout: str = None
- deleted_user: int = None
- library_name: str = None
- art: str = None
- stream_video_resolution: str = None
- video_profile: str = None
- sort_title: str = None
- stream_video_codec_level: str = None
- stream_video_height: str = None
- year: str = None
- stream_duration: str = None
- stream_audio_channels: str = None
- video_language_code: str = None
- transcode_key: str = None
- transcode_throttled: int = None
- container: str = None
- stream_audio_bitrate: str = None
- user: str = None
- selected: int = None
- product_version: str = None
- subtitle_location: str = None
- transcode_hw_requested: int = None
- video_height: str = None
- state: str = None
- is_restricted: int = None
- email: str = None
- stream_container: str = None
- transcode_speed: str = None
- video_bit_depth: str = None
- stream_audio_sample_rate: str = None
- grandparent_title: str = None
- studio: str = None
- transcode_decision: str = None
- video_width: str = None
- bitrate: str = None
- machine_id: str = None
- originally_available_at: str = None
- video_frame_rate: str = None
- synced_version_profile: str = None
- friendly_name: str = None
- audio_profile: str = None
- optimized_version_title: str = None
- platform_name: str = None
- stream_video_language: str = None
- keep_history: int = None
- stream_audio_codec: str = None
- stream_video_codec: str = None
- grandparent_thumb: str = None
- synced_version: int = None
- transcode_hw_decode: str = None
- user_thumb: str = None
- stream_video_width: str = None
- height: str = None
- stream_subtitle_decision: str = None
- audio_codec: str = None
- parent_title: str = None
- guid: str = None
- audio_language_code: str = None
- transcode_video_codec: str = None
- transcode_audio_codec: str = None
- stream_video_decision: str = None
- user_id: int = None
- transcode_height: str = None
- transcode_hw_full_pipeline: int = None
- throttled: str = None
- quality_profile: str = None
- width: str = None
- live: int = None
- stream_subtitle_forced: int = None
- media_type: str = None
- video_resolution: str = None
- stream_subtitle_location: str = None
- do_notify: int = None
- video_ref_frames: str = None
- stream_subtitle_language_code: str = None
- audio_channels: str = None
- stream_audio_language_code: str = None
- optimized_version_profile: str = None
- relay: int = None
- duration: str = None
- rating_image: str = None
- is_home_user: int = None
- is_admin: int = None
- ip_address_public: str = None
- allow_guest: int = None
- transcode_audio_channels: str = None
- stream_audio_channel_layout_: str = None
- media_index: str = None
- stream_video_framerate: str = None
- transcode_hw_encode: str = None
- grandparent_rating_key: str = None
- original_title: str = None
- added_at: str = None
- banner: str = None
- bif_thumb: str = None
- parent_media_index: str = None
- live_uuid: str = None
- audio_language: str = None
- stream_audio_bitrate_mode: str = None
- username: str = None
- subtitle_decision: str = None
- children_count: str = None
- updated_at: str = None
- player: str = None
- subtitle_format: str = None
- file: str = None
- file_size: str = None
- session_key: str = None
- id: str = None
- subtitle_container: str = None
- genres: list = None
- stream_video_language_code: str = None
- indexes: int = None
- video_decision: str = None
- stream_audio_language: str = None
- writers: list = None
- actors: list = None
- progress_percent: str = None
- audio_decision: str = None
- subtitle_forced: int = None
- profile: str = None
- product: str = None
- view_offset: str = None
- type: str = None
- audience_rating_image: str = None
- audio_bitrate: str = None
- section_id: str = None
- stream_subtitle_codec: str = None
- subtitle_codec: str = None
- video_codec: str = None
- device: str = None
- stream_video_bit_depth: str = None
- video_framerate: str = None
- transcode_hw_encoding: int = None
- transcode_protocol: str = None
- shared_libraries: list = None
- stream_aspect_ratio: str = None
- content_rating: str = None
- session_id: str = None
- directors: list = None
- parent_thumb: str = None
- subtitle_language_code: str = None
- transcode_progress: int = None
- subtitle_language: str = None
- stream_subtitle_container: str = None
- sub_type: str = None
- extra_type: str = None
-
-
-class TVShow(NamedTuple):
- seriesId: int = None
- episodeFileId: int = None
- seasonNumber: int = None
- episodeNumber: int = None
- title: str = None
- airDate: str = None
- airDateUtc: str = None
- overview: str = None
- episodeFile: dict = None
- hasFile: bool = None
- monitored: bool = None
- unverifiedSceneNumbering: bool = None
- absoluteEpisodeNumber: int = None
- sceneAbsoluteEpisodeNumber: int = None
- sceneEpisodeNumber: int = None
- sceneSeasonNumber: int = None
- series: dict = None
- id: int = None
-
-
-class Movie(NamedTuple):
- title: str = None
- alternativeTitles: list = None
- secondaryYearSourceId: int = None
- sortTitle: str = None
- sizeOnDisk: int = None
- status: str = None
- overview: str = None
- inCinemas: str = None
- images: list = None
- downloaded: bool = None
- year: int = None
- secondaryYear: str = None
- hasFile: bool = None
- youTubeTrailerId: str = None
- studio: str = None
- path: str = None
- profileId: int = None
- pathState: str = None
- monitored: bool = None
- minimumAvailability: str = None
- isAvailable: bool = None
- folderName: str = None
- runtime: int = None
- lastInfoSync: str = None
- cleanTitle: str = None
- imdbId: str = None
- tmdbId: int = None
- titleSlug: str = None
- genres: list = None
- tags: list = None
- added: str = None
- ratings: dict = None
- movieFile: dict = None
- qualityProfileId: int = None
- physicalRelease: str = None
- physicalReleaseNote: str = None
- website: str = None
- id: int = None
-
-
-class OmbiMovieRequest(NamedTuple):
- theMovieDbId: int = None
- issueId: None = None
- issues: None = None
- subscribed: bool = None
- showSubscribe: bool = None
- rootPathOverride: int = None
- qualityOverride: int = None
- imdbId: str = None
- overview: str = None
- posterPath: str = None
- releaseDate: str = None
- digitalReleaseDate: None = None
- status: str = None
- background: str = None
- released: bool = None
- digitalRelease: bool = None
- title: str = None
- approved: bool = None
- markedAsApproved: str = None
- requestedDate: str = None
- available: bool = None
- markedAsAvailable: None = None
- requestedUserId: str = None
- denied: bool = None
- markedAsDenied: str = None
- deniedReason: None = None
- requestType: int = None
- requestedUser: dict = None
- canApprove: bool = None
- id: int = None
+class OmbiIssuesCounts(NamedTuple):
+ inProgress: int = 0
+ pending: int = 0
+ resolved: int = 0
class OmbiTVRequest(NamedTuple):
- tvDbId: int = None
- imdbId: str = None
- qualityOverride: None = None
- rootFolder: None = None
- overview: str = None
- title: str = None
- posterPath: str = None
background: str = None
- releaseDate: str = None
- status: str = None
- totalSeasons: int = None
childRequests: list = None
+ denied: bool = None
+ deniedReason: None = None
id: int = None
+ imdbId: str = None
+ markedAsDenied: str = None
+ overview: str = None
+ posterPath: str = None
+ qualityOverride: None = None
+ releaseDate: str = None
+ rootFolder: None = None
+ status: str = None
+ title: str = None
+ totalSeasons: int = None
+ tvDbId: int = None
+class OmbiMovieRequest(NamedTuple):
+ approved: bool = None
+ available: bool = None
+ background: str = None
+ canApprove: bool = None
+ denied: bool = None
+ deniedReason: None = None
+ digitalRelease: bool = None
+ digitalReleaseDate: None = None
+ id: int = None
+ imdbId: str = None
+ issueId: None = None
+ issues: None = None
+ markedAsApproved: str = None
+ markedAsAvailable: None = None
+ markedAsDenied: str = None
+ overview: str = None
+ posterPath: str = None
+ qualityOverride: int = None
+ released: bool = None
+ releaseDate: str = None
+ requestedDate: str = None
+ requestedUser: dict = None
+ requestedUserId: str = None
+ requestType: int = None
+ rootPathOverride: int = None
+ showSubscribe: bool = None
+ status: str = None
+ subscribed: bool = None
+ theMovieDbId: int = None
+ title: str = None
+
+
+# Sonarr
+class SonarrTVShow(NamedTuple):
+ absoluteEpisodeNumber: int = None
+ airDate: str = None
+ airDateUtc: str = None
+ episodeFile: dict = None
+ episodeFileId: int = None
+ episodeNumber: int = None
+ hasFile: bool = None
+ id: int = None
+ lastSearchTime: str = None
+ monitored: bool = None
+ overview: str = None
+ sceneAbsoluteEpisodeNumber: int = None
+ sceneEpisodeNumber: int = None
+ sceneSeasonNumber: int = None
+ seasonNumber: int = None
+ series: dict = None
+ seriesId: int = None
+ title: str = None
+ unverifiedSceneNumbering: bool = None
+
+
+# Radarr
+class RadarrMovie(NamedTuple):
+ added: str = None
+ addOptions: str = None
+ alternativeTitles: list = None
+ certification: str = None
+ cleanTitle: str = None
+ downloaded: bool = None
+ folderName: str = None
+ genres: list = None
+ hasFile: bool = None
+ id: int = None
+ images: list = None
+ imdbId: str = None
+ inCinemas: str = None
+ isAvailable: bool = None
+ lastInfoSync: str = None
+ minimumAvailability: str = None
+ monitored: bool = None
+ movieFile: dict = None
+ overview: str = None
+ path: str = None
+ pathState: str = None
+ physicalRelease: str = None
+ physicalReleaseNote: str = None
+ profileId: int = None
+ qualityProfileId: int = None
+ ratings: dict = None
+ runtime: int = None
+ secondaryYear: str = None
+ secondaryYearSourceId: int = None
+ sizeOnDisk: int = None
+ sortTitle: str = None
+ status: str = None
+ studio: str = None
+ tags: list = None
+ title: str = None
+ titleSlug: str = None
+ tmdbId: int = None
+ website: str = None
+ year: int = None
+ youTubeTrailerId: str = None
+
+
+# Sickchill
class SickChillTVShow(NamedTuple):
airdate: str = None
airs: str = None
+ episode: int = None
ep_name: str = None
ep_plot: str = None
- episode: int = None
indexerid: int = None
network: str = None
paused: int = None
@@ -425,3 +253,198 @@ class SickChillTVShow(NamedTuple):
show_status: str = None
tvdbid: int = None
weekday: int = None
+
+
+# Tautulli
+class TautulliStream(NamedTuple):
+ actors: list = None
+ added_at: str = None
+ allow_guest: int = None
+ art: str = None
+ aspect_ratio: str = None
+ audience_rating: str = None
+ audience_rating_image: str = None
+ audio_bitrate: str = None
+ audio_bitrate_mode: str = None
+ audio_channels: str = None
+ audio_channel_layout: str = None
+ audio_codec: str = None
+ audio_decision: str = None
+ audio_language: str = None
+ audio_language_code: str = None
+ audio_profile: str = None
+ audio_sample_rate: str = None
+ bandwidth: str = None
+ banner: str = None
+ bif_thumb: str = None
+ bitrate: str = None
+ channel_icon: str = None
+ channel_stream: int = None
+ channel_title: str = None
+ children_count: str = None
+ collections: list = None
+ container: str = None
+ content_rating: str = None
+ deleted_user: int = None
+ device: str = None
+ directors: list = None
+ do_notify: int = None
+ duration: str = None
+ email: str = None
+ extra_type: str = None
+ file: str = None
+ file_size: str = None
+ friendly_name: str = None
+ full_title: str = None
+ genres: list = None
+ grandparent_rating_key: str = None
+ grandparent_thumb: str = None
+ grandparent_title: str = None
+ guid: str = None
+ height: str = None
+ id: str = None
+ indexes: int = None
+ ip_address: str = None
+ ip_address_public: str = None
+ is_admin: int = None
+ is_allow_sync: int = None
+ is_home_user: int = None
+ is_restricted: int = None
+ keep_history: int = None
+ labels: list = None
+ last_viewed_at: str = None
+ library_name: str = None
+ live: int = None
+ live_uuid: str = None
+ local: str = None
+ location: str = None
+ machine_id: str = None
+ media_index: str = None
+ media_type: str = None
+ optimized_version: int = None
+ optimized_version_profile: str = None
+ optimized_version_title: str = None
+ originally_available_at: str = None
+ original_title: str = None
+ parent_media_index: str = None
+ parent_rating_key: str = None
+ parent_thumb: str = None
+ parent_title: str = None
+ platform: str = None
+ platform_name: str = None
+ platform_version: str = None
+ player: str = None
+ product: str = None
+ product_version: str = None
+ profile: str = None
+ progress_percent: str = None
+ quality_profile: str = None
+ rating: str = None
+ rating_image: str = None
+ rating_key: str = None
+ relay: int = None
+ section_id: str = None
+ selected: int = None
+ session_id: str = None
+ session_key: str = None
+ shared_libraries: list = None
+ sort_title: str = None
+ state: str = None
+ stream_aspect_ratio: str = None
+ stream_audio_bitrate: str = None
+ stream_audio_bitrate_mode: str = None
+ stream_audio_channels: str = None
+ stream_audio_channel_layout: str = None
+ stream_audio_channel_layout_: str = None
+ stream_audio_codec: str = None
+ stream_audio_decision: str = None
+ stream_audio_language: str = None
+ stream_audio_language_code: str = None
+ stream_audio_sample_rate: str = None
+ stream_bitrate: str = None
+ stream_container: str = None
+ stream_container_decision: str = None
+ stream_duration: str = None
+ stream_subtitle_codec: str = None
+ stream_subtitle_container: str = None
+ stream_subtitle_decision: str = None
+ stream_subtitle_forced: int = None
+ stream_subtitle_format: str = None
+ stream_subtitle_language: str = None
+ stream_subtitle_language_code: str = None
+ stream_subtitle_location: str = None
+ stream_video_bitrate: str = None
+ stream_video_bit_depth: str = None
+ stream_video_codec: str = None
+ stream_video_codec_level: str = None
+ stream_video_decision: str = None
+ stream_video_framerate: str = None
+ stream_video_height: str = None
+ stream_video_language: str = None
+ stream_video_language_code: str = None
+ stream_video_ref_frames: str = None
+ stream_video_resolution: str = None
+ stream_video_width: str = None
+ studio: str = None
+ subtitles: int = None
+ subtitle_codec: str = None
+ subtitle_container: str = None
+ subtitle_decision: str = None
+ subtitle_forced: int = None
+ subtitle_format: str = None
+ subtitle_language: str = None
+ subtitle_language_code: str = None
+ subtitle_location: str = None
+ sub_type: str = None
+ summary: str = None
+ synced_version: int = None
+ synced_version_profile: str = None
+ tagline: str = None
+ throttled: str = None
+ thumb: str = None
+ title: str = None
+ transcode_audio_channels: str = None
+ transcode_audio_codec: str = None
+ transcode_container: str = None
+ transcode_decision: str = None
+ transcode_height: str = None
+ transcode_hw_decode: str = None
+ transcode_hw_decode_title: str = None
+ transcode_hw_decoding: int = None
+ transcode_hw_encode: str = None
+ transcode_hw_encode_title: str = None
+ transcode_hw_encoding: int = None
+ transcode_hw_full_pipeline: int = None
+ transcode_hw_requested: int = None
+ transcode_key: str = None
+ transcode_progress: int = None
+ transcode_protocol: str = None
+ transcode_speed: str = None
+ transcode_throttled: int = None
+ transcode_video_codec: str = None
+ transcode_width: str = None
+ type: str = None
+ updated_at: str = None
+ user: str = None
+ username: str = None
+ user_id: int = None
+ user_rating: str = None
+ user_thumb: str = None
+ video_bitrate: str = None
+ video_bit_depth: str = None
+ video_codec: str = None
+ video_codec_level: str = None
+ video_decision: str = None
+ video_framerate: str = None
+ video_frame_rate: str = None
+ video_height: str = None
+ video_language: str = None
+ video_language_code: str = None
+ video_profile: str = None
+ video_ref_frames: str = None
+ video_resolution: str = None
+ video_width: str = None
+ view_offset: str = None
+ width: str = None
+ writers: list = None
+ year: str = None
diff --git a/varken/tautulli.py b/varken/tautulli.py
index 5bde35a..336bba9 100644
--- a/varken/tautulli.py
+++ b/varken/tautulli.py
@@ -108,6 +108,8 @@ class TautulliAPI(object):
"quality": quality,
"video_decision": video_decision.title(),
"transcode_decision": decision.title(),
+ "transcode_hw_decoding": session.transcode_hw_decoding,
+ "transcode_hw_encoding": session.transcode_hw_encoding,
"media_type": session.media_type.title(),
"audio_codec": session.audio_codec.upper(),
"audio_profile": session.audio_profile.upper(),