Commit 181dcaa8 authored by nanahira's avatar nanahira

Merge branch 'database' into mc

parents 02164f83 6057583f
Pipeline #1248 passed with stages
in 10 minutes and 3 seconds
......@@ -2,6 +2,7 @@
jsconfig.json
coffeelint.json
.vscode/
.idea
password.json
config.*.json
......@@ -13,6 +14,7 @@ config.user.bak
/windbot
/decks
/decks_save*
/deck_log
/replays
/node_modules
/ssl
......
......@@ -2,6 +2,7 @@
jsconfig.json
coffeelint.json
.vscode/
.idea
password.json
config.*.json
......@@ -13,6 +14,7 @@ config.user.bak
/windbot
/decks
/decks_save*
/deck_log
/replays
/node_modules
/ssl
......
# Dockerfile for SRVPro
FROM node:12-buster-slim
FROM node:14-buster-slim
RUN npm install -g pm2
# apt
RUN apt update && \
env DEBIAN_FRONTEND=noninteractive apt install -y wget git build-essential libevent-dev libsqlite3-dev mono-complete p7zip-full redis-server python3 liblua5.3-dev && \
env DEBIAN_FRONTEND=noninteractive apt install -y wget git build-essential libevent-dev libsqlite3-dev mono-complete p7zip-full python3 liblua5.3-dev && \
rm -rf /var/lib/apt/lists/*
# srvpro
......
# Dockerfile for SRVPro Lite
FROM node:12-buster-slim
FROM node:14-buster-slim
# apt
RUN apt update && \
......
......@@ -117,7 +117,7 @@ class Replay
@header == null ? false : @header.isTag
@fromFile: (filePath) ->
Replay.fromBuffer fs.readFileSync filePath
Replay.fromBuffer await fs.promises.readFile filePath
@fromBuffer: (buffer) ->
reader = new ReplayReader buffer
......
......@@ -175,8 +175,8 @@
};
}
static fromFile(filePath) {
return Replay.fromBuffer(fs.readFileSync(filePath));
static async fromFile(filePath) {
return Replay.fromBuffer((await fs.promises.readFile(filePath)));
}
static fromBuffer(buffer) {
......
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataManager = void 0;
const moment_1 = __importDefault(require("moment"));
const typeorm_1 = require("typeorm");
const CloudReplay_1 = require("./entities/CloudReplay");
const CloudReplayPlayer_1 = require("./entities/CloudReplayPlayer");
const Ban_1 = require("./entities/Ban");
const RandomDuelBan_1 = require("./entities/RandomDuelBan");
const underscore_1 = __importDefault(require("underscore"));
const DuelLog_1 = require("./entities/DuelLog");
const DuelLogPlayer_1 = require("./entities/DuelLogPlayer");
const User_1 = require("./entities/User");
const RandomDuelScore_1 = require("./entities/RandomDuelScore");
class DataManager {
constructor(config, log) {
this.config = config;
this.ready = false;
this.log = log;
}
async transaction(fun) {
const runner = await this.db.createQueryRunner();
await runner.connect();
await runner.startTransaction();
let result = false;
try {
result = await fun(runner.manager);
}
catch (e) {
result = false;
this.log.warn(`Failed running transaction: ${e.toString()}`);
}
if (result) {
await runner.commitTransaction();
}
else {
await runner.rollbackTransaction();
}
}
async init() {
this.db = await typeorm_1.createConnection({
type: "mysql",
synchronize: true,
entities: ["./data-manager/entities/*.js"],
...this.config
});
this.ready = true;
}
async getCloudReplaysFromKey(key) {
try {
const replays = await this.db.createQueryBuilder(CloudReplay_1.CloudReplay, "replay")
.where("exists (select id from cloud_replay_player where cloud_replay_player.cloudReplayId = replay.id and cloud_replay_player.key = :key)", { key })
.orderBy("replay.date", "DESC")
.limit(10)
.leftJoinAndSelect("replay.players", "player")
.getMany();
return replays;
}
catch (e) {
this.log.warn(`Failed to load replay of ${key}: ${e.toString()}`);
return [];
}
}
async getCloudReplayFromId(id) {
try {
return await this.db.getRepository(CloudReplay_1.CloudReplay).findOne(id, { relations: ["players"] });
}
catch (e) {
this.log.warn(`Failed to load replay R#${id}: ${e.toString()}`);
return null;
}
}
async getRandomCloudReplay() {
try {
const [minQuery, maxQuery] = await Promise.all(["min", "max"].map(minOrMax => this.db.createQueryBuilder()
.select(`${minOrMax}(id)`, "value")
.from(CloudReplay_1.CloudReplay, "replay")
.getRawOne()));
if (!minQuery || !maxQuery) {
return null;
}
const [minId, maxId] = [minQuery, maxQuery].map(query => parseInt(query.value));
const targetId = Math.floor((maxId - minId) * Math.random()) + minId;
return await this.db.createQueryBuilder(CloudReplay_1.CloudReplay, "replay")
.where("replay.id >= :targetId", { targetId })
.orderBy("replay.id", "ASC")
.limit(4) //there may be 4 players
.leftJoinAndSelect("replay.players", "player")
.getOne();
}
catch (e) {
this.log.warn(`Failed to load random replay: ${e.toString()}`);
return null;
}
}
async saveCloudReplay(id, buffer, playerInfos) {
const replay = new CloudReplay_1.CloudReplay();
replay.id = id;
replay.fromBuffer(buffer);
replay.date = moment_1.default().toDate();
const players = playerInfos.map(p => {
const player = CloudReplayPlayer_1.CloudReplayPlayer.fromPlayerInfo(p);
return player;
});
await this.transaction(async (mdb) => {
try {
const nreplay = await mdb.save(replay);
for (let player of players) {
player.cloudReplay = nreplay;
}
await mdb.save(players);
return true;
}
catch (e) {
this.log.warn(`Failed to save replay R#${replay.id}: ${e.toString()}`);
return false;
}
});
}
async checkBan(field, value) {
const banQuery = {};
banQuery[field] = value;
try {
return await this.db.getRepository(Ban_1.Ban).findOne(banQuery);
}
catch (e) {
this.log.warn(`Failed to load ban ${field} ${value}: ${e.toString()}`);
return null;
}
}
async checkBanWithNameAndIP(name, ip) {
try {
return await this.db.getRepository(Ban_1.Ban).findOne({ name, ip });
}
catch (e) {
this.log.warn(`Failed to load ban ${name} ${ip}: ${e.toString()}`);
return null;
}
}
getBan(name, ip) {
const ban = new Ban_1.Ban();
ban.ip = ip;
ban.name = name;
return ban;
}
async banPlayer(ban) {
try {
const repo = this.db.getRepository(Ban_1.Ban);
if (await repo.findOne({
ip: ban.ip,
name: ban.name
})) {
return;
}
return await repo.save(ban);
}
catch (e) {
this.log.warn(`Failed to update ban ${JSON.stringify(ban)}: ${e.toString()}`);
return null;
}
}
async getRandomDuelBan(ip) {
const repo = this.db.getRepository(RandomDuelBan_1.RandomDuelBan);
try {
const ban = await repo.findOne(ip);
//console.log(ip, ban);
return ban;
}
catch (e) {
this.log.warn(`Failed to fetch random duel ban ${ip}: ${e.toString()}`);
return null;
}
}
async updateRandomDuelBan(ban) {
const repo = this.db.getRepository(RandomDuelBan_1.RandomDuelBan);
try {
await repo.save(ban);
}
catch (e) {
this.log.warn(`Failed to update random duel ban ${ban.ip}: ${e.toString()}`);
}
}
async randomDuelBanPlayer(ip, reason, countadd) {
const count = countadd || 1;
const repo = this.db.getRepository(RandomDuelBan_1.RandomDuelBan);
try {
let ban = await repo.findOne(ip);
if (ban) {
ban.count += count;
const banTime = ban.count > 3 ? Math.pow(2, ban.count - 3) * 2 : 0;
const banDate = moment_1.default(ban.time);
if (moment_1.default().isAfter(banDate)) {
ban.time = moment_1.default().add(banTime, 'm').toDate();
}
else {
ban.time = moment_1.default(banDate).add(banTime, 'm').toDate();
}
if (!underscore_1.default.contains(ban.reasons, reason)) {
ban.reasons.push(reason);
}
ban.needTip = 1;
}
else {
ban = new RandomDuelBan_1.RandomDuelBan();
ban.ip = ip;
ban.time = moment_1.default().toDate();
ban.count = count;
ban.reasons = [reason];
ban.needTip = 1;
}
return await repo.save(ban);
}
catch (e) {
this.log.warn(`Failed to update random duel ban ${ip}: ${e.toString()}`);
return null;
}
}
async getAllDuelLogs() {
const repo = this.db.getRepository(DuelLog_1.DuelLog);
try {
const allDuelLogs = await repo.find({ relations: ["players"] });
return allDuelLogs;
}
catch (e) {
this.log.warn(`Failed to fetch duel logs: ${e.toString()}`);
return [];
}
}
async getDuelLogFromId(id) {
const repo = this.db.getRepository(DuelLog_1.DuelLog);
try {
const duelLog = await repo.findOne(id, { relations: ["players"] });
return duelLog;
}
catch (e) {
this.log.warn(`Failed to fetch duel logs: ${e.toString()}`);
return null;
}
}
async getDuelLogFromRecoverSearch(realName) {
const repo = this.db.getRepository(DuelLog_1.DuelLog);
try {
const duelLogs = await repo.createQueryBuilder("duelLog")
.where("startDeckBuffer is not null and currentDeckBuffer is not null and roomMode != 2 and exists (select id from duel_log_player where duel_log_player.duelLogId = duelLog.id and duel_log_player.realName = :realName)", { realName })
.orderBy("duelLog.id", "DESC")
.limit(10)
.leftJoinAndSelect("duelLog.players", "player")
.getMany();
return duelLogs;
}
catch (e) {
this.log.warn(`Failed to fetch duel logs: ${e.toString()}`);
return null;
}
}
async getDuelLogJSON(tournamentModeSettings) {
const allDuelLogs = await this.getAllDuelLogs();
return allDuelLogs.map(duelLog => duelLog.getViewJSON(tournamentModeSettings));
}
async getAllReplayFilenames() {
const allDuelLogs = await this.getAllDuelLogs();
return allDuelLogs.map(duelLog => duelLog.replayFileName);
}
async clearDuelLog() {
const runner = this.db.createQueryRunner();
try {
await runner.connect();
await runner.startTransaction();
await runner.query("SET FOREIGN_KEY_CHECKS = 0; ");
await runner.clearTable("duel_log_player");
await runner.clearTable("duel_log");
await runner.query("SET FOREIGN_KEY_CHECKS = 1; ");
await runner.commitTransaction();
}
catch (e) {
await runner.rollbackTransaction();
this.log.warn(`Failed to clear duel logs: ${e.toString()}`);
}
}
async saveDuelLog(name, roomId, cloudReplayId, replayFilename, roomMode, duelCount, playerInfos) {
const duelLog = new DuelLog_1.DuelLog();
duelLog.name = name;
duelLog.time = moment_1.default().toDate();
duelLog.roomId = roomId;
duelLog.cloudReplayId = cloudReplayId;
duelLog.replayFileName = replayFilename;
duelLog.roomMode = roomMode;
duelLog.duelCount = duelCount;
const players = playerInfos.map(p => DuelLogPlayer_1.DuelLogPlayer.fromDuelLogPlayerInfo(p));
await this.transaction(async (mdb) => {
try {
const savedDuelLog = await mdb.save(duelLog);
for (let player of players) {
player.duelLog = savedDuelLog;
}
await mdb.save(players);
return true;
}
catch (e) {
this.log.warn(`Failed to save duel log ${name}: ${e.toString()}`);
return false;
}
});
}
async getUser(key) {
const repo = this.db.getRepository(User_1.User);
try {
const user = await repo.findOne(key);
return user;
}
catch (e) {
this.log.warn(`Failed to fetch user: ${e.toString()}`);
return null;
}
}
async getOrCreateUser(key) {
const user = await this.getUser(key);
if (user) {
return user;
}
const newUser = new User_1.User();
newUser.key = key;
return await this.saveUser(newUser);
}
async saveUser(user) {
const repo = this.db.getRepository(User_1.User);
try {
return await repo.save(user);
}
catch (e) {
this.log.warn(`Failed to save user: ${e.toString()}`);
return null;
}
}
async getUserChatColor(key) {
const user = await this.getUser(key);
return user ? user.chatColor : null;
}
async setUserChatColor(key, color) {
let user = await this.getOrCreateUser(key);
user.chatColor = color;
return await this.saveUser(user);
}
async migrateChatColors(data) {
await this.transaction(async (mdb) => {
try {
const users = [];
for (let key in data) {
const chatColor = data[key];
let user = await mdb.findOne(User_1.User, key);
if (!user) {
user = new User_1.User();
user.key = key;
}
user.chatColor = chatColor;
users.push(user);
}
await mdb.save(users);
return true;
}
catch (e) {
this.log.warn(`Failed to migrate chat color data: ${e.toString()}`);
return false;
}
});
}
async getRandomDuelScore(name) {
const repo = this.db.getRepository(RandomDuelScore_1.RandomDuelScore);
try {
const score = await repo.findOne(name);
return score;
}
catch (e) {
this.log.warn(`Failed to fetch random duel score ${name}: ${e.toString()}`);
return null;
}
}
async saveRandomDuelScore(score) {
const repo = this.db.getRepository(RandomDuelScore_1.RandomDuelScore);
try {
return await repo.save(score);
}
catch (e) {
this.log.warn(`Failed to save random duel score: ${e.toString()}`);
return null;
}
}
async getOrCreateRandomDuelScore(name) {
const score = await this.getRandomDuelScore(name);
if (score) {
return score;
}
const newScore = new RandomDuelScore_1.RandomDuelScore();
newScore.name = name;
return await this.saveRandomDuelScore(newScore);
}
async getRandomDuelScoreDisplay(name) {
const score = await this.getRandomDuelScore(name);
if (!score) {
return `${name.split("$")[0]} \${random_score_blank}`;
}
return score.getScoreText();
}
async randomDuelPlayerWin(name) {
const score = await this.getOrCreateRandomDuelScore(name);
score.win();
await this.saveRandomDuelScore(score);
}
async randomDuelPlayerLose(name) {
const score = await this.getOrCreateRandomDuelScore(name);
score.lose();
await this.saveRandomDuelScore(score);
}
async randomDuelPlayerFlee(name) {
const score = await this.getOrCreateRandomDuelScore(name);
score.flee();
await this.saveRandomDuelScore(score);
}
async getRandomScoreTop10() {
try {
const scores = await this.db.getRepository(RandomDuelScore_1.RandomDuelScore)
.createQueryBuilder("score")
.orderBy("score.win", "DESC")
.addOrderBy("score.lose", "ASC")
.addOrderBy("score.flee", "ASC")
.limit(10)
.getMany();
return scores.map(score => [score.getDisplayName(), {
win: score.winCount,
lose: score.loseCount,
flee: score.fleeCount,
combo: score.winCombo
}]);
}
catch (e) {
this.log.warn(`Failed to fetch random duel score ${name}: ${e.toString()}`);
return [];
}
}
}
exports.DataManager = DataManager;
//# sourceMappingURL=DataManager.js.map
\ No newline at end of file
import moment from "moment";
import bunyan from "bunyan";
import {Connection, ConnectionOptions, createConnection, EntityManager} from "typeorm";
import {CloudReplay} from "./entities/CloudReplay";
import {CloudReplayPlayer} from "./entities/CloudReplayPlayer";
import {Ban} from "./entities/Ban";
import {RandomDuelBan} from "./entities/RandomDuelBan";
import _ from "underscore";
import {DuelLog} from "./entities/DuelLog";
import {Deck} from "./DeckEncoder";
import {DuelLogPlayer} from "./entities/DuelLogPlayer";
import {User} from "./entities/User";
import {RandomDuelScore} from "./entities/RandomDuelScore";
interface BasePlayerInfo {
name: string;
pos: number
}
export interface CloudReplayPlayerInfo extends BasePlayerInfo {
key: string;
}
export interface DuelLogPlayerInfo extends BasePlayerInfo {
realName: string;
startDeckBuffer: Buffer;
deck: Deck;
isFirst: boolean;
winner: boolean;
ip: string;
score: number;
lp: number;
cardCount: number;
}
export class DataManager {
config: ConnectionOptions;
ready: boolean;
db: Connection;
log: bunyan;
constructor(config: ConnectionOptions, log: bunyan) {
this.config = config;
this.ready = false;
this.log = log;
}
private async transaction(fun: (mdb: EntityManager) => Promise<boolean>) {
const runner = await this.db.createQueryRunner();
await runner.connect();
await runner.startTransaction();
let result = false;
try {
result = await fun(runner.manager);
} catch(e) {
result = false;
this.log.warn(`Failed running transaction: ${e.toString()}`)
}
if(result) {
await runner.commitTransaction();
} else {
await runner.rollbackTransaction();
}
}
async init() {
this.db = await createConnection({
type: "mysql",
synchronize: true,
entities: ["./data-manager/entities/*.js"],
...this.config
});
this.ready = true;
}
async getCloudReplaysFromKey(key: string) {
try {
const replays = await this.db.createQueryBuilder(CloudReplay, "replay")
.where("exists (select id from cloud_replay_player where cloud_replay_player.cloudReplayId = replay.id and cloud_replay_player.key = :key)", { key })
.orderBy("replay.date", "DESC")
.limit(10)
.leftJoinAndSelect("replay.players", "player")
.getMany();
return replays;
} catch (e) {
this.log.warn(`Failed to load replay of ${key}: ${e.toString()}`);
return [];
}
}
async getCloudReplayFromId(id: number) {
try {
return await this.db.getRepository(CloudReplay).findOne(id, { relations: ["players"] });
} catch (e) {
this.log.warn(`Failed to load replay R#${id}: ${e.toString()}`);
return null;
}
}
async getRandomCloudReplay() {
try {
const [minQuery, maxQuery] = await Promise.all(["min", "max"].map(minOrMax => this.db.createQueryBuilder()
.select(`${minOrMax}(id)`, "value")
.from(CloudReplay, "replay")
.getRawOne()
));
if(!minQuery || !maxQuery) {
return null;
}
const [minId, maxId] = [minQuery, maxQuery].map(query => parseInt(query.value));
const targetId = Math.floor((maxId - minId) * Math.random()) + minId;
return await this.db.createQueryBuilder(CloudReplay, "replay")
.where("replay.id >= :targetId", {targetId})
.orderBy("replay.id", "ASC")
.limit(4) //there may be 4 players
.leftJoinAndSelect("replay.players", "player")
.getOne();
} catch (e) {
this.log.warn(`Failed to load random replay: ${e.toString()}`);
return null;
}
}
async saveCloudReplay(id: number, buffer: Buffer, playerInfos: CloudReplayPlayerInfo[]) {
const replay = new CloudReplay();
replay.id = id;
replay.fromBuffer(buffer);
replay.date = moment().toDate();
const players = playerInfos.map(p => {
const player = CloudReplayPlayer.fromPlayerInfo(p);
return player;
});
await this.transaction(async (mdb) => {
try {
const nreplay = await mdb.save(replay);
for (let player of players) {
player.cloudReplay = nreplay;
}
await mdb.save(players);
return true;
} catch (e) {
this.log.warn(`Failed to save replay R#${replay.id}: ${e.toString()}`);
return false;
}
});
}
async checkBan(field: string, value: string) {
const banQuery: any = {};
banQuery[field] = value;
try {
return await this.db.getRepository(Ban).findOne(banQuery);
} catch (e) {
this.log.warn(`Failed to load ban ${field} ${value}: ${e.toString()}`);
return null;
}
}
async checkBanWithNameAndIP(name: string, ip: string) {
try {
return await this.db.getRepository(Ban).findOne({ name, ip });
} catch (e) {
this.log.warn(`Failed to load ban ${name} ${ip}: ${e.toString()}`);
return null;
}
}
getBan(name: string, ip: string) {
const ban = new Ban();
ban.ip = ip;
ban.name = name;
return ban;
}
async banPlayer(ban: Ban) {
try {
const repo = this.db.getRepository(Ban);
if (await repo.findOne({
ip: ban.ip,
name: ban.name
})) {
return;
}
return await repo.save(ban);
} catch (e) {
this.log.warn(`Failed to update ban ${JSON.stringify(ban)}: ${e.toString()}`);
return null;
}
}
async getRandomDuelBan(ip: string) {
const repo = this.db.getRepository(RandomDuelBan);
try {
const ban = await repo.findOne(ip);
//console.log(ip, ban);
return ban;
} catch (e) {
this.log.warn(`Failed to fetch random duel ban ${ip}: ${e.toString()}`);
return null;
}
}
async updateRandomDuelBan(ban: RandomDuelBan) {
const repo = this.db.getRepository(RandomDuelBan);
try {
await repo.save(ban);
} catch (e) {
this.log.warn(`Failed to update random duel ban ${ban.ip}: ${e.toString()}`);
}
}
async randomDuelBanPlayer(ip: string, reason: string, countadd?: number) {
const count = countadd || 1;
const repo = this.db.getRepository(RandomDuelBan);
try {
let ban = await repo.findOne(ip);
if(ban) {
ban.count += count;
const banTime = ban.count > 3 ? Math.pow(2, ban.count - 3) * 2 : 0;
const banDate = moment(ban.time);
if(moment().isAfter(banDate)) {
ban.time = moment().add(banTime, 'm').toDate();
} else {
ban.time = moment(banDate).add(banTime, 'm').toDate();
}
if(!_.contains(ban.reasons, reason)) {
ban.reasons.push(reason);
}
ban.needTip = 1;
} else {
ban = new RandomDuelBan();
ban.ip = ip;
ban.time = moment().toDate();
ban.count = count;
ban.reasons = [reason];
ban.needTip = 1;
}
return await repo.save(ban);
} catch (e) {
this.log.warn(`Failed to update random duel ban ${ip}: ${e.toString()}`);
return null;
}
}
async getAllDuelLogs() {
const repo = this.db.getRepository(DuelLog);
try {
const allDuelLogs = await repo.find({relations: ["players"]});
return allDuelLogs;
} catch (e) {
this.log.warn(`Failed to fetch duel logs: ${e.toString()}`);
return [];
}
}
async getDuelLogFromId(id: number) {
const repo = this.db.getRepository(DuelLog);
try {
const duelLog = await repo.findOne(id, {relations: ["players"]});
return duelLog;
} catch (e) {
this.log.warn(`Failed to fetch duel logs: ${e.toString()}`);
return null;
}
}
async getDuelLogFromRecoverSearch(realName: string) {
const repo = this.db.getRepository(DuelLog);
try {
const duelLogs = await repo.createQueryBuilder("duelLog")
.where("startDeckBuffer is not null and currentDeckBuffer is not null and roomMode != 2 and exists (select id from duel_log_player where duel_log_player.duelLogId = duelLog.id and duel_log_player.realName = :realName)", { realName })
.orderBy("duelLog.id", "DESC")
.limit(10)
.leftJoinAndSelect("duelLog.players", "player")
.getMany();
return duelLogs;
} catch (e) {
this.log.warn(`Failed to fetch duel logs: ${e.toString()}`);
return null;
}
}
async getDuelLogJSON(tournamentModeSettings: any) {
const allDuelLogs = await this.getAllDuelLogs();
return allDuelLogs.map(duelLog => duelLog.getViewJSON(tournamentModeSettings));
}
async getAllReplayFilenames() {
const allDuelLogs = await this.getAllDuelLogs();
return allDuelLogs.map(duelLog => duelLog.replayFileName);
}
async clearDuelLog() {
const runner = this.db.createQueryRunner();
try {
await runner.connect();
await runner.startTransaction();
await runner.query("SET FOREIGN_KEY_CHECKS = 0; ");
await runner.clearTable("duel_log_player");
await runner.clearTable("duel_log");
await runner.query("SET FOREIGN_KEY_CHECKS = 1; ");
await runner.commitTransaction();
} catch (e) {
await runner.rollbackTransaction();
this.log.warn(`Failed to clear duel logs: ${e.toString()}`);
}
}
async saveDuelLog(name: string, roomId: number, cloudReplayId: number, replayFilename: string, roomMode: number, duelCount: number, playerInfos: DuelLogPlayerInfo[]) {
const duelLog = new DuelLog();
duelLog.name = name;
duelLog.time = moment().toDate();
duelLog.roomId = roomId;
duelLog.cloudReplayId = cloudReplayId;
duelLog.replayFileName = replayFilename;
duelLog.roomMode = roomMode;
duelLog.duelCount = duelCount;
const players = playerInfos.map(p => DuelLogPlayer.fromDuelLogPlayerInfo(p));
await this.transaction(async (mdb) => {
try {
const savedDuelLog = await mdb.save(duelLog);
for (let player of players) {
player.duelLog = savedDuelLog;
}
await mdb.save(players);
return true;
} catch (e) {
this.log.warn(`Failed to save duel log ${name}: ${e.toString()}`);
return false;
}
});
}
async getUser(key: string) {
const repo = this.db.getRepository(User);
try {
const user = await repo.findOne(key);
return user;
} catch (e) {
this.log.warn(`Failed to fetch user: ${e.toString()}`);
return null;
}
}
async getOrCreateUser(key: string) {
const user = await this.getUser(key);
if(user) {
return user;
}
const newUser = new User();
newUser.key = key;
return await this.saveUser(newUser);
}
async saveUser(user: User) {
const repo = this.db.getRepository(User);
try {
return await repo.save(user);
} catch (e) {
this.log.warn(`Failed to save user: ${e.toString()}`);
return null;
}
}
async getUserChatColor(key: string) {
const user = await this.getUser(key);
return user ? user.chatColor : null;
}
async setUserChatColor(key: string, color: string) {
let user = await this.getOrCreateUser(key);
user.chatColor = color;
return await this.saveUser(user);
}
async migrateChatColors(data: any) {
await this.transaction(async (mdb) => {
try {
const users: User[] = [];
for(let key in data) {
const chatColor: string = data[key];
let user = await mdb.findOne(User, key);
if(!user) {
user = new User();
user.key = key;
}
user.chatColor = chatColor;
users.push(user);
}
await mdb.save(users);
return true;
} catch (e) {
this.log.warn(`Failed to migrate chat color data: ${e.toString()}`);
return false;
}
});
}
async getRandomDuelScore(name: string) {
const repo = this.db.getRepository(RandomDuelScore);
try {
const score = await repo.findOne(name);
return score;
} catch (e) {
this.log.warn(`Failed to fetch random duel score ${name}: ${e.toString()}`);
return null;
}
}
async saveRandomDuelScore(score: RandomDuelScore) {
const repo = this.db.getRepository(RandomDuelScore);
try {
return await repo.save(score);
} catch (e) {
this.log.warn(`Failed to save random duel score: ${e.toString()}`);
return null;
}
}
async getOrCreateRandomDuelScore(name: string) {
const score = await this.getRandomDuelScore(name);
if(score) {
return score;
}
const newScore = new RandomDuelScore();
newScore.name = name;
return await this.saveRandomDuelScore(newScore);
}
async getRandomDuelScoreDisplay(name: string) {
const score = await this.getRandomDuelScore(name);
if(!score) {
return `${name.split("$")[0]} \${random_score_blank}`;
}
return score.getScoreText();
}
async randomDuelPlayerWin(name: string) {
const score = await this.getOrCreateRandomDuelScore(name);
score.win();
await this.saveRandomDuelScore(score);
}
async randomDuelPlayerLose(name: string) {
const score = await this.getOrCreateRandomDuelScore(name);
score.lose();
await this.saveRandomDuelScore(score);
}
async randomDuelPlayerFlee(name: string) {
const score = await this.getOrCreateRandomDuelScore(name);
score.flee();
await this.saveRandomDuelScore(score);
}
async getRandomScoreTop10() {
try {
const scores = await this.db.getRepository(RandomDuelScore)
.createQueryBuilder("score")
.orderBy("score.win", "DESC")
.addOrderBy("score.lose", "ASC")
.addOrderBy("score.flee", "ASC")
.limit(10)
.getMany();
return scores.map(score => [score.getDisplayName(), {
win: score.winCount,
lose: score.loseCount,
flee: score.fleeCount,
combo: score.winCombo
}]);
} catch (e) {
this.log.warn(`Failed to fetch random duel score ${name}: ${e.toString()}`);
return [];
}
}
}
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.decodeDeck = exports.encodeDeck = void 0;
const assert_1 = __importDefault(require("assert"));
function encodeDeck(deck) {
let pointer = 0;
const bufferSize = (2 + deck.main.length + deck.side.length) * 4;
const buffer = Buffer.allocUnsafe(bufferSize);
buffer.writeInt32LE(deck.main.length, pointer);
pointer += 4;
buffer.writeInt32LE(deck.side.length, pointer);
pointer += 4;
for (let cardCode of deck.main.concat(deck.side)) {
buffer.writeInt32LE(cardCode, pointer);
pointer += 4;
}
assert_1.default(pointer === bufferSize, `Invalid buffer size. Expected: ${bufferSize}. Got: ${pointer}`);
return buffer;
}
exports.encodeDeck = encodeDeck;
function decodeDeck(buffer) {
let pointer = 0;
const mainLength = buffer.readInt32LE(pointer);
pointer += 4;
const sideLength = buffer.readInt32LE(pointer);
pointer += 4;
const correctBufferLength = (2 + mainLength + sideLength) * 4;
assert_1.default(buffer.length >= (2 + mainLength + sideLength) * 4, `Invalid buffer size. Expected: ${correctBufferLength}. Got: ${buffer.length}`);
const main = [];
const side = [];
for (let i = 0; i < mainLength; ++i) {
main.push(buffer.readInt32LE(pointer));
pointer += 4;
}
for (let i = 0; i < sideLength; ++i) {
side.push(buffer.readInt32LE(pointer));
pointer += 4;
}
return { main, side };
}
exports.decodeDeck = decodeDeck;
//# sourceMappingURL=DeckEncoder.js.map
\ No newline at end of file
import assert from "assert";
export interface Deck {
main: number[];
side: number[];
}
export function encodeDeck(deck: Deck) {
let pointer = 0;
const bufferSize = (2 + deck.main.length + deck.side.length) * 4;
const buffer = Buffer.allocUnsafe(bufferSize);
buffer.writeInt32LE(deck.main.length, pointer);
pointer += 4;
buffer.writeInt32LE(deck.side.length, pointer);
pointer += 4;
for(let cardCode of deck.main.concat(deck.side)) {
buffer.writeInt32LE(cardCode, pointer);
pointer += 4;
}
assert(pointer === bufferSize, `Invalid buffer size. Expected: ${bufferSize}. Got: ${pointer}`);
return buffer;
}
export function decodeDeck(buffer: Buffer): Deck {
let pointer = 0;
const mainLength = buffer.readInt32LE(pointer);
pointer += 4;
const sideLength = buffer.readInt32LE(pointer);
pointer += 4;
const correctBufferLength = (2 + mainLength + sideLength) * 4;
assert(buffer.length >= (2 + mainLength + sideLength) * 4, `Invalid buffer size. Expected: ${correctBufferLength}. Got: ${buffer.length}`);
const main: number[] = [];
const side: number[] = [];
for(let i = 0; i < mainLength; ++i) {
main.push(buffer.readInt32LE(pointer));
pointer += 4;
}
for(let i = 0; i < sideLength; ++i) {
side.push(buffer.readInt32LE(pointer));
pointer += 4;
}
return {main, side};
}
\ No newline at end of file
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Ban = void 0;
const typeorm_1 = require("typeorm");
let Ban = class Ban {
};
__decorate([
typeorm_1.PrimaryGeneratedColumn({ unsigned: true, type: "bigint" }),
__metadata("design:type", Number)
], Ban.prototype, "id", void 0);
__decorate([
typeorm_1.Index(),
typeorm_1.Column({ type: "varchar", length: 64, nullable: true }),
__metadata("design:type", String)
], Ban.prototype, "ip", void 0);
__decorate([
typeorm_1.Index(),
typeorm_1.Column({ type: "varchar", length: 20, nullable: true }),
__metadata("design:type", String)
], Ban.prototype, "name", void 0);
Ban = __decorate([
typeorm_1.Entity(),
typeorm_1.Unique(["ip", "name"])
], Ban);
exports.Ban = Ban;
//# sourceMappingURL=Ban.js.map
\ No newline at end of file
import {Column, Entity, Index, PrimaryGeneratedColumn, Unique} from "typeorm";
@Entity()
@Unique(["ip", "name"])
export class Ban {
@PrimaryGeneratedColumn({ unsigned: true, type: "bigint" })
id: number;
@Index()
@Column({ type: "varchar", length: 64, nullable: true })
ip: string;
@Index()
@Column({ type: "varchar", length: 20, nullable: true })
name: string;
}
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BasePlayer = void 0;
const typeorm_1 = require("typeorm");
class BasePlayer {
}
__decorate([
typeorm_1.PrimaryGeneratedColumn({ unsigned: true, type: "bigint" }),
__metadata("design:type", Number)
], BasePlayer.prototype, "id", void 0);
__decorate([
typeorm_1.Column({ type: "varchar", length: 20 }),
__metadata("design:type", String)
], BasePlayer.prototype, "name", void 0);
__decorate([
typeorm_1.Column({ type: "tinyint" }),
__metadata("design:type", Number)
], BasePlayer.prototype, "pos", void 0);
exports.BasePlayer = BasePlayer;
//# sourceMappingURL=BasePlayer.js.map
\ No newline at end of file
import {Column, PrimaryGeneratedColumn} from "typeorm";
export abstract class BasePlayer {
@PrimaryGeneratedColumn({unsigned: true, type: "bigint"})
id: number;
@Column({ type: "varchar", length: 20 })
name: string;
@Column({ type: "tinyint" })
pos: number;
}
\ No newline at end of file
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CloudReplay = void 0;
const typeorm_1 = require("typeorm");
const CloudReplayPlayer_1 = require("./CloudReplayPlayer");
const underscore_1 = __importDefault(require("underscore"));
const moment_1 = __importDefault(require("moment"));
let CloudReplay = class CloudReplay {
fromBuffer(buffer) {
this.data = buffer.toString("base64");
}
toBuffer() {
return Buffer.from(this.data, "base64");
}
getDateString() {
return moment_1.default(this.date).format('YYYY-MM-DD HH:mm:ss');
}
getPlayerNamesString() {
const playerInfos = underscore_1.default.clone(this.players);
playerInfos.sort((p1, p2) => p1.pos - p2.pos);
return playerInfos[0].name + (playerInfos[2] ? "+" + playerInfos[2].name : "") + " VS " + (playerInfos[1] ? playerInfos[1].name : "AI") + (playerInfos[3] ? "+" + playerInfos[3].name : "");
}
getDisplayString() {
return `R#${this.id} ${this.getPlayerNamesString()} ${this.getDateString()}`;
}
};
__decorate([
typeorm_1.PrimaryColumn({ unsigned: true, type: "bigint" }),
__metadata("design:type", Number)
], CloudReplay.prototype, "id", void 0);
__decorate([
typeorm_1.Column({ type: "text" }),
__metadata("design:type", String)
], CloudReplay.prototype, "data", void 0);
__decorate([
typeorm_1.Column({ type: "datetime" }),
__metadata("design:type", Date)
], CloudReplay.prototype, "date", void 0);
__decorate([
typeorm_1.OneToMany(() => CloudReplayPlayer_1.CloudReplayPlayer, player => player.cloudReplay),
__metadata("design:type", Array)
], CloudReplay.prototype, "players", void 0);
CloudReplay = __decorate([
typeorm_1.Entity()
], CloudReplay);
exports.CloudReplay = CloudReplay;
//# sourceMappingURL=CloudReplay.js.map
\ No newline at end of file
import {Column, Entity, OneToMany, PrimaryColumn} from "typeorm";
import {CloudReplayPlayer} from "./CloudReplayPlayer";
import _ from "underscore";
import moment from "moment";
@Entity()
export class CloudReplay {
@PrimaryColumn({ unsigned: true, type: "bigint" })
id: number;
@Column({ type: "text" })
data: string;
fromBuffer(buffer: Buffer) {
this.data = buffer.toString("base64");
}
toBuffer() {
return Buffer.from(this.data, "base64");
}
@Column({ type: "datetime" })
date: Date;
getDateString() {
return moment(this.date).format('YYYY-MM-DD HH:mm:ss')
}
@OneToMany(() => CloudReplayPlayer, player => player.cloudReplay)
players: CloudReplayPlayer[];
getPlayerNamesString() {
const playerInfos = _.clone(this.players);
playerInfos.sort((p1, p2) => p1.pos - p2.pos);
return playerInfos[0].name + (playerInfos[2] ? "+" + playerInfos[2].name : "") + " VS " + (playerInfos[1] ? playerInfos[1].name : "AI") + (playerInfos[3] ? "+" + playerInfos[3].name : "");
}
getDisplayString() {
return `R#${this.id} ${this.getPlayerNamesString()} ${this.getDateString()}`;
}
}
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var CloudReplayPlayer_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CloudReplayPlayer = void 0;
const typeorm_1 = require("typeorm");
const CloudReplay_1 = require("./CloudReplay");
const BasePlayer_1 = require("./BasePlayer");
let CloudReplayPlayer = CloudReplayPlayer_1 = class CloudReplayPlayer extends BasePlayer_1.BasePlayer {
static fromPlayerInfo(info) {
const p = new CloudReplayPlayer_1();
p.key = info.key;
p.name = info.name;
p.pos = info.pos;
return p;
}
};
__decorate([
typeorm_1.Index(),
typeorm_1.Column({ type: "varchar", length: 128 }),
__metadata("design:type", String)
], CloudReplayPlayer.prototype, "key", void 0);
__decorate([
typeorm_1.ManyToOne(() => CloudReplay_1.CloudReplay, replay => replay.players),
__metadata("design:type", CloudReplay_1.CloudReplay)
], CloudReplayPlayer.prototype, "cloudReplay", void 0);
CloudReplayPlayer = CloudReplayPlayer_1 = __decorate([
typeorm_1.Entity()
], CloudReplayPlayer);
exports.CloudReplayPlayer = CloudReplayPlayer;
//# sourceMappingURL=CloudReplayPlayer.js.map
\ No newline at end of file
import {Column, Entity, Index, ManyToOne} from "typeorm";
import {CloudReplayPlayerInfo} from "../DataManager";
import {CloudReplay} from "./CloudReplay";
import {BasePlayer} from "./BasePlayer";
@Entity()
export class CloudReplayPlayer extends BasePlayer {
@Index()
@Column({ type: "varchar", length: 128 })
key: string;
@ManyToOne(() => CloudReplay, replay => replay.players)
cloudReplay: CloudReplay;
static fromPlayerInfo(info: CloudReplayPlayerInfo) {
const p = new CloudReplayPlayer();
p.key = info.key;
p.name = info.name;
p.pos = info.pos;
return p;
}
}
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DuelLog = void 0;
const typeorm_1 = require("typeorm");
const DuelLogPlayer_1 = require("./DuelLogPlayer");
const moment_1 = __importDefault(require("moment"));
const underscore_1 = __importDefault(require("underscore"));
let DuelLog = class DuelLog {
getViewString() {
const viewPlayers = underscore_1.default.clone(this.players);
viewPlayers.sort((p1, p2) => p1.pos - p2.pos);
const playerString = viewPlayers[0].realName.split("$")[0] + (viewPlayers[2] ? "+" + viewPlayers[2].realName.split("$")[0] : "") + " VS " + (viewPlayers[1] ? viewPlayers[1].realName.split("$")[0] : "AI") + (viewPlayers[3] ? "+" + viewPlayers[3].realName.split("$")[0] : "");
return `<${this.id}> ${playerString} ${moment_1.default(this.time).format("YYYY-MM-DD HH-mm-ss")}`;
}
getViewJSON(tournamentModeSettings) {
const data = {
id: this.id,
time: moment_1.default(this.time).format("YYYY-MM-DD HH:mm:ss"),
name: this.name + (tournamentModeSettings.show_info ? " (Duel:" + this.duelCount + ")" : ""),
roomid: this.roomId,
cloud_replay_id: "R#" + this.cloudReplayId,
replay_filename: this.replayFileName,
roommode: this.roomMode,
players: this.players.map(player => {
return {
pos: player.pos,
is_first: player.isFirst === 1,
name: player.name + (tournamentModeSettings.show_ip ? " (IP: " + player.ip.slice(7) + ")" : "") + (tournamentModeSettings.show_info && !(this.roomMode === 2 && player.pos % 2 > 0) ? " (Score:" + player.score + " LP:" + (player.lp != null ? player.lp : "???") + (this.roomMode !== 2 ? " Cards:" + (player.cardCount != null ? player.cardCount : "???") : "") + ")" : ""),
winner: player.winner === 1
};
})
};
return data;
}
};
__decorate([
typeorm_1.PrimaryGeneratedColumn({ unsigned: true, type: "bigint" }),
__metadata("design:type", Number)
], DuelLog.prototype, "id", void 0);
__decorate([
typeorm_1.Column("datetime"),
__metadata("design:type", Date)
], DuelLog.prototype, "time", void 0);
__decorate([
typeorm_1.Index(),
typeorm_1.Column({ type: "varchar", length: 20 }),
__metadata("design:type", String)
], DuelLog.prototype, "name", void 0);
__decorate([
typeorm_1.Column("int"),
__metadata("design:type", Number)
], DuelLog.prototype, "roomId", void 0);
__decorate([
typeorm_1.Column("bigint"),
__metadata("design:type", Number)
], DuelLog.prototype, "cloudReplayId", void 0);
__decorate([
typeorm_1.Column({ type: "varchar", length: 256 }),
__metadata("design:type", String)
], DuelLog.prototype, "replayFileName", void 0);
__decorate([
typeorm_1.Column("tinyint", { unsigned: true }),
__metadata("design:type", Number)
], DuelLog.prototype, "roomMode", void 0);
__decorate([
typeorm_1.Column("tinyint", { unsigned: true }),
__metadata("design:type", Number)
], DuelLog.prototype, "duelCount", void 0);
__decorate([
typeorm_1.OneToMany(() => DuelLogPlayer_1.DuelLogPlayer, player => player.duelLog),
__metadata("design:type", Array)
], DuelLog.prototype, "players", void 0);
DuelLog = __decorate([
typeorm_1.Entity()
], DuelLog);
exports.DuelLog = DuelLog;
//# sourceMappingURL=DuelLog.js.map
\ No newline at end of file
import {Column, Entity, Index, OneToMany, PrimaryGeneratedColumn} from "typeorm";
import {DuelLogPlayer} from "./DuelLogPlayer";
import moment from "moment";
import _ from "underscore";
@Entity()
export class DuelLog {
@PrimaryGeneratedColumn({unsigned: true, type: "bigint"})
id: number;
@Column("datetime")
time: Date;
@Index()
@Column({type: "varchar", length: 20})
name: string;
@Column("int")
roomId: number;
@Column("bigint")
cloudReplayId: number; // not very needed to become a relation
@Column({type: "varchar", length: 256})
replayFileName: string;
@Column("tinyint", {unsigned: true})
roomMode: number;
@Column("tinyint", {unsigned: true})
duelCount: number;
@OneToMany(() => DuelLogPlayer, player => player.duelLog)
players: DuelLogPlayer[];
getViewString() {
const viewPlayers = _.clone(this.players);
viewPlayers.sort((p1, p2) => p1.pos - p2.pos);
const playerString = viewPlayers[0].realName.split("$")[0] + (viewPlayers[2] ? "+" + viewPlayers[2].realName.split("$")[0] : "") + " VS " + (viewPlayers[1] ? viewPlayers[1].realName.split("$")[0] : "AI") + (viewPlayers[3] ? "+" + viewPlayers[3].realName.split("$")[0] : "");
return `<${this.id}> ${playerString} ${moment(this.time).format("YYYY-MM-DD HH-mm-ss")}`;
}
getViewJSON(tournamentModeSettings: any) {
const data = {
id: this.id,
time: moment(this.time).format("YYYY-MM-DD HH:mm:ss"),
name: this.name + (tournamentModeSettings.show_info ? " (Duel:" + this.duelCount + ")" : ""),
roomid: this.roomId,
cloud_replay_id: "R#" + this.cloudReplayId,
replay_filename: this.replayFileName,
roommode: this.roomMode,
players: this.players.map(player => {
return {
pos: player.pos,
is_first: player.isFirst === 1,
name: player.name + (tournamentModeSettings.show_ip ? " (IP: " + player.ip.slice(7) + ")" : "") + (tournamentModeSettings.show_info && !(this.roomMode === 2 && player.pos % 2 > 0) ? " (Score:" + player.score + " LP:" + (player.lp != null ? player.lp : "???") + (this.roomMode !== 2 ? " Cards:" + (player.cardCount != null ? player.cardCount : "???") : "") + ")" : ""),
winner: player.winner === 1
}
})
}
return data;
}
}
\ No newline at end of file
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var DuelLogPlayer_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DuelLogPlayer = void 0;
const typeorm_1 = require("typeorm");
const BasePlayer_1 = require("./BasePlayer");
const DuelLog_1 = require("./DuelLog");
const DeckEncoder_1 = require("../DeckEncoder");
let DuelLogPlayer = DuelLogPlayer_1 = class DuelLogPlayer extends BasePlayer_1.BasePlayer {
setStartDeck(deck) {
if (deck === null) {
this.startDeckBuffer = null;
return;
}
this.startDeckBuffer = DeckEncoder_1.encodeDeck(deck).toString("base64");
}
getStartDeck() {
return DeckEncoder_1.decodeDeck(Buffer.from(this.startDeckBuffer, "base64"));
}
setCurrentDeck(deck) {
if (deck === null) {
this.currentDeckBuffer = null;
return;
}
this.currentDeckBuffer = DeckEncoder_1.encodeDeck(deck).toString("base64");
}
getCurrentDeck() {
return DeckEncoder_1.decodeDeck(Buffer.from(this.currentDeckBuffer, "base64"));
}
static fromDuelLogPlayerInfo(info) {
const p = new DuelLogPlayer_1();
p.name = info.name;
p.pos = info.pos;
p.realName = info.realName;
p.lp = info.lp;
p.ip = info.ip;
p.score = info.score;
p.cardCount = info.cardCount;
p.isFirst = info.isFirst ? 1 : 0;
p.winner = info.winner ? 1 : 0;
p.startDeckBuffer = info.startDeckBuffer.toString("base64");
p.setCurrentDeck(info.deck);
return p;
}
};
__decorate([
typeorm_1.Index(),
typeorm_1.Column({ type: "varchar", length: 20 }),
__metadata("design:type", String)
], DuelLogPlayer.prototype, "realName", void 0);
__decorate([
typeorm_1.Column({ type: "varchar", length: 64, nullable: true }),
__metadata("design:type", String)
], DuelLogPlayer.prototype, "ip", void 0);
__decorate([
typeorm_1.Column("tinyint", { unsigned: true }),
__metadata("design:type", Number)
], DuelLogPlayer.prototype, "isFirst", void 0);
__decorate([
typeorm_1.Column("tinyint"),
__metadata("design:type", Number)
], DuelLogPlayer.prototype, "score", void 0);
__decorate([
typeorm_1.Column("int", { nullable: true }),
__metadata("design:type", Number)
], DuelLogPlayer.prototype, "lp", void 0);
__decorate([
typeorm_1.Column("smallint", { nullable: true }),
__metadata("design:type", Number)
], DuelLogPlayer.prototype, "cardCount", void 0);
__decorate([
typeorm_1.Column("text", { nullable: true }),
__metadata("design:type", String)
], DuelLogPlayer.prototype, "startDeckBuffer", void 0);
__decorate([
typeorm_1.Column("text", { nullable: true }),
__metadata("design:type", String)
], DuelLogPlayer.prototype, "currentDeckBuffer", void 0);
__decorate([
typeorm_1.Column("tinyint"),
__metadata("design:type", Number)
], DuelLogPlayer.prototype, "winner", void 0);
__decorate([
typeorm_1.ManyToOne(() => DuelLog_1.DuelLog, duelLog => duelLog.players),
__metadata("design:type", DuelLog_1.DuelLog)
], DuelLogPlayer.prototype, "duelLog", void 0);
DuelLogPlayer = DuelLogPlayer_1 = __decorate([
typeorm_1.Entity()
], DuelLogPlayer);
exports.DuelLogPlayer = DuelLogPlayer;
//# sourceMappingURL=DuelLogPlayer.js.map
\ No newline at end of file
import {Column, Entity, Index, ManyToOne} from "typeorm";
import {BasePlayer} from "./BasePlayer";
import {DuelLog} from "./DuelLog";
import {Deck, decodeDeck, encodeDeck} from "../DeckEncoder";
import {DuelLogPlayerInfo} from "../DataManager";
@Entity()
export class DuelLogPlayer extends BasePlayer {
@Index()
@Column({ type: "varchar", length: 20 })
realName: string;
@Column({ type: "varchar", length: 64, nullable: true })
ip: string;
@Column("tinyint", {unsigned: true})
isFirst: number;
@Column("tinyint")
score: number;
@Column("int", {nullable: true})
lp: number;
@Column("smallint", {nullable: true})
cardCount: number;
@Column("text", {nullable: true})
startDeckBuffer: string;
@Column("text", {nullable: true})
currentDeckBuffer: string;
@Column("tinyint")
winner: number;
setStartDeck(deck: Deck) {
if(deck === null) {
this.startDeckBuffer = null;
return;
}
this.startDeckBuffer = encodeDeck(deck).toString("base64");
}
getStartDeck() {
return decodeDeck(Buffer.from(this.startDeckBuffer, "base64"));
}
setCurrentDeck(deck: Deck) {
if(deck === null) {
this.currentDeckBuffer = null;
return;
}
this.currentDeckBuffer = encodeDeck(deck).toString("base64");
}
getCurrentDeck() {
return decodeDeck(Buffer.from(this.currentDeckBuffer, "base64"));
}
@ManyToOne(() => DuelLog, duelLog => duelLog.players)
duelLog: DuelLog;
static fromDuelLogPlayerInfo(info: DuelLogPlayerInfo) {
const p = new DuelLogPlayer();
p.name = info.name;
p.pos = info.pos;
p.realName = info.realName;
p.lp = info.lp;
p.ip = info.ip;
p.score = info.score;
p.cardCount = info.cardCount;
p.isFirst = info.isFirst ? 1 : 0;
p.winner = info.winner ? 1 : 0;
p.startDeckBuffer = info.startDeckBuffer.toString("base64");
p.setCurrentDeck(info.deck);
return p;
}
}
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RandomDuelBan = void 0;
const typeorm_1 = require("typeorm");
let RandomDuelBan = class RandomDuelBan {
setNeedTip(need) {
this.needTip = need ? 1 : 0;
}
getNeedTip() {
return this.needTip > 0 ? true : false;
}
};
__decorate([
typeorm_1.PrimaryColumn({ type: "varchar", length: 64 }),
__metadata("design:type", String)
], RandomDuelBan.prototype, "ip", void 0);
__decorate([
typeorm_1.Column("datetime"),
__metadata("design:type", Date)
], RandomDuelBan.prototype, "time", void 0);
__decorate([
typeorm_1.Column("smallint"),
__metadata("design:type", Number)
], RandomDuelBan.prototype, "count", void 0);
__decorate([
typeorm_1.Column({ type: "simple-array" }),
__metadata("design:type", Array)
], RandomDuelBan.prototype, "reasons", void 0);
__decorate([
typeorm_1.Column({ type: "tinyint", unsigned: true }),
__metadata("design:type", Number)
], RandomDuelBan.prototype, "needTip", void 0);
RandomDuelBan = __decorate([
typeorm_1.Entity()
], RandomDuelBan);
exports.RandomDuelBan = RandomDuelBan;
//# sourceMappingURL=RandomDuelBan.js.map
\ No newline at end of file
import {Column, Entity, PrimaryColumn} from "typeorm";
@Entity()
export class RandomDuelBan {
@PrimaryColumn({type: "varchar", length: 64})
ip: string;
@Column("datetime")
time: Date;
@Column("smallint")
count: number;
@Column({type: "simple-array"})
reasons: string[]
@Column({type: "tinyint", unsigned: true})
needTip: number;
setNeedTip(need: boolean) {
this.needTip = need ? 1 : 0;
}
getNeedTip() {
return this.needTip > 0 ? true : false;
}
}
\ No newline at end of file
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RandomDuelScore = void 0;
const typeorm_1 = require("typeorm");
let RandomDuelScore = class RandomDuelScore {
getDisplayName() {
return this.name.split("$")[0];
}
win() {
++this.winCount;
++this.winCombo;
}
lose() {
++this.loseCount;
this.winCombo = 0;
}
flee() {
++this.fleeCount;
this.lose();
}
getScoreText() {
const displayName = this.getDisplayName();
const total = this.winCount + this.loseCount;
if (this.winCount < 2 && total < 3) {
return `${displayName} \${random_this_not_enough}`;
}
if (this.winCombo >= 2) {
return `\${random_this_part1}${displayName} \${random_this_part2} ${Math.ceil(this.winCount / total * 100)}\${random_this_part3} ${Math.ceil(this.fleeCount / total * 100)}\${random_this_part4_combo}${this.winCombo}\${random_this_part5_combo}`;
}
else {
//return displayName + " 的今日战绩:胜率" + Math.ceil(this.winCount/total*100) + "%,逃跑率" + Math.ceil(this.fleeCount/total*100) + "%," + this.winCombo + "连胜中!"
return `\${random_this_part1}${displayName} \${random_this_part2} ${Math.ceil(this.winCount / total * 100)}\${random_this_part3} ${Math.ceil(this.fleeCount / total * 100)}\${random_this_part4}`;
}
}
};
__decorate([
typeorm_1.PrimaryColumn({ type: "varchar", length: 20 }),
__metadata("design:type", String)
], RandomDuelScore.prototype, "name", void 0);
__decorate([
typeorm_1.Index(),
typeorm_1.Column("int", { unsigned: true, default: 0 }),
__metadata("design:type", Number)
], RandomDuelScore.prototype, "winCount", void 0);
__decorate([
typeorm_1.Index(),
typeorm_1.Column("int", { unsigned: true, default: 0 }),
__metadata("design:type", Number)
], RandomDuelScore.prototype, "loseCount", void 0);
__decorate([
typeorm_1.Index(),
typeorm_1.Column("int", { unsigned: true, default: 0 }),
__metadata("design:type", Number)
], RandomDuelScore.prototype, "fleeCount", void 0);
__decorate([
typeorm_1.Column("int", { unsigned: true, default: 0 }),
__metadata("design:type", Number)
], RandomDuelScore.prototype, "winCombo", void 0);
RandomDuelScore = __decorate([
typeorm_1.Entity()
], RandomDuelScore);
exports.RandomDuelScore = RandomDuelScore;
//# sourceMappingURL=RandomDuelScore.js.map
\ No newline at end of file
import {Column, Entity, Index, PrimaryColumn} from "typeorm";
@Entity()
export class RandomDuelScore {
@PrimaryColumn({type: "varchar", length: 20})
name: string;
@Index()
@Column("int", {unsigned: true, default: 0})
winCount: number;
@Index()
@Column("int", {unsigned: true, default: 0})
loseCount: number;
@Index()
@Column("int", {unsigned: true, default: 0})
fleeCount: number;
@Column("int", {unsigned: true, default: 0})
winCombo: number;
getDisplayName() {
return this.name.split("$")[0];
}
win() {
++this.winCount;
++this.winCombo;
}
lose() {
++this.loseCount;
this.winCombo = 0;
}
flee() {
++this.fleeCount;
this.lose();
}
getScoreText() {
const displayName = this.getDisplayName();
const total = this.winCount + this.loseCount;
if (this.winCount < 2 && total < 3) {
return `${displayName} \${random_score_not_enough}`;
}
if (this.winCombo >= 2) {
return `\${random_score_part1}${displayName} \${random_score_part2} ${Math.ceil(this.winCount / total * 100)}\${random_score_part3} ${Math.ceil(this.fleeCount / total * 100)}\${random_score_part4_combo}${this.winCombo}\${random_score_part5_combo}`;
} else {
//return displayName + " 的今日战绩:胜率" + Math.ceil(this.winCount/total*100) + "%,逃跑率" + Math.ceil(this.fleeCount/total*100) + "%," + this.winCombo + "连胜中!"
return `\${random_score_part1}${displayName} \${random_score_part2} ${Math.ceil(this.winCount / total * 100)}\${random_score_part3} ${Math.ceil(this.fleeCount / total * 100)}\${random_score_part4}`;
}
}
}
\ No newline at end of file
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.User = void 0;
const typeorm_1 = require("typeorm");
let User = class User {
};
__decorate([
typeorm_1.PrimaryColumn({ type: "varchar", length: 128 }),
__metadata("design:type", String)
], User.prototype, "key", void 0);
__decorate([
typeorm_1.Column("varchar", { length: 16, nullable: true }),
__metadata("design:type", String)
], User.prototype, "chatColor", void 0);
User = __decorate([
typeorm_1.Entity()
], User);
exports.User = User;
//# sourceMappingURL=User.js.map
\ No newline at end of file
import {Column, Entity, PrimaryColumn} from "typeorm";
@Entity()
export class User {
@PrimaryColumn({type: "varchar", length: 128})
key: string;
@Column("varchar", {length: 16, nullable: true})
chatColor: string;
}
......@@ -82,13 +82,18 @@
"ready_time": 20,
"hang_timeout": 90
},
"cloud_replay": {
"mysql": {
"enabled": false,
"redis": {
"db": {
"host": "127.0.0.1",
"port": 6379
},
"never_expire": false,
"port": 3306,
"username": "root",
"password": "localhost",
"database": "srvpro"
}
},
"cloud_replay": {
"enabled": false,
"enable_halfway_watch": true
},
"windbot": {
......@@ -315,8 +320,6 @@
}
},
"ban": {
"banned_user": [],
"banned_ip": [],
"illegal_id": [
"^Lv\\.-*\\d+\\s*(.*)",
"^VIP\\.\\d+\\s*(.*)"
......
......@@ -24,14 +24,6 @@
]
}
},
"duel_log": {
"file": "./config/duel_log.json",
"duel_log": []
},
"chat_color": {
"file": "./config/chat_color.json",
"save_list": {}
},
"users": {
"file": "./config/admin_user.json",
"permission_examples": {
......
......@@ -67,14 +67,14 @@
"exp_value_part2": " exp",
"exp_value_part3": ", your score is ",
"exp_value_part4": ". These points may be reset after the stable release of MyCard.",
"random_score_part1": "Today's record of ",
"random_score_part1": "Record of ",
"random_score_part2": ": ",
"random_score_part3": "% WIN, ",
"random_score_part4_combo": "% RAGE QUIT, WIN COMBO ",
"random_score_part5_combo": " !",
"random_score_part4": "%",
"random_score_blank": " didn't have enough duel record today.",
"random_score_not_enough": " didn't have enough duel record today.",
"random_score_blank": " didn't have enough duel record.",
"random_score_not_enough": " didn't have enough duel record.",
"lp_low_opponent": "もはやお前のライフは風前の灯!",
"lp_low_self": "*Low LP Alert*",
"kicked_by_player": "was evicted from the game.",
......@@ -386,13 +386,13 @@
"exp_value_part3": ",你的战斗力是",
"exp_value_part4": "。竞技场部分积分按赛季重置。",
"random_score_part1": "",
"random_score_part2": "的今日战绩:胜率",
"random_score_part2": "的战绩:胜率",
"random_score_part3": "%,逃跑率",
"random_score_part4_combo": "%,",
"random_score_part5_combo": "连胜中!",
"random_score_part4": "%",
"random_score_blank": "的今日战绩:正在统计中",
"random_score_not_enough": "的今日战绩:正在统计中",
"random_score_blank": "的战绩:正在统计中",
"random_score_not_enough": "的战绩:正在统计中",
"lp_low_opponent": "你的生命已经如风中残烛了!",
"lp_low_self": "背水一战!",
"kicked_by_player": "被请出了房间",
......
......@@ -11,11 +11,6 @@
"cwd": "/ygopro-server/windbot/",
"args": "ServerMode=true ServerPort=2399",
"interpreter": "mono"
},
{
"name": "redis-server",
"script": "/usr/bin/redis-server",
"cwd": "/redis"
}
]
}
......@@ -5,37 +5,50 @@
"requires": true,
"dependencies": {
"@babel/code-frame": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
"integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
"integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
"requires": {
"@babel/highlight": "^7.10.1"
"@babel/highlight": "^7.10.4"
}
},
"@babel/helper-validator-identifier": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz",
"integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw=="
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw=="
},
"@babel/highlight": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz",
"integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
"integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
"requires": {
"@babel/helper-validator-identifier": "^7.10.1",
"@babel/helper-validator-identifier": "^7.10.4",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"@sqltools/formatter": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz",
"integrity": "sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q=="
},
"@types/bunyan": {
"version": "1.8.6",
"resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.6.tgz",
"integrity": "sha512-YiozPOOsS6bIuz31ilYqR5SlLif4TBWsousN2aCWLi5233nZSX19tFbcQUPdR7xJ8ypPyxkCGNxg0CIV5n9qxQ==",
"requires": {
"@types/node": "*"
}
},
"@types/node": {
"version": "14.0.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.13.tgz",
"integrity": "sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA=="
},
"@types/underscore": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.0.tgz",
"integrity": "sha512-ZAbqul7QAKpM2h1PFGa5ETN27ulmqtj0QviYHasw9LffvXZvVHuraOx/FOsIPPDNGZN0Qo1nASxxSfMYOtSoCw=="
"version": "1.10.24",
"resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.24.tgz",
"integrity": "sha512-T3NQD8hXNW2sRsSbLNjF/aBo18MyJlbw0lSpQHB/eZZtScPdexN4HSa8cByYwTw9Wy7KuOFr81mlDQcQQaZ79w=="
},
"abbrev": {
"version": "1.1.1",
......@@ -43,9 +56,9 @@
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"ajv": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
......@@ -66,6 +79,16 @@
"color-convert": "^1.9.0"
}
},
"any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
},
"app-root-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz",
"integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw=="
},
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
......@@ -80,6 +103,21 @@
"readable-stream": "^2.0.6"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
},
"dependencies": {
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
}
}
},
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
......@@ -109,9 +147,9 @@
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz",
"integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA=="
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
},
"axios": {
"version": "0.19.2",
......@@ -126,6 +164,11 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
......@@ -134,6 +177,11 @@
"tweetnacl": "^0.14.3"
}
},
"bignumber.js": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="
},
"binary": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
......@@ -143,6 +191,15 @@
"chainsaw": "~0.1.0"
}
},
"block-stream": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
"optional": true,
"requires": {
"inherits": "~2.0.0"
}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
......@@ -152,6 +209,15 @@
"concat-map": "0.0.1"
}
},
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"buffer-writer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz",
......@@ -163,16 +229,21 @@
"integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s="
},
"bunyan": {
"version": "1.8.12",
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz",
"integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=",
"version": "1.8.14",
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.14.tgz",
"integrity": "sha512-LlahJUxXzZLuw/hetUQJmRgZ1LF6+cr5TPpRj6jf327AsiIq2jhYEH4oqUUkVKTor+9w2BT3oxVwhzE5lw9tcg==",
"requires": {
"dtrace-provider": "~0.8",
"moment": "^2.10.6",
"moment": "^2.19.3",
"mv": "~2",
"safe-json-stringify": "~1"
}
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
......@@ -206,11 +277,160 @@
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"cli-highlight": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.4.tgz",
"integrity": "sha512-s7Zofobm20qriqDoU9sXptQx0t2R9PEgac92mENNm7xaEe1hn71IIMsXMK+6encA6WRCWWxIGQbipr3q998tlQ==",
"requires": {
"chalk": "^3.0.0",
"highlight.js": "^9.6.0",
"mz": "^2.4.0",
"parse5": "^5.1.1",
"parse5-htmlparser2-tree-adapter": "^5.1.1",
"yargs": "^15.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"requires": {
"has-flag": "^4.0.0"
}
},
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
}
}
}
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
},
"coffeescript": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz",
"integrity": "sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ=="
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
......@@ -268,6 +488,11 @@
"ms": "^2.1.1"
}
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
......@@ -288,16 +513,16 @@
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"denque": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
"integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
},
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
},
"dotenv": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
},
"dtrace-provider": {
"version": "0.8.8",
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz",
......@@ -316,6 +541,11 @@
"safer-buffer": "^2.1.0"
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
......@@ -324,11 +554,21 @@
"is-arrayish": "^0.2.1"
}
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
......@@ -340,15 +580,29 @@
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fast-deep-equal": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"figlet": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.0.tgz",
"integrity": "sha512-ZQJM4aifMpz6H19AW1VqvZ7l4pOE9p7i/3LyxgO2kp+PO/VcDYNqIHEMtkccqIhTXMKci4kjueJr/iCQEaT/Ww=="
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
......@@ -493,6 +747,11 @@
}
}
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
......@@ -525,14 +784,22 @@
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
},
"har-validator": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"requires": {
"ajv": "^6.5.5",
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
}
},
"has-ansi": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
"requires": {
"ansi-regex": "^2.0.0"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
......@@ -543,6 +810,11 @@
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
},
"highlight.js": {
"version": "9.18.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.3.tgz",
"integrity": "sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ=="
},
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
......@@ -561,6 +833,11 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"ignore-walk": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
......@@ -611,6 +888,12 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"optional": true
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
......@@ -626,15 +909,24 @@
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
},
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
"json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"json-schema": {
"version": "0.2.3",
......@@ -690,6 +982,14 @@
}
}
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"requires": {
"p-locate": "^4.1.0"
}
},
"lru-cache": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
......@@ -752,9 +1052,9 @@
}
},
"moment": {
"version": "2.26.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz",
"integrity": "sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw=="
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"ms": {
"version": "2.1.2",
......@@ -772,10 +1072,39 @@
"rimraf": "~2.4.0"
}
},
"mysql": {
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
"integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
"requires": {
"bignumber.js": "9.0.0",
"readable-stream": "2.3.7",
"safe-buffer": "5.1.2",
"sqlstring": "2.3.1"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
},
"mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"requires": {
"any-promise": "^1.0.0",
"object-assign": "^4.0.1",
"thenify-all": "^1.0.0"
}
},
"nan": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
"optional": true
},
"natives": {
"version": "1.1.6",
......@@ -789,15 +1118,74 @@
"optional": true
},
"needle": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz",
"integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz",
"integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==",
"requires": {
"debug": "^3.2.6",
"iconv-lite": "^0.4.4",
"sax": "^1.2.4"
}
},
"node-addon-api": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz",
"integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA=="
},
"node-gyp": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
"integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
"optional": true,
"requires": {
"fstream": "^1.0.0",
"glob": "^7.0.3",
"graceful-fs": "^4.1.2",
"mkdirp": "^0.5.0",
"nopt": "2 || 3",
"npmlog": "0 || 1 || 2 || 3 || 4",
"osenv": "0",
"request": "^2.87.0",
"rimraf": "2",
"semver": "~5.3.0",
"tar": "^2.0.0",
"which": "1"
},
"dependencies": {
"fstream": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"optional": true,
"requires": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
"mkdirp": ">=0.5 0",
"rimraf": "2"
}
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"optional": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"optional": true
}
}
},
"node-pre-gyp": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz",
......@@ -828,6 +1216,15 @@
"path-is-absolute": "^1.0.0"
}
},
"nopt": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
"requires": {
"abbrev": "1",
"osenv": "^0.1.4"
}
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
......@@ -835,16 +1232,30 @@
"requires": {
"glob": "^7.1.3"
}
},
"tar": {
"version": "4.4.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
"requires": {
"chownr": "^1.1.1",
"fs-minipass": "^1.2.5",
"minipass": "^2.8.6",
"minizlib": "^1.2.1",
"mkdirp": "^0.5.0",
"safe-buffer": "^5.1.2",
"yallist": "^3.0.3"
}
}
}
},
"nopt": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"optional": true,
"requires": {
"abbrev": "1",
"osenv": "^0.1.4"
"abbrev": "1"
}
},
"npm-bundled": {
......@@ -933,22 +1344,66 @@
"resolved": "https://registry.npmjs.org/over/-/over-0.0.5.tgz",
"integrity": "sha1-8phS5w/X4l82DgE6jsRMgq7bVwg="
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"requires": {
"p-limit": "^2.2.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"packet-reader": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz",
"integrity": "sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc="
},
"parent-require": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz",
"integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc="
},
"parse-json": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
"integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz",
"integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==",
"requires": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
"json-parse-better-errors": "^1.0.1",
"json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
}
},
"parse5": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="
},
"parse5-htmlparser2-tree-adapter": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-5.1.1.tgz",
"integrity": "sha512-CF+TKjXqoqyDwHqBhFQ+3l5t83xYi6fVT1tQNg+Ye0JRLnTxWvIroCjEp1A0k4lneHNBGnICUf0cfYVYGEazqw==",
"requires": {
"parse5": "^5.1.1"
}
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
......@@ -1074,6 +1529,11 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
},
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
......@@ -1116,34 +1576,10 @@
}
}
},
"redis": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/redis/-/redis-3.0.2.tgz",
"integrity": "sha512-PNhLCrjU6vKVuMOyFu7oSP296mwBkcE6lrAjruBYG5LgdSqtRBoVQIylrMyVZD/lkF24RSNNatzvYag6HRBHjQ==",
"requires": {
"denque": "^1.4.1",
"redis-commands": "^1.5.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0"
}
},
"redis-commands": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz",
"integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg=="
},
"redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60="
},
"redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
"requires": {
"redis-errors": "^1.0.0"
}
"reflect-metadata": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
"integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
},
"request": {
"version": "2.88.2",
......@@ -1172,6 +1608,16 @@
"uuid": "^3.3.2"
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"rimraf": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
......@@ -1202,15 +1648,24 @@
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"sha.js": {
"version": "2.4.11",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"requires": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"sigmund": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
......@@ -1235,14 +1690,20 @@
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
},
"sqlite3": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.2.0.tgz",
"integrity": "sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.0.tgz",
"integrity": "sha512-rjvqHFUaSGnzxDy2AHCwhHy6Zp6MNJzCPGYju4kD8yi6bze4d1/zMTg6C7JI49b7/EM7jKMTvyfN/4ylBKdwfw==",
"requires": {
"nan": "^2.12.1",
"node-addon-api": "2.0.0",
"node-gyp": "3.x",
"node-pre-gyp": "^0.11.0"
}
},
"sqlstring": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
"integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A="
},
"sshpk": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
......@@ -1316,17 +1777,50 @@
}
},
"tar": {
"version": "4.4.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
"requires": {
"chownr": "^1.1.1",
"fs-minipass": "^1.2.5",
"minipass": "^2.8.6",
"minizlib": "^1.2.1",
"mkdirp": "^0.5.0",
"safe-buffer": "^5.1.2",
"yallist": "^3.0.3"
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
"optional": true,
"requires": {
"block-stream": "*",
"fstream": "^1.0.12",
"inherits": "2"
},
"dependencies": {
"fstream": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"optional": true,
"requires": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
"mkdirp": ">=0.5 0",
"rimraf": "2"
}
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"optional": true
}
}
},
"thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"requires": {
"any-promise": "^1.0.0"
}
},
"thenify-all": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=",
"requires": {
"thenify": ">= 3.1.0 < 4"
}
},
"through": {
......@@ -1348,6 +1842,11 @@
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
"integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk="
},
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
......@@ -1366,15 +1865,114 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
"integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg=="
},
"typeorm": {
"version": "0.2.29",
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.29.tgz",
"integrity": "sha512-ih1vrTe3gEAGKRcWlcsTRxTL7gNjacQE498wVGuJ3ZRujtMqPZlbAWuC7xDzWCRjQnkZYNwZQeG9UgKfxSHB5g==",
"requires": {
"@sqltools/formatter": "1.2.2",
"app-root-path": "^3.0.0",
"buffer": "^5.5.0",
"chalk": "^4.1.0",
"cli-highlight": "^2.1.4",
"debug": "^4.1.1",
"dotenv": "^8.2.0",
"glob": "^7.1.6",
"js-yaml": "^3.14.0",
"mkdirp": "^1.0.4",
"reflect-metadata": "^0.1.13",
"sha.js": "^2.4.11",
"tslib": "^1.13.0",
"xml2js": "^0.4.23",
"yargonaut": "^1.1.2",
"yargs": "^16.0.3"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"debug": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"requires": {
"ms": "2.1.2"
}
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"typescript": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz",
"integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ=="
},
"ultron": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
"integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po="
},
"underscore": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz",
"integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg=="
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.11.0.tgz",
"integrity": "sha512-xY96SsN3NA461qIRKZ/+qox37YXPtSBswMGfiNptr+wrt6ds4HaMw23TP612fEyGekRE6LNRiLYr/aqbHXNedw=="
},
"underscore.string": {
"version": "3.3.5",
......@@ -1396,9 +1994,9 @@
}
},
"uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
"integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==",
"requires": {
"punycode": "^2.1.0"
}
......@@ -1423,6 +2021,20 @@
"extsprintf": "^1.2.0"
}
},
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"optional": true,
"requires": {
"isexe": "^2.0.0"
}
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"wide-align": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
......@@ -1431,6 +2043,67 @@
"string-width": "^1.0.2 || 2"
}
},
"wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
......@@ -1445,15 +2118,172 @@
"ultron": "1.0.x"
}
},
"xml2js": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
}
},
"xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
},
"xtend": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
},
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
},
"yargonaut": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz",
"integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==",
"requires": {
"chalk": "^1.1.1",
"figlet": "^1.1.1",
"parent-require": "^1.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
}
}
},
"yargs": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.1.0.tgz",
"integrity": "sha512-upWFJOmDdHN0syLuESuvXDmrRcWd1QafJolHskzaw79uZa7/x53gxQKiR07W59GWY1tFhhU/Th9DrtSfpS782g==",
"requires": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.0",
"y18n": "^5.0.2",
"yargs-parser": "^20.2.2"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^7.0.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
},
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
},
"y18n": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
"integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
},
"yargs-parser": {
"version": "20.2.4",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
"integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA=="
}
}
},
"yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
}
......@@ -11,29 +11,35 @@
],
"author": "zh99998 <zh99998@gmail.com>, mercury233 <me@mercury233.me>, Nanahira <78877@qq.com>",
"dependencies": {
"@types/bunyan": "^1.8.6",
"@types/node": "^14.0.13",
"@types/underscore": "^1.10.0",
"@types/underscore": "^1.10.24",
"async": "^3.2.0",
"axios": "^0.19.2",
"bunyan": "latest",
"bunyan": "^1.8.14",
"challonge": "latest",
"coffeescript": "^2.5.1",
"deepmerge": "latest",
"formidable": "latest",
"geoip-country-lite": "latest",
"load-json-file": "latest",
"lzma": "^2.3.2",
"moment": "latest",
"moment": "^2.29.1",
"mysql": "^2.18.1",
"pg": "^6.4.2",
"q": "^1.5.1",
"querystring": "^0.2.0",
"redis": "latest",
"request": "latest",
"sqlite3": "latest",
"underscore": "latest",
"typeorm": "^0.2.29",
"typescript": "^4.0.5",
"underscore": "^1.11.0",
"underscore.string": "latest",
"ws": "^1.1.1"
},
"license": "AGPL-3.0",
"scripts": {
"build": "coffee -c *.coffee && tsc",
"start": "node ygopro-server.js",
"tournament": "node ygopro-tournament.js",
"pre": "node ygopro-pre.js",
......
......@@ -4,11 +4,15 @@
"target": "esnext",
"esModuleInterop": true,
"resolveJsonModule": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true
},
"compileOnSave": true,
"allowJs": true,
"include": [
"*.ts"
"*.ts",
"data-manager/*.ts",
"data-manager/entities/*.ts"
]
}
......@@ -59,7 +59,7 @@ add_log = (message) ->
text = mt.format('YYYY-MM-DD HH:mm:ss') + " --> " + message + "\n"
res = false
try
await util.promisify(fs.appendFile)("./logs/"+mt.format('YYYY-MM-DD')+".log", text)
await fs.promises.appendFile("./logs/"+mt.format('YYYY-MM-DD')+".log", text)
res = true
catch
res = false
......@@ -69,7 +69,7 @@ add_log = (message) ->
default_data = loadJSON('./data/default_data.json')
setting_save = (settings) ->
try
await util.promisify(fs.writeFile)(settings.file, JSON.stringify(settings, null, 2))
await fs.promises.writeFile(settings.file, JSON.stringify(settings, null, 2))
catch e
add_log("save fail");
return
......
......@@ -73,7 +73,7 @@
text = mt.format('YYYY-MM-DD HH:mm:ss') + " --> " + message + "\n";
res = false;
try {
await util.promisify(fs.appendFile)("./logs/" + mt.format('YYYY-MM-DD') + ".log", text);
await fs.promises.appendFile("./logs/" + mt.format('YYYY-MM-DD') + ".log", text);
res = true;
} catch (error) {
res = false;
......@@ -86,7 +86,7 @@
setting_save = async function(settings) {
var e;
try {
await util.promisify(fs.writeFile)(settings.file, JSON.stringify(settings, null, 2));
await fs.promises.writeFile(settings.file, JSON.stringify(settings, null, 2));
} catch (error) {
e = error;
add_log("save fail");
......
......@@ -20,6 +20,8 @@ _.mixin(_.str.exports())
request = require 'request'
axios = require 'axios'
qs = require "querystring"
zlib = require 'zlib'
axios = require 'axios'
bunyan = require 'bunyan'
log = global.log = bunyan.createLogger name: "mycard"
......@@ -82,69 +84,36 @@ merge = require 'deepmerge'
loadJSON = require('load-json-file').sync
loadJSONAsync = require('load-json-file')
util = require("util")
Q = require("q")
#heapdump = require 'heapdump'
# 配置
# 导入旧配置
if not fs.existsSync('./config')
fs.mkdirSync('./config')
try
oldconfig=loadJSON('./config.user.json')
if oldconfig.tips
oldtips = {}
oldtips.file = './config/tips.json'
oldtips.tips = oldconfig.tips
fs.writeFileSync(oldtips.file, JSON.stringify(oldtips, null, 2))
delete oldconfig.tips
if oldconfig.dialogues
olddialogues = {}
olddialogues.file = './config/dialogues.json'
olddialogues.dialogues = oldconfig.dialogues
fs.writeFileSync(olddialogues.file, JSON.stringify(olddialogues, null, 2))
delete oldconfig.dialogues
if oldconfig.modules
if oldconfig.modules.tournament_mode and oldconfig.modules.tournament_mode.duel_log
oldduellog = {}
oldduellog.file = './config/duel_log.json'
oldduellog.duel_log = oldconfig.modules.tournament_mode.duel_log
fs.writeFileSync(oldduellog.file, JSON.stringify(oldduellog, null, 2))
delete oldconfig.oldduellog
oldbadwords={}
if oldconfig.ban
if oldconfig.ban.badword_level0
oldbadwords.level0 = oldconfig.ban.badword_level0
if oldconfig.ban.badword_level1
oldbadwords.level1 = oldconfig.ban.badword_level1
if oldconfig.ban.badword_level2
oldbadwords.level2 = oldconfig.ban.badword_level2
if oldconfig.ban.badword_level3
oldbadwords.level3 = oldconfig.ban.badword_level3
if not _.isEmpty(oldbadwords)
oldbadwords.file = './config/badwords.json'
fs.writeFileSync(oldbadwords.file, JSON.stringify(oldbadwords, null, 2))
delete oldconfig.ban.badword_level0
delete oldconfig.ban.badword_level1
delete oldconfig.ban.badword_level2
delete oldconfig.ban.badword_level3
if not _.isEmpty(oldconfig)
# log.info oldconfig
fs.writeFileSync('./config/config.json', JSON.stringify(oldconfig, null, 2))
log.info 'imported old config from config.user.json'
fs.renameSync('./config.user.json', './config.user.bak')
catch e
log.info e unless e.code == 'ENOENT'
setting_save = global.setting_save = (settings, callback) ->
if !callback
callback = (err) ->
if(err)
log.warn("setting save fail", err.toString())
fs.writeFile(settings.file, JSON.stringify(settings, null, 2), callback)
checkFileExists = (path) =>
try
await fs.promises.access(path)
return true
catch e
return false
createDirectoryIfNotExists = (path) =>
try
if path and !await checkFileExists(path)
await fs.promises.mkdir(path, {recursive: true})
catch e
log.warn("Failed to create directory #{path}: #{e.toString()}")
setting_save = global.setting_save = (settings) ->
try
await fs.promises.writeFile(settings.file, JSON.stringify(settings, null, 2))
catch e
log.warn("setting save fail", e.toString())
return
setting_change = global.setting_change = (settings, path, val, callback) ->
setting_change = global.setting_change = (settings, path, val) ->
# path should be like "modules:welcome"
log.info("setting changed", path, val) if _.isString(val)
path=path.split(':')
......@@ -157,220 +126,86 @@ setting_change = global.setting_change = (settings, path, val, callback) ->
target=target[key]
key = path.shift()
target[key] = val
setting_save(settings, callback)
await setting_save(settings)
return
# 读取配置
default_config = loadJSON('./data/default_config.json')
if fs.existsSync('./config/config.json')
importOldConfig = () ->
try
config = loadJSON('./config/config.json')
oldconfig=await loadJSONAsync('./config.user.json')
if oldconfig.tips
oldtips = {}
oldtips.file = './config/tips.json'
oldtips.tips = oldconfig.tips
await fs.promises.writeFile(oldtips.file, JSON.stringify(oldtips, null, 2))
delete oldconfig.tips
if oldconfig.dialogues
olddialogues = {}
olddialogues.file = './config/dialogues.json'
olddialogues.dialogues = oldconfig.dialogues
await fs.promises.writeFile(olddialogues.file, JSON.stringify(olddialogues, null, 2))
delete oldconfig.dialogues
oldbadwords={}
if oldconfig.ban
if oldconfig.ban.badword_level0
oldbadwords.level0 = oldconfig.ban.badword_level0
if oldconfig.ban.badword_level1
oldbadwords.level1 = oldconfig.ban.badword_level1
if oldconfig.ban.badword_level2
oldbadwords.level2 = oldconfig.ban.badword_level2
if oldconfig.ban.badword_level3
oldbadwords.level3 = oldconfig.ban.badword_level3
if not _.isEmpty(oldbadwords)
oldbadwords.file = './config/badwords.json'
await fs.promises.writeFile(oldbadwords.file, JSON.stringify(oldbadwords, null, 2))
delete oldconfig.ban.badword_level0
delete oldconfig.ban.badword_level1
delete oldconfig.ban.badword_level2
delete oldconfig.ban.badword_level3
if not _.isEmpty(oldconfig)
# log.info oldconfig
await fs.promises.writeFile('./config/config.json', JSON.stringify(oldconfig, null, 2))
log.info 'imported old config from config.user.json'
await fs.promises.rename('./config.user.json', './config.user.bak')
catch e
console.error("Failed reading config: ", e.toString())
process.exit(1)
else
config = {}
settings = global.settings = merge(default_config, config, { arrayMerge: (destination, source) -> source })
log.info e unless e.code == 'ENOENT'
auth = global.auth = require './ygopro-auth.js'
#import old configs
imported = false
#reset http.quick_death_rule from true to 1
if settings.modules.http.quick_death_rule == true
settings.modules.http.quick_death_rule = 1
imported = true
#import the old redis port
if settings.modules.cloud_replay.redis_port
settings.modules.cloud_replay.redis.port = settings.modules.cloud_replay.redis_port
delete settings.modules.cloud_replay.redis_port
imported = true
#import the old passwords to new admin user system
if settings.modules.http.password
auth.add_user("olduser", settings.modules.http.password, true, {
"get_rooms": true,
"shout": true,
"stop": true,
"change_settings": true,
"ban_user": true,
"kick_user": true,
"start_death": true
})
delete settings.modules.http.password
imported = true
if settings.modules.tournament_mode.password
auth.add_user("tournament", settings.modules.tournament_mode.password, true, {
"duel_log": true,
"download_replay": true,
"clear_duel_log": true,
"deck_dashboard_read": true,
"deck_dashboard_write": true,
})
delete settings.modules.tournament_mode.password
imported = true
if settings.modules.pre_util.password
auth.add_user("pre", settings.modules.pre_util.password, true, {
"pre_dashboard": true
})
delete settings.modules.pre_util.password
imported = true
if settings.modules.update_util.password
auth.add_user("update", settings.modules.update_util.password, true, {
"update_dashboard": true
})
delete settings.modules.update_util.password
imported = true
#import the old enable_priority hostinfo
if settings.hostinfo.enable_priority or settings.hostinfo.enable_priority == false
if settings.hostinfo.enable_priority
settings.hostinfo.duel_rule = 3
else
settings.hostinfo.duel_rule = 5
delete settings.hostinfo.enable_priority
imported = true
#import the old Challonge api key option
if settings.modules.challonge.api_key
settings.modules.challonge.options.apiKey = settings.modules.challonge.api_key
delete settings.modules.challonge.api_key
imported = true
#import the old random_duel.blank_pass_match option
if settings.modules.random_duel.blank_pass_match == true
settings.modules.random_duel.blank_pass_modes = {"S":true,"M":true,"T":false}
delete settings.modules.random_duel.blank_pass_match
imported = true
if settings.modules.random_duel.blank_pass_match == false
settings.modules.random_duel.blank_pass_modes = {"S":true,"M":false,"T":false}
delete settings.modules.random_duel.blank_pass_match
imported = true
#finish
if imported
setting_save(settings)
# 读取数据
default_data = loadJSON('./data/default_data.json')
try
tips = global.tips = loadJSON('./config/tips.json')
catch
tips = global.tips = default_data.tips
setting_save(tips)
try
dialogues = global.dialogues = loadJSON('./config/dialogues.json')
catch
dialogues = global.dialogues = default_data.dialogues
setting_save(dialogues)
try
badwords = global.badwords = loadJSON('./config/badwords.json')
catch
badwords = global.badwords = default_data.badwords
setting_save(badwords)
try
duel_log = global.duel_log = loadJSON('./config/duel_log.json')
catch
duel_log = global.duel_log = default_data.duel_log
setting_save(duel_log)
try
chat_color = global.chat_color = loadJSON('./config/chat_color.json')
catch
chat_color = global.chat_color = default_data.chat_color
setting_save(chat_color)
try
cppversion = parseInt(fs.readFileSync('ygopro/gframe/game.cpp', 'utf8').match(/PRO_VERSION = ([x\dABCDEF]+)/)[1], '16')
setting_change(settings, "version", cppversion)
log.info "ygopro version 0x"+settings.version.toString(16), "(from source code)"
catch
#settings.version = settings.version_default
log.info "ygopro version 0x"+settings.version.toString(16), "(from config)"
# load the lflist of current date
lflists = global.lflists = []
# expansions/lflist
try
for list in fs.readFileSync('ygopro/expansions/lflist.conf', 'utf8').match(/!.*/g)
date=list.match(/!([\d\.]+)/)
continue unless date
lflists.push({date: moment(list.match(/!([\d\.]+)/)[1], 'YYYY.MM.DD').utcOffset("-08:00"), tcg: list.indexOf('TCG') != -1})
catch
# lflist
try
for list in fs.readFileSync('ygopro/lflist.conf', 'utf8').match(/!.*/g)
date=list.match(/!([\d\.]+)/)
continue unless date
lflists.push({date: moment(list.match(/!([\d\.]+)/)[1], 'YYYY.MM.DD').utcOffset("-08:00"), tcg: list.indexOf('TCG') != -1})
catch
if settings.modules.cloud_replay.enabled
redis = require 'redis'
zlib = require 'zlib'
redisdb = global.redisdb = redis.createClient(settings.modules.cloud_replay.redis)
redisdb.on 'error', (err)->
log.warn err
return
if settings.modules.windbot.enabled
windbots = global.windbots = loadJSON(settings.modules.windbot.botlist).windbots
real_windbot_server_ip = global.real_windbot_server_ip = settings.modules.windbot.server_ip
if !settings.modules.windbot.server_ip.includes("127.0.0.1")
dns = require('dns')
dns.lookup(settings.modules.windbot.server_ip,(err,addr) ->
if(!err)
real_windbot_server_ip = global.real_windbot_server_ip = addr
)
if settings.modules.heartbeat_detection.enabled
long_resolve_cards = global.long_resolve_cards = loadJSON('./data/long_resolve_cards.json')
if settings.modules.tournament_mode.enable_recover
ReplayParser = global.ReplayParser = require "./Replay.js"
if settings.modules.athletic_check.enabled
AthleticChecker = require("./athletic-check.js").AthleticChecker
athleticChecker = global.athleticChecker = new AthleticChecker(settings.modules.athletic_check)
# 组件
ygopro = global.ygopro = require './ygopro.js'
roomlist = global.roomlist = require './roomlist.js' if settings.modules.http.websocket_roomlist
if settings.modules.i18n.auto_pick
geoip = require('geoip-country-lite')
roomlist = null
# cache users of mycard login
settings = {}
tips = null
dialogues = null
badwords = null
lflists = global.lflists = []
real_windbot_server_ip = null
long_resolve_cards = []
ReplayParser = null
athleticChecker = null
users_cache = {}
if settings.modules.mycard.enabled
pgClient = require('pg').Client
pg_client = global.pg_client = new pgClient(settings.modules.mycard.auth_database)
pg_client.on 'error', (err) ->
log.warn "PostgreSQL ERROR: ", err
return
pg_query = pg_client.query('SELECT username, id from users')
pg_query.on 'error', (err) ->
log.warn "PostgreSQL Query ERROR: ", err
return
pg_query.on 'row', (row) ->
#log.info "load user", row.username, row.id
users_cache[row.username] = row.id
return
pg_query.on 'end', (result) ->
log.info "users loaded", result.rowCount
return
pg_client.on 'drain', pg_client.end.bind(pg_client)
log.info "loading mycard user..."
pg_client.connect()
if settings.modules.arena_mode.enabled and settings.modules.arena_mode.init_post.enabled
request.post { url : settings.modules.arena_mode.init_post.url , qs : {
ak: settings.modules.arena_mode.init_post.accesskey,
arena: settings.modules.arena_mode.mode
}}, (error, response, body)=>
if error
log.warn 'ARENA INIT POST ERROR', error
else
if response.statusCode >= 400
log.warn 'ARENA INIT POST FAIL', response.statusCode, response.statusMessage, body
#else
# log.info 'ARENA INIT POST OK', response.statusCode, response.statusMessage
return
geoip = null
dataManager = null
windbots = []
disconnect_list = {} # {old_client, old_server, room_id, timeout, deckbuf}
challonge = null
challonge_cache = {
participants: null
matches: null
}
challonge_queue_callbacks = {
participants: []
matches: []
}
is_challonge_requesting = {
participants: null
matches: null
}
get_callback = () ->
replaced_index = () ->
refresh_challonge_cache = () ->
class ResolveData
constructor: (@func) ->
......@@ -382,152 +217,438 @@ class ResolveData
@func(err, data)
return true
if settings.modules.challonge.enabled
challonge_module_name = 'challonge'
if settings.modules.challonge.use_custom_module
challonge_module_name = settings.modules.challonge.use_custom_module
challonge = global.challonge = require(challonge_module_name).createClient(settings.modules.challonge.options)
if settings.modules.challonge.cache_ttl
loadLFList = (path) ->
try
for list in (await fs.promises.readFile(path, 'utf8')).match(/!.*/g)
date=list.match(/!([\d\.]+)/)
continue unless date
lflists.push({date: moment(list.match(/!([\d\.]+)/)[1], 'YYYY.MM.DD').utcOffset("-08:00"), tcg: list.indexOf('TCG') != -1})
catch
init = () ->
await createDirectoryIfNotExists("./config")
await importOldConfig()
defaultConfig = await loadJSONAsync('./data/default_config.json')
if await checkFileExists("./config/config.json")
try
config = await loadJSONAsync('./config/config.json')
catch e
console.error("Failed reading config: ", e.toString())
process.exit(1)
else
config = {}
settings = global.settings = merge(defaultConfig, config, { arrayMerge: (destination, source) -> source })
#import old configs
imported = false
#reset http.quick_death_rule from true to 1
if settings.modules.http.quick_death_rule == true
settings.modules.http.quick_death_rule = 1
imported = true
#import the old passwords to new admin user system
if settings.modules.http.password
await auth.add_user("olduser", settings.modules.http.password, true, {
"get_rooms": true,
"shout": true,
"stop": true,
"change_settings": true,
"ban_user": true,
"kick_user": true,
"start_death": true
})
delete settings.modules.http.password
imported = true
if settings.modules.tournament_mode.password
await auth.add_user("tournament", settings.modules.tournament_mode.password, true, {
"duel_log": true,
"download_replay": true,
"clear_duel_log": true,
"deck_dashboard_read": true,
"deck_dashboard_write": true,
})
delete settings.modules.tournament_mode.password
imported = true
if settings.modules.pre_util.password
await auth.add_user("pre", settings.modules.pre_util.password, true, {
"pre_dashboard": true
})
delete settings.modules.pre_util.password
imported = true
if settings.modules.update_util.password
await auth.add_user("update", settings.modules.update_util.password, true, {
"update_dashboard": true
})
delete settings.modules.update_util.password
imported = true
#import the old enable_priority hostinfo
if settings.hostinfo.enable_priority or settings.hostinfo.enable_priority == false
if settings.hostinfo.enable_priority
settings.hostinfo.duel_rule = 3
else
settings.hostinfo.duel_rule = 5
delete settings.hostinfo.enable_priority
imported = true
#import the old Challonge api key option
if settings.modules.challonge.api_key
settings.modules.challonge.options.apiKey = settings.modules.challonge.api_key
delete settings.modules.challonge.api_key
imported = true
#import the old random_duel.blank_pass_match option
if settings.modules.random_duel.blank_pass_match == true
settings.modules.random_duel.blank_pass_modes = {"S":true,"M":true,"T":false}
delete settings.modules.random_duel.blank_pass_match
imported = true
if settings.modules.random_duel.blank_pass_match == false
settings.modules.random_duel.blank_pass_modes = {"S":true,"M":false,"T":false}
delete settings.modules.random_duel.blank_pass_match
imported = true
#finish
if imported
await setting_save(settings)
if settings.modules.mysql.enabled
DataManager = require('./data-manager/DataManager.js').DataManager
dataManager = global.dataManager = new DataManager(settings.modules.mysql.db, log)
await dataManager.init()
else
log.warn("Some functions may be limited without MySQL .")
if settings.modules.cloud_replay.enabled
settings.modules.cloud_replay.enabled = false
await setting_save(settings)
log.warn("Cloud replay cannot be enabled because no MySQL.")
if settings.modules.enable_recover.enabled
settings.modules.enable_recover.enabled = false
await setting_save(settings)
log.warn("Recover mode cannot be enabled because no MySQL.")
if settings.modules.chat_color.enabled
settings.modules.chat_color.enabled = false
await setting_save(settings)
log.warn("Chat color cannot be enabled because no MySQL.")
if settings.modules.random_duel.record_match_scores
settings.modules.random_duel.record_match_scores = false
await setting_save(settings)
log.warn("Cannot record random match scores because no MySQL.")
# 读取数据
default_data = await loadJSONAsync('./data/default_data.json')
try
tips = global.tips = await loadJSONAsync('./config/tips.json')
catch
tips = global.tips = default_data.tips
await setting_save(tips)
try
dialogues = global.dialogues = await loadJSONAsync('./config/dialogues.json')
catch
dialogues = global.dialogues = default_data.dialogues
await setting_save(dialogues)
try
badwords = global.badwords = await loadJSONAsync('./config/badwords.json')
catch
badwords = global.badwords = default_data.badwords
await setting_save(badwords)
if settings.modules.chat_color.enabled and await checkFileExists('./config/chat_color.json')
try
chat_color = await loadJSONAsync('./config/chat_color.json')
if chat_color
await dataManager.migrateChatColors(chat_color.save_list);
await fs.promises.rename('./config/chat_color.json', './config/chat_color.json.bak')
log.info("Chat color migrated.")
catch
try
cppversion = parseInt(await fs.promises.readFile('ygopro/gframe/game.cpp', 'utf8').match(/PRO_VERSION = ([x\dABCDEF]+)/)[1], '16')
await setting_change(settings, "version", cppversion)
log.info "ygopro version 0x"+settings.version.toString(16), "(from source code)"
catch
#settings.version = settings.version_default
log.info "ygopro version 0x"+settings.version.toString(16), "(from config)"
# load the lflist of current date
await loadLFList('ygopro/expansions/lflist.conf')
await loadLFList('ygopro/lflist.conf')
if settings.modules.windbot.enabled
windbots = global.windbots = (await loadJSONAsync(settings.modules.windbot.botlist)).windbots
real_windbot_server_ip = global.real_windbot_server_ip = settings.modules.windbot.server_ip
if !settings.modules.windbot.server_ip.includes("127.0.0.1")
dns = require('dns')
real_windbot_server_ip = global.real_windbot_server_ip = await util.promisify(dns.lookup)(settings.modules.windbot.server_ip)
if settings.modules.heartbeat_detection.enabled
long_resolve_cards = global.long_resolve_cards = await loadJSONAsync('./data/long_resolve_cards.json')
if settings.modules.tournament_mode.enable_recover
ReplayParser = global.ReplayParser = require "./Replay.js"
if settings.modules.athletic_check.enabled
AthleticChecker = require("./athletic-check.js").AthleticChecker
athleticChecker = global.athleticChecker = new AthleticChecker(settings.modules.athletic_check)
if settings.modules.http.websocket_roomlist
roomlist = global.roomlist = require './roomlist.js'
if settings.modules.i18n.auto_pick
geoip = require('geoip-country-lite')
if settings.modules.mycard.enabled
pgClient = require('pg').Client
pg_client = global.pg_client = new pgClient(settings.modules.mycard.auth_database)
pg_client.on 'error', (err) ->
log.warn "PostgreSQL ERROR: ", err
return
pg_query = pg_client.query('SELECT username, id from users')
pg_query.on 'error', (err) ->
log.warn "PostgreSQL Query ERROR: ", err
return
pg_query.on 'row', (row) ->
#log.info "load user", row.username, row.id
users_cache[row.username] = row.id
return
pg_query.on 'end', (result) ->
log.info "users loaded", result.rowCount
return
pg_client.on 'drain', pg_client.end.bind(pg_client)
log.info "loading mycard user..."
pg_client.connect()
if settings.modules.arena_mode.enabled and settings.modules.arena_mode.init_post.enabled
postData = qs.stringify({
ak: settings.modules.arena_mode.init_post.accesskey,
arena: settings.modules.arena_mode.mode
})
try
await axios.post(settings.modules.arena_mode.init_post.url + "?" + postData, {
responseType: "json"
})
catch e
log.warn 'ARENA INIT POST ERROR', e
if settings.modules.challonge.enabled
challonge_module_name = 'challonge'
if settings.modules.challonge.use_custom_module
challonge_module_name = settings.modules.challonge.use_custom_module
challonge = global.challonge = require(challonge_module_name).createClient(settings.modules.challonge.options)
challonge_cache = {
participants: null
matches: null
}
challonge_queue_callbacks = {
participants: []
matches: []
}
is_challonge_requesting = {
participants: null
matches: null
}
get_callback = (challonge_type, resolve_data) ->
return ((err, data) ->
if settings.modules.challonge.cache_ttl and !err and data
challonge_cache[challonge_type] = data
is_challonge_requesting[challonge_type] = null
resolve_data.resolve(err, data)
while challonge_queue_callbacks[challonge_type].length
cur_resolve_data = challonge_queue_callbacks[challonge_type].splice(0, 1)[0]
cur_resolve_data.resolve(err, data)
challonge_queue_callbacks = {
participants: []
matches: []
}
is_challonge_requesting = {
participants: null
matches: null
}
get_callback = (challonge_type, resolve_data) ->
return ((err, data) ->
if settings.modules.challonge.cache_ttl and !err and data
challonge_cache[challonge_type] = data
is_challonge_requesting[challonge_type] = null
resolve_data.resolve(err, data)
while challonge_queue_callbacks[challonge_type].length
cur_resolve_data = challonge_queue_callbacks[challonge_type].splice(0, 1)[0]
cur_resolve_data.resolve(err, data)
return
)
replaced_index = (challonge_type) ->
return (_data) ->
resolve_data = new ResolveData(_data.callback)
if settings.modules.challonge.cache_ttl and !_data.no_cache and challonge_cache[challonge_type]
resolve_data.resolve(null, challonge_cache[challonge_type])
else if is_challonge_requesting[challonge_type] and moment() - is_challonge_requesting[challonge_type] <= 5000
challonge_queue_callbacks[challonge_type].push(resolve_data)
else
_data.callback = get_callback(challonge_type, resolve_data)
is_challonge_requesting[challonge_type] = moment()
try
challonge[challonge_type].index(_data)
catch err
_data.callback(err, null)
return
for challonge_type in ["participants", "matches"]
challonge[challonge_type]._index = replaced_index(challonge_type)
challonge.matches._update = (_data) ->
try
challonge.matches.update(_data)
catch err
log.warn("Errored pushing scores to Challonge.", err)
return
)
replaced_index = (challonge_type) ->
return (_data) ->
resolve_data = new ResolveData(_data.callback)
if settings.modules.challonge.cache_ttl and !_data.no_cache and challonge_cache[challonge_type]
resolve_data.resolve(null, challonge_cache[challonge_type])
else if is_challonge_requesting[challonge_type] and moment() - is_challonge_requesting[challonge_type] <= 5000
challonge_queue_callbacks[challonge_type].push(resolve_data)
else
_data.callback = get_callback(challonge_type, resolve_data)
is_challonge_requesting[challonge_type] = moment()
try
challonge[challonge_type].index(_data)
catch err
_data.callback(err, null)
refresh_challonge_cache = global.refresh_challonge_cache = () ->
if settings.modules.challonge.cache_ttl
challonge_cache.participants = null
challonge_cache.matches = null
return
for challonge_type in ["participants", "matches"]
challonge[challonge_type]._index = replaced_index(challonge_type)
challonge.matches._update = (_data) ->
try
challonge.matches.update(_data)
catch err
log.warn("Errored pushing scores to Challonge.", err)
return
refresh_challonge_cache = global.refresh_challonge_cache = () ->
refresh_challonge_cache()
if settings.modules.challonge.cache_ttl
challonge_cache.participants = null
challonge_cache.matches = null
setInterval(refresh_challonge_cache, settings.modules.challonge.cache_ttl)
if settings.modules.tips.get
load_tips()
if settings.modules.tips.enabled
setInterval ()->
for room in ROOM_all when room and room.established
ygopro.stoc_send_random_tip_to_room(room) if room.duel_stage == ygopro.constants.DUEL_STAGE.SIDING or room.duel_stage == ygopro.constants.DUEL_STAGE.BEGIN
return
, 30000
if settings.modules.dialogues.get
load_dialogues()
if settings.modules.random_duel.post_match_scores and settings.modules.mysql.enabled
setInterval(()->
scores = await dataManager.getRandomScoreTop10()
try
await axios.post(settings.modules.random_duel.post_match_scores, {
headers: { 'content-type': 'application/x-www-form-urlencoded' },
data: qs.stringify({
accesskey: settings.modules.random_duel.post_match_accesskey,
rank: JSON.stringify(scores)
responseType: "json"
})
})
catch e
log.warn 'RANDOM SCORE POST ERROR', e.toString()
return
, 60000)
if settings.modules.random_duel.enabled
setInterval ()->
for room in ROOM_all when room and room.duel_stage != ygopro.constants.DUEL_STAGE.BEGIN and room.random_type and room.last_active_time and room.waiting_for_player and room.get_disconnected_count() == 0 and (!settings.modules.side_timeout or room.duel_stage != ygopro.constants.DUEL_STAGE.SIDING) and !room.recovered
time_passed = Math.floor((moment() - room.last_active_time) / 1000)
#log.info time_passed
if time_passed >= settings.modules.random_duel.hang_timeout
room.last_active_time = moment()
await ROOM_ban_player(room.waiting_for_player.name, room.waiting_for_player.ip, "${random_ban_reason_AFK}")
room.scores[room.waiting_for_player.name_vpass] = -9
#log.info room.waiting_for_player.name, room.scores[room.waiting_for_player.name_vpass]
ygopro.stoc_send_chat_to_room(room, "#{room.waiting_for_player.name} ${kicked_by_system}", ygopro.constants.COLORS.RED)
CLIENT_send_replays(room.waiting_for_player, room)
CLIENT_kick(room.waiting_for_player)
else if time_passed >= (settings.modules.random_duel.hang_timeout - 20) and not (time_passed % 10)
ygopro.stoc_send_chat_to_room(room, "#{room.waiting_for_player.name} ${afk_warn_part1}#{settings.modules.random_duel.hang_timeout - time_passed}${afk_warn_part2}", ygopro.constants.COLORS.RED)
ROOM_unwelcome(room, room.waiting_for_player, "${random_ban_reason_AFK}")
return
, 1000
if settings.modules.mycard.enabled
setInterval ()->
for room in ROOM_all when room and room.duel_stage != ygopro.constants.DUEL_STAGE.BEGIN and room.arena and room.last_active_time and room.waiting_for_player and room.get_disconnected_count() == 0 and (!settings.modules.side_timeout or room.duel_stage != ygopro.constants.DUEL_STAGE.SIDING) and !room.recovered
time_passed = Math.floor((moment() - room.last_active_time) / 1000)
#log.info time_passed
if time_passed >= settings.modules.random_duel.hang_timeout
room.last_active_time = moment()
ygopro.stoc_send_chat_to_room(room, "#{room.waiting_for_player.name} ${kicked_by_system}", ygopro.constants.COLORS.RED)
room.scores[room.waiting_for_player.name_vpass] = -9
#log.info room.waiting_for_player.name, room.scores[room.waiting_for_player.name_vpass]
CLIENT_send_replays(room.waiting_for_player, room)
CLIENT_kick(room.waiting_for_player)
else if time_passed >= (settings.modules.random_duel.hang_timeout - 20) and not (time_passed % 10)
ygopro.stoc_send_chat_to_room(room, "#{room.waiting_for_player.name} ${afk_warn_part1}#{settings.modules.random_duel.hang_timeout - time_passed}${afk_warn_part2}", ygopro.constants.COLORS.RED)
if true # settings.modules.arena_mode.punish_quit_before_match
for room in ROOM_all when room and room.arena and room.duel_stage == ygopro.constants.DUEL_STAGE.BEGIN and room.get_playing_player().length < 2
player = room.get_playing_player()[0]
if player and player.join_time and !player.arena_quit_free
waited_time = moment() - player.join_time
if waited_time >= 30000
ygopro.stoc_send_chat(player, "${arena_wait_timeout}", ygopro.constants.COLORS.BABYBLUE)
player.arena_quit_free = true
else if waited_time >= 5000 and waited_time < 6000
ygopro.stoc_send_chat(player, "${arena_wait_hint}", ygopro.constants.COLORS.BABYBLUE)
return
, 1000
if settings.modules.heartbeat_detection.enabled
setInterval ()->
for room in ROOM_all when room and room.duel_stage != ygopro.constants.DUEL_STAGE.BEGIN and (room.hostinfo.time_limit == 0 or room.duel_stage != ygopro.constants.DUEL_STAGE.DUELING) and !room.windbot
for player in room.get_playing_player() when player and (room.duel_stage != ygopro.constants.DUEL_STAGE.SIDING or player.selected_preduel)
CLIENT_heartbeat_register(player, true)
return
, settings.modules.heartbeat_detection.interval
if settings.modules.windbot.enabled and settings.modules.windbot.spawn
spawn_windbot()
setInterval ()->
current_time = moment()
for room in ROOM_all when room and room.duel_stage != ygopro.constants.DUEL_STAGE.BEGIN and room.hostinfo.auto_death and !room.auto_death_triggered and current_time - moment(room.start_time) > 60000 * room.hostinfo.auto_death
room.auto_death_triggered = true
room.start_death()
, 1000
net.createServer(netRequestHandler).listen settings.port, ->
log.info "server started", settings.port
return
refresh_challonge_cache()
# challonge.participants._index({
# id: settings.modules.challonge.tournament_id,
# callback: (() ->
# challonge.matches._index({
# id: settings.modules.challonge.tournament_id,
# callback: (() ->
# return
# )
# })
# return
# )
# })
if settings.modules.challonge.cache_ttl
setInterval(refresh_challonge_cache, settings.modules.challonge.cache_ttl)
if settings.modules.stop
log.info "NOTE: server not open due to config, ", settings.modules.stop
http_server = http.createServer(httpRequestListener)
http_server.listen settings.modules.http.port
if settings.modules.http.ssl.enabled
https = require 'https'
options =
cert: await fs.promises.readFile(settings.modules.http.ssl.cert)
key: await fs.promises.readFile(settings.modules.http.ssl.key)
https_server = https.createServer(options, httpRequestListener)
if settings.modules.http.websocket_roomlist and roomlist
roomlist.init https_server, ROOM_all
https_server.listen settings.modules.http.ssl.port
mkdirList = [
"./plugins",
settings.modules.tournament_mode.deck_path,
settings.modules.tournament_mode.replay_path,
settings.modules.tournament_mode.log_save_path,
settings.modules.deck_log.local
]
for path in mkdirList
await createDirectoryIfNotExists(path)
plugin_list = await fs.promises.readdir("./plugins")
for plugin_filename in plugin_list
plugin_path = process.cwd() + "/plugins/" + plugin_filename
require(plugin_path)
log.info("Plugin loaded:", plugin_filename)
return
# 获取可用内存
memory_usage = global.memory_usage = 0
get_memory_usage = get_memory_usage = ()->
prc_free = exec("free")
prc_free.stdout.on 'data', (data)->
lines = data.toString().split(/\n/g)
line = lines[0].split(/\s+/)
new_free = if line[6] == 'available' then true else false
line = lines[1].split(/\s+/)
total = parseInt(line[1], 10)
free = parseInt(line[3], 10)
buffers = parseInt(line[5], 10)
if new_free
actualFree = parseInt(line[6], 10)
else
cached = parseInt(line[6], 10)
actualFree = free + buffers + cached
percentUsed = parseFloat(((1 - (actualFree / total)) * 100).toFixed(2))
memory_usage = global.memory_usage = percentUsed
return
get_memory_usage = global.get_memory_usage = ()->
percentUsed = os.freemem() / os.totalmem() * 100
memory_usage = global.memory_usage = percentUsed
return
get_memory_usage()
setInterval(get_memory_usage, 3000)
Cloud_replay_ids = global.Cloud_replay_ids = []
ROOM_all = global.ROOM_all = []
ROOM_players_oppentlist = global.ROOM_players_oppentlist = {}
ROOM_players_banned = global.ROOM_players_banned = []
ROOM_players_scores = global.ROOM_players_scores = {}
ROOM_connected_ip = global.ROOM_connected_ip = {}
ROOM_bad_ip = global.ROOM_bad_ip = {}
# ban a user manually and permanently
ban_user = global.ban_user = (name, callback) ->
settings.ban.banned_user.push(name)
setting_save(settings)
bad_ip = []
_async.each(ROOM_all, (room, done)->
if !(room and room.established)
done()
return
_async.each(["players", "watchers"], (player_type, _done)->
_async.each(room[player_type], (player, __done)->
if player and (player.name == name or bad_ip.indexOf(player.ip) != -1)
bad_ip.push(player.ip)
ROOM_bad_ip[bad_ip]=99
settings.ban.banned_ip.push(player.ip)
ygopro.stoc_send_chat_to_room(room, "#{player.name} ${kicked_by_system}", ygopro.constants.COLORS.RED)
CLIENT_send_replays(player, room)
CLIENT_kick(player)
__done()
, _done)
, done)
, callback)
ban_user = global.ban_user = (name) ->
if !settings.modules.mysql.enabled
throw "MySQL is not enabled"
bans = [dataManager.getBan(name, null)]
for room in ROOM_all when room and room.established
for playerType in ["players", "watchers"]
for player in room[playerType] when player.name == name or bans.find(ban => player.ip == ban.ip)
bans.push(dataManager.getBan(name, player.ip))
ROOM_bad_ip[player.ip]=99
ygopro.stoc_send_chat_to_room(room, "#{player.name} ${kicked_by_system}", ygopro.constants.COLORS.RED)
CLIENT_send_replays(player, room)
CLIENT_kick(player)
for ban in bans
await dataManager.banPlayer(ban)
return
# automatically ban user to use random duel
ROOM_ban_player = global.ROOM_ban_player = (name, ip, reason, countadd = 1)->
return if settings.modules.test_mode.no_ban_player
bannedplayer = _.find ROOM_players_banned, (bannedplayer)->
ip == bannedplayer.ip
if bannedplayer
bannedplayer.count = bannedplayer.count + countadd
bantime = if bannedplayer.count > 3 then Math.pow(2, bannedplayer.count - 3) * 2 else 0
bannedplayer.time = if moment() < bannedplayer.time then moment(bannedplayer.time).add(bantime, 'm') else moment().add(bantime, 'm')
bannedplayer.reasons.push(reason) if not _.find bannedplayer.reasons, (bannedreason)->
bannedreason == reason
bannedplayer.need_tip = true
else
bannedplayer = {"ip": ip, "time": moment(), "count": countadd, "reasons": [reason], "need_tip": true}
ROOM_players_banned.push(bannedplayer)
#log.info("banned", name, ip, reason, bannedplayer.count)
return if settings.modules.test_mode.no_ban_player or !settings.modules.mysql.enabled
await dataManager.randomDuelBanPlayer(ip, reason, countadd)
return
ROOM_kick = (name, callback)->
......@@ -537,7 +658,7 @@ ROOM_kick = (name, callback)->
done()
return
found = true
room.termiate()
room.terminate()
done()
, (err)->
callback(null, found)
......@@ -546,92 +667,65 @@ ROOM_kick = (name, callback)->
ROOM_player_win = global.ROOM_player_win = (name)->
if !ROOM_players_scores[name]
ROOM_players_scores[name]={win:0, lose:0, flee:0, combo:0}
ROOM_players_scores[name].win = ROOM_players_scores[name].win + 1
ROOM_players_scores[name].combo = ROOM_players_scores[name].combo + 1
if !settings.modules.mysql.enabled
return
await dataManager.randomDuelPlayerWin(name)
return
ROOM_player_lose = global.ROOM_player_lose = (name)->
if !ROOM_players_scores[name]
ROOM_players_scores[name]={win:0, lose:0, flee:0, combo:0}
ROOM_players_scores[name].lose = ROOM_players_scores[name].lose + 1
ROOM_players_scores[name].combo = 0
if !settings.modules.mysql.enabled
return
await dataManager.randomDuelPlayerLose(name)
return
ROOM_player_flee = global.ROOM_player_flee = (name)->
if !ROOM_players_scores[name]
ROOM_players_scores[name]={win:0, lose:0, flee:0, combo:0}
ROOM_players_scores[name].flee = ROOM_players_scores[name].flee + 1
ROOM_players_scores[name].combo = 0
if !settings.modules.mysql.enabled
return
await dataManager.randomDuelPlayerFlee(name)
return
ROOM_player_get_score = global.ROOM_player_get_score = (player)->
name = player.name_vpass
score = ROOM_players_scores[name]
if !score
return "#{player.name} ${random_score_blank}"
total = score.win + score.lose
if score.win < 2 and total < 3
return "#{player.name} ${random_score_not_enough}"
if score.combo >= 2
return "${random_score_part1}#{player.name} ${random_score_part2} #{Math.ceil(score.win/total*100)}${random_score_part3} #{Math.ceil(score.flee/total*100)}${random_score_part4_combo}#{score.combo}${random_score_part5_combo}"
#return player.name + " 的今日战绩:胜率" + Math.ceil(score.win/total*100) + "%,逃跑率" + Math.ceil(score.flee/total*100) + "%," + score.combo + "连胜中!"
else
return "${random_score_part1}#{player.name} ${random_score_part2} #{Math.ceil(score.win/total*100)}${random_score_part3} #{Math.ceil(score.flee/total*100)}${random_score_part4}"
return
if settings.modules.random_duel.post_match_scores
setInterval(()->
scores_pair = _.pairs ROOM_players_scores
scores_by_lose = _.sortBy(scores_pair, (score)-> return score[1].lose).reverse() # 败场由高到低
scores_by_win = _.sortBy(scores_by_lose, (score)-> return score[1].win).reverse() # 然后胜场由低到高,再逆转,就是先排胜场再排败场
scores = _.first(scores_by_win, 10)
#log.info scores
request.post { url : settings.modules.random_duel.post_match_scores , form : {
accesskey: settings.modules.random_duel.post_match_accesskey,
rank: JSON.stringify(scores)
}}, (error, response, body)=>
if error
log.warn 'RANDOM SCORE POST ERROR', error
else
if response.statusCode != 204 and response.statusCode != 200
log.warn 'RANDOM SCORE POST FAIL', response.statusCode, response.statusMessage, body
#else
# log.info 'RANDOM SCORE POST OK', response.statusCode, response.statusMessage
return
return
, 60000)
if !settings.modules.mysql.enabled
return ""
return await dataManager.getRandomDuelScoreDisplay(player.name_vpass)
ROOM_find_or_create_by_name = global.ROOM_find_or_create_by_name = (name, player_ip)->
uname=name.toUpperCase()
if settings.modules.windbot.enabled and (uname[0...2] == 'AI' or (!settings.modules.random_duel.enabled and uname == ''))
return ROOM_find_or_create_ai(name)
if settings.modules.random_duel.enabled and (uname == '' or uname == 'S' or uname == 'M' or uname == 'T')
return ROOM_find_or_create_random(uname, player_ip)
return await ROOM_find_or_create_random(uname, player_ip)
if room = ROOM_find_by_name(name)
return room
else if memory_usage >= 90
return null
else
return new Room(name)
room = new Room(name)
if room.recover_duel_log_id
success = await room.initialize_recover()
if !success
return {"error": "${cloud_replay_no}"}
return room
ROOM_find_or_create_random = global.ROOM_find_or_create_random = (type, player_ip)->
bannedplayer = _.find ROOM_players_banned, (bannedplayer)->
return player_ip == bannedplayer.ip
if bannedplayer
if bannedplayer.count > 6 and moment() < bannedplayer.time
return {"error": "${random_banned_part1}#{bannedplayer.reasons.join('${random_ban_reason_separator}')}${random_banned_part2}#{moment(bannedplayer.time).fromNow(true)}${random_banned_part3}"}
if bannedplayer.count > 3 and moment() < bannedplayer.time and bannedplayer.need_tip and type != 'T'
bannedplayer.need_tip = false
return {"error": "${random_deprecated_part1}#{bannedplayer.reasons.join('${random_ban_reason_separator}')}${random_deprecated_part2}#{moment(bannedplayer.time).fromNow(true)}${random_deprecated_part3}"}
else if bannedplayer.need_tip
bannedplayer.need_tip = false
return {"error": "${random_warn_part1}#{bannedplayer.reasons.join('${random_ban_reason_separator}')}${random_warn_part2}"}
else if bannedplayer.count > 2
bannedplayer.need_tip = true
if settings.modules.mysql.enabled
randomDuelBanRecord = await dataManager.getRandomDuelBan(player_ip)
if randomDuelBanRecord
if randomDuelBanRecord.count > 6 and moment().isBefore(randomDuelBanRecord.time)
return {"error": "${random_banned_part1}#{randomDuelBanRecord.reasons.join('${random_ban_reason_separator}')}${random_banned_part2}#{moment(randomDuelBanRecord.time).fromNow(true)}${random_banned_part3}"}
if randomDuelBanRecord.count > 3 and moment().isBefore(randomDuelBanRecord.time) and randomDuelBanRecord.getNeedTip() and type != 'T'
randomDuelBanRecord.setNeedTip(false)
await dataManager.updateRandomDuelBan(randomDuelBanRecord)
return {"error": "${random_deprecated_part1}#{randomDuelBanRecord.reasons.join('${random_ban_reason_separator}')}${random_deprecated_part2}#{moment(randomDuelBanRecord.time).fromNow(true)}${random_deprecated_part3}"}
else if randomDuelBanRecord.getNeedTip()
randomDuelBanRecord.setNeedTip(false)
await dataManager.updateRandomDuelBan(randomDuelBanRecord)
return {"error": "${random_warn_part1}#{randomDuelBanRecord.reasons.join('${random_ban_reason_separator}')}${random_warn_part2}"}
else if randomDuelBanRecord.count > 2
randomDuelBanRecord.setNeedTip(true)
await dataManager.updateRandomDuelBan(randomDuelBanRecord)
max_player = if type == 'T' then 4 else 2
playerbanned = (bannedplayer and bannedplayer.count > 3 and moment() < bannedplayer.time)
playerbanned = (randomDuelBanRecord and randomDuelBanRecord.count > 3 and moment() < randomDuelBanRecord.time)
result = _.find ROOM_all, (room)->
return room and room.random_type != '' and room.duel_stage == ygopro.constants.DUEL_STAGE.BEGIN and !room.windbot and
((type == '' and
......@@ -868,13 +962,13 @@ CLIENT_is_able_to_reconnect = global.CLIENT_is_able_to_reconnect = (client, deck
if !room
CLIENT_reconnect_unregister(client)
return false
if deckbuf and !_.isEqual(deckbuf, disconnect_info.deckbuf)
if deckbuf and deckbuf.compare(disconnect_info.deckbuf) != 0
return false
return true
CLIENT_get_kick_reconnect_target = global.CLIENT_get_kick_reconnect_target = (client, deckbuf) ->
for room in ROOM_all when room and room.duel_stage != ygopro.constants.DUEL_STAGE.BEGIN and !room.windbot
for player in room.get_playing_player() when !player.closed and player.name == client.name and (settings.modules.challonge.enabled or player.pass == client.pass) and (settings.modules.mycard.enabled or settings.modules.tournament_mode.enabled or player.ip == client.ip or (client.vpass and client.vpass == player.vpass)) and (!deckbuf or _.isEqual(player.start_deckbuf, deckbuf))
for player in room.get_playing_player() when !player.closed and player.name == client.name and (settings.modules.challonge.enabled or player.pass == client.pass) and (settings.modules.mycard.enabled or settings.modules.tournament_mode.enabled or player.ip == client.ip or (client.vpass and client.vpass == player.vpass)) and (!deckbuf or deckbuf.compare(player.start_deckbuf) == 0)
return player
return null
......@@ -998,9 +1092,6 @@ CLIENT_kick_reconnect = global.CLIENT_kick_reconnect = (client, deckbuf) ->
CLIENT_reconnect_unregister(client, true)
return
if settings.modules.reconnect.enabled
disconnect_list = {} # {old_client, old_server, room_id, timeout, deckbuf}
CLIENT_heartbeat_unregister = global.CLIENT_heartbeat_unregister = (client) ->
if !settings.modules.heartbeat_detection.enabled or !client.heartbeat_timeout
return false
......@@ -1209,14 +1300,7 @@ class Room
@recovered = true
@recovering = true
@recover_from_turn = parseInt(param[4])
duel_log_id = parseInt(param[3])
@recover_duel_log = _.find(duel_log.duel_log, (duel) ->
return duel.id == duel_log_id and duel.roommode != 2 and duel.players[0].deck
)
if !@recover_duel_log || !fs.existsSync(settings.modules.tournament_mode.replay_path + @recover_duel_log.replay_filename)
@error = "${cloud_replay_no}"
return
@recover_replay = ReplayParser.fromFile(settings.modules.tournament_mode.replay_path + @recover_duel_log.replay_filename)
@recover_duel_log_id = parseInt(param[3])
@recover_buffers = [[], [], [], []]
@welcome = "${recover_hint}"
......@@ -1227,12 +1311,16 @@ class Room
if (settings.modules.tournament_mode.enabled and settings.modules.tournament_mode.block_replay_to_player) or (@hostinfo.mode == 1 and settings.modules.replay_delay)
@hostinfo.replay_mode |= 0x2
if !@recovered
@spawn()
spawn: (firstSeed) ->
param = [0, @hostinfo.lflist, @hostinfo.rule, @hostinfo.mode, @hostinfo.duel_rule,
(if @hostinfo.no_check_deck then 'T' else 'F'), (if @hostinfo.no_shuffle_deck then 'T' else 'F'),
@hostinfo.start_lp, @hostinfo.start_hand, @hostinfo.draw_count, @hostinfo.time_limit, @hostinfo.replay_mode]
if @recovered
param.push(@recover_replay.header.seed)
if firstSeed
param.push(firstSeed)
seeds = getSeedTimet(2)
param.push(seeds[i]) for i in [0...2]
else
......@@ -1358,33 +1446,12 @@ class Room
replay_id = @cloud_replay_id
if @has_ygopro_error
log_rep_id = true
player_names=@player_datas[0].name + (if @player_datas[2] then "+" + @player_datas[2].name else "") +
" VS " +
(if @player_datas[1] then @player_datas[1].name else "AI") +
(if @player_datas[3] then "+" + @player_datas[3].name else "")
player_ips=[]
_.each @player_datas, (player)->
player_ips.push(player.key)
return
recorder_buffer=Buffer.concat(@recorder_buffers)
player_datas = @player_datas
zlib.deflate recorder_buffer, (err, replay_buffer) ->
replay_buffer=replay_buffer.toString('binary')
#log.info err, replay_buffer
date_time=moment().format('YYYY-MM-DD HH:mm:ss')
#replay_id=Math.floor(Math.random()*100000000)
redisdb.hmset("replay:"+replay_id,
"replay_id", replay_id,
"replay_buffer", replay_buffer,
"player_names", player_names,
"date_time", date_time)
if !log_rep_id and !settings.modules.cloud_replay.never_expire
redisdb.expire("replay:"+replay_id, 60*60*24)
recorded_ip=[]
_.each player_ips, (player_ip)->
return if _.contains(recorded_ip, player_ip)
recorded_ip.push player_ip
redisdb.lpush(player_ip+":replays", replay_id)
return
dataManager.saveCloudReplay(replay_id, replay_buffer, player_datas).catch((err) ->
log.warn("Replay save error: R##{replay_id} #{err.toString()}")
)
if log_rep_id
log.info "error replay: R#" + replay_id
return
......@@ -1402,6 +1469,23 @@ class Room
roomlist.delete this if !@windbot and @established and settings.modules.http.websocket_roomlist
return
initialize_recover: ->
@recover_duel_log = await dataManager.getDuelLogFromId(@recover_duel_log_id)
#console.log(@recover_duel_log, fs.existsSync(settings.modules.tournament_mode.replay_path + @recover_duel_log.replayFileName))
if !@recover_duel_log || !fs.existsSync(settings.modules.tournament_mode.replay_path + @recover_duel_log.replayFileName)
@terminate()
return false
try
@recover_replay = await ReplayParser.fromFile(settings.modules.tournament_mode.replay_path + @recover_duel_log.replayFileName)
@spawn(@recover_replay.header.seed)
return true
catch e
log.warn("LOAD RECOVER REPLAY FAIL", e.toString())
@terminate()
return false
get_playing_player: ->
playing_player = []
_.each @players, (player)->
......@@ -1593,19 +1677,22 @@ class Room
ygopro.stoc_send_chat_to_room(this, "${death_cancel}", ygopro.constants.COLORS.BABYBLUE)
return true
termiate: ->
terminate: ->
if @duel_stage != ygopro.constants.DUEL_STAGE.BEGIN
@scores[@dueling_players[0].name_vpass] = 0
@scores[@dueling_players[1].name_vpass] = 0
@kicked = true
@send_replays()
@process.kill()
if @process
try
@process.kill()
catch e
@delete()
finish_recover: (fail) ->
if fail
ygopro.stoc_send_chat_to_room(this, "${recover_fail}", ygopro.constants.COLORS.RED)
@termiate()
@terminate()
else
ygopro.stoc_send_chat_to_room(this, "${recover_success}", ygopro.constants.COLORS.BABYBLUE)
@recovering = false
......@@ -1628,7 +1715,7 @@ class Room
await return
# 网络连接
net.createServer (client) ->
netRequestHandler = (client) ->
client.ip = client.remoteAddress
client.is_local = client.ip and (client.ip.includes('127.0.0.1') or client.ip.includes(real_windbot_server_ip))
......@@ -1725,22 +1812,21 @@ net.createServer (client) ->
return
if settings.modules.cloud_replay.enabled
client.open_cloud_replay= (err, replay)->
if err or !replay
client.open_cloud_replay = (replay)->
if !replay
ygopro.stoc_die(client, "${cloud_replay_no}")
return
redisdb.expire("replay:"+replay.replay_id, 60*60*48)
buffer=Buffer.from(replay.replay_buffer,'binary')
zlib.unzip buffer, (err, replay_buffer) ->
if err
log.info "cloud replay unzip error: " + err
ygopro.stoc_send_chat(client, "${cloud_replay_error}", ygopro.constants.COLORS.RED)
CLIENT_kick(client)
return
ygopro.stoc_send_chat(client, "${cloud_replay_playing} R##{replay.replay_id} #{replay.player_names} #{replay.date_time}", ygopro.constants.COLORS.BABYBLUE)
client.write replay_buffer, ()->
CLIENT_kick(client)
return
buffer=replay.toBuffer()
replay_buffer = null
try
replay_buffer = await util.promisify(zlib.unzip)(buffer)
catch e
log.info "cloud replay unzip error: " + err
ygopro.stoc_die(client, "${cloud_replay_error}")
return
ygopro.stoc_send_chat(client, "${cloud_replay_playing} #{replay.getDisplayString()}", ygopro.constants.COLORS.BABYBLUE)
client.write replay_buffer, ()->
CLIENT_kick(client)
return
return
......@@ -1809,12 +1895,6 @@ net.createServer (client) ->
return
return
.listen settings.port, ->
log.info "server started", settings.port
return
if settings.modules.stop
log.info "NOTE: server not open due to config, ", settings.modules.stop
deck_name_match = global.deck_name_match = (deck_name, player_name) ->
if deck_name == player_name or deck_name == player_name + ".ydk" or deck_name == player_name + ".ydk.ydk"
......@@ -1891,66 +1971,34 @@ ygopro.ctos_follow 'JOIN_GAME', true, (buffer, info, client, server, datas)->
else if info.pass.toUpperCase()=="R" and settings.modules.cloud_replay.enabled
ygopro.stoc_send_chat(client,"${cloud_replay_hint}", ygopro.constants.COLORS.BABYBLUE)
redisdb.lrange CLIENT_get_authorize_key(client)+":replays", 0, 2, (err, result)->
_.each result, (replay_id,id)->
redisdb.hgetall "replay:"+replay_id, (err, replay)->
if err or !replay
log.info "cloud replay getall error: " + err if err
return
ygopro.stoc_send_chat(client,"<#{id-0+1}> R##{replay_id} #{replay.player_names} #{replay.date_time}", ygopro.constants.COLORS.BABYBLUE)
return
return
return
# 强行等待异步执行完毕_(:з」∠)_
setTimeout (()->
ygopro.stoc_send client, 'ERROR_MSG',{
msg: 1
code: 9
}
CLIENT_kick(client)
return), 500
replays = await dataManager.getCloudReplaysFromKey(CLIENT_get_authorize_key(client))
for replay,index in replays
ygopro.stoc_send_chat(client,"<#{index + 1}> #{replay.getDisplayString()}", ygopro.constants.COLORS.BABYBLUE)
ygopro.stoc_send client, 'ERROR_MSG', {
msg: 1
code: 9
}
CLIENT_kick(client)
else if info.pass.toUpperCase()=="RC" and settings.modules.tournament_mode.enable_recover
ygopro.stoc_send_chat(client,"${recover_replay_hint}", ygopro.constants.COLORS.BABYBLUE)
available_logs = duel_log.duel_log.filter((duel) ->
return duel.id and duel.players[0].deck and duel.roommode != 2 and _.any(duel.players, (player) ->
return player.real_name == client.name_vpass
)
).slice(0, 8)
_.each(available_logs, (duel) ->
player_names = duel.players[0].real_name.split("$")[0] + (if duel.players[2] then "+" + duel.players[2].real_name.split("$")[0] else "") +
" VS " +
(if duel.players[1] then duel.players[1].real_name.split("$")[0] else "AI") +
(if duel.players[3] then "+" + duel.players[3].real_name.split("$")[0] else "")
ygopro.stoc_send_chat(client,"<#{duel.id}> #{player_names} #{duel.time}", ygopro.constants.COLORS.BABYBLUE)
)
# 强行等待异步执行完毕_(:з」∠)_
setTimeout (()->
ygopro.stoc_send client, 'ERROR_MSG',{
msg: 1
code: 9
}
CLIENT_kick(client)
return), 500
available_logs = await dataManager.getDuelLogFromRecoverSearch(client.name_vpass)
for duelLog in available_logs
ygopro.stoc_send_chat(client, duelLog.getViewString(), ygopro.constants.COLORS.BABYBLUE)
ygopro.stoc_send client, 'ERROR_MSG',{
msg: 1
code: 9
}
CLIENT_kick(client)
else if info.pass[0...2].toUpperCase()=="R#" and settings.modules.cloud_replay.enabled
replay_id=info.pass.split("#")[1]
if (replay_id>0 and replay_id<=9)
redisdb.lindex client.ip+":replays", replay_id-1, (err, replay_id)->
if err or !replay_id
log.info "cloud replay replayid error: " + err if err
ygopro.stoc_die(client, "${cloud_replay_no}")
return
redisdb.hgetall "replay:"+replay_id, client.open_cloud_replay
return
else if replay_id
redisdb.hgetall "replay:"+replay_id, client.open_cloud_replay
else
ygopro.stoc_die(client, "${cloud_replay_no}")
replay = await dataManager.getCloudReplayFromId(replay_id)
await client.open_cloud_replay(replay)
else if info.pass.toUpperCase()=="W" and settings.modules.cloud_replay.enabled
replay_id=Cloud_replay_ids[Math.floor(Math.random()*Cloud_replay_ids.length)]
redisdb.hgetall "replay:"+replay_id, client.open_cloud_replay
replay = await dataManager.getRandomCloudReplay()
await client.open_cloud_replay(replay)
else if info.version != settings.version and !settings.alternative_versions.includes(info.version)
ygopro.stoc_send_chat(client, settings.modules.update, ygopro.constants.COLORS.RED)
......@@ -2062,7 +2110,7 @@ ygopro.ctos_follow 'JOIN_GAME', true, (buffer, info, client, server, datas)->
if match_permit and !match_permit.permit
ygopro.stoc_die(client, '${invalid_password_unauthorized}')
return
room = ROOM_find_or_create_by_name('M#' + info.pass.slice(8))
room = await ROOM_find_or_create_by_name('M#' + info.pass.slice(8))
if room
for player in room.get_playing_player() when player and player.name == client.name
ygopro.stoc_die(client, '${invalid_password_unauthorized}')
......@@ -2189,6 +2237,8 @@ ygopro.ctos_follow 'JOIN_GAME', true, (buffer, info, client, server, datas)->
if(err)
ygopro.stoc_die(client, err)
return
create_room_with_action(data.get_user.original, data.get_user.decrypted, data.match_permit)
)
......@@ -2260,7 +2310,7 @@ ygopro.ctos_follow 'JOIN_GAME', true, (buffer, info, client, server, datas)->
create_room_name = 'M#' + found.id
if recover_match
create_room_name = recover_match[0] + ',' + create_room_name
room = ROOM_find_or_create_by_name(create_room_name)
room = await ROOM_find_or_create_by_name(create_room_name)
if room
room.challonge_info = found
# room.max_player = 2
......@@ -2301,13 +2351,15 @@ ygopro.ctos_follow 'JOIN_GAME', true, (buffer, info, client, server, datas)->
log.warn("MULTI LOGIN", client.name, client.ip)
ygopro.stoc_die(client, "${too_much_connection}" + client.ip)
else if _.indexOf(settings.ban.banned_user, client.name) > -1 #账号被封
settings.ban.banned_ip.push(client.ip)
setting_save(settings)
else if settings.modules.mysql.enabled and await dataManager.checkBan("name", client.name) #账号被封
exactBan = await dataManager.checkBanWithNameAndIP(client.name, client.ip)
if !exactBan
exactBan = dataManager.getBan(client.name, client.ip)
await dataManager.banPlayer(exactBan)
log.warn("BANNED USER LOGIN", client.name, client.ip)
ygopro.stoc_die(client, "${banned_user_login}")
else if _.indexOf(settings.ban.banned_ip, client.ip) > -1 #IP被封
else if settings.modules.mysql.enabled and await dataManager.checkBan("ip", client.ip) #IP被封
log.warn("BANNED IP LOGIN", client.name, client.ip)
ygopro.stoc_die(client, "${banned_ip_login}")
......@@ -2344,7 +2396,7 @@ ygopro.ctos_follow 'JOIN_GAME', true, (buffer, info, client, server, datas)->
buffer = struct.buffer
#log.info 'join_game',info.pass, client.name
room = ROOM_find_or_create_by_name(info.pass, client.ip)
room = await ROOM_find_or_create_by_name(info.pass, client.ip)
if !room
ygopro.stoc_die(client, "${server_full}")
else if room.error
......@@ -2397,9 +2449,9 @@ ygopro.stoc_follow 'JOIN_GAME', false, (buffer, info, client, server, datas)->
#client.score_shown = true
return
if settings.modules.random_duel.record_match_scores and room.random_type == 'M'
ygopro.stoc_send_chat_to_room(room, ROOM_player_get_score(client), ygopro.constants.COLORS.GREEN)
ygopro.stoc_send_chat_to_room(room, await ROOM_player_get_score(client), ygopro.constants.COLORS.GREEN)
for player in room.players when player.pos != 7 and player != client
ygopro.stoc_send_chat(client, ROOM_player_get_score(player), ygopro.constants.COLORS.GREEN)
ygopro.stoc_send_chat(client, await ROOM_player_get_score(player), ygopro.constants.COLORS.GREEN)
if !room.recorder
room.recorder = recorder = net.connect room.port, ->
ygopro.ctos_send recorder, 'PLAYER_INFO', {
......@@ -2447,25 +2499,8 @@ ygopro.stoc_follow 'JOIN_GAME', false, (buffer, info, client, server, datas)->
await return
# 登场台词
load_dialogues = global.load_dialogues = (callback) ->
request
url: settings.modules.dialogues.get
json: true
, (error, response, body)->
if _.isString body
log.warn "dialogues bad json", body
else if error or !body
log.warn 'dialogues error', error, response
else
setting_change(dialogues, "dialogues", body)
log.info "dialogues loaded", _.size dialogues.dialogues
if callback
callback(error, body)
return
await return
if settings.modules.dialogues.get
load_dialogues()
load_dialogues = global.load_dialogues = () ->
return await loadRemoteData(dialogues, "dialogues", settings.modules.dialogues.get)
ygopro.stoc_follow 'GAME_MSG', true, (buffer, info, client, server, datas)->
room=ROOM_all[client.rid]
......@@ -2788,7 +2823,7 @@ ygopro.ctos_follow 'HS_KICK', true, (buffer, info, client, server, datas)->
client.kick_count = if client.kick_count then client.kick_count+1 else 1
if client.kick_count>=5 and room.random_type
ygopro.stoc_send_chat_to_room(room, "#{client.name} ${kicked_by_system}", ygopro.constants.COLORS.RED)
ROOM_ban_player(player.name, player.ip, "${random_ban_reason_zombie}")
await ROOM_ban_player(player.name, player.ip, "${random_ban_reason_zombie}")
CLIENT_kick(client)
return true
ygopro.stoc_send_chat_to_room(room, "#{player.name} ${kicked_by_player}", ygopro.constants.COLORS.RED)
......@@ -2903,7 +2938,7 @@ wait_room_start = (room, time)->
else
for player in room.players
if player and player.is_host
ROOM_ban_player(player.name, player.ip, "${random_ban_reason_zombie}")
await ROOM_ban_player(player.name, player.ip, "${random_ban_reason_zombie}")
ygopro.stoc_send_chat_to_room(room, "#{player.name} ${kicked_by_system}", ygopro.constants.COLORS.RED)
CLIENT_kick(player)
await return
......@@ -2934,30 +2969,26 @@ ygopro.stoc_send_random_tip_to_room = (room)->
ygopro.stoc_send_chat_to_room(room, "Tip: " + tips.tips[Math.floor(Math.random() * tips.tips.length)])
await return
load_tips = global.load_tips = (callback)->
request
url: settings.modules.tips.get
json: true
, (error, response, body)->
loadRemoteData = global.loadRemoteData = (loadObject, name, url)->
try
body = (await axios.get(url, {
responseType: "json"
})).data
if _.isString body
log.warn "tips bad json", body
else if error or !body
log.warn 'tips error', error, response
else
setting_change(tips, "tips", body)
log.info "tips loaded", tips.tips.length
if callback
callback(error, body)
return
await return
log.warn "#{name} bad json", body
return false
if !body
log.warn "#{name} empty", body
return false
await setting_change(loadObject, name, body)
log.info "#{name} loaded"
return true
catch e
log.warn "#{name} error", e
return false
if settings.modules.tips.get
load_tips()
setInterval ()->
for room in ROOM_all when room and room.established
ygopro.stoc_send_random_tip_to_room(room) if room.duel_stage == ygopro.constants.DUEL_STAGE.SIDING or room.duel_stage == ygopro.constants.DUEL_STAGE.BEGIN
return
, 30000
load_tips = global.load_tips = ()->
return await loadRemoteData(tips, "tips", settings.modules.tips.get)
ygopro.stoc_follow 'DUEL_START', false, (buffer, info, client, server, datas)->
room=ROOM_all[client.rid]
......@@ -2969,10 +3000,10 @@ ygopro.stoc_follow 'DUEL_START', false, (buffer, info, client, server, datas)->
roomlist.start room if !room.windbot and settings.modules.http.websocket_roomlist
#room.duels = []
room.dueling_players = []
for player in room.players when player.pos != 7
for player in room.get_playing_player()
room.dueling_players[player.pos] = player
room.scores[player.name_vpass] = 0
room.player_datas.push key: CLIENT_get_authorize_key(player), name: player.name
room.player_datas.push key: CLIENT_get_authorize_key(player), name: player.name, pos: player.pos
if room.random_type == 'T'
# 双打房不记录匹配过
ROOM_players_oppentlist[player.ip] = null
......@@ -3123,19 +3154,18 @@ ygopro.ctos_follow 'CHAT', true, (buffer, info, client, server, datas)->
for cname,cvalue of ygopro.constants.COLORS when cvalue > 10
ygopro.stoc_send_chat(client, cname, cvalue)
else if cmsg.toLowerCase() == "default"
chat_color.save_list[cip] = false
setting_save(chat_color)
await dataManager.setUserChatColor(cip, null)
ygopro.stoc_send_chat(client, "${set_chat_color_default}", ygopro.constants.COLORS.BABYBLUE)
else
ccolor = cmsg.toUpperCase()
if ygopro.constants.COLORS[ccolor] and ygopro.constants.COLORS[ccolor] > 10 and ygopro.constants.COLORS[ccolor] < 20
chat_color.save_list[cip] = ccolor
setting_save(chat_color)
await dataManager.setUserChatColor(cip, ccolor)
ygopro.stoc_send_chat(client, "${set_chat_color_part1}" + ccolor + "${set_chat_color_part2}", ygopro.constants.COLORS.BABYBLUE)
else
ygopro.stoc_send_chat(client, "${color_not_found_part1}" + ccolor + "${color_not_found_part2}", ygopro.constants.COLORS.RED)
else
if color = chat_color.save_list[cip]
color = await dataManager.getUserChatColor(cip)
if color
ygopro.stoc_send_chat(client, "${get_chat_color_part1}" + color + "${get_chat_color_part2}", ygopro.constants.COLORS.BABYBLUE)
else
ygopro.stoc_send_chat(client, "${get_chat_color_default}", ygopro.constants.COLORS.BABYBLUE)
......@@ -3166,8 +3196,8 @@ ygopro.ctos_follow 'CHAT', true, (buffer, info, client, server, datas)->
cancel = true
if client.abuse_count>0
ygopro.stoc_send_chat(client, "${banned_duel_tip}", ygopro.constants.COLORS.RED)
ROOM_ban_player(client.name, client.ip, "${random_ban_reason_abuse}")
ROOM_ban_player(client.name, client.ip, "${random_ban_reason_abuse}", 3)
await ROOM_ban_player(client.name, client.ip, "${random_ban_reason_abuse}")
await ROOM_ban_player(client.name, client.ip, "${random_ban_reason_abuse}", 3)
CLIENT_send_replays(client, room)
CLIENT_kick(client)
return true
......@@ -3221,7 +3251,7 @@ ygopro.ctos_follow 'CHAT', true, (buffer, info, client, server, datas)->
ROOM_unwelcome(room, client, "${random_ban_reason_abuse}")
if client.abuse_count>=5
ygopro.stoc_send_chat_to_room(room, "#{client.name} ${chat_banned}", ygopro.constants.COLORS.RED)
ROOM_ban_player(client.name, client.ip, "${random_ban_reason_abuse}")
await ROOM_ban_player(client.name, client.ip, "${random_ban_reason_abuse}")
if !cancel and settings.modules.display_watchers and client.is_post_watcher
ygopro.stoc_send_chat_to_room(room, "#{client.name}: #{msg}", 9)
return true
......@@ -3278,13 +3308,14 @@ ygopro.ctos_follow 'UPDATE_DECK', true, (buffer, info, client, server, datas)->
room.last_active_time = moment()
if room.duel_stage == ygopro.constants.DUEL_STAGE.BEGIN and room.recovering
recover_player_data = _.find(room.recover_duel_log.players, (player) ->
return player.real_name == client.name_vpass and _.isEqual(buffer, Buffer.from(player.deckbuf, "base64"))
return player.realName == client.name_vpass and buffer.compare(Buffer.from(player.startDeckBuffer, "base64")) == 0
)
if recover_player_data
struct.set("mainc", recover_player_data.deck.main.length)
struct.set("sidec", recover_player_data.deck.side.length)
struct.set("deckbuf", recover_player_data.deck.main.concat(recover_player_data.deck.side))
if recover_player_data.is_first
recoveredDeck = recover_player_data.getCurrentDeck()
struct.set("mainc", recoveredDeck.main.length)
struct.set("sidec", recoveredDeck.side.length)
struct.set("deckbuf", recoveredDeck.main.concat(recoveredDeck.side))
if recover_player_data.isFirst
room.determine_firstgo = client
else
struct.set("mainc", 1)
......@@ -3429,7 +3460,7 @@ ygopro.stoc_follow 'CHAT', true, (buffer, info, client, server, datas)->
for player in room.players when player and player.pos == pid
tplayer = player
return unless tplayer
tcolor = chat_color.save_list[CLIENT_get_authorize_key(tplayer)]
tcolor = await dataManager.getUserChatColor(CLIENT_get_authorize_key(tplayer));
if tcolor
ygopro.stoc_send client, 'CHAT', {
player: ygopro.constants.COLORS[tcolor]
......@@ -3531,15 +3562,12 @@ ygopro.stoc_follow 'CHANGE_SIDE', false, (buffer, info, client, server, datas)->
ygopro.stoc_follow 'REPLAY', true, (buffer, info, client, server, datas)->
room=ROOM_all[client.rid]
return settings.modules.tournament_mode.enabled and settings.modules.tournament_mode.block_replay_to_player or settings.modules.replay_delay unless room
if settings.modules.cloud_replay.enabled and room.random_type
Cloud_replay_ids.push room.cloud_replay_id
if !room.replays[room.duel_count - 1]
# console.log("Replay saved: ", room.duel_count - 1, client.pos)
room.replays[room.duel_count - 1] = buffer
if settings.modules.tournament_mode.enabled and settings.modules.tournament_mode.replay_safe or settings.modules.tournament_mode.enable_recover
if settings.modules.mysql.enabled
if client.pos == 0
dueltime=moment().format('YYYY-MM-DD HH-mm-ss')
replay_filename=dueltime
replay_filename=moment().format("YYYY-MM-DD HH-mm-ss")
if room.hostinfo.mode != 2
for player,i in room.dueling_players
replay_filename=replay_filename + (if i > 0 then " VS " else " ") + player.name
......@@ -3547,127 +3575,34 @@ ygopro.stoc_follow 'REPLAY', true, (buffer, info, client, server, datas)->
for player,i in room.dueling_players
replay_filename=replay_filename + (if i > 0 then (if i == 2 then " VS " else " & ") else " ") + player.name
replay_filename=replay_filename.replace(/[\/\\\?\*]/g, '_')+".yrp"
duellog = {
id: duel_log.duel_log.length + 1,
time: dueltime,
name: room.name + (if settings.modules.tournament_mode.show_info then (" (Duel:" + room.duel_count + ")") else ""),
roomid: room.process_pid.toString(),
cloud_replay_id: "R#"+room.cloud_replay_id,
replay_filename: replay_filename,
roommode: room.hostinfo.mode,
players: (for player in room.dueling_players
real_name: player.name_vpass,
deckbuf: player.start_deckbuf.toString("base64"),
playerInfos = room.dueling_players.map((player) ->
return {
name: player.name
pos: player.pos
realName: player.name_vpass
startDeckBuffer: player.start_deckbuf
deck: {
main: player.main,
side: player.side
}
pos: player.pos
is_first: player.is_first
name: player.name + (if settings.modules.tournament_mode.show_ip and !player.is_local then (" (IP: " + player.ip.slice(7) + ")") else "") + (if settings.modules.tournament_mode.show_info and not (room.hostinfo.mode == 2 and player.pos % 2 > 0) then (" (Score:" + room.scores[player.name_vpass] + " LP:" + (if player.lp? then player.lp else room.hostinfo.start_lp) + (if room.hostinfo.mode != 2 then (" Cards:" + (if player.card_count? then player.card_count else room.hostinfo.start_hand)) else "") + ")") else ""),
isFirst: player.is_first
winner: player.pos == room.winner
)
}
duel_log.duel_log.unshift duellog
setting_save(duel_log)
ip: player.ip
score: room.scores[player.name_vpass]
lp: if player.lp? then player.lp else room.hostinfo.start_lp
cardCount: if player.card_count? then player.card_count else room.hostinfo.start_hand
}
)
fs.writeFile(settings.modules.tournament_mode.replay_path + replay_filename, buffer, (err)->
if err then log.warn "SAVE REPLAY ERROR", replay_filename, err
)
dataManager.saveDuelLog(room.name, room.process_pid.toString(), room.cloud_replay_id, replay_filename, room.hostinfo.mode, room.duel_count, playerInfos) # no synchronize here because too slow
if settings.modules.cloud_replay.enabled and settings.modules.tournament_mode.enabled and settings.modules.tournament_mode.replay_safe
ygopro.stoc_send_chat(client, "${cloud_replay_delay_part1}R##{room.cloud_replay_id}${cloud_replay_delay_part2}", ygopro.constants.COLORS.BABYBLUE)
await return settings.modules.tournament_mode.enabled and settings.modules.tournament_mode.block_replay_to_player or settings.modules.replay_delay and room.hostinfo.mode == 1
else
await return settings.modules.replay_delay and room.hostinfo.mode == 1
if settings.modules.random_duel.enabled
setInterval ()->
_async.each(ROOM_all, (room, done) ->
if !(room and room.duel_stage != ygopro.constants.DUEL_STAGE.BEGIN and room.random_type and room.last_active_time and room.waiting_for_player and room.get_disconnected_count() == 0 and (!settings.modules.side_timeout or room.duel_stage != ygopro.constants.DUEL_STAGE.SIDING) and !room.recovered)
done()
return
time_passed = Math.floor((moment() - room.last_active_time) / 1000)
#log.info time_passed
if time_passed >= settings.modules.random_duel.hang_timeout
room.last_active_time = moment()
ROOM_ban_player(room.waiting_for_player.name, room.waiting_for_player.ip, "${random_ban_reason_AFK}")
room.scores[room.waiting_for_player.name_vpass] = -9
#log.info room.waiting_for_player.name, room.scores[room.waiting_for_player.name_vpass]
ygopro.stoc_send_chat_to_room(room, "#{room.waiting_for_player.name} ${kicked_by_system}", ygopro.constants.COLORS.RED)
CLIENT_send_replays(room.waiting_for_player, room)
CLIENT_kick(room.waiting_for_player)
else if time_passed >= (settings.modules.random_duel.hang_timeout - 20) and not (time_passed % 10)
ygopro.stoc_send_chat_to_room(room, "#{room.waiting_for_player.name} ${afk_warn_part1}#{settings.modules.random_duel.hang_timeout - time_passed}${afk_warn_part2}", ygopro.constants.COLORS.RED)
ROOM_unwelcome(room, room.waiting_for_player, "${random_ban_reason_AFK}")
done()
return
)
return
, 1000
if settings.modules.mycard.enabled
setInterval ()->
_async.each(ROOM_all, (room, done) ->
if not (room and room.duel_stage != ygopro.constants.DUEL_STAGE.BEGIN and room.arena and room.last_active_time and room.waiting_for_player and room.get_disconnected_count() == 0 and (!settings.modules.side_timeout or room.duel_stage != ygopro.constants.DUEL_STAGE.SIDING) and !room.recovered)
done()
return
time_passed = Math.floor((moment() - room.last_active_time) / 1000)
#log.info time_passed
if time_passed >= settings.modules.random_duel.hang_timeout
room.last_active_time = moment()
ygopro.stoc_send_chat_to_room(room, "#{room.waiting_for_player.name} ${kicked_by_system}", ygopro.constants.COLORS.RED)
room.scores[room.waiting_for_player.name_vpass] = -9
#log.info room.waiting_for_player.name, room.scores[room.waiting_for_player.name_vpass]
CLIENT_send_replays(room.waiting_for_player, room)
CLIENT_kick(room.waiting_for_player)
else if time_passed >= (settings.modules.random_duel.hang_timeout - 20) and not (time_passed % 10)
ygopro.stoc_send_chat_to_room(room, "#{room.waiting_for_player.name} ${afk_warn_part1}#{settings.modules.random_duel.hang_timeout - time_passed}${afk_warn_part2}", ygopro.constants.COLORS.RED)
done()
return
)
if true # settings.modules.arena_mode.punish_quit_before_match
_async.each(ROOM_all, (room, done) ->
if not (room and room.arena and room.duel_stage == ygopro.constants.DUEL_STAGE.BEGIN and room.get_playing_player().length < 2)
done()
return
player = room.get_playing_player()[0]
if player and player.join_time and !player.arena_quit_free
waited_time = moment() - player.join_time
if waited_time >= 30000
ygopro.stoc_send_chat(player, "${arena_wait_timeout}", ygopro.constants.COLORS.BABYBLUE)
player.arena_quit_free = true
else if waited_time >= 5000 and waited_time < 6000
ygopro.stoc_send_chat(player, "${arena_wait_hint}", ygopro.constants.COLORS.BABYBLUE)
done()
return
)
return
, 1000
if settings.modules.heartbeat_detection.enabled
setInterval ()->
_async.each ROOM_all, (room, done)->
if room and room.duel_stage != ygopro.constants.DUEL_STAGE.BEGIN and (room.hostinfo.time_limit == 0 or room.duel_stage != ygopro.constants.DUEL_STAGE.DUELING) and !room.windbot
_async.each(room.get_playing_player(), (player, _done)->
if player and (room.duel_stage != ygopro.constants.DUEL_STAGE.SIDING or player.selected_preduel)
CLIENT_heartbeat_register(player, true)
_done()
, done)
else
done()
return
, settings.modules.heartbeat_detection.interval
setInterval ()->
current_time = moment()
_async.each ROOM_all, (room, done)->
if room and room.duel_stage != ygopro.constants.DUEL_STAGE.BEGIN and room.hostinfo.auto_death and !room.auto_death_triggered and current_time - moment(room.start_time) > 60000 * room.hostinfo.auto_death
room.auto_death_triggered = true
room.start_death()
done()
, 1000
# spawn windbot
windbot_looplimit = 0
windbot_process = global.windbot_process = null
......@@ -3704,18 +3639,15 @@ spawn_windbot = global.spawn_windbot = () ->
return
return
if settings.modules.windbot.enabled and settings.modules.windbot.spawn
spawn_windbot()
global.rebooted = false
#http
if settings.modules.http
if true
addCallback = (callback, text)->
if not callback then return text
return callback + "( " + text + " );"
requestListener = (request, response)->
httpRequestListener = (request, response)->
parseQueryString = true
u = url.parse(request.url, parseQueryString)
#pass_validated = u.query.pass == settings.modules.http.password
......@@ -3757,17 +3689,17 @@ if settings.modules.http
)
else if u.pathname == '/api/duellog' and settings.modules.tournament_mode.enabled
else if u.pathname == '/api/duellog' and settings.modules.mysql.enabled
if !await auth.auth(u.query.username, u.query.pass, "duel_log", "duel_log")
response.writeHead(200)
response.end(addCallback(u.query.callback, "[{name:'密码错误'}]"))
return
else
response.writeHead(200)
duellog = JSON.stringify duel_log.duel_log, null, 2
duellog = JSON.stringify(await dataManager.getDuelLogJSON(settings.modules.tournament_mode), null, 2)
response.end(addCallback(u.query.callback, duellog))
else if u.pathname == '/api/archive.zip' and settings.modules.tournament_mode.enabled
else if u.pathname == '/api/archive.zip' and settings.modules.mysql.enabled
if !await auth.auth(u.query.username, u.query.pass, "download_replay", "download_replay_archive")
response.writeHead(403)
response.end("Invalid password.")
......@@ -3777,9 +3709,9 @@ if settings.modules.http
archive_name = moment().format('YYYY-MM-DD HH-mm-ss') + ".zip"
archive_args = ["a", "-mx0", "-y", archive_name]
check = false
for replay in duel_log.duel_log
for filename in await dataManager.getAllReplayFilenames()
check = true
archive_args.push(replay.replay_filename)
archive_args.push(filename)
if !check
response.writeHead(403)
response.end("Duel logs not found.")
......@@ -3810,7 +3742,7 @@ if settings.modules.http
response.writeHead(403)
response.end("Failed reading replays. " + error)
else if u.pathname == '/api/clearlog' and settings.modules.tournament_mode.enabled
else if u.pathname == '/api/clearlog' and settings.modules.mysql.enabled
if !await auth.auth(u.query.username, u.query.pass, "clear_duel_log", "clear_duel_log")
response.writeHead(200)
response.end(addCallback(u.query.callback, "[{name:'密码错误'}]"))
......@@ -3818,15 +3750,14 @@ if settings.modules.http
else
response.writeHead(200)
if settings.modules.tournament_mode.log_save_path
fs.writeFile(settings.modules.tournament_mode.log_save_path + 'duel_log.' + moment().format('YYYY-MM-DD HH-mm-ss') + '.json', JSON.stringify(duel_log, null, 2), (err) ->
fs.writeFile(settings.modules.tournament_mode.log_save_path + 'duel_log.' + moment().format('YYYY-MM-DD HH-mm-ss') + '.json', JSON.stringify(await dataManager.getDuelLogJSON(settings.modules.tournament_mode), null, 2), (err) ->
if err
log.warn 'DUEL LOG SAVE ERROR', err
)
duel_log.duel_log = []
setting_save(duel_log)
await dataManager.clearDuelLog()
response.end(addCallback(u.query.callback, "[{name:'Success'}]"))
else if _.startsWith(u.pathname, '/api/replay') and settings.modules.tournament_mode.enabled
else if _.startsWith(u.pathname, '/api/replay') and settings.modules.mysql.enabled
if !await auth.auth(u.query.username, u.query.pass, "download_replay", "download_replay")
response.writeHead(403)
response.end("密码错误")
......@@ -3841,15 +3772,13 @@ if settings.modules.http
response.writeHead(404)
response.end("bad filename")
return
fs.readFile(settings.modules.tournament_mode.replay_path + filename, (error, buffer)->
if error
response.writeHead(404)
response.end("未找到文件 " + filename)
else
response.writeHead(200, { "Content-Type": "application/octet-stream", "Content-Disposition": "attachment" })
response.end(buffer)
return
)
try
buffer = await fs.promises.readFile(settings.modules.tournament_mode.replay_path + filename)
response.writeHead(200, { "Content-Type": "application/octet-stream", "Content-Disposition": "attachment" })
response.end(buffer)
catch e
response.writeHead(404)
response.end("未找到文件 " + filename)
else if u.pathname == '/api/message'
#if !pass_validated
......@@ -3877,7 +3806,7 @@ if settings.modules.http
u.query.stop = false
response.writeHead(200)
try
await util.promisify(setting_change)(settings, 'modules:stop', u.query.stop)
await setting_change(settings, 'modules:stop', u.query.stop)
response.end(addCallback(u.query.callback, "['stop ok', '" + u.query.stop + "']"))
catch err
response.end(addCallback(u.query.callback, "['stop fail', '" + u.query.stop + "']"))
......@@ -3888,7 +3817,7 @@ if settings.modules.http
response.end(addCallback(u.query.callback, "['密码错误', 0]"))
return
try
await util.promisify(setting_change)(settings, 'modules:welcome', u.query.welcome)
await setting_change(settings, 'modules:welcome', u.query.welcome)
response.end(addCallback(u.query.callback, "['welcome ok', '" + u.query.welcome + "']"))
catch err
response.end(addCallback(u.query.callback, "['welcome fail', '" + u.query.welcome + "']"))
......@@ -3906,39 +3835,39 @@ if settings.modules.http
response.writeHead(200)
response.end(addCallback(u.query.callback, "['密码错误', 0]"))
return
load_tips((err)->
response.writeHead(200)
if(err)
response.end(addCallback(u.query.callback, "['tip fail', '" + settings.modules.tips.get + "']"))
else
response.end(addCallback(u.query.callback, "['tip ok', '" + settings.modules.tips.get + "']"))
)
success = await load_tips()
response.writeHead(200)
if success
response.end(addCallback(u.query.callback, "['tip ok', '" + settings.modules.tips.get + "']"))
else
response.end(addCallback(u.query.callback, "['tip fail', '" + settings.modules.tips.get + "']"))
else if u.query.loaddialogues
if !await auth.auth(u.query.username, u.query.pass, "change_settings", "change_dialogues")
response.writeHead(200)
response.end(addCallback(u.query.callback, "['密码错误', 0]"))
return
load_dialogues((err)->
response.writeHead(200)
if(err)
response.end(addCallback(u.query.callback, "['dialogues fail', '" + settings.modules.dialogues.get + "']"))
else
response.end(addCallback(u.query.callback, "['dialogues ok', '" +settings.modules.dialogues.get + "']"))
)
success = await load_dialogues()
response.writeHead(200)
if success
response.end(addCallback(u.query.callback, "['dialogue ok', '" + settings.modules.tips.get + "']"))
else
response.end(addCallback(u.query.callback, "['dialogue fail', '" + settings.modules.tips.get + "']"))
else if u.query.ban
if !await auth.auth(u.query.username, u.query.pass, "ban_user", "ban_user")
response.writeHead(200)
response.end(addCallback(u.query.callback, "['密码错误', 0]"))
return
ban_user(u.query.ban, (err)->
try
await ban_user(u.query.ban)
catch e
log.warn("ban fail", e.toString())
response.writeHead(200)
if(err)
response.end(addCallback(u.query.callback, "['ban fail', '" + u.query.ban + "']"))
else
response.end(addCallback(u.query.callback, "['ban ok', '" + u.query.ban + "']"))
)
response.end(addCallback(u.query.callback, "['ban fail', '" + u.query.ban + "']"))
return
response.writeHead(200)
response.end(addCallback(u.query.callback, "['ban ok', '" + u.query.ban + "']"))
else if u.query.kick
if !await auth.auth(u.query.username, u.query.pass, "kick_user", "kick_user")
......@@ -4023,24 +3952,4 @@ if settings.modules.http
response.end()
return
http_server = http.createServer(requestListener)
http_server.listen settings.modules.http.port
if settings.modules.http.ssl.enabled
https = require 'https'
options =
cert: fs.readFileSync(settings.modules.http.ssl.cert)
key: fs.readFileSync(settings.modules.http.ssl.key)
https_server = https.createServer(options, requestListener)
if settings.modules.http.websocket_roomlist and roomlist
roomlist.init https_server, ROOM_all
https_server.listen settings.modules.http.ssl.port
if not fs.existsSync('./plugins')
fs.mkdirSync('./plugins')
plugin_list = fs.readdirSync("./plugins")
for plugin_filename in plugin_list
plugin_path = process.cwd() + "/plugins/" + plugin_filename
require(plugin_path)
log.info("Plugin loaded:", plugin_filename)
init()
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment