Initial commit.
This commit is contained in:
parent
489a16c0b5
commit
3cb6f97610
17 changed files with 1881 additions and 0 deletions
26
server/config.template.toml
Normal file
26
server/config.template.toml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
[server]
|
||||
host = ""
|
||||
port = 6969
|
||||
|
||||
[server.auth]
|
||||
enabled = false
|
||||
# username = ""
|
||||
# password = ""
|
||||
|
||||
|
||||
[log]
|
||||
level = "info"
|
||||
|
||||
[twitch]
|
||||
client_id = ""
|
||||
client_secret = ""
|
||||
|
||||
[twitch.auth]
|
||||
redirect_uri = "/twitch/login/callback"
|
||||
base_url = "https://id.twitch.tv/oauth2"
|
||||
scopes = [
|
||||
"channel:read:polls",
|
||||
"channel:manage:polls",
|
||||
"channel:read:predictions",
|
||||
"channel:manage:predictions"
|
||||
]
|
||||
33
server/package.json
Normal file
33
server/package.json
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Oliver Akins",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@types/glob": "^7.2.0",
|
||||
"@types/hapi__basic": "^5.1.2",
|
||||
"@types/hapi__hapi": "^20.0.10",
|
||||
"@types/hapi__inert": "^5.2.3",
|
||||
"@types/node": "^17.0.17"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/basic": "^6.0.0",
|
||||
"@hapi/boom": "^9.1.4",
|
||||
"@hapi/hapi": "^20.2.1",
|
||||
"@hapi/inert": "^6.0.5",
|
||||
"axios": "^0.24.0",
|
||||
"glob": "^7.2.0",
|
||||
"module-alias": "^2.2.2",
|
||||
"path": "^0.12.7",
|
||||
"toml": "^3.0.0",
|
||||
"tslog": "^3.3.2"
|
||||
},
|
||||
"_moduleAliases": {
|
||||
"~": "./dist"
|
||||
}
|
||||
}
|
||||
506
server/pnpm-lock.yaml
generated
Normal file
506
server/pnpm-lock.yaml
generated
Normal file
|
|
@ -0,0 +1,506 @@
|
|||
lockfileVersion: 5.3
|
||||
|
||||
specifiers:
|
||||
'@hapi/basic': ^6.0.0
|
||||
'@hapi/boom': ^9.1.4
|
||||
'@hapi/hapi': ^20.2.1
|
||||
'@hapi/inert': ^6.0.5
|
||||
'@types/glob': ^7.2.0
|
||||
'@types/hapi__basic': ^5.1.2
|
||||
'@types/hapi__hapi': ^20.0.10
|
||||
'@types/hapi__inert': ^5.2.3
|
||||
'@types/node': ^17.0.17
|
||||
axios: ^0.24.0
|
||||
glob: ^7.2.0
|
||||
module-alias: ^2.2.2
|
||||
path: ^0.12.7
|
||||
toml: ^3.0.0
|
||||
tslog: ^3.3.2
|
||||
|
||||
dependencies:
|
||||
'@hapi/basic': 6.0.0
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/hapi': 20.2.1
|
||||
'@hapi/inert': 6.0.5
|
||||
axios: 0.24.0
|
||||
glob: 7.2.0
|
||||
module-alias: 2.2.2
|
||||
path: 0.12.7
|
||||
toml: 3.0.0
|
||||
tslog: 3.3.2
|
||||
|
||||
devDependencies:
|
||||
'@types/glob': 7.2.0
|
||||
'@types/hapi__basic': 5.1.2
|
||||
'@types/hapi__hapi': 20.0.10
|
||||
'@types/hapi__inert': 5.2.3
|
||||
'@types/node': 17.0.17
|
||||
|
||||
packages:
|
||||
|
||||
/@hapi/accept/5.0.2:
|
||||
resolution: {integrity: sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw==}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/hoek': 9.2.1
|
||||
dev: false
|
||||
|
||||
/@hapi/ammo/5.0.1:
|
||||
resolution: {integrity: sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA==}
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.2.1
|
||||
dev: false
|
||||
|
||||
/@hapi/b64/5.0.0:
|
||||
resolution: {integrity: sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw==}
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.2.1
|
||||
|
||||
/@hapi/basic/6.0.0:
|
||||
resolution: {integrity: sha512-nWWSXNCq3WptnP3To2c8kfQiRFDUnd9FQOcMS0B85y1x/m12c0hhp+VdmK60BMe44k6WIog1n6g8f9gZOagqBg==}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/hoek': 9.2.1
|
||||
dev: false
|
||||
|
||||
/@hapi/boom/9.1.4:
|
||||
resolution: {integrity: sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==}
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.2.1
|
||||
|
||||
/@hapi/bounce/2.0.0:
|
||||
resolution: {integrity: sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A==}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/hoek': 9.2.1
|
||||
dev: false
|
||||
|
||||
/@hapi/bourne/2.0.0:
|
||||
resolution: {integrity: sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==}
|
||||
|
||||
/@hapi/call/8.0.1:
|
||||
resolution: {integrity: sha512-bOff6GTdOnoe5b8oXRV3lwkQSb/LAWylvDMae6RgEWWntd0SHtkYbQukDHKlfaYtVnSAgIavJ0kqszF/AIBb6g==}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/hoek': 9.2.1
|
||||
dev: false
|
||||
|
||||
/@hapi/catbox-memory/5.0.1:
|
||||
resolution: {integrity: sha512-QWw9nOYJq5PlvChLWV8i6hQHJYfvdqiXdvTupJFh0eqLZ64Xir7mKNi96d5/ZMUAqXPursfNDIDxjFgoEDUqeQ==}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/hoek': 9.2.1
|
||||
dev: false
|
||||
|
||||
/@hapi/catbox/11.1.1:
|
||||
resolution: {integrity: sha512-u/8HvB7dD/6X8hsZIpskSDo4yMKpHxFd7NluoylhGrL6cUfYxdQPnvUp9YU2C6F9hsyBVLGulBd9vBN1ebfXOQ==}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/hoek': 9.2.1
|
||||
'@hapi/podium': 4.1.3
|
||||
'@hapi/validate': 1.1.3
|
||||
dev: false
|
||||
|
||||
/@hapi/content/5.0.2:
|
||||
resolution: {integrity: sha512-mre4dl1ygd4ZyOH3tiYBrOUBzV7Pu/EOs8VLGf58vtOEECWed8Uuw6B4iR9AN/8uQt42tB04qpVaMyoMQh0oMw==}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
dev: false
|
||||
|
||||
/@hapi/cryptiles/5.1.0:
|
||||
resolution: {integrity: sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
|
||||
/@hapi/file/2.0.0:
|
||||
resolution: {integrity: sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ==}
|
||||
dev: false
|
||||
|
||||
/@hapi/hapi/20.2.1:
|
||||
resolution: {integrity: sha512-OXAU+yWLwkMfPFic+KITo+XPp6Oxpgc9WUH+pxXWcTIuvWbgco5TC/jS8UDvz+NFF5IzRgF2CL6UV/KLdQYUSQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
'@hapi/accept': 5.0.2
|
||||
'@hapi/ammo': 5.0.1
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/bounce': 2.0.0
|
||||
'@hapi/call': 8.0.1
|
||||
'@hapi/catbox': 11.1.1
|
||||
'@hapi/catbox-memory': 5.0.1
|
||||
'@hapi/heavy': 7.0.1
|
||||
'@hapi/hoek': 9.2.1
|
||||
'@hapi/mimos': 6.0.0
|
||||
'@hapi/podium': 4.1.3
|
||||
'@hapi/shot': 5.0.5
|
||||
'@hapi/somever': 3.0.1
|
||||
'@hapi/statehood': 7.0.3
|
||||
'@hapi/subtext': 7.0.3
|
||||
'@hapi/teamwork': 5.1.0
|
||||
'@hapi/topo': 5.1.0
|
||||
'@hapi/validate': 1.1.3
|
||||
dev: false
|
||||
|
||||
/@hapi/heavy/7.0.1:
|
||||
resolution: {integrity: sha512-vJ/vzRQ13MtRzz6Qd4zRHWS3FaUc/5uivV2TIuExGTM9Qk+7Zzqj0e2G7EpE6KztO9SalTbiIkTh7qFKj/33cA==}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/hoek': 9.2.1
|
||||
'@hapi/validate': 1.1.3
|
||||
dev: false
|
||||
|
||||
/@hapi/hoek/9.2.1:
|
||||
resolution: {integrity: sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw==}
|
||||
|
||||
/@hapi/inert/6.0.5:
|
||||
resolution: {integrity: sha512-eVAdUVhJLmmXLM/Zt7u5H5Vzazs9GKe4zfPK2b97ePHEfs3g/AQkxHfYQjJqMy11hvyB7a21Z6rBEA0R//dtXw==}
|
||||
dependencies:
|
||||
'@hapi/ammo': 5.0.1
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/bounce': 2.0.0
|
||||
'@hapi/hoek': 9.2.1
|
||||
'@hapi/validate': 1.1.3
|
||||
lru-cache: 6.0.0
|
||||
dev: false
|
||||
|
||||
/@hapi/iron/6.0.0:
|
||||
resolution: {integrity: sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw==}
|
||||
dependencies:
|
||||
'@hapi/b64': 5.0.0
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/bourne': 2.0.0
|
||||
'@hapi/cryptiles': 5.1.0
|
||||
'@hapi/hoek': 9.2.1
|
||||
|
||||
/@hapi/mimos/6.0.0:
|
||||
resolution: {integrity: sha512-Op/67tr1I+JafN3R3XN5DucVSxKRT/Tc+tUszDwENoNpolxeXkhrJ2Czt6B6AAqrespHoivhgZBWYSuANN9QXg==}
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.2.1
|
||||
mime-db: 1.51.0
|
||||
dev: false
|
||||
|
||||
/@hapi/nigel/4.0.2:
|
||||
resolution: {integrity: sha512-ht2KoEsDW22BxQOEkLEJaqfpoKPXxi7tvabXy7B/77eFtOyG5ZEstfZwxHQcqAiZhp58Ae5vkhEqI03kawkYNw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.2.1
|
||||
'@hapi/vise': 4.0.0
|
||||
dev: false
|
||||
|
||||
/@hapi/pez/5.0.3:
|
||||
resolution: {integrity: sha512-mpikYRJjtrbJgdDHG/H9ySqYqwJ+QU/D7FXsYciS9P7NYBXE2ayKDAy3H0ou6CohOCaxPuTV4SZ0D936+VomHA==}
|
||||
dependencies:
|
||||
'@hapi/b64': 5.0.0
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/content': 5.0.2
|
||||
'@hapi/hoek': 9.2.1
|
||||
'@hapi/nigel': 4.0.2
|
||||
dev: false
|
||||
|
||||
/@hapi/podium/4.1.3:
|
||||
resolution: {integrity: sha512-ljsKGQzLkFqnQxE7qeanvgGj4dejnciErYd30dbrYzUOF/FyS/DOF97qcrT3bhoVwCYmxa6PEMhxfCPlnUcD2g==}
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.2.1
|
||||
'@hapi/teamwork': 5.1.0
|
||||
'@hapi/validate': 1.1.3
|
||||
|
||||
/@hapi/shot/5.0.5:
|
||||
resolution: {integrity: sha512-x5AMSZ5+j+Paa8KdfCoKh+klB78otxF+vcJR/IoN91Vo2e5ulXIW6HUsFTCU+4W6P/Etaip9nmdAx2zWDimB2A==}
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.2.1
|
||||
'@hapi/validate': 1.1.3
|
||||
dev: false
|
||||
|
||||
/@hapi/somever/3.0.1:
|
||||
resolution: {integrity: sha512-4ZTSN3YAHtgpY/M4GOtHUXgi6uZtG9nEZfNI6QrArhK0XN/RDVgijlb9kOmXwCR5VclDSkBul9FBvhSuKXx9+w==}
|
||||
dependencies:
|
||||
'@hapi/bounce': 2.0.0
|
||||
'@hapi/hoek': 9.2.1
|
||||
dev: false
|
||||
|
||||
/@hapi/statehood/7.0.3:
|
||||
resolution: {integrity: sha512-pYB+pyCHkf2Amh67QAXz7e/DN9jcMplIL7Z6N8h0K+ZTy0b404JKPEYkbWHSnDtxLjJB/OtgElxocr2fMH4G7w==}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/bounce': 2.0.0
|
||||
'@hapi/bourne': 2.0.0
|
||||
'@hapi/cryptiles': 5.1.0
|
||||
'@hapi/hoek': 9.2.1
|
||||
'@hapi/iron': 6.0.0
|
||||
'@hapi/validate': 1.1.3
|
||||
dev: false
|
||||
|
||||
/@hapi/subtext/7.0.3:
|
||||
resolution: {integrity: sha512-CekDizZkDGERJ01C0+TzHlKtqdXZxzSWTOaH6THBrbOHnsr3GY+yiMZC+AfNCypfE17RaIakGIAbpL2Tk1z2+A==}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/bourne': 2.0.0
|
||||
'@hapi/content': 5.0.2
|
||||
'@hapi/file': 2.0.0
|
||||
'@hapi/hoek': 9.2.1
|
||||
'@hapi/pez': 5.0.3
|
||||
'@hapi/wreck': 17.1.0
|
||||
dev: false
|
||||
|
||||
/@hapi/teamwork/5.1.0:
|
||||
resolution: {integrity: sha512-llqoQTrAJDTXxG3c4Kz/uzhBS1TsmSBa/XG5SPcVXgmffHE1nFtyLIK0hNJHCB3EuBKT84adzd1hZNY9GJLWtg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
/@hapi/topo/5.1.0:
|
||||
resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.2.1
|
||||
|
||||
/@hapi/validate/1.1.3:
|
||||
resolution: {integrity: sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA==}
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.2.1
|
||||
'@hapi/topo': 5.1.0
|
||||
|
||||
/@hapi/vise/4.0.0:
|
||||
resolution: {integrity: sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg==}
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.2.1
|
||||
dev: false
|
||||
|
||||
/@hapi/wreck/17.1.0:
|
||||
resolution: {integrity: sha512-nx6sFyfqOpJ+EFrHX+XWwJAxs3ju4iHdbB/bwR8yTNZOiYmuhA8eCe7lYPtYmb4j7vyK/SlbaQsmTtUrMvPEBw==}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/bourne': 2.0.0
|
||||
'@hapi/hoek': 9.2.1
|
||||
dev: false
|
||||
|
||||
/@sideway/address/4.1.3:
|
||||
resolution: {integrity: sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ==}
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.2.1
|
||||
dev: true
|
||||
|
||||
/@sideway/formula/3.0.0:
|
||||
resolution: {integrity: sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==}
|
||||
dev: true
|
||||
|
||||
/@sideway/pinpoint/2.0.0:
|
||||
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
|
||||
dev: true
|
||||
|
||||
/@types/glob/7.2.0:
|
||||
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
|
||||
dependencies:
|
||||
'@types/minimatch': 3.0.5
|
||||
'@types/node': 17.0.17
|
||||
dev: true
|
||||
|
||||
/@types/hapi__basic/5.1.2:
|
||||
resolution: {integrity: sha512-sqoQ34nwmRlNgQ5fdHHnghyVzs23aC2d30l9G1sKvdrh4PDV22bnQ+8fBCjoItT+0gGSuXIoG4qdoVZGBjH5EQ==}
|
||||
dependencies:
|
||||
'@types/hapi__hapi': 20.0.10
|
||||
joi: 17.6.0
|
||||
dev: true
|
||||
|
||||
/@types/hapi__catbox/10.2.4:
|
||||
resolution: {integrity: sha512-A6ivRrXD5glmnJna1UAGw87QNZRp/vdFO9U4GS+WhOMWzHnw+oTGkMvg0g6y1930CbeheGOCm7A1qHsqH7AXqg==}
|
||||
dev: true
|
||||
|
||||
/@types/hapi__hapi/20.0.10:
|
||||
resolution: {integrity: sha512-Nt/SY/20/JAlHhbgH616j0g18vsANR9OWoyMdQcytlW6o7TBN+wRgf0MB8AgzjYpuzQam5oTiqyED9WwHmQKYQ==}
|
||||
dependencies:
|
||||
'@hapi/boom': 9.1.4
|
||||
'@hapi/iron': 6.0.0
|
||||
'@hapi/podium': 4.1.3
|
||||
'@types/hapi__catbox': 10.2.4
|
||||
'@types/hapi__mimos': 4.1.4
|
||||
'@types/hapi__shot': 4.1.2
|
||||
'@types/node': 17.0.17
|
||||
joi: 17.6.0
|
||||
dev: true
|
||||
|
||||
/@types/hapi__inert/5.2.3:
|
||||
resolution: {integrity: sha512-I1mWQrEc7oMqGtofT0rwBgRBCBurz0wNzbq8QZsHWR+aXM0bk1j9GA6zwyGIeO53PNl2C1c2kpXlc084xCV+Tg==}
|
||||
dependencies:
|
||||
'@types/hapi__hapi': 20.0.10
|
||||
dev: true
|
||||
|
||||
/@types/hapi__mimos/4.1.4:
|
||||
resolution: {integrity: sha512-i9hvJpFYTT/qzB5xKWvDYaSXrIiNqi4ephi+5Lo6+DoQdwqPXQgmVVOZR+s3MBiHoFqsCZCX9TmVWG3HczmTEQ==}
|
||||
dependencies:
|
||||
'@types/mime-db': 1.43.1
|
||||
dev: true
|
||||
|
||||
/@types/hapi__shot/4.1.2:
|
||||
resolution: {integrity: sha512-8wWgLVP1TeGqgzZtCdt+F+k15DWQvLG1Yv6ZzPfb3D5WIo5/S+GGKtJBVo2uNEcqabP5Ifc71QnJTDnTmw1axA==}
|
||||
dependencies:
|
||||
'@types/node': 17.0.17
|
||||
dev: true
|
||||
|
||||
/@types/mime-db/1.43.1:
|
||||
resolution: {integrity: sha512-kGZJY+R+WnR5Rk+RPHUMERtb2qBRViIHCBdtUrY+NmwuGb8pQdfTqQiCKPrxpdoycl8KWm2DLdkpoSdt479XoQ==}
|
||||
dev: true
|
||||
|
||||
/@types/minimatch/3.0.5:
|
||||
resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==}
|
||||
dev: true
|
||||
|
||||
/@types/node/17.0.17:
|
||||
resolution: {integrity: sha512-e8PUNQy1HgJGV3iU/Bp2+D/DXh3PYeyli8LgIwsQcs1Ar1LoaWHSIT6Rw+H2rNJmiq6SNWiDytfx8+gYj7wDHw==}
|
||||
dev: true
|
||||
|
||||
/axios/0.24.0:
|
||||
resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==}
|
||||
dependencies:
|
||||
follow-redirects: 1.14.8
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
dev: false
|
||||
|
||||
/balanced-match/1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: false
|
||||
|
||||
/brace-expansion/1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
dev: false
|
||||
|
||||
/buffer-from/1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
dev: false
|
||||
|
||||
/concat-map/0.0.1:
|
||||
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
|
||||
dev: false
|
||||
|
||||
/follow-redirects/1.14.8:
|
||||
resolution: {integrity: sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/fs.realpath/1.0.0:
|
||||
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
|
||||
dev: false
|
||||
|
||||
/glob/7.2.0:
|
||||
resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
inflight: 1.0.6
|
||||
inherits: 2.0.4
|
||||
minimatch: 3.0.5
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
dev: false
|
||||
|
||||
/inflight/1.0.6:
|
||||
resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
wrappy: 1.0.2
|
||||
dev: false
|
||||
|
||||
/inherits/2.0.3:
|
||||
resolution: {integrity: sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=}
|
||||
dev: false
|
||||
|
||||
/inherits/2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
dev: false
|
||||
|
||||
/joi/17.6.0:
|
||||
resolution: {integrity: sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==}
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.2.1
|
||||
'@hapi/topo': 5.1.0
|
||||
'@sideway/address': 4.1.3
|
||||
'@sideway/formula': 3.0.0
|
||||
'@sideway/pinpoint': 2.0.0
|
||||
dev: true
|
||||
|
||||
/lru-cache/6.0.0:
|
||||
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
yallist: 4.0.0
|
||||
dev: false
|
||||
|
||||
/mime-db/1.51.0:
|
||||
resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/minimatch/3.0.5:
|
||||
resolution: {integrity: sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==}
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
dev: false
|
||||
|
||||
/module-alias/2.2.2:
|
||||
resolution: {integrity: sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==}
|
||||
dev: false
|
||||
|
||||
/once/1.4.0:
|
||||
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
dev: false
|
||||
|
||||
/path-is-absolute/1.0.1:
|
||||
resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/path/0.12.7:
|
||||
resolution: {integrity: sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=}
|
||||
dependencies:
|
||||
process: 0.11.10
|
||||
util: 0.10.4
|
||||
dev: false
|
||||
|
||||
/process/0.11.10:
|
||||
resolution: {integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI=}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
dev: false
|
||||
|
||||
/source-map-support/0.5.21:
|
||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
source-map: 0.6.1
|
||||
dev: false
|
||||
|
||||
/source-map/0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/toml/3.0.0:
|
||||
resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==}
|
||||
dev: false
|
||||
|
||||
/tslog/3.3.2:
|
||||
resolution: {integrity: sha512-K+XduMfa9+yiHE/vxbUD/dL7RAbw9yIfi9tMjQj3uQ8evkPRKkmw0mQgEkzmueyg23hJHGaOQmDnCEZoKEws+w==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
source-map-support: 0.5.21
|
||||
dev: false
|
||||
|
||||
/util/0.10.4:
|
||||
resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==}
|
||||
dependencies:
|
||||
inherits: 2.0.3
|
||||
dev: false
|
||||
|
||||
/wrappy/1.0.2:
|
||||
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
|
||||
dev: false
|
||||
|
||||
/yallist/4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
dev: false
|
||||
48
server/src/endpoints/auth/twitch/callback.ts
Normal file
48
server/src/endpoints/auth/twitch/callback.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { Request, ResponseToolkit } from "@hapi/hapi";
|
||||
import { TwitchAuth } from "~/utils/TwitchAuth";
|
||||
import { config, log, twitchAuths } from "~/main";
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
method: `GET`, path: `/twitch/login/callback`,
|
||||
options: { auth: false },
|
||||
async handler(request: Request, _: ResponseToolkit): Promise<any> {
|
||||
log.silly(`An authentication request is being processed`);
|
||||
try {
|
||||
|
||||
let code: string = request.query.code;
|
||||
|
||||
let qs = new URLSearchParams();
|
||||
qs.set("client_id", config.twitch.client_id);
|
||||
qs.set("client_secret", config.twitch.client_secret);
|
||||
qs.set("redirect_uri", config.twitch.auth.redirect_uri);
|
||||
qs.set("grant_type", "authorization_code");
|
||||
qs.set("code", code);
|
||||
|
||||
let r = await axios.post(
|
||||
config.twitch.auth.base_url + "/token?" + qs.toString()
|
||||
);
|
||||
|
||||
if (r.status !== 200) {
|
||||
return _.response({
|
||||
message: "Something went wrong while request access from Twitch",
|
||||
}).code(403);
|
||||
};
|
||||
|
||||
let auth = new TwitchAuth(r.data);
|
||||
let userdata = await auth.request<any>(`GET`, `/users`);
|
||||
let user = userdata.data[0];
|
||||
auth.channel = user.login;
|
||||
auth.bid = user.id;
|
||||
|
||||
twitchAuths[auth.channel as string] = auth;
|
||||
|
||||
return _.response({
|
||||
message: `Setup auth correctly for ${auth.channel}, you can leave this page now.`,
|
||||
}).code(200);
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
throw err;
|
||||
};
|
||||
},
|
||||
};
|
||||
19
server/src/endpoints/auth/twitch/redirect.ts
Normal file
19
server/src/endpoints/auth/twitch/redirect.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { Request, ResponseToolkit } from "@hapi/hapi";
|
||||
import { config, log } from "~/main";
|
||||
|
||||
export default {
|
||||
method: `GET`, path: `/twitch/login`,
|
||||
options: { auth: false },
|
||||
async handler(_: Request, h: ResponseToolkit): Promise<any> {
|
||||
log.silly(`A new authentication process has begun`);
|
||||
|
||||
let qs = new URLSearchParams();
|
||||
|
||||
qs.set("client_id", config.twitch.client_id);
|
||||
qs.set("redirect_uri", config.twitch.auth.redirect_uri);
|
||||
qs.set("response_type", "code");
|
||||
qs.set("scope", config.twitch.auth.scopes.join(" "));
|
||||
|
||||
return h.redirect( config.twitch.auth.base_url + "/authorize?" + qs.toString() );
|
||||
},
|
||||
};
|
||||
36
server/src/endpoints/polls/create.ts
Normal file
36
server/src/endpoints/polls/create.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { Request, ResponseToolkit } from "@hapi/hapi";
|
||||
import { log, twitchAuths } from "~/main";
|
||||
import boom from "@hapi/boom";
|
||||
|
||||
export default {
|
||||
method: `POST`, path: `/twitch/{user}/poll`,
|
||||
options: { auth: false },
|
||||
async handler(request: Request, h: ResponseToolkit): Promise<any> {
|
||||
log.debug(`Making a Twitch poll!`);
|
||||
let user = request.params.user;
|
||||
|
||||
// Assert user existance
|
||||
if (twitchAuths[user] == null) {
|
||||
throw boom.notFound("Invalid user");
|
||||
};
|
||||
|
||||
let auth = twitchAuths[user];
|
||||
let pollData = request.payload as any;
|
||||
|
||||
pollData.broadcaster_id = auth.bid;
|
||||
|
||||
try {
|
||||
let r = await auth.request<any>("POST", "/polls", {
|
||||
data: pollData,
|
||||
});
|
||||
|
||||
let poll = r.data[0];
|
||||
|
||||
return h.response({
|
||||
poll_id: poll.id,
|
||||
})
|
||||
} catch (err: any) {
|
||||
throw boom.internal(err.response.message);
|
||||
};
|
||||
},
|
||||
};
|
||||
23
server/src/endpoints/site/channel_polls.ts
Normal file
23
server/src/endpoints/site/channel_polls.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { Request, ResponseToolkit } from "@hapi/hapi";
|
||||
import { twitchAuths, log } from "~/main";
|
||||
import boom from "@hapi/boom";
|
||||
import path from "path";
|
||||
|
||||
export default {
|
||||
method: `GET`, path: `/dashboard/{user}`,
|
||||
options: { auth: 'simple' },
|
||||
async handler(request: Request, h: ResponseToolkit): Promise<any> {
|
||||
let user = request.params.user;
|
||||
|
||||
if (twitchAuths[user] == null) {
|
||||
throw boom.notFound("Invalid user");
|
||||
};
|
||||
|
||||
let uri = path.join(process.cwd(), `../site/${user}.html`);
|
||||
log.silly(`Filepath: ${uri}`)
|
||||
|
||||
return h.file(uri, {
|
||||
confine: false
|
||||
});
|
||||
},
|
||||
};
|
||||
73
server/src/main.ts
Normal file
73
server/src/main.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// Filepath alias resolution
|
||||
import "module-alias/register";
|
||||
|
||||
import { clean_exit } from "~/utils/clean_exit";
|
||||
import { init_webserver } from "~/webserver";
|
||||
import { Logger } from "tslog";
|
||||
import toml from "toml";
|
||||
import fs from "fs";
|
||||
import { TwitchAuth } from "./utils/TwitchAuth";
|
||||
|
||||
|
||||
// Load the config from disk
|
||||
if (!fs.existsSync(`config.toml`)) {
|
||||
console.log(`Please create the config and edit it then run the server again.`);
|
||||
process.exit(1);
|
||||
};
|
||||
export const config: Config = toml.parse(fs.readFileSync(`config.toml`, `utf-8`));
|
||||
|
||||
|
||||
// Setup the logger with the appropriate settings
|
||||
export const log = new Logger({
|
||||
displayFilePath: `hidden`,
|
||||
displayFunctionName: false,
|
||||
displayDateTime: true,
|
||||
displayLogLevel: true,
|
||||
minLevel: config.log.level,
|
||||
name: config.log.name,
|
||||
});
|
||||
|
||||
|
||||
// Load the database
|
||||
if (!fs.existsSync(`data/db.json`)) {
|
||||
log.info(`Can't find database file, creating default`);
|
||||
try {
|
||||
fs.writeFileSync(`data/db.json`, `{"authed_channels": {}}`);
|
||||
} catch (err) {
|
||||
log.error(`Unable to create the default database, make sure the data directory exists.`);
|
||||
process.exit(1);
|
||||
};
|
||||
};
|
||||
export const db: Database = JSON.parse(
|
||||
fs.readFileSync(`data/db.json`, `utf-8`)
|
||||
);
|
||||
|
||||
export const twitchAuths: {[index: string]: TwitchAuth} = {};
|
||||
|
||||
|
||||
// Signal listeners to save persistent storage
|
||||
process.on(`SIGINT`, clean_exit);
|
||||
process.on(`SIGTERM`, clean_exit);
|
||||
process.on(`uncaughtException`, clean_exit);
|
||||
|
||||
|
||||
// Setup the utilities that are needed throughout the system
|
||||
async function init() {
|
||||
|
||||
// Restore all the auth classes for users.
|
||||
for (var token_data of db.authed_channels) {
|
||||
if (token_data.channel) {
|
||||
twitchAuths[token_data.channel] = new TwitchAuth(token_data);
|
||||
} else {
|
||||
let auth = new TwitchAuth(token_data);
|
||||
let userdata = await auth.request<any>(`GET`, `/users`);
|
||||
auth.channel = userdata.data[0].login;
|
||||
|
||||
twitchAuths[auth.channel as string] = auth;
|
||||
};
|
||||
};
|
||||
|
||||
await init_webserver();
|
||||
};
|
||||
|
||||
init();
|
||||
24
server/src/types/Config.d.ts
vendored
Normal file
24
server/src/types/Config.d.ts
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
interface Config {
|
||||
server: {
|
||||
host: string;
|
||||
port: number;
|
||||
auth: {
|
||||
enabled: boolean;
|
||||
username?: string;
|
||||
password?: string;
|
||||
};
|
||||
};
|
||||
twitch: {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
auth: {
|
||||
redirect_uri: string;
|
||||
base_url: string;
|
||||
scopes: string[];
|
||||
};
|
||||
};
|
||||
log: {
|
||||
name: string;
|
||||
level: "silly" | "trace" | "debug" | "info" | "warn" | "error" | "fatal";
|
||||
};
|
||||
};
|
||||
13
server/src/types/Database.d.ts
vendored
Normal file
13
server/src/types/Database.d.ts
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
interface TokenData {
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
channel?: string;
|
||||
scope: string;
|
||||
token_type: string;
|
||||
bid?: string;
|
||||
}
|
||||
|
||||
interface Database {
|
||||
authed_channels: any[];
|
||||
}
|
||||
160
server/src/utils/TwitchAuth.ts
Normal file
160
server/src/utils/TwitchAuth.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import { config, log, twitchAuths } from "~/main";
|
||||
import axios, { Method } from "axios";
|
||||
|
||||
export class TwitchAuth {
|
||||
private _channel: string | undefined;
|
||||
private _token: string;
|
||||
private _refresh_token: string;
|
||||
private token_type: string;
|
||||
private scope: string;
|
||||
private expires_in: number;
|
||||
private invalidated: boolean = false;
|
||||
private _bid: string | undefined;
|
||||
|
||||
constructor(token_data: TokenData) {
|
||||
|
||||
this._token = token_data.access_token;
|
||||
this._refresh_token = token_data.refresh_token;
|
||||
|
||||
this.token_type = token_data.token_type;
|
||||
this.scope = token_data.scope;
|
||||
this.expires_in = token_data.expires_in;
|
||||
|
||||
if (token_data?.channel == null) {
|
||||
log.silly("Unknown channel authenticated, not adding to the twitchAuths.");
|
||||
} else {
|
||||
log.silly("Known channel authenticated.");
|
||||
this._channel = token_data.channel;
|
||||
twitchAuths[this._channel] = this;
|
||||
};
|
||||
if (token_data?.bid == null) {
|
||||
log.silly("Channel auth doesn't contain broadcaster ID");
|
||||
} else {
|
||||
this._bid = token_data.bid;
|
||||
};
|
||||
};
|
||||
|
||||
/** Get the authenticated user's channel name. */
|
||||
private async getChannel() {
|
||||
try {
|
||||
log.debug(`Requesting user info`)
|
||||
let r = await this.request<any>("GET", "/users");
|
||||
this._channel = r.data.data[0].login;
|
||||
if (this._channel) {
|
||||
twitchAuths[this._channel] = this;
|
||||
};
|
||||
} catch (err) {
|
||||
log.error(`User info errored`)
|
||||
setTimeout(() => {
|
||||
this.getChannel();
|
||||
}, 2000);
|
||||
};
|
||||
};
|
||||
|
||||
/** The channel login name that this authentication is for */
|
||||
get channel() { return this._channel };
|
||||
set channel(value: string | undefined) {
|
||||
if (!this._channel) {
|
||||
this._channel = value;
|
||||
};
|
||||
};
|
||||
|
||||
/** The authenticated user's broadcaster ID */
|
||||
get bid() { return this._bid };
|
||||
set bid(value: string | undefined) {
|
||||
if (!this._bid) {
|
||||
this._bid = value
|
||||
};
|
||||
};
|
||||
|
||||
/** The token used to make requests for this channel */
|
||||
get token() { return `Bearer ${this._token}` };
|
||||
|
||||
private async refresh() {
|
||||
|
||||
let qs = new URLSearchParams({
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: this._refresh_token,
|
||||
client_id: config.twitch.client_id,
|
||||
client_secret: config.twitch.client_secret,
|
||||
});
|
||||
|
||||
try {
|
||||
let r = await this.request<TokenData>(
|
||||
"POST",
|
||||
config.twitch.auth.base_url + "/token?" + encodeURIComponent(qs.toString()),
|
||||
);
|
||||
this._refresh_token = r.refresh_token;
|
||||
this._token = r.access_token,
|
||||
this.expires_in = r.expires_in;
|
||||
this.scope = r.scope;
|
||||
this.token_type = r.token_type;
|
||||
} catch (err) {
|
||||
log.error(`Could not refresh the token for ${this._channel}`)
|
||||
}
|
||||
};
|
||||
|
||||
public saveData(): TokenData {
|
||||
return {
|
||||
access_token: this._token,
|
||||
refresh_token: this._refresh_token,
|
||||
token_type: this.token_type,
|
||||
expires_in: this.expires_in,
|
||||
scope: this.scope,
|
||||
channel: this.channel,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes a request to Twitch on behalf of the authenticated user.
|
||||
*
|
||||
* @param method The HTTP method to make the request with
|
||||
* @param url The URL to make the request to Twitch with. Relative URLs are supported.
|
||||
* @param conf The Axios RequestConfig, without method, url, or baseURL
|
||||
* @returns The data returned from the Twitch API
|
||||
*/
|
||||
public async request<T>(method: Method, url: string, conf:any={}): Promise<T> {
|
||||
|
||||
if (this.invalidated) {
|
||||
throw new Error("Can't make a request with an invalidated token");
|
||||
};
|
||||
|
||||
try {
|
||||
let headers = {
|
||||
"Client-Id": config.twitch.client_id,
|
||||
"Authorization": this.token, // Bearer <token>
|
||||
};
|
||||
if (conf?.headers != null) {
|
||||
headers = {
|
||||
...headers,
|
||||
...conf.headers,
|
||||
};
|
||||
delete conf.headers;
|
||||
};
|
||||
|
||||
let r = await axios({
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
baseURL: "https://api.twitch.tv/helix",
|
||||
...(conf ?? {})
|
||||
});
|
||||
|
||||
return r.data as T;
|
||||
} catch (err: any) {
|
||||
if (err.response) {
|
||||
|
||||
// global error handling,
|
||||
switch (err.response.status) {
|
||||
case 403:
|
||||
this.refresh();
|
||||
break;
|
||||
default:
|
||||
log.error(err);
|
||||
};
|
||||
};
|
||||
log.error(err);
|
||||
throw err;
|
||||
};
|
||||
};
|
||||
}
|
||||
24
server/src/utils/clean_exit.ts
Normal file
24
server/src/utils/clean_exit.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { db, log, twitchAuths } from "~/main";
|
||||
import fs from "fs";
|
||||
|
||||
export function clean_exit() {
|
||||
log.info(`Exiting the program cleanly`);
|
||||
|
||||
db.authed_channels = [];
|
||||
for (var channel in twitchAuths) {
|
||||
log.debug(`Saving ${channel}'s auth information into the database file.`);
|
||||
db.authed_channels.push(twitchAuths[channel].saveData());
|
||||
};
|
||||
|
||||
// Attempt to write the persistent storage to the database
|
||||
try {
|
||||
fs.writeFileSync(
|
||||
`./data/db.json`,
|
||||
JSON.stringify(db)
|
||||
);
|
||||
} catch (err) {
|
||||
log.error(`Couldn't save program storage properly, writing to log.`, db);
|
||||
};
|
||||
|
||||
process.exit(1);
|
||||
};
|
||||
63
server/src/webserver.ts
Normal file
63
server/src/webserver.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { ResponseToolkit, Server, ServerRoute } from "@hapi/hapi";
|
||||
import { config, log } from "~/main";
|
||||
import basic from "@hapi/basic";
|
||||
import inert from "@hapi/inert";
|
||||
import glob from "glob";
|
||||
import path from "path";
|
||||
|
||||
export async function init_webserver() {
|
||||
const server = new Server({
|
||||
port: config.server.port,
|
||||
});
|
||||
await server.register(inert);
|
||||
|
||||
|
||||
await server.register(basic);
|
||||
server.auth.strategy(`simple`, `basic`, {
|
||||
// @ts-expect-error
|
||||
async validate(r: Request, user: string, password: string, h: ResponseToolkit) {
|
||||
|
||||
if (!config.server.auth.enabled) {
|
||||
return {
|
||||
isValid: true,
|
||||
credentials: {
|
||||
user: "",
|
||||
password: "",
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// Assert usernames match if it was set in the config
|
||||
if (config.server.auth.username != null) {
|
||||
if (user !== config.server.auth.username) {
|
||||
return {
|
||||
isValid: false,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
isValid: config.server.auth.password === password,
|
||||
credentials: { user, password }
|
||||
};
|
||||
},
|
||||
allowEmptyUsername: true,
|
||||
});
|
||||
server.auth.default(`simple`);
|
||||
|
||||
|
||||
// Register all the endpoints that we need for the server functionality
|
||||
let files = glob.sync(
|
||||
`endpoints/**/!(*.map)`,
|
||||
{ cwd: __dirname, nodir: true }
|
||||
);
|
||||
for (var file of files) {
|
||||
let route: ServerRoute = (await import(path.join(__dirname, file))).default;
|
||||
log.debug(`Registering route: ${route.method} ${route.path}`);
|
||||
server.route(route);
|
||||
};
|
||||
|
||||
server.start().then(() => {
|
||||
log.info(`Server running on: ${config.server.host}:${config.server.port}`);
|
||||
});
|
||||
};
|
||||
74
server/tsconfig.json
Normal file
74
server/tsconfig.json
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
"sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./dist", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
"noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
"paths": {
|
||||
"~/*": [ "./src/*" ]
|
||||
}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue