Skip to content

Commit 9ebc2e3

Browse files
authored
Merge pull request #73 from Makhuta/master
Release version 0.4.2 - Added config_flow-based install/config. Thank you @Makhuta!!!!!
2 parents e16e4d1 + 705df3a commit 9ebc2e3

File tree

15 files changed

+803
-466
lines changed

15 files changed

+803
-466
lines changed

README.md

Lines changed: 40 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -17,71 +17,47 @@ Read through these two resources before posting issues to GitHub or the forums.
1717
## Installation:
1818
1. Install this component by copying [these files](https://github.com/custom-components/sensor.plex_recently_added/tree/master/custom_components/plex_recently_added) to `custom_components/plex_recently_added/`.
1919
2. Install the card: [Upcoming Media Card](https://github.com/custom-cards/upcoming-media-card)
20-
3. Add the code to your `configuration.yaml` using the config options below.
21-
4. Add the code for the card to your `ui-lovelace.yaml`.
22-
5. **You will need to restart after installation for the component to start working.**
23-
24-
### Options
25-
26-
| key | default | required | description |
27-
| ---------------- | ----------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
28-
| name | Plex_Recently_Added | no | Name of the sensor. Useful to make multiple sensors with different libraries. |
29-
| token | | yes | Your Plex token [(Find your Plex token)](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/) |
30-
| host | localhost | yes | The host Plex is running on. |
31-
| port | 32400 | yes | The port Plex is running on. |
32-
| ssl | false | no | Set to true if you use SSL to access Plex. |
33-
| max | 5 | no | Max number of items to show in sensor. |
34-
| on_deck | False | no | Set to true to show "on deck" items. |
35-
| download_images | true | no | Setting this to false will turn off downloading of images, but will require certain Plex settings to work. See below. |
36-
| img_dir | '/upcoming-media-card-images/plex/' | no | This option allows you to choose a custom directory to store images in if you enable download_images. Directory must start and end with a `/`. |
37-
| ssl_cert | false | no | If you provide your own SSL certificate in Plex's network settings set this to true. |
38-
| section_types | movie, show | no | Allows you to specify which section types to consider [movie, show]. |
39-
| image_resolution | 200 | no | Allows you to change the resolution of the generated images (in px), useful to display higher quality images as a background somewhere. |
40-
| exclude_keywords | | no | Allows you to specify a list of keywords to be exclude from the sensor if in the title. |
41-
42-
#### By default this addon automatically downloads images from Plex to your /www/custom-lovelace/upcoming-media-card/ directory. The directory is automatically created & only images reported in the upcoming list are downloaded. Images are small in size and are removed automatically when no longer needed. Currently & unfortunately, this may not work on all systems.
43-
44-
#### If you prefer to not download the images you may set download_images to false, but you either have to set "Secure connections" to "preferred" or "disabled" (no SSL) or have a custom certificate set (these options are found in your Plex server's network settings). This is needed because the default SSL certificate supplied by Plex is for their own domain and not for your Plex server. Your server also needs to be "fully accessible outside your network" if you wish to be able to see images remotely. If your Plex server provides it's own certificate you only need to set ssl_cert to true and download_images to false.
45-
46-
</br></br>
20+
3. Add the code for the card to your `ui-lovelace.yaml`.
21+
4. **You will need to restart after installation for the component to start working.**
22+
23+
### Adding device
24+
To add the **Plex Recently added** integration to your Home Assistant, use this My button:
25+
26+
<a href="https://my.home-assistant.io/redirect/config_flow_start?domain=plex_recently_added" class="my badge" target="_blank"><img src="https://my.home-assistant.io/badges/config_flow_start.svg"></a>
27+
28+
<details><summary style="list-style: none"><h3><b style="cursor: pointer">Manual configuration steps</b></h3></summary>
29+
30+
If the above My button doesn’t work, you can also perform the following steps manually:
31+
32+
- Browse to your Home Assistant instance.
33+
34+
- Go to [Settings > Devices & Services](https://my.home-assistant.io/redirect/integrations/).
35+
36+
- In the bottom right corner, select the [Add Integration button.](https://my.home-assistant.io/redirect/config_flow_start?domain=plex_recently_added)
37+
38+
- From the list, select **Plex Recently added**.
39+
40+
- Follow the instructions on screen to complete the setup.
41+
</details>
42+
43+
The number of items in sensor, library types, libraries in general, excluded words and show "on deck" options can be changed later.
44+
4745
**Do not just copy examples, please use config options above to build your own!**
48-
### Sample for minimal config needed in configuration.yaml:
49-
```yaml
50-
sensor:
51-
- platform: plex_recently_added
52-
token: YOUR_PLEX_TOKEN
53-
host: 192.168.1.42
54-
port: 32400
55-
```
56-
### Sample for ui-lovelace.yaml:
57-
```yaml
58-
- type: custom:upcoming-media-card
59-
entity: sensor.plex_recently_added
60-
title: Recently Downloaded
61-
```
62-
### Multiple sensor sample for configuration.yaml:
63-
```yaml
64-
- platform: plex_recently_added
65-
name: Recently Added Movies # will create sensor.recently_added_movies
66-
token: !secret token
67-
host: !secret host
68-
port: 32400
69-
section_types:
70-
- movie
71-
72-
- platform: plex_recently_added
73-
name: Recently Added TV # will create sensor.recently_added_tv
74-
token: !secret token
75-
host: !secret host
76-
port: 32400
77-
section_types:
78-
- show
79-
exclude_keywords:
80-
- Walking dead
81-
- kardashians
82-
```
83-
84-
## \*Currently genres, rating, and studio only work for Movies
46+
47+
## FAQ:
48+
### When I tried it said *"User already configured"*
49+
This is because the integration uses the Plex token as a part of its *unique_id* so it does not colide with other instances of the same integration
50+
51+
### I want to change the config of the integration, how do I do it?
52+
This is very simple, when you go to the *'Settings/Devices & services/Plex Recently Added'* you will see your instance of the Plex Recently Added integration and on the right side you will see **Configure** button, when you press it you can change all necessary config you might need to change and click submit, the instance then should restart and show new values basen on your new settings.
53+
</br><small>(If you want to change Plex address, token or sensors prefix name you will need to readd the integration with your new parameters)</small>
54+
55+
### The number of items in sensor is not the amount I set it to be
56+
The sensor you most likely mean is the merged sensor which shows all the data (also sorted) that are in the other sensors, meaning if you've set your man number if values to 7 and you've got 3 section types (movie, show, artist) the total number of items in the merged sensor will be 21 (7 *<small>(for max)</small>* * 3 *<small>(for section types)</small>*)
57+
58+
### My sensor is not showing any values and there are no errors
59+
This may be caused by incorrectly set *Libraries to consider* you can change them in [Config](#i-want-to-change-the-config-of-the-integration-how-do-i-do-it) where if you've configured your Plex token and address right will now show all libraries in dropdown selection.
60+
8561
### Card Content Defaults
8662

8763
| key | default | example |
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,78 @@
1+
from homeassistant.config_entries import ConfigEntry
2+
from homeassistant.const import Platform
3+
from homeassistant.core import HomeAssistant
4+
from homeassistant.exceptions import ConfigEntryNotReady
5+
from homeassistant.const import (
6+
CONF_NAME,
7+
CONF_API_KEY,
8+
CONF_HOST,
9+
CONF_PORT,
10+
CONF_SSL
11+
)
112

13+
from .const import (
14+
DOMAIN,
15+
CONF_TOKEN,
16+
CONF_MAX,
17+
CONF_SECTION_TYPES,
18+
CONF_SECTION_LIBRARIES,
19+
CONF_EXCLUDE_KEYWORDS,
20+
CONF_ON_DECK,
21+
CONF_VERIFY_SSL
22+
)
23+
24+
25+
from .coordinator import PlexDataCoordinator
26+
from .helpers import setup_client
27+
from .plex_api import (
28+
FailedToLogin,
29+
)
30+
from .redirect import ImagesRedirect
31+
32+
33+
PLATFORMS = [
34+
Platform.SENSOR
35+
]
36+
37+
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
38+
try:
39+
client = await setup_client(
40+
hass,
41+
config_entry.data[CONF_NAME],
42+
config_entry.data[CONF_SSL],
43+
config_entry.data[CONF_API_KEY],
44+
config_entry.data[CONF_MAX],
45+
config_entry.data[CONF_ON_DECK],
46+
config_entry.data[CONF_HOST],
47+
config_entry.data[CONF_PORT],
48+
config_entry.data.get(CONF_SECTION_TYPES, []),
49+
config_entry.data.get(CONF_SECTION_LIBRARIES, []),
50+
config_entry.data.get(CONF_EXCLUDE_KEYWORDS, []),
51+
config_entry.data[CONF_VERIFY_SSL],
52+
)
53+
except FailedToLogin as err:
54+
raise ConfigEntryNotReady("Failed to Log-in") from err
55+
coordinator = PlexDataCoordinator(hass, client)
56+
57+
hass.http.register_view(ImagesRedirect(config_entry))
58+
await coordinator.async_config_entry_first_refresh()
59+
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = coordinator
60+
61+
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
62+
config_entry.async_on_unload(config_entry.add_update_listener(update_listener))
63+
64+
return True
65+
66+
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
67+
"""Unload Plex config entry."""
68+
if unload_ok := await hass.config_entries.async_unload_platforms(
69+
config_entry, PLATFORMS
70+
):
71+
del hass.data[DOMAIN][config_entry.entry_id]
72+
if not hass.data[DOMAIN]:
73+
del hass.data[DOMAIN]
74+
return unload_ok
75+
76+
async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
77+
"""Handle options update."""
78+
await hass.config_entries.async_reload(config_entry.entry_id)
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from typing import Any
2+
3+
import voluptuous as vol
4+
5+
from homeassistant.helpers.selector import (
6+
SelectSelector,
7+
SelectSelectorConfig,
8+
SelectSelectorMode,
9+
TextSelector,
10+
TextSelectorConfig,
11+
ConstantSelector,
12+
ConstantSelectorConfig
13+
)
14+
from homeassistant.config_entries import ConfigEntry
15+
from homeassistant.core import callback
16+
from homeassistant.config_entries import ConfigFlow
17+
from homeassistant.const import (
18+
CONF_API_KEY,
19+
CONF_NAME,
20+
CONF_HOST,
21+
CONF_PORT,
22+
CONF_SSL
23+
)
24+
25+
from .const import (
26+
DOMAIN,
27+
DEFAULT_NAME,
28+
CONF_MAX,
29+
CONF_SECTION_TYPES,
30+
ALL_SECTION_TYPES,
31+
CONF_SECTION_LIBRARIES,
32+
CONF_EXCLUDE_KEYWORDS,
33+
CONF_SECTION_LIBRARIES_LABEL,
34+
CONF_EXCLUDE_KEYWORDS_LABEL,
35+
CONF_ON_DECK,
36+
CONF_VERIFY_SSL
37+
)
38+
39+
from .helpers import setup_client
40+
from .plex_api import (
41+
FailedToLogin,
42+
)
43+
from .options_flow import PlexOptionFlow
44+
45+
PLEX_SCHEMA = vol.Schema({
46+
vol.Optional(CONF_NAME, default=''): vol.All(str),
47+
vol.Required(CONF_HOST, default='localhost'): vol.All(str),
48+
vol.Required(CONF_PORT, default=32400): vol.All(vol.Coerce(int), vol.Range(min=0)),
49+
vol.Required(CONF_API_KEY): vol.All(str),
50+
vol.Optional(CONF_SSL, default=False): vol.All(bool),
51+
vol.Optional(CONF_VERIFY_SSL, default=True): vol.All(bool),
52+
vol.Optional(CONF_MAX, default=5): vol.All(vol.Coerce(int), vol.Range(min=0)),
53+
vol.Optional(CONF_ON_DECK, default=False): vol.All(bool),
54+
vol.Optional(CONF_SECTION_TYPES, default={"movie", "show"}): SelectSelector(SelectSelectorConfig(options=ALL_SECTION_TYPES, mode=SelectSelectorMode.DROPDOWN ,multiple=True)),
55+
vol.Optional(CONF_SECTION_LIBRARIES + "_label"): ConstantSelector(ConstantSelectorConfig(value=CONF_SECTION_LIBRARIES_LABEL)),
56+
vol.Optional(CONF_SECTION_LIBRARIES): TextSelector(TextSelectorConfig(multiple=True, multiline=False)),
57+
vol.Optional(CONF_EXCLUDE_KEYWORDS + "_label"): ConstantSelector(ConstantSelectorConfig(value=CONF_EXCLUDE_KEYWORDS_LABEL)),
58+
vol.Optional(CONF_EXCLUDE_KEYWORDS): TextSelector(TextSelectorConfig(multiple=True, multiline=False)),
59+
})
60+
61+
class PlexConfigFlow(ConfigFlow, domain=DOMAIN):
62+
"""Config flow for the Plex integration."""
63+
@staticmethod
64+
@callback
65+
def async_get_options_flow(config_entry: ConfigEntry) -> PlexOptionFlow:
66+
return PlexOptionFlow(config_entry)
67+
68+
async def async_step_user(
69+
self, user_input: dict[str, Any] | None = None
70+
):
71+
errors = {}
72+
73+
if user_input is not None:
74+
self._async_abort_entries_match({CONF_API_KEY: user_input[CONF_API_KEY]})
75+
try:
76+
await setup_client(
77+
self.hass,
78+
user_input[CONF_NAME],
79+
user_input[CONF_SSL],
80+
user_input[CONF_API_KEY],
81+
user_input[CONF_MAX],
82+
user_input[CONF_ON_DECK],
83+
user_input[CONF_HOST],
84+
user_input[CONF_PORT],
85+
user_input.get(CONF_SECTION_TYPES, []),
86+
user_input.get(CONF_SECTION_LIBRARIES, []),
87+
user_input.get(CONF_EXCLUDE_KEYWORDS, []),
88+
user_input[CONF_VERIFY_SSL],
89+
)
90+
except FailedToLogin as err:
91+
errors = {'base': 'failed_to_login'}
92+
else:
93+
return self.async_create_entry(title=user_input[CONF_NAME] if len(user_input[CONF_NAME]) > 0 else DEFAULT_NAME, data=user_input)
94+
95+
schema = self.add_suggested_values_to_schema(PLEX_SCHEMA, user_input)
96+
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from typing import Final
2+
3+
DOMAIN: Final = "plex_recently_added"
4+
5+
6+
DEFAULT_NAME: Final = 'Plex Recently Added'
7+
CONF_TOKEN: Final = 'token'
8+
CONF_MAX: Final = 'max'
9+
CONF_SECTION_TYPES: Final = 'section_types'
10+
ALL_SECTION_TYPES: Final = ["movie", "show", "artist", "photo"]
11+
CONF_SECTION_LIBRARIES: Final = 'section_libraries'
12+
CONF_EXCLUDE_KEYWORDS: Final = 'exclude_keywords'
13+
CONF_SECTION_LIBRARIES_LABEL: Final = 'Which libraries to consider:'
14+
CONF_EXCLUDE_KEYWORDS_LABEL: Final = 'Keyword to be exclude from the sensor:'
15+
CONF_ON_DECK: Final = 'on_deck'
16+
CONF_VERIFY_SSL: Final = 'verify_ssl'
17+
18+
19+
USER_AGENT: Final = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36"
20+
ACCEPTS: Final = "application/xml, text/xml;q=0.9"
21+
22+
DEFAULT_PARSE_DICT: Final = {
23+
'title_default': '$title',
24+
'line1_default': '$episode',
25+
'line2_default': '$release',
26+
'line3_default': '$number - $rating - $runtime',
27+
'line4_default': '$genres',
28+
'icon': 'mdi:eye-off'
29+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from datetime import timedelta
2+
import logging
3+
from typing import Dict, Any
4+
5+
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
6+
from homeassistant.core import HomeAssistant
7+
from homeassistant.exceptions import ConfigEntryError
8+
9+
from .const import DOMAIN
10+
from .plex_api import (
11+
PlexApi,
12+
FailedToLogin,
13+
)
14+
15+
_LOGGER = logging.getLogger(__name__)
16+
17+
class PlexDataCoordinator(DataUpdateCoordinator[Dict[str, Any]]):
18+
def __init__(self, hass: HomeAssistant, client: PlexApi):
19+
self._client = client
20+
21+
super().__init__(
22+
hass,
23+
_LOGGER,
24+
name=DOMAIN,
25+
update_method=self._async_update_data,
26+
update_interval=timedelta(minutes=10),
27+
)
28+
29+
async def _async_update_data(self) -> Dict[str, Any]:
30+
try:
31+
return await self._client.update()
32+
except FailedToLogin:
33+
raise ConfigEntryError("Failed to Log-in") from err
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from homeassistant.core import HomeAssistant
2+
from .plex_api import PlexApi
3+
4+
async def setup_client(
5+
hass: HomeAssistant,
6+
name: str,
7+
ssl: bool,
8+
token: str,
9+
max: int,
10+
on_deck: bool,
11+
host: str,
12+
port: int,
13+
section_types: list,
14+
section_libraries: list,
15+
exclude_keywords: list,
16+
verify_ssl: bool,
17+
):
18+
client = PlexApi(hass, name, ssl, token, max, on_deck, host, port, section_types, section_libraries, exclude_keywords, verify_ssl)
19+
20+
await client.update()
21+
return client

custom_components/plex_recently_added/manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"domain": "plex_recently_added",
33
"name": "Plex Recently Added",
44
"codeowners": ["@maykar"],
5+
"config_flow": true,
56
"dependencies": [],
67
"documentation": "https://github.com/custom-components/sensor.plex_recently_added",
78
"iot_class": "local_polling",

0 commit comments

Comments
 (0)