diff --git a/.gitignore b/.gitignore index 6704566..d439228 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +data/ +config.toml + # Logs logs *.log diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..e105c02 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,126 @@ + + + + + + + Lurk Message Manager + + + + + + + +
+
+

Login

+ + + + + +
+
+ +
+
+
+
+

Message Group {{i + 1}}

+ +
+ + +
+
+ Group ID: {{group.id}} +
+
+

Lurk Messages

+
+
{{msg}}
+ +
+
+
+ + +
+
+
+
+

Unlurk Messages

+
+
{{msg}}
+ +
+
+
+ + +
+
+
+
+
+
+ + \ No newline at end of file diff --git a/docs/script.js b/docs/script.js new file mode 100644 index 0000000..1d08966 --- /dev/null +++ b/docs/script.js @@ -0,0 +1,182 @@ +const app = new Vue({ + el: `#app`, + data() {return { + url: `http://localhost:4000`, + authenticated: false, + channels: [], + channel: ``, + login: { + username: `alkali`, + password: `metal`, + }, + api: null, + messages: [], + new_group: false, + }}, + methods: { + async tryLogin() { + try { + let r = await axios.post( + `${this.url}/login`, + undefined, + { auth: this.login } + ); + this.api = axios.create({ + baseURL: this.url, + validateStatus: null, + auth: this.login, + }); + this.channels.push(...r.data.sort()); + this.authenticated = true; + } catch (_) {}; + }, + async getMessages() { + if (!this.api) { return }; + let r = await this.api.get(`/manage/${this.channel}`); + if (r.status === 200) { + for (const m of r.data) { + m.new = { + lurk: ``, + unlurk: ``, + }; + }; + this.messages = r.data; + } else { + this.messages = []; + }; + }, + async addLurk(group) { + if (group.id === `new-group`) { + group.lurk.push(group.new.lurk); + } else { + if (!this.api) { return }; + let payload = { + lurk: [ + ...group.lurk, + group.new.lurk + ], + unlurk: group.unlurk, + }; + let r = await this.api.patch( + `/manage/${this.channel}/message/${group.id}`, + payload + ); + if (r.status != 200) { + alert(`Failed to update the group. Status: ${r.status}`); + } else { + group.lurk.push(group.new.lurk); + alert(`Added lurk message successfully.`); + }; + }; + group.new.lurk = ``; + }, + async addUnlurk(group) { + if (group.id === `new-group`) { + group.unlurk.push(group.new.unlurk); + } else { + if (!this.api) { return }; + let payload = { + lurk: group.lurk, + unlurk: [ + ...group.unlurk, + group.new.unlurk + ], + }; + let r = await this.api.patch( + `/manage/${this.channel}/message/${group.id}`, + payload + ); + if (r.status != 200) { + alert(`Failed to update the group. Status: ${r.status}`); + } else { + group.unlurk.push(group.new.unlurk); + alert(`Added unlurk message successfully.`); + }; + }; + group.new.unlurk = ``; + }, + addGroup() { + this.new_group = true; + this.messages.unshift({ + lurk: [], + unlurk: [], + id: "new-group", + new: { + lurk: ``, + unlurk: ``, + }, + }); + }, + async saveGroup(group) { + if (!this.api) { return }; + let r = await this.api.post(`/manage/${this.channel}/message`, { + lurk: group.lurk, + unlurk: group.unlurk, + }); + if (r.status === 200) { + this.messages.shift() + this.messages.push({ + ...r.data, + new: { + lurk: ``, + unlurk: ``, + }, + }); + alert(`Group has been saved`); + }; + this.new_group = false; + }, + async deleteGroup(group) { + if (!this.api) { return }; + let r = await this.api.delete(`/manage/${this.channel}/message/${group.id}`); + if (r.status === 200) { + this.messages.splice(this.messages.indexOf(group, 1)); + alert(`Group has been deleted`); + }; + }, + async deleteLurk(group, message) { + if (group.id == "new-group") { + group.lurk.splice(group.lurk.indexOf(message), 1); + } else { + if (!this.api) { return }; + let r = await this.api.patch( + `/manage/${this.channel}/message/${group.id}`, + { + lurk: group.lurk.filter(m => m != message), + unlurk: group.unlurk, + } + ); + if (r.status == 200) { + group.lurk.splice(group.lurk.indexOf(message), 1); + alert(`Deleted message successfully`); + } else { + alert(`Failed to delete the message. Status: ${r.status}`); + }; + }; + }, + async deleteUnlurk(group, message) { + if (group.id === `new-group`) { + group.unlurk.splice(group.unlurk.indexOf(message), 1); + } else { + if (!this.api) { return }; + let r = await this.api.patch( + `/manage/${this.channel}/message/${group.id}`, + { + lurk: group.lurk, + unlurk: group.unlurk.filter(m => m != message), + } + ); + if (r.status == 200) { + group.unlurk.splice(group.unlurk.indexOf(message), 1); + alert(`Deleted message successfully`); + } else { + alert(`Failed to delete the message. Status: ${r.status}`); + }; + }; + }, + removeNewGroup() { + this.messages.shift(); + this.new_group = false; + }, + }, +}); \ No newline at end of file diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 0000000..12cd7f4 --- /dev/null +++ b/docs/style.css @@ -0,0 +1,35 @@ +[v-cloak] { + display: none; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 5px; +} + +#login { + text-align: center; +} + +#login > input { + margin: 5px auto; +} + +.lurk-group { + background: #1a242f; + border-radius: 10px; + margin: 7px; + padding: 10px; +} + +.message { + display: flex; + align-items: center; +} + +.flex-container { + display: flex; +} + +.message-input { + flex-grow: 1; +} \ No newline at end of file diff --git a/makefile b/makefile new file mode 100644 index 0000000..9083453 --- /dev/null +++ b/makefile @@ -0,0 +1,8 @@ +.PHONY: dist dev prod + +dist: + @rm -rf dist + tsc + +dev: dist + NODE_ENV=development node dist/main.js \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6296103 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "twitch-lurk-message-api", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "Oliver Akins", + "license": "UNLICENSED", + "dependencies": { + "@hapi/basic": "^6.0.0", + "@hapi/boom": "^10.0.0", + "@hapi/hapi": "^20.2.2", + "glob": "^8.0.3", + "joi": "^17.6.0", + "module-alias": "^2.2.2", + "toml": "^3.0.0", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@types/glob": "^7.2.0", + "@types/hapi__basic": "^5.1.2", + "@types/hapi__hapi": "^20.0.12", + "@types/node": "^18.0.6", + "@types/uuid": "^8.3.4" + }, + "_moduleAliases": { + "@": "./dist" + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..1601e83 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,417 @@ +lockfileVersion: 5.4 + +specifiers: + '@hapi/basic': ^6.0.0 + '@hapi/boom': ^10.0.0 + '@hapi/hapi': ^20.2.2 + '@types/glob': ^7.2.0 + '@types/hapi__basic': ^5.1.2 + '@types/hapi__hapi': ^20.0.12 + '@types/node': ^18.0.6 + '@types/uuid': ^8.3.4 + glob: ^8.0.3 + joi: ^17.6.0 + module-alias: ^2.2.2 + toml: ^3.0.0 + uuid: ^8.3.2 + +dependencies: + '@hapi/basic': 6.0.0 + '@hapi/boom': 10.0.0 + '@hapi/hapi': 20.2.2 + glob: 8.0.3 + joi: 17.6.0 + module-alias: 2.2.2 + toml: 3.0.0 + uuid: 8.3.2 + +devDependencies: + '@types/glob': 7.2.0 + '@types/hapi__basic': 5.1.2 + '@types/hapi__hapi': 20.0.12 + '@types/node': 18.0.6 + '@types/uuid': 8.3.4 + +packages: + + /@hapi/accept/5.0.2: + resolution: {integrity: sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw==} + dependencies: + '@hapi/boom': 9.1.4 + '@hapi/hoek': 9.3.0 + dev: false + + /@hapi/ammo/5.0.1: + resolution: {integrity: sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA==} + dependencies: + '@hapi/hoek': 9.3.0 + dev: false + + /@hapi/b64/5.0.0: + resolution: {integrity: sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw==} + dependencies: + '@hapi/hoek': 9.3.0 + + /@hapi/basic/6.0.0: + resolution: {integrity: sha512-nWWSXNCq3WptnP3To2c8kfQiRFDUnd9FQOcMS0B85y1x/m12c0hhp+VdmK60BMe44k6WIog1n6g8f9gZOagqBg==} + dependencies: + '@hapi/boom': 9.1.4 + '@hapi/hoek': 9.3.0 + dev: false + + /@hapi/boom/10.0.0: + resolution: {integrity: sha512-1YVs9tLHhypBqqinKQRqh7FUERIolarQApO37OWkzD+z6y6USi871Sv746zBPKcIOBuI6g6y4FrwX87mmJ90Gg==} + dependencies: + '@hapi/hoek': 10.0.0 + dev: false + + /@hapi/boom/9.1.4: + resolution: {integrity: sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==} + dependencies: + '@hapi/hoek': 9.3.0 + + /@hapi/bounce/2.0.0: + resolution: {integrity: sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A==} + dependencies: + '@hapi/boom': 9.1.4 + '@hapi/hoek': 9.3.0 + dev: false + + /@hapi/bourne/2.1.0: + resolution: {integrity: sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q==} + + /@hapi/call/8.0.1: + resolution: {integrity: sha512-bOff6GTdOnoe5b8oXRV3lwkQSb/LAWylvDMae6RgEWWntd0SHtkYbQukDHKlfaYtVnSAgIavJ0kqszF/AIBb6g==} + dependencies: + '@hapi/boom': 9.1.4 + '@hapi/hoek': 9.3.0 + dev: false + + /@hapi/catbox-memory/5.0.1: + resolution: {integrity: sha512-QWw9nOYJq5PlvChLWV8i6hQHJYfvdqiXdvTupJFh0eqLZ64Xir7mKNi96d5/ZMUAqXPursfNDIDxjFgoEDUqeQ==} + dependencies: + '@hapi/boom': 9.1.4 + '@hapi/hoek': 9.3.0 + dev: false + + /@hapi/catbox/11.1.1: + resolution: {integrity: sha512-u/8HvB7dD/6X8hsZIpskSDo4yMKpHxFd7NluoylhGrL6cUfYxdQPnvUp9YU2C6F9hsyBVLGulBd9vBN1ebfXOQ==} + dependencies: + '@hapi/boom': 9.1.4 + '@hapi/hoek': 9.3.0 + '@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.2: + resolution: {integrity: sha512-crhU6TIKt7QsksWLYctDBAXogk9PYAm7UzdpETyuBHC2pCa6/+B5NykiOVLG/3FCIgHo/raPVtan8bYtByHORQ==} + 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.3.0 + '@hapi/mimos': 6.0.0 + '@hapi/podium': 4.1.3 + '@hapi/shot': 5.0.5 + '@hapi/somever': 3.0.1 + '@hapi/statehood': 7.0.4 + '@hapi/subtext': 7.0.4 + '@hapi/teamwork': 5.1.1 + '@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.3.0 + '@hapi/validate': 1.1.3 + dev: false + + /@hapi/hoek/10.0.0: + resolution: {integrity: sha512-CeNFz1JcLZ5xE8Vc9aau37cgHw9bxXqSDK/D55GF2GAOv0n0XjyyjSodHtKahB7A1tV3FlgCpijp3zkSITmBdA==} + dev: false + + /@hapi/hoek/9.3.0: + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + /@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.1.0 + '@hapi/cryptiles': 5.1.0 + '@hapi/hoek': 9.3.0 + + /@hapi/mimos/6.0.0: + resolution: {integrity: sha512-Op/67tr1I+JafN3R3XN5DucVSxKRT/Tc+tUszDwENoNpolxeXkhrJ2Czt6B6AAqrespHoivhgZBWYSuANN9QXg==} + dependencies: + '@hapi/hoek': 9.3.0 + mime-db: 1.52.0 + dev: false + + /@hapi/nigel/4.0.2: + resolution: {integrity: sha512-ht2KoEsDW22BxQOEkLEJaqfpoKPXxi7tvabXy7B/77eFtOyG5ZEstfZwxHQcqAiZhp58Ae5vkhEqI03kawkYNw==} + engines: {node: '>=12.0.0'} + dependencies: + '@hapi/hoek': 9.3.0 + '@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.3.0 + '@hapi/nigel': 4.0.2 + dev: false + + /@hapi/podium/4.1.3: + resolution: {integrity: sha512-ljsKGQzLkFqnQxE7qeanvgGj4dejnciErYd30dbrYzUOF/FyS/DOF97qcrT3bhoVwCYmxa6PEMhxfCPlnUcD2g==} + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/teamwork': 5.1.1 + '@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.3.0 + '@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.3.0 + dev: false + + /@hapi/statehood/7.0.4: + resolution: {integrity: sha512-Fia6atroOVmc5+2bNOxF6Zv9vpbNAjEXNcUbWXavDqhnJDlchwUUwKS5LCi5mGtCTxRhUKKHwuxuBZJkmLZ7fw==} + dependencies: + '@hapi/boom': 9.1.4 + '@hapi/bounce': 2.0.0 + '@hapi/bourne': 2.1.0 + '@hapi/cryptiles': 5.1.0 + '@hapi/hoek': 9.3.0 + '@hapi/iron': 6.0.0 + '@hapi/validate': 1.1.3 + dev: false + + /@hapi/subtext/7.0.4: + resolution: {integrity: sha512-Y72moHhbRuO8kwBHFEnCRw7oOnhNh4Pl+aonxAze18jkyMpE4Gwz4lNID7ei8vd3lpXC2rKdkxXJgtfY+WttRw==} + dependencies: + '@hapi/boom': 9.1.4 + '@hapi/bourne': 2.1.0 + '@hapi/content': 5.0.2 + '@hapi/file': 2.0.0 + '@hapi/hoek': 9.3.0 + '@hapi/pez': 5.0.3 + '@hapi/wreck': 17.2.0 + dev: false + + /@hapi/teamwork/5.1.1: + resolution: {integrity: sha512-1oPx9AE5TIv+V6Ih54RP9lTZBso3rP8j4Xhb6iSVwPXtAM+sDopl5TFMv5Paw73UnpZJ9gjcrTE1BXrWt9eQrg==} + engines: {node: '>=12.0.0'} + + /@hapi/topo/5.1.0: + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + dependencies: + '@hapi/hoek': 9.3.0 + + /@hapi/validate/1.1.3: + resolution: {integrity: sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA==} + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + + /@hapi/vise/4.0.0: + resolution: {integrity: sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg==} + dependencies: + '@hapi/hoek': 9.3.0 + dev: false + + /@hapi/wreck/17.2.0: + resolution: {integrity: sha512-pJ5kjYoRPYDv+eIuiLQqhGon341fr2bNIYZjuotuPJG/3Ilzr/XtI+JAp0A86E2bYfsS3zBPABuS2ICkaXFT8g==} + dependencies: + '@hapi/boom': 9.1.4 + '@hapi/bourne': 2.1.0 + '@hapi/hoek': 9.3.0 + dev: false + + /@sideway/address/4.1.4: + resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} + dependencies: + '@hapi/hoek': 9.3.0 + + /@sideway/formula/3.0.0: + resolution: {integrity: sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==} + + /@sideway/pinpoint/2.0.0: + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + + /@types/glob/7.2.0: + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} + dependencies: + '@types/minimatch': 3.0.5 + '@types/node': 18.0.6 + dev: true + + /@types/hapi__basic/5.1.2: + resolution: {integrity: sha512-sqoQ34nwmRlNgQ5fdHHnghyVzs23aC2d30l9G1sKvdrh4PDV22bnQ+8fBCjoItT+0gGSuXIoG4qdoVZGBjH5EQ==} + dependencies: + '@types/hapi__hapi': 20.0.12 + 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.12: + resolution: {integrity: sha512-B+0fceCzFvbIOVv5YWOZzbHtEff8BLlGH3etrkcOedyj7F0unC5FjzFfaaO5gwlhJDdX0cmmMeRg2pwRdMa2CQ==} + 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': 18.0.6 + joi: 17.6.0 + 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': 18.0.6 + 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/18.0.6: + resolution: {integrity: sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==} + dev: true + + /@types/uuid/8.3.4: + resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} + dev: true + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: false + + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: false + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: false + + /glob/8.0.3: + resolution: {integrity: sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.0 + once: 1.4.0 + dev: false + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + 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.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.4 + '@sideway/formula': 3.0.0 + '@sideway/pinpoint': 2.0.0 + + /mime-db/1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /minimatch/5.1.0: + resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: false + + /module-alias/2.2.2: + resolution: {integrity: sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==} + dev: false + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: false + + /toml/3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + dev: false + + /uuid/8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + dev: false + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: false diff --git a/src/endpoints/login.ts b/src/endpoints/login.ts new file mode 100644 index 0000000..8474bd1 --- /dev/null +++ b/src/endpoints/login.ts @@ -0,0 +1,17 @@ +import { db } from "@/main"; +import { ServerRoute } from "@hapi/hapi"; + +const data: ServerRoute = { + method: `POST`, path: `/login`, + async handler(request, h) { + const { access } = request.auth.credentials as { username: string, access: string[] }; + + let channels = access.filter(x => x != "*"); + if (access.includes(`*`)) { + channels.push(...Object.keys(db).filter(c => !channels.includes(c))); + }; + + return h.response(channels).code(200); + }, +}; +export default data; \ No newline at end of file diff --git a/src/endpoints/management/create_channel.ts b/src/endpoints/management/create_channel.ts new file mode 100644 index 0000000..7f1e02a --- /dev/null +++ b/src/endpoints/management/create_channel.ts @@ -0,0 +1,27 @@ +import { ServerRoute } from "@hapi/hapi"; +import boom from "@hapi/boom"; +import { db } from "@/main"; +import Joi from "joi"; + +const data: ServerRoute = { + method: `POST`, path: `/manage`, + options: { + validate: { + payload: Joi.object({ + channel: Joi.string().alphanum(), + }), + }, + }, + async handler(request, h) { + const { channel } = request.params; + + if (!db[channel]) { + throw boom.notFound(`Invalid channel`); + }; + + db[channel].lurkers = {}; + + return h.response().code(200); + }, +}; +export default data; \ No newline at end of file diff --git a/src/endpoints/management/create_message.ts b/src/endpoints/management/create_message.ts new file mode 100644 index 0000000..2fd67f7 --- /dev/null +++ b/src/endpoints/management/create_message.ts @@ -0,0 +1,38 @@ +import { ServerRoute } from "@hapi/hapi"; +import boom from "@hapi/boom"; +import { db } from "@/main"; +import { v4 } from "uuid"; +import Joi from "joi"; + +const data: ServerRoute = { + method: [`POST`, `PUT`], path: `/manage/{channel}/message`, + options: { + validate: { + payload: Joi.object({ + lurk: Joi.array().items(Joi.string().min(1)).min(1), + unlurk: Joi.array().items(Joi.string().min(1)).min(1), + }), + params: Joi.object({ + channel: Joi.string().alphanum(), + }), + }, + }, + async handler(request, h) { + const { channel } = request.params; + const data = request.payload as lurk_message; + const id = v4(); + + if (!db[channel]) { + throw boom.notFound(`Invalid channel`); + }; + + db[channel].messages[id] = data; + + return h.response({ + lurk: data.lurk, + unlurk: data.unlurk, + id, + }).code(200); + }, +}; +export default data; \ No newline at end of file diff --git a/src/endpoints/management/delete_message.ts b/src/endpoints/management/delete_message.ts new file mode 100644 index 0000000..1dd4ae1 --- /dev/null +++ b/src/endpoints/management/delete_message.ts @@ -0,0 +1,33 @@ +import { ServerRoute } from "@hapi/hapi"; +import boom from "@hapi/boom"; +import { db } from "@/main"; +import Joi from "joi"; + +const data: ServerRoute = { + method: `DELETE`, path: `/manage/{channel}/message/{id}`, + options: { + validate: { + params: Joi.object({ + channel: Joi.string().alphanum(), + id: Joi.string().uuid(), + }), + }, + }, + async handler(request, h) { + const { channel, id } = request.params; + + if (!db[channel]) { + throw boom.notFound(`Invalid channel`); + }; + + if (!db[channel].messages[id]) { + throw boom.notFound(`Invalid ID`); + }; + + let message = db[channel].messages[id]; + delete db[channel].messages[id]; + + return h.response(message).code(200); + }, +}; +export default data; \ No newline at end of file diff --git a/src/endpoints/management/list_messages.ts b/src/endpoints/management/list_messages.ts new file mode 100644 index 0000000..02c3746 --- /dev/null +++ b/src/endpoints/management/list_messages.ts @@ -0,0 +1,27 @@ +import { ServerRoute } from "@hapi/hapi"; +import boom from "@hapi/boom"; +import { db } from "@/main"; + +const data: ServerRoute = { + method: `GET`, path: `/manage/{channel}`, + async handler(request, h) { + const { channel } = request.params; + + if (!db[channel]) { + throw boom.notFound(`Invalid channel`); + }; + + let messages = []; + for (const messageId in db[channel].messages) { + let message = db[channel].messages[messageId]; + messages.push({ + id: messageId, + lurk: message.lurk, + unlurk: message.unlurk, + }); + }; + + return h.response(messages).code(200); + }, +}; +export default data; \ No newline at end of file diff --git a/src/endpoints/management/reset_lurkers.ts b/src/endpoints/management/reset_lurkers.ts new file mode 100644 index 0000000..71b419a --- /dev/null +++ b/src/endpoints/management/reset_lurkers.ts @@ -0,0 +1,27 @@ +import { ServerRoute } from "@hapi/hapi"; +import boom from "@hapi/boom"; +import { db } from "@/main"; +import Joi from "joi"; + +const data: ServerRoute = { + method: `DELETE`, path: `/manage/{channel}/lurkers`, + options: { + validate: { + params: Joi.object({ + channel: Joi.string().alphanum(), + }), + }, + }, + async handler(request, h) { + const { channel } = request.params; + + if (!db[channel]) { + throw boom.notFound(`Invalid channel`); + }; + + db[channel].lurkers = {}; + + return h.response().code(200); + }, +}; +export default data; \ No newline at end of file diff --git a/src/endpoints/management/update_message.ts b/src/endpoints/management/update_message.ts new file mode 100644 index 0000000..f9f72f2 --- /dev/null +++ b/src/endpoints/management/update_message.ts @@ -0,0 +1,34 @@ +import { ServerRoute } from "@hapi/hapi"; +import boom from "@hapi/boom"; +import { db } from "@/main"; +import { v4 } from "uuid"; +import Joi from "joi"; + +const data: ServerRoute = { + method: `PATCH`, path: `/manage/{channel}/message/{id}`, + options: { + validate: { + payload: Joi.object({ + lurk: Joi.array().items(Joi.string().min(1)).min(1), + unlurk: Joi.array().items(Joi.string().min(1)).min(1), + }), + params: Joi.object({ + channel: Joi.string().alphanum(), + id: Joi.string().uuid(), + }), + }, + }, + async handler(request, h) { + const { channel, id } = request.params; + const data = request.payload as lurk_message; + + if (!db[channel]) { + throw boom.notFound(`Invalid channel`); + }; + + db[channel].messages[id] = data; + + return h.response(`Updated message set with ID: ${id}`).code(200); + }, +}; +export default data; \ No newline at end of file diff --git a/src/endpoints/public/lurk.ts b/src/endpoints/public/lurk.ts new file mode 100644 index 0000000..ed2779e --- /dev/null +++ b/src/endpoints/public/lurk.ts @@ -0,0 +1,46 @@ +import { ServerRoute } from "@hapi/hapi"; +import { formatMessage } from "@/utils"; +import boom from "@hapi/boom"; +import { db } from "@/main"; +import Joi from "joi"; + +const data: ServerRoute = { + method: `GET`, path: `/{channel}/lurk`, + options: { + auth: false, + validate: { + params: Joi.object({ + channel: Joi.string().alphanum(), + }), + query: Joi.object({ + user: Joi.string(), + }), + }, + }, + async handler(request, h) { + const { channel } = request.params; + const { user } = request.query; + + if (!db[channel]) { + throw boom.notFound(`Invalid channel`); + }; + + const messages = db[channel].messages; + const messageIds = Object.keys(messages); + const messageId = messageIds[Math.floor(Math.random() * messageIds.length)]; + const message = messages[messageId]; + let lurkMessage = message.lurk[Math.floor(Math.random() * message.lurk.length)]; + + db[channel].lurkers[user] = messageId; + + let twitchMessage = formatMessage( + lurkMessage, + { + user, + } + ); + + return h.response(twitchMessage).code(200); + }, +}; +export default data; \ No newline at end of file diff --git a/src/endpoints/public/unlurk.ts b/src/endpoints/public/unlurk.ts new file mode 100644 index 0000000..a5ffe85 --- /dev/null +++ b/src/endpoints/public/unlurk.ts @@ -0,0 +1,46 @@ +import { ServerRoute } from "@hapi/hapi"; +import { formatMessage } from "@/utils"; +import boom from "@hapi/boom"; +import { db } from "@/main"; +import Joi from "joi"; + +const data: ServerRoute = { + method: `GET`, path: `/{channel}/unlurk`, + options: { + auth: false, + validate: { + params: Joi.object({ + channel: Joi.string().alphanum(), + }), + query: Joi.object({ + user: Joi.string(), + }), + }, + }, + async handler(request, h) { + const { channel } = request.params; + const { user } = request.query; + + if (!db[channel]) { + throw boom.notFound(`Invalid channel`); + }; + + const messages = db[channel].messages; + const messageId = db[channel].lurkers[user]; + const message = messages[messageId]; + let lurkMessage = message.unlurk[Math.floor(Math.random() * message.unlurk.length)]; + + delete db[channel].lurkers[user]; + + let twitchMessage = formatMessage( + lurkMessage, + { + user, + channel, + } + ); + + return h.response(twitchMessage).code(200); + }, +}; +export default data; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..35e60be --- /dev/null +++ b/src/main.ts @@ -0,0 +1,95 @@ +// Filepath alias resolution +import "module-alias/register"; + +// Begin personal code +import { Server, Request } from "@hapi/hapi"; +import basic from "@hapi/basic"; +import path from "path"; +import glob from "glob"; +import toml from "toml"; +import fs from "fs"; + +const isDev = process.env.NODE_ENV?.startsWith(`dev`); + +// load the config +if (!fs.existsSync(`config.toml`)) { + console.log(`Please fill out the config and then try starting the server again.`); + process.exit(1); +}; +export const config: config = toml.parse(fs.readFileSync(`config.toml`, `utf-8`)); + + +// Load the database +if (!fs.existsSync(`data/db.json`)) { + console.log(`Can't find database file, creating default`); + fs.writeFileSync(`data/db.json`, `{}`); +}; +export var db: database = JSON.parse(fs.readFileSync(`data/db.json`, `utf-8`)); + + +function saveDB() { + console.log(`Saving database`); + fs.writeFileSync(`data/db.json`, JSON.stringify(db, null, `\t`)); + process.exit(0); +}; + +process.on(`SIGINT`, saveDB); +process.on(`SIGTERM`, saveDB); +process.on(`uncaughtException`, saveDB); + +async function init() { + + const server = new Server({ + port: config.server.port, + routes: { + cors: { + origin: [ + isDev ? `*` : `oliver.akins.me/Twitch-Lurk-Message-API/`, + ], + credentials: true, + }, + }, + }); + + await server.register(basic); + server.auth.strategy(`basic`, `basic`, { + async validate(request: Request, username: string, password: string) { + let isValid = false; + let user: account|undefined; + for (const account of config.accounts) { + if (username == account.username) { + user = account; + isValid = ( + password == account.password + && ( + request.params?.channel == null + || account.access.includes(`*`) + || account.access.includes(request.params.channel) + ) + ); + break; + }; + }; + + return { isValid, credentials: { username, access: user?.access } }; + }, + }); + server.auth.default(`basic`); + + // Register all the routes + let files = glob.sync( + `endpoints/**/!(*.map)`, + { cwd: __dirname, nodir: true} + ); + for (var file of files) { + let route = (await import(path.join(__dirname, file))).default; + console.log(`Registering route: ${route.method} ${route.path}`); + server.route(route); + }; + + server.start().then(() => { + console.log(`Server listening on ${config.server.host}:${config.server.port}`); + }); +}; + +init(); \ No newline at end of file diff --git a/src/types/config.d.ts b/src/types/config.d.ts new file mode 100644 index 0000000..6e75f67 --- /dev/null +++ b/src/types/config.d.ts @@ -0,0 +1,14 @@ +interface account { + username: string; + password: string; + access: string[]; +} + + +interface config { + server: { + host: string; + port: number; + }; + accounts: account[]; +} \ No newline at end of file diff --git a/src/types/database.d.ts b/src/types/database.d.ts new file mode 100644 index 0000000..bd5f923 --- /dev/null +++ b/src/types/database.d.ts @@ -0,0 +1,11 @@ +interface lurk_message { + lurk: string[]; + unlurk: string[]; +} + +interface database { + [index: string]: { + lurkers: {[index: string]: string} + messages: {[index: string]: lurk_message} + }; +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..ec984fe --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,4 @@ +export function formatMessage(message: string, meta: any): string { + return message + .replace(/\$\(user\)/, meta.user); +}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a86e4d7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,105 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + "rootDir": "./src", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + "paths": { + "@/*": [ "./src/*" ] + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}