Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamfk committed Oct 14, 2020
0 parents commit ef6e88b
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 0 deletions.
78 changes: 78 additions & 0 deletions README.md
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.
8 changes: 8 additions & 0 deletions medusa/custom_components/medusa/manifest.json
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"]
}
185 changes: 185 additions & 0 deletions medusa/custom_components/medusa/sensor.py
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.")

0 comments on commit ef6e88b

Please sign in to comment.