ggejs
Version:
A powerful JavaScript library for interacting with the server of Goodgame Empire: Four Kingdoms
245 lines (222 loc) • 9.52 kB
JavaScript
'use strict'
const connection = require('./e4kserver/connection');
const {onData} = require('./e4kserver/data');
const AllianceManager = require('./managers/AllianceManager');
const EquipmentManager = require("./managers/EquipmentManager");
const MovementManager = require('./managers/MovementManager');
const PlayerManager = require('./managers/PlayerManager');
const WorldMapManager = require('./managers/WorldMapManager');
const {WaitUntil} = require('./tools/wait');
const EventEmitter = require('node:events');
const {NetworkInstance, languages} = require('e4k-data');
const ClientUserDataManager = require("./managers/ClientUserDataManager");
const PremiumBoostData = require("./structures/boosters/PremiumBoostData");
const QuestData = require("./structures/quests/QuestData");
const {execute: verifyLoginData} = require('./e4kserver/commands/verifyLoginData');
const {execute: registerOrLogin} = require('./e4kserver/commands/registerOrLogin');
class Client extends EventEmitter {
#name = "";
#password = "";
/** @type {{token:string, tokenExpirationDate: Date}} */
#apiToken;
uniqueAccountId = "";
/** @type {Client | null} */
externalClient = null;
_serverInstance = require('e4k-data').network.instances.instance[33];
/** @type {Socket} */
_socket = new (require("net").Socket)();
_networkId = -1;
/**
* @param {string} name
* @param {string} password
* @param {boolean} debug
* @param {NetworkInstance} serverInstance
*/
constructor(name, password, serverInstance, debug = false) {
super();
this._serverInstance = serverInstance;
this.#name = name;
this.#password = password;
this._socket = new (require("net").Socket)();
this._socket.client = this;
this._socket["__reconnTimeoutSec"] = 300;
this._socket.debug = debug;
this.alliances = new AllianceManager(this);
this.clientUserData = new ClientUserDataManager();
this.clientUserData.boostData = new PremiumBoostData(this);
this.clientUserData.questData = new QuestData(this);
this.equipments = new EquipmentManager(this);
this.movements = new MovementManager(this);
this.players = new PlayerManager(this);
this.worldMaps = new WorldMapManager(this);
this.#addSocketListeners(this._socket);
}
static registerNewAccount() {
// see room
}
_language = 'en';
/** @param {string} val */
set language(val) {
if (languages[val] == null) return;
this._language = val;
}
/** @param {number} val */
set reconnectTimeout(val) {
this._socket["__reconnTimeoutSec"] = val;
}
/** @return {MailMessage[]} */
get mailMessages() {
return this._socket['mailMessages'];
}
async connect() {
if (this._socket["__connected"]) return this;
await _connect(this._socket);
this.emit('connected');
return this;
}
_verifyLoginData() {
if (this.#name === "" && this.#password !== "") return registerOrLogin(this._socket, this.#password);
verifyLoginData(this._socket, this.#name, this.#password);
}
/** @param {string} message */
sendChatMessage(message) {
require('./e4kserver/commands/sendAllianceChatMessage').execute(this._socket, message);
}
/**
* @param {string} playerName
* @param {string} subject
* @param {string} message
*/
sendMailMessage(playerName, subject, message) {
require('./e4kserver/commands/sendMailMessage').execute(this._socket, playerName, subject, message);
}
/**
* @param {InteractiveMapobject} mapObject
* @returns {Promise<Castle>}
*/
async getCastleInfo(mapObject) {
if (!mapObject || !mapObject.objectId) throw "WorldMapArea is not valid";
require('./e4kserver/commands/joinArea').execute(this._socket, mapObject);
const data = await WaitUntil(this._socket, `join_area_${mapObject.objectId}_data`, "join_area_error");
delete this._socket[`join_area_${mapObject.objectId}_data`];
return data;
}
async __x__x__relogin() {
await _disconnect(this._socket);
await WaitUntil(this._socket, "__connected", "__connection_error");
}
/** @param {Socket} socket */
#addSocketListeners(socket) {
socket["unfinishedDataString"] = "";
socket.addListener("error", (err) => {
if (socket.debug) console.error(`\x1b[31m[SOCKET ERROR] ${err}\x1b[0m`);
if (socket.debug) console.error(err);
socket.end();
});
socket.addListener('data', (data) => {
onData(socket, data);
});
socket.addListener('ready', () => {
connection.sendVersionCheck(socket);
});
socket.addListener('end', () => {
if (socket["isReconnecting"]) return;
if (socket.debug) console.warn("Socket Ended!");
socket["__disconnecting"] = false;
socket["__connected"] = false;
socket["__disconnect"] = true;
});
socket.addListener('timeout', () => {
if (socket.debug) console.warn("Socket Timeout!");
socket.end();
});
socket.addListener('close', hadError => {
if (socket["isReconnecting"]) return;
socket["isReconnecting"] = true;
this._socket = {};
if (socket.debug) console.warn(`Socket Closed${hadError ? ", caused by error" : ""}!`);
socket.removeAllListeners();
/** @type {Socket} */
const new_socket = createCleanSocket(socket);
socket = null;
this.#addSocketListeners(new_socket);
setTimeout(async () => {
new_socket.client = this;
this._socket = new_socket;
if (new_socket.debug) console.log("Reconnecting!");
while (true) {
try {
await this.connect();
break;
} catch (e) {
await new Promise(res => setTimeout(res, 1000));
}
}
}, new_socket["__reconnTimeoutSec"] * 1000);
});
}
/** @param {{token:string, tokenExpirationDate: Date}} val */
set _apiToken(val) {
this.#apiToken = val;
(async () => {
try {
const gameId = 16;
const f = await fetch(`https://accounts.public.ggs-ep.com/players/${gameId}-${this._networkId}-${this._serverInstance.value}-${this.clientUserData.playerId}/gnip-phrase`, {
headers: {Authorization: `Bearer ${val.token}`}
});
this.uniqueAccountId = JSON.parse(Buffer.from(await f.arrayBuffer()).toString())["gnipPhrase"] ?? "";
/*
const f2 = await fetch(`https://accounts.public.ggs-ep.com/players/${gameId}-${this._networkId}-${this._serverInstance.value}-${this.clientUserData.playerId}/onetime-links/mbs`, {
headers: {Authorization: `Bearer ${val.token}`}
})
this.webshopOneTimeLink = JSON.parse(Buffer.from(await f2.arrayBuffer()).toString())["link"];
const storeId = "googleplay" // "googleplay" || "local"
const f3 = await fetch(`https://mobile-payments.public.ggs-e4k.com/api/players/${gameId}-${this._networkId}-${this._serverInstance.value}-${this.clientUserData.playerId}/catalog/${storeId}`, {
headers: {Authorization: `Bearer ${val.token}`}
})
console.log( JSON.parse(Buffer.from(await f3.arrayBuffer()).toString()) );
*/
} catch (e) {
if (this._socket.debug) console.error(e);
}
})();
}
}
/** @param {Socket} socket */
async function _connect(socket) {
connection.connect(socket);
await WaitUntil(socket, "__connected", "__connection_error");
}
/**
* @param {Socket} socket
* @param {string} name
* @param {string} password
*/
async function login(socket, name, password) {
if (name === "") throw "Missing name while logging in";
if (password === "") throw "Missing password while logging in";
connection.login(socket, name, password);
await WaitUntil(socket, "__connected", "__connection_error");
}
/** @param {Socket} socket */
async function _disconnect(socket) {
socket["__disconnect"] = false;
socket["__disconnecting"] = true;
if (socket?._host == null) throw "Socket missing!";
socket.end();
await WaitUntil(socket, "__disconnect");
}
/**
* Creates new socket and only copies important fields.
* @param {Socket} old_socket
*/
function createCleanSocket(old_socket) {
/** @type {Socket} */
const new_socket = new (require("net").Socket)();
new_socket["__reconnTimeoutSec"] = old_socket["__reconnTimeoutSec"];
new_socket.debug = old_socket.debug;
new_socket["ultraDebug"] = old_socket["ultraDebug"];
new_socket['mailMessages'] = old_socket['mailMessages'] ?? [];
return new_socket;
}
module.exports = Client;