forked from IDmedia/hass-medusa
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ef6e88b
Showing
4 changed files
with
271 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
Medusa Upcoming TV Shows | ||
============ | ||
A Home Assistant sensor that pulls the latest upcoming TV shows from Medusa. This is a fork of Youdroid's [SickChill Wanted Tv Shows](https://github.com/youdroid/home-assistant-sickchill) with a few fixes such as compability with the awesome [Flex Table](https://github.com/custom-cards/flex-table-card) and better airdate handling. The sensor should work with any Sick* fork (SickChill, SickRage, etc.). | ||
|
||
## Installation using HACS (Recommended) | ||
1. Navigate to HACS and add a custom repository | ||
**URL:** https://git.idmedia.no/home-assistant/hass-medusa | ||
**Category:** Integration | ||
2. Install module as usual | ||
3. Restart Home Assistant | ||
|
||
## Configuration | ||
The sensor is compatible with [Upcoming Media Card](https://github.com/custom-cards/upcoming-media-card) and [Flex Table](https://github.com/custom-cards/flex-table-card) so you may install either depending on your preferences. | ||
|
||
| Key | Default | Required | Description | ||
| --- | --- | --- | --- | ||
| token | | yes | Your Medusa token (Config > General > Interface > API Key > Generate) | ||
| name | medusa | no | Name of the sensor. | ||
| host | localhost | no | The host which Medusa is running on. | ||
| port | 8081 | no | The port which Medusa is running on. | ||
| protocol | http | no | The HTTP protocol used by Medusa. | ||
| sort | name | no | Parameter to sort TV Shows **[date, name]** | ||
| webroot | | no | WebRoot parameter if you change it in config.ini (Syntax : **/newWebRoot**) | ||
|
||
## Example | ||
Add the following to your `configuration.yaml`: | ||
``` | ||
sensor: | ||
- platform: medusa | ||
name: medusa | ||
host: !secret medusa_host | ||
token: !secret medusa_token | ||
sort: date | ||
``` | ||
|
||
Add the following to your `lovelace.yaml`: | ||
``` | ||
- type: 'custom:flex-table-card' | ||
title: Upcoming TV Shows | ||
clickable: false | ||
max_rows: 5 | ||
strict: true | ||
entities: | ||
include: sensor.medusa | ||
columns: | ||
- data: data | ||
name: ' ' | ||
modify: >- | ||
x.poster ? '<img src="' + x.poster + '" height="100">' : | ||
'<img src="https://via.placeholder.com/68x100/?text=No%20poster" height="100">'; | ||
- data: data | ||
name: ' ' | ||
modify: >- | ||
const hourDiff = (Date.parse(x.airdate) - Date.now()); | ||
const secDiff = hourDiff / 1000; | ||
const minDiff = hourDiff / 60 / 1000; | ||
const hDiff = hourDiff / 3600 / 1000; | ||
const dDiff = hourDiff / 3600 / 1000 / 24; | ||
const days = Math.floor(dDiff); | ||
const hours = Math.floor(hDiff - (days * 24)); | ||
const minutes = Math.floor(minDiff - 60 * Math.floor(hDiff)); | ||
const tdays = (Math.abs(days) > 1) ? days + " days " : ((Math.abs(days) == 1) ? days + " day " : ""); | ||
const thours = (Math.abs(hours) > 1) ? hours + " hours " : ((Math.abs(hours) == 1) ? hours + " hour " : ""); | ||
const tminutes = (Math.abs(minutes) > 1) ? minutes + " minutes " : ((Math.abs(minutes) == 1) ? minutes + " minute " : ""); | ||
const episodeNumber = x.number ? x.number : ''; | ||
const episodeTitle = x.episode ? x.episode : ''; | ||
const title = x.title ? x.title : ''; | ||
const subTitle = [episodeNumber, episodeTitle].filter(Boolean).join(" - "); | ||
const timeLeft = (hourDiff > 0) ? tdays + thours + tminutes : '<span style="color:red;font-weight:bold;">Not downloaded yet</span>'; | ||
if (title) { | ||
"<b>" + title + "</b><br />" + | ||
subTitle + "<br />" + | ||
timeLeft | ||
} else { | ||
null; | ||
} | ||
``` |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"domain": "medusa", | ||
"name": "Medusa Upcoming TV Shows", | ||
"documentation": "https://github.com/IDmedia/hass-medusa", | ||
"requirements": [], | ||
"dependencies": [], | ||
"codeowners": ["@IDmedia"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
"""Platform for sensor integration.""" | ||
import json | ||
import logging | ||
import os | ||
import re | ||
from datetime import datetime | ||
import homeassistant.helpers.config_validation as cv | ||
import requests | ||
import voluptuous as vol | ||
from homeassistant.components.switch import PLATFORM_SCHEMA | ||
from homeassistant.const import * | ||
from homeassistant.helpers.entity import Entity | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
DEFAULT_NAME = "medusa" | ||
DEFAULT_HOST = "localhost" | ||
DEFAULT_PROTO = "http" | ||
DEFAULT_PORT = "8081" | ||
DEFAULT_SORTING = "name" | ||
CONF_SORTING = "sort" | ||
CONF_WEB_ROOT = "webroot" | ||
DEFAULT_WEB_ROOT = "" | ||
|
||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
vol.Required(CONF_TOKEN): cv.string, | ||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string, | ||
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, | ||
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTO): cv.string, | ||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, | ||
vol.Optional(CONF_SORTING, default=DEFAULT_SORTING): cv.string, | ||
vol.Optional(CONF_WEB_ROOT, default=DEFAULT_WEB_ROOT): cv.string | ||
}) | ||
|
||
|
||
def setup_platform(hass, config, add_entities, discovery_info=None): | ||
add_entities([MedusaSensor(config, hass)]) | ||
|
||
|
||
class MedusaSensor(Entity): | ||
"""Representation of a Sensor.""" | ||
|
||
def __init__(self, config, hass): | ||
self._state = None | ||
self._name = config.get(CONF_NAME) | ||
self.token = config.get(CONF_TOKEN) | ||
self.host = config.get(CONF_HOST) | ||
self.protocol = config.get(CONF_PROTOCOL) | ||
self.port = config.get(CONF_PORT) | ||
self.base_dir = str(hass.config.path()) + '/' | ||
self.data = None | ||
self.sort = config.get(CONF_SORTING) | ||
self.web_root = config.get(CONF_WEB_ROOT) | ||
|
||
@property | ||
def name(self): | ||
"""Return the name of the sensor.""" | ||
return self._name | ||
|
||
@property | ||
def state(self): | ||
"""Return the state of the sensor.""" | ||
return self._state | ||
|
||
@property | ||
def device_state_attributes(self): | ||
"""Return the state attributes.""" | ||
return self.data | ||
|
||
def update(self): | ||
attributes = {} | ||
card_json = [] | ||
card_shows = [] | ||
init = {} | ||
"""Initialized JSON Object""" | ||
init['title_default'] = '$title' | ||
init['line1_default'] = '$episode' | ||
init['line2_default'] = '$release' | ||
init['line3_default'] = '$number - $rating - $runtime' | ||
init['line4_default'] = '$genres' | ||
init['icon'] = 'mdi:eye-off' | ||
card_json.append(init) | ||
|
||
tv_shows = self.get_infos(self.protocol, self.host, self.port, self.token, self.web_root, 'future') | ||
|
||
directory = "{0}/www/custom-lovelace/{1}/images/".format(self.base_dir, self._name) | ||
if not os.path.exists(directory): | ||
os.makedirs(directory) | ||
regex_img = re.compile(r'\d+-(fanart|poster)\.jpg') | ||
lst_images = list(filter(regex_img.search, | ||
os.listdir(directory))) | ||
|
||
for category in tv_shows["data"]: | ||
for show in tv_shows["data"].get(category): | ||
|
||
airdate_str = show["airdate"] + ' ' + show["airs"] | ||
airdate_dt = datetime.strptime(airdate_str, "%Y-%m-%d %A %I:%M %p") | ||
airdate = airdate_dt.strftime("%Y-%m-%d %H:%M:%SZ") | ||
|
||
number = "S" + str(show["season"]).zfill(2) + "E" + str(show["episode"]).zfill(2) | ||
|
||
banner = "{0}-banner.jpg".format(show["indexerid"]) | ||
fanart = "{0}-fanart.jpg".format(show["indexerid"]) | ||
poster = "{0}-poster.jpg".format(show["indexerid"]) | ||
|
||
card_items = {} | ||
card_items["airdate"] = airdate | ||
card_items["number"] = number | ||
card_items["category"] = category | ||
card_items["studio"] = show["network"] | ||
card_items["title"] = show["show_name"] | ||
card_items["episode"] = show["ep_name"] | ||
card_items["release"] = '$day, $date $time' | ||
card_items["poster"] = self.add_poster(lst_images, directory, poster, show["indexerid"], card_items) | ||
card_items["fanart"] = self.add_fanart(lst_images, directory, fanart, show["indexerid"], card_items) | ||
card_items["banner"] = self.add_banner(lst_images, directory, banner, show["indexerid"], card_items) | ||
|
||
card_shows.append(card_items) | ||
|
||
if self.sort == "date": | ||
card_shows.sort(key=lambda x: x.get("airdate")) | ||
card_json = card_json + card_shows | ||
attributes["data"] = card_json | ||
self._state = tv_shows["result"] | ||
self.data = attributes | ||
self.delete_old_tvshows(lst_images, directory) | ||
|
||
def get_infos(self, proto, host, port, token, web_root, cmd): | ||
url = "{0}://{1}:{2}{3}/api/{4}/?cmd={5}".format( | ||
proto, host, port, web_root, token, cmd) | ||
ifs_movies = requests.get(url).json() | ||
return ifs_movies | ||
|
||
def add_poster(self, lst_images, directory, poster, id, card_items): | ||
if poster in lst_images: | ||
lst_images.remove(poster) | ||
else: | ||
img_data = requests.get("{0}://{1}:{2}{3}/api/v1/{4}/?cmd=show.getposter&indexerid={5}".format(self.protocol, self.host, self.port, self.web_root, self.token, id)) | ||
|
||
if not img_data.status_code.__eq__(200): | ||
_LOGGER.error(card_items) | ||
return "" | ||
|
||
try: | ||
open(directory + poster, 'wb').write(img_data.content) | ||
except IOError: | ||
_LOGGER.error("Unable to create file.") | ||
return "/local/custom-lovelace/{0}/images/{1}".format(self._name, poster) | ||
|
||
def add_fanart(self, lst_images, directory, fanart, id, card_items): | ||
if fanart in lst_images: | ||
lst_images.remove(fanart) | ||
else: | ||
img_data = requests.get("{0}://{1}:{2}{3}/api/v1/{4}/?cmd=show.getfanart&indexerid={5}".format(self.protocol, self.host, self.port, self.web_root, self.token, id)) | ||
|
||
if not img_data.status_code.__eq__(200): | ||
return "" | ||
|
||
try: | ||
open(directory + fanart, 'wb').write(img_data.content) | ||
except IOError: | ||
_LOGGER.error("Unable to create file.") | ||
return "/local/custom-lovelace/{0}/images/{1}".format(self._name, fanart) | ||
|
||
def add_banner(self, lst_images, directory, banner, id, card_items): | ||
if banner in lst_images: | ||
lst_images.remove(banner) | ||
else: | ||
img_data = requests.get("{0}://{1}:{2}/api/v1/{3}/?cmd=show.getbanner&indexerid={4}".format(self.protocol, self.host, self.port, self.token, id)) | ||
|
||
if not img_data.status_code.__eq__(200): | ||
return "" | ||
|
||
try: | ||
open(directory + banner, 'wb').write(img_data.content) | ||
except IOError: | ||
_LOGGER.error("Unable to create file.") | ||
return "/local/custom-lovelace/{0}/images/{1}".format(self._name, banner) | ||
|
||
def delete_old_tvshows(self, lst_images, directory): | ||
for img in lst_images: | ||
try: | ||
os.remove(directory + img) | ||
_LOGGER.info("Delete finished tv show images") | ||
except IOError: | ||
_LOGGER.error("Unable to delete file.") |