mirror of
https://github.com/streamwall/streamwall.git
synced 2025-12-06 01:45:37 -05:00
Add Twitch stream focus announcement bot support
This commit is contained in:
@@ -53,6 +53,10 @@ Streamwall can load stream data from both JSON APIs and TOML files. Data sources
|
||||
npm start -- --data.json-url="https://your-site/api/streams.json" --data.toml-file="./streams.toml"
|
||||
```
|
||||
|
||||
## Twitch bot
|
||||
|
||||
Streamwall can announce the name and URL of streams to your Twitch channel as you focus their audio. Use [twitchtokengenerator.com](https://twitchtokengenerator.com/?scope=chat:read+chat:edit) to generate an OAuth token. See `example.config.toml` for all available options.
|
||||
|
||||
## Hotkeys
|
||||
|
||||
The following hotkeys are available with the "control" webpage focused:
|
||||
|
||||
@@ -35,6 +35,18 @@ json-url = ["https://woke.net/api/streams.json"]
|
||||
# See example.streams.toml for a sample.
|
||||
#toml-file = ["./example.streams.toml"]
|
||||
|
||||
[twitch]
|
||||
#channel = "woke"
|
||||
#username = "bot-username"
|
||||
#color = "#ff0000"
|
||||
|
||||
# Use https://twitchtokengenerator.com/?scope=chat:read+chat:edit to generate an OAuth token:
|
||||
#token = "twitch-oauth-token"
|
||||
|
||||
[twitch.announce]
|
||||
#template = "SingsMic <%- stream.source %> <%- stream.city && stream.state ? `(${stream.city} ${stream.state})` : '' %> <%- stream.link %>"
|
||||
#interval = 60
|
||||
|
||||
[cert]
|
||||
# SSL certificates (optional)
|
||||
# If you specify an https:// URL for the "webserver" option, a certificate will be automatically generated and signed by Let's Encrypt.
|
||||
|
||||
217
package-lock.json
generated
217
package-lock.json
generated
@@ -2102,6 +2102,19 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
||||
},
|
||||
"@types/debug": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz",
|
||||
"integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ=="
|
||||
},
|
||||
"@types/duplexify": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.0.tgz",
|
||||
"integrity": "sha512-5zOA53RUlzN74bvrSGwjudssD9F3a797sDZQkiYpUOxW+WHaXTCPz4/d5Dgi6FKnOqZ2CpaTo0DhgIfsXAOE/A==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/graceful-fs": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz",
|
||||
@@ -2566,6 +2579,11 @@
|
||||
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
|
||||
"dev": true
|
||||
},
|
||||
"array-uniq": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz",
|
||||
"integrity": "sha1-X8w3OSB3VyPP1k1lxkvvU7+eum0="
|
||||
},
|
||||
"array-unique": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
|
||||
@@ -3517,6 +3535,15 @@
|
||||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz",
|
||||
"integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==",
|
||||
"requires": {
|
||||
"color-convert": "^1.9.1",
|
||||
"color-string": "^1.5.2"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
@@ -3530,6 +3557,15 @@
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
},
|
||||
"color-string": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz",
|
||||
"integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==",
|
||||
"requires": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@@ -4155,6 +4191,50 @@
|
||||
"integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
|
||||
"dev": true
|
||||
},
|
||||
"dank-twitch-irc": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/dank-twitch-irc/-/dank-twitch-irc-3.3.0.tgz",
|
||||
"integrity": "sha512-EIGr6Q9LNRR+r274rvv2RrgHHI7vIY0bePY5TR0yxV0dN+36ObKSToFg2BQ8fEMkUotB/0tECwHoK1i1ixCcTQ==",
|
||||
"requires": {
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/duplexify": "^3.6.0",
|
||||
"debug-logger": "^0.4.1",
|
||||
"duplexify": "^4.1.1",
|
||||
"eventemitter3": "^4.0.4",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.pickby": "^4.6.0",
|
||||
"make-error-cause": "^2.3.0",
|
||||
"ms": "^2.1.2",
|
||||
"randomstring": "^1.1.5",
|
||||
"semaphore-async-await": "^1.5.1",
|
||||
"simple-websocket": "^9.0.0",
|
||||
"split2": "^3.1.1",
|
||||
"ts-toolbelt": "^6.9.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"duplexify": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz",
|
||||
"integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==",
|
||||
"requires": {
|
||||
"end-of-stream": "^1.4.1",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1",
|
||||
"stream-shift": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
@@ -4183,6 +4263,29 @@
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"debug-logger": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug-logger/-/debug-logger-0.4.1.tgz",
|
||||
"integrity": "sha1-4zhJw2njzTYbULKZ1xylIkuqGuE=",
|
||||
"requires": {
|
||||
"debug": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
}
|
||||
}
|
||||
},
|
||||
"decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
@@ -4712,6 +4815,11 @@
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"dev": true
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
|
||||
"integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ=="
|
||||
},
|
||||
"events": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz",
|
||||
@@ -8349,6 +8457,16 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
},
|
||||
"lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
|
||||
},
|
||||
"lodash.pickby": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz",
|
||||
"integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8="
|
||||
},
|
||||
"lodash.sortby": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
|
||||
@@ -8402,6 +8520,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
|
||||
},
|
||||
"make-error-cause": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-2.3.0.tgz",
|
||||
"integrity": "sha512-etgt+n4LlOkGSJbBTV9VROHA5R7ekIPS4vfh+bCAoJgRrJWdqJCBbpS3osRJ/HrT7R68MzMiY3L3sDJ/Fd8aBg==",
|
||||
"requires": {
|
||||
"make-error": "^1.3.5"
|
||||
}
|
||||
},
|
||||
"makeerror": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
|
||||
@@ -9795,11 +9926,15 @@
|
||||
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
|
||||
"dev": true
|
||||
},
|
||||
"queue-microtask": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.1.3.tgz",
|
||||
"integrity": "sha512-zC1ZDLKFhZSa8vAdFbkOGouHcOUMgUAI/2/3on/KktpY+BaVqABkzDSsCSvJfmLbICOnrEuF9VIMezZf+T0mBA=="
|
||||
},
|
||||
"randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
@@ -9814,6 +9949,14 @@
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"randomstring": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/randomstring/-/randomstring-1.1.5.tgz",
|
||||
"integrity": "sha1-bfBij3XL1ZMpMNn+OrTpVqGFGMM=",
|
||||
"requires": {
|
||||
"array-uniq": "1.0.2"
|
||||
}
|
||||
},
|
||||
"react-hotkeys-hook": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-2.1.3.tgz",
|
||||
@@ -10362,6 +10505,11 @@
|
||||
"ajv-keywords": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"semaphore-async-await": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/semaphore-async-await/-/semaphore-async-await-1.5.1.tgz",
|
||||
"integrity": "sha1-hXvvXjZEYBykuVcLh+nfXKEpdPo="
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
@@ -10507,6 +10655,45 @@
|
||||
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
|
||||
"dev": true
|
||||
},
|
||||
"simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
|
||||
"requires": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"simple-websocket": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.0.0.tgz",
|
||||
"integrity": "sha512-Q+u1BJ06/FR30xS1Sf6zDuL+vAdAA7VFqZ0TdKpmQKB2uNTAPKWQFFhUDV4YD7TDi7gSRJXoxv21WprNPR0ykQ==",
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
"queue-microtask": "^1.1.0",
|
||||
"randombytes": "^2.0.3",
|
||||
"readable-stream": "^3.1.1",
|
||||
"ws": "^7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sisteransi": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||
@@ -10730,6 +10917,26 @@
|
||||
"extend-shallow": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"split2": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-3.1.1.tgz",
|
||||
"integrity": "sha512-emNzr1s7ruq4N+1993yht631/JH+jaj0NYBosuKmLcq+JkGQ9MmTw1RB1fGaTCzUuseRIClrlSLHRNYGwWQ58Q==",
|
||||
"requires": {
|
||||
"readable-stream": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
|
||||
@@ -10853,8 +11060,7 @@
|
||||
"stream-shift": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
|
||||
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
|
||||
},
|
||||
"string-length": {
|
||||
"version": "4.0.1",
|
||||
@@ -11336,6 +11542,11 @@
|
||||
"utf8-byte-length": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"ts-toolbelt": {
|
||||
"version": "6.9.9",
|
||||
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.9.9.tgz",
|
||||
"integrity": "sha512-5a8k6qfbrL54N4Dw+i7M6kldrbjgDWb5Vit8DnT+gwThhvqMg8KtxLE5Vmnft+geIgaSOfNJyAcnmmlflS+Vdg=="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@repeaterjs/repeater": "^3.0.1",
|
||||
"chokidar": "^3.4.0",
|
||||
"color": "^3.1.2",
|
||||
"dank-twitch-irc": "^3.3.0",
|
||||
"ejs": "^3.1.3",
|
||||
"electron": "^9.0.4",
|
||||
"koa": "^2.12.1",
|
||||
|
||||
104
src/node/TwitchBot.js
Normal file
104
src/node/TwitchBot.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import EventEmitter from 'events'
|
||||
|
||||
import ejs from 'ejs'
|
||||
import { State } from 'xstate'
|
||||
import { ChatClient, SlowModeRateLimiter, LoginError } from 'dank-twitch-irc'
|
||||
|
||||
export default class TwitchBot extends EventEmitter {
|
||||
constructor(config) {
|
||||
super()
|
||||
const { username, token } = config
|
||||
this.config = config
|
||||
this.announceTemplate = ejs.compile(config.announce.template)
|
||||
const client = new ChatClient({
|
||||
username,
|
||||
password: `oauth:${token}`,
|
||||
rateLimits: 'default',
|
||||
})
|
||||
client.use(new SlowModeRateLimiter(client, 0))
|
||||
this.client = client
|
||||
|
||||
this.streams = null
|
||||
this.listeningURL = null
|
||||
this.announceTimeouts = new Map()
|
||||
|
||||
client.on('ready', () => {
|
||||
this.onReady()
|
||||
})
|
||||
client.on('error', (err) => {
|
||||
console.error('Twitch connection error:', err)
|
||||
if (err instanceof LoginError) {
|
||||
client.close()
|
||||
}
|
||||
})
|
||||
client.on('close', (err) => {
|
||||
console.log('Twitch bot disconnected.')
|
||||
if (err != null) {
|
||||
console.error('Twitch bot disconnected due to error:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
connect() {
|
||||
const { client } = this
|
||||
client.connect()
|
||||
}
|
||||
|
||||
async onReady() {
|
||||
const { client } = this
|
||||
const { channel, color } = this.config
|
||||
await client.setColor(color)
|
||||
await client.join(channel)
|
||||
this.emit('connected')
|
||||
}
|
||||
|
||||
onState({ views, streams }) {
|
||||
this.streams = streams
|
||||
|
||||
const listeningView = views.find(({ state, context }) =>
|
||||
State.from(state, context).matches('displaying.running.audio.listening'),
|
||||
)
|
||||
if (!listeningView) {
|
||||
return
|
||||
}
|
||||
|
||||
const listeningURL = listeningView.context.content.url
|
||||
if (listeningURL === this.listeningURL) {
|
||||
return
|
||||
}
|
||||
this.listeningURL = listeningURL
|
||||
this.onListeningURLChange(listeningURL)
|
||||
}
|
||||
|
||||
async onListeningURLChange(listeningURL) {
|
||||
if (!this.announceTimeouts.has(listeningURL)) {
|
||||
await this.announce()
|
||||
}
|
||||
}
|
||||
|
||||
async announce() {
|
||||
const { client, listeningURL, streams } = this
|
||||
const { channel, announce } = this.config
|
||||
|
||||
if (!client.ready) {
|
||||
return
|
||||
}
|
||||
|
||||
const stream = streams.find((s) => s.link === listeningURL)
|
||||
if (!stream) {
|
||||
return
|
||||
}
|
||||
|
||||
const msg = this.announceTemplate({ stream })
|
||||
await client.say(channel, msg)
|
||||
this.emit('sent', msg)
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
this.announceTimeouts.delete(listeningURL)
|
||||
if (this.listeningURL === listeningURL) {
|
||||
this.announce()
|
||||
}
|
||||
}, announce.interval * 1000)
|
||||
this.announceTimeouts.set(listeningURL, timeout)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from 'fs'
|
||||
import yargs from 'yargs'
|
||||
import TOML from '@iarna/toml'
|
||||
import Color from 'color'
|
||||
import { Repeater } from '@repeaterjs/repeater'
|
||||
import { app, shell, session, BrowserWindow } from 'electron'
|
||||
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
combineDataSources,
|
||||
} from './data'
|
||||
import StreamWindow from './StreamWindow'
|
||||
import TwitchBot from './TwitchBot'
|
||||
import StreamdelayClient from './StreamdelayClient'
|
||||
import initWebServer from './server'
|
||||
|
||||
@@ -71,6 +73,45 @@ function parseArgs() {
|
||||
array: true,
|
||||
default: [],
|
||||
})
|
||||
.group(
|
||||
[
|
||||
'twitch.channel',
|
||||
'twitch.username',
|
||||
'twitch.password',
|
||||
'twitch.color',
|
||||
'twitch.announce.template',
|
||||
'twitch.announce.interval-seconds',
|
||||
],
|
||||
'Twitch Chat',
|
||||
)
|
||||
.option('twitch.channel', {
|
||||
describe: 'Name of Twitch channel',
|
||||
default: null,
|
||||
})
|
||||
.option('twitch.username', {
|
||||
describe: 'Username of Twitch bot account',
|
||||
default: null,
|
||||
})
|
||||
.option('twitch.password', {
|
||||
describe: 'Password of Twitch bot account',
|
||||
default: null,
|
||||
})
|
||||
.option('twitch.color', {
|
||||
describe: 'Color of Twitch bot username',
|
||||
coerce: (text) => Color(text).object(),
|
||||
default: { r: 255, g: 0, b: 0 },
|
||||
})
|
||||
.option('twitch.announce.template', {
|
||||
describe: 'Message template for stream announcements',
|
||||
default:
|
||||
'SingsMic <%- stream.source %> <%- stream.city && stream.state ? `(${stream.city} ${stream.state})` : `` %> <%- stream.link %>',
|
||||
})
|
||||
.option('twitch.announce.interval', {
|
||||
describe:
|
||||
'Minimum time interval (in seconds) between re-announcing the same stream',
|
||||
number: true,
|
||||
default: 60,
|
||||
})
|
||||
.group(
|
||||
[
|
||||
'control.username',
|
||||
@@ -163,6 +204,7 @@ async function main() {
|
||||
streamWindow.init()
|
||||
|
||||
let browseWindow = null
|
||||
let twitchBot = null
|
||||
let streamdelayClient = null
|
||||
|
||||
const clientState = {
|
||||
@@ -250,10 +292,18 @@ async function main() {
|
||||
streamdelayClient.connect()
|
||||
}
|
||||
|
||||
if (argv.twitch.token) {
|
||||
twitchBot = new TwitchBot(argv.twitch)
|
||||
twitchBot.connect()
|
||||
}
|
||||
|
||||
streamWindow.on('state', (viewStates) => {
|
||||
clientState.views = viewStates
|
||||
streamWindow.send('state', clientState)
|
||||
broadcastState(clientState)
|
||||
if (twitchBot) {
|
||||
twitchBot.onState(clientState)
|
||||
}
|
||||
})
|
||||
|
||||
const dataSources = [
|
||||
|
||||
Reference in New Issue
Block a user