Skip to content

Commit

Permalink
add switch platform
Browse files Browse the repository at this point in the history
add entity to override platform state
add options flow
bump pynintendoparental to 0.0.2
update translations
update readme
middleware now uses bootstrap
add more info to middleware
  • Loading branch information
pantherale0 committed Sep 5, 2023
1 parent 55398a5 commit 873b926
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 26 deletions.
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ _Integration to integrate with [ha-nintendoparentalcontrols][ha-nintendoparental

**This integration will set up the following platforms.**

| Platform | Description |
| -------- | ---------------------------------- |
| `sensor` | Show per device screen time usage. |
| Platform | Description |
| -------- | ---------------------------------------------- |
| `sensor` | Show per device screen time usage. |
| `switch` | Override switch to enable or disable a device. |

## Installation

Expand All @@ -32,11 +33,19 @@ _Integration to integrate with [ha-nintendoparentalcontrols][ha-nintendoparental
1. The configuration flow should then show some additional options, don't adjust the first box as this is the session token that will be used to refresh the tokens in the background
1. Click `Submit`

## Middleware notes

The middleware to return the OAuth response back to Home Assistant is located as a static file on one of my own servers (available at static.system32.uk)

The file contains no callbacks to any other 3rd party services, all it does is open a new window in your browser taking you to the Nintendo login site. After _you_ provide the response URL into the text box, the button simply creates a required URL in the background and navigates your web browser back. Home Assistant is then responsible for closing the window.

This "middleware" will only ever be used during the initial account linking process, afterwards a session token that is collected by HA will be used (which you will see in the config flow).

## Configuration is done in the UI

<!---->

No options are currently available for this integration
Currently only configuration of the update interval is supported. Future versions will allow you to re-authenticate with the Nintendo Online Service.

## Contributions are welcome!

Expand Down
8 changes: 6 additions & 2 deletions custom_components/nintendo_parental/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@
from .coordinator import NintendoUpdateCoordinator, Authenticator, NintendoParental

PLATFORMS: list[Platform] = [
Platform.SENSOR
Platform.SENSOR,
Platform.SWITCH
]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up this integration using UI."""
hass.data.setdefault(DOMAIN, {})
nintendo_auth = await Authenticator.complete_login(None, entry.data["session_token"], True)
coord = NintendoUpdateCoordinator(hass, entry.data["update_interval"], nintendo_auth)
update_interval = entry.data["update_interval"]
if entry.options:
update_interval = entry.options.get("update_interval")
coord = NintendoUpdateCoordinator(hass, update_interval, nintendo_auth)
# request first data sync
await coord.async_request_refresh()
hass.data[DOMAIN][entry.entry_id] = coord
Expand Down
51 changes: 46 additions & 5 deletions custom_components/nintendo_parental/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
"""Adds config flow for Blueprint."""
from __future__ import annotations
from typing import Any

from urllib.parse import quote
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.core import callback
from homeassistant.components import http
from homeassistant.components.http.view import HomeAssistantView
from urllib.parse import quote

from pynintendoparental import Authenticator
from aiohttp import web_response

from .const import DOMAIN, MIDDLEWARE_URL, AUTH_CALLBACK_PATH, AUTH_CALLBACK_NAME
from .const import DOMAIN, MIDDLEWARE_URL, AUTH_CALLBACK_PATH, AUTH_CALLBACK_NAME, NAME

from pynintendoparental import Authenticator

class BlueprintFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for Blueprint."""

VERSION = 1
AUTH = None

@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry
) -> config_entries.OptionsFlow:
"""Create the options flow."""
return OptionsFlowHandler(config_entry)

async def async_step_user(
self,
user_input: dict | None = None,
Expand All @@ -42,7 +51,9 @@ async def async_step_nintendo_website_auth(self, user_input=None):
forward_url = f"{hass_url}{AUTH_CALLBACK_PATH}?flow_id={self.flow_id}"
auth_url = MIDDLEWARE_URL.format(
NAV=quote(self.AUTH.login_url, safe=""),
RETURN=forward_url
RETURN=forward_url,
TITLE=quote("Nintendo OAuth Redirection"),
INFO=quote(f"This request has come from your Home Assistant instance to setup {NAME}")
)
return self.async_external_step(step_id="obtain_token", url=auth_url)

Expand Down Expand Up @@ -75,6 +86,36 @@ async def async_step_complete(self, user_input=None):
}
)

class OptionsFlowHandler(config_entries.OptionsFlow):
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> config_entries.FlowResult:
if user_input is not None:
return self.async_create_entry(
title=self.config_entry.title,
data={
"session_token": self.config_entry.data["session_token"],
"update_interval": user_input["update_interval"]
}
)

update_interval = self.config_entry.data["update_interval"]
if self.config_entry.options:
update_interval = self.config_entry.options.get("update_interval")

return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required("update_interval", default=update_interval): int
}
)
)

class MiddlewareCallbackView(HomeAssistantView):
"""Handle callback from external auth."""

Expand Down
4 changes: 2 additions & 2 deletions custom_components/nintendo_parental/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

NAME = "Nintendo Switch Parental Controls"
DOMAIN = "nintendo_parental"
VERSION = "0.0.1"
VERSION = "0.0.2"

MIDDLEWARE_URL = "https://static.system32.uk/hass_middleware.html?return_url={RETURN}&nav_url={NAV}"
MIDDLEWARE_URL = "https://static.system32.uk/hass_middleware.html?return_url={RETURN}&nav_url={NAV}&title={TITLE}&info={INFO}"
AUTH_CALLBACK_PATH = "/auth/nintendo/callback"
AUTH_CALLBACK_NAME = "auth:nintendo:callback"
6 changes: 4 additions & 2 deletions custom_components/nintendo_parental/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"documentation": "https://github.com/pantherale0/ha-nintendoparentalcontrols",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/pantherale0/ha-nintendoparentalcontrols/issues",
"version": "0.0.1",
"requirements": ["pynintendoparental==0.0.1"]
"version": "0.0.2",
"requirements": [
"pynintendoparental==0.0.2"
]
}
72 changes: 72 additions & 0 deletions custom_components/nintendo_parental/switch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Nintendo Switch Parental Controls switch platform."""

import logging
from typing import Any

from pynintendoparental.device import Device

from homeassistant.components.switch import SwitchEntity, SwitchDeviceClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .coordinator import NintendoUpdateCoordinator

from .const import (
DOMAIN
)

from .entity import NintendoDevice

_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Nintendo Switch Parental Control switches."""
coordinator: NintendoUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
entities = []
for device in coordinator.api.devices:
entities.append(
DeviceOverrideSwitch(coordinator, device.device_id)
)
async_add_entities(entities, True)

class DeviceOverrideSwitch(NintendoDevice, SwitchEntity):
"""Defines a switch that can enable or disable a device."""

def __init__(
self,
coordinator,
device_id
) -> None:
"""Initialize the sensor class."""
super().__init__(coordinator, device_id, "override")
self._old_state = self._device.limit_time

@property
def name(self) -> str:
return "Block Device Access"

@property
def is_on(self) -> bool:
return self._device.limit_time == 0

@property
def device_class(self) -> SwitchDeviceClass | None:
return SwitchDeviceClass.SWITCH

async def async_turn_on(self, **kwargs: Any) -> None:
self._old_state = self._device.limit_time
return await self._device.update_max_daily_playtime(0)

async def async_turn_off(self, **kwargs: Any) -> None:
if self._old_state == 0:
_LOGGER.warning("Attempted to disable device block when screentime limit was already 0, defaulting to 180 minutes.")
# defaulting to 180 minutes
await self._device.update_max_daily_playtime(180)
else:
await self._device.update_max_daily_playtime(self._old_state)
await self.coordinator.async_request_refresh()
20 changes: 20 additions & 0 deletions custom_components/nintendo_parental/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,25 @@
"connection": "Unable to connect to the server.",
"unknown": "Unknown error occurred."
}
},
"options": {
"step": {
"init": {
"description": "Update Nintendo Switch Parental Controls configuration",
"data": {
"update_interval": "Update interval (seconds)"
}
},
"reauth": {
"description": "Reauthenticate with Nintendo Online service."
},
"post": {
"description":"Do not adjust the session token parameter at the top. By default sensors will update every 60 seconds.",
"data": {
"session_token": "Session token",
"update_interval": "Update interval (seconds)"
}
}
}
}
}
45 changes: 35 additions & 10 deletions middleware.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Nintendo OAuth Redirection</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
</head>
<body onload="process_load()">
<script>
Expand All @@ -20,6 +20,16 @@
if(popup) popup.close();
}
}
// set additional props
if (params.get("info")) {
document.getElementById("extra_info").innerHTML = params.get("info")
}
// set title
if (params.get("title")) {
document.title = params.get("title")
} else {
document.title = "HASS Redirection Helper"
}
}
function return_to_hass(){
let redir = document.getElementById("return_url").value
Expand All @@ -29,15 +39,30 @@
window.location.replace(url)
}
</script>
<!--<input id="navigate_url" readonly/>
<button id="navigate" onclick="navigate()">Navigate to URL</button>
<br>
<br>-->
<label>Response URL: </label>
<input id="response_token"/>
<input hidden id="return_url" readonly>
<br>
<br>
<button id="response" onclick="return_to_hass()">Return back to Home Assistant</button>
<div class="container text-center">
<div class="form-text">The redirect URL below is where this page will redirect after clicking the return button.</div>
<div class="row">
<div class="col">
<label class="form-label">Redirect URL</label>
<input id="return_url" readonly>
</div>
</div>
<br>
<div class="row">
<div class="col">
<label for="response_token" class="form-label">Response URL</label>
<input id="response_token"/>
</div>
</div>
<div class="row">
<div class="col">
<button type="button" class="btn btn-primary" id="response" onclick="return_to_hass()">Return back to Home Assistant</button>
</div>
</div>
<div class="form-text">Data entered on this form will not be stored or shared anywhere.</div>
<div class="form-text" id="extra_info"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ colorlog==6.7.0
homeassistant==2023.2.0
pip>=21.0,<23.2
ruff==0.0.267
pynintendoparental
pynintendoparental==0.0.2

0 comments on commit 873b926

Please sign in to comment.