From 20fdb8e7cbc8d72c6b61bc6e436c3cd2f7130239 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Wed, 1 Sep 2021 12:17:07 -0700 Subject: [PATCH] amcrest --- plugins/amcrest/.gitignore | 4 + plugins/amcrest/.npmignore | 4 + plugins/amcrest/.vscode/launch.json | 22 ++ plugins/amcrest/.vscode/settings.json | 4 + plugins/amcrest/.vscode/tasks.json | 20 ++ plugins/amcrest/README.md | 15 + plugins/amcrest/package-lock.json | 413 ++++++++++++++++++++++++++ plugins/amcrest/package.json | 42 +++ plugins/amcrest/src/amcrest-api.ts | 75 +++++ plugins/amcrest/src/main.ts | 133 +++++++++ 10 files changed, 732 insertions(+) create mode 100644 plugins/amcrest/.gitignore create mode 100644 plugins/amcrest/.npmignore create mode 100644 plugins/amcrest/.vscode/launch.json create mode 100644 plugins/amcrest/.vscode/settings.json create mode 100644 plugins/amcrest/.vscode/tasks.json create mode 100644 plugins/amcrest/README.md create mode 100644 plugins/amcrest/package-lock.json create mode 100644 plugins/amcrest/package.json create mode 100644 plugins/amcrest/src/amcrest-api.ts create mode 100644 plugins/amcrest/src/main.ts diff --git a/plugins/amcrest/.gitignore b/plugins/amcrest/.gitignore new file mode 100644 index 000000000..9cdb546bf --- /dev/null +++ b/plugins/amcrest/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +out/ +node_modules/ +dist/ diff --git a/plugins/amcrest/.npmignore b/plugins/amcrest/.npmignore new file mode 100644 index 000000000..295d8caf5 --- /dev/null +++ b/plugins/amcrest/.npmignore @@ -0,0 +1,4 @@ +.DS_Store +out/ +node_modules/ +dist/*.map \ No newline at end of file diff --git a/plugins/amcrest/.vscode/launch.json b/plugins/amcrest/.vscode/launch.json new file mode 100644 index 000000000..547daa1d7 --- /dev/null +++ b/plugins/amcrest/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Scrypted Debugger", + "address": "${config:scrypted.debugHost}", + "port": 9091, + "request": "attach", + "skipFiles": [ + "/**" + ], + "preLaunchTask": "scrypted: deploy+debug", + "sourceMaps": true, + "localRoot": "${workspaceFolder}/out", + "remoteRoot": "/plugin/", + "type": "pwa-node" + } + ] +} \ No newline at end of file diff --git a/plugins/amcrest/.vscode/settings.json b/plugins/amcrest/.vscode/settings.json new file mode 100644 index 000000000..77ccdbd6d --- /dev/null +++ b/plugins/amcrest/.vscode/settings.json @@ -0,0 +1,4 @@ + +{ + "scrypted.debugHost": "127.0.0.1", +} \ No newline at end of file diff --git a/plugins/amcrest/.vscode/tasks.json b/plugins/amcrest/.vscode/tasks.json new file mode 100644 index 000000000..4d922a539 --- /dev/null +++ b/plugins/amcrest/.vscode/tasks.json @@ -0,0 +1,20 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "scrypted: deploy+debug", + "type": "shell", + "presentation": { + "echo": true, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, + "command": "npm run scrypted-vscode-launch ${config:scrypted.debugHost}", + }, + ] +} diff --git a/plugins/amcrest/README.md b/plugins/amcrest/README.md new file mode 100644 index 000000000..1251e854f --- /dev/null +++ b/plugins/amcrest/README.md @@ -0,0 +1,15 @@ +# Amcrest Plugin + +## npm commands + * npm run scrypted-webpack + * npm run scrypted-deploy + * npm run scrypted-debug + +## scrypted distribution via npm + 1. Ensure package.json is set up properly for publishing on npm. + 2. npm publish + +## Visual Studio Code configuration + +* If using a remote server, edit [.vscode/settings.json](blob/master/.vscode/settings.json) to specify the IP Address of the Scrypted server. +* Launch Scrypted Debugger from the launch menu. diff --git a/plugins/amcrest/package-lock.json b/plugins/amcrest/package-lock.json new file mode 100644 index 000000000..eab93fece --- /dev/null +++ b/plugins/amcrest/package-lock.json @@ -0,0 +1,413 @@ +{ + "name": "@scrypted/amcrest", + "version": "0.0.16", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@scrypted/amcrest", + "version": "0.0.16", + "license": "Apache", + "dependencies": { + "@mhoc/axios-digest-auth": "^0.7.0", + "axios": "^0.21.1", + "multiparty": "^4.2.2", + "url-parse": "^1.4.7" + }, + "devDependencies": { + "@scrypted/sdk": "file:../../sdk", + "@types/multiparty": "^0.0.33", + "@types/node": "^16.7.10" + } + }, + "../../sdk": { + "name": "@scrypted/sdk", + "version": "0.0.67", + "dev": true, + "license": "ISC", + "dependencies": { + "@babel/core": "^7.2.2", + "@babel/plugin-proposal-class-properties": "^7.4.4", + "@babel/plugin-transform-modules-commonjs": "^7.2.0", + "@babel/plugin-transform-typescript": "^7.15.0", + "@babel/polyfill": "^7.2.5", + "@babel/preset-env": "^7.2.3", + "@babel/preset-typescript": "^7.15.0", + "adm-zip": "^0.4.13", + "axios": "^0.21.1", + "babel-loader": "^8.0.4", + "babel-polyfill": "^6.26.0", + "babel-template": "^6.26.0", + "browserify-buffertools": "^1.0.2", + "bytebuffer": "^5.0.1", + "chalk": "^2.4.2", + "clean-webpack-plugin": "^3.0.0", + "engine.io-client": "^3.3.2", + "event-target-shim": "^5.0.1", + "events": "^3.0.0", + "long": "^4.0.0", + "node-cmd": "^3.0.0", + "node-ip": "^0.1.2", + "raw-loader": "^1.0.0", + "terser": "^3.14.1", + "ts-loader": "^5.4.5", + "tsconfig-paths-webpack-plugin": "^3.2.0", + "typescript": "^4.3.5", + "webpack": "^4.28.1", + "webpack-cli": "^3.1.2", + "webpack-inject-plugin": "^1.0.2" + }, + "bin": { + "scrypted-debug": "bin/scrypted-debug.js", + "scrypted-deploy": "bin/scrypted-deploy.js", + "scrypted-deploy-debug": "bin/scrypted-deploy-debug.js", + "scrypted-package-json": "bin/scrypted-package-json.js", + "scrypted-readme": "bin/scrypted-readme.js", + "scrypted-webpack": "bin/scrypted-webpack.js" + }, + "devDependencies": { + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@types/node": "^16.6.1", + "babel-plugin-const-enum": "^1.1.0", + "babel-plugin-minify-dead-code-elimination": "^0.5.1" + } + }, + "../sdk": { + "extraneous": true + }, + "node_modules/@mhoc/axios-digest-auth": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@mhoc/axios-digest-auth/-/axios-digest-auth-0.7.0.tgz", + "integrity": "sha512-fDi8fNmuLmX86XEtqD6E59ytLROjmo3O9ZZAT5ndnFJpntOg1912sV/dA218GfQv6UviCFnpEogwuyWTnHmGng==", + "dependencies": { + "axios": "0.21.1" + } + }, + "node_modules/@scrypted/sdk": { + "resolved": "../../sdk", + "link": true + }, + "node_modules/@types/multiparty": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/multiparty/-/multiparty-0.0.33.tgz", + "integrity": "sha512-Il6cJUpSqgojT7NxbVJUvXkCblm50/yEJYtblISDsNIeNYf4yMAhdizzidUk6h8pJ8yhwK/3Fkb+3Dwcgtwl8w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "16.7.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", + "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==", + "dev": true + }, + "node_modules/axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "dependencies": { + "follow-redirects": "^1.10.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/follow-redirects": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz", + "integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/multiparty": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.2.tgz", + "integrity": "sha512-NtZLjlvsjcoGrzojtwQwn/Tm90aWJ6XXtPppYF4WmOk/6ncdwMMKggFY2NlRRN9yiCEIVxpOfPWahVEG2HAG8Q==", + "dependencies": { + "http-errors": "~1.8.0", + "safe-buffer": "5.2.1", + "uid-safe": "2.1.5" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + } + }, + "dependencies": { + "@mhoc/axios-digest-auth": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@mhoc/axios-digest-auth/-/axios-digest-auth-0.7.0.tgz", + "integrity": "sha512-fDi8fNmuLmX86XEtqD6E59ytLROjmo3O9ZZAT5ndnFJpntOg1912sV/dA218GfQv6UviCFnpEogwuyWTnHmGng==", + "requires": { + "axios": "0.21.1" + } + }, + "@scrypted/sdk": { + "version": "file:../../sdk", + "requires": { + "@babel/core": "^7.2.2", + "@babel/plugin-proposal-class-properties": "^7.4.4", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/plugin-transform-modules-commonjs": "^7.2.0", + "@babel/plugin-transform-typescript": "^7.15.0", + "@babel/polyfill": "^7.2.5", + "@babel/preset-env": "^7.2.3", + "@babel/preset-typescript": "^7.15.0", + "@types/node": "^16.6.1", + "adm-zip": "^0.4.13", + "axios": "^0.21.1", + "babel-loader": "^8.0.4", + "babel-plugin-const-enum": "^1.1.0", + "babel-plugin-minify-dead-code-elimination": "^0.5.1", + "babel-polyfill": "^6.26.0", + "babel-template": "^6.26.0", + "browserify-buffertools": "^1.0.2", + "bytebuffer": "^5.0.1", + "chalk": "^2.4.2", + "clean-webpack-plugin": "^3.0.0", + "engine.io-client": "^3.3.2", + "event-target-shim": "^5.0.1", + "events": "^3.0.0", + "long": "^4.0.0", + "node-cmd": "^3.0.0", + "node-ip": "^0.1.2", + "raw-loader": "^1.0.0", + "terser": "^3.14.1", + "ts-loader": "^5.4.5", + "tsconfig-paths-webpack-plugin": "^3.2.0", + "typescript": "^4.3.5", + "webpack": "^4.28.1", + "webpack-cli": "^3.1.2", + "webpack-inject-plugin": "^1.0.2" + } + }, + "@types/multiparty": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/multiparty/-/multiparty-0.0.33.tgz", + "integrity": "sha512-Il6cJUpSqgojT7NxbVJUvXkCblm50/yEJYtblISDsNIeNYf4yMAhdizzidUk6h8pJ8yhwK/3Fkb+3Dwcgtwl8w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "16.7.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", + "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==", + "dev": true + }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "follow-redirects": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz", + "integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==" + }, + "http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "multiparty": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.2.tgz", + "integrity": "sha512-NtZLjlvsjcoGrzojtwQwn/Tm90aWJ6XXtPppYF4WmOk/6ncdwMMKggFY2NlRRN9yiCEIVxpOfPWahVEG2HAG8Q==", + "requires": { + "http-errors": "~1.8.0", + "safe-buffer": "5.2.1", + "uid-safe": "2.1.5" + } + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + } + } +} diff --git a/plugins/amcrest/package.json b/plugins/amcrest/package.json new file mode 100644 index 000000000..53de39235 --- /dev/null +++ b/plugins/amcrest/package.json @@ -0,0 +1,42 @@ +{ + "name": "@scrypted/amcrest", + "version": "0.0.16", + "description": "Amcrest Plugin for Scrypted", + "author": "Scrypted", + "license": "Apache", + "scripts": { + "prepublishOnly": "NODE_ENV=production scrypted-webpack", + "prescrypted-vscode-launch": "scrypted-webpack", + "scrypted-vscode-launch": "scrypted-deploy-debug", + "scrypted-deploy-debug": "scrypted-deploy-debug", + "scrypted-debug": "scrypted-debug", + "scrypted-deploy": "scrypted-deploy", + "scrypted-readme": "scrypted-readme", + "scrypted-package-json": "scrypted-package-json", + "scrypted-webpack": "scrypted-webpack" + }, + "keywords": [ + "scrypted", + "plugin", + "amcrest" + ], + "scrypted": { + "name": "Amcrest Camera Controller", + "type": "DeviceProvider", + "interfaces": [ + "DeviceProvider", + "Settings" + ] + }, + "devDependencies": { + "@scrypted/sdk": "file:../../sdk", + "@types/multiparty": "^0.0.33", + "@types/node": "^16.7.10" + }, + "dependencies": { + "@mhoc/axios-digest-auth": "^0.7.0", + "axios": "^0.21.1", + "multiparty": "^4.2.2", + "url-parse": "^1.4.7" + } +} diff --git a/plugins/amcrest/src/amcrest-api.ts b/plugins/amcrest/src/amcrest-api.ts new file mode 100644 index 000000000..c756f55e4 --- /dev/null +++ b/plugins/amcrest/src/amcrest-api.ts @@ -0,0 +1,75 @@ +import AxiosDigestAuth from '@mhoc/axios-digest-auth'; +import { Socket } from 'net'; +import { PassThrough, Readable, Stream } from 'stream'; +import {Form, Part} from 'multiparty'; +import { once } from 'events'; + +export enum AmcrestEvent { + MotionStart = "MotionStart", + MotionStop = "MotionStop", +} + +async function readEvent(readable: Readable): Promise { + const pt = new PassThrough(); + readable.pipe(pt); + const buffers: Buffer[] = []; + for await (const buffer of pt) { + buffers.push(buffer); + const data = Buffer.concat(buffers).toString(); + if (data.indexOf('Code=VideoMotion;action=Stop') !== -1) { + return AmcrestEvent.MotionStop; + } + else if (data.indexOf('Code=VideoMotion;action=Start') !== -1) { + return AmcrestEvent.MotionStart; + } + } +} + +export class AmcrestCameraClient { + digestAuth: AxiosDigestAuth; + + constructor(public ip: string, public username: string, public password: string) { + + this.digestAuth = new AxiosDigestAuth({ + username, + password, + }); + } + + async jpegSnapshot(): Promise { + const response = await this.digestAuth.request({ + method: "GET", + responseType: 'arraybuffer', + url: `http://${this.ip}/cgi-bin/snapshot.cgi`, + }); + + return Buffer.from(response.data); + } + + async* listenForMotionEvents(): AsyncGenerator { + const response = await this.digestAuth.request({ + method: "GET", + responseType: 'stream', + url: `http://${this.ip}/cgi-bin/eventManager.cgi?action=attach&codes=[VideoMotion]`, + }); + + const stream = response.data; + + const form = new Form(); + + try { + // massage this so the parser doesn't fail on a bad content type + stream.headers['content-type'] = stream.headers['content-type'].replace('multipart/x-mixed-replace', 'multipart/form-data'); + form.parse(stream); + while (true) { + const [part] = await once(form, 'part'); + const event = await readEvent(part); + if (event) + yield event; + } + } + finally { + stream.destroy(); + } + } +} diff --git a/plugins/amcrest/src/main.ts b/plugins/amcrest/src/main.ts new file mode 100644 index 000000000..a9145c0f4 --- /dev/null +++ b/plugins/amcrest/src/main.ts @@ -0,0 +1,133 @@ +import sdk, { ScryptedDeviceBase, DeviceProvider, Settings, Setting, ScryptedDeviceType, VideoCamera, MediaObject, VideoStreamOptions, Camera, ScryptedInterface } from "@scrypted/sdk"; +import { Stream } from "stream"; +import { AmcrestCameraClient, AmcrestEvent } from "./amcrest-api"; +const { log, deviceManager, mediaManager } = sdk; + +class AmcrestCamera extends ScryptedDeviceBase implements VideoCamera, Camera, Settings { + eventStream: Stream; + + constructor(nativeId: string) { + super(nativeId); + + this.createMotionStream(); + } + + async createMotionStream() { + while (true) { + try { + this.motionDetected = false; + + const api = new AmcrestCameraClient(this.storage.getItem('ip'), this.storage.getItem('username'), this.storage.getItem('password')); + for await (const event of api.listenForMotionEvents()) { + if (event === AmcrestEvent.MotionStart) + this.motionDetected = true; + else if (event === AmcrestEvent.MotionStop) { + this.motionDetected = false; + } + } + } + catch (e) { + console.error('event listener failure', e); + await new Promise(resolve => setTimeout(resolve, 10000)); + } + } + } + + createClient() { + return new AmcrestCameraClient(this.storage.getItem('ip'), this.storage.getItem('username'), this.storage.getItem('password')); + } + + async takePicture(): Promise { + const api = new AmcrestCameraClient(this.storage.getItem('ip'), this.storage.getItem('username'), this.storage.getItem('password')); + return mediaManager.createMediaObject(api.jpegSnapshot(), 'image/jpeg'); + } + + async getVideoStreamOptions(): Promise { + } + async getVideoStream(): Promise { + const ip = this.storage.getItem('ip'); + if (!ip) { + return null; + } + const username = this.storage.getItem("username") + const password = this.storage.getItem("password"); + const url = `rtsp://${username}:${password}@${ip}/cam/realmonitor?channel=1&subtype=0`; + + return mediaManager.createFFmpegMediaObject({ + inputArguments: [ + "-i", + url, + '-analyzeduration', '15000000', + '-probesize', '100000000', + "-reorder_queue_size", + "1024", + "-max_delay", + "20000000", + ] + }); + } + getSetting(key: string): string { + return this.storage.getItem(key); + } + async getSettings(): Promise { + return [ + { + key: 'ip', + title: 'Amcrest Camera IP', + placeholder: '192.168.1.100', + value: this.getSetting('ip'), + }, + { + key: 'username', + title: 'Username', + value: this.getSetting('username'), + }, + { + key: 'password', + title: 'Password', + value: this.getSetting('password'), + type: 'Password', + } + ]; + } + async putSetting(key: string, value: string | number) { + this.storage.setItem(key, value.toString()); + } +} + +class AmcrestProvider extends ScryptedDeviceBase implements DeviceProvider, Settings { + async getSettings(): Promise { + return [ + { + key: 'new-camera', + title: 'Add Camera', + placeholder: 'Camera name, e.g.: Back Yard Camera, Baby Camera, etc', + } + ] + } + async putSetting(key: string, value: string | number) { + // generate a random id + var nativeId = Math.random().toString(); + var name = value.toString(); + + deviceManager.onDeviceDiscovered({ + nativeId, + name: name, + interfaces: [ + ScryptedInterface.VideoCamera, + ScryptedInterface.Camera, + ScryptedInterface.MotionSensor, + ScryptedInterface.Settings, + ], + type: ScryptedDeviceType.Camera, + }); + } + async discoverDevices(duration: number) { + } + + getDevice(nativeId: string): object { + return new AmcrestCamera(nativeId); + } +} + +export default new AmcrestProvider();