chore: add and apply eslint

This commit is contained in:
Axel Cocat 2022-05-14 18:44:50 +02:00
parent 7cd2748d33
commit bb6428955f
17 changed files with 1455 additions and 132 deletions

View file

@ -98,6 +98,7 @@ export class Armory {
layoutsDir: path.join(process.cwd(), "static"),
defaultLayout: "layout.hbs",
helpers: {
// eslint-disable-next-line @typescript-eslint/no-var-requires
...require("handlebars-helpers")(),
},
}),
@ -131,14 +132,14 @@ export class Armory {
}),
);
app.use("/js", express.static(`static/js`));
app.use("/css", express.static(`static/css`));
app.use("/img", express.static(`static/img`));
app.use("/data/mo3", express.static(`data/mo3`));
app.use("/data/meta", express.static(`data/meta`));
app.use("/data/bone", express.static(`data/bone`));
app.use("/data/textures", express.static(`data/textures`));
app.use("/data/background.png", express.static(`data/modelviewer-background.png`));
app.use("/js", express.static("static/js"));
app.use("/css", express.static("static/css"));
app.use("/img", express.static("static/img"));
app.use("/data/mo3", express.static("data/mo3"));
app.use("/data/meta", express.static("data/meta"));
app.use("/data/bone", express.static("data/bone"));
app.use("/data/textures", express.static("data/textures"));
app.use("/data/background.png", express.static("data/modelviewer-background.png"));
const indexController = new IndexController(this);
app.get("/", this.wrapRoute(indexController.index.bind(indexController)));
@ -208,7 +209,7 @@ export class Armory {
const db = this.getCharactersDb(realm);
if (!(realm in this.charsetCache)) {
const [rows, fields] = await db.query({
const [rows] = await db.query({
sql: `
SELECT CCSA.character_set_name AS charset FROM information_schema.\`TABLES\` T,
information_schema.\`COLLATION_CHARACTER_SET_APPLICABILITY\` CCSA
@ -235,7 +236,7 @@ export class Armory {
}, 500);
}
private wrapRoute(fn: (req: express.Request, res: express.Response, next: express.NextFunction) => Promise<any>) {
private wrapRoute(fn: (req: express.Request, res: express.Response, next: express.NextFunction) => Promise<void>) {
// Adds error handling for promise-based controller methods
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
try {

View file

@ -36,8 +36,8 @@ export class Config {
public worldDatabase: IDatabaseConfig;
public dbQueryTimeout: number;
private static envPrefix: string = "ACORE_ARMORY";
private static checkedMissingField: boolean = false;
private static envPrefix = "ACORE_ARMORY";
private static checkedMissingField = false;
public static async load(logger: winston.Logger): Promise<Config> {
try {
@ -71,13 +71,13 @@ export class Config {
return config as Config;
}
private static loadObjFromEnv(logger: winston.Logger, obj: object, model: object, parentName: string = "") {
private static loadObjFromEnv(logger: winston.Logger, obj: object, model: object, parentName = "") {
if (parentName !== "") {
parentName += ".";
}
for (const field in model) {
if (!model.hasOwnProperty(field)) {
if (!Object.hasOwnProperty.call(model, field)) {
continue;
}
@ -86,9 +86,9 @@ export class Config {
} else if (typeof model[field] === "object") {
obj[field] = {};
Config.loadObjFromEnv(logger, obj[field], model[field], parentName + field);
} else if (!obj.hasOwnProperty(field)) {
} else if (!Object.hasOwnProperty.call(obj, field)) {
const key = Config.getEnvKey(parentName + field);
if (process.env.hasOwnProperty(key)) {
if (Object.hasOwnProperty.call(process.env, key)) {
obj[field] = Config.parseEnvValue(process.env[key], model[field]);
} else if (!Config.checkedMissingField) {
logger.warn(`Config field ${key} is missing from .env!`);
@ -97,14 +97,14 @@ export class Config {
}
}
private static loadArrayFromEnv(logger: winston.Logger, model: any, parentName: string = ""): any[] {
private static loadArrayFromEnv(logger: winston.Logger, model, parentName = ""): unknown[] {
if (parentName !== "") {
parentName += ".";
}
const arr = [];
let i = 0;
while (true) {
for (;;) {
const key = Config.getEnvKey(parentName + i);
const found = Object.keys(process.env).some((k) => k.startsWith(key));
if (!found) {
@ -119,7 +119,7 @@ export class Config {
if (Object.keys(obj).length) {
arr.push(obj);
}
} else if (process.env.hasOwnProperty(key)) {
} else if (Object.hasOwnProperty.call(process.env, key)) {
arr.push(Config.parseEnvValue(process.env[key], model));
} else {
break;
@ -142,7 +142,7 @@ export class Config {
);
}
private static parseEnvValue(value: string, model: any): any {
private static parseEnvValue(value: string, model: boolean | number | string): boolean | number | string {
const type = typeof model;
const lower = value.toLowerCase();
if (type === "boolean") {
@ -154,7 +154,7 @@ export class Config {
return value;
}
private static checkAllMissingFields(logger: winston.Logger, obj: object, model: object, parentName: string = "") {
private static checkAllMissingFields(logger: winston.Logger, obj: object, model: object, parentName = "") {
const missing = Config.hasMissingFields(obj, model);
if (parentName !== "") {
parentName += ".";
@ -163,7 +163,7 @@ export class Config {
logger.warn(`Field ${parentName}${field} is missing from config.json!`);
}
for (const key of Object.keys(model)) {
if (typeof model[key] === "object" && obj.hasOwnProperty(key)) {
if (typeof model[key] === "object" && Object.hasOwnProperty.call(obj, key)) {
Config.checkAllMissingFields(logger, obj[key], model[key], parentName + key);
}
}

View file

@ -1,17 +1,17 @@
import { Pool } from "mysql2/promise";
import { Pool, RowDataPacket } from "mysql2/promise";
import { Query } from "express-serve-static-core";
export interface IResult {
recordsTotal: number;
recordsFiltered: number;
draw: number;
data: any[][];
data: unknown[][];
}
export interface IColumnSettings {
name: string;
collation?: string;
formatter?: (data: string | number | null, row: any) => string;
formatter?: (data: string | number | null, row: unknown) => string;
table?: string;
database?: string;
}
@ -60,11 +60,11 @@ export class DataTablesSsp {
private wheres: string[] = [];
private filterBindings: (string | number)[] = [];
private customBindings: (string | number)[] = [];
private filterWhereSql: string = "1";
private customWhereSql: string = "1";
private limitSql: string = "";
private orderSql: string = "";
private joinSql: string = "";
private filterWhereSql = "1";
private customWhereSql = "1";
private limitSql = "";
private orderSql = "";
private joinSql = "";
public constructor(query: Query, db: Pool, table: string, primaryKey: string, columnSettings: IColumnSettings[]) {
this.start = parseInt(query.start as string, 10);
@ -73,7 +73,9 @@ export class DataTablesSsp {
this._order = (query.order as { column: string; dir: string }[]).map((order) => {
return { column: parseInt(order.column, 10), dir: order.dir };
});
this.columns = (query.columns as { data: string; name: string; searchable: string; orderable: string; search: any }[]).map((column) => {
this.columns = (
query.columns as { data: string; name: string; searchable: string; orderable: string; search: { value: string; regex: string } }[]
).map((column) => {
return {
data: parseInt(column.data, 10),
name: column.name,
@ -83,8 +85,8 @@ export class DataTablesSsp {
};
});
this.search = {
value: (query.search as any).value as string,
regex: (query.search as any).regex === "true",
value: (query.search as unknown)["value"] as string,
regex: (query.search as unknown)["regex"] === "true",
};
this.db = db;
@ -197,32 +199,32 @@ export class DataTablesSsp {
`;
}
public async run(queryTimeout: number = 10_000): Promise<IResult> {
public async run(queryTimeout = 10_000): Promise<IResult> {
this.limit().order().join().filter();
const bindings = [...this.filterBindings, ...this.customBindings];
let [rows, fields] = await this.db.query({
let [rows] = await this.db.query({
sql: this.buildTotalCountSql(),
values: this.customBindings,
timeout: queryTimeout,
});
const recordsTotal = rows[0].count;
[rows, fields] = await this.db.query({
[rows] = await this.db.query({
sql: this.buildFilteredCountSql(),
values: bindings,
timeout: queryTimeout,
});
const recordsFiltered = rows[0].count;
[rows, fields] = await this.db.query({
[rows] = await this.db.query({
sql: this.sql(),
rowsAsArray: true,
values: bindings,
timeout: queryTimeout,
});
rows = (rows as any[][]).map((row) => {
rows = (rows as RowDataPacket[]).map((row) => {
for (let i = 0; i < this.columnSettings.length; ++i) {
const col = this.columnSettings[i];
if (col.formatter !== undefined) {
@ -236,7 +238,7 @@ export class DataTablesSsp {
recordsTotal,
recordsFiltered,
draw: this.draw,
data: rows,
data: rows as unknown[][],
};
}

View file

@ -41,7 +41,16 @@ export class Utils {
return [1, 3, 4, 7, 11].includes(race) ? EFaction.Alliance : EFaction.Horde;
}
public static makeEmblemObject(obj: any, padWithZeroes: boolean = true): IEmblem {
public static makeEmblemObject(
obj: {
background: number;
emblemStyle: number;
emblemColor: number;
borderStyle: number;
borderColor: number;
},
padWithZeroes = true,
): IEmblem {
const padLength = padWithZeroes ? 2 : 0;
return {
icon: obj.emblemStyle.toString().padStart(padLength, "0"),

View file

@ -1,10 +1,10 @@
import * as express from "express";
import { RowDataPacket } from "mysql2/promise";
import { Utils } from "../Utils";
import { Armory } from "../Armory";
import { IRealmConfig } from "../Config";
import { IAchievement } from "../data/DbcReader";
import { IEmblem, Utils } from "../Utils";
import { IAchievement as IAchievementDbc } from "../data/DbcReader";
interface ICharacterData {
guid: number;
@ -27,12 +27,14 @@ interface IEquipmentData {
slot: number;
itemEntry: number;
flags: number;
enchantments: string;
enchantments: string | number[];
randomPropertyId: number;
classId: number;
subclassId: number;
quality: number;
transmog?: number;
icon?: number;
gems?: number[];
}
interface ICustomizationOption {
@ -46,6 +48,30 @@ interface IMount {
icon: string;
}
interface IAchievement {
id: number;
category: number;
title: string;
description: string;
points: number;
icon: string;
}
interface IArenaTeam {
id: number;
name: string;
type: number;
rating: number;
seasonWins: number;
seasonGames: number;
background: number;
emblemStyle: number;
emblemColor: number;
borderStyle: number;
borderColor: number;
emblem?: IEmblem;
}
const ItemClassGem = 3;
const SpellMechanicMounted = 21;
const RaceDisplayName = {
@ -82,7 +108,7 @@ export class CharacterController {
private itemSocketBonuses: { [key: number]: number };
private mountSpells: number[];
private mountBySpellId: { [key: number]: IMount };
private achievementById: { [key: number]: IAchievement };
private achievementById: { [key: number]: IAchievementDbc };
public constructor(armory: Armory) {
this.armory = armory;
@ -121,7 +147,7 @@ export class CharacterController {
}
this.itemSocketBonuses = {};
const [rows, fields] = await this.armory.worldDb.query({
const [rows] = await this.armory.worldDb.query({
sql: "SELECT entry, socketBonus FROM item_template WHERE socketBonus <> 0",
timeout: this.armory.config.dbQueryTimeout,
});
@ -175,9 +201,9 @@ export class CharacterController {
const equipmentData = await this.getEquipmentData(realmName, charData.guid);
const customization = this.getCustomizationOptions(charData);
const equipment = equipmentData.map((row) => {
(row as any).icon = this.itemIcons[row.itemEntry];
(row as any).gems = this.getGemsFromEnchantments(row.enchantments);
(row as any).enchantments = this.filterEnchantments(row.itemEntry, row.enchantments);
row.icon = this.itemIcons[row.itemEntry];
row.gems = this.getGemsFromEnchantments(row.enchantments as string);
row.enchantments = this.filterEnchantments(row.itemEntry, row.enchantments as string);
return row;
});
const mounts = await this.getMounts(realmName, charData.guid);
@ -314,7 +340,7 @@ export class CharacterController {
private async getCharacterData(realm: IRealmConfig, character: string | number): Promise<ICharacterData> {
const where = typeof character === "string" ? "LOWER(`characters`.`name`) = LOWER(?)" : "`characters`.`guid` = ?";
const [rows, fields] = await this.armory.getCharactersDb(realm.name).query({
const [rows] = await this.armory.getCharactersDb(realm.name).query({
sql: `
SELECT \`characters\`.\`guid\`, \`characters\`.\`name\`, \`race\`, \`class\`, \`gender\`, \`level\`, \`skin\`, \`face\`, \`hairStyle\`, \`hairColor\`, \`facialStyle\`, \`playerFlags\`, \`online\`, \`guild\`.\`name\` AS \`guild\`
FROM \`characters\`
@ -337,8 +363,10 @@ export class CharacterController {
private async getEquipmentData(realm: string, charGuid: number): Promise<IEquipmentData[]> {
const transmogSelect = this.armory.config.transmogModule ? ", custom_transmogrification.FakeEntry AS transmog" : "";
const transmogJoin = this.armory.config.transmogModule ? "LEFT JOIN custom_transmogrification ON custom_transmogrification.GUID = item_instance.guid" : "";
let [rows, fields] = await this.armory.getCharactersDb(realm).query({
const transmogJoin = this.armory.config.transmogModule
? "LEFT JOIN custom_transmogrification ON custom_transmogrification.GUID = item_instance.guid"
: "";
let [rows] = await this.armory.getCharactersDb(realm).query({
sql: `
SELECT
character_inventory.slot, item_instance.itemEntry, item_instance.flags, item_instance.enchantments, item_instance.randomPropertyId
@ -360,7 +388,7 @@ export class CharacterController {
row.subclassId = item.subclassId;
}
[rows, fields] = await this.armory.worldDb.query({
[rows] = await this.armory.worldDb.query({
sql: "SELECT entry, quality FROM item_template WHERE entry IN (?)",
values: [data.map((row) => row.itemEntry)],
timeout: this.armory.config.dbQueryTimeout,
@ -374,7 +402,7 @@ export class CharacterController {
}
private async getMounts(realm: string, charGuid: number): Promise<IMount[]> {
const [rows, fields] = await this.armory.getCharactersDb(realm).query({
const [rows] = await this.armory.getCharactersDb(realm).query({
sql: `
SELECT spell
FROM character_spell
@ -455,7 +483,7 @@ export class CharacterController {
const data = this.armory.characterCustomization.getCharacterCustomizationData(charData.race, charData.gender);
const options = [];
const setOptionByChoiceIndex = (optionName: string, choiceIndex: number) => {
const option = data.Options.find((opt) => opt.Name === optionName);
const option = data["Options"].find((opt) => opt.Name === optionName);
if (option !== undefined) {
const choice = option.Choices.find((choice) => choice.OrderIndex === choiceIndex);
if (choice !== undefined) {
@ -464,7 +492,7 @@ export class CharacterController {
}
};
const setOptionByChoiceName = (optionName: string, choiceName: string) => {
const option = data.Options.find((opt) => opt.Name === optionName);
const option = data["Options"].find((opt) => opt.Name === optionName);
if (option !== undefined) {
const choice = option.Choices.find((ch) => ch.Name === choiceName);
if (choice !== undefined) {
@ -473,7 +501,7 @@ export class CharacterController {
}
};
const setOptionByChoiceId = (optionName: string, choiceId: number) => {
const option = data.Options.find((opt) => opt.Name === optionName);
const option = data["Options"].find((opt) => opt.Name === optionName);
if (option !== undefined) {
options.push({ optionId: option.Id, choiceId: choiceId });
}
@ -907,7 +935,7 @@ export class CharacterController {
}
private async getTalents(realm: string, character: number): Promise<number[][]> {
const [rows, fields] = await this.armory.getCharactersDb(realm).query({
const [rows] = await this.armory.getCharactersDb(realm).query({
sql: `
SELECT spell, specMask
FROM character_talent
@ -959,8 +987,8 @@ export class CharacterController {
return texturePath.toLowerCase().replace("interface\\icons\\", "").replace("interface\\spellbook\\", "").replace(/\.$/, "");
}
private async getGlyphs(realm: string, character: number): Promise<any[][]> {
const [rows, fields] = await this.armory.getCharactersDb(realm).query({
private async getGlyphs(realm: string, character: number): Promise<number[][]> {
const [rows] = await this.armory.getCharactersDb(realm).query({
sql: `
SELECT guid, talentGroup, glyph1, glyph2, glyph3, glyph4, glyph5, glyph6
FROM character_glyphs
@ -970,7 +998,7 @@ export class CharacterController {
timeout: this.armory.config.dbQueryTimeout,
});
const glyphs = [[], []];
const glyphs: number[][] = [[], []];
for (const row of rows as RowDataPacket[]) {
const glyphIds = [row.glyph1, row.glyph2, row.glyph3, row.glyph4, row.glyph5, row.glyph6].filter((id) => id !== 0);
for (const glyphId of glyphIds) {
@ -985,7 +1013,10 @@ export class CharacterController {
return glyphs;
}
private async getAchievements(realm: string, charData: ICharacterData): Promise<{ achievements: any[]; earned: { [key: number]: any } }> {
private async getAchievements(
realm: string,
charData: ICharacterData,
): Promise<{ achievements: IAchievement[]; earned: { [key: number]: number } }> {
const promises = await this.armory.dbc
.achievement()
.filter((ach) => ach.faction === -1 || ach.faction === Utils.getFactionFromRaceId(charData.race))
@ -1003,7 +1034,7 @@ export class CharacterController {
.toArray();
const achievements = await Promise.all(promises);
const [rows, fields] = await this.armory.getCharactersDb(realm).query({
const [rows] = await this.armory.getCharactersDb(realm).query({
sql: `
SELECT achievement, date
FROM character_achievement
@ -1012,11 +1043,9 @@ export class CharacterController {
values: [charData.guid],
timeout: this.armory.config.dbQueryTimeout,
});
const earned = {};
const earned: { [key: number]: number } = {};
for (const row of rows as RowDataPacket[]) {
earned[row.achievement] = {
date: row.date,
};
earned[row.achievement] = row.date;
}
return {
@ -1026,7 +1055,7 @@ export class CharacterController {
}
private async getPvpKills(realm: string, charGuid: number): Promise<{ total: number; today: number; yesterday: number }> {
const [rows, fields] = await this.armory.getCharactersDb(realm).query({
const [rows] = await this.armory.getCharactersDb(realm).query({
sql: `
SELECT totalKills, todayKills, yesterdayKills
FROM characters
@ -1044,8 +1073,8 @@ export class CharacterController {
};
}
private async getArenaTeams(realm: string, charGuid: number): Promise<any[]> {
const [rows, fields] = await this.armory.getCharactersDb(realm).query({
private async getArenaTeams(realm: string, charGuid: number): Promise<IArenaTeam[]> {
const [rows] = await this.armory.getCharactersDb(realm).query({
sql: `
SELECT
arena_team.arenaTeamId AS id, arena_team.name, arena_team.type, arena_team.rating, arena_team.seasonWins, arena_team.seasonGames,
@ -1059,7 +1088,7 @@ export class CharacterController {
timeout: this.armory.config.dbQueryTimeout,
});
return (rows as RowDataPacket[]).map((row) => {
return (rows as IArenaTeam[]).map((row) => {
row.emblem = Utils.makeEmblemObject(row, false);
return row;
});

View file

@ -2,16 +2,25 @@ import * as express from "express";
import { encode } from "html-entities";
import { RowDataPacket } from "mysql2/promise";
import { Utils } from "../Utils";
import { Armory } from "../Armory";
import { IRealmConfig } from "../Config";
import { DataTablesSsp } from "../DataTablesSsp";
import { Utils, IEmblem, EFaction } from "../Utils";
interface IGuildRank {
id: number;
name: string;
}
interface IGuildData {
id: number;
name: string;
leader: string;
faction: EFaction;
emblem: IEmblem;
membersCount: number;
}
export class GuildController {
private armory: Armory;
@ -87,17 +96,20 @@ export class GuildController {
const result = await ssp.where("`guildid` = ?", guildId).where("`deleteInfos_Account` IS NULL").run(this.armory.config.dbQueryTimeout);
const ranks = await this.getGuildRanks(realm, guildId);
(result as any).ranks = {};
const ranksObj: { [key: number]: string } = {};
for (const rank of ranks) {
(result as any).ranks[rank.id] = encode(rank.name);
ranksObj[rank.id] = encode(rank.name);
}
res.json(result);
res.json({
...result,
ranks: ranksObj,
});
}
private async getGuildData(realm: IRealmConfig, name: string): Promise<any> {
private async getGuildData(realm: IRealmConfig, name: string): Promise<IGuildData> {
const db = this.armory.getCharactersDb(realm.name);
let [rows, fields] = await db.query({
let [rows] = await db.query({
sql: `
SELECT guildid, name, leaderguid, EmblemStyle AS emblemStyle, EmblemColor AS emblemColor, BorderStyle AS borderStyle, BorderColor AS borderColor, BackgroundColor AS background
FROM guild WHERE name = ?
@ -110,7 +122,7 @@ export class GuildController {
}
const guild = rows[0];
[rows, fields] = await db.query({
[rows] = await db.query({
sql: `
SELECT name, race FROM characters
WHERE guid = ?
@ -120,7 +132,7 @@ export class GuildController {
});
const leader = rows[0];
[rows, fields] = await db.query({
[rows] = await db.query({
sql: `
SELECT COUNT(guid) AS \`count\` FROM guild_member
WHERE guildid = ?
@ -142,7 +154,7 @@ export class GuildController {
private async getGuildId(realm: IRealmConfig, name: string): Promise<number> {
const db = this.armory.getCharactersDb(realm.name);
const [rows, fields] = await db.query({
const [rows] = await db.query({
sql: `
SELECT guildid
FROM guild WHERE name = ?
@ -159,7 +171,7 @@ export class GuildController {
private async guildExists(realm: IRealmConfig, id: number): Promise<boolean> {
const db = this.armory.getCharactersDb(realm.name);
const [rows, fields] = await db.query({
const [rows] = await db.query({
sql: `
SELECT guildid
FROM guild WHERE guildid
@ -173,7 +185,7 @@ export class GuildController {
private async getGuildRanks(realm: IRealmConfig, id: number): Promise<IGuildRank[]> {
const db = this.armory.getCharactersDb(realm.name);
const [rows, fields] = await db.query({
const [rows] = await db.query({
sql: `
SELECT rid AS id, rname AS name
FROM guild_rank WHERE guildid = ?

View file

@ -56,8 +56,10 @@ export class IndexController {
}
const result = await ssp.where("`deleteInfos_Account` IS NULL").run(this.armory.config.dbQueryTimeout);
(result as any).realm = realm.name;
res.json(result);
res.json({
...result,
realm: realm.name,
});
}
}

View file

@ -3,7 +3,7 @@ const fsp = fs.promises;
import * as path from "path";
export class CharacterCustomization {
private data: { [key: number]: { [key: number]: any } };
private data: { [key: number]: { [key: number]: unknown } };
public async loadData(): Promise<void> {
this.data = {};
@ -21,7 +21,7 @@ export class CharacterCustomization {
}
}
public getCharacterCustomizationData(race: number, gender: number): any {
public getCharacterCustomizationData(race: number, gender: number): unknown {
return this.data[race][gender];
}
}

View file

@ -219,10 +219,13 @@ class DbcReader<T> {
return;
}
const headerCols = headerLine.value.map((header) => camelCase(header).replace(/[\[\]]/g, ""));
const headerCols = headerLine.value.map((header) => camelCase(header).replace(/[[\]]/g, ""));
for await (const arr of itr) {
const cols = arr.map((value) => (isNaN(value as any) ? value : parseInt(value, 10)));
const cols = arr.map((value) => {
const parsed = parseInt(value, 10);
return isNaN(parsed) ? value : parsed;
});
const row = {};
headerCols.forEach((header, headerIdx) => {
if (this.fields.length === 0 || this.fields.includes(header)) {
@ -243,8 +246,8 @@ class DbcReader<T> {
const str = chunk.toString();
// Iterate over each character, keep track of current column (of the returned array)
for (let c = 0; c < str.length; ++c) {
let ch = str[c],
nch = str[c + 1]; // Current character, next character
const ch = str[c];
const nch = str[c + 1]; // Current character, next character
if (!(col in arr)) {
arr[col] = ""; // Create a new column (start with empty string) if necessary
}

View file

@ -1,9 +1,9 @@
import "dotenv/config";
import "source-map-support/register";
import { Armory } from "./Armory";
async function main(): Promise<void> {
require("source-map-support").install();
const armory = new Armory();
await armory.start();
}