Skip to content

Commit

Permalink
feat(hltb): add offset, main+extra and simplify managing (#4173)
Browse files Browse the repository at this point in the history
Fixes #4171
  • Loading branch information
sogehige committed Oct 13, 2020
1 parent 7fe625a commit 19ac3ad
Show file tree
Hide file tree
Showing 14 changed files with 610 additions and 230 deletions.
8 changes: 1 addition & 7 deletions PULL_REQUEST_TEMPLATE.md
@@ -1,9 +1,3 @@
###### CHECKLIST

<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [ ] tests are changed or added
- [ ] documentation is changed or added
- [ ] locales are changed or added
- [ ] db relationship (tools/database) are changed or added
- [ ] commit message follows [commit guidelines](https://github.com/sogehige/sogeBot/blob/master/CONTRIBUTING.md#commit-guidelines)
- [ ] I read [contributing docs](https://github.com/sogehige/sogeBot/blob/master/CONTRIBUTING.md)
6 changes: 3 additions & 3 deletions locales/en/systems/howlongtobeat.json
@@ -1,5 +1,5 @@
{
"error": "$sender, $game not found in db or is multiplayer game.",
"game": "$sender, $game | Main$doneMain: $currentMain/$hltbMainh - $percentMain% | Completionist$doneCompletionist: $currentCompletionist/$hltbCompletionisth - $percentCompletionist%",
"done": "(done)"
"error": "$sender, $game not found in db.",
"game": "$sender, $game | Main: $currentMain/$hltbMainh - $percentMain% | Main+Extra: $currentMainExtra/$hltbMainExtrah - $percentMainExtra% | Completionist: $currentCompletionist/$hltbCompletionisth - $percentCompletionist%",
"multiplayer-game": "$sender, $game | Main: $currentMainh | Main+Extra: $currentMainExtrah | Completionist: $currentCompletionisth"
}
15 changes: 14 additions & 1 deletion locales/en/ui/systems/howlongtobeat.json
@@ -1,5 +1,18 @@
{
"settings": {
"enabled": "Status"
}
},
"empty": "No games were tracked yet.",
"emptyAfterSearch": "No tracked games were found by your search for <strong>\"$search\"</strong>.",
"when": "When streamed",
"time": "Tracked time",
"offset": "Offset of tracked time",
"main": "Main",
"extra": "Main+Extra",
"completionist": "Completionist",
"game": "Tracked game",
"startedAt": "Tracking started at",
"showHistory": "Show history ($count)",
"hideHistory": "Hide history ($count)",
"searchToAddNewGame": "Search to add new game to track"
}
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion scss/themes/light.scss
Expand Up @@ -93,5 +93,5 @@ $status-connecting-border: $yellow !default;
}

.border-gray, .border-input {
border-color: $gray-600 !important
border-color: $gray-400 !important
}
46 changes: 35 additions & 11 deletions src/bot/database/entity/howLongToBeatGame.ts
Expand Up @@ -5,13 +5,22 @@ export interface HowLongToBeatGameInterface {
id?: string;
game: string;
startedAt?: number;
isFinishedMain: boolean;
isFinishedCompletionist: boolean;
timeToBeatMain?: number;
timeToBeatCompletionist?: number;
gameplayMain?: number;
gameplayCompletionist?: number;
imageUrl: string;
gameplayMain: number;
gameplayMainExtra: number;
gameplayCompletionist: number;
offset: number;
}

export interface HowLongToBeatGameItemInterface {
id?: string;
hltb_id: string;
createdAt: number;
timestamp: number;
offset: number;
isMainCounted: boolean;
isCompletionistCounted: boolean;
isExtraCounted: boolean;
}

export const HowLongToBeatGame = new EntitySchema<Readonly<Required<HowLongToBeatGameInterface>>>({
Expand All @@ -20,15 +29,30 @@ export const HowLongToBeatGame = new EntitySchema<Readonly<Required<HowLongToBea
id: { type: 'uuid', primary: true, generated: 'uuid' },
game: { type: String },
imageUrl: { type: String },
isFinishedMain: { type: Boolean },
isFinishedCompletionist: { type: Boolean },
startedAt: { type: 'bigint', transformer: new ColumnNumericTransformer(), default: 0 },
timeToBeatMain: { type: 'bigint', transformer: new ColumnNumericTransformer(), default: 0 },
timeToBeatCompletionist: { type: 'bigint', transformer: new ColumnNumericTransformer(), default: 0 },
startedAt: { type: 'bigint', transformer: new ColumnNumericTransformer() },
offset: { type: 'bigint', transformer: new ColumnNumericTransformer(), default: 0 },
gameplayMain: { type: 'float', transformer: new ColumnNumericTransformer(), default: 0, precision: (process.env.TYPEORM_CONNECTION ?? 'sqlite') === 'mysql' ? 12 : undefined },
gameplayMainExtra: { type: 'float', transformer: new ColumnNumericTransformer(), default: 0, precision: (process.env.TYPEORM_CONNECTION ?? 'sqlite') === 'mysql' ? 12 : undefined },
gameplayCompletionist: { type: 'float', transformer: new ColumnNumericTransformer(), default: 0, precision: (process.env.TYPEORM_CONNECTION ?? 'sqlite') === 'mysql' ? 12 : undefined },
},
indices: [
{ name: 'IDX_301758e0e3108fc902d5436527', columns: ['game'], unique: true },
],
});

export const HowLongToBeatGameItem = new EntitySchema<Readonly<Required<HowLongToBeatGameItemInterface>>>({
name: 'how_long_to_beat_game_item',
columns: {
id: { type: 'uuid', primary: true, generated: 'uuid' },
hltb_id: { type: Number },
createdAt: { type: 'bigint', transformer: new ColumnNumericTransformer() },
timestamp: { type: 'bigint', transformer: new ColumnNumericTransformer(), default: 0 },
offset: { type: 'bigint', transformer: new ColumnNumericTransformer(), default: 0 },
isMainCounted: { type: Boolean, default: false },
isCompletionistCounted: { type: Boolean, default: false },
isExtraCounted: { type: Boolean, default: false },
},
indices: [
{ name: 'IDX_hltb_id', columns: ['hltb_id'] },
],
});
24 changes: 24 additions & 0 deletions src/bot/database/migration/mysql/1602499070262-hltbRefactor.ts
@@ -0,0 +1,24 @@
import {MigrationInterface, QueryRunner} from 'typeorm';

export class hltbRefactor1602499070262 implements MigrationInterface {
name = 'hltbRefactor1602499070262';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE \`how_long_to_beat_game\``, undefined);
await queryRunner.query('CREATE TABLE `how_long_to_beat_game` (`id` varchar(36) NOT NULL, `game` varchar(255) NOT NULL, `imageUrl` varchar(255) NOT NULL, `startedAt` bigint NOT NULL, `gameplayMain` float(12) NOT NULL DEFAULT 0, `gameplayCompletionist` float(12) NOT NULL DEFAULT 0, UNIQUE INDEX `IDX_301758e0e3108fc902d5436527` (`game`), PRIMARY KEY (`id`)) ENGINE=InnoDB');
await queryRunner.query('CREATE TABLE `how_long_to_beat_game_item` (`id` varchar(36) NOT NULL, `hltb_id` int NOT NULL, `createdAt` bigint NOT NULL, `timestamp` bigint NOT NULL DEFAULT 0, `offset` bigint NOT NULL DEFAULT 0, `isMainCounted` tinyint NOT NULL DEFAULT 0, `isCompletionistCounted` tinyint NOT NULL DEFAULT 0, INDEX `IDX_hltb_id` (`hltb_id`), PRIMARY KEY (`id`)) ENGINE=InnoDB');
await queryRunner.query('ALTER TABLE `how_long_to_beat_game` ADD `gameplayMainExtra` float(12) NOT NULL DEFAULT 0');
await queryRunner.query('ALTER TABLE `how_long_to_beat_game_item` ADD `isExtraCounted` tinyint NOT NULL DEFAULT 0');
await queryRunner.query('ALTER TABLE `how_long_to_beat_game` ADD `offset` bigint NOT NULL DEFAULT 0');

}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `IDX_hltb_id` ON `how_long_to_beat_game_item`');
await queryRunner.query('DROP TABLE `how_long_to_beat_game_item`');
await queryRunner.query('DROP INDEX `IDX_301758e0e3108fc902d5436527` ON `how_long_to_beat_game`');
await queryRunner.query('DROP TABLE `how_long_to_beat_game`');
await queryRunner.query('CREATE TABLE `how_long_to_beat_game` (`id` int NOT NULL AUTO_INCREMENT, `game` varchar(255) NOT NULL, `startedAt` bigint NOT NULL DEFAULT 0, `isFinishedMain` tinyint NOT NULL, `isFinishedCompletionist` tinyint NOT NULL, `timeToBeatMain` bigint NOT NULL DEFAULT 0, `timeToBeatCompletionist` bigint NOT NULL DEFAULT 0, `gameplayMain` float NOT NULL DEFAULT 0, `gameplayCompletionist` float NOT NULL DEFAULT 0, `imageUrl` varchar(255) NOT NULL, UNIQUE INDEX `IDX_301758e0e3108fc902d5436527` (`game`), PRIMARY KEY (`id`)) ENGINE=InnoDB', undefined);
}

}
26 changes: 26 additions & 0 deletions src/bot/database/migration/postgres/1602499070262-hltbRefactor.ts
@@ -0,0 +1,26 @@
import {MigrationInterface, QueryRunner} from 'typeorm';

export class hltbRefactor1602499070262 implements MigrationInterface {
name = 'hltbRefactor1602499070262';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "how_long_to_beat_game"`, undefined);
await queryRunner.query(`CREATE TABLE "how_long_to_beat_game_item" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "hltb_id" integer NOT NULL, "createdAt" bigint NOT NULL, "timestamp" bigint NOT NULL DEFAULT 0, "offset" bigint NOT NULL DEFAULT 0, "isMainCounted" boolean NOT NULL DEFAULT false, "isCompletionistCounted" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_920cb816276ba242619a4f40326" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE TABLE "how_long_to_beat_game" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "game" character varying NOT NULL, "startedAt" bigint NOT NULL, "gameplayMain" double precision NOT NULL DEFAULT 0, "gameplayCompletionist" double precision NOT NULL DEFAULT 0, "imageUrl" character varying NOT NULL, CONSTRAINT "PK_c6fbf5fc15e97e46c2659dccea1" PRIMARY KEY ("id"))`, undefined);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_301758e0e3108fc902d5436527" ON "how_long_to_beat_game" ("game") `, undefined);
await queryRunner.query(`CREATE INDEX "IDX_hltb_id" ON "how_long_to_beat_game_item" ("hltb_id") `);
await queryRunner.query(`ALTER TABLE "how_long_to_beat_game" ADD "gameplayMainExtra" double precision NOT NULL DEFAULT 0`);
await queryRunner.query(`ALTER TABLE "how_long_to_beat_game_item" ADD "isExtraCounted" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "how_long_to_beat_game" ADD "offset" bigint NOT NULL DEFAULT 0`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_301758e0e3108fc902d5436527"`);
await queryRunner.query(`DROP INDEX "IDX_hltb_id"`);
await queryRunner.query(`DROP TABLE "how_long_to_beat_game_item"`);
await queryRunner.query(`DROP TABLE "how_long_to_beat_game"`);
await queryRunner.query(`CREATE TABLE "how_long_to_beat_game" ("id" SERIAL NOT NULL, "game" character varying NOT NULL, "startedAt" bigint NOT NULL DEFAULT 0, "isFinishedMain" boolean NOT NULL, "isFinishedCompletionist" boolean NOT NULL, "timeToBeatMain" bigint NOT NULL DEFAULT 0, "timeToBeatCompletionist" bigint NOT NULL DEFAULT 0, "gameplayMain" double precision NOT NULL DEFAULT 0, "gameplayCompletionist" double precision NOT NULL DEFAULT 0, "imageUrl" character varying NOT NULL, CONSTRAINT "PK_c6fbf5fc15e97e46c2659dccea1" PRIMARY KEY ("id"))`, undefined);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_301758e0e3108fc902d5436527" ON "how_long_to_beat_game" ("game") `, undefined);
}

}
23 changes: 23 additions & 0 deletions src/bot/database/migration/sqlite/1602499070262-hltbRefactor.ts
@@ -0,0 +1,23 @@
import {MigrationInterface, QueryRunner} from 'typeorm';

export class hltbRefactor1602499070262 implements MigrationInterface {
name = 'hltbRefactor1602499070262';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "how_long_to_beat_game_item" ("id" varchar PRIMARY KEY NOT NULL, "hltb_id" integer NOT NULL, "createdAt" bigint NOT NULL, "timestamp" bigint NOT NULL DEFAULT (0), "offset" bigint NOT NULL DEFAULT (0), "isMainCounted" boolean NOT NULL DEFAULT (0), "isExtraCounted" boolean NOT NULL DEFAULT (0), "isCompletionistCounted" boolean NOT NULL DEFAULT (0))`);
await queryRunner.query(`CREATE INDEX "IDX_hltb_id" ON "how_long_to_beat_game_item" ("hltb_id") `);
await queryRunner.query(`DROP INDEX "IDX_301758e0e3108fc902d5436527"`);
await queryRunner.query(`DROP TABLE "how_long_to_beat_game"`);
await queryRunner.query(`CREATE TABLE "how_long_to_beat_game" ("id" varchar PRIMARY KEY NOT NULL, "game" varchar NOT NULL, "startedAt" bigint NOT NULL, "gameplayMain" float NOT NULL DEFAULT (0), "gameplayCompletionist" float NOT NULL DEFAULT (0), "gameplayMainExtra" float NOT NULL DEFAULT (0), "imageUrl" varchar NOT NULL, "offset" bigint NOT NULL DEFAULT (0))`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_301758e0e3108fc902d5436527" ON "how_long_to_beat_game" ("game") `);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_301758e0e3108fc902d5436527"`);
await queryRunner.query(`DROP INDEX "IDX_hltb_id"`);
await queryRunner.query(`DROP TABLE "how_long_to_beat_game_item"`);
await queryRunner.query(`DROP TABLE "how_long_to_beat_game"`);
await queryRunner.query(`CREATE TABLE "how_long_to_beat_game" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "game" varchar NOT NULL, "startedAt" bigint NOT NULL DEFAULT (0), "gameplayMain" float NOT NULL DEFAULT (0), "gameplayCompletionist" float NOT NULL DEFAULT (0), "imageUrl" varchar NOT NULL)`);
}

}
30 changes: 30 additions & 0 deletions src/bot/helpers/getTime.ts
Expand Up @@ -29,4 +29,34 @@ export function getTime(time: null | number, isChat: boolean) {
seconds = now.seconds >= 0 && now.seconds < 10 ? '0' + now.seconds : now.seconds;
return days + hours + minutes + seconds;
}
}

export function timestampToObject(timestamp: null | number) {
let days: string | number = 0;
let hours: string | number = 0;
let minutes: string | number = 0;
let seconds: string | number = 0;

if (timestamp) {
days = Math.floor(timestamp / 86400000);
if (days >= 1) {
timestamp -= days * 86400000;
}

hours = Math.floor(timestamp / 3600000);
if (hours >= 1) {
timestamp -= hours * 3600000;
}

minutes = Math.floor(timestamp / 60000);
if (minutes >= 1) {
timestamp -= minutes * 60000;
}

seconds = Math.floor(timestamp / 1000);
}

return {
days: Math.floor(days), hours: Math.floor(hours), minutes: Math.floor(minutes), seconds: Math.floor(seconds),
};
}
9 changes: 6 additions & 3 deletions src/bot/helpers/socket.ts
Expand Up @@ -6,7 +6,7 @@ import type { GoalGroupInterface } from '../database/entity/goal';
import type { AlertInterface, AlertMediaInterface } from '../database/entity/alert';
import type { RandomizerInterface } from '../database/entity/randomizer';
import type { CooldownInterface } from '../database/entity/cooldown';
import type { HowLongToBeatGameInterface } from '../database/entity/howLongToBeatGame';
import type { HowLongToBeatGameInterface, HowLongToBeatGameItemInterface } from '../database/entity/howLongToBeatGame';
import type { KeywordInterface } from '../database/entity/keyword';
import type { RankInterface } from '../database/entity/rank';
import type { TimerInterface } from '../database/entity/timer';
Expand Down Expand Up @@ -91,8 +91,11 @@ function adminEndpoint (nsp: string, on: 'alerts::saveMedia', callback: (item: R
function adminEndpoint (nsp: string, on: 'alerts::cloneMedia', callback: (toClone: [string, string], cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'alerts::save' | 'alerts::delete', callback: (item: Readonly<Required<AlertInterface>>, cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'randomizer::save' | 'randomizer::remove', callback: (item: Readonly<Required<RandomizerInterface>> & Readonly<Required<RandomizerInterface>>[], cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'cooldown::save', callback: (item: Readonly<Required<CooldownInterface>> & Readonly<Required<RandomizerInterface>>[], cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'hltb::save', callback: (item: Readonly<Required<HowLongToBeatGameInterface>> & Readonly<Required<RandomizerInterface>>[], cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'cooldown::save', callback: (item: Readonly<Required<CooldownInterface>> & Readonly<Required<CooldownInterface>>[], cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'hltb::save', callback: (item: Readonly<Required<HowLongToBeatGameInterface>> & Readonly<Required<HowLongToBeatGameInterface>>[], cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'hltb::saveStreamChange', callback: (stream: Readonly<Required<HowLongToBeatGameItemInterface>> & Readonly<Required<HowLongToBeatGameItemInterface>>[], cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'hltb::getGamesFromHLTB', callback: (game: string, cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'hltb::addNewGame', callback: (game: string, cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'keywords::save', callback: (item: Readonly<Required<KeywordInterface>> & Readonly<Required<RandomizerInterface>>[], cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'polls::save' | 'polls::close', callback: (item: Readonly<Required<Poll>> & Readonly<Required<RandomizerInterface>>[], cb: (error: Error | string | null, ...response: any) => void) => void): void;
function adminEndpoint (nsp: string, on: 'ranks::save', callback: (item: Readonly<Required<RankInterface>> & Readonly<Required<RandomizerInterface>>[], cb: (error: Error | string | null, ...response: any) => void) => void): void;
Expand Down

0 comments on commit 19ac3ad

Please sign in to comment.