diff --git a/GuildWarsPartySearch.NodeJSServer/server.mjs b/GuildWarsPartySearch.NodeJSServer/server.mjs index bb59008..04eca60 100644 --- a/GuildWarsPartySearch.NodeJSServer/server.mjs +++ b/GuildWarsPartySearch.NodeJSServer/server.mjs @@ -2,7 +2,7 @@ import {is_numeric, is_uuid, json_parse, to_number} from "./src/js/string_functi import express from "express"; import bodyParser from "body-parser"; import {is_quarantine_hit} from "./src/js/spam_filter.mjs"; -import {PartySearch} from "./src/js/PartySearch.class.mjs"; +import {party_json_keys, PartySearch} from "./src/js/PartySearch.class.mjs"; import {start_websocket_server} from "./src/js/websocket_server.mjs"; import {assert} from "./src/js/assert.mjs"; import * as http from "http"; @@ -143,6 +143,8 @@ function send_map_parties(map_id, request_or_websocket = null) { * @param party {PartySearch} */ function remove_party(party) { + if(!party) + return; const now = new Date(); let idx = all_parties.indexOf(party); if (idx !== -1) { @@ -422,6 +424,55 @@ function on_recv_parties(ws, data) { reassign_bot_clients(); } +/** + * Sent from bot clients to update existing map data instead of the whole lot. + * On error, any connected client should then send "client_parties" to refresh the list + * @param ws + * @param data + */ +function on_updated_parties(ws, data) { + const client_id = get_client_id(ws); + assert(client_id, "on_updated_parties: no client_id"); + const bot_client = get_bot_client(ws); + assert(bot_client, "on_updated_parties: get_bot_client failed"); + assert(is_numeric(bot_client.map_id) && is_numeric(bot_client.district_region),"on_updated_parties: no bot district or region"); + + const existing_parties = all_parties.filter((party) => { + return party.client_id === client_id; + }); + + let map_count_changed = false; + + data.parties.filter((changed_party_info) => { + return !is_quarantine_hit(changed_party_info.message || ''); + }).forEach((changed_party_info) => { + const party_id = to_number(changed_party_info.party_id || changed_party_info[party_json_keys['i']]); + let existing_party = existing_parties.find((party) => { + return party.party_id === party_id; + }); + if(changed_party_info.r) { + remove_party(existing_party); + map_count_changed = true; + return; + } + if(!existing_party) { + changed_party_info.client_id = client_id; + changed_party_info.district_region = bot_client.district_region; + changed_party_info.map_id = bot_client.map_id; + const new_party = new PartySearch(changed_party_info); + add_party(new_party); + map_count_changed = true; + return; + } + existing_party.update(changed_party_info); + }); + + // Broadcast to other connections + if(map_count_changed) + send_available_maps(); + send_map_parties(to_number(bot_client.map_id)); +} + /** * Send a summary list of map_ids that have available parties to a http request or endpoint * @param request_or_websocket {WebSocket|Request} @@ -511,6 +562,18 @@ async function on_request_message(request, data) { assert(request.is_bot_client, "Not a client"); on_recv_parties(request, data); break; + case "updated_parties": + assert(request.is_bot_client, "Not a client"); + try { + on_updated_parties(request,data); + } catch(e) { + send_header(request, 500, e.message); + // On error (whatever it may be!) request client to send all parties + send_json(request, { + type: "server_requested_client_parties" + }); + } + break; case "available_maps": send_available_maps(request); break; diff --git a/GuildWarsPartySearch.NodeJSServer/src/js/PartySearch.class.mjs b/GuildWarsPartySearch.NodeJSServer/src/js/PartySearch.class.mjs index 7a6f33d..660105d 100644 --- a/GuildWarsPartySearch.NodeJSServer/src/js/PartySearch.class.mjs +++ b/GuildWarsPartySearch.NodeJSServer/src/js/PartySearch.class.mjs @@ -9,7 +9,7 @@ import { } from "./gw_constants.mjs"; import {to_number} from "./string_functions.mjs"; -const json_keys = { +export const party_json_keys = { 'party_id':'i', 'party_size':'ps', 'sender':'s', @@ -30,26 +30,50 @@ const json_keys = { export class PartySearch { constructor(json) { this.client_id = json.client_id || ''; - this.message = json.message || json[json_keys['message']] || ''; - this.sender = json.sender || json[json_keys['sender']] || ''; - this.party_id = to_number(json.party_id || json[json_keys['i']] || 0); - this.hardmode = to_number(json.hardmode || json[json_keys['hardmode']] || 0); - this.party_size = to_number(json.party_size || json[json_keys['party_size']] || 1); - this.hero_count = to_number(json.hero_count || json[json_keys['hero_count']] || 0); - this.level = to_number(json.level || json[json_keys['level']] || 20); - this.search_type = to_number(json.search_type || json[json_keys['search_type']]); - this.primary = to_number(json.primary || json[json_keys['primary']] || 0); - this.secondary = to_number(json.secondary || json[json_keys['secondary']] || 0); - this.district_number = to_number(json.district_number || json[json_keys['district_number']] || 1); - this.district_region = to_number(json.district_region || json[json_keys['district_region']] || 0); - this.district_language = to_number(json.district_language || json.language || json[json_keys['district_language']] || 0); - this.map_id = to_number(json.map_id || json[json_keys['map_id']] || 0); + this.message = json.message || json[party_json_keys['message']] || ''; + this.sender = json.sender || json[party_json_keys['sender']] || ''; + this.party_id = to_number(json.party_id || json[party_json_keys['i']] || 0); + this.hardmode = to_number(json.hardmode || json[party_json_keys['hardmode']] || 0); + this.party_size = to_number(json.party_size || json[party_json_keys['party_size']] || 1); + this.hero_count = to_number(json.hero_count || json[party_json_keys['hero_count']] || 0); + this.level = to_number(json.level || json[party_json_keys['level']] || 20); + this.search_type = to_number(json.search_type || json[party_json_keys['search_type']]); + this.primary = to_number(json.primary || json[party_json_keys['primary']] || 0); + this.secondary = to_number(json.secondary || json[party_json_keys['secondary']] || 0); + this.district_number = to_number(json.district_number || json[party_json_keys['district_number']] || 1); + this.district_region = to_number(json.district_region || json[party_json_keys['district_region']] || 0); + this.district_language = to_number(json.district_language || json.language || json[party_json_keys['district_language']] || 0); + this.map_id = to_number(json.map_id || json[party_json_keys['map_id']] || 0); this.validate(); - this.district = json.district || json[json_keys['district']] || district_from_region(this.district_region); + this.district = json.district || json[party_json_keys['district']] || district_from_region(this.district_region); } - toJSON() { + + update(json) { + Object.keys(party_json_keys).forEach((full_key) => { + const abbr_key = party_json_keys[full_key]; + if(json.hasOwnProperty(abbr_key)) + json[full_key] = json[abbr_key]; + }); + if(json.hasOwnProperty('message')) + this.message = json.message; + if(json.hasOwnProperty('sender')) + this.sender = json.sender; + // NB: Map and district can't change in practive without the party being removed anyway + ['hardmode','parry_size','hero_count','level','search_type','primary','secondary'].forEach((key) => { + if(json.hasOwnProperty(key)) + this[key] = to_number(json[key]); + }); + this.validate(); + } + + /** + * + * @param full + * @return {{}} + */ + toJSON(full = false) { const json_to_set = [ 'message', 'sender',