diff --git a/.dockerignore b/.dockerignore index 1691176..59e511a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,13 @@ node_modules/ +.github/ +.git/ .vscode/ assets/ config.*.js docker-compose.*.yml -output/ \ No newline at end of file +docker-compose.yml +output/ +LICENSE +*.md +*.yml +*.yaml \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5645fa7..59e73b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,29 +2,26 @@ name: ci on: push: - branches: main + tags: [ 'v*.*.*' ] jobs: multi: runs-on: ubuntu-latest steps: - - - name: Checkout + - name: Checkout uses: actions/checkout@v2 - - - name: Set up QEMU + - name: Extract package version + run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV + - name: Set up QEMU uses: docker/setup-qemu-action@v1 - - - name: Set up Docker Buildx + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - - - name: Login to DockerHub - uses: docker/login-action@v1 + - name: Login to Docker Hub + uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push + - name: Build and push to Docker uses: docker/build-push-action@v2 with: context: . @@ -32,4 +29,39 @@ jobs: platforms: linux/amd64,linux/arm/v7,linux/arm64 push: true tags: | + sibbl/hass-lovelace-kindle-screensaver:${{ env.PACKAGE_VERSION }}, sibbl/hass-lovelace-kindle-screensaver:latest + - name: Build and push HA_Addon AMD64 to Docker + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile.HA_ADDON + build-args: BUILD_FROM=homeassistant/amd64-base:latest + platforms: linux/amd64 + push: true + tags: | + sibbl/hass-lovelace-kindle-screensaver-ha-addon-amd64:${{ env.PACKAGE_VERSION }}, + sibbl/hass-lovelace-kindle-screensaver-ha-addon-amd64:latest + - name: Build and push HA_Addon aarch64 to Docker + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile.HA_ADDON + build-args: BUILD_FROM=homeassistant/aarch64-base:latest + platforms: linux/arm64/v8 + push: true + tags: | + sibbl/hass-lovelace-kindle-screensaver-ha-addon-aarch64:${{ env.PACKAGE_VERSION }}, + sibbl/hass-lovelace-kindle-screensaver-ha-addon-aarch64:latest + + - name: Build and push HA_Addon ARMv7 to Docker + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile.HA_ADDON + build-args: BUILD_FROM=homeassistant/armv7-base:latest + platforms: linux/arm/v7 + push: true + tags: | + sibbl/hass-lovelace-kindle-screensaver-ha-addon-armv7:${{ env.PACKAGE_VERSION }}, + sibbl/hass-lovelace-kindle-screensaver-ha-addon-armv7:latest \ No newline at end of file diff --git a/.gitignore b/.gitignore index 70c659c..be4cc39 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules/ config.*.js docker-compose.*.yml cover.png -output/ \ No newline at end of file +.devcontainer/ +.DS_Store \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..809ac9d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,48 @@ +# Changelog + +## 1.0.13 + +### Added + +* Allow configuring contrast, saturation, black level and white level. JPEG quality is set to 100% (thanks to [@harry48225](https://github.com/harry48225)) + +## 1.0.12 + +### Fixed + +* Fix scaling bug by using zoom css property instead of transforms (thanks to [@avhm](https://github.com/avhm)) + +## 1.0.11 + +### Fixed + +* Avoid viewport resize causing another rerender when taking screenshot (thanks to [@beeh5](https://github.com/beeh5)) + +## 1.0.10 + +### Fixed + +* Fix REMOVE_GAMMA and DITHER always being enabled for Home Assistant Add-On + +## 1.0.9 + +### Added + +* Add jpeg support via new `IMAGE_TYPE` config env variable (thanks to [@nbarrientos](https://github.com/nbarrientos)) + +## 1.0.8 + +### Fixed + +* Remove DITHER option from Home Assistant Add-On again until the gm/im incompatibility will be fixed + +## 1.0.7 + +### Added + +* Finally there's a changelog +* Allow custom environment variables to Home Assistant Add-On + +### Fixed + +* Add missing config variables to Home Assistant Add-On diff --git a/Dockerfile b/Dockerfile index 0dc7297..f83e5ac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,12 @@ -FROM node:12-alpine +FROM node:16-alpine3.17 WORKDIR /app RUN apk add --no-cache \ chromium \ nss \ - freetype \ + freetype \ + font-noto-emoji \ freetype-dev \ harfbuzz \ ca-certificates \ @@ -17,10 +18,11 @@ ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \ USE_IMAGE_MAGICK=true COPY package*.json ./ +COPY local.conf /etc/fonts/local.conf RUN npm ci -COPY . . +COPY *.js ./ EXPOSE 5000 diff --git a/Dockerfile.HA_ADDON b/Dockerfile.HA_ADDON new file mode 100644 index 0000000..0100f83 --- /dev/null +++ b/Dockerfile.HA_ADDON @@ -0,0 +1,33 @@ +ARG BUILD_FROM +FROM ${BUILD_FROM} + +WORKDIR /app +RUN apk add --no-cache \ + chromium \ + nss \ + freetype \ + font-noto-emoji \ + freetype-dev \ + harfbuzz \ + ca-certificates \ + ttf-freefont \ + imagemagick \ + nodejs \ + npm + +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \ + PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser \ + USE_IMAGE_MAGICK=true + +COPY package*.json ./ +COPY local.conf /etc/fonts/local.conf +COPY *.js ./ + +RUN npm ci + + +# Copy data for add-on +COPY run.sh / +RUN chmod a+x /run.sh + +CMD [ "/run.sh" ] \ No newline at end of file diff --git a/README.md b/README.md index 74637f7..3423531 100644 --- a/README.md +++ b/README.md @@ -12,36 +12,109 @@ If you're looking for a way to render your own HTML, see my other project [hass- ## Features -This tool regularly takes a screenshot of a specific page of your home assistant setup. It converts it into the PNG grayscale format which Kindles can display. +This tool regularly takes a screenshot of a specific page of your home assistant setup. It converts it into the PNG/JPEG grayscale format which Kindles can display. Using my [own Kindle 4 setup guide](https://github.com/sibbl/hass-lovelace-kindle-4) or the [online screensaver extension](https://www.mobileread.com/forums/showthread.php?t=236104) for any jailbroken Kindle, this image can be regularly polled from your device so you can use it as a weather station, a display for next public transport departures etc. ## Usage -You may simple set up the [sibbl/hass-lovelace-kindle-screensaver](https://hub.docker.com/r/sibbl/hass-lovelace-kindle-screensaver) docker container. The container exposes a single port (5000 by default). You can access the image by doing a simple GET request to e.g. `http://localhost:5000/` to receive the most recent image. +You may simple set up the [sibbl/hass-lovelace-kindle-screensaver](https://hub.docker.com/r/sibbl/hass-lovelace-kindle-screensaver) docker container. The container exposes a single port (5000 by default). -Home Assistant related stuff: - -- `HA_BASE_URL=https://your-hass-instance.com:8123` -- `HA_SCREENSHOT_URL=/lovelace/screensaver?kiosk` (I recommend the [kiosk mode](https://github.com/maykar/kiosk-mode) project) -- `HA_ACCESS_TOKEN=eyJ0...` (you need to create this token in Home Assistant first) -- `LANGUAGE=en` (language to use in Home Assistant frontend) -- `CRON_JOB=* * * * *` (how often to take screenshots, by default every minute) +Another way is to use Hassio Addons where you have to add this repository or click [here](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Fsibbl%2Fhass-lovelace-kindle-screensaver). Then Reload and you should see options to the Lovelace Kindle Screensaver Addon -Kindle related stuff: +I recommend simply using the `docker-compose.yml` file inside this repository, configure everything in there and run `docker-compose up -d` within the file's directory. This will pull the docker image, create the container with all environment variables from the file and run it in detached mode (using `-d`, so it continues running even when you exit your shell/bash/ssh connection). +Additionally, you can then later use `docker-compose pull && docker-compose up -d` to update the image in case you want to update it. -- `RENDERING_TIMEOUT=10000` (timeout of render process, necessary if your HASS instance might be down, in milliseconds) -- `RENDERING_DELAY=0` (how long to wait between navigating to the page and taking the screenshot, in milliseconds) -- `RENDERING_SCREEN_HEIGHT=800` (height of your kindle screen resolution, see below) -- `RENDERING_SCREEN_WIDTH=600` (width of your kindle screen resolution, see below) -- `ROTATION=0` (rotation of image in degrees, i.e. use 90 or 270 to render in landscape) -- `SCALING=1` (scaling factor, i.e. 1.5 to zoom in or 0.75 to zoom out) -- `GRAYSCALE_DEPTH=8` (grayscale bit depth your kindle supports) +You can then access the image by doing a simple GET request to e.g. `http://localhost:5000/` to receive the most recent image (might take up to 60s after the first run). -Advanced stuff: +Home Assistant related stuff: -- `OUTPUT_PATH=./output.png` (destination of rendered image) +| Env Var | Sample value | Required | Array?\* | Description | +|---------------------------|---------------------------------------| -------- | -------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `HA_BASE_URL` | `https://your-hass-instance.com:8123` | yes | no | Base URL of your home assistant instance | +| `HA_SCREENSHOT_URL` | `/lovelace/screensaver?kiosk` | yes | yes | Relative URL to take screenshot of (btw, the `?kiosk` parameter hides the nav bar using the [kiosk mode](https://github.com/NemesisRE/kiosk-mode) project) | +| `HA_ACCESS_TOKEN` | `eyJ0...` | yes | no | Long-lived access token from Home Assistant, see [official docs](https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token) | +| `HA_BATTERY_WEBHOOK` | `set_kindle_battery_level` | no | yes | Webhook definied in HA which receives `batteryLevel` (number between 0-100) and `isCharging` (boolean) as JSON | +| `LANGUAGE` | `en` | no | no | Language to set in browser and home assistant | +| `PREFERS_COLOR_SCHEME` | `light` | no | no | Enable browser dark mode, use `light` or `dark`. | +| `CRON_JOB` | `* * * * *` | no | no | How often to take screenshot | +| `RENDERING_TIMEOUT` | `10000` | no | no | Timeout of render process, helpful if your HASS instance might be down | +| `RENDERING_DELAY` | `0` | no | yes | how long to wait between navigating to the page and taking the screenshot, in milliseconds | +| `RENDERING_SCREEN_HEIGHT` | `800` | no | yes | Height of your kindle screen resolution | +| `RENDERING_SCREEN_WIDTH` | `600` | no | yes | Width of your kindle screen resolution | +| `BROWSER_LAUNCH_TIMEOUT` | `30000` | no | no | Timeout for browser launch, helpful if your HASS instance is slow | +| `ROTATION` | `0` | no | yes | Rotation of image in degrees, e.g. use 90 or 270 to render in landscape | +| `SCALING` | `1` | no | yes | Scaling factor, e.g. `1.5` to zoom in or `0.75` to zoom out | +| `GRAYSCALE_DEPTH` | `8` | no | yes | Grayscale bit depth your kindle supports | +| `COLOR_MODE` | `GrayScale` | no | yes | ColorMode to use, ex: `GrayScale`, or `TrueColor`. | +| `IMAGE_FORMAT` | `png` | no | no | Format for the generated images. Acceptable values are `png` or `jpeg`. | +| `DITHER` | `false` | no | yes | Apply a dither to the images. | +| `REMOVE_GAMMA` | `true` | no | no | Remove gamma correction from image. Computer images are normally gamma corrected since monitors expect gamma corrected data, however some E-Ink displays expect images not to have gamma correction. | +| SATURATION | 2 | no | no | Saturation level multiplier, e.g. 2 doubles the saturation | +| CONTRAST | 2 | no | no | Contrast level multiplier, e.g. 2 doubles the contrast | +| BLACK_LEVEL | 30% | no | no | Black point as percentage of MaxRGB, i.e. crushes blacks below specified level | +| WHITE_LEVEL | 90% | no | no | White point as percentage of MaxRGB, i.e. crushes whites above specified level | + +**\* Array** means that you can set `HA_SCREENSHOT_URL_2`, `HA_SCREENSHOT_URL_3`, ... `HA_SCREENSHOT_URL_n` to render multiple pages within the same instance. +If you use `HA_SCREENSHOT_URL_2`, you can also set `ROTATION_2=180`. If there is no `ROTATION_n` set, then `ROTATION` will be used as a fallback. +You can access these additional images by making GET Requests `http://localhost:5000/2`, `http://localhost:5000/3` etc. + +To make us of the array feature in the Home Assistant Add-On, you may use `ADDITIONAL_ENV_VARS`. It expects a format like this to set any additional environment variable: + +```yaml +- name: "HA_SCREENSHOT_URL_2" + value: "/lovelace/second-page" +- name: "ROTATION_2" + value: "180" +- name: "HA_SCREENSHOT_URL_3" + value: "/lovelace/third-page" +``` + +To avoid problems, please ensure that the name only contains upper case letters, numbers and underscores. The value field must be a string, so it's better to always put your value (especially numbers) into a `"string"` . + +### How to set up the webhook + +The webhook setting is to let HA keep track of the battery level of the Kindle, so it can warn you about charging it. You need to do the following: + +1. See below for a patch needed to make the Kindle Online Screensaver plugin send the battery level to this application. +1. Create two new helper entities in Home Assistant: + 1. a new `input_number` entity, e.g. `input_number.kindle_battery_level` + 1. a new `input_boolean` entity, e.g. `input_boolean.kindle_battery_charging` +1. Add an automation to set the values of these entities using a webhook: [![import blueprint](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fgithub.com%2Fsibbl%2Fhass-lovelace-kindle-screensaver%2Fblob%2Fmain%2Fbattery_sensor_blueprint.yaml) +1. Define this application's environment variable `HA_BATTERY_WEBHOOK` to the name of the webhook defined in the previous step. For multiple devices, `HA_BATTERY_WEBHOOK_2`, ... `HA_BATTERY_WEBHOOK_n` is supported as well. + +#### Patch for [Kindle Online Screensaver extension](https://www.mobileread.com/forums/showthread.php?t=236104) + +Modify the following lines in the Kindle Online Screensaver extension's `bin/update.sh` (absolute path on device should be `/mnt/us/extensions/onlinescreensaver/bin/update.sh`): + +```diff +... +if [ 1 -eq $CONNECTED ]; then +- if wget -q $IMAGE_URI -O $TMPFILE; then ++ batteryLevel=`/usr/bin/powerd_test -s | awk -F: '/Battery Level/ {print substr($2, 0, length($2)-1) - 0}'` ++ isCharging=`/usr/bin/powerd_test -s | awk -F: '/Charging/ {print substr($2,2,length($2))}'` ++ if wget -q "$IMAGE_URI?batteryLevel=$batteryLevel&isCharging=$isCharging" -O $TMPFILE; then + mv $TMPFILE $SCREENSAVERFILE + logger "Screen saver image updated" +... +``` + +#### Patch for [HASS Lovelace Kindle 4 extension](https://github.com/sibbl/hass-lovelace-kindle-4/) + +Modify the following lines in the HASS Lovelace Kindle 4 extension's [`script.sh`](https://github.com/sibbl/hass-lovelace-kindle-4/blob/main/extensions/homeassistant/script.sh#L133) (absolute path on device should be `/mnt/us/extensions/homeassistant/script.sh`): + +```diff +... +- DOWNLOADRESULT=$(wget -q "$IMAGE_URI" -O $TMPFILE) ++ DOWNLOADRESULT=$(wget -q "$IMAGE_URI?batteryLevel=$CHECKBATTERY&isCharging=$IS_CHARGING" -O $TMPFILE) +... +``` + +### Advanced configuration + +Some advanced variables for local usage which shouldn't be necessary when using Docker: + +- `OUTPUT_PATH=./output` (destination of rendered image, without extension. `OUTPUT_2`, `OUTPUT_3`, ... is also supported) - `PORT=5000` (port of server, which returns the last image) - `USE_IMAGE_MAGICK=false` (use ImageMagick instead of GraphicsMagick) - -You may also simply use the `docker-compose.yml` file inside this repository, configure everything in there and run `docker-compose up`. +- `UNSAFE_IGNORE_CERTIFICATE_ERRORS=true` (ignore certificate errors of e.g. self-signed certificates at your own risk) diff --git a/battery_sensor_blueprint.yaml b/battery_sensor_blueprint.yaml new file mode 100644 index 0000000..8fdce8e --- /dev/null +++ b/battery_sensor_blueprint.yaml @@ -0,0 +1,44 @@ +blueprint: + name: Kindle Screensaver Battery Level + domain: automation + input: + webhook_id: + name: Webhook ID + description: Unique and secret ID of ID to use for webhook. It's recommended to run `openssl rand -hex 16` to generate something random. + default: set_kindle_battery_level + local_only: + name: Only allow connections from the local network + selector: + boolean: + default: true + battery_level: + name: Entity to save battery level in + description: Please create a new helper entity first. + selector: + entity: + domain: input_number + battery_charging: + name: Entity to save battery charging state in + description: Please create a new helper entity first. + selector: + entity: + domain: input_boolean + +trigger: + - platform: webhook + webhook_id: !input webhook_id + local_only: !input local_only +action: + - service: input_number.set_value + target: + entity_id: !input battery_level + data: + value: "{{ trigger.json.batteryLevel }}" + - service_template: >- + {% if trigger.json.isCharging %} + input_boolean.turn_on + {% else %} + input_boolean.turn_off + {% endif %} + target: + entity_id: !input battery_charging \ No newline at end of file diff --git a/config.js b/config.js index b9725a2..d94b9c8 100644 --- a/config.js +++ b/config.js @@ -1,20 +1,58 @@ +function getEnvironmentVariable(key, suffix, fallbackValue) { + const value = process.env[key + suffix]; + if (value !== undefined) return value; + return fallbackValue || process.env[key]; +} + +function getPagesConfig() { + const pages = []; + let i = 0; + while (++i) { + const suffix = i === 1 ? "" : `_${i}`; + const screenShotUrl = process.env[`HA_SCREENSHOT_URL${suffix}`]; + if (!screenShotUrl) return pages; + pages.push({ + screenShotUrl, + imageFormat: getEnvironmentVariable("IMAGE_FORMAT", suffix) || "png", + outputPath: getEnvironmentVariable( + "OUTPUT_PATH", + suffix, + `output/cover${suffix}` + ), + renderingDelay: getEnvironmentVariable("RENDERING_DELAY", suffix) || 0, + renderingScreenSize: { + height: + getEnvironmentVariable("RENDERING_SCREEN_HEIGHT", suffix) || 800, + width: getEnvironmentVariable("RENDERING_SCREEN_WIDTH", suffix) || 600, + }, + grayscaleDepth: getEnvironmentVariable("GRAYSCALE_DEPTH", suffix) || 8, + removeGamma: getEnvironmentVariable("REMOVE_GAMMA", suffix) === "true" || false, + blackLevel: getEnvironmentVariable("BLACK_LEVEL", suffix) || "0%", + whiteLevel: getEnvironmentVariable("WHITE_LEVEL", suffix) || "100%", + dither: getEnvironmentVariable("DITHER", suffix) === "true" || false, + colorMode: getEnvironmentVariable("COLOR_MODE", suffix) || "GrayScale", + prefersColorScheme: getEnvironmentVariable("PREFERS_COLOR_SCHEME", suffix) || "light", + rotation: getEnvironmentVariable("ROTATION", suffix) || 0, + scaling: getEnvironmentVariable("SCALING", suffix) || 1, + batteryWebHook: getEnvironmentVariable("HA_BATTERY_WEBHOOK", suffix) || null, + saturation: getEnvironmentVariable("SATURATION", suffix) || 1, + contrast: getEnvironmentVariable("CONTRAST", suffix) || 1, + }); + } + return pages; +} + module.exports = { baseUrl: process.env.HA_BASE_URL, - screenShotUrl: process.env.HA_SCREENSHOT_URL || "", accessToken: process.env.HA_ACCESS_TOKEN, cronJob: process.env.CRON_JOB || "* * * * *", - outputPath: process.env.OUTPUT_PATH || "output/cover.png", - renderingTimeout: process.env.RENDERING_TIMEOUT || 10000, - renderingDelay: process.env.RENDERING_DELAY || 0, - renderingScreenSize: { - height: process.env.RENDERING_SCREEN_HEIGHT || 800, - width: process.env.RENDERING_SCREEN_WIDTH || 600, - }, - grayscaleDepth: process.env.GRAYSCALE_DEPTH || 8, useImageMagick: process.env.USE_IMAGE_MAGICK === "true", + pages: getPagesConfig(), port: process.env.PORT || 5000, + renderingTimeout: process.env.RENDERING_TIMEOUT || 10000, + browserLaunchTimeout: process.env.BROWSER_LAUNCH_TIMEOUT || 30000, language: process.env.LANGUAGE || "en", - rotation: process.env.ROTATION || 0, - scaling: process.env.SCALING || 1, - debug: process.env.DEBUG === "true" + debug: process.env.DEBUG === "true", + ignoreCertificateErrors: + process.env.UNSAFE_IGNORE_CERTIFICATE_ERRORS === "true", }; diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..cfce801 --- /dev/null +++ b/config.yaml @@ -0,0 +1,71 @@ +name: Lovelace Kindle Screensaver +version: 1.0.13 +slug: kindle-screensaver +description: This tool can be used to display a Lovelace view of your Home Assistant instance on a jailbroken Kindle device. It regularly takes a screenshot which can be polled and used as a screensaver image of the online screensaver plugin. +startup: application +boot: auto +arch: + - aarch64 + - amd64 + - armv7 +url: 'https://github.com/sibbl/hass-lovelace-kindle-screensaver' +image: 'sibbl/hass-lovelace-kindle-screensaver-ha-addon-{arch}' +webui: 'http://[HOST]:[PORT:5000]' +ingress: true +ingress_port: 5000 +ports: + 5000/tcp: 5000 +ports_description: + 5000/tcp: 'Node Webserver hosting rendered image' +map: + - media:rw +watchdog: 'http://[HOST]:[PORT:5000]/' +init: false +options: + HA_BASE_URL: 'https://your-path-to-home-assistant:8123' + HA_SCREENSHOT_URL: '/lovelace/0' + HA_ACCESS_TOKEN: '' + LANGUAGE: 'en' + CRON_JOB: '* * * * *' + RENDERING_TIMEOUT: '60000' + RENDERING_DELAY: '0' + RENDERING_SCREEN_HEIGHT: '800' + RENDERING_SCREEN_WIDTH: '600' + BROWSER_LAUNCH_TIMEOUT: '30000' + ROTATION: '0' + SCALING: '1' + GRAYSCALE_DEPTH: '8' + IMAGE_FORMAT: 'png' + COLOR_MODE: 'GrayScale' + REMOVE_GAMMA: true + PREFERS_COLOR_SCHEME: 'light' + HA_BATTERY_WEBHOOK: '' + SATURATION: 1 + CONTRAST: 1 + ADDITIONAL_ENV_VARS: [] +schema: + HA_BASE_URL: "url" + HA_SCREENSHOT_URL: "str" + HA_ACCESS_TOKEN: "password" + LANGUAGE: "str?" + CRON_JOB: "str?" + RENDERING_TIMEOUT: "int?" + RENDERING_DELAY: "int?" + RENDERING_SCREEN_HEIGHT: "int?" + RENDERING_SCREEN_WIDTH: "int?" + BROWSER_LAUNCH_TIMEOUT: "int?" + ROTATION: "int?" + SCALING: "float?" + GRAYSCALE_DEPTH: "int?" + IMAGE_FORMAT: "list(png|jpeg)?" + COLOR_MODE: "list(GrayScale|TrueColor)?" + REMOVE_GAMMA: "bool?" + PREFERS_COLOR_SCHEME: "list(light|dark)?" + HA_BATTERY_WEBHOOK: "str?" + SATURATION: "int?" + CONTRAST: "int?" + ADDITIONAL_ENV_VARS: + - name: match(^[A-Z0-9_]+$) + value: str +environment: + output_path: "/output/cover" diff --git a/docker-compose.yml b/docker-compose.yml index 1da0140..cfcaf63 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: - RENDERING_SCREEN_HEIGHT=800 - RENDERING_SCREEN_WIDTH=600 - GRAYSCALE_DEPTH=8 - - OUTPUT_PATH=/output/cover.png + - OUTPUT_PATH=/output/cover - LANGUAGE=en - ROTATION=0 - SCALING=1 diff --git a/index.js b/index.js index f380b03..525667d 100644 --- a/index.js +++ b/index.js @@ -1,20 +1,28 @@ const config = require("./config"); const path = require("path"); const http = require("http"); +const https = require("https"); const { promises: fs } = require("fs"); const fsExtra = require("fs-extra"); const puppeteer = require("puppeteer"); const { CronJob } = require("cron"); const gm = require("gm"); +// keep state of current battery level and whether the device is charging +const batteryStore = {}; + (async () => { - if (config.rotation % 90 > 0) { - console.error("Invalid rotation value: " + config.rotation); - return; + if (config.pages.length === 0) { + return console.error("Please check your configuration"); + } + for (const i in config.pages) { + const pageConfig = config.pages[i]; + if (pageConfig.rotation % 90 > 0) { + return console.error( + `Invalid rotation value for entry ${i + 1}: ${pageConfig.rotation}` + ); + } } - - const outputDir = path.dirname(config.outputPath); - await fsExtra.ensureDir(outputDir); console.log("Starting browser..."); let browser = await puppeteer.launch({ @@ -22,22 +30,26 @@ const gm = require("gm"); "--disable-dev-shm-usage", "--no-sandbox", `--lang=${config.language}`, - ], - headless: config.debug !== true, + config.ignoreCertificateErrors && "--ignore-certificate-errors" + ].filter((x) => x), + defaultViewport: null, + timeout: config.browserLaunchTimeout, + headless: config.debug !== true }); - console.log("Adding authentication entry to browser's local storage..."); + console.log(`Visiting '${config.baseUrl}' to login...`); let page = await browser.newPage(); await page.goto(config.baseUrl, { - timeout: config.renderingTimeout, + timeout: config.renderingTimeout }); const hassTokens = { hassUrl: config.baseUrl, access_token: config.accessToken, - token_type: "Bearer", + token_type: "Bearer" }; + console.log("Adding authentication entry to browser's local storage..."); await page.evaluate( (hassTokens, selectedLanguage) => { localStorage.setItem("hassTokens", hassTokens); @@ -55,21 +67,86 @@ const gm = require("gm"); ); renderAndConvertAsync(browser); } else { + console.log("Starting first render..."); + await renderAndConvertAsync(browser); console.log("Starting rendering cronjob..."); new CronJob({ cronTime: config.cronJob, onTick: () => renderAndConvertAsync(browser), - start: true, + start: true }); } - const httpServer = http.createServer(async (_, response) => { + const httpServer = http.createServer(async (request, response) => { + // Parse the request + const url = new URL(request.url, `http://${request.headers.host}`); + // Check the page number + const pageNumberStr = url.pathname; + // and get the battery level, if any + // (see https://github.com/sibbl/hass-lovelace-kindle-screensaver/README.md for patch to generate it on Kindle) + const batteryLevel = parseInt(url.searchParams.get("batteryLevel")); + const isCharging = url.searchParams.get("isCharging"); + const pageNumber = + pageNumberStr === "/" ? 1 : parseInt(pageNumberStr.substr(1)); + if ( + isFinite(pageNumber) === false || + pageNumber > config.pages.length || + pageNumber < 1 + ) { + console.log(`Invalid request: ${request.url} for page ${pageNumber}`); + response.writeHead(400); + response.end("Invalid request"); + return; + } try { - const data = await fs.readFile(config.outputPath); - console.log("Image was accessed"); - response.setHeader("Content-Length", Buffer.byteLength(data)); - response.writeHead(200, { "Content-Type": "image/png" }); + // Log when the page was accessed + const n = new Date(); + console.log(`${n.toISOString()}: Image ${pageNumber} was accessed`); + + const pageIndex = pageNumber - 1; + const configPage = config.pages[pageIndex]; + + const outputPathWithExtension = configPage.outputPath + "." + configPage.imageFormat + const data = await fs.readFile(outputPathWithExtension); + const stat = await fs.stat(outputPathWithExtension); + + const lastModifiedTime = new Date(stat.mtime).toUTCString(); + + response.writeHead(200, { + "Content-Type": "image/" + configPage.imageFormat, + "Content-Length": Buffer.byteLength(data), + "Last-Modified": lastModifiedTime + }); response.end(data); + + let pageBatteryStore = batteryStore[pageIndex]; + if (!pageBatteryStore) { + pageBatteryStore = batteryStore[pageIndex] = { + batteryLevel: null, + isCharging: false + }; + } + if (!isNaN(batteryLevel) && batteryLevel >= 0 && batteryLevel <= 100) { + if (batteryLevel !== pageBatteryStore.batteryLevel) { + pageBatteryStore.batteryLevel = batteryLevel; + console.log( + `New battery level: ${batteryLevel} for page ${pageNumber}` + ); + } + + if ( + (isCharging === "Yes" || isCharging === "1") && + pageBatteryStore.isCharging !== true) { + pageBatteryStore.isCharging = true; + console.log(`Battery started charging for page ${pageNumber}`); + } else if ( + (isCharging === "No" || isCharging === "0") && + pageBatteryStore.isCharging !== false + ) { + console.log(`Battery stopped charging for page ${pageNumber}`); + pageBatteryStore.isCharging = false; + } + } } catch (e) { console.error(e); response.writeHead(404); @@ -84,72 +161,130 @@ const gm = require("gm"); })(); async function renderAndConvertAsync(browser) { - const url = `${config.baseUrl}${config.screenShotUrl}`; + for (let pageIndex = 0; pageIndex < config.pages.length; pageIndex++) { + const pageConfig = config.pages[pageIndex]; + const pageBatteryStore = batteryStore[pageIndex]; + + const url = `${config.baseUrl}${pageConfig.screenShotUrl}`; - const outputPath = config.outputPath; - const tempPath = outputPath + ".temp"; + const outputPath = pageConfig.outputPath + "." + pageConfig.imageFormat; + await fsExtra.ensureDir(path.dirname(outputPath)); - console.log(`Rendering ${url} to image...`); - await renderUrlToImageAsync(browser, url, tempPath); + const tempPath = outputPath + ".temp"; - console.log(`Converting rendered screenshot of ${url} to grayscale png...`); - await convertImageToKindleCompatiblePngAsync(tempPath, outputPath); + console.log(`Rendering ${url} to image...`); + await renderUrlToImageAsync(browser, pageConfig, url, tempPath); - fs.unlink(tempPath); - console.log(`Finished ${url}`); + console.log(`Converting rendered screenshot of ${url} to grayscale...`); + await convertImageToKindleCompatiblePngAsync( + pageConfig, + tempPath, + outputPath + ); + + fs.unlink(tempPath); + console.log(`Finished ${url}`); + + if ( + pageBatteryStore && + pageBatteryStore.batteryLevel !== null && + pageConfig.batteryWebHook + ) { + sendBatteryLevelToHomeAssistant( + pageIndex, + pageBatteryStore, + pageConfig.batteryWebHook + ); + } + } } -async function renderUrlToImageAsync(browser, url, path) { +function sendBatteryLevelToHomeAssistant( + pageIndex, + batteryStore, + batteryWebHook +) { + const batteryStatus = JSON.stringify(batteryStore); + const options = { + method: "POST", + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(batteryStatus) + }, + rejectUnauthorized: !config.ignoreCertificateErrors + }; + const url = `${config.baseUrl}/api/webhook/${batteryWebHook}`; + const httpLib = url.toLowerCase().startsWith("https") ? https : http; + const req = httpLib.request(url, options, (res) => { + if (res.statusCode !== 200) { + console.error( + `Update device ${pageIndex} at ${url} status ${res.statusCode}: ${res.statusMessage}` + ); + } + }); + req.on("error", (e) => { + console.error(`Update ${pageIndex} at ${url} error: ${e.message}`); + }); + req.write(batteryStatus); + req.end(); +} + +async function renderUrlToImageAsync(browser, pageConfig, url, path) { let page; try { page = await browser.newPage(); await page.emulateMediaFeatures([ { name: "prefers-color-scheme", - value: "light", - }, + value: `${pageConfig.prefersColorScheme}` + } ]); let size = { - width: Number(config.renderingScreenSize.width), - height: Number(config.renderingScreenSize.height) + width: Number(pageConfig.renderingScreenSize.width), + height: Number(pageConfig.renderingScreenSize.height) }; - if (config.rotation % 180 > 0) { + if (pageConfig.rotation % 180 > 0) { size = { width: size.height, - height: size.width, + height: size.width }; } await page.setViewport(size); + const startTime = new Date().valueOf(); await page.goto(url, { waitUntil: ["domcontentloaded", "load", "networkidle0"], - timeout: config.renderingTimeout, + timeout: config.renderingTimeout + }); + + const navigateTimespan = new Date().valueOf() - startTime; + await page.waitForSelector("home-assistant", { + timeout: Math.max(config.renderingTimeout - navigateTimespan, 1000) }); await page.addStyleTag({ content: ` body { - width: calc(${config.renderingScreenSize.width}px / ${config.scaling}); - height: calc(${config.renderingScreenSize.height}px / ${config.scaling}); - transform-origin: 0 0; - transform: scale(${config.scaling}); + zoom: ${pageConfig.scaling * 100}%; overflow: hidden; - }`, + }` }); - if (config.renderingDelay > 0) { - await delay(config.renderingDelay); + if (pageConfig.renderingDelay > 0) { + await page.waitForTimeout(pageConfig.renderingDelay); } await page.screenshot({ path, - type: "png", + type: pageConfig.imageFormat, + captureBeyondViewport: false, clip: { x: 0, y: 0, - ...size, + ...size }, + ...(pageConfig.imageFormat=="jpeg") && {quality: 100} }); } catch (e) { console.error("Failed to render", e); @@ -160,15 +295,25 @@ async function renderUrlToImageAsync(browser, url, path) { } } -function convertImageToKindleCompatiblePngAsync(inputPath, outputPath) { +function convertImageToKindleCompatiblePngAsync( + pageConfig, + inputPath, + outputPath +) { return new Promise((resolve, reject) => { gm(inputPath) .options({ - imageMagick: config.useImageMagick === true, + imageMagick: config.useImageMagick === true }) - .rotate("white", config.rotation) - .type("GrayScale") - .bitdepth(config.grayscaleDepth) + .gamma(pageConfig.removeGamma ? 1.0 / 2.2 : 1.0) + .modulate(100, 100 * pageConfig.saturation) + .contrast(pageConfig.contrast) + .dither(pageConfig.dither) + .rotate("white", pageConfig.rotation) + .type(pageConfig.colorMode) + .level(pageConfig.blackLevel, pageConfig.whiteLevel) + .bitdepth(pageConfig.grayscaleDepth) + .quality(100) .write(outputPath, (err) => { if (err) { reject(err); @@ -178,8 +323,3 @@ function convertImageToKindleCompatiblePngAsync(inputPath, outputPath) { }); }); } -function delay(time) { - return new Promise((resolve) => { - setTimeout(resolve, time); - }); -} diff --git a/local.conf b/local.conf new file mode 100644 index 0000000..ed6c08b --- /dev/null +++ b/local.conf @@ -0,0 +1,31 @@ + + + + + + sans-serif + + Main sans-serif font name goes here + Noto Color Emoji + Noto Emoji + + + + + serif + + Main serif font name goes here + Noto Color Emoji + Noto Emoji + + + + + monospace + + Main monospace font name goes here + Noto Color Emoji + Noto Emoji + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 93a9f5d..fb43a8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,28 +1,31 @@ { "name": "hass-lovelace-kindle-screensaver", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@types/node": { - "version": "14.14.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz", - "integrity": "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==", + "version": "16.11.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz", + "integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==", "optional": true }, "@types/yauzl": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", - "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", + "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", "optional": true, "requires": { "@types/node": "*" } }, "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } }, "array-parallel": { "version": "0.1.3", @@ -34,15 +37,10 @@ "resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz", "integrity": "sha1-3103v8XC7wdV4qpPkv6ufUtaly8=" }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" - }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { "version": "1.5.1", @@ -50,9 +48,9 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -110,17 +108,17 @@ } }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "requires": { "ms": "2.1.2" } }, "devtools-protocol": { - "version": "0.0.818844", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", - "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==" + "version": "0.0.901419", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.901419.tgz", + "integrity": "sha512-4INMPwNm9XRpBukhNbF7OB6fNTTCaI8pzy/fXg0xQzAy5h3zL1P8xT3QazgKqBrb/hAYwIBizqDBZ7GtJE74QQ==" }, "end-of-stream": { "version": "1.4.4", @@ -164,14 +162,13 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", "requires": { - "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "universalify": "^2.0.0" } }, "fs.realpath": { @@ -188,9 +185,9 @@ } }, "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -222,16 +219,16 @@ } }, "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, "https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "requires": { - "agent-base": "5", + "agent-base": "6", "debug": "4" } }, @@ -266,13 +263,6 @@ "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - } } }, "locate-path": { @@ -324,9 +314,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", + "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", + "requires": { + "whatwg-url": "^5.0.0" + } }, "once": { "version": "1.4.0", @@ -405,22 +398,22 @@ } }, "puppeteer": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-5.5.0.tgz", - "integrity": "sha512-OM8ZvTXAhfgFA7wBIIGlPQzvyEETzDjeRa4mZRCRHxYL+GNH5WAuYUQdja3rpWZvkX/JKqmuVgbsxDNsDFjMEg==", - "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.818844", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^4.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-11.0.0.tgz", + "integrity": "sha512-6rPFqN1ABjn4shgOICGDBITTRV09EjXVqhDERBDKwCLz0UyBxeeBH6Ay0vQUJ84VACmlxwzOIzVEJXThcF3aNg==", + "requires": { + "debug": "4.3.2", + "devtools-protocol": "0.0.901419", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "node-fetch": "2.6.5", + "pkg-dir": "4.2.0", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.2.3" } }, "readable-stream": { @@ -466,9 +459,9 @@ } }, "tar-stream": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", - "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "requires": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -482,6 +475,11 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, "unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", @@ -492,15 +490,29 @@ } }, "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -515,9 +527,9 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", - "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==" + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" }, "yallist": { "version": "2.1.2", diff --git a/package.json b/package.json index 21f3221..9afe7a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hass-lovelace-kindle-screensaver", - "version": "1.0.0", + "version": "1.0.13", "description": "", "main": "index.js", "scripts": { @@ -10,8 +10,8 @@ "license": "MIT", "dependencies": { "cron": "^1.8.2", - "fs-extra": "^9.0.1", + "fs-extra": "^10.0.0", "gm": "^1.23.1", - "puppeteer": "^5.5.0" + "puppeteer": "^11.0.0" } } diff --git a/repository.yaml b/repository.yaml new file mode 100644 index 0000000..1249b7a --- /dev/null +++ b/repository.yaml @@ -0,0 +1,3 @@ +name: 'Lovelace Kindle Screensaver' +url: 'https://github.com/sibbl/hass-lovelace-kindle-screensaver' +maintainer: 'sibbl' \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..f0bbddc --- /dev/null +++ b/run.sh @@ -0,0 +1,39 @@ +#!/usr/bin/with-contenv bashio + +bashio::log.info "Loading config..." + +export HA_BASE_URL="$(bashio::config 'HA_BASE_URL')" +export HA_SCREENSHOT_URL=$(bashio::config 'HA_SCREENSHOT_URL') +export HA_ACCESS_TOKEN="$(bashio::config 'HA_ACCESS_TOKEN')" +export LANGUAGE=$(bashio::config 'LANGUAGE') +export CRON_JOB=$(bashio::config 'CRON_JOB') +export RENDERING_TIMEOUT=$(bashio::config 'RENDERING_TIMEOUT') +export RENDERING_DELAY=$(bashio::config 'RENDERING_DELAY') +export RENDERING_SCREEN_HEIGHT=$(bashio::config 'RENDERING_SCREEN_HEIGHT') +export RENDERING_SCREEN_WIDTH=$(bashio::config 'RENDERING_SCREEN_WIDTH') +export BROWSER_LAUNCH_TIMEOUT=$(bashio::config 'BROWSER_LAUNCH_TIMEOUT') +export ROTATION=$(bashio::config 'ROTATION') +export SCALING=$(bashio::config 'SCALING') +export GRAYSCALE_DEPTH=$(bashio::config 'GRAYSCALE_DEPTH') +export IMAGE_FORMAT=$(bashio::config 'IMAGE_FORMAT') +export COLOR_MODE=$(bashio::config 'COLOR_MODE') +export REMOVE_GAMMA=$(bashio::config 'REMOVE_GAMMA') +export PREFERS_COLOR_SCHEME=$(bashio::config 'PREFERS_COLOR_SCHEME') +export HA_BATTERY_WEBHOOK=$(bashio::config 'HA_BATTERY_WEBHOOK') + +bashio::log.info "Loading additional environment variables..." + +# Load custom environment variables +for var in $(bashio::config 'ADDITIONAL_ENV_VARS|keys'); do + name=$(bashio::config "ADDITIONAL_ENV_VARS[${var}].name") + value=$(bashio::config "ADDITIONAL_ENV_VARS[${var}].value") + bashio::log.info "Setting ${name} to ${value}" + export "${name}=${value}" +done + +bashio::log.info "Using HA_BASE_URL: ${HA_BASE_URL}" + +bashio::log.info "Starting server..." + +cd /app +exec /usr/bin/npm start