chore: apply prettier on all files
This commit is contained in:
parent
6933e8ee86
commit
5ba5244e4b
19 changed files with 12727 additions and 2254 deletions
15
.prettierignore
Normal file
15
.prettierignore
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
.tmp
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
.npmrc
|
||||||
|
.nvmrc
|
||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
build-tools/
|
||||||
|
reports/
|
||||||
|
coverage/
|
||||||
|
dist/
|
||||||
|
**/*.md
|
||||||
|
**/*.yml
|
||||||
|
package.json
|
||||||
|
package-lock.json
|
||||||
10
.prettierrc
Normal file
10
.prettierrc
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"printWidth": 140,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "always"
|
||||||
|
}
|
||||||
22
package-lock.json
generated
22
package-lock.json
generated
|
|
@ -41,6 +41,7 @@
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"concurrently": "^7.0.0",
|
"concurrently": "^7.0.0",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
|
"prettier": "^2.6.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"typescript": "^4.5.5"
|
"typescript": "^4.5.5"
|
||||||
}
|
}
|
||||||
|
|
@ -3644,6 +3645,21 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin-prettier.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pretty-ms": {
|
"node_modules/pretty-ms": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz",
|
||||||
|
|
@ -7983,6 +7999,12 @@
|
||||||
"integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
|
"integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"prettier": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"pretty-ms": {
|
"pretty-ms": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"concurrently": "^7.0.0",
|
"concurrently": "^7.0.0",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
|
"prettier": "^2.6.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"typescript": "^4.5.5"
|
"typescript": "^4.5.5"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,254 +1,259 @@
|
||||||
import * as path from "path";
|
import * as path from 'path';
|
||||||
import * as uuid from "uuid";
|
import * as uuid from 'uuid';
|
||||||
import { Express } from "express";
|
import { Express } from 'express';
|
||||||
import * as express from "express";
|
import * as express from 'express';
|
||||||
import * as winston from "winston";
|
import * as winston from 'winston';
|
||||||
import * as morgan from "morgan";
|
import * as morgan from 'morgan';
|
||||||
import { Pool, createPool } from "mysql2/promise";
|
import { Pool, createPool } from 'mysql2/promise';
|
||||||
import { engine as handlebarsEngine } from "express-handlebars";
|
import { engine as handlebarsEngine } from 'express-handlebars';
|
||||||
|
|
||||||
import { Config, IRealmConfig } from "./Config";
|
import { Config, IRealmConfig } from './Config';
|
||||||
import { DbcManager } from "./data/DbcReader";
|
import { DbcManager } from './data/DbcReader';
|
||||||
import { CharacterCustomization } from "./data/CharacterCustomization";
|
import { CharacterCustomization } from './data/CharacterCustomization';
|
||||||
import { IndexController } from "./controllers/IndexController";
|
import { IndexController } from './controllers/IndexController';
|
||||||
import { CharacterController } from "./controllers/CharacterController";
|
import { CharacterController } from './controllers/CharacterController';
|
||||||
import { GuildController } from "./controllers/GuildController";
|
import { GuildController } from './controllers/GuildController';
|
||||||
|
|
||||||
export class Armory {
|
export class Armory {
|
||||||
public characterCustomization: CharacterCustomization;
|
public characterCustomization: CharacterCustomization;
|
||||||
public dbc: DbcManager;
|
public dbc: DbcManager;
|
||||||
public config: Config;
|
public config: Config;
|
||||||
public worldDb: Pool;
|
public worldDb: Pool;
|
||||||
public logger: winston.Logger;
|
public logger: winston.Logger;
|
||||||
public charsetCache: { [key: string]: string };
|
public charsetCache: { [key: string]: string };
|
||||||
|
|
||||||
private charsDbs: { [key: string]: Pool };
|
private charsDbs: { [key: string]: Pool };
|
||||||
private errorNames: { [key: number]: string };
|
private errorNames: { [key: number]: string };
|
||||||
private errorDescriptions: { [key: number]: string };
|
private errorDescriptions: { [key: number]: string };
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.dbc = new DbcManager();
|
this.dbc = new DbcManager();
|
||||||
this.characterCustomization = new CharacterCustomization();
|
this.characterCustomization = new CharacterCustomization();
|
||||||
this.charsDbs = {};
|
this.charsDbs = {};
|
||||||
this.logger = winston.createLogger({
|
this.logger = winston.createLogger({
|
||||||
level: "info",
|
level: 'info',
|
||||||
format: winston.format.combine(
|
format: winston.format.combine(
|
||||||
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }),
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
|
||||||
winston.format.printf((info) => `[${info.timestamp}] [${info.level.toUpperCase()}]: ${info.message}`),
|
winston.format.printf((info) => `[${info.timestamp}] [${info.level.toUpperCase()}]: ${info.message}`),
|
||||||
),
|
),
|
||||||
transports: [
|
transports: [
|
||||||
new winston.transports.Console({ level: "debug" }),
|
new winston.transports.Console({ level: 'debug' }),
|
||||||
new winston.transports.File({ filename: path.join("logs", "armory.error.log"), level: "error" }),
|
new winston.transports.File({ filename: path.join('logs', 'armory.error.log'), level: 'error' }),
|
||||||
new winston.transports.File({ filename: path.join("logs", "armory.combined.log"), level: "http" }),
|
new winston.transports.File({ filename: path.join('logs', 'armory.combined.log'), level: 'http' }),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
this.charsetCache = {};
|
this.charsetCache = {};
|
||||||
|
|
||||||
this.errorNames = {
|
this.errorNames = {
|
||||||
400: "Bad Request",
|
400: 'Bad Request',
|
||||||
401: "Unauthorized",
|
401: 'Unauthorized',
|
||||||
403: "Forbidden",
|
403: 'Forbidden',
|
||||||
404: "Not Found",
|
404: 'Not Found',
|
||||||
500: "Internal Server Error",
|
500: 'Internal Server Error',
|
||||||
};
|
};
|
||||||
this.errorDescriptions = {
|
this.errorDescriptions = {
|
||||||
400: "Invalid request.",
|
400: 'Invalid request.',
|
||||||
404: "Sorry, we could not find what you were looking for.",
|
404: 'Sorry, we could not find what you were looking for.',
|
||||||
500: "An unexpected internal error has occurred. Please contact the site owner.",
|
500: 'An unexpected internal error has occurred. Please contact the site owner.',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async start(): Promise<void> {
|
public async start(): Promise<void> {
|
||||||
const app: Express = express();
|
const app: Express = express();
|
||||||
const listenPort = 48733;
|
const listenPort = 48733;
|
||||||
|
|
||||||
this.logger.info("Loading config...");
|
this.logger.info('Loading config...');
|
||||||
this.config = await Config.load(this.logger);
|
this.config = await Config.load(this.logger);
|
||||||
this.logger.info("Loading data files...");
|
this.logger.info('Loading data files...');
|
||||||
if (this.config.loadDbcs) {
|
if (this.config.loadDbcs) {
|
||||||
await this.dbc.loadAllFiles();
|
await this.dbc.loadAllFiles();
|
||||||
}
|
}
|
||||||
await this.characterCustomization.loadData();
|
await this.characterCustomization.loadData();
|
||||||
|
|
||||||
this.logger.info("Connecting to databases...");
|
this.logger.info('Connecting to databases...');
|
||||||
this.worldDb = createPool(this.config.worldDatabase);
|
this.worldDb = createPool(this.config.worldDatabase);
|
||||||
for (const realm of this.config.realms) {
|
for (const realm of this.config.realms) {
|
||||||
this.charsDbs[realm.name.toLowerCase()] = createPool(realm.charactersDatabase);
|
this.charsDbs[realm.name.toLowerCase()] = createPool(realm.charactersDatabase);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info("Starting server...");
|
this.logger.info('Starting server...');
|
||||||
|
|
||||||
const locals = {
|
const locals = {
|
||||||
aowow: this.config.aowowUrl,
|
aowow: this.config.aowowUrl,
|
||||||
websiteUrl: this.config.websiteUrl,
|
websiteUrl: this.config.websiteUrl,
|
||||||
websiteName: this.config.websiteName,
|
websiteName: this.config.websiteName,
|
||||||
websiteRoot: this.config.websiteRoot,
|
websiteRoot: this.config.websiteRoot,
|
||||||
iframeMode: this.config.iframeMode,
|
iframeMode: this.config.iframeMode,
|
||||||
};
|
};
|
||||||
for (const key in locals) {
|
for (const key in locals) {
|
||||||
if (locals.hasOwnProperty(key)) {
|
if (locals.hasOwnProperty(key)) {
|
||||||
app.locals[key] = locals[key];
|
app.locals[key] = locals[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.locals.locals = locals;
|
app.locals.locals = locals;
|
||||||
|
|
||||||
app.engine(".hbs", handlebarsEngine({
|
app.engine(
|
||||||
extname: "hbs",
|
'.hbs',
|
||||||
partialsDir: path.join(process.cwd(), "static", "partials"),
|
handlebarsEngine({
|
||||||
layoutsDir: path.join(process.cwd(), "static"),
|
extname: 'hbs',
|
||||||
defaultLayout: "layout.hbs",
|
partialsDir: path.join(process.cwd(), 'static', 'partials'),
|
||||||
helpers: {
|
layoutsDir: path.join(process.cwd(), 'static'),
|
||||||
...require("handlebars-helpers")(),
|
defaultLayout: 'layout.hbs',
|
||||||
},
|
helpers: {
|
||||||
}));
|
...require('handlebars-helpers')(),
|
||||||
app.set("view engine", "handlebars");
|
},
|
||||||
app.set("views", path.join(process.cwd(), "static"));
|
}),
|
||||||
|
);
|
||||||
|
app.set('view engine', 'handlebars');
|
||||||
|
app.set('views', path.join(process.cwd(), 'static'));
|
||||||
|
|
||||||
app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
req.id = uuid.v4();
|
req.id = uuid.v4();
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
morgan.token("id", (req: express.Request) => {
|
morgan.token('id', (req: express.Request) => {
|
||||||
return req.id;
|
return req.id;
|
||||||
});
|
});
|
||||||
morgan.token("ip", (req: express.Request) => {
|
morgan.token('ip', (req: express.Request) => {
|
||||||
const forwardedFor = req.headers["x-forwarded-for"];
|
const forwardedFor = req.headers['x-forwarded-for'];
|
||||||
if (forwardedFor) {
|
if (forwardedFor) {
|
||||||
if (typeof forwardedFor === "string") {
|
if (typeof forwardedFor === 'string') {
|
||||||
return forwardedFor;
|
return forwardedFor;
|
||||||
}
|
}
|
||||||
return forwardedFor.join(", ");
|
return forwardedFor.join(', ');
|
||||||
}
|
}
|
||||||
return req.socket.remoteAddress;
|
return req.socket.remoteAddress;
|
||||||
});
|
});
|
||||||
app.use(morgan(":method :url :status - ID :id - IP :ip - :response-time ms", {
|
app.use(
|
||||||
stream: {
|
morgan(':method :url :status - ID :id - IP :ip - :response-time ms', {
|
||||||
write: (msg) => this.logger.http(msg.trim()),
|
stream: {
|
||||||
},
|
write: (msg) => this.logger.http(msg.trim()),
|
||||||
}));
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
app.use("/js", express.static(`static/js`));
|
app.use('/js', express.static(`static/js`));
|
||||||
app.use("/css", express.static(`static/css`));
|
app.use('/css', express.static(`static/css`));
|
||||||
app.use("/img", express.static(`static/img`));
|
app.use('/img', express.static(`static/img`));
|
||||||
app.use("/data/mo3", express.static(`data/mo3`));
|
app.use('/data/mo3', express.static(`data/mo3`));
|
||||||
app.use("/data/meta", express.static(`data/meta`));
|
app.use('/data/meta', express.static(`data/meta`));
|
||||||
app.use("/data/bone", express.static(`data/bone`));
|
app.use('/data/bone', express.static(`data/bone`));
|
||||||
app.use("/data/textures", express.static(`data/textures`));
|
app.use('/data/textures', express.static(`data/textures`));
|
||||||
app.use("/data/background.png", express.static(`data/modelviewer-background.png`));
|
app.use('/data/background.png', express.static(`data/modelviewer-background.png`));
|
||||||
|
|
||||||
const indexController = new IndexController(this);
|
const indexController = new IndexController(this);
|
||||||
app.get("/", this.wrapRoute(indexController.index.bind(indexController)));
|
app.get('/', this.wrapRoute(indexController.index.bind(indexController)));
|
||||||
app.get("/search", this.wrapRoute(indexController.search.bind(indexController)));
|
app.get('/search', this.wrapRoute(indexController.search.bind(indexController)));
|
||||||
|
|
||||||
const charsController = new CharacterController(this);
|
const charsController = new CharacterController(this);
|
||||||
await charsController.load();
|
await charsController.load();
|
||||||
app.get("/character/:realm/:name", this.wrapRoute(charsController.character.bind(charsController)));
|
app.get('/character/:realm/:name', this.wrapRoute(charsController.character.bind(charsController)));
|
||||||
app.get("/character/:realm/:name/talents", this.wrapRoute(charsController.talents.bind(charsController)));
|
app.get('/character/:realm/:name/talents', this.wrapRoute(charsController.talents.bind(charsController)));
|
||||||
app.get("/character/:realm/:name/achievements", this.wrapRoute(charsController.achievements.bind(charsController)));
|
app.get('/character/:realm/:name/achievements', this.wrapRoute(charsController.achievements.bind(charsController)));
|
||||||
app.get("/character/:realm/:character/achievements/data", this.wrapRoute(charsController.achievementsData.bind(charsController)));
|
app.get('/character/:realm/:character/achievements/data', this.wrapRoute(charsController.achievementsData.bind(charsController)));
|
||||||
app.get("/character/:realm/:name/pvp", this.wrapRoute(charsController.pvp.bind(charsController)));
|
app.get('/character/:realm/:name/pvp', this.wrapRoute(charsController.pvp.bind(charsController)));
|
||||||
|
|
||||||
const guildsController = new GuildController(this);
|
const guildsController = new GuildController(this);
|
||||||
app.get("/guild/:realm/:name", this.wrapRoute(guildsController.guild.bind(guildsController)));
|
app.get('/guild/:realm/:name', this.wrapRoute(guildsController.guild.bind(guildsController)));
|
||||||
app.get("/guild/:realm/:guild/members", this.wrapRoute(guildsController.members.bind(guildsController)));
|
app.get('/guild/:realm/:guild/members', this.wrapRoute(guildsController.members.bind(guildsController)));
|
||||||
|
|
||||||
app.use((err, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
app.use((err, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
// Error handler
|
// Error handler
|
||||||
|
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
const contents = err.stack ?? `${err.name}: ${err.message}`;
|
const contents = err.stack ?? `${err.name}: ${err.message}`;
|
||||||
this.logger.error(`Error on request ${req.id}. ${contents}`);
|
this.logger.error(`Error on request ${req.id}. ${contents}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = 500;
|
let status = 500;
|
||||||
if (typeof err === "number") {
|
if (typeof err === 'number') {
|
||||||
status = err;
|
status = err;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(status).render("error.hbs", this.getErrorViewData(status, req));
|
res.status(status).render('error.hbs', this.getErrorViewData(status, req));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
// 404 handler
|
// 404 handler
|
||||||
res.status(404);
|
res.status(404);
|
||||||
|
|
||||||
// Respond with html page
|
// Respond with html page
|
||||||
if (req.accepts("html")) {
|
if (req.accepts('html')) {
|
||||||
return res.render("error.hbs", this.getErrorViewData(404, req));
|
return res.render('error.hbs', this.getErrorViewData(404, req));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respond with json
|
// Respond with json
|
||||||
if (req.accepts("json")) {
|
if (req.accepts('json')) {
|
||||||
return res.json({ error: this.errorNames[404] });
|
return res.json({ error: this.errorNames[404] });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to plain-text
|
// Default to plain-text
|
||||||
res.type("txt").send(this.errorNames[404]);
|
res.type('txt').send(this.errorNames[404]);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.gc();
|
this.gc();
|
||||||
app.listen(listenPort, "0.0.0.0", () => {
|
app.listen(listenPort, '0.0.0.0', () => {
|
||||||
this.logger.info(`Server is listening on 0.0.0.0:${listenPort}.`);
|
this.logger.info(`Server is listening on 0.0.0.0:${listenPort}.`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCharactersDb(realm: string): Pool {
|
public getCharactersDb(realm: string): Pool {
|
||||||
return this.charsDbs[realm.toLowerCase()];
|
return this.charsDbs[realm.toLowerCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRealm(realm: string): IRealmConfig {
|
public getRealm(realm: string): IRealmConfig {
|
||||||
return this.config.realms.find(r => r.name.toLowerCase() === realm.toLowerCase());
|
return this.config.realms.find((r) => r.name.toLowerCase() === realm.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getDatabaseCharset(realm: string): Promise<string> {
|
public async getDatabaseCharset(realm: string): Promise<string> {
|
||||||
const db = this.getCharactersDb(realm);
|
const db = this.getCharactersDb(realm);
|
||||||
|
|
||||||
if (!(realm in this.charsetCache)) {
|
if (!(realm in this.charsetCache)) {
|
||||||
const [rows, fields] = await db.query({
|
const [rows, fields] = await db.query({
|
||||||
sql: `
|
sql: `
|
||||||
SELECT CCSA.character_set_name AS charset FROM information_schema.\`TABLES\` T,
|
SELECT CCSA.character_set_name AS charset FROM information_schema.\`TABLES\` T,
|
||||||
information_schema.\`COLLATION_CHARACTER_SET_APPLICABILITY\` CCSA
|
information_schema.\`COLLATION_CHARACTER_SET_APPLICABILITY\` CCSA
|
||||||
WHERE CCSA.collation_name = T.table_collation
|
WHERE CCSA.collation_name = T.table_collation
|
||||||
AND T.table_schema = "${(await db.getConnection()).config.database}"
|
AND T.table_schema = "${(await db.getConnection()).config.database}"
|
||||||
AND T.table_name = "characters"
|
AND T.table_name = "characters"
|
||||||
`,
|
`,
|
||||||
timeout: this.config.dbQueryTimeout,
|
timeout: this.config.dbQueryTimeout,
|
||||||
});
|
});
|
||||||
this.charsetCache[realm] = rows[0].charset;
|
this.charsetCache[realm] = rows[0].charset;
|
||||||
}
|
}
|
||||||
return this.charsetCache[realm];
|
return this.charsetCache[realm];
|
||||||
}
|
}
|
||||||
|
|
||||||
public gc(): void {
|
public gc(): void {
|
||||||
if (this.config.loadDbcs) {
|
if (this.config.loadDbcs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (global.gc) {
|
if (global.gc) {
|
||||||
global.gc();
|
global.gc();
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 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<any>) {
|
||||||
// Adds error handling for promise-based controller methods
|
// Adds error handling for promise-based controller methods
|
||||||
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
try {
|
try {
|
||||||
await fn(req, res, next);
|
await fn(req, res, next);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
next(e);
|
next(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getErrorViewData(status: number, req: express.Request) {
|
private getErrorViewData(status: number, req: express.Request) {
|
||||||
return {
|
return {
|
||||||
status,
|
status,
|
||||||
name: this.errorNames[status] || "An error occurred",
|
name: this.errorNames[status] || 'An error occurred',
|
||||||
description: this.errorDescriptions[status] || "",
|
description: this.errorDescriptions[status] || '',
|
||||||
reqId: req.id,
|
reqId: req.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,176 +1,180 @@
|
||||||
import * as fs from "fs";
|
import * as fs from 'fs';
|
||||||
const fsp = fs.promises;
|
const fsp = fs.promises;
|
||||||
|
|
||||||
import * as winston from "winston";
|
import * as winston from 'winston';
|
||||||
|
|
||||||
export interface IDatabaseConfig {
|
export interface IDatabaseConfig {
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
user: string;
|
user: string;
|
||||||
password: string;
|
password: string;
|
||||||
database: string;
|
database: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRealmConfig {
|
export interface IRealmConfig {
|
||||||
name: string;
|
name: string;
|
||||||
realmId: number;
|
realmId: number;
|
||||||
authDatabase: string;
|
authDatabase: string;
|
||||||
charactersDatabase: IDatabaseConfig;
|
charactersDatabase: IDatabaseConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIframeModeConfig {
|
export interface IIframeModeConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
public aowowUrl: string;
|
public aowowUrl: string;
|
||||||
public websiteUrl: string;
|
public websiteUrl: string;
|
||||||
public websiteName: string;
|
public websiteName: string;
|
||||||
public websiteRoot: string;
|
public websiteRoot: string;
|
||||||
public iframeMode: IIframeModeConfig;
|
public iframeMode: IIframeModeConfig;
|
||||||
public loadDbcs: boolean;
|
public loadDbcs: boolean;
|
||||||
public hideGameMasters: boolean;
|
public hideGameMasters: boolean;
|
||||||
public realms: IRealmConfig[];
|
public realms: IRealmConfig[];
|
||||||
public worldDatabase: IDatabaseConfig;
|
public worldDatabase: IDatabaseConfig;
|
||||||
public dbQueryTimeout: number;
|
public dbQueryTimeout: number;
|
||||||
|
|
||||||
private static envPrefix: string = "ACORE_ARMORY";
|
private static envPrefix: string = 'ACORE_ARMORY';
|
||||||
private static checkedMissingField: boolean = false;
|
private static checkedMissingField: boolean = false;
|
||||||
|
|
||||||
public static async load(logger: winston.Logger): Promise<Config> {
|
public static async load(logger: winston.Logger): Promise<Config> {
|
||||||
try {
|
try {
|
||||||
await fsp.access("config.json");
|
await fsp.access('config.json');
|
||||||
return await Config.loadFromFile(logger);
|
return await Config.loadFromFile(logger);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return await Config.loadFromEnv(logger);
|
return await Config.loadFromEnv(logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async loadFromFile(logger: winston.Logger): Promise<Config> {
|
private static async loadFromFile(logger: winston.Logger): Promise<Config> {
|
||||||
const json: Buffer = await fsp.readFile("config.json");
|
const json: Buffer = await fsp.readFile('config.json');
|
||||||
const config = JSON.parse(json.toString()) as Config;
|
const config = JSON.parse(json.toString()) as Config;
|
||||||
|
|
||||||
if (!Config.checkedMissingField) {
|
if (!Config.checkedMissingField) {
|
||||||
const defaultConfigJson = await fsp.readFile("config.default.json");
|
const defaultConfigJson = await fsp.readFile('config.default.json');
|
||||||
const defaultConfig = JSON.parse(defaultConfigJson.toString());
|
const defaultConfig = JSON.parse(defaultConfigJson.toString());
|
||||||
Config.checkAllMissingFields(logger, config, defaultConfig);
|
Config.checkAllMissingFields(logger, config, defaultConfig);
|
||||||
Config.checkedMissingField = true;
|
Config.checkedMissingField = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async loadFromEnv(logger: winston.Logger): Promise<Config> {
|
private static async loadFromEnv(logger: winston.Logger): Promise<Config> {
|
||||||
const config = {};
|
const config = {};
|
||||||
const json = await fsp.readFile("config.default.json");
|
const json = await fsp.readFile('config.default.json');
|
||||||
const defaultConfig = JSON.parse(json.toString());
|
const defaultConfig = JSON.parse(json.toString());
|
||||||
Config.loadObjFromEnv(logger, config, defaultConfig);
|
Config.loadObjFromEnv(logger, config, defaultConfig);
|
||||||
Config.checkedMissingField = true;
|
Config.checkedMissingField = true;
|
||||||
return config as 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: string = '') {
|
||||||
if (parentName !== "") {
|
if (parentName !== '') {
|
||||||
parentName += ".";
|
parentName += '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const field in model) {
|
for (const field in model) {
|
||||||
if (!model.hasOwnProperty(field)) {
|
if (!model.hasOwnProperty(field)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(model[field])) {
|
if (Array.isArray(model[field])) {
|
||||||
obj[field] = Config.loadArrayFromEnv(logger, model[field][0], parentName + field);
|
obj[field] = Config.loadArrayFromEnv(logger, model[field][0], parentName + field);
|
||||||
} else if (typeof model[field] === "object") {
|
} else if (typeof model[field] === 'object') {
|
||||||
obj[field] = {};
|
obj[field] = {};
|
||||||
Config.loadObjFromEnv(logger, obj[field], model[field], parentName + field);
|
Config.loadObjFromEnv(logger, obj[field], model[field], parentName + field);
|
||||||
} else if (!obj.hasOwnProperty(field)) {
|
} else if (!obj.hasOwnProperty(field)) {
|
||||||
const key = Config.getEnvKey(parentName + field);
|
const key = Config.getEnvKey(parentName + field);
|
||||||
if (process.env.hasOwnProperty(key)) {
|
if (process.env.hasOwnProperty(key)) {
|
||||||
obj[field] = Config.parseEnvValue(process.env[key], model[field]);
|
obj[field] = Config.parseEnvValue(process.env[key], model[field]);
|
||||||
} else if (!Config.checkedMissingField) {
|
} else if (!Config.checkedMissingField) {
|
||||||
logger.warn(`Config field ${key} is missing from .env!`);
|
logger.warn(`Config field ${key} is missing from .env!`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static loadArrayFromEnv(logger: winston.Logger, model: any, parentName: string = ""): any[] {
|
private static loadArrayFromEnv(logger: winston.Logger, model: any, parentName: string = ''): any[] {
|
||||||
if (parentName !== "") {
|
if (parentName !== '') {
|
||||||
parentName += ".";
|
parentName += '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
const arr = [];
|
const arr = [];
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
const key = Config.getEnvKey(parentName + i);
|
const key = Config.getEnvKey(parentName + i);
|
||||||
const found = Object.keys(process.env).some(k => k.startsWith(key));
|
const found = Object.keys(process.env).some((k) => k.startsWith(key));
|
||||||
if (!found) {
|
if (!found) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(model)) {
|
if (Array.isArray(model)) {
|
||||||
arr.push(Config.loadArrayFromEnv(logger, model[0], parentName + i));
|
arr.push(Config.loadArrayFromEnv(logger, model[0], parentName + i));
|
||||||
} else if (typeof model === "object") {
|
} else if (typeof model === 'object') {
|
||||||
const obj = {};
|
const obj = {};
|
||||||
Config.loadObjFromEnv(logger, obj, model, parentName + i);
|
Config.loadObjFromEnv(logger, obj, model, parentName + i);
|
||||||
if (Object.keys(obj).length > 0) {
|
if (Object.keys(obj).length > 0) {
|
||||||
arr.push(obj);
|
arr.push(obj);
|
||||||
}
|
}
|
||||||
} else if (process.env.hasOwnProperty(key)) {
|
} else if (process.env.hasOwnProperty(key)) {
|
||||||
arr.push(Config.parseEnvValue(process.env[key], model));
|
arr.push(Config.parseEnvValue(process.env[key], model));
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getEnvKey(key: string): string {
|
private static getEnvKey(key: string): string {
|
||||||
return Config.envPrefix + "_" + key
|
return (
|
||||||
.replace(/\./g, "__")
|
Config.envPrefix +
|
||||||
.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`)
|
'_' +
|
||||||
.toUpperCase();
|
key
|
||||||
}
|
.replace(/\./g, '__')
|
||||||
|
.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
||||||
|
.toUpperCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private static parseEnvValue(value: string, model: any): any {
|
private static parseEnvValue(value: string, model: any): any {
|
||||||
const type = typeof model;
|
const type = typeof model;
|
||||||
const lower = value.toLowerCase();
|
const lower = value.toLowerCase();
|
||||||
if (type === "boolean") {
|
if (type === 'boolean') {
|
||||||
return lower === "true" || value === "1";
|
return lower === 'true' || value === '1';
|
||||||
}
|
}
|
||||||
if (type === "number") {
|
if (type === 'number') {
|
||||||
return parseFloat(value);
|
return parseFloat(value);
|
||||||
}
|
}
|
||||||
return value;
|
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: string = '') {
|
||||||
const missing = Config.hasMissingFields(obj, model);
|
const missing = Config.hasMissingFields(obj, model);
|
||||||
if (parentName !== "") {
|
if (parentName !== '') {
|
||||||
parentName += ".";
|
parentName += '.';
|
||||||
}
|
}
|
||||||
for (const field of missing) {
|
for (const field of missing) {
|
||||||
logger.warn(`Field ${parentName}${field} is missing from config.json!`);
|
logger.warn(`Field ${parentName}${field} is missing from config.json!`);
|
||||||
}
|
}
|
||||||
for (const key in model) {
|
for (const key in model) {
|
||||||
if (typeof model[key] === "object" && obj.hasOwnProperty(key)) {
|
if (typeof model[key] === 'object' && obj.hasOwnProperty(key)) {
|
||||||
Config.checkAllMissingFields(logger, obj[key], model[key], parentName + key);
|
Config.checkAllMissingFields(logger, obj[key], model[key], parentName + key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static hasMissingFields(obj: object, model: object): string[] {
|
private static hasMissingFields(obj: object, model: object): string[] {
|
||||||
const missing = [];
|
const missing = [];
|
||||||
for (const key in model) {
|
for (const key in model) {
|
||||||
if (!obj.hasOwnProperty(key)) {
|
if (!obj.hasOwnProperty(key)) {
|
||||||
missing.push(key);
|
missing.push(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return missing;
|
return missing;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,173 +1,170 @@
|
||||||
import { Pool } from "mysql2/promise";
|
import { Pool } from 'mysql2/promise';
|
||||||
import { Query } from "express-serve-static-core";
|
import { Query } from 'express-serve-static-core';
|
||||||
|
|
||||||
export interface IResult {
|
export interface IResult {
|
||||||
recordsTotal: number;
|
recordsTotal: number;
|
||||||
recordsFiltered: number;
|
recordsFiltered: number;
|
||||||
draw: number;
|
draw: number;
|
||||||
data: any[][];
|
data: any[][];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IColumnSettings {
|
export interface IColumnSettings {
|
||||||
name: string;
|
name: string;
|
||||||
collation?: string;
|
collation?: string;
|
||||||
formatter?: (data: string | number | null, row: any) => string;
|
formatter?: (data: string | number | null, row: any) => string;
|
||||||
table?: string;
|
table?: string;
|
||||||
database?: string;
|
database?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IColumnJoin {
|
export interface IColumnJoin {
|
||||||
table1: string;
|
table1: string;
|
||||||
column1: string;
|
column1: string;
|
||||||
table2: string;
|
table2: string;
|
||||||
column2: string;
|
column2: string;
|
||||||
database2?: string;
|
database2?: string;
|
||||||
kind: "INNER" | "FULL OUTER" | "LEFT" | "RIGHT";
|
kind: 'INNER' | 'FULL OUTER' | 'LEFT' | 'RIGHT';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DataTablesSsp {
|
export class DataTablesSsp {
|
||||||
public draw: number;
|
public draw: number;
|
||||||
public joins: IColumnJoin[] = [];
|
public joins: IColumnJoin[] = [];
|
||||||
public extraDataColumns: string[] = [];
|
public extraDataColumns: string[] = [];
|
||||||
|
|
||||||
private db: Pool;
|
private db: Pool;
|
||||||
private table: string;
|
private table: string;
|
||||||
private primaryKey: string;
|
private primaryKey: string;
|
||||||
private columnSettings: IColumnSettings[];
|
private columnSettings: IColumnSettings[];
|
||||||
|
|
||||||
private start: number;
|
private start: number;
|
||||||
private length: number;
|
private length: number;
|
||||||
private _order: {
|
private _order: {
|
||||||
column: number,
|
column: number;
|
||||||
dir: string,
|
dir: string;
|
||||||
}[];
|
}[];
|
||||||
private columns: {
|
private columns: {
|
||||||
data: number,
|
data: number;
|
||||||
name: string,
|
name: string;
|
||||||
searchable: boolean,
|
searchable: boolean;
|
||||||
orderable: boolean,
|
orderable: boolean;
|
||||||
search: {
|
search: {
|
||||||
value: string,
|
value: string;
|
||||||
regex: boolean,
|
regex: boolean;
|
||||||
}
|
};
|
||||||
}[];
|
}[];
|
||||||
private search: {
|
private search: {
|
||||||
value: string,
|
value: string;
|
||||||
regex: boolean,
|
regex: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
private wheres: string[] = [];
|
private wheres: string[] = [];
|
||||||
private filterBindings: (string | number)[] = [];
|
private filterBindings: (string | number)[] = [];
|
||||||
private customBindings: (string | number)[] = [];
|
private customBindings: (string | number)[] = [];
|
||||||
private filterWhereSql: string = "1";
|
private filterWhereSql: string = '1';
|
||||||
private customWhereSql: string = "1";
|
private customWhereSql: string = '1';
|
||||||
private limitSql: string = "";
|
private limitSql: string = '';
|
||||||
private orderSql: string = "";
|
private orderSql: string = '';
|
||||||
private joinSql: string = "";
|
private joinSql: string = '';
|
||||||
|
|
||||||
public constructor(query: Query, db: Pool, table: string, primaryKey: string, columnSettings: IColumnSettings[]) {
|
public constructor(query: Query, db: Pool, table: string, primaryKey: string, columnSettings: IColumnSettings[]) {
|
||||||
this.start = parseInt(query.start as string, 10);
|
this.start = parseInt(query.start as string, 10);
|
||||||
this.length = parseInt(query.length as string, 10);
|
this.length = parseInt(query.length as string, 10);
|
||||||
this.draw = parseInt(query.draw as string, 10);
|
this.draw = parseInt(query.draw as string, 10);
|
||||||
this._order = (query.order as { column: string, dir: string }[])
|
this._order = (query.order as { column: string; dir: string }[]).map((order) => {
|
||||||
.map(order => { return { column: parseInt(order.column, 10), dir: order.dir, }; });
|
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: any }[]).map((column) => {
|
||||||
return {
|
return {
|
||||||
data: parseInt(column.data, 10),
|
data: parseInt(column.data, 10),
|
||||||
name: column.name,
|
name: column.name,
|
||||||
searchable: column.searchable === "true",
|
searchable: column.searchable === 'true',
|
||||||
orderable: column.orderable === "true",
|
orderable: column.orderable === 'true',
|
||||||
search: { value: column.search.value, regex: column.search.regex === "true" },
|
search: { value: column.search.value, regex: column.search.regex === 'true' },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
this.search = {
|
this.search = {
|
||||||
value: (query.search as any).value as string,
|
value: (query.search as any).value as string,
|
||||||
regex: (query.search as any).regex === "true",
|
regex: (query.search as any).regex === 'true',
|
||||||
};
|
};
|
||||||
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.table = table;
|
this.table = table;
|
||||||
this.primaryKey = primaryKey;
|
this.primaryKey = primaryKey;
|
||||||
this.columnSettings = columnSettings;
|
this.columnSettings = columnSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private colSettingsToStr(colSettings: IColumnSettings) {
|
private colSettingsToStr(colSettings: IColumnSettings) {
|
||||||
const db = colSettings.database ? ("`" + colSettings.database + "`.") : "";
|
const db = colSettings.database ? '`' + colSettings.database + '`.' : '';
|
||||||
return `${db}\`${colSettings.table || this.table}\`.\`${colSettings.name}\``;
|
return `${db}\`${colSettings.table || this.table}\`.\`${colSettings.name}\``;
|
||||||
}
|
}
|
||||||
|
|
||||||
private limit() {
|
private limit() {
|
||||||
if (this.start !== undefined && this.length !== -1) {
|
if (this.start !== undefined && this.length !== -1) {
|
||||||
this.limitSql = `LIMIT ${this.length} OFFSET ${this.start}`;
|
this.limitSql = `LIMIT ${this.length} OFFSET ${this.start}`;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private order() {
|
private order() {
|
||||||
if (this._order === undefined) {
|
if (this._order === undefined) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderBy = [];
|
const orderBy = [];
|
||||||
for (const order of this._order) {
|
for (const order of this._order) {
|
||||||
const requestColumn = this.columns[order.column];
|
const requestColumn = this.columns[order.column];
|
||||||
if (!requestColumn.orderable) {
|
if (!requestColumn.orderable) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const colSettings = this.columnSettings[requestColumn.data];
|
const colSettings = this.columnSettings[requestColumn.data];
|
||||||
orderBy.push(`${this.colSettingsToStr(colSettings)} ${order.dir}`);
|
orderBy.push(`${this.colSettingsToStr(colSettings)} ${order.dir}`);
|
||||||
}
|
}
|
||||||
orderBy.push(`\`${this.table}\`.\`${this.primaryKey}\``);
|
orderBy.push(`\`${this.table}\`.\`${this.primaryKey}\``);
|
||||||
|
|
||||||
if (orderBy.length > 0) {
|
if (orderBy.length > 0) {
|
||||||
this.orderSql = "ORDER BY " + orderBy.join(", ");
|
this.orderSql = 'ORDER BY ' + orderBy.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private join() {
|
private join() {
|
||||||
for (const join of this.joins) {
|
for (const join of this.joins) {
|
||||||
const db2 = join.database2 ? "`" + join.database2 + "`." : "";
|
const db2 = join.database2 ? '`' + join.database2 + '`.' : '';
|
||||||
this.joinSql += `${join.kind} JOIN ${db2}\`${join.table2}\` ON ${db2}\`${join.table2}\`.\`${join.column2}\` = \`${join.table1}\`.\`${join.column1}\`\n`;
|
this.joinSql += `${join.kind} JOIN ${db2}\`${join.table2}\` ON ${db2}\`${join.table2}\`.\`${join.column2}\` = \`${join.table1}\`.\`${join.column1}\`\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private filter() {
|
private filter() {
|
||||||
if (this.search.value?.length > 0) {
|
if (this.search.value?.length > 0) {
|
||||||
const filterWheres = [];
|
const filterWheres = [];
|
||||||
this.filterBindings = [];
|
this.filterBindings = [];
|
||||||
|
|
||||||
for (const col of this.columns) {
|
for (const col of this.columns) {
|
||||||
if (!col.searchable) {
|
if (!col.searchable) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const colSettings = this.columnSettings[col.data];
|
const colSettings = this.columnSettings[col.data];
|
||||||
const collate = colSettings.collation !== undefined ? `COLLATE ${colSettings.collation} ` : "";
|
const collate = colSettings.collation !== undefined ? `COLLATE ${colSettings.collation} ` : '';
|
||||||
filterWheres.push(`${this.colSettingsToStr(colSettings)} ${collate}LIKE ?`);
|
filterWheres.push(`${this.colSettingsToStr(colSettings)} ${collate}LIKE ?`);
|
||||||
this.filterBindings.push(`%${this.search.value}%`);
|
this.filterBindings.push(`%${this.search.value}%`);
|
||||||
}
|
}
|
||||||
if (filterWheres.length > 0) {
|
if (filterWheres.length > 0) {
|
||||||
this.filterWhereSql = "(" + filterWheres.map(w => `(${w})`).join(" OR ") + ")";
|
this.filterWhereSql = '(' + filterWheres.map((w) => `(${w})`).join(' OR ') + ')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.customWhereSql = this.wheres.map(w => `(${w})`).join(" AND ");
|
this.customWhereSql = this.wheres.map((w) => `(${w})`).join(' AND ');
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sql(): string {
|
public sql(): string {
|
||||||
const columns = [
|
const columns = [...this.columnSettings.map((c) => this.colSettingsToStr(c)), ...this.extraDataColumns];
|
||||||
...this.columnSettings.map(c => this.colSettingsToStr(c)),
|
return `
|
||||||
...this.extraDataColumns,
|
SELECT ${columns.join(', ')}
|
||||||
];
|
|
||||||
return `
|
|
||||||
SELECT ${columns.join(", ")}
|
|
||||||
FROM ${this.table}
|
FROM ${this.table}
|
||||||
${this.joinSql}
|
${this.joinSql}
|
||||||
WHERE
|
WHERE
|
||||||
|
|
@ -176,19 +173,19 @@ export class DataTablesSsp {
|
||||||
${this.orderSql}
|
${this.orderSql}
|
||||||
${this.limitSql}
|
${this.limitSql}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildTotalCountSql(): string {
|
private buildTotalCountSql(): string {
|
||||||
return `
|
return `
|
||||||
SELECT COUNT(\`${this.table}\`.\`${this.primaryKey}\`) AS \`count\`
|
SELECT COUNT(\`${this.table}\`.\`${this.primaryKey}\`) AS \`count\`
|
||||||
FROM ${this.table}
|
FROM ${this.table}
|
||||||
${this.joinSql}
|
${this.joinSql}
|
||||||
WHERE ${this.customWhereSql}
|
WHERE ${this.customWhereSql}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildFilteredCountSql(): string {
|
private buildFilteredCountSql(): string {
|
||||||
return `
|
return `
|
||||||
SELECT COUNT(\`${this.table}\`.\`${this.primaryKey}\`) AS \`count\`
|
SELECT COUNT(\`${this.table}\`.\`${this.primaryKey}\`) AS \`count\`
|
||||||
FROM ${this.table}
|
FROM ${this.table}
|
||||||
${this.joinSql}
|
${this.joinSql}
|
||||||
|
|
@ -196,59 +193,56 @@ export class DataTablesSsp {
|
||||||
${this.filterWhereSql} AND
|
${this.filterWhereSql} AND
|
||||||
${this.customWhereSql}
|
${this.customWhereSql}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async run(queryTimeout: number = 10_000): Promise<IResult> {
|
public async run(queryTimeout: number = 10_000): Promise<IResult> {
|
||||||
this.limit()
|
this.limit().order().join().filter();
|
||||||
.order()
|
|
||||||
.join()
|
|
||||||
.filter();
|
|
||||||
|
|
||||||
const bindings = [...this.filterBindings, ...this.customBindings];
|
const bindings = [...this.filterBindings, ...this.customBindings];
|
||||||
|
|
||||||
let [rows, fields] = await this.db.query({
|
let [rows, fields] = await this.db.query({
|
||||||
sql: this.buildTotalCountSql(),
|
sql: this.buildTotalCountSql(),
|
||||||
values: this.customBindings,
|
values: this.customBindings,
|
||||||
timeout: queryTimeout,
|
timeout: queryTimeout,
|
||||||
});
|
});
|
||||||
const recordsTotal = rows[0].count;
|
const recordsTotal = rows[0].count;
|
||||||
|
|
||||||
[rows, fields] = await this.db.query({
|
[rows, fields] = await this.db.query({
|
||||||
sql: this.buildFilteredCountSql(),
|
sql: this.buildFilteredCountSql(),
|
||||||
values: bindings,
|
values: bindings,
|
||||||
timeout: queryTimeout,
|
timeout: queryTimeout,
|
||||||
});
|
});
|
||||||
const recordsFiltered = rows[0].count;
|
const recordsFiltered = rows[0].count;
|
||||||
|
|
||||||
[rows, fields] = await this.db.query({
|
[rows, fields] = await this.db.query({
|
||||||
sql: this.sql(),
|
sql: this.sql(),
|
||||||
rowsAsArray: true,
|
rowsAsArray: true,
|
||||||
values: bindings,
|
values: bindings,
|
||||||
timeout: queryTimeout,
|
timeout: queryTimeout,
|
||||||
});
|
});
|
||||||
rows = (rows as any[][]).map(row => {
|
rows = (rows as any[][]).map((row) => {
|
||||||
for (let i = 0; i < this.columnSettings.length; ++i) {
|
for (let i = 0; i < this.columnSettings.length; ++i) {
|
||||||
const col = this.columnSettings[i];
|
const col = this.columnSettings[i];
|
||||||
if (col.formatter !== undefined) {
|
if (col.formatter !== undefined) {
|
||||||
row[i] = col.formatter(row[i], row);
|
row[i] = col.formatter(row[i], row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
recordsTotal,
|
recordsTotal,
|
||||||
recordsFiltered,
|
recordsFiltered,
|
||||||
draw: this.draw,
|
draw: this.draw,
|
||||||
data: rows,
|
data: rows,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public where(condition: string, binding?: string | number) {
|
public where(condition: string, binding?: string | number) {
|
||||||
this.wheres.push(condition);
|
this.wheres.push(condition);
|
||||||
if (binding !== undefined) {
|
if (binding !== undefined) {
|
||||||
this.customBindings.push(binding);
|
this.customBindings.push(binding);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,54 @@
|
||||||
export enum EFaction {
|
export enum EFaction {
|
||||||
Horde = 0,
|
Horde = 0,
|
||||||
Alliance = 1,
|
Alliance = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IEmblem {
|
export interface IEmblem {
|
||||||
icon: string;
|
icon: string;
|
||||||
iconColor: string;
|
iconColor: string;
|
||||||
border: string;
|
border: string;
|
||||||
borderColor: string;
|
borderColor: string;
|
||||||
background: string;
|
background: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Utils {
|
export class Utils {
|
||||||
public static raceNames = {
|
public static raceNames = {
|
||||||
1: "human",
|
1: 'human',
|
||||||
2: "orc",
|
2: 'orc',
|
||||||
3: "dwarf",
|
3: 'dwarf',
|
||||||
4: "nightelf",
|
4: 'nightelf',
|
||||||
5: "scourge",
|
5: 'scourge',
|
||||||
6: "tauren",
|
6: 'tauren',
|
||||||
7: "gnome",
|
7: 'gnome',
|
||||||
8: "troll",
|
8: 'troll',
|
||||||
10: "bloodelf",
|
10: 'bloodelf',
|
||||||
11: "draenei",
|
11: 'draenei',
|
||||||
};
|
};
|
||||||
public static classNames = {
|
public static classNames = {
|
||||||
1: "warrior",
|
1: 'warrior',
|
||||||
2: "paladin",
|
2: 'paladin',
|
||||||
3: "hunter",
|
3: 'hunter',
|
||||||
4: "rogue",
|
4: 'rogue',
|
||||||
5: "priest",
|
5: 'priest',
|
||||||
6: "deathknight",
|
6: 'deathknight',
|
||||||
7: "shaman",
|
7: 'shaman',
|
||||||
8: "mage",
|
8: 'mage',
|
||||||
9: "warlock",
|
9: 'warlock',
|
||||||
11: "druid",
|
11: 'druid',
|
||||||
};
|
};
|
||||||
|
|
||||||
public static getFactionFromRaceId(race: number): EFaction {
|
public static getFactionFromRaceId(race: number): EFaction {
|
||||||
return [1, 3, 4, 7, 11].includes(race) ? EFaction.Alliance : EFaction.Horde;
|
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: any, padWithZeroes: boolean = true): IEmblem {
|
||||||
const padLength = padWithZeroes ? 2 : 0;
|
const padLength = padWithZeroes ? 2 : 0;
|
||||||
return {
|
return {
|
||||||
icon: obj.emblemStyle.toString().padStart(padLength, "0"),
|
icon: obj.emblemStyle.toString().padStart(padLength, '0'),
|
||||||
iconColor: obj.emblemColor.toString().padStart(padLength, "0"),
|
iconColor: obj.emblemColor.toString().padStart(padLength, '0'),
|
||||||
border: obj.borderStyle.toString().padStart(padLength, "0"),
|
border: obj.borderStyle.toString().padStart(padLength, '0'),
|
||||||
borderColor: obj.borderColor.toString().padStart(padLength, "0"),
|
borderColor: obj.borderColor.toString().padStart(padLength, '0'),
|
||||||
background: obj.background.toString().padStart(padLength, "0"),
|
background: obj.background.toString().padStart(padLength, '0'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,184 +1,188 @@
|
||||||
import * as express from "express";
|
import * as express from 'express';
|
||||||
import { encode } from "html-entities";
|
import { encode } from 'html-entities';
|
||||||
import { RowDataPacket } from "mysql2/promise";
|
import { RowDataPacket } from 'mysql2/promise';
|
||||||
|
|
||||||
import { Utils } from "../Utils";
|
import { Utils } from '../Utils';
|
||||||
import { Armory } from "../Armory";
|
import { Armory } from '../Armory';
|
||||||
import { IRealmConfig } from "../Config";
|
import { IRealmConfig } from '../Config';
|
||||||
import { DataTablesSsp } from "../DataTablesSsp";
|
import { DataTablesSsp } from '../DataTablesSsp';
|
||||||
|
|
||||||
interface IGuildRank {
|
interface IGuildRank {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GuildController {
|
export class GuildController {
|
||||||
private armory: Armory;
|
private armory: Armory;
|
||||||
|
|
||||||
public constructor(armory: Armory) {
|
public constructor(armory: Armory) {
|
||||||
this.armory = armory;
|
this.armory = armory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async guild(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
public async guild(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
||||||
const realmName = req.params.realm;
|
const realmName = req.params.realm;
|
||||||
const guildName = req.params.name;
|
const guildName = req.params.name;
|
||||||
|
|
||||||
const realm = this.armory.getRealm(realmName);
|
const realm = this.armory.getRealm(realmName);
|
||||||
if (realm === undefined) {
|
if (realm === undefined) {
|
||||||
// Could not find realm
|
// Could not find realm
|
||||||
return next(404);
|
return next(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const guildData = await this.getGuildData(realm, guildName);
|
const guildData = await this.getGuildData(realm, guildName);
|
||||||
if (guildData === null) {
|
if (guildData === null) {
|
||||||
// Could not find guild
|
// Could not find guild
|
||||||
return next(404);
|
return next(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render("guild.hbs", {
|
res.render('guild.hbs', {
|
||||||
title: `Armory - ${guildName}`,
|
title: `Armory - ${guildName}`,
|
||||||
realm: realm.name,
|
realm: realm.name,
|
||||||
...guildData,
|
...guildData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async members(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
public async members(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
||||||
const realmName = req.params.realm;
|
const realmName = req.params.realm;
|
||||||
const guildId = parseInt(req.params.guild);
|
const guildId = parseInt(req.params.guild);
|
||||||
|
|
||||||
const realm = this.armory.getRealm(realmName);
|
const realm = this.armory.getRealm(realmName);
|
||||||
if (realm === undefined) {
|
if (realm === undefined) {
|
||||||
// Could not find realm
|
// Could not find realm
|
||||||
return next(404);
|
return next(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNaN(guildId) || !(await this.guildExists(realm, guildId))) {
|
if (isNaN(guildId) || !(await this.guildExists(realm, guildId))) {
|
||||||
// Could not find guild
|
// Could not find guild
|
||||||
return next(404);
|
return next(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = this.armory.getCharactersDb(realm.name);
|
const db = this.armory.getCharactersDb(realm.name);
|
||||||
const charSet = await this.armory.getDatabaseCharset(realm.name);
|
const charSet = await this.armory.getDatabaseCharset(realm.name);
|
||||||
|
|
||||||
let ssp = new DataTablesSsp(req.query, db, "guild_member", "guid", [
|
let ssp = new DataTablesSsp(req.query, db, 'guild_member', 'guid', [
|
||||||
{ name: "name", table: "characters", collation: `${charSet}_general_ci` },
|
{ name: 'name', table: 'characters', collation: `${charSet}_general_ci` },
|
||||||
{ name: "rank" },
|
{ name: 'rank' },
|
||||||
{ name: "level", table: "characters" },
|
{ name: 'level', table: 'characters' },
|
||||||
{ name: "class", table: "characters", formatter: cls => Utils.classNames[cls] },
|
{ name: 'class', table: 'characters', formatter: (cls) => Utils.classNames[cls] },
|
||||||
{ name: "race", table: "characters", formatter: (race, row) => `${Utils.raceNames[race]}_${row[6] === 0 ? "male" : "female"}` },
|
{ name: 'race', table: 'characters', formatter: (race, row) => `${Utils.raceNames[race]}_${row[6] === 0 ? 'male' : 'female'}` },
|
||||||
{ name: "online", table: "characters", formatter: online => online === 1 },
|
{ name: 'online', table: 'characters', formatter: (online) => online === 1 },
|
||||||
]);
|
]);
|
||||||
ssp.joins = [
|
ssp.joins = [{ table1: 'guild_member', column1: 'guid', table2: 'characters', column2: 'guid', kind: 'LEFT' }];
|
||||||
{ table1: "guild_member", column1: "guid", table2: "characters", column2: "guid", kind: "LEFT" },
|
ssp.extraDataColumns = ['`characters`.`gender`'];
|
||||||
];
|
|
||||||
ssp.extraDataColumns = ["`characters`.`gender`"];
|
|
||||||
|
|
||||||
if (this.armory.config.hideGameMasters) {
|
if (this.armory.config.hideGameMasters) {
|
||||||
ssp.joins.push({ table1: "characters", column1: "account", table2: "account_access", column2: "id", database2: realm.authDatabase, kind: "LEFT" });
|
ssp.joins.push({
|
||||||
ssp = ssp.where(`\`account_access\`.\`id\` IS NULL OR \`account_access\`.\`RealmID\` NOT IN (-1, ${realm.realmId}) OR \`account_access\`.\`gmlevel\` = 0`);
|
table1: 'characters',
|
||||||
}
|
column1: 'account',
|
||||||
|
table2: 'account_access',
|
||||||
|
column2: 'id',
|
||||||
|
database2: realm.authDatabase,
|
||||||
|
kind: 'LEFT',
|
||||||
|
});
|
||||||
|
ssp = ssp.where(
|
||||||
|
`\`account_access\`.\`id\` IS NULL OR \`account_access\`.\`RealmID\` NOT IN (-1, ${realm.realmId}) OR \`account_access\`.\`gmlevel\` = 0`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const result = await ssp
|
const result = await ssp.where('`guildid` = ?', guildId).where('`deleteInfos_Account` IS NULL').run(this.armory.config.dbQueryTimeout);
|
||||||
.where("`guildid` = ?", guildId)
|
|
||||||
.where("`deleteInfos_Account` IS NULL")
|
|
||||||
.run(this.armory.config.dbQueryTimeout);
|
|
||||||
|
|
||||||
const ranks = await this.getGuildRanks(realm, guildId);
|
const ranks = await this.getGuildRanks(realm, guildId);
|
||||||
(result as any).ranks = {};
|
(result as any).ranks = {};
|
||||||
for (const rank of ranks) {
|
for (const rank of ranks) {
|
||||||
(result as any).ranks[rank.id] = encode(rank.name);
|
(result as any).ranks[rank.id] = encode(rank.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getGuildData(realm: IRealmConfig, name: string): Promise<any> {
|
private async getGuildData(realm: IRealmConfig, name: string): Promise<any> {
|
||||||
const db = this.armory.getCharactersDb(realm.name);
|
const db = this.armory.getCharactersDb(realm.name);
|
||||||
let [rows, fields] = await db.query({
|
let [rows, fields] = await db.query({
|
||||||
sql: `
|
sql: `
|
||||||
SELECT guildid, name, leaderguid, EmblemStyle AS emblemStyle, EmblemColor AS emblemColor, BorderStyle AS borderStyle, BorderColor AS borderColor, BackgroundColor AS background
|
SELECT guildid, name, leaderguid, EmblemStyle AS emblemStyle, EmblemColor AS emblemColor, BorderStyle AS borderStyle, BorderColor AS borderColor, BackgroundColor AS background
|
||||||
FROM guild WHERE name = ?
|
FROM guild WHERE name = ?
|
||||||
`,
|
`,
|
||||||
values: [name],
|
values: [name],
|
||||||
timeout: this.armory.config.dbQueryTimeout,
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
});
|
});
|
||||||
if ((rows as RowDataPacket[]).length === 0) {
|
if ((rows as RowDataPacket[]).length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const guild = rows[0];
|
const guild = rows[0];
|
||||||
|
|
||||||
[rows, fields] = await db.query({
|
[rows, fields] = await db.query({
|
||||||
sql: `
|
sql: `
|
||||||
SELECT name, race FROM characters
|
SELECT name, race FROM characters
|
||||||
WHERE guid = ?
|
WHERE guid = ?
|
||||||
`,
|
`,
|
||||||
values: [guild.leaderguid],
|
values: [guild.leaderguid],
|
||||||
timeout: this.armory.config.dbQueryTimeout,
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
});
|
});
|
||||||
const leader = rows[0];
|
const leader = rows[0];
|
||||||
|
|
||||||
[rows, fields] = await db.query({
|
[rows, fields] = await db.query({
|
||||||
sql: `
|
sql: `
|
||||||
SELECT COUNT(guid) AS \`count\` FROM guild_member
|
SELECT COUNT(guid) AS \`count\` FROM guild_member
|
||||||
WHERE guildid = ?
|
WHERE guildid = ?
|
||||||
`,
|
`,
|
||||||
values: [guild.guildid],
|
values: [guild.guildid],
|
||||||
timeout: this.armory.config.dbQueryTimeout,
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
});
|
});
|
||||||
const membersCount = rows[0].count;
|
const membersCount = rows[0].count;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: guild.guildid,
|
id: guild.guildid,
|
||||||
name: guild.name,
|
name: guild.name,
|
||||||
leader: leader.name,
|
leader: leader.name,
|
||||||
faction: Utils.getFactionFromRaceId(leader.race),
|
faction: Utils.getFactionFromRaceId(leader.race),
|
||||||
emblem: Utils.makeEmblemObject(guild),
|
emblem: Utils.makeEmblemObject(guild),
|
||||||
membersCount,
|
membersCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getGuildId(realm: IRealmConfig, name: string): Promise<number> {
|
private async getGuildId(realm: IRealmConfig, name: string): Promise<number> {
|
||||||
const db = this.armory.getCharactersDb(realm.name);
|
const db = this.armory.getCharactersDb(realm.name);
|
||||||
const [rows, fields] = await db.query({
|
const [rows, fields] = await db.query({
|
||||||
sql: `
|
sql: `
|
||||||
SELECT guildid
|
SELECT guildid
|
||||||
FROM guild WHERE name = ?
|
FROM guild WHERE name = ?
|
||||||
`,
|
`,
|
||||||
values: [name],
|
values: [name],
|
||||||
timeout: this.armory.config.dbQueryTimeout,
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
});
|
});
|
||||||
if ((rows as RowDataPacket[]).length === 0) {
|
if ((rows as RowDataPacket[]).length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rows[0].guildid;
|
return rows[0].guildid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async guildExists(realm: IRealmConfig, id: number): Promise<boolean> {
|
private async guildExists(realm: IRealmConfig, id: number): Promise<boolean> {
|
||||||
const db = this.armory.getCharactersDb(realm.name);
|
const db = this.armory.getCharactersDb(realm.name);
|
||||||
const [rows, fields] = await db.query({
|
const [rows, fields] = await db.query({
|
||||||
sql: `
|
sql: `
|
||||||
SELECT guildid
|
SELECT guildid
|
||||||
FROM guild WHERE guildid
|
FROM guild WHERE guildid
|
||||||
`,
|
`,
|
||||||
values: [id],
|
values: [id],
|
||||||
timeout: this.armory.config.dbQueryTimeout,
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (rows as RowDataPacket[]).length !== 0;
|
return (rows as RowDataPacket[]).length !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getGuildRanks(realm: IRealmConfig, id: number): Promise<IGuildRank[]> {
|
private async getGuildRanks(realm: IRealmConfig, id: number): Promise<IGuildRank[]> {
|
||||||
const db = this.armory.getCharactersDb(realm.name);
|
const db = this.armory.getCharactersDb(realm.name);
|
||||||
const [rows, fields] = await db.query({
|
const [rows, fields] = await db.query({
|
||||||
sql: `
|
sql: `
|
||||||
SELECT rid AS id, rname AS name
|
SELECT rid AS id, rname AS name
|
||||||
FROM guild_rank WHERE guildid = ?
|
FROM guild_rank WHERE guildid = ?
|
||||||
`,
|
`,
|
||||||
values: [id],
|
values: [id],
|
||||||
timeout: this.armory.config.dbQueryTimeout,
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
});
|
});
|
||||||
|
|
||||||
return rows as IGuildRank[];
|
return rows as IGuildRank[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,64 @@
|
||||||
import * as express from "express";
|
import * as express from 'express';
|
||||||
|
|
||||||
import { Utils } from "../Utils";
|
import { Utils } from '../Utils';
|
||||||
import { Armory } from "../Armory";
|
import { Armory } from '../Armory';
|
||||||
import { DataTablesSsp } from "../DataTablesSsp";
|
import { DataTablesSsp } from '../DataTablesSsp';
|
||||||
|
|
||||||
export class IndexController {
|
export class IndexController {
|
||||||
private armory: Armory;
|
private armory: Armory;
|
||||||
|
|
||||||
public constructor(armory: Armory) {
|
public constructor(armory: Armory) {
|
||||||
this.armory = armory;
|
this.armory = armory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async index(req: express.Request, res: express.Response): Promise<void> {
|
public async index(req: express.Request, res: express.Response): Promise<void> {
|
||||||
res.render("index.hbs", {
|
res.render('index.hbs', {
|
||||||
title: "Armory",
|
title: 'Armory',
|
||||||
realms: this.armory.config.realms.map(r => r.name),
|
realms: this.armory.config.realms.map((r) => r.name),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async search(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
public async search(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
||||||
const realmName = req.query.realm as string;
|
const realmName = req.query.realm as string;
|
||||||
const realm = realmName === undefined ?
|
const realm = realmName === undefined ? this.armory.config.realms[0] : this.armory.config.realms.find((r) => r.name === realmName);
|
||||||
this.armory.config.realms[0] :
|
if (realm === undefined) {
|
||||||
this.armory.config.realms.find(r => r.name === realmName);
|
return next(400);
|
||||||
if (realm === undefined) {
|
}
|
||||||
return next(400);
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = this.armory.getCharactersDb(realm.name);
|
const db = this.armory.getCharactersDb(realm.name);
|
||||||
const charSet = await this.armory.getDatabaseCharset(realm.name);
|
const charSet = await this.armory.getDatabaseCharset(realm.name);
|
||||||
|
|
||||||
let ssp = new DataTablesSsp(req.query, db, "characters", "guid", [
|
let ssp = new DataTablesSsp(req.query, db, 'characters', 'guid', [
|
||||||
{ name: "name", collation: `${charSet}_general_ci` },
|
{ name: 'name', collation: `${charSet}_general_ci` },
|
||||||
{ table: "guild", name: "name" },
|
{ table: 'guild', name: 'name' },
|
||||||
{ name: "level" },
|
{ name: 'level' },
|
||||||
{ name: "class", formatter: cls => Utils.classNames[cls] },
|
{ name: 'class', formatter: (cls) => Utils.classNames[cls] },
|
||||||
{ name: "race", formatter: (race, row) => `${Utils.raceNames[race]}_${row[6] === 0 ? "male" : "female"}` },
|
{ name: 'race', formatter: (race, row) => `${Utils.raceNames[race]}_${row[6] === 0 ? 'male' : 'female'}` },
|
||||||
{ name: "online", formatter: online => online === 1 },
|
{ name: 'online', formatter: (online) => online === 1 },
|
||||||
]);
|
]);
|
||||||
ssp.joins = [
|
ssp.joins = [
|
||||||
{ table1: "characters", column1: "guid", table2: "guild_member", column2: "guid", kind: "LEFT" },
|
{ table1: 'characters', column1: 'guid', table2: 'guild_member', column2: 'guid', kind: 'LEFT' },
|
||||||
{ table1: "guild_member", column1: "guildid", table2: "guild", column2: "guildid", kind: "LEFT" },
|
{ table1: 'guild_member', column1: 'guildid', table2: 'guild', column2: 'guildid', kind: 'LEFT' },
|
||||||
];
|
];
|
||||||
ssp.extraDataColumns = ["`characters`.`gender`"];
|
ssp.extraDataColumns = ['`characters`.`gender`'];
|
||||||
|
|
||||||
if (this.armory.config.hideGameMasters) {
|
if (this.armory.config.hideGameMasters) {
|
||||||
ssp.joins.push({ table1: "characters", column1: "account", table2: "account_access", column2: "id", database2: realm.authDatabase, kind: "LEFT" });
|
ssp.joins.push({
|
||||||
ssp = ssp.where(`\`account_access\`.\`id\` IS NULL OR \`account_access\`.\`RealmID\` NOT IN (-1, ${realm.realmId}) OR \`account_access\`.\`gmlevel\` = 0`);
|
table1: 'characters',
|
||||||
}
|
column1: 'account',
|
||||||
|
table2: 'account_access',
|
||||||
|
column2: 'id',
|
||||||
|
database2: realm.authDatabase,
|
||||||
|
kind: 'LEFT',
|
||||||
|
});
|
||||||
|
ssp = ssp.where(
|
||||||
|
`\`account_access\`.\`id\` IS NULL OR \`account_access\`.\`RealmID\` NOT IN (-1, ${realm.realmId}) OR \`account_access\`.\`gmlevel\` = 0`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const result = await ssp
|
const result = await ssp.where('`deleteInfos_Account` IS NULL').run(this.armory.config.dbQueryTimeout);
|
||||||
.where("`deleteInfos_Account` IS NULL")
|
(result as any).realm = realm.name;
|
||||||
.run(this.armory.config.dbQueryTimeout);
|
|
||||||
(result as any).realm = realm.name;
|
|
||||||
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
import * as fs from "fs";
|
import * as fs from 'fs';
|
||||||
const fsp = fs.promises;
|
const fsp = fs.promises;
|
||||||
import * as path from "path";
|
import * as path from 'path';
|
||||||
|
|
||||||
export class CharacterCustomization {
|
export class CharacterCustomization {
|
||||||
private data: { [key: number]: { [key: number]: any } };
|
private data: { [key: number]: { [key: number]: any } };
|
||||||
|
|
||||||
public async loadData(): Promise<void> {
|
public async loadData(): Promise<void> {
|
||||||
this.data = {};
|
this.data = {};
|
||||||
const races = [1, 2, 3, 4, 5, 6, 7, 8, 10, 11];
|
const races = [1, 2, 3, 4, 5, 6, 7, 8, 10, 11];
|
||||||
const genders = [0, 1];
|
const genders = [0, 1];
|
||||||
|
|
||||||
for (const race of races) {
|
for (const race of races) {
|
||||||
this.data[race] = {};
|
this.data[race] = {};
|
||||||
|
|
||||||
for (const gender of genders) {
|
for (const gender of genders) {
|
||||||
const buffer = await fsp.readFile(path.join(process.cwd(), `data/meta/charactercustomization2/${race}_${gender}.json`));
|
const buffer = await fsp.readFile(path.join(process.cwd(), `data/meta/charactercustomization2/${race}_${gender}.json`));
|
||||||
const customizationData = JSON.parse(buffer.toString());
|
const customizationData = JSON.parse(buffer.toString());
|
||||||
this.data[race][gender] = customizationData;
|
this.data[race][gender] = customizationData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCharacterCustomizationData(race: number, gender: number): any {
|
public getCharacterCustomizationData(race: number, gender: number): any {
|
||||||
return this.data[race][gender];
|
return this.data[race][gender];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,437 +1,460 @@
|
||||||
import * as fs from "fs";
|
import * as fs from 'fs';
|
||||||
import * as path from "path";
|
import * as path from 'path';
|
||||||
|
|
||||||
import * as camelCase from "camelcase";
|
import * as camelCase from 'camelcase';
|
||||||
|
|
||||||
export interface IGlyphProperties {
|
export interface IGlyphProperties {
|
||||||
id: number;
|
id: number;
|
||||||
spellId: number;
|
spellId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAchievement {
|
export interface IAchievement {
|
||||||
id: number;
|
id: number;
|
||||||
faction: number;
|
faction: number;
|
||||||
titleLang0: string;
|
titleLang0: string;
|
||||||
descriptionLang0: string;
|
descriptionLang0: string;
|
||||||
category: number;
|
category: number;
|
||||||
points: number;
|
points: number;
|
||||||
flags: number;
|
flags: number;
|
||||||
iconId: number;
|
iconId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAchievementCategory {
|
export interface IAchievementCategory {
|
||||||
id: number;
|
id: number;
|
||||||
parent: number;
|
parent: number;
|
||||||
nameLang0: string;
|
nameLang0: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IItemDbc {
|
export interface IItemDbc {
|
||||||
id: number;
|
id: number;
|
||||||
classId: number;
|
classId: number;
|
||||||
subclassId: number;
|
subclassId: number;
|
||||||
displayInfoId: number;
|
displayInfoId: number;
|
||||||
inventoryType: number;
|
inventoryType: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IItemRetailDbc {
|
export interface IItemRetailDbc {
|
||||||
id: number;
|
id: number;
|
||||||
inventoryType: number;
|
inventoryType: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IItemAppearanceDbc {
|
export interface IItemAppearanceDbc {
|
||||||
id: number;
|
id: number;
|
||||||
itemDisplayInfoId: number;
|
itemDisplayInfoId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IItemModifiedAppearanceDbc {
|
export interface IItemModifiedAppearanceDbc {
|
||||||
id: number;
|
id: number;
|
||||||
itemId: number;
|
itemId: number;
|
||||||
itemAppearanceId: number;
|
itemAppearanceId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IItemDisplayInfoDbc {
|
export interface IItemDisplayInfoDbc {
|
||||||
id: number;
|
id: number;
|
||||||
inventoryIcon0: number;
|
inventoryIcon0: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMountDbc {
|
export interface IMountDbc {
|
||||||
id: number;
|
id: number;
|
||||||
sourceSpellId: number;
|
sourceSpellId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMountXDisplayDbc {
|
export interface IMountXDisplayDbc {
|
||||||
id: number;
|
id: number;
|
||||||
creatureDisplayInfoId: number;
|
creatureDisplayInfoId: number;
|
||||||
mountId: number;
|
mountId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISpellDbc {
|
export interface ISpellDbc {
|
||||||
id: number;
|
id: number;
|
||||||
mechanic: number;
|
mechanic: number;
|
||||||
spellIconId: number;
|
spellIconId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISpellItemEnchantmentDbc {
|
export interface ISpellItemEnchantmentDbc {
|
||||||
id: number;
|
id: number;
|
||||||
srcItemId: number;
|
srcItemId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISpellIcon {
|
export interface ISpellIcon {
|
||||||
id: number;
|
id: number;
|
||||||
textureFilename: string;
|
textureFilename: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITalent {
|
export interface ITalent {
|
||||||
id: number;
|
id: number;
|
||||||
tabId: number;
|
tabId: number;
|
||||||
tierId: number;
|
tierId: number;
|
||||||
columnIndex: number;
|
columnIndex: number;
|
||||||
spellRank0: number;
|
spellRank0: number;
|
||||||
spellRank1: number;
|
spellRank1: number;
|
||||||
spellRank2: number;
|
spellRank2: number;
|
||||||
spellRank3: number;
|
spellRank3: number;
|
||||||
spellRank4: number;
|
spellRank4: number;
|
||||||
prereqTalent0: number;
|
prereqTalent0: number;
|
||||||
prereqRank0: number;
|
prereqRank0: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITalentTab {
|
export interface ITalentTab {
|
||||||
id: number;
|
id: number;
|
||||||
nameLang0: string;
|
nameLang0: string;
|
||||||
spellIconId: number;
|
spellIconId: number;
|
||||||
classMask: number;
|
classMask: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAsyncGeneratorWithArrayMethods<T> {
|
interface IAsyncGeneratorWithArrayMethods<T> {
|
||||||
[Symbol.asyncIterator](): AsyncGenerator<T>;
|
[Symbol.asyncIterator](): AsyncGenerator<T>;
|
||||||
toArray(): Promise<T[]>;
|
toArray(): Promise<T[]>;
|
||||||
map<M>(fn: (t: T) => M): IAsyncGeneratorWithArrayMethods<M>;
|
map<M>(fn: (t: T) => M): IAsyncGeneratorWithArrayMethods<M>;
|
||||||
filter(fn: (t: T) => boolean): IAsyncGeneratorWithArrayMethods<T>;
|
filter(fn: (t: T) => boolean): IAsyncGeneratorWithArrayMethods<T>;
|
||||||
find(fn: (t: T) => boolean): Promise<T>;
|
find(fn: (t: T) => boolean): Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArrayAsAsyncGenerator<T> implements IAsyncGeneratorWithArrayMethods<T> {
|
class ArrayAsAsyncGenerator<T> implements IAsyncGeneratorWithArrayMethods<T> {
|
||||||
private data: T[];
|
private data: T[];
|
||||||
|
|
||||||
public constructor(data: T[]) {
|
public constructor(data: T[]) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async toArray(): Promise<T[]> {
|
async toArray(): Promise<T[]> {
|
||||||
return this.data;
|
return this.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async *[Symbol.asyncIterator](): AsyncGenerator<T> {
|
async *[Symbol.asyncIterator](): AsyncGenerator<T> {
|
||||||
for (const x of this.data) {
|
for (const x of this.data) {
|
||||||
yield x;
|
yield x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public map<M>(fn: (t: T) => M): ArrayAsAsyncGenerator<M> {
|
public map<M>(fn: (t: T) => M): ArrayAsAsyncGenerator<M> {
|
||||||
return new ArrayAsAsyncGenerator<M>(this.data.map(fn));
|
return new ArrayAsAsyncGenerator<M>(this.data.map(fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
filter(fn: (t: T) => boolean): ArrayAsAsyncGenerator<T> {
|
filter(fn: (t: T) => boolean): ArrayAsAsyncGenerator<T> {
|
||||||
return new ArrayAsAsyncGenerator<T>(this.data.filter(fn));
|
return new ArrayAsAsyncGenerator<T>(this.data.filter(fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
async find(fn: (t: T) => boolean): Promise<T> {
|
async find(fn: (t: T) => boolean): Promise<T> {
|
||||||
return this.data.find(fn);
|
return this.data.find(fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AsyncGenWrapper<T> implements IAsyncGeneratorWithArrayMethods<T> {
|
class AsyncGenWrapper<T> implements IAsyncGeneratorWithArrayMethods<T> {
|
||||||
private gen: AsyncGenerator<T>;
|
private gen: AsyncGenerator<T>;
|
||||||
|
|
||||||
public constructor(gen: AsyncGenerator<T>) {
|
public constructor(gen: AsyncGenerator<T>) {
|
||||||
this.gen = gen;
|
this.gen = gen;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static from<T>(array: T[]): AsyncGenWrapper<T> {
|
public static from<T>(array: T[]): AsyncGenWrapper<T> {
|
||||||
return new AsyncGenWrapper<T>(async function* () {
|
return new AsyncGenWrapper<T>(
|
||||||
for (const x of array) {
|
(async function* () {
|
||||||
yield x;
|
for (const x of array) {
|
||||||
}
|
yield x;
|
||||||
}());
|
}
|
||||||
}
|
})(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public async *[Symbol.asyncIterator](): AsyncGenerator<T> {
|
public async *[Symbol.asyncIterator](): AsyncGenerator<T> {
|
||||||
for await (const x of this.gen) {
|
for await (const x of this.gen) {
|
||||||
yield x;
|
yield x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async toArray(): Promise<T[]> {
|
public async toArray(): Promise<T[]> {
|
||||||
const values = [];
|
const values = [];
|
||||||
for await (const x of this) {
|
for await (const x of this) {
|
||||||
values.push(x);
|
values.push(x);
|
||||||
}
|
}
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
private wrap<X>(g: (that: AsyncGenWrapper<T>) => AsyncGenerator<X>): AsyncGenWrapper<X> {
|
private wrap<X>(g: (that: AsyncGenWrapper<T>) => AsyncGenerator<X>): AsyncGenWrapper<X> {
|
||||||
return new AsyncGenWrapper<X>(g(this));
|
return new AsyncGenWrapper<X>(g(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public map<M>(fn: (t: T) => M): AsyncGenWrapper<M> {
|
public map<M>(fn: (t: T) => M): AsyncGenWrapper<M> {
|
||||||
return this.wrap(async function* (me) {
|
return this.wrap(async function* (me) {
|
||||||
for await (const x of me) {
|
for await (const x of me) {
|
||||||
yield fn(x);
|
yield fn(x);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public filter(fn: (t: T) => boolean): AsyncGenWrapper<T> {
|
public filter(fn: (t: T) => boolean): AsyncGenWrapper<T> {
|
||||||
return this.wrap(async function* (me) {
|
return this.wrap(async function* (me) {
|
||||||
for await (const x of me) {
|
for await (const x of me) {
|
||||||
if (fn(x)) {
|
if (fn(x)) {
|
||||||
yield x;
|
yield x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async find(fn: (t: T) => boolean): Promise<T> {
|
public async find(fn: (t: T) => boolean): Promise<T> {
|
||||||
for await (const x of this) {
|
for await (const x of this) {
|
||||||
if (fn(x)) {
|
if (fn(x)) {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DbcReader<T> {
|
class DbcReader<T> {
|
||||||
private filePath: string;
|
private filePath: string;
|
||||||
private fields: string[];
|
private fields: string[];
|
||||||
|
|
||||||
public constructor(filePath: string, keepFields: string[] = []) {
|
public constructor(filePath: string, keepFields: string[] = []) {
|
||||||
this.filePath = filePath;
|
this.filePath = filePath;
|
||||||
this.fields = keepFields;
|
this.fields = keepFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async *read(): AsyncGenerator<T> {
|
public async *read(): AsyncGenerator<T> {
|
||||||
const stream = fs.createReadStream(this.filePath);
|
const stream = fs.createReadStream(this.filePath);
|
||||||
const itr = this.parseCsv(stream);
|
const itr = this.parseCsv(stream);
|
||||||
const headerLine = await itr.next();
|
const headerLine = await itr.next();
|
||||||
if (headerLine.done === true) {
|
if (headerLine.done === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerCols = headerLine.value
|
const headerCols = headerLine.value.map((header) => camelCase(header).replace(/[\[\]]/g, ''));
|
||||||
.map(header => camelCase(header).replace(/[\[\]]/g, ""));
|
|
||||||
|
|
||||||
for await (const arr of itr) {
|
for await (const arr of itr) {
|
||||||
const cols = arr.map(value => isNaN(value as any) ? value : parseInt(value, 10));
|
const cols = arr.map((value) => (isNaN(value as any) ? value : parseInt(value, 10)));
|
||||||
const row = {};
|
const row = {};
|
||||||
headerCols.forEach((header, headerIdx) => {
|
headerCols.forEach((header, headerIdx) => {
|
||||||
if (this.fields.length === 0 || this.fields.includes(header)) {
|
if (this.fields.length === 0 || this.fields.includes(header)) {
|
||||||
row[header] = cols[headerIdx];
|
row[header] = cols[headerIdx];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
yield row as T;
|
yield row as T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async *parseCsv(stream: fs.ReadStream): AsyncGenerator<string[]> {
|
private async *parseCsv(stream: fs.ReadStream): AsyncGenerator<string[]> {
|
||||||
// Adapted from https://stackoverflow.com/a/14991797
|
// Adapted from https://stackoverflow.com/a/14991797
|
||||||
const arr = [];
|
const arr = [];
|
||||||
let col = 0;
|
let col = 0;
|
||||||
let quote = false; // 'true' means we're inside a quoted field
|
let quote = false; // 'true' means we're inside a quoted field
|
||||||
|
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
const str = chunk.toString();
|
const str = chunk.toString();
|
||||||
// Iterate over each character, keep track of current column (of the returned array)
|
// Iterate over each character, keep track of current column (of the returned array)
|
||||||
for (let c = 0; c < str.length; ++c) {
|
for (let c = 0; c < str.length; ++c) {
|
||||||
let ch = str[c], nch = str[c + 1]; // Current character, next character
|
let ch = str[c],
|
||||||
if (!(col in arr)) {
|
nch = str[c + 1]; // Current character, next character
|
||||||
arr[col] = ""; // Create a new column (start with empty string) if necessary
|
if (!(col in arr)) {
|
||||||
}
|
arr[col] = ''; // Create a new column (start with empty string) if necessary
|
||||||
|
}
|
||||||
|
|
||||||
// If the current character is a quotation mark, and we're inside a
|
// If the current character is a quotation mark, and we're inside a
|
||||||
// quoted field, and the next character is also a quotation mark,
|
// quoted field, and the next character is also a quotation mark,
|
||||||
// add a quotation mark to the current column and skip the next character
|
// add a quotation mark to the current column and skip the next character
|
||||||
if (ch == '"' && quote && nch == '"') {
|
if (ch == '"' && quote && nch == '"') {
|
||||||
arr[col] += ch;
|
arr[col] += ch;
|
||||||
++c;
|
++c;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's just one quotation mark, begin/end quoted field
|
// If it's just one quotation mark, begin/end quoted field
|
||||||
if (ch == '"') {
|
if (ch == '"') {
|
||||||
quote = !quote;
|
quote = !quote;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a comma and we're not in a quoted field, move on to the next column
|
// If it's a comma and we're not in a quoted field, move on to the next column
|
||||||
if (ch == ',' && !quote) {
|
if (ch == ',' && !quote) {
|
||||||
++col;
|
++col;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a newline (CRLF) and we're not in a quoted field, skip the next character
|
// If it's a newline (CRLF) and we're not in a quoted field, skip the next character
|
||||||
// and move on to the next row and move to column 0 of that new row
|
// and move on to the next row and move to column 0 of that new row
|
||||||
if (ch == '\r' && nch == '\n' && !quote) {
|
if (ch == '\r' && nch == '\n' && !quote) {
|
||||||
yield arr;
|
yield arr;
|
||||||
arr.length = 0; // Clear the row
|
arr.length = 0; // Clear the row
|
||||||
col = 0;
|
col = 0;
|
||||||
++c;
|
++c;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a newline (LF or CR) and we're not in a quoted field,
|
// If it's a newline (LF or CR) and we're not in a quoted field,
|
||||||
// move on to the next row and move to column 0 of that new row
|
// move on to the next row and move to column 0 of that new row
|
||||||
if (!quote && (ch == '\r' || ch == '\n')) {
|
if (!quote && (ch == '\r' || ch == '\n')) {
|
||||||
yield arr;
|
yield arr;
|
||||||
arr.length = 0; // Clear the row
|
arr.length = 0; // Clear the row
|
||||||
col = 0;
|
col = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, append the current character to the current column
|
// Otherwise, append the current character to the current column
|
||||||
arr[col] += ch;
|
arr[col] += ch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dir = path.join(process.cwd(), "data");
|
const dir = path.join(process.cwd(), 'data');
|
||||||
export const DbcFiles = {
|
export const DbcFiles = {
|
||||||
achievement: path.join(dir, "Achievement_3.3.5_12340.csv"),
|
achievement: path.join(dir, 'Achievement_3.3.5_12340.csv'),
|
||||||
achievementCategory: path.join(dir, "AchievementCategory_3.3.5_12340.csv"),
|
achievementCategory: path.join(dir, 'AchievementCategory_3.3.5_12340.csv'),
|
||||||
glyphProperties: path.join(dir, "GlyphProperties_3.3.5_12340.csv"),
|
glyphProperties: path.join(dir, 'GlyphProperties_3.3.5_12340.csv'),
|
||||||
item: path.join(dir, "Item_3.3.5_12340.csv"),
|
item: path.join(dir, 'Item_3.3.5_12340.csv'),
|
||||||
itemRetail: path.join(dir, "Item_9.2.0_41462.csv"),
|
itemRetail: path.join(dir, 'Item_9.2.0_41462.csv'),
|
||||||
itemAppearance: path.join(dir, "ItemAppearance_9.2.0_41462.csv"),
|
itemAppearance: path.join(dir, 'ItemAppearance_9.2.0_41462.csv'),
|
||||||
itemModifiedAppearance: path.join(dir, "ItemModifiedAppearance_9.2.0_41462.csv"),
|
itemModifiedAppearance: path.join(dir, 'ItemModifiedAppearance_9.2.0_41462.csv'),
|
||||||
itemDisplayInfo: path.join(dir, "ItemDisplayInfo_3.3.5_12340.csv"),
|
itemDisplayInfo: path.join(dir, 'ItemDisplayInfo_3.3.5_12340.csv'),
|
||||||
mount: path.join(dir, "Mount_9.2.0_41462.csv"),
|
mount: path.join(dir, 'Mount_9.2.0_41462.csv'),
|
||||||
mountDisplay: path.join(dir, "MountXDisplay_9.2.0_41462.csv"),
|
mountDisplay: path.join(dir, 'MountXDisplay_9.2.0_41462.csv'),
|
||||||
spell: path.join(dir, "Spell_3.3.5_12340.csv"),
|
spell: path.join(dir, 'Spell_3.3.5_12340.csv'),
|
||||||
spellItemEnchantment: path.join(dir, "SpellItemEnchantment_3.3.5_12340.csv"),
|
spellItemEnchantment: path.join(dir, 'SpellItemEnchantment_3.3.5_12340.csv'),
|
||||||
spellIcon: path.join(dir, "SpellIcon_3.3.5_12340.csv"),
|
spellIcon: path.join(dir, 'SpellIcon_3.3.5_12340.csv'),
|
||||||
talent: path.join(dir, "Talent_3.3.5_12340.csv"),
|
talent: path.join(dir, 'Talent_3.3.5_12340.csv'),
|
||||||
talentTab: path.join(dir, "TalentTab_3.3.5_12340.csv"),
|
talentTab: path.join(dir, 'TalentTab_3.3.5_12340.csv'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const dbcFields = {
|
const dbcFields = {
|
||||||
achievement: ["id", "faction", "titleLang0", "descriptionLang0", "category", "points", "flags", "iconId"],
|
achievement: ['id', 'faction', 'titleLang0', 'descriptionLang0', 'category', 'points', 'flags', 'iconId'],
|
||||||
achievementCategory: ["id", "parent", "nameLang0"],
|
achievementCategory: ['id', 'parent', 'nameLang0'],
|
||||||
glyphProperties: ["id", "spellId"],
|
glyphProperties: ['id', 'spellId'],
|
||||||
item: ["id", "classId", "subclassId", "displayInfoId", "inventoryType"],
|
item: ['id', 'classId', 'subclassId', 'displayInfoId', 'inventoryType'],
|
||||||
itemRetail: ["id", "inventoryType"],
|
itemRetail: ['id', 'inventoryType'],
|
||||||
itemAppearance: ["id", "itemDisplayInfoId"],
|
itemAppearance: ['id', 'itemDisplayInfoId'],
|
||||||
itemModifiedAppearance: ["id", "itemId", "itemAppearanceId"],
|
itemModifiedAppearance: ['id', 'itemId', 'itemAppearanceId'],
|
||||||
itemDisplayInfo: ["id", "inventoryIcon0"],
|
itemDisplayInfo: ['id', 'inventoryIcon0'],
|
||||||
mount: ["id", "sourceSpellId"],
|
mount: ['id', 'sourceSpellId'],
|
||||||
mountDisplay: ["id", "creatureDisplayInfoId", "mountId"],
|
mountDisplay: ['id', 'creatureDisplayInfoId', 'mountId'],
|
||||||
spell: ["id", "mechanic", "spellIconId"],
|
spell: ['id', 'mechanic', 'spellIconId'],
|
||||||
spellItemEnchantment: ["id", "srcItemId"],
|
spellItemEnchantment: ['id', 'srcItemId'],
|
||||||
spellIcon: ["id", "textureFilename"],
|
spellIcon: ['id', 'textureFilename'],
|
||||||
talent: ["id", "tabId", "tierId", "columnIndex", "spellRank0", "spellRank1", "spellRank2", "spellRank3", "spellRank4", "prereqTalent0", "prereqRank0"],
|
talent: [
|
||||||
talentTab: ["id", "nameLang0", "spellIconId", "classMask"],
|
'id',
|
||||||
|
'tabId',
|
||||||
|
'tierId',
|
||||||
|
'columnIndex',
|
||||||
|
'spellRank0',
|
||||||
|
'spellRank1',
|
||||||
|
'spellRank2',
|
||||||
|
'spellRank3',
|
||||||
|
'spellRank4',
|
||||||
|
'prereqTalent0',
|
||||||
|
'prereqRank0',
|
||||||
|
],
|
||||||
|
talentTab: ['id', 'nameLang0', 'spellIconId', 'classMask'],
|
||||||
};
|
};
|
||||||
|
|
||||||
export class DbcManager {
|
export class DbcManager {
|
||||||
private _achievement: IAchievement[];
|
private _achievement: IAchievement[];
|
||||||
private _achievementCategory: IAchievementCategory[];
|
private _achievementCategory: IAchievementCategory[];
|
||||||
private _glyphProperties: IGlyphProperties[];
|
private _glyphProperties: IGlyphProperties[];
|
||||||
private _item: IItemDbc[];
|
private _item: IItemDbc[];
|
||||||
private _itemRetail: IItemRetailDbc[];
|
private _itemRetail: IItemRetailDbc[];
|
||||||
private _itemAppearance: IItemAppearanceDbc[];
|
private _itemAppearance: IItemAppearanceDbc[];
|
||||||
private _itemModifiedAppearance: IItemModifiedAppearanceDbc[];
|
private _itemModifiedAppearance: IItemModifiedAppearanceDbc[];
|
||||||
private _itemDisplayInfo: IItemDisplayInfoDbc[];
|
private _itemDisplayInfo: IItemDisplayInfoDbc[];
|
||||||
private _mount: IMountDbc[];
|
private _mount: IMountDbc[];
|
||||||
private _mountDisplay: IMountXDisplayDbc[];
|
private _mountDisplay: IMountXDisplayDbc[];
|
||||||
private _spell: ISpellDbc[];
|
private _spell: ISpellDbc[];
|
||||||
private _spellItemEnchantment: ISpellItemEnchantmentDbc[];
|
private _spellItemEnchantment: ISpellItemEnchantmentDbc[];
|
||||||
private _spellIcon: ISpellIcon[];
|
private _spellIcon: ISpellIcon[];
|
||||||
private _talent: ITalent[];
|
private _talent: ITalent[];
|
||||||
private _talentTab: ITalentTab[];
|
private _talentTab: ITalentTab[];
|
||||||
|
|
||||||
public async loadAllFiles(): Promise<void> {
|
public async loadAllFiles(): Promise<void> {
|
||||||
this._achievement = await this.read<IAchievement>(DbcFiles.achievement, dbcFields.achievement).toArray();
|
this._achievement = await this.read<IAchievement>(DbcFiles.achievement, dbcFields.achievement).toArray();
|
||||||
this._achievementCategory = await this.read<IAchievementCategory>(DbcFiles.achievementCategory, dbcFields.achievementCategory).toArray();
|
this._achievementCategory = await this.read<IAchievementCategory>(
|
||||||
this._glyphProperties = await this.read<IGlyphProperties>(DbcFiles.glyphProperties, dbcFields.glyphProperties).toArray();
|
DbcFiles.achievementCategory,
|
||||||
this._item = await this.read<IItemDbc>(DbcFiles.item, dbcFields.item).toArray();
|
dbcFields.achievementCategory,
|
||||||
this._itemRetail = await this.read<IItemRetailDbc>(DbcFiles.itemRetail, dbcFields.itemRetail).toArray();
|
).toArray();
|
||||||
this._itemAppearance = await this.read<IItemAppearanceDbc>(DbcFiles.itemAppearance, dbcFields.itemAppearance).toArray();
|
this._glyphProperties = await this.read<IGlyphProperties>(DbcFiles.glyphProperties, dbcFields.glyphProperties).toArray();
|
||||||
this._itemModifiedAppearance = await this.read<IItemModifiedAppearanceDbc>(DbcFiles.itemModifiedAppearance, dbcFields.itemModifiedAppearance).toArray();
|
this._item = await this.read<IItemDbc>(DbcFiles.item, dbcFields.item).toArray();
|
||||||
this._itemDisplayInfo = await this.read<IItemDisplayInfoDbc>(DbcFiles.itemDisplayInfo, dbcFields.itemDisplayInfo).toArray();
|
this._itemRetail = await this.read<IItemRetailDbc>(DbcFiles.itemRetail, dbcFields.itemRetail).toArray();
|
||||||
this._mount = await this.read<IMountDbc>(DbcFiles.mount, dbcFields.mount).toArray();
|
this._itemAppearance = await this.read<IItemAppearanceDbc>(DbcFiles.itemAppearance, dbcFields.itemAppearance).toArray();
|
||||||
this._mountDisplay = await this.read<IMountXDisplayDbc>(DbcFiles.mountDisplay, dbcFields.mountDisplay).toArray();
|
this._itemModifiedAppearance = await this.read<IItemModifiedAppearanceDbc>(
|
||||||
this._spell = await this.read<ISpellDbc>(DbcFiles.spell, dbcFields.spell).toArray();
|
DbcFiles.itemModifiedAppearance,
|
||||||
this._spellItemEnchantment = await this.read<ISpellItemEnchantmentDbc>(DbcFiles.spellItemEnchantment, dbcFields.spellItemEnchantment).toArray();
|
dbcFields.itemModifiedAppearance,
|
||||||
this._spellIcon = await this.read<ISpellIcon>(DbcFiles.spellIcon, dbcFields.spellIcon).toArray();
|
).toArray();
|
||||||
this._talent = await this.read<ITalent>(DbcFiles.talent, dbcFields.talent).toArray();
|
this._itemDisplayInfo = await this.read<IItemDisplayInfoDbc>(DbcFiles.itemDisplayInfo, dbcFields.itemDisplayInfo).toArray();
|
||||||
this._talentTab = await this.read<ITalentTab>(DbcFiles.talentTab, dbcFields.talentTab).toArray();
|
this._mount = await this.read<IMountDbc>(DbcFiles.mount, dbcFields.mount).toArray();
|
||||||
}
|
this._mountDisplay = await this.read<IMountXDisplayDbc>(DbcFiles.mountDisplay, dbcFields.mountDisplay).toArray();
|
||||||
|
this._spell = await this.read<ISpellDbc>(DbcFiles.spell, dbcFields.spell).toArray();
|
||||||
|
this._spellItemEnchantment = await this.read<ISpellItemEnchantmentDbc>(
|
||||||
|
DbcFiles.spellItemEnchantment,
|
||||||
|
dbcFields.spellItemEnchantment,
|
||||||
|
).toArray();
|
||||||
|
this._spellIcon = await this.read<ISpellIcon>(DbcFiles.spellIcon, dbcFields.spellIcon).toArray();
|
||||||
|
this._talent = await this.read<ITalent>(DbcFiles.talent, dbcFields.talent).toArray();
|
||||||
|
this._talentTab = await this.read<ITalentTab>(DbcFiles.talentTab, dbcFields.talentTab).toArray();
|
||||||
|
}
|
||||||
|
|
||||||
public achievement() {
|
public achievement() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.achievement, this._achievement, dbcFields.achievement);
|
return this.getLoadedDataOrRead(DbcFiles.achievement, this._achievement, dbcFields.achievement);
|
||||||
}
|
}
|
||||||
|
|
||||||
public achievementCategory() {
|
public achievementCategory() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.achievementCategory, this._achievementCategory, dbcFields.achievementCategory);
|
return this.getLoadedDataOrRead(DbcFiles.achievementCategory, this._achievementCategory, dbcFields.achievementCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public glyphProperties() {
|
public glyphProperties() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.glyphProperties, this._glyphProperties, dbcFields.glyphProperties);
|
return this.getLoadedDataOrRead(DbcFiles.glyphProperties, this._glyphProperties, dbcFields.glyphProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public item() {
|
public item() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.item, this._item, dbcFields.item);
|
return this.getLoadedDataOrRead(DbcFiles.item, this._item, dbcFields.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public itemRetail() {
|
public itemRetail() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.itemRetail, this._itemRetail, dbcFields.itemRetail);
|
return this.getLoadedDataOrRead(DbcFiles.itemRetail, this._itemRetail, dbcFields.itemRetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
public itemAppearance() {
|
public itemAppearance() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.itemAppearance, this._itemAppearance, dbcFields.itemAppearance);
|
return this.getLoadedDataOrRead(DbcFiles.itemAppearance, this._itemAppearance, dbcFields.itemAppearance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public itemModifiedAppearance() {
|
public itemModifiedAppearance() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.itemModifiedAppearance, this._itemModifiedAppearance, dbcFields.itemModifiedAppearance);
|
return this.getLoadedDataOrRead(DbcFiles.itemModifiedAppearance, this._itemModifiedAppearance, dbcFields.itemModifiedAppearance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public itemDisplayInfo() {
|
public itemDisplayInfo() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.itemDisplayInfo, this._itemDisplayInfo, dbcFields.itemDisplayInfo);
|
return this.getLoadedDataOrRead(DbcFiles.itemDisplayInfo, this._itemDisplayInfo, dbcFields.itemDisplayInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public mount() {
|
public mount() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.mount, this._mount, dbcFields.mount);
|
return this.getLoadedDataOrRead(DbcFiles.mount, this._mount, dbcFields.mount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public mountDisplay() {
|
public mountDisplay() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.mountDisplay, this._mountDisplay, dbcFields.mountDisplay);
|
return this.getLoadedDataOrRead(DbcFiles.mountDisplay, this._mountDisplay, dbcFields.mountDisplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public spell() {
|
public spell() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.spell, this._spell, dbcFields.spell);
|
return this.getLoadedDataOrRead(DbcFiles.spell, this._spell, dbcFields.spell);
|
||||||
}
|
}
|
||||||
|
|
||||||
public spellItemEnchantment() {
|
public spellItemEnchantment() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.spellItemEnchantment, this._spellItemEnchantment, dbcFields.spellItemEnchantment);
|
return this.getLoadedDataOrRead(DbcFiles.spellItemEnchantment, this._spellItemEnchantment, dbcFields.spellItemEnchantment);
|
||||||
}
|
}
|
||||||
|
|
||||||
public spellIcon() {
|
public spellIcon() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.spellIcon, this._spellIcon, dbcFields.spellIcon);
|
return this.getLoadedDataOrRead(DbcFiles.spellIcon, this._spellIcon, dbcFields.spellIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
public talent() {
|
public talent() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.talent, this._talent, dbcFields.talent);
|
return this.getLoadedDataOrRead(DbcFiles.talent, this._talent, dbcFields.talent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public talentTab() {
|
public talentTab() {
|
||||||
return this.getLoadedDataOrRead(DbcFiles.talentTab, this._talentTab, dbcFields.talentTab);
|
return this.getLoadedDataOrRead(DbcFiles.talentTab, this._talentTab, dbcFields.talentTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
private read<T>(file: string, keepFields: string[] = []): AsyncGenWrapper<T> {
|
private read<T>(file: string, keepFields: string[] = []): AsyncGenWrapper<T> {
|
||||||
const reader = new DbcReader<T>(file, keepFields);
|
const reader = new DbcReader<T>(file, keepFields);
|
||||||
return new AsyncGenWrapper(reader.read());
|
return new AsyncGenWrapper(reader.read());
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLoadedDataOrRead<T>(path: string, data: T[], keepFields: string[] = []): IAsyncGeneratorWithArrayMethods<T> {
|
private getLoadedDataOrRead<T>(path: string, data: T[], keepFields: string[] = []): IAsyncGeneratorWithArrayMethods<T> {
|
||||||
return data === undefined ? this.read<T>(path, keepFields) : new ArrayAsAsyncGenerator(data);
|
return data === undefined ? this.read<T>(path, keepFields) : new ArrayAsAsyncGenerator(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import "dotenv/config";
|
import 'dotenv/config';
|
||||||
import { Armory } from "./Armory";
|
import { Armory } from './Armory';
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
require("source-map-support").install();
|
require('source-map-support').install();
|
||||||
|
|
||||||
const armory = new Armory();
|
const armory = new Armory();
|
||||||
await armory.start();
|
await armory.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|
|
||||||
6
src/index.d.ts
vendored
6
src/index.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
declare module Express {
|
declare module Express {
|
||||||
export interface Request {
|
export interface Request {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,79 @@
|
||||||
import * as fs from "fs";
|
import * as fs from 'fs';
|
||||||
const fsp = fs.promises;
|
const fsp = fs.promises;
|
||||||
import * as path from "path";
|
import * as path from 'path';
|
||||||
|
|
||||||
import * as pako from "pako";
|
import * as pako from 'pako';
|
||||||
import fetch from "node-fetch";
|
import fetch from 'node-fetch';
|
||||||
import * as mkdirp from "mkdirp";
|
import * as mkdirp from 'mkdirp';
|
||||||
import * as glob from "glob-promise";
|
import * as glob from 'glob-promise';
|
||||||
import { Response } from "node-fetch";
|
import { Response } from 'node-fetch';
|
||||||
import * as prettyMs from "pretty-ms";
|
import * as prettyMs from 'pretty-ms';
|
||||||
import * as cliProgress from "cli-progress";
|
import * as cliProgress from 'cli-progress';
|
||||||
import promisepool = require("@supercharge/promise-pool");
|
import promisepool = require('@supercharge/promise-pool');
|
||||||
|
|
||||||
import { DbcManager, IItemAppearanceDbc, IItemModifiedAppearanceDbc, IMountDbc, IMountXDisplayDbc } from "../armory/data/DbcReader";
|
import { DbcManager, IItemAppearanceDbc, IItemModifiedAppearanceDbc, IMountDbc, IMountXDisplayDbc } from '../armory/data/DbcReader';
|
||||||
|
|
||||||
require("source-map-support").install();
|
require('source-map-support').install();
|
||||||
|
|
||||||
const baseUrl = "https://wow.zamimg.com/modelviewer/live";
|
const baseUrl = 'https://wow.zamimg.com/modelviewer/live';
|
||||||
|
|
||||||
class Stopwatch {
|
class Stopwatch {
|
||||||
private startTime: number;
|
private startTime: number;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(): void {
|
public start(): void {
|
||||||
this.startTime = Date.now();
|
this.startTime = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
public stop(text?: string): void {
|
public stop(text?: string): void {
|
||||||
const dt = Date.now() - this.startTime;
|
const dt = Date.now() - this.startTime;
|
||||||
const txt = text ?? "Done in {time}";
|
const txt = text ?? 'Done in {time}';
|
||||||
console.log(txt.replace("{time}", prettyMs(dt)));
|
console.log(txt.replace('{time}', prettyMs(dt)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Progress {
|
class Progress {
|
||||||
private bar: cliProgress.SingleBar;
|
private bar: cliProgress.SingleBar;
|
||||||
private stopwatch: Stopwatch;
|
private stopwatch: Stopwatch;
|
||||||
|
|
||||||
public constructor(text: string, operations: number) {
|
public constructor(text: string, operations: number) {
|
||||||
this.bar = this.createProgressBar(text, operations);
|
this.bar = this.createProgressBar(text, operations);
|
||||||
this.stopwatch = new Stopwatch();
|
this.stopwatch = new Stopwatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
public increment(step?: number): void {
|
public increment(step?: number): void {
|
||||||
this.bar.increment(step);
|
this.bar.increment(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
public stop(): void {
|
public stop(): void {
|
||||||
this.bar.stop();
|
this.bar.stop();
|
||||||
this.stopwatch.stop();
|
this.stopwatch.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private createProgressBar(text: string, total: number): cliProgress.SingleBar {
|
private createProgressBar(text: string, total: number): cliProgress.SingleBar {
|
||||||
const progress = new cliProgress.SingleBar({
|
const progress = new cliProgress.SingleBar(
|
||||||
format: `${text} {bar} {percentage}% ({value} / {total})`,
|
{
|
||||||
}, cliProgress.Presets.shades_classic);
|
format: `${text} {bar} {percentage}% ({value} / {total})`,
|
||||||
progress.start(total, 0);
|
},
|
||||||
return progress;
|
cliProgress.Presets.shades_classic,
|
||||||
}
|
);
|
||||||
|
progress.start(total, 0);
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HttpRequestError extends Error {
|
class HttpRequestError extends Error {
|
||||||
public response: Response;
|
public response: Response;
|
||||||
|
|
||||||
public constructor(response: Response) {
|
public constructor(response: Response) {
|
||||||
super(`Could not download ${response.url} (${response.status})`);
|
super(`Could not download ${response.url} (${response.status})`);
|
||||||
this.name = "HttpRequestError";
|
this.name = 'HttpRequestError';
|
||||||
this.response = response;
|
this.response = response;
|
||||||
Object.setPrototypeOf(this, HttpRequestError.prototype);
|
Object.setPrototypeOf(this, HttpRequestError.prototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dbc: DbcManager;
|
let dbc: DbcManager;
|
||||||
|
|
@ -90,345 +93,339 @@ const texturesDownloadQueue = new Set<number>();
|
||||||
const bonesDownloadQueue = new Set<number>();
|
const bonesDownloadQueue = new Set<number>();
|
||||||
|
|
||||||
async function download(dir: string, file: string): Promise<string | any> {
|
async function download(dir: string, file: string): Promise<string | any> {
|
||||||
const dataDir = path.join(process.cwd(), "data");
|
const dataDir = path.join(process.cwd(), 'data');
|
||||||
const fullPath = `${dir}/${file}`;
|
const fullPath = `${dir}/${file}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${baseUrl}/${fullPath}`);
|
const res = await fetch(`${baseUrl}/${fullPath}`);
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new HttpRequestError(res);
|
throw new HttpRequestError(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
await mkdirp(path.join(dataDir, dir));
|
await mkdirp(path.join(dataDir, dir));
|
||||||
|
|
||||||
if (res.headers.get("Content-Type") === "application/json") {
|
if (res.headers.get('Content-Type') === 'application/json') {
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
fsp.writeFile(path.join(dataDir, fullPath), JSON.stringify(json));
|
fsp.writeFile(path.join(dataDir, fullPath), JSON.stringify(json));
|
||||||
return json;
|
return json;
|
||||||
} else {
|
} else {
|
||||||
const fileStream = fs.createWriteStream(path.join(dataDir, fullPath));
|
const fileStream = fs.createWriteStream(path.join(dataDir, fullPath));
|
||||||
await new Promise((resolve, rej) => {
|
await new Promise((resolve, rej) => {
|
||||||
res.body.pipe(fileStream);
|
res.body.pipe(fileStream);
|
||||||
res.body.on("error", rej);
|
res.body.on('error', rej);
|
||||||
fileStream.on("finish", resolve);
|
fileStream.on('finish', resolve);
|
||||||
});
|
});
|
||||||
|
|
||||||
return fileStream.path.toString();
|
return fileStream.path.toString();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function queueTexturesAndModels(item: any): void {
|
function queueTexturesAndModels(item: any): void {
|
||||||
if (item.TextureFiles !== null) {
|
if (item.TextureFiles !== null) {
|
||||||
for (const key in item.TextureFiles) {
|
for (const key in item.TextureFiles) {
|
||||||
for (const file of item.TextureFiles[key]) {
|
for (const file of item.TextureFiles[key]) {
|
||||||
if (file.FileDataId !== 0) {
|
if (file.FileDataId !== 0) {
|
||||||
texturesDownloadQueue.add(file.FileDataId);
|
texturesDownloadQueue.add(file.FileDataId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.ModelFiles !== null) {
|
if (item.ModelFiles !== null) {
|
||||||
for (const key in item.ModelFiles) {
|
for (const key in item.ModelFiles) {
|
||||||
for (const file of item.ModelFiles[key]) {
|
for (const file of item.ModelFiles[key]) {
|
||||||
if (file.FileDataId !== 0) {
|
if (file.FileDataId !== 0) {
|
||||||
modelsDownloadQueue.add(file.FileDataId);
|
modelsDownloadQueue.add(file.FileDataId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof item.Model === "number" && item.Model !== 0) {
|
if (typeof item.Model === 'number' && item.Model !== 0) {
|
||||||
modelsDownloadQueue.add(item.Model);
|
modelsDownloadQueue.add(item.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.Textures !== null) {
|
if (item.Textures !== null) {
|
||||||
for (const key in item.Textures) {
|
for (const key in item.Textures) {
|
||||||
if (item.Textures[key] !== 0) {
|
if (item.Textures[key] !== 0) {
|
||||||
texturesDownloadQueue.add(item.Textures[key]);
|
texturesDownloadQueue.add(item.Textures[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.Textures2 !== null) {
|
if (item.Textures2 !== null) {
|
||||||
for (const key in item.Textures2) {
|
for (const key in item.Textures2) {
|
||||||
if (item.Textures2[key] !== 0) {
|
if (item.Textures2[key] !== 0) {
|
||||||
texturesDownloadQueue.add(item.Textures2[key]);
|
texturesDownloadQueue.add(item.Textures2[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadRaces(): Promise<void> {
|
async function downloadRaces(): Promise<void> {
|
||||||
const races = [
|
const races = ['human', 'nightelf', 'dwarf', 'gnome', 'draenei', 'orc', 'troll', 'tauren', 'bloodelf', 'scourge'];
|
||||||
"human",
|
const genders = ['male', 'female'];
|
||||||
"nightelf",
|
const raceGenderCombo = races.map((race) => [race + genders[0], race + genders[1]]).flat();
|
||||||
"dwarf",
|
|
||||||
"gnome",
|
|
||||||
"draenei",
|
|
||||||
"orc",
|
|
||||||
"troll",
|
|
||||||
"tauren",
|
|
||||||
"bloodelf",
|
|
||||||
"scourge",
|
|
||||||
];
|
|
||||||
const genders = ["male", "female"];
|
|
||||||
const raceGenderCombo = races.map((race) => [race + genders[0], race + genders[1]]).flat();
|
|
||||||
|
|
||||||
const progress = new Progress("Downloading races data...", raceGenderCombo.length);
|
const progress = new Progress('Downloading races data...', raceGenderCombo.length);
|
||||||
|
|
||||||
await promisepool.PromisePool
|
await promisepool.PromisePool.for(raceGenderCombo)
|
||||||
.for(raceGenderCombo)
|
.withConcurrency(4)
|
||||||
.withConcurrency(4)
|
.process(async (race) => {
|
||||||
.process(async (race) => {
|
const characterJson = await download('meta/character', `${race}.json`);
|
||||||
const characterJson = await download("meta/character", `${race}.json`);
|
modelsDownloadQueue.add(characterJson.Model);
|
||||||
modelsDownloadQueue.add(characterJson.Model);
|
|
||||||
|
|
||||||
const customizationJson = await download("meta/charactercustomization2", `${characterJson.Race}_${characterJson.Gender}.json`);
|
const customizationJson = await download('meta/charactercustomization2', `${characterJson.Race}_${characterJson.Gender}.json`);
|
||||||
for (const option of customizationJson.Options) {
|
for (const option of customizationJson.Options) {
|
||||||
for (const choice of option.Choices) {
|
for (const choice of option.Choices) {
|
||||||
for (const element of choice.Elements) {
|
for (const element of choice.Elements) {
|
||||||
if (element.SkinnedModel !== null && typeof element.SkinnedModel.CollectionFileDataID === "number" && element.SkinnedModel.CollectionFileDataID !== 0) {
|
if (
|
||||||
modelsDownloadQueue.add(element.SkinnedModel.CollectionFileDataID);
|
element.SkinnedModel !== null &&
|
||||||
}
|
typeof element.SkinnedModel.CollectionFileDataID === 'number' &&
|
||||||
if (element.BoneSet !== null && typeof element.BoneSet.BoneFileDataID === "number" && element.BoneSet.BoneFileDataID !== 0) {
|
element.SkinnedModel.CollectionFileDataID !== 0
|
||||||
bonesDownloadQueue.add(element.BoneSet.BoneFileDataID);
|
) {
|
||||||
}
|
modelsDownloadQueue.add(element.SkinnedModel.CollectionFileDataID);
|
||||||
}
|
}
|
||||||
}
|
if (element.BoneSet !== null && typeof element.BoneSet.BoneFileDataID === 'number' && element.BoneSet.BoneFileDataID !== 0) {
|
||||||
}
|
bonesDownloadQueue.add(element.BoneSet.BoneFileDataID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const textureFiles = Object.keys(customizationJson.TextureFiles)
|
const textureFiles = Object.keys(customizationJson.TextureFiles)
|
||||||
.map(key => customizationJson.TextureFiles[key])
|
.map((key) => customizationJson.TextureFiles[key])
|
||||||
.flat();
|
.flat();
|
||||||
for (const file of textureFiles) {
|
for (const file of textureFiles) {
|
||||||
texturesDownloadQueue.add(file.FileDataId);
|
texturesDownloadQueue.add(file.FileDataId);
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.increment();
|
progress.increment();
|
||||||
});
|
});
|
||||||
|
|
||||||
progress.stop();
|
progress.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadArmors(): Promise<void> {
|
async function downloadArmors(): Promise<void> {
|
||||||
const rows = await dbc.item().filter((row) => row.classId === classIdArmor).toArray();
|
const rows = await dbc
|
||||||
|
.item()
|
||||||
|
.filter((row) => row.classId === classIdArmor)
|
||||||
|
.toArray();
|
||||||
|
|
||||||
const progress = new Progress("Downloading armor data...", rows.length);
|
const progress = new Progress('Downloading armor data...', rows.length);
|
||||||
|
|
||||||
await promisepool.PromisePool
|
await promisepool.PromisePool.for(rows)
|
||||||
.for(rows)
|
.withConcurrency(50)
|
||||||
.withConcurrency(50)
|
.process(async (row) => {
|
||||||
.process(async (row) => {
|
const modifiedAppearance = dbcItemModifiedAppearanceByItemId[row.id];
|
||||||
const modifiedAppearance = dbcItemModifiedAppearanceByItemId[row.id];
|
if (modifiedAppearance === undefined) {
|
||||||
if (modifiedAppearance === undefined) {
|
progress.increment();
|
||||||
progress.increment();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
const appearance = dbcItemAppearanceById[modifiedAppearance.itemAppearanceId];
|
||||||
const appearance = dbcItemAppearanceById[modifiedAppearance.itemAppearanceId];
|
const metaPath = [invTypeShield, invTypeOffHand].includes(row.inventoryType) ? 'item' : `armor/${row.inventoryType}`;
|
||||||
const metaPath = [invTypeShield, invTypeOffHand].includes(row.inventoryType) ? "item" : `armor/${row.inventoryType}`;
|
try {
|
||||||
try {
|
const itemJson = await download(`meta/${metaPath}`, `${appearance.itemDisplayInfoId}.json`);
|
||||||
const itemJson = await download(`meta/${metaPath}`, `${appearance.itemDisplayInfoId}.json`);
|
queueTexturesAndModels(itemJson);
|
||||||
queueTexturesAndModels(itemJson);
|
progress.increment();
|
||||||
progress.increment();
|
} catch (err) {
|
||||||
} catch (err) {
|
if (err instanceof HttpRequestError && err.response.status === 404) {
|
||||||
if (err instanceof HttpRequestError && err.response.status === 404) {
|
progress.increment();
|
||||||
progress.increment();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
throw err;
|
||||||
throw err;
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
progress.stop();
|
progress.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadWeapons(): Promise<void> {
|
async function downloadWeapons(): Promise<void> {
|
||||||
const rows = await dbc.item().filter((row) => row.classId === classIdWeapon).toArray();
|
const rows = await dbc
|
||||||
|
.item()
|
||||||
|
.filter((row) => row.classId === classIdWeapon)
|
||||||
|
.toArray();
|
||||||
|
|
||||||
const progress = new Progress("Downloading weapon data...", rows.length);
|
const progress = new Progress('Downloading weapon data...', rows.length);
|
||||||
|
|
||||||
await promisepool.PromisePool
|
await promisepool.PromisePool.for(rows)
|
||||||
.for(rows)
|
.withConcurrency(50)
|
||||||
.withConcurrency(50)
|
.process(async (row) => {
|
||||||
.process(async (row) => {
|
const modifiedAppearance = dbcItemModifiedAppearanceByItemId[row.id];
|
||||||
const modifiedAppearance = dbcItemModifiedAppearanceByItemId[row.id];
|
if (modifiedAppearance === undefined) {
|
||||||
if (modifiedAppearance === undefined) {
|
progress.increment();
|
||||||
progress.increment();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
const appearance = dbcItemAppearanceById[modifiedAppearance.itemAppearanceId];
|
||||||
const appearance = dbcItemAppearanceById[modifiedAppearance.itemAppearanceId];
|
try {
|
||||||
try {
|
const itemJson = await download(`meta/item`, `${appearance.itemDisplayInfoId}.json`);
|
||||||
const itemJson = await download(`meta/item`, `${appearance.itemDisplayInfoId}.json`);
|
queueTexturesAndModels(itemJson);
|
||||||
queueTexturesAndModels(itemJson);
|
progress.increment();
|
||||||
progress.increment();
|
} catch (err) {
|
||||||
} catch (err) {
|
if (err instanceof HttpRequestError && err.response.status === 404) {
|
||||||
if (err instanceof HttpRequestError && err.response.status === 404) {
|
progress.increment();
|
||||||
progress.increment();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
throw err;
|
||||||
throw err;
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
progress.stop();
|
progress.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readDbcData(): Promise<void> {
|
async function readDbcData(): Promise<void> {
|
||||||
console.log("Reading DBC data...");
|
console.log('Reading DBC data...');
|
||||||
|
|
||||||
dbc = new DbcManager();
|
dbc = new DbcManager();
|
||||||
await dbc.loadAllFiles();
|
await dbc.loadAllFiles();
|
||||||
|
|
||||||
dbcItemAppearanceById = {};
|
dbcItemAppearanceById = {};
|
||||||
for await (const row of dbc.itemAppearance()) {
|
for await (const row of dbc.itemAppearance()) {
|
||||||
dbcItemAppearanceById[row.id] = row;
|
dbcItemAppearanceById[row.id] = row;
|
||||||
}
|
}
|
||||||
|
|
||||||
dbcItemModifiedAppearanceByItemId = {};
|
dbcItemModifiedAppearanceByItemId = {};
|
||||||
for await (const row of dbc.itemModifiedAppearance()) {
|
for await (const row of dbc.itemModifiedAppearance()) {
|
||||||
dbcItemModifiedAppearanceByItemId[row.itemId] = row;
|
dbcItemModifiedAppearanceByItemId[row.itemId] = row;
|
||||||
}
|
}
|
||||||
|
|
||||||
dbcMountBySourceSpellId = {};
|
dbcMountBySourceSpellId = {};
|
||||||
for await (const row of dbc.mount()) {
|
for await (const row of dbc.mount()) {
|
||||||
dbcMountBySourceSpellId[row.sourceSpellId] = row;
|
dbcMountBySourceSpellId[row.sourceSpellId] = row;
|
||||||
}
|
}
|
||||||
|
|
||||||
dbcMountDisplayByMountId = {};
|
dbcMountDisplayByMountId = {};
|
||||||
for await (const row of dbc.mountDisplay()) {
|
for await (const row of dbc.mountDisplay()) {
|
||||||
if (!(row.mountId in dbcMountDisplayByMountId)) {
|
if (!(row.mountId in dbcMountDisplayByMountId)) {
|
||||||
dbcMountDisplayByMountId[row.mountId] = row;
|
dbcMountDisplayByMountId[row.mountId] = row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadMounts(): Promise<void> {
|
async function downloadMounts(): Promise<void> {
|
||||||
const mountSpells = await dbc.spell().filter(spell => spell.mechanic === spellMechanicMounted).toArray();
|
const mountSpells = await dbc
|
||||||
const progress = new Progress("Downloading mount data...", mountSpells.length);
|
.spell()
|
||||||
|
.filter((spell) => spell.mechanic === spellMechanicMounted)
|
||||||
|
.toArray();
|
||||||
|
const progress = new Progress('Downloading mount data...', mountSpells.length);
|
||||||
|
|
||||||
await promisepool.PromisePool
|
await promisepool.PromisePool.for(mountSpells)
|
||||||
.for(mountSpells)
|
.withConcurrency(50)
|
||||||
.withConcurrency(50)
|
.process(async (spell) => {
|
||||||
.process(async (spell) => {
|
const mount = dbcMountBySourceSpellId[spell.id];
|
||||||
const mount = dbcMountBySourceSpellId[spell.id];
|
if (mount === undefined) {
|
||||||
if (mount === undefined) {
|
progress.increment();
|
||||||
progress.increment();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const display = dbcMountDisplayByMountId[mount.id];
|
const display = dbcMountDisplayByMountId[mount.id];
|
||||||
const json = await download("meta/npc", `${display.creatureDisplayInfoId}.json`);
|
const json = await download('meta/npc', `${display.creatureDisplayInfoId}.json`);
|
||||||
queueTexturesAndModels(json);
|
queueTexturesAndModels(json);
|
||||||
|
|
||||||
progress.increment();
|
progress.increment();
|
||||||
});
|
});
|
||||||
|
|
||||||
progress.stop();
|
progress.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadTextures(): Promise<void> {
|
async function downloadTextures(): Promise<void> {
|
||||||
const progress = new Progress("Downloading textures...", texturesDownloadQueue.size);
|
const progress = new Progress('Downloading textures...', texturesDownloadQueue.size);
|
||||||
|
|
||||||
await promisepool.PromisePool
|
await promisepool.PromisePool.for(Array.from(texturesDownloadQueue))
|
||||||
.for(Array.from(texturesDownloadQueue))
|
.withConcurrency(25)
|
||||||
.withConcurrency(25)
|
.process(async (fileDataId) => {
|
||||||
.process(async (fileDataId) => {
|
await download('textures', `${fileDataId}.png`);
|
||||||
await download("textures", `${fileDataId}.png`);
|
progress.increment();
|
||||||
progress.increment();
|
});
|
||||||
});
|
|
||||||
|
|
||||||
progress.stop();
|
progress.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadModels(): Promise<void> {
|
async function downloadModels(): Promise<void> {
|
||||||
const progress = new Progress("Downloading models...", modelsDownloadQueue.size);
|
const progress = new Progress('Downloading models...', modelsDownloadQueue.size);
|
||||||
|
|
||||||
await promisepool.PromisePool
|
await promisepool.PromisePool.for(Array.from(modelsDownloadQueue))
|
||||||
.for(Array.from(modelsDownloadQueue))
|
.withConcurrency(25)
|
||||||
.withConcurrency(25)
|
.process(async (fileDataId) => {
|
||||||
.process(async (fileDataId) => {
|
await download('mo3', `${fileDataId}.mo3`);
|
||||||
await download("mo3", `${fileDataId}.mo3`);
|
progress.increment();
|
||||||
progress.increment();
|
});
|
||||||
});
|
|
||||||
|
|
||||||
progress.stop();
|
progress.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadBones(): Promise<void> {
|
async function downloadBones(): Promise<void> {
|
||||||
const progress = new Progress("Downloading bones...", bonesDownloadQueue.size);
|
const progress = new Progress('Downloading bones...', bonesDownloadQueue.size);
|
||||||
|
|
||||||
await promisepool.PromisePool
|
await promisepool.PromisePool.for(Array.from(bonesDownloadQueue))
|
||||||
.for(Array.from(bonesDownloadQueue))
|
.withConcurrency(25)
|
||||||
.withConcurrency(25)
|
.process(async (fileDataId) => {
|
||||||
.process(async (fileDataId) => {
|
try {
|
||||||
try {
|
await download('bone', `${fileDataId}.bone`);
|
||||||
await download("bone", `${fileDataId}.bone`);
|
progress.increment();
|
||||||
progress.increment();
|
} catch (err) {
|
||||||
} catch (err) {
|
if (err instanceof HttpRequestError && err.response.status === 404) {
|
||||||
if (err instanceof HttpRequestError && err.response.status === 404) {
|
progress.increment();
|
||||||
progress.increment();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
throw err;
|
||||||
throw err;
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
progress.stop();
|
progress.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseModels(): Promise<void> {
|
async function parseModels(): Promise<void> {
|
||||||
const files = await glob("data/mo3/*.mo3");
|
const files = await glob('data/mo3/*.mo3');
|
||||||
const progress = new Progress("Reading model files for texture references...", files.length);
|
const progress = new Progress('Reading model files for texture references...', files.length);
|
||||||
|
|
||||||
await promisepool.PromisePool
|
await promisepool.PromisePool.for(files)
|
||||||
.for(files)
|
.withConcurrency(20)
|
||||||
.withConcurrency(20)
|
.process(async (file) => {
|
||||||
.process(async (file) => {
|
const buffer = await fsp.readFile(file);
|
||||||
const buffer = await fsp.readFile(file);
|
|
||||||
|
|
||||||
const texturesOffset = buffer.readUInt32LE(60);
|
const texturesOffset = buffer.readUInt32LE(60);
|
||||||
const uncompressedSize = buffer.readUInt32LE(112);
|
const uncompressedSize = buffer.readUInt32LE(112);
|
||||||
const compressedData = buffer.slice(116);
|
const compressedData = buffer.slice(116);
|
||||||
const data = Buffer.from(pako.inflate(compressedData));
|
const data = Buffer.from(pako.inflate(compressedData));
|
||||||
if (data.length !== uncompressedSize) {
|
if (data.length !== uncompressedSize) {
|
||||||
throw `Unexpected data size ${data.length}, expected ${uncompressedSize}`;
|
throw `Unexpected data size ${data.length}, expected ${uncompressedSize}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nbTextures = data.readInt32LE(texturesOffset);
|
const nbTextures = data.readInt32LE(texturesOffset);
|
||||||
let offset = texturesOffset + 4;
|
let offset = texturesOffset + 4;
|
||||||
for (let i = 0; i < nbTextures; ++i) {
|
for (let i = 0; i < nbTextures; ++i) {
|
||||||
const textureId = data.readUInt32LE(offset + 4 + 4);
|
const textureId = data.readUInt32LE(offset + 4 + 4);
|
||||||
if (textureId !== 0) {
|
if (textureId !== 0) {
|
||||||
texturesDownloadQueue.add(textureId);
|
texturesDownloadQueue.add(textureId);
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += 4 + 4 + 4;
|
offset += 4 + 4 + 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.increment();
|
progress.increment();
|
||||||
});
|
});
|
||||||
|
|
||||||
progress.stop();
|
progress.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const sw = new Stopwatch();
|
const sw = new Stopwatch();
|
||||||
|
|
||||||
await readDbcData();
|
await readDbcData();
|
||||||
await downloadRaces(); // Download info for all races
|
await downloadRaces(); // Download info for all races
|
||||||
await downloadArmors(); // Download info for all armors
|
await downloadArmors(); // Download info for all armors
|
||||||
await downloadWeapons(); // Download info for all weapons
|
await downloadWeapons(); // Download info for all weapons
|
||||||
await downloadMounts(); // Download info for all mounts
|
await downloadMounts(); // Download info for all mounts
|
||||||
await downloadModels(); // Download all queued models
|
await downloadModels(); // Download all queued models
|
||||||
await parseModels(); // Read model files to find texture references
|
await parseModels(); // Read model files to find texture references
|
||||||
await downloadTextures(); // Download all queued textures
|
await downloadTextures(); // Download all queued textures
|
||||||
await downloadBones(); // Download all queued bones
|
await downloadBones(); // Download all queued bones
|
||||||
|
|
||||||
sw.stop("Everything done in {time}");
|
sw.stop('Everything done in {time}');
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|
|
||||||
|
|
@ -1,112 +1,118 @@
|
||||||
function waitForEmblemImages($emblem) {
|
function waitForEmblemImages($emblem) {
|
||||||
return Promise.all($emblem.find(".images img").map((idx, img) => {
|
return Promise.all(
|
||||||
return new Promise((res, rej) => {
|
$emblem.find('.images img').map((idx, img) => {
|
||||||
if (img.complete) {
|
return new Promise((res, rej) => {
|
||||||
res();
|
if (img.complete) {
|
||||||
} else {
|
res();
|
||||||
img.addEventListener("load", res);
|
} else {
|
||||||
img.addEventListener("error", rej);
|
img.addEventListener('load', res);
|
||||||
}
|
img.addEventListener('error', rej);
|
||||||
});
|
}
|
||||||
}));
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createGuildEmblem(emblem, el) {
|
function createGuildEmblem(emblem, el) {
|
||||||
const $emblem = $(el);
|
const $emblem = $(el);
|
||||||
const canvas = $emblem.find("canvas")[0];
|
const canvas = $emblem.find('canvas')[0];
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
const imgUrl = (type, section, value, value2) => `${handlebarsData.websiteRoot}/img/guild-emblems/${type}_${value}${value2 ? ("_" + value2) : ""}_T${section}_U.PNG`;
|
const imgUrl = (type, section, value, value2) =>
|
||||||
|
`${handlebarsData.websiteRoot}/img/guild-emblems/${type}_${value}${value2 ? '_' + value2 : ''}_T${section}_U.PNG`;
|
||||||
|
|
||||||
const $images = $("<div>").addClass("images").appendTo($emblem);
|
const $images = $('<div>').addClass('images').appendTo($emblem);
|
||||||
const bgUpper = $("<img>").attr("src", imgUrl("Background", "U", emblem.background)).appendTo($images)[0];
|
const bgUpper = $('<img>').attr('src', imgUrl('Background', 'U', emblem.background)).appendTo($images)[0];
|
||||||
const bgLower = $("<img>").attr("src", imgUrl("Background", "L", emblem.background)).appendTo($images)[0];
|
const bgLower = $('<img>').attr('src', imgUrl('Background', 'L', emblem.background)).appendTo($images)[0];
|
||||||
const iconUpper = $("<img>").attr("src", imgUrl("Emblem", "U", emblem.icon, emblem.iconColor)).appendTo($images)[0];
|
const iconUpper = $('<img>').attr('src', imgUrl('Emblem', 'U', emblem.icon, emblem.iconColor)).appendTo($images)[0];
|
||||||
const iconLower = $("<img>").attr("src", imgUrl("Emblem", "L", emblem.icon, emblem.iconColor)).appendTo($images)[0];
|
const iconLower = $('<img>').attr('src', imgUrl('Emblem', 'L', emblem.icon, emblem.iconColor)).appendTo($images)[0];
|
||||||
const borderUpper = $("<img>").attr("src", imgUrl("Border", "U", emblem.border, emblem.borderColor)).appendTo($images)[0];
|
const borderUpper = $('<img>').attr('src', imgUrl('Border', 'U', emblem.border, emblem.borderColor)).appendTo($images)[0];
|
||||||
const borderLower = $("<img>").attr("src", imgUrl("Border", "L", emblem.border, emblem.borderColor)).appendTo($images)[0];
|
const borderLower = $('<img>').attr('src', imgUrl('Border', 'L', emblem.border, emblem.borderColor)).appendTo($images)[0];
|
||||||
|
|
||||||
const drawEmblemLayer = (ctx, layer) => {
|
const drawEmblemLayer = (ctx, layer) => {
|
||||||
const [upper, lower] = layer;
|
const [upper, lower] = layer;
|
||||||
|
|
||||||
const w = upper.width / 2;
|
const w = upper.width / 2;
|
||||||
const uh = upper.height;
|
const uh = upper.height;
|
||||||
const lh = lower.height;
|
const lh = lower.height;
|
||||||
|
|
||||||
ctx.drawImage(upper, 0, 0, w, uh, w, 0, w, uh);
|
ctx.drawImage(upper, 0, 0, w, uh, w, 0, w, uh);
|
||||||
ctx.drawImage(lower, 0, 0, w, lh, w, upper.height, w, lh);
|
ctx.drawImage(lower, 0, 0, w, lh, w, upper.height, w, lh);
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.scale(-1, 1);
|
ctx.scale(-1, 1);
|
||||||
ctx.drawImage(upper, 0, 0, w, uh, 0, 0, -w, uh);
|
ctx.drawImage(upper, 0, 0, w, uh, 0, 0, -w, uh);
|
||||||
ctx.drawImage(lower, 0, 0, w, lh, 0, upper.height, -w, lh);
|
ctx.drawImage(lower, 0, 0, w, lh, 0, upper.height, -w, lh);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
};
|
};
|
||||||
|
|
||||||
waitForEmblemImages($emblem).then(() => {
|
waitForEmblemImages($emblem).then(() => {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(0, 0);
|
ctx.moveTo(0, 0);
|
||||||
ctx.lineTo(26, 0);
|
ctx.lineTo(26, 0);
|
||||||
ctx.lineTo(64, 28);
|
ctx.lineTo(64, 28);
|
||||||
ctx.lineTo(102, 0);
|
ctx.lineTo(102, 0);
|
||||||
ctx.lineTo(128, 0);
|
ctx.lineTo(128, 0);
|
||||||
ctx.lineTo(128, 96);
|
ctx.lineTo(128, 96);
|
||||||
ctx.lineTo(0, 96);
|
ctx.lineTo(0, 96);
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
drawEmblemLayer(ctx, [bgUpper, bgLower]);
|
drawEmblemLayer(ctx, [bgUpper, bgLower]);
|
||||||
drawEmblemLayer(ctx, [iconUpper, iconLower]);
|
drawEmblemLayer(ctx, [iconUpper, iconLower]);
|
||||||
drawEmblemLayer(ctx, [borderUpper, borderLower]);
|
drawEmblemLayer(ctx, [borderUpper, borderLower]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createArenaEmblem(teamSize, emblem, el) {
|
function createArenaEmblem(teamSize, emblem, el) {
|
||||||
const $emblem = $(el);
|
const $emblem = $(el);
|
||||||
const canvas = $emblem.find("canvas")[0];
|
const canvas = $emblem.find('canvas')[0];
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
const imgUrl = (teamSize, type, value) => `${handlebarsData.websiteRoot}/img/arena-banners/PVP-Banner${teamSize ? ("-" + teamSize) : ""}${type ? ("-" + type) : ""}${value ? ("-" + value) : ""}.PNG`;
|
const imgUrl = (teamSize, type, value) =>
|
||||||
|
`${handlebarsData.websiteRoot}/img/arena-banners/PVP-Banner${teamSize ? '-' + teamSize : ''}${type ? '-' + type : ''}${
|
||||||
|
value ? '-' + value : ''
|
||||||
|
}.PNG`;
|
||||||
|
|
||||||
const $images = $("<div>").addClass("images").appendTo($emblem);
|
const $images = $('<div>').addClass('images').appendTo($emblem);
|
||||||
const banner = $("<img>").attr("src", imgUrl(teamSize)).appendTo($images)[0];
|
const banner = $('<img>').attr('src', imgUrl(teamSize)).appendTo($images)[0];
|
||||||
const bannerCrop = $("<img>").attr("src", imgUrl(teamSize, "Crop")).appendTo($images)[0];
|
const bannerCrop = $('<img>').attr('src', imgUrl(teamSize, 'Crop')).appendTo($images)[0];
|
||||||
const border = $("<img>").attr("src", imgUrl(teamSize, "Border", emblem.border)).appendTo($images)[0];
|
const border = $('<img>').attr('src', imgUrl(teamSize, 'Border', emblem.border)).appendTo($images)[0];
|
||||||
const icon = $("<img>").attr("src", imgUrl(undefined, "Emblem", emblem.icon)).appendTo($images)[0];
|
const icon = $('<img>').attr('src', imgUrl(undefined, 'Emblem', emblem.icon)).appendTo($images)[0];
|
||||||
|
|
||||||
const colorToHex = (color) => "#" + parseInt(color).toString(16).substring(2);
|
const colorToHex = (color) => '#' + parseInt(color).toString(16).substring(2);
|
||||||
|
|
||||||
waitForEmblemImages($emblem).then(() => {
|
waitForEmblemImages($emblem).then(() => {
|
||||||
const srcH = 224;
|
const srcH = 224;
|
||||||
const h = 128;
|
const h = 128;
|
||||||
const w = (banner.width / srcH) * h;
|
const w = (banner.width / srcH) * h;
|
||||||
const iconW = icon.width * 0.35;
|
const iconW = icon.width * 0.35;
|
||||||
const iconH = icon.height * 0.35;
|
const iconH = icon.height * 0.35;
|
||||||
ctx.drawImage(banner, 0, 0, banner.width, srcH, 0, 0, w, h);
|
ctx.drawImage(banner, 0, 0, banner.width, srcH, 0, 0, w, h);
|
||||||
ctx.drawImage(tintImage(bannerCrop, colorToHex(emblem.background)), 0, 0, banner.width, srcH, 0, 0, w, h);
|
ctx.drawImage(tintImage(bannerCrop, colorToHex(emblem.background)), 0, 0, banner.width, srcH, 0, 0, w, h);
|
||||||
ctx.drawImage(tintImage(border, colorToHex(emblem.borderColor)), 0, 0, border.width, srcH, 0, 0, w, h);
|
ctx.drawImage(tintImage(border, colorToHex(emblem.borderColor)), 0, 0, border.width, srcH, 0, 0, w, h);
|
||||||
ctx.drawImage(tintImage(icon, colorToHex(emblem.iconColor)), w * 0.385 - iconW / 2, h * 0.325 - iconH / 2, iconW, iconH);
|
ctx.drawImage(tintImage(icon, colorToHex(emblem.iconColor)), w * 0.385 - iconW / 2, h * 0.325 - iconH / 2, iconW, iconH);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const tintCanvas = document.createElement("canvas");
|
const tintCanvas = document.createElement('canvas');
|
||||||
const tintContext = tintCanvas.getContext("2d");
|
const tintContext = tintCanvas.getContext('2d');
|
||||||
function tintImage(image, color, opacity = 1.0) {
|
function tintImage(image, color, opacity = 1.0) {
|
||||||
const ctx = tintContext;
|
const ctx = tintContext;
|
||||||
|
|
||||||
ctx.canvas.width = image.width;
|
ctx.canvas.width = image.width;
|
||||||
ctx.canvas.height = image.height;
|
ctx.canvas.height = image.height;
|
||||||
|
|
||||||
// First draw the image to the buffer
|
// First draw the image to the buffer
|
||||||
ctx.drawImage(image, 0, 0);
|
ctx.drawImage(image, 0, 0);
|
||||||
|
|
||||||
// Multiply with a rectangle of the specified color
|
// Multiply with a rectangle of the specified color
|
||||||
ctx.fillStyle = color;
|
ctx.fillStyle = color;
|
||||||
ctx.globalCompositeOperation = "multiply";
|
ctx.globalCompositeOperation = 'multiply';
|
||||||
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||||
|
|
||||||
// Finally, fix masking issues and globalAlpha
|
// Finally, fix masking issues and globalAlpha
|
||||||
ctx.globalAlpha = opacity;
|
ctx.globalAlpha = opacity;
|
||||||
ctx.globalCompositeOperation = "destination-in";
|
ctx.globalCompositeOperation = 'destination-in';
|
||||||
ctx.drawImage(image, 0, 0);
|
ctx.drawImage(image, 0, 0);
|
||||||
|
|
||||||
return ctx.canvas;
|
return ctx.canvas;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
window.parent.postMessage({
|
window.parent.postMessage(
|
||||||
url: window.location.pathname.replace(handlebarsData.websiteRoot, ""),
|
{
|
||||||
}, "*");
|
url: window.location.pathname.replace(handlebarsData.websiteRoot, ''),
|
||||||
|
},
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
|
|
||||||
10203
static/js/viewer.min.js
vendored
10203
static/js/viewer.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue