diff --git a/backend/package.json b/backend/package.json index c12a58d..624b08f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -24,6 +24,7 @@ "@nestjs/common": "^7.0.0", "@nestjs/core": "^7.0.0", "@nestjs/platform-express": "^7.0.0", + "node-fetch": "^2.6.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^6.5.4" diff --git a/backend/src/app.controller.ts b/backend/src/app.controller.ts index da6b2b2..06d5b78 100644 --- a/backend/src/app.controller.ts +++ b/backend/src/app.controller.ts @@ -15,7 +15,7 @@ export class AppController { return this.appService.getHello(); } - @Get() + @Get('/stats') async getStats(): Promise { return await this.sinusBotService.fetchStats(); } diff --git a/backend/src/services/sinusbot.service.ts b/backend/src/services/sinusbot.service.ts index 340e934..d911e5d 100644 --- a/backend/src/services/sinusbot.service.ts +++ b/backend/src/services/sinusbot.service.ts @@ -1,29 +1,45 @@ import { TableEntry } from "../models/TableEntry"; import { TunakillUser } from "../models/TunakillUser"; -import { Injectable } from "@nestjs/common"; +import {HttpException, HttpStatus, Injectable} from "@nestjs/common"; import {TunakillLogin} from "../models/TunakillLogin"; +import fetch from 'node-fetch'; @Injectable() export class SinusBotService { private host = process.env.HOST - private botId = process.env.SINUSBOT_BOTID; + private botInfo = { + id: process.env.SINUSBOT_BOTID, + instanceId: null + } - private tunaKillURL = `${this.host}/api/v1/bot/i/${this.botId}/event/tunakill_rank_all_user`; + private tunaKillURL = `${this.host}/api/v1/bot/i/${this.botInfo.instanceId}/event/tunakill_rank_all_user`; private loginURL = `${this.host}/api/v1/bot/login`; private credentials = { username: process.env.SINUSBOT_USER, password: process.env.SINUSBOT_PASSWORD } - private bearer: string; + private bearer?: string; - public async fetchStats(): Promise { - if (this.bearer === null) { - this.bearer = await this.login().catch(error => error.message); + public async fetchStats(): Promise { + if (this.bearer == null) { + console.log(`Hey! I'm trying to get my Bearer token!`); + await this.login() + .then(res => this.logResponse(res)) + .then(token => this.bearer = token) + .catch(error => { + throw this.createHttpException(HttpStatus.UNAUTHORIZED, error.message); + }); + console.log(`Seems like I have my Bearer token! It's called: ${this.bearer}`); } - return await fetch(this.tunaKillURL) - .then(res => res.json()) - .then(data => this.consumeTunakillResponse(data)); + console.log(`I try to fetch user data now!`) + return await fetch(this.tunaKillURL, this.requestConfig(null, this.bearer)) + .then(res => this.checkStatus(res)) + .then(data => data.json()) + .then(data => this.consumeTunakillResponse(data)) + .catch(error => { + throw this.createHttpException(HttpStatus.UNAUTHORIZED, error.message); + }); } /** @@ -31,33 +47,48 @@ export class SinusBotService { */ private async login(): Promise { const body: TunakillLogin = { - botId: this.botId, - username: this.credentials.password, - password: this.credentials.username, + username: this.credentials.username, + password: this.credentials.password, + botId: this.botInfo.id } + console.log(`> I try to login now! My credentials are: ${JSON.stringify(body)}`) return await fetch(this.loginURL, this.requestConfig(JSON.stringify(body))) - .then(res => { - if (res.ok && res.body !== null) { - return res.json(); - } else { - throw new Error('Response is invalid.'); - } - }) - .then(data => { - if (data.token !== null) { - return data.token; - } else { - throw new Error('Token in response does not exist.'); - } - }); + .then(res => this.checkStatus(res)) + .then(data => data.json()) + .then(data => data.token); } - requestConfig = (body?: string, bearerToken?: string): RequestInit => { + private consumeTunakillResponse(data: any): TableEntry[] { + if (!(data !== null || data[0] !== null || data[0].data !== null)) { + throw Error('Response from SinusBot does not have any data to parse.') + } + + const response = data[0].data; + if (!(response.length > 0)) { + throw Error('User list is empty.') + } + + return response + // TODO: Remove hardcoded username filter for bots. + .filter((user: TunakillUser) => user.name !== "Server Query Admin" && user.name !== "DJ Inshalla") + + .filter((user: TunakillUser) => user.time != null) + .map((user: TunakillUser) => { + return { + name: user.name, + rawTime: user.time, + onlineTime: this.humanizeTime(user.time) + }; + }) + .sort((a: any, b: any) => this.sortByDescendingTime(a.rawTime, b.rawTime)); + } + private requestConfig = (body?: string, bearerToken?: string, requestType: string = 'POST'): RequestInit => { return { - method: 'POST', + method: requestType, body: body, headers: { + 'Accept': '*/*', 'Content-Type': 'application/json', 'User-Agent': 'HumeniusTSRankingBackend/0.0.1', 'Authorization': `Bearer ${bearerToken}` @@ -65,37 +96,43 @@ export class SinusBotService { }; } - private consumeTunakillResponse(data: any): TableEntry[] | undefined { - if (!(data !== null || data[0] !== null || data[0].data !== null)) { - return undefined; + private createHttpException = (statusCode: HttpStatus, message?: string): HttpException => { + return new HttpException({ + status: statusCode, + error: message + }, statusCode); + } + + private checkStatus = response => { + if (!response.ok) { + throw Error(response.statusText); + } + return response; + } + + private sortByDescendingTime = (a: number, b: number) => { + if (a < b) { + return 1; } - const response = data[0].data; - if (!(response.length > 0)) { - return undefined; + if (a > b) { + return -1; } - return response - .map((user: TunakillUser) => { - if (user.name === "Server Query Admin") { return null; } - return { - name: user.name, - rawTime: user.time, - onlineTime: new Date(user.time * 1000) - .toISOString() - .substr(11, 8) - }; - }) - .sort((a: any, b: any) => { - if (a.rawTime < b.rawTime) { - return -1; - } + return 0; + } - if (a.rawTime > b.rawTime) { - return 1; - } + private humanizeTime = (seconds: number) => { + const d = Math.floor(seconds / (3600 * 24)); + const h = Math.floor(seconds % (3600 * 24) / 3600); + const m = Math.floor(seconds % 3600 / 60); + const s = Math.floor(seconds % 60); - return 0; - }); + return `${d}d ${h}h ${m}m ${s}s`; + } + + private logResponse(res: any) { + console.log(res); + return res; } }