Files
ts-onlinetime-ranks/backend/src/services/sinusbot.service.ts
2021-01-12 14:15:33 +01:00

212 lines
6.0 KiB
TypeScript

import { TableEntry } from '../models/TableEntry';
import { TunakillUser } from '../models/TunakillUser';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { TunakillLogin } from '../models/TunakillLogin';
import fetch from 'node-fetch';
import logger from '../logger/Logger';
@Injectable()
export class SinusBotService {
private host = process.env.HOST;
private credentials = {
username: process.env.SINUSBOT_USER,
password: process.env.SINUSBOT_PASSWORD,
};
private botInfo = {
id: undefined,
instanceId: process.env.SINUSBOT_INSTANCEID,
};
private botIdURL = `${this.host}/api/v1/botId`;
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 bearer: string;
public async fetchStats(): Promise<TableEntry[]> {
// Skip check as either way
// - An interval needs to reset this.bearer to null
// - The Sinusbot token is not a JWT => Expiration date is not decodable
// - Estimated expiration time: 1d?
// - I don't know if it makes a difference to check via interval or to just fetch the token
// everytime a request is sent against this API.
// if (this.bearer == null) {
logger.info(`Hey! I'm trying to get my Bearer token!`);
await this.login()
.then(token => (this.bearer = token))
.then(() => {
logger.debug(
`Seems like I have my Bearer token!
Looks like it's ${
this.bearer == null ? 'undefined' : 'not undefined'
}`,
);
});
// }
logger.info(
`I try to fetch user data now! The URL is called ${this.tunaKillURL}`,
);
return await fetch(this.tunaKillURL, this.requestConfig(null, this.bearer))
.then(res => this.checkStatus(res))
.then(res => res.json())
.then(data => this.consumeTunakillResponse(data))
.catch(error => {
logger.error(`I couldn't fetch user data.`, error);
throw this.createHttpException(
HttpStatus.INTERNAL_SERVER_ERROR,
error.message,
);
});
}
/**
* Returns bearer token if login was successful
*/
private async login(): Promise<string> {
if (this.botInfo.id == null) {
logger.debug(`I have to fetch a bot ID before I can continue!`);
await this.fetchDefaultBotId()
.then(defaultBotId => (this.botInfo.id = defaultBotId))
.catch(error => {
logger.warn(
`I couldn't retrieve SinusBot bot information. Login is likely to fail!`,
error,
);
throw this.createHttpException(
HttpStatus.NOT_FOUND,
`Could not fetch enough bot information for further requests.`,
);
});
logger.info(`The bot ID now is ${this.botInfo.id}`);
}
const body: TunakillLogin = {
username: this.credentials.username,
password: this.credentials.password,
botId: this.botInfo.id,
};
logger.info(`Logging in for Bearer token!`);
return await fetch(this.loginURL, this.requestConfig(JSON.stringify(body)))
.then(res => this.checkStatus(res))
.then(res => res.json())
.then(data => data.token)
.catch(error => {
logger.error(
`Oh oh! Something went wrong while fetching Bearer token.`,
error,
);
throw this.createHttpException(
HttpStatus.UNAUTHORIZED,
`Fetching Bearer token for Sinusbot failed.
Please refresh page or try again later!`,
);
});
}
private async fetchDefaultBotId(): Promise<string> {
return await fetch(this.botIdURL)
.then(res => this.checkStatus(res))
.then(res => res.json())
.then(data => data.defaultBotId);
}
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('Response from SinusBot does not have any data to parse.');
}
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: requestType,
body: body,
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
'User-Agent': 'HumeniusTSRankingBackend/0.0.2',
Authorization: `Bearer ${bearerToken}`,
},
};
};
private createHttpException = (
statusCode: HttpStatus,
message?: string,
): HttpException => {
return new HttpException(
{
status: statusCode,
error: message,
},
statusCode,
);
};
private checkStatus = response => {
if (!response.ok) {
let err = new RequestError(response.errorText);
err.response = response;
throw err;
}
return response;
};
private sortByDescendingTime = (a: number, b: number) => {
if (a < b) {
return 1;
}
if (a > b) {
return -1;
}
return 0;
};
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 `${d}d ${h}h ${m}m ${s}s`;
};
private static logResponse(res: any) {
logger.debug(res);
return res;
}
}