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. */
+ }
+}