diff --git a/.dockerignore b/.dockerignore
index 0918a81f..30461e40 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -19,4 +19,5 @@ public/saved_vods
client-vue/node_modules
client-vue/dist
client-vue/.env
-client-vue/.env.production
\ No newline at end of file
+client-vue/.env.production
+client-broker
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index b169c900..4f3472d5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -53,6 +53,7 @@ ENV TCD_BIN_DIR=/usr/bin
ENV TCD_FFMPEG_PATH=/usr/bin/ffmpeg
ENV TCD_MEDIAINFO_PATH=/usr/bin/mediainfo
ENV TCD_DOCKER=1
+ENV TCD_WEBSOCKET_ENABLED=1
USER nobody
WORKDIR /var/www/twitchautomator
diff --git a/README.md b/README.md
index c1397c0e..d104a395 100644
--- a/README.md
+++ b/README.md
@@ -26,16 +26,20 @@ Reminder that i don't use docker myself on my capturing setup, so any specific e
Known issues:
- TwitchDownloaderCLI doesn't work in alpine/docker: /bin/sh: ./TwitchDownloaderCLI: not found
-### Manual build (recommended)
-Run `docker-compose up --build -d` in the app directory. The docker-compose.yml file is required.
+### Docker hub
-If you want the public webapp to have a custom base folder, you must provide `BASE_URL` and `VUE_APP_BASE_URL` in the environment variable settings.
+1. Download the [docker-compose.yml](https://raw.githubusercontent.com/MrBrax/TwitchAutomator/master/docker-compose.yml) file and place it in a directory.
+2. Run `docker-compose pull` and `docker-compose up -d` to start it.
+3. Visit the webapp at `localhost:8082`
-### Docker hub
+Hub: https://hub.docker.com/r/mrbrax/twitchautomator
-Pull the image from https://hub.docker.com/r/mrbrax/twitchautomator
+*The dockerhub build is preconfigured to be hosted at the root (`/`) and such, does not work when placed in a subdirectory.*
-Docker hub doesn't seem to fully support docker-compose apps, so the cron stuff won't work.
+### Manual build
+Run `docker-compose up --build -d` in the app directory. The `docker-compose.yml` file is required.
+
+If you want the public webapp to have a custom base folder, you must provide `BASE_URL` and `VUE_APP_BASE_URL` in the environment variable settings.
## Standalone setup
diff --git a/client-broker/.dockerignore b/client-broker/.dockerignore
new file mode 100644
index 00000000..40b878db
--- /dev/null
+++ b/client-broker/.dockerignore
@@ -0,0 +1 @@
+node_modules/
\ No newline at end of file
diff --git a/client-broker/Dockerfile b/client-broker/Dockerfile
new file mode 100644
index 00000000..1bffe444
--- /dev/null
+++ b/client-broker/Dockerfile
@@ -0,0 +1,6 @@
+FROM alpine:3.7
+RUN apk --no-cache add nodejs yarn
+COPY . /var/broker
+WORKDIR /var/broker
+RUN yarn install
+ENTRYPOINT [ "yarn", "start" ]
\ No newline at end of file
diff --git a/client-broker/package.json b/client-broker/package.json
new file mode 100644
index 00000000..e259e6d8
--- /dev/null
+++ b/client-broker/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "client-broker",
+ "version": "1.0.0",
+ "main": "server.js",
+ "license": "MIT",
+ "dependencies": {
+ "ws": "^7.4.3"
+ }
+}
diff --git a/client-broker/server.js b/client-broker/server.js
new file mode 100644
index 00000000..d692efe2
--- /dev/null
+++ b/client-broker/server.js
@@ -0,0 +1,77 @@
+// const { cli } = require('webpack');
+const WebSocket = require('ws');
+
+class ClientBroker {
+ constructor(){
+ this.clients = [];
+ this.wss = null;
+ }
+ start () {
+ const a = process.argv.slice(2);
+ const serverPort = a[0] ? parseInt(a[0]) : 8765;
+
+ console.log(`Starting on port ${serverPort}...`);
+ try {
+ this.wss = new WebSocket.Server({ port: serverPort });
+ } catch (error) {
+ console.error("Fatal error when starting broker server", error);
+ return false;
+ }
+
+ this.wss.on('error', (error) => {
+ console.log("Websocket server error", error);
+ });
+
+ this.wss.on('connection', this.onConnect.bind(this));
+ }
+
+ onConnect(ws, req){
+ const clientIP = req.connection.remoteAddress;
+ ws.clientIP = clientIP;
+ // console.log(clientIP);
+ this.clients.push(ws);
+
+ ws.on("message", (message) => this.onMessage(ws, message));
+ ws.on("pong", (heartbeat) => {
+ ws.isAlive = true;
+ console.log(`Pong from ${clientIP}`);
+ });
+ ws.on("error", (err) => {
+ console.error("Client error", err)
+ });
+ }
+
+ onMessage(ws, message){
+ // console.log("message", ws, message);
+
+ if(message == "ping"){
+ console.log(`Pong to ${ws.clientIP}`);
+ ws.send("pong");
+ return;
+ }
+
+ let data;
+
+ try {
+ data = JSON.parse(message);
+ } catch (error) {
+ console.error(`Invalid data from ${ws.clientIP}: ${message}`)
+ return;
+ }
+
+ if(data.server){
+ this.wss.clients.forEach((client) => {
+ client.send(JSON.stringify({
+ action: "server",
+ data: data.data
+ }));
+ });
+ }
+
+ console.log(`json from ${ws.clientIP}:`, data);
+ console.debug(`Clients: ${this.wss.clients.size}`);
+ }
+}
+
+const cb = new ClientBroker();
+cb.start();
\ No newline at end of file
diff --git a/client-broker/yarn.lock b/client-broker/yarn.lock
new file mode 100644
index 00000000..04e8fb52
--- /dev/null
+++ b/client-broker/yarn.lock
@@ -0,0 +1,8 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+ws@^7.4.3:
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.3.tgz#1f9643de34a543b8edb124bdcbc457ae55a6e5cd"
+ integrity sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==
diff --git a/client-vue/package.json b/client-vue/package.json
index 6fa158f3..d7f2148a 100644
--- a/client-vue/package.json
+++ b/client-vue/package.json
@@ -1,6 +1,6 @@
{
"name": "twitchautomator-client",
- "version": "0.1.3",
+ "version": "0.2.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -19,6 +19,7 @@
"core-js": "^3.6.5",
"date-fns": "^2.16.1",
"normalize.css": "^8.0.1",
+ "register-service-worker": "^1.7.1",
"vue": "3.0.5",
"vue-axios": "^3.2.2",
"vue-router": "4.0.3",
@@ -29,6 +30,7 @@
"@typescript-eslint/parser": "^4.14.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
+ "@vue/cli-plugin-pwa": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
diff --git a/client-vue/public/index.html b/client-vue/public/index.html
index 5cb709b7..165d9ee6 100644
--- a/client-vue/public/index.html
+++ b/client-vue/public/index.html
@@ -5,6 +5,7 @@
+
<%= htmlWebpackPlugin.options.title %>
diff --git a/client-vue/public/manifest/site.webmanifest b/client-vue/public/manifest.json
similarity index 72%
rename from client-vue/public/manifest/site.webmanifest
rename to client-vue/public/manifest.json
index 538f7bd1..267593b8 100644
--- a/client-vue/public/manifest/site.webmanifest
+++ b/client-vue/public/manifest.json
@@ -3,12 +3,12 @@
"short_name": "TwitchAutomator",
"icons": [
{
- "src": "android-chrome-192x192.png",
+ "src": "manifest/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
- "src": "android-chrome-512x512.png",
+ "src": "manifest/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
@@ -16,5 +16,5 @@
"theme_color": "#0072bc",
"background_color": "#ffffff",
"display": "standalone",
- "start_url": "../dashboard"
+ "start_url": "."
}
diff --git a/client-vue/public/robots.txt b/client-vue/public/robots.txt
new file mode 100644
index 00000000..eb053628
--- /dev/null
+++ b/client-vue/public/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:
diff --git a/client-vue/src/App.vue b/client-vue/src/App.vue
index c09584df..73facbed 100644
--- a/client-vue/src/App.vue
+++ b/client-vue/src/App.vue
@@ -37,7 +37,7 @@ export default defineComponent({
this.fetchData();
},
methods: {
- fetchData() {
+ async fetchData() {
// client config
const currentClientConfig = localStorage.getItem("twitchautomator_config")
? JSON.parse(localStorage.getItem("twitchautomator_config") as string)
@@ -47,10 +47,9 @@ export default defineComponent({
// clear config
this.$store.commit("updateConfig", []);
- return this.$http.get(`/api/v0/settings/list`).then((response) => {
- this.$store.commit("updateConfig", response.data.data.config);
- this.$store.commit("updateVersion", response.data.data.version);
- });
+ const response = await this.$http.get(`/api/v0/settings/list`);
+ this.$store.commit("updateConfig", response.data.data.config);
+ this.$store.commit("updateVersion", response.data.data.version);
},
},
components: {
diff --git a/client-vue/src/assets/_mobile.scss b/client-vue/src/assets/_mobile.scss
index d1d464e0..378c9076 100644
--- a/client-vue/src/assets/_mobile.scss
+++ b/client-vue/src/assets/_mobile.scss
@@ -259,6 +259,10 @@
display: none;
}
+ #jobs-status {
+ display: none;
+ }
+
.jobs_list {
display: none;
}
diff --git a/client-vue/src/components/DurationDisplay.vue b/client-vue/src/components/DurationDisplay.vue
index e9572c3c..22f04fd3 100644
--- a/client-vue/src/components/DurationDisplay.vue
+++ b/client-vue/src/components/DurationDisplay.vue
@@ -37,7 +37,8 @@ export default defineComponent({
if ((dur.seconds && dur.seconds > 0) || (dur.minutes && dur.minutes > 0)) str += `${dur.seconds}s `;
this.timeString = str.trim();
} else {
- this.timeString = dur.hours?.toString().padStart(2, "0") + ":" + dur.minutes?.toString().padStart(2, "0") + ":" + dur.seconds?.toString().padStart(2, "0");
+ this.timeString =
+ dur.hours?.toString().padStart(2, "0") + ":" + dur.minutes?.toString().padStart(2, "0") + ":" + dur.seconds?.toString().padStart(2, "0");
}
},
},
diff --git a/client-vue/src/components/SideMenuStreamer.vue b/client-vue/src/components/SideMenuStreamer.vue
index 943198b8..62d45baf 100644
--- a/client-vue/src/components/SideMenuStreamer.vue
+++ b/client-vue/src/components/SideMenuStreamer.vue
@@ -28,7 +28,7 @@
{{ streamer.current_game.game_name }}
- Playing {{ streamer.current_game.game_name }}
+ Playing {{ streamer.current_game ? streamer.current_game.game_name : "(none)" }}
for
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- {{ formatDate(vod.dt_started_at.date) }}
- {{ humanDate(vod.dt_started_at.date, true) }}
+
+
+ {{ formatDate(vod.dt_started_at.date) }}
+
+
+ {{ humanDate(vod.dt_started_at.date, true) }}
- · ( )
- · {{ formatBytes(vod.api_getRecordingSize, 2) }}+
+
+ · ( )
+ · {{ formatBytes(vod.api_getRecordingSize, 2) }}+
- · ({{ $store.state.clientConfig.useRelativeTime ? niceDuration(vod.duration_seconds) : humanDuration(vod.duration_seconds) }})
- · {{ formatBytes(vod.total_size, 2) }}
+
+ · ({{
+ $store.state.clientConfig.useRelativeTime ? niceDuration(vod.duration_seconds) : humanDuration(vod.duration_seconds)
+ }})
+ · {{ formatBytes(vod.total_size, 2) }}
-
-
-
-
+
+
+
+
diff --git a/client-vue/src/components/Vod.vue b/client-vue/src/components/Vod.vue
index 008e1ce4..a1dcc3b0 100644
--- a/client-vue/src/components/Vod.vue
+++ b/client-vue/src/components/Vod.vue
@@ -173,7 +173,8 @@
- The ID was {{ vod?.twitch_vod_id }} .
+ The ID was {{ vod?.twitch_vod_id }} .
The VOD probably never got saved.
@@ -195,7 +196,7 @@
Current duration:
Watch live:
- Twitch
+ Twitch
Cronjobs
-
- {{ cron }}
+
+ {{ cron_name }}
{{ cron_status }}
+ Run now
@@ -180,6 +181,19 @@ export default defineComponent({
});
*/
},
+ runCron(type: string) {
+ this.$http
+ .get(`/api/v0/cron/${type}`)
+ .then((response) => {
+ const json = response.data;
+ console.debug("cronData", json);
+ if (json.message) alert(json.message);
+ this.fetchData();
+ })
+ .catch((err) => {
+ console.error("about error", err.response);
+ });
+ },
},
});
diff --git a/client-vue/src/views/Dashboard.vue b/client-vue/src/views/Dashboard.vue
index 125869da..be29a5f6 100644
--- a/client-vue/src/views/Dashboard.vue
+++ b/client-vue/src/views/Dashboard.vue
@@ -49,7 +49,12 @@
- {{ loading ? "Loading..." : `Refreshing in ${timer} seconds.` }}
+
+ {{ wsConnected ? "Connected" : "Disconnected" }}
+
+
+ {{ loading ? "Loading..." : `Refreshing in ${timer} seconds.` }}
+
@@ -94,6 +99,9 @@ export default defineComponent({
logModule: "",
oldData: {} as Record,
notificationSub: Function as any,
+ ws: {} as WebSocket,
+ wsConnected: false,
+ wsKeepalive: 0,
};
},
created() {
@@ -111,11 +119,17 @@ export default defineComponent({
});
},
mounted() {
- this.interval = setInterval(() => {
- this.fetchTicker();
- }, 1000);
this.processNotifications();
+
+ if (this.$store.state.config.websocket_enabled) {
+ this.connectWebsocket();
+ } else {
+ console.debug("No websocket url");
+ this.interval = setInterval(() => {
+ this.fetchTicker();
+ }, 1000);
+ }
},
unmounted() {
if (this.interval) {
@@ -127,8 +141,80 @@ export default defineComponent({
console.log("unsubscribing from notifications, unmounted");
this.notificationSub();
}
+
+ if (this.ws) {
+ this.disconnectWebsocket();
+ }
},
methods: {
+ connectWebsocket() {
+ if (this.ws) this.disconnectWebsocket();
+ const proto = window.location.protocol === "https:" ? "wss://" : "ws://";
+ const websocket_url_public = proto + window.location.host + this.$store.state.config.basepath + "/socket/";
+ const websocket_url = process.env.NODE_ENV === "development" ? "ws://localhost:8765/socket/" : websocket_url_public;
+ console.log(`Connecting to ${websocket_url}`);
+ this.ws = new WebSocket(websocket_url);
+ this.ws.onopen = (ev: Event) => {
+ console.log(`Connected to websocket!`);
+ this.ws.send(JSON.stringify({ action: "helloworld" }));
+ this.wsConnected = true;
+ this.wsKeepalive = setInterval(() => {
+ console.debug("send ping");
+ this.ws.send("ping");
+ }, 10000);
+ };
+ this.ws.onmessage = (ev: MessageEvent) => {
+ // console.log("ws message", ev);
+ let text = ev.data;
+
+ if (text == "pong") {
+ console.log("pong recieved");
+ return;
+ }
+
+ let json: any = {};
+ try {
+ json = JSON.parse(text);
+ } catch (error) {
+ console.error("Couldn't parse json", text);
+ return;
+ }
+ // console.log("json return", json);
+ // this.$emit("websocketData", json);
+ if (json.data.action && ["start_capture", "finish_capture", "chapter_update"].indexOf(json.data.action) !== -1) {
+ console.log("Websocket update");
+ this.fetchStreamers().then((sl) => {
+ this.$store.commit("updateStreamerList", sl);
+ this.loading = false;
+ });
+ } else {
+ console.log(`Websocket wrong action (${json.data.action})`);
+ }
+ };
+ this.ws.onerror = (ev: Event) => {
+ console.error("Websocket error", ev);
+ this.wsConnected = false;
+ clearInterval(this.wsKeepalive);
+ };
+ this.ws.onclose = (ev: CloseEvent) => {
+ console.log(`Disconnected from websocket!`, ev);
+ setTimeout(() => {
+ if (!ev.wasClean) {
+ this.connectWebsocket();
+ }
+ }, 10000);
+ this.wsConnected = false;
+ clearInterval(this.wsKeepalive);
+ };
+ return this.ws;
+ },
+ disconnectWebsocket() {
+ if (this.ws && this.ws.close) {
+ console.log("Closing websocket...");
+ this.ws.close(undefined, "pageleave");
+ if (this.wsKeepalive) clearInterval(this.wsKeepalive);
+ }
+ },
async fetchStreamers() {
let response;
try {
@@ -256,8 +342,8 @@ export default defineComponent({
}*/
// console.log( "values", Object.(mutation.payload[0]));
const streamerPronounciation: { [key: string]: string } = {
- "pokelawls": "pookelawls",
- "xQcOW": "eckscueseeow"
+ pokelawls: "pookelawls",
+ xQcOW: "eckscueseeow",
};
// console.debug("notification payload", mutation);
diff --git a/client-vue/src/views/Settings.vue b/client-vue/src/views/Settings.vue
index 1863884b..13d5c0a0 100644
--- a/client-vue/src/views/Settings.vue
+++ b/client-vue/src/views/Settings.vue
@@ -59,10 +59,10 @@
- 0 5 * * 1 curl {{ $store.state.config.app_url }}/api/v0/cron/sub
- 0 */12 * * * curl {{ $store.state.config.app_url }}/api/v0/cron/check_muted_vods
+ 0 5 * * 1 curl {{ $store.state.config.app_url }}/api/v0/cron/sub
+ 0 */12 * * * curl {{ $store.state.config.app_url }}/api/v0/cron/check_muted_vods
10 */12 * * * curl {{ $store.state.config.app_url }}/api/v0/cron/check_deleted_vods
- 0 1 * * * curl {{ $store.state.config.app_url }}/api/v0/cron/playlist_dump
+ 0 1 * * * curl {{ $store.state.config.app_url }}/api/v0/cron/dump_playlists
This will subscribe to the webhook every 5 days, check muted & deleted vods every 12 hours, and dump playlists once per day.
diff --git a/client-vue/vue.config.js b/client-vue/vue.config.js
index d121beb0..a23e0545 100644
--- a/client-vue/vue.config.js
+++ b/client-vue/vue.config.js
@@ -5,6 +5,18 @@ module.exports = {
publicPath: process.env.BASE_URL,
// publicPath: './',
assetsDir: "assets",
+ pwa: {
+ manifestPath: "manifest.json",
+ themeColor: "#ffffff",
+ msTileColor: "#000000",
+ iconPaths: {
+ favicon32: "manifest/favicon-32x32.png",
+ favicon16: "manifest/favicon-16x16.png",
+ appleTouchIcon: "manifest/apple-touch-icon.png",
+ maskIcon: "manifest/safari-pinned-tab.svg",
+ msTileImage: "manifest/mstile-150x150.png",
+ },
+ },
devServer: {
port: 8081,
proxy: {
diff --git a/client-vue/yarn.lock b/client-vue/yarn.lock
index 7a5cd24a..2d002cc4 100644
--- a/client-vue/yarn.lock
+++ b/client-vue/yarn.lock
@@ -713,6 +713,13 @@
"@babel/types" "^7.4.4"
esutils "^2.0.2"
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.4":
+ version "7.12.13"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d"
+ integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/runtime@^7.11.0", "@babel/runtime@^7.8.4":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
@@ -817,7 +824,7 @@
version "8.5.1"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06"
-"@hapi/joi@^15.0.1":
+"@hapi/joi@^15.0.0", "@hapi/joi@^15.0.1":
version "15.1.1"
resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7"
dependencies:
@@ -1239,6 +1246,15 @@
webpack "^4.0.0"
yorkie "^2.0.0"
+"@vue/cli-plugin-pwa@~4.5.0":
+ version "4.5.11"
+ resolved "https://registry.yarnpkg.com/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.11.tgz#3031b4d3798707c353f2ad47b7c57a4708a17611"
+ integrity sha512-7wvNdR8EXRWaLlybPlk4B6+lE8y+sG2kXq69EyH4ILRmZnFYc22dY22eUhR1EIFTmRxHR0g8qDBrp1BvaNeaYA==
+ dependencies:
+ "@vue/cli-shared-utils" "^4.5.11"
+ webpack "^4.0.0"
+ workbox-webpack-plugin "^4.3.1"
+
"@vue/cli-plugin-router@^4.5.10", "@vue/cli-plugin-router@~4.5.0":
version "4.5.10"
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.10.tgz#546eaf6295bb125ce2365fb6db83548979776b0d"
@@ -1345,6 +1361,24 @@
semver "^6.1.0"
strip-ansi "^6.0.0"
+"@vue/cli-shared-utils@^4.5.11":
+ version "4.5.11"
+ resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.5.11.tgz#fff71673ee9128f998c691515b9d327071b4f41e"
+ integrity sha512-+aaQ+ThQG3+WMexfSWNl0y6f43edqVqRNbguE53F3TIH81I7saS5S750ayqXhZs2r6STJJyqorQnKtAWfHo29A==
+ dependencies:
+ "@hapi/joi" "^15.0.1"
+ chalk "^2.4.2"
+ execa "^1.0.0"
+ launch-editor "^2.2.1"
+ lru-cache "^5.1.1"
+ node-ipc "^9.1.1"
+ open "^6.3.0"
+ ora "^3.4.0"
+ read-pkg "^5.1.1"
+ request "^2.88.2"
+ semver "^6.1.0"
+ strip-ansi "^6.0.0"
+
"@vue/compiler-core@3.0.5":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.0.5.tgz#a6e54cabe9536e74c6513acd2649f311af1d43ac"
@@ -1863,6 +1897,13 @@ babel-code-frame@^6.22.0:
esutils "^2.0.2"
js-tokens "^3.0.2"
+babel-extract-comments@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz#0a2aedf81417ed391b85e18b4614e693a0351a21"
+ integrity sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ==
+ dependencies:
+ babylon "^6.18.0"
+
babel-loader@^8.1.0:
version "8.2.2"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81"
@@ -1878,6 +1919,32 @@ babel-plugin-dynamic-import-node@^2.3.3:
dependencies:
object.assign "^4.1.0"
+babel-plugin-syntax-object-rest-spread@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
+ integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=
+
+babel-plugin-transform-object-rest-spread@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06"
+ integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=
+ dependencies:
+ babel-plugin-syntax-object-rest-spread "^6.8.0"
+ babel-runtime "^6.26.0"
+
+babel-runtime@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
+ integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
+ dependencies:
+ core-js "^2.4.0"
+ regenerator-runtime "^0.11.0"
+
+babylon@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
+ integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
+
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -2496,6 +2563,11 @@ commander@~2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
+common-tags@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
+ integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==
+
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -2622,6 +2694,11 @@ core-js-compat@^3.6.5, core-js-compat@^3.8.0:
browserslist "^4.16.1"
semver "7.0.0"
+core-js@^2.4.0:
+ version "2.6.12"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
+ integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
+
core-js@^3.6.5:
version "3.8.3"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.8.3.tgz#c21906e1f14f3689f93abcc6e26883550dd92dd0"
@@ -3815,6 +3892,15 @@ from2@^2.1.0:
inherits "^2.0.1"
readable-stream "^2.0.0"
+fs-extra@^4.0.2:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
+ integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^4.0.0"
+ universalify "^0.1.0"
+
fs-extra@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
@@ -3896,6 +3982,11 @@ get-intrinsic@^1.0.2:
has "^1.0.3"
has-symbols "^1.0.1"
+get-own-enumerable-property-symbols@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
+ integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==
+
get-stdin@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
@@ -4581,6 +4672,11 @@ is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+is-obj@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
+ integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
+
is-obj@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
@@ -4617,6 +4713,11 @@ is-regex@^1.0.4, is-regex@^1.1.1:
dependencies:
has-symbols "^1.0.1"
+is-regexp@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
+ integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
+
is-resolvable@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
@@ -4753,6 +4854,13 @@ json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
+json-stable-stringify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
+ integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=
+ dependencies:
+ jsonify "~0.0.0"
+
json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -4791,6 +4899,11 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
+jsonify@~0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
+ integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
+
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -4901,6 +5014,11 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
+lodash._reinterpolate@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
+ integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
+
lodash.camelcase@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
@@ -4921,6 +5039,21 @@ lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+lodash.template@^4.4.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
+ integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==
+ dependencies:
+ lodash._reinterpolate "^3.0.0"
+ lodash.templatesettings "^4.0.0"
+
+lodash.templatesettings@^4.0.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33"
+ integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==
+ dependencies:
+ lodash._reinterpolate "^3.0.0"
+
lodash.transform@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.transform/-/lodash.transform-4.6.0.tgz#12306422f63324aed8483d3f38332b5f670547a0"
@@ -6141,6 +6274,11 @@ prettier@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
+pretty-bytes@^5.1.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.5.0.tgz#0cecda50a74a941589498011cf23275aa82b339e"
+ integrity sha512-p+T744ZyjjiaFlMUZZv6YPC5JrkNj8maRmPaQCWFJFplUAzpIUTRaTcS+7wmZtUoFXHtESJb23ISliaWyz3SHA==
+
pretty-error@^2.0.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.2.tgz#be89f82d81b1c86ec8fdfbc385045882727f93b6"
@@ -6338,6 +6476,11 @@ regenerate@^1.4.0:
version "1.4.2"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
+regenerator-runtime@^0.11.0:
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
+ integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
+
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
@@ -6377,6 +6520,11 @@ regexpu-core@^4.7.1:
unicode-match-property-ecmascript "^1.0.4"
unicode-match-property-value-ecmascript "^1.2.0"
+register-service-worker@^1.7.1:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/register-service-worker/-/register-service-worker-1.7.2.tgz#6516983e1ef790a98c4225af1216bc80941a4bd2"
+ integrity sha512-CiD3ZSanZqcMPRhtfct5K9f7i3OLCcBBWsJjLh1gW9RO/nS94sVzY59iS+fgYBOBqaBpf4EzfqUF3j9IG+xo8A==
+
regjsgen@^0.5.1:
version "0.5.2"
resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733"
@@ -7058,6 +7206,15 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
+stringify-object@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
+ integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==
+ dependencies:
+ get-own-enumerable-property-symbols "^3.0.0"
+ is-obj "^1.0.1"
+ is-regexp "^1.0.0"
+
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -7082,6 +7239,14 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
+strip-comments@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-1.0.2.tgz#82b9c45e7f05873bee53f37168af930aa368679d"
+ integrity sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw==
+ dependencies:
+ babel-extract-comments "^1.0.0"
+ babel-plugin-transform-object-rest-spread "^6.26.0"
+
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@@ -7846,6 +8011,141 @@ word-wrap@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
+workbox-background-sync@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz#26821b9bf16e9e37fd1d640289edddc08afd1950"
+ integrity sha512-1uFkvU8JXi7L7fCHVBEEnc3asPpiAL33kO495UMcD5+arew9IbKW2rV5lpzhoWcm/qhGB89YfO4PmB/0hQwPRg==
+ dependencies:
+ workbox-core "^4.3.1"
+
+workbox-broadcast-update@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-4.3.1.tgz#e2c0280b149e3a504983b757606ad041f332c35b"
+ integrity sha512-MTSfgzIljpKLTBPROo4IpKjESD86pPFlZwlvVG32Kb70hW+aob4Jxpblud8EhNb1/L5m43DUM4q7C+W6eQMMbA==
+ dependencies:
+ workbox-core "^4.3.1"
+
+workbox-build@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-4.3.1.tgz#414f70fb4d6de47f6538608b80ec52412d233e64"
+ integrity sha512-UHdwrN3FrDvicM3AqJS/J07X0KXj67R8Cg0waq1MKEOqzo89ap6zh6LmaLnRAjpB+bDIz+7OlPye9iii9KBnxw==
+ dependencies:
+ "@babel/runtime" "^7.3.4"
+ "@hapi/joi" "^15.0.0"
+ common-tags "^1.8.0"
+ fs-extra "^4.0.2"
+ glob "^7.1.3"
+ lodash.template "^4.4.0"
+ pretty-bytes "^5.1.0"
+ stringify-object "^3.3.0"
+ strip-comments "^1.0.2"
+ workbox-background-sync "^4.3.1"
+ workbox-broadcast-update "^4.3.1"
+ workbox-cacheable-response "^4.3.1"
+ workbox-core "^4.3.1"
+ workbox-expiration "^4.3.1"
+ workbox-google-analytics "^4.3.1"
+ workbox-navigation-preload "^4.3.1"
+ workbox-precaching "^4.3.1"
+ workbox-range-requests "^4.3.1"
+ workbox-routing "^4.3.1"
+ workbox-strategies "^4.3.1"
+ workbox-streams "^4.3.1"
+ workbox-sw "^4.3.1"
+ workbox-window "^4.3.1"
+
+workbox-cacheable-response@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-4.3.1.tgz#f53e079179c095a3f19e5313b284975c91428c91"
+ integrity sha512-Rp5qlzm6z8IOvnQNkCdO9qrDgDpoPNguovs0H8C+wswLuPgSzSp9p2afb5maUt9R1uTIwOXrVQMmPfPypv+npw==
+ dependencies:
+ workbox-core "^4.3.1"
+
+workbox-core@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-4.3.1.tgz#005d2c6a06a171437afd6ca2904a5727ecd73be6"
+ integrity sha512-I3C9jlLmMKPxAC1t0ExCq+QoAMd0vAAHULEgRZ7kieCdUd919n53WC0AfvokHNwqRhGn+tIIj7vcb5duCjs2Kg==
+
+workbox-expiration@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-4.3.1.tgz#d790433562029e56837f341d7f553c4a78ebe921"
+ integrity sha512-vsJLhgQsQouv9m0rpbXubT5jw0jMQdjpkum0uT+d9tTwhXcEZks7qLfQ9dGSaufTD2eimxbUOJfWLbNQpIDMPw==
+ dependencies:
+ workbox-core "^4.3.1"
+
+workbox-google-analytics@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-4.3.1.tgz#9eda0183b103890b5c256e6f4ea15a1f1548519a"
+ integrity sha512-xzCjAoKuOb55CBSwQrbyWBKqp35yg1vw9ohIlU2wTy06ZrYfJ8rKochb1MSGlnoBfXGWss3UPzxR5QL5guIFdg==
+ dependencies:
+ workbox-background-sync "^4.3.1"
+ workbox-core "^4.3.1"
+ workbox-routing "^4.3.1"
+ workbox-strategies "^4.3.1"
+
+workbox-navigation-preload@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-4.3.1.tgz#29c8e4db5843803b34cd96dc155f9ebd9afa453d"
+ integrity sha512-K076n3oFHYp16/C+F8CwrRqD25GitA6Rkd6+qAmLmMv1QHPI2jfDwYqrytOfKfYq42bYtW8Pr21ejZX7GvALOw==
+ dependencies:
+ workbox-core "^4.3.1"
+
+workbox-precaching@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-4.3.1.tgz#9fc45ed122d94bbe1f0ea9584ff5940960771cba"
+ integrity sha512-piSg/2csPoIi/vPpp48t1q5JLYjMkmg5gsXBQkh/QYapCdVwwmKlU9mHdmy52KsDGIjVaqEUMFvEzn2LRaigqQ==
+ dependencies:
+ workbox-core "^4.3.1"
+
+workbox-range-requests@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-4.3.1.tgz#f8a470188922145cbf0c09a9a2d5e35645244e74"
+ integrity sha512-S+HhL9+iTFypJZ/yQSl/x2Bf5pWnbXdd3j57xnb0V60FW1LVn9LRZkPtneODklzYuFZv7qK6riZ5BNyc0R0jZA==
+ dependencies:
+ workbox-core "^4.3.1"
+
+workbox-routing@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-4.3.1.tgz#a675841af623e0bb0c67ce4ed8e724ac0bed0cda"
+ integrity sha512-FkbtrODA4Imsi0p7TW9u9MXuQ5P4pVs1sWHK4dJMMChVROsbEltuE79fBoIk/BCztvOJ7yUpErMKa4z3uQLX+g==
+ dependencies:
+ workbox-core "^4.3.1"
+
+workbox-strategies@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-4.3.1.tgz#d2be03c4ef214c115e1ab29c9c759c9fe3e9e646"
+ integrity sha512-F/+E57BmVG8dX6dCCopBlkDvvhg/zj6VDs0PigYwSN23L8hseSRwljrceU2WzTvk/+BSYICsWmRq5qHS2UYzhw==
+ dependencies:
+ workbox-core "^4.3.1"
+
+workbox-streams@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-4.3.1.tgz#0b57da70e982572de09c8742dd0cb40a6b7c2cc3"
+ integrity sha512-4Kisis1f/y0ihf4l3u/+ndMkJkIT4/6UOacU3A4BwZSAC9pQ9vSvJpIi/WFGQRH/uPXvuVjF5c2RfIPQFSS2uA==
+ dependencies:
+ workbox-core "^4.3.1"
+
+workbox-sw@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-4.3.1.tgz#df69e395c479ef4d14499372bcd84c0f5e246164"
+ integrity sha512-0jXdusCL2uC5gM3yYFT6QMBzKfBr2XTk0g5TPAV4y8IZDyVNDyj1a8uSXy3/XrvkVTmQvLN4O5k3JawGReXr9w==
+
+workbox-webpack-plugin@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-4.3.1.tgz#47ff5ea1cc074b6c40fb5a86108863a24120d4bd"
+ integrity sha512-gJ9jd8Mb8wHLbRz9ZvGN57IAmknOipD3W4XNE/Lk/4lqs5Htw4WOQgakQy/o/4CoXQlMCYldaqUg+EJ35l9MEQ==
+ dependencies:
+ "@babel/runtime" "^7.0.0"
+ json-stable-stringify "^1.0.1"
+ workbox-build "^4.3.1"
+
+workbox-window@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-4.3.1.tgz#ee6051bf10f06afa5483c9b8dfa0531994ede0f3"
+ integrity sha512-C5gWKh6I58w3GeSc0wp2Ne+rqVw8qwcmZnQGpjiek8A2wpbxSJb1FdCoQVO+jDJs35bFgo/WETgl1fqgsxN0Hg==
+ dependencies:
+ workbox-core "^4.3.1"
+
worker-farm@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
diff --git a/composer.json b/composer.json
index 90dbdd19..172a0b23 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,5 @@
{
"require": {
- "james-heinrich/getid3": "^1.9",
"twig/twig": "3.0",
"slim/twig-view": "^3.1",
"slim/slim": "4.*",
@@ -12,7 +11,8 @@
"guzzlehttp/guzzle": "7.0",
"tuupola/slim-basic-auth": "^3.2",
"symfony/process": "^5.1",
- "twig/html-extra": "^3.0"
+ "twig/html-extra": "^3.0",
+ "textalk/websocket": "^1.5"
},
"autoload": {
"psr-4": {
diff --git a/composer.lock b/composer.lock
index 73676117..dbb27c21 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "9af8117743cb429c1cd894bb4c6bace3",
+ "content-hash": "f9f8bd01e93f2b374c7f26c533f8cae9",
"packages": [
{
"name": "fig/http-message-util",
@@ -261,69 +261,6 @@
],
"time": "2020-09-30T07:37:11+00:00"
},
- {
- "name": "james-heinrich/getid3",
- "version": "v1.9.20",
- "source": {
- "type": "git",
- "url": "https://github.com/JamesHeinrich/getID3.git",
- "reference": "3c15e353b9bb1252201c73394bb8390b573a751d"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/JamesHeinrich/getID3/zipball/3c15e353b9bb1252201c73394bb8390b573a751d",
- "reference": "3c15e353b9bb1252201c73394bb8390b573a751d",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0"
- },
- "require-dev": {
- "jakub-onderka/php-parallel-lint": "^0.9 || ^1.0"
- },
- "suggest": {
- "ext-SimpleXML": "SimpleXML extension is required to analyze RIFF/WAV/BWF audio files (also requires `ext-libxml`).",
- "ext-com_dotnet": "COM extension is required when loading files larger than 2GB on Windows.",
- "ext-ctype": "ctype extension is required when loading files larger than 2GB on 32-bit PHP (also on 64-bit PHP on Windows) or executing `getid3_lib::CopyTagsToComments`.",
- "ext-dba": "DBA extension is required to use the DBA database as a cache storage.",
- "ext-exif": "EXIF extension is required for graphic modules.",
- "ext-iconv": "iconv extension is required to work with different character sets (when `ext-mbstring` is not available).",
- "ext-json": "JSON extension is required to analyze Apple Quicktime videos.",
- "ext-libxml": "libxml extension is required to analyze RIFF/WAV/BWF audio files.",
- "ext-mbstring": "mbstring extension is required to work with different character sets.",
- "ext-mysql": "MySQL extension is required to use the MySQL database as a cache storage (deprecated in PHP 5.5, removed in PHP >= 7.0, use `ext-mysqli` instead).",
- "ext-mysqli": "MySQLi extension is required to use the MySQL database as a cache storage.",
- "ext-rar": "RAR extension is required for RAR archive module.",
- "ext-sqlite3": "SQLite3 extension is required to use the SQLite3 database as a cache storage.",
- "ext-xml": "XML extension is required for graphic modules to analyze the XML metadata.",
- "ext-zlib": "Zlib extension is required for archive modules and compressed metadata."
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.9.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "getid3/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "GPL-1.0-or-later",
- "LGPL-3.0-only",
- "MPL-2.0"
- ],
- "description": "PHP script that extracts useful information from popular multimedia file formats",
- "homepage": "https://www.getid3.org/",
- "keywords": [
- "codecs",
- "php",
- "tags"
- ],
- "time": "2020-06-30T18:43:34+00:00"
- },
{
"name": "nikic/fast-route",
"version": "v1.3.0",
@@ -1933,6 +1870,51 @@
],
"time": "2020-12-08T17:03:37+00:00"
},
+ {
+ "name": "textalk/websocket",
+ "version": "1.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Textalk/websocket-php.git",
+ "reference": "aada79791f013d821c8265e9e2ae565aa9bdca2f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Textalk/websocket-php/zipball/aada79791f013d821c8265e9e2ae565aa9bdca2f",
+ "reference": "aada79791f013d821c8265e9e2ae565aa9bdca2f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 | ^8.0",
+ "psr/log": "^1.0"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.0",
+ "phpunit/phpunit": "^8.0|^9.0",
+ "squizlabs/php_codesniffer": "^3.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "WebSocket\\": "lib"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "ISC"
+ ],
+ "authors": [
+ {
+ "name": "Fredrik Liljegren"
+ },
+ {
+ "name": "Sören Jensen",
+ "email": "soren@abicart.se"
+ }
+ ],
+ "description": "WebSocket client and server",
+ "time": "2021-02-07T19:10:14+00:00"
+ },
{
"name": "tuupola/callable-handler",
"version": "1.1.0",
@@ -3919,12 +3901,12 @@
"version": "1.9.1",
"source": {
"type": "git",
- "url": "https://github.com/webmozart/assert.git",
+ "url": "https://github.com/webmozarts/assert.git",
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
"shasum": ""
},
diff --git a/docker-compose.yml b/docker-compose.yml
index 92afcf91..32d357be 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -4,7 +4,7 @@ services:
build: .
image: mrbrax/twitchautomator
ports:
- - "8081:8080"
+ - "8082:8080"
volumes:
- ./config:/var/www/twitchautomator/config
- ./cache:/var/www/twitchautomator/cache
@@ -13,4 +13,8 @@ services:
- ./docker/saved_clips:/var/www/twitchautomator/public/saved_clips
- ./docker/saved_vods:/var/www/twitchautomator/public/saved_vods
cron:
- build: ./docker/cron-container/
\ No newline at end of file
+ build: ./docker/cron-container/
+ image: mrbrax/twitchautomator:latest-cron
+ broker:
+ build: ./client-broker/
+ image: mrbrax/twitchautomator:latest-broker
\ No newline at end of file
diff --git a/docker/nginx.conf b/docker/nginx.conf
index fe101e3a..f79a2a3f 100644
--- a/docker/nginx.conf
+++ b/docker/nginx.conf
@@ -81,6 +81,21 @@ http {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
}
+
+ location ~ /socket/ {
+ proxy_pass http://broker:8765;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "Upgrade";
+ proxy_set_header Host $host;
+ }
+
+ # upstream ws-backend {
+ # # enable sticky session based on IP
+ # ip_hash;
+ # server broker:8765;
+ # }
+
}
gzip on;
diff --git a/package.json b/package.json
index 35a192f8..1ce5ad25 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "twitchautomator",
- "version": "5.0.3",
+ "version": "5.0.5",
"description": "Twitch VOD automator fluff",
"main": "src/Client/main.ts",
"scripts": {
diff --git a/src/Routes/routes.php b/src/Routes/routes.php
index 87dc8eb2..bc88a72d 100644
--- a/src/Routes/routes.php
+++ b/src/Routes/routes.php
@@ -12,6 +12,7 @@
use App\Controller\CronController;
use App\TwitchConfig;
+use App\TwitchHelper;
/** @var \Slim\App $app */
@@ -163,6 +164,14 @@
$group->get('/log/{filename}[/{last_line}]', ApiController::class . ':display_log')->setName('api_display_log');
+ $group->any('/test_webhook', function (Request $request, Response $response, array $args) {
+ TwitchHelper::webhook([
+ 'action' => 'test'
+ ]);
+ $response->getBody()->write("Tested");
+ return $response;
+ });
+
// $group->post('/hook', ApiController::class . ':hook')->setName('hook_post');
// $group->get('/playlist_dump/{username}', ApiController::class . ':playlist_dump')->setName('api_playlist_dump');
diff --git a/src/TwitchConfig.php b/src/TwitchConfig.php
index 689c9007..1dfc46a2 100644
--- a/src/TwitchConfig.php
+++ b/src/TwitchConfig.php
@@ -26,6 +26,7 @@ class TwitchConfig
['key' => 'webhook_url', 'group' => 'Basic', 'text' => 'Webhook URL', 'type' => 'string', 'help' => 'For external scripting'],
['key' => 'password', 'group' => 'Interface', 'text' => 'Password', 'type' => 'string', 'help' => 'Keep blank for none. Username is admin'],
['key' => 'password_secure', 'group' => 'Interface', 'text' => 'Force HTTPS for password', 'type' => 'boolean', 'default' => true],
+ ['key' => 'websocket_enabled', 'group' => 'Interface', 'text' => 'Websockets enabled', 'type' => 'boolean'],
['key' => 'storage_per_streamer', 'group' => 'Basic', 'text' => 'Gigabytes of storage per streamer', 'type' => 'number', 'default' => 100],
['key' => 'hls_timeout', 'group' => 'Advanced', 'text' => 'HLS Timeout in seconds (ads)', 'type' => 'number', 'default' => 200],
['key' => 'vods_to_keep', 'group' => 'Basic', 'text' => 'VODs to keep per streamer', 'type' => 'number', 'default' => 5],
diff --git a/src/TwitchHelper.php b/src/TwitchHelper.php
index e74dbd6a..64364d14 100644
--- a/src/TwitchHelper.php
+++ b/src/TwitchHelper.php
@@ -7,6 +7,7 @@
use GuzzleHttp\Client;
use Symfony\Component\Process\Process;
use App\TwitchAutomatorJob;
+use WebSocket;
class TwitchHelper
{
@@ -1105,29 +1106,30 @@ public static function findJob(string $search)
public static function webhook(array $data)
{
- if (!TwitchConfig::cfg('webhook_url')) {
- return;
- // throw new \Exception("No webhook URL set");
- }
-
- $client = new \GuzzleHttp\Client();
-
- try {
- $client->request("POST", TwitchConfig::cfg('webhook_url'), [
- 'form_params' => $data,
- 'connect_timeout' => 5,
- 'timeout' => 10
- ]);
- } catch (\Throwable $th) {
- self::logAdvanced(self::LOG_ERROR, "helper", "Webhook POST error: " . $th->getMessage());
+ if(TwitchConfig::cfg('websocket_enabled') || getenv('TCD_DOCKER') == 1 ){
+ $websocket_url = getenv('TCD_DOCKER') == 1 ? "ws://broker:8765/socket/" : preg_replace("/https?/", "ws", TwitchConfig::cfg('app_url')) . "/socket/";
+ $client = new Websocket\Client($websocket_url);
+
+ try {
+ $client->text(json_encode([
+ 'server' => true,
+ 'data' => $data
+ ]));
+ } catch (\Throwable $th) {
+ TwitchHelper::log(TwitchHelper::LOG_ERROR, "Websocket send error: " . $th->getMessage());
+ }
+
+ if($client && $client->isConnected()){
+ try {
+ $client->close();
+ } catch (\Throwable $th) {
+ TwitchHelper::log(TwitchHelper::LOG_ERROR, "Websocket close error: " . $th->getMessage());
+ }
+
+ }
}
- }
- /*
- public static function websocket(array $data)
- {
-
- if (!TwitchConfig::cfg('websocket_url')) {
+ if (!TwitchConfig::cfg('webhook_url')) {
return;
// throw new \Exception("No webhook URL set");
}
@@ -1144,7 +1146,6 @@ public static function websocket(array $data)
self::logAdvanced(self::LOG_ERROR, "helper", "Webhook POST error: " . $th->getMessage());
}
}
- */
public static function vodFolder(string $username = null)
{
diff --git a/templates/client.twig b/templates/client.twig
deleted file mode 100644
index e69de29b..00000000
diff --git a/templates/components/logviewer.twig b/templates/components/logviewer.twig
deleted file mode 100644
index 9b1a6f4c..00000000
--- a/templates/components/logviewer.twig
+++ /dev/null
@@ -1,29 +0,0 @@
-
- {#
- {% apply spaceless %}
- {% for line in log_lines %}
-
- {% if line.source %}
- {{ line.date_string }} <{{ line.level }}> [{{ line.source.file|basename }}:{{ line.source.line }} {{line.source.function}}({{ line.source.args ? line.source.args|join : '' }})] {{ line.text }}
- {% elseif line.module %}
- {{ line.date_string }} <
{{ line.module }} > <{{ line.level }}> {{ line.text }}
- {% else %}
- {{ line.date_string }} <{{ line.level }}> {{ line.text }}
- {% endif %}
- {% if line.count %}
{{ line.count }} {% endif %}
- {% if config.debug and line.metadata %}
m {% endif %}
-
- {% endfor %}
- {% endapply %}
- #}
-
- {% for line in log_lines %}
-
- {{ line.date_string }}
- {{ line.module }}
- {{ line.level }}
- {{ line.text }}
-
- {% endfor %}
-
-
\ No newline at end of file
diff --git a/templates/components/menu.twig b/templates/components/menu.twig
deleted file mode 100644
index 5a17605e..00000000
--- a/templates/components/menu.twig
+++ /dev/null
@@ -1,127 +0,0 @@
-
\ No newline at end of file
diff --git a/templates/components/streamer.twig b/templates/components/streamer.twig
deleted file mode 100644
index 0d0d1368..00000000
--- a/templates/components/streamer.twig
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
- {{ streamer.quality|join(", ") }} {# quality #}
- · {{ streamer.vods_list|length }} vods {# vods #}
- · {{ streamer.vods_size|formatBytes }} {# total size #}
- ·
- {% if streamer.subbed_at and streamer.expires_at %}
- {{ streamer.expires_at|date("Y-m-d") }}
- {% else %}
- Not subbed
- {% endif %}
-
- {% if streamer.is_live %}
- · {# abort recording #}
- {% else %}
- · {# force recording #}
- {% endif %}
- {# dump playlist #}
-
-
-
-
- {% if streamer.vods_list|length == 0 %}
-
-
None
-
- {% else %}
-
- {% for vodclass in streamer.vods_list %}
-
- {% include 'components/vod.twig' %}
-
- {% endfor %}
-
- {% endif %}
-
-
diff --git a/templates/dialog.twig b/templates/dialog.twig
deleted file mode 100644
index 51668eac..00000000
--- a/templates/dialog.twig
+++ /dev/null
@@ -1,9 +0,0 @@
-{% extends "base.twig" %}
-
-{% block content %}
-
-{% endblock %}
\ No newline at end of file
diff --git a/templates/player.twig b/templates/player.twig
deleted file mode 100644
index 1a723980..00000000
--- a/templates/player.twig
+++ /dev/null
@@ -1,146 +0,0 @@
-{% extends "base.twig" %}
-
-{% block content %}
-
-
-
-
-
-
-
- Play
- Pause
-
-
-
-
-
- {% for chapter in vodclass.chapters %}
-
-
-
{{ chapter.title }}
-
{{ chapter.game_name }}
-
-
- {% endfor %}
-
-
-
-
-
-
-
-
-
-
-
-{% endblock %}
diff --git a/templates/settings.twig b/templates/settings.twig
deleted file mode 100644
index 8c000546..00000000
--- a/templates/settings.twig
+++ /dev/null
@@ -1,249 +0,0 @@
-{% extends "base.twig" %}
-
-{% block content %}
-
-
-
-
-
-
Streamers
-
-
-
- {% for streamer in streamers %}
-
-
-
{{ streamer.username }}
-
-
-
- {% endfor %}
-
-
-
-
-
-
-
-
-
-
Settings
-
-
-
-
-
-
-
Guessed app url
-
{{ app_calc }}
-
-
This might not be correct. Please enter it manually, and take proxies and custom port numbers into consideration.
-
-
-
Check the status of the external utilities on the about page.
-
- {% if not is_docker %}
-
-
-
-
Crontab example
-
The Slim framework doesn't have a good way to execute code from the command line, so you'll have to set up cron manually.
- {% if config.app_url %}
-
- 0 5 * * 1 curl {{ config.app_url }}/cron/sub
- 0 */12 * * * curl {{ config.app_url }}/cron/check_muted_vods
- 10 */12 * * * curl {{ config.app_url }}/cron/check_deleted_vods
- 0 1 * * * curl {{ config.app_url }}/cron/playlist_dump
-
-
This will subscribe to the webhook every 5 days, check muted & deleted vods every 12 hours, and dump playlists once per day.
- {% else %}
-
Can't show example, app url has not been set
- {% endif %}
-
- {% endif %}
-
-
-
-
-
-
-
-
Favourite games
-
-
-
-
-
-
-
-{% endblock %}
diff --git a/templates/tools.twig b/templates/tools.twig
deleted file mode 100644
index 525f01c0..00000000
--- a/templates/tools.twig
+++ /dev/null
@@ -1,153 +0,0 @@
-{% extends "base.twig" %}
-
-{% block content %}
-
-
-
-
-
-
Full VOD fetch and burn chat
-
-
-
-
-
-
-
-
-
-
Chat download
-
-
-
-
-
-
-
-
Saved VODs
-
-
-
- {% if saved_vods %}
-
- {% for vod in saved_vods %}
- {{ vod.name }} ({{ vod.size|formatBytes }})
- {% endfor %}
-
- {% else %}
-
None
- {% endif %}
-
-
-
-
-
-
-
-
Current jobs
-
-
-
-
- {% for job in current_jobs %}
-
- {{ job.name }}
- {{ job.pid }}
- {{ job.status ? 'Running' : 'Unexpected exit' }}
- {% if job.status %}Kill {% endif %}
-
- {% endfor %}
-
-
- {% if not current_jobs %}
-
None
- {% endif %}
-
-
-
-
-
-
-
-{% endblock %}