diff --git a/release/server.js b/release/server.js index a44e5bf..61556c0 100644 --- a/release/server.js +++ b/release/server.js @@ -1 +1 @@ -const{match:match}=require("assert"),cp=require("child_process");var installingDependencies=!1;const irequire=async e=>{try{require.resolve(e)}catch(e){installingDependencies||(installingDependencies=!0,console.log("INSTALLING DEPENDENCIES...\nTHIS PROCESS MAY TAKE SOME TIME. PLEASE WAIT")),cp.execSync("npm install"),await setImmediate((()=>{})),console.log("DEPENDECIES INSTALLED")}console.log(`Requiring "${e}"`);try{return require(e)}catch(s){console.log(`Could not include "${e}". Restart the script`),restartProcess(0,1)}};installUpdateDependencies=async()=>{console.log("INSTALLING/UPDATING DEPENDENCIES...\nTHIS PROCESS MAY TAKE SOME TIME. PLEASE WAIT"),cp.execSync("npm install")};var subcomponent_status={discord_bot:!1,squadjs:[]},subcomponent_data={discord_bot:{invite_link:""},squadjs:[],database:{root_user_registered:!1},updater:{updating:!1}};async function init(){const e=(await irequire("./package.json")).version,s=await irequire("fs-extra"),t=await irequire("node-stream-zip"),o=await irequire("https"),n=await irequire("http"),i=await irequire("express"),r=i(),a=await irequire("path"),l=await irequire("mongodb"),c=l.MongoClient,d=l.ObjectID,u=await irequire("crypto"),p=await irequire("body-parser"),_=await irequire("cookie-parser"),m=await irequire("nocache"),g=await irequire("log4js"),f=await irequire("axios"),h=(await irequire("minimist"))(process.argv.slice(2)),y=(await irequire("node-run-cmd"),await irequire("express-force-ssl")),w=await irequire("find-free-port"),{mainModule:S}=await irequire("process"),v=await irequire("discord.js"),{io:b}=await irequire("socket.io-client"),$=await irequire("dns"),k=require("util").promisify($.lookup);try{(await irequire("dotenv")).config()}catch(e){console.error(e)}const I=!0;var D=0;const O=h.c||"conf.json",C=console.log,x=console.error;let A=new Date;const q=a.join(__dirname,"logs",A.toISOString().replace(/T/g,"_").replace(/(:|-|\.|Z)/g,"")+".log");s.existsSync("logs")||s.mkdirSync("logs"),s.existsSync(q)||s.writeFileSync(q,""),g.configure({appenders:{App:{type:"file",filename:q}},categories:{default:{appenders:["App"],level:"all"}}});const T=g.getLogger("App");console.log("Log-file:",q);var E,R={http:void 0,https:void 0,configs:{https:{port:void 0},http:{port:void 0}},logging:{requests:!1}},P={ws:null,initDone:!1},L=[];const N=!0;var j,U;const F=new Map,M=new Map;function B(){async function n(e,s="",t=!1,o=!1){const n=`/${e}/${s}?${t}`;let i,r=!1;const a=F.get(n);a&&(r=!0,i=a);const l=Date.now();if(a&&!o||(i=await function(e,s="",t=!1){let o=new Promise(((o,n)=>{W((n=>{n.collection("lists").findOne({output_path:e},((i,r)=>{if(i)J(res,i);else if(null!=r){let i=s&&""!=s?{clan_code:s}:{},a="",l=[],c=[],d=[],u=[],p=[];const _=Q(6);n.collection("clans").find(i).toArray(((i,m)=>{for(let e of m)c[e._id.toString()]=e,d.push(e._id);n.collection("groups").find().sort({group_name:1}).toArray(((i,m)=>{for(let e of m)l[e._id.toString()]=e;l[_]={group_name:_,group_permissions:["reserve"]};const g=[{$match:{approved:!0,id_clan:{$in:d},id_list:r._id}},{$lookup:{from:"players",let:{steamid64:"$steamid64"},pipeline:[{$match:{$expr:{$eq:["$steamid64","$$steamid64"]},discord_user_id:{$exists:!0}}}],as:"serverPlayerData"}},{$sort:{id_clan:1,id_group:1,username_l:1}}];n.collection("whitelists").aggregate(g).toArray(((i,r)=>{if(i)J(res,i);else if(null!=r){for(let g of r){let f=(g.serverPlayerData&&g.serverPlayerData[0]?g.serverPlayerData[0].discord_username:null)||g.discord_username||"";p.push({username:g.username,steamid64:g.steamid64,groupId:g.id_group,clanTag:c[g.id_clan].tag,discordUsername:f})}if(s)m();else{const h=[{$match:{steamid64:{$ne:null},discord_roles_ids:{$exists:!0}}},{$lookup:{from:"lists",let:{pl_roles:"$discord_roles_ids"},pipeline:[{$match:{output_path:e}},{$addFields:{int_r:{$setIntersection:["$discord_roles","$$pl_roles"]}}},{$match:{int_r:{$ne:[]}}}],as:"lists"}},{$lookup:{from:"groups",let:{pl_roles:"$discord_roles_ids"},pipeline:[{$addFields:{int_r:{$setIntersection:["$discord_roles","$$pl_roles"]}}},{$match:{int_r:{$ne:[]}}}],as:"groups"}},{$project:{discord_roles_ids:0,"groups.discord_roles":0,"groups.intersection_roles":0,"groups.int_r":0,"groups.require_appr":0}},{$match:{lists:{$ne:[]}}}];n.collection("players").aggregate(h).toArray(((e,s)=>{if(e)res.sendStatus(500),console.error(e);else for(let e of s)if(t)a+=e.username+"\n";else for(let s of e.groups)p.push({username:e.username,steamid64:e.steamid64,groupId:s._id,clanTag:"Discord Role",discordUsername:null!=e.discord_username?e.discord_username:""});n.collection("configs").findOne({category:"seeding_tracker"},((e,s)=>{if(s&&s.config.reward_enabled&&"true"==s.config.reward_enabled){const e=s.config,t=e.reward_needed_time.value*e.reward_needed_time.option/1e3/60;n.collection("players").find({steamid64:{$ne:null},seeding_points:{$gte:t}}).toArray(((s,t)=>{s&&J(res,s);const o=t.map((s=>({username:s.username,steamid64:s.steamid64,groupId:e.reward_group_id,clanTag:"Seeder",discordUsername:null!=s.discord_username?s.discord_username:""})));p.push(...o),m()}))}else m()}))}))}function d(){for(let e of p)l[e.groupId]?(e.groupId=`${e.groupId}`,""==e.discordUsername||e.discordUsername.startsWith("@")||(e.discordUsername="@"+e.discordUsername),a+=`Admin=${e.steamid64}:${l[e.groupId].group_name} // [${e.clanTag}] ${e.username} ${e.discordUsername}\n`,u.includes(e.groupId)||u.push(e.groupId)):(console.log("Could not find group with id",e.groupId,l[e.groupId]),n.collection("whitelists").deleteMany({id_group:e.groupId}));a="\n"+a;for(let e of u){const s=l[e];a=`Group=${s.group_name}:${s.group_permissions.join(",")}\n`+a}}function m(){!E.other.whitelist_developers||t||s||p.push({username:"JetDave",steamid64:"76561198419229279",groupId:_,clanTag:"SQUAD Whitelister Developer",discordUsername:"@=BIA=JetDave#1001"}),d(),o(a)}}else o("")}))}))}))}else o(null)}))}))}));return o}(e,s,t)),!i)return null;if((Date.now()-l>1e3&&!r||o)&&(F.set(n,i),M.set(n,new Date)),r){i=`//////////////////////////////////////////////\n // Cached\n // Last update: ${M.get(n).toLocaleString()}\n //////////////////////////////////////////////\n `.replace(/^\s*/gm,"")+"\n"+i}return i}async function l(){const e=F.keys();for(let s of e){const e=s.match(/\/(.+)\/(.*)\?(true|false)/i);await n(e[1],e[2],"true"==e[3],!0)}}function c(e=null){return new Promise(((s,t)=>{W((o=>{o.collection("whitelists").deleteOne({expiration:{$lte:new Date}},((o,n)=>{o&&(console.error(o),t(o)),e&&e(),s(n)}))}))}))}function g(e,s){const t=e.originalUrl.replace(/\?.*$/,"");let o=[];for(let e of o)if(t.startsWith(e))return e;return t.endsWith("/")?t.substring(0,t.length-1):t}function w(e,s,t){$(e)?t():s.redirect("/")}function S(o=!1,n=null){let i=new Date;console.log("Current version: ",e,"\n > Checking for updates",i.toLocaleString()),f.get("https://api.github.com/repos/fantinodavide/Squad_Whitelister/releases").then((i=>{const r=i.data[0],l=r.tag_name.toUpperCase().replace("V","").split("."),c=e.toString().split("."),d=E.other.install_beta_versions&&r.prerelease||!r.prerelease,u=parseInt(c[0]) Update found: "+r.tag_name,r.name),o?function(e){const o=e.assets.filter((e=>"release.zip"==e.name))[0].browser_download_url;console.log(" > Downloading update: "+e.tag_name,e.name,o);const n=a.resolve(__dirname,"tmp_update"),i=a.resolve(n,"gitupd.zip");s.existsSync(n)||s.mkdirSync(n);const r=s.createWriteStream(i);f({method:"get",url:o,responseType:"stream"}).then((e=>{e.data.pipe(r)})),r.on("finish",(e=>{setTimeout((()=>{!function(e,o,n){const i=new t({file:o,storeEntries:!0,skipEntryNameValidation:!0});i.on("ready",(()=>{s.remove(__dirname+"/dist",(()=>{i.extract("release/",__dirname,(async(t,o)=>{i.close(),await installUpdateDependencies(),console.log(" > Extracted",o,"files"),s.remove(e,(()=>{console.log(`${e} folder deleted`);const s=5e3;console.log(" > Restart in",s/1e3,"seconds"),restartProcess(s,0,h)}))}))}))}))}(n,i)}),1e3)})),r.on("error",(e=>{console.error(e)}))}(r):n&&n()):(console.log(" > No updates found"),n&&n())})).catch((e=>{console.error(" > Couldn't check for updates. Proceding startup",e),n&&n()}))}function $(e){return e.userSession&&e.userSession.access_level<=5}S(E.other.automatic_updates,(async()=>{console.log(" > Starting up"),setInterval((()=>{S(E.other.automatic_updates)}),1e3*E.other.update_check_interval_seconds),function(){function e(){W((async e=>{const s=await e.collection("configs").findOne({category:"seeding_tracker"});if(!s)return;const t=s.config;"fixed_reset"==t.tracking_mode&&t.reset_seeding_time&&t.next_reset&&new Date>new Date(t.next_reset)&&(e.collection("players").updateMany({},{$set:{seeding_points:0}}),e.collection("configs").updateOne({category:"seeding_tracker"},{$set:{"config.next_reset":new Date((new Date).valueOf()+t.reset_seeding_time.value*t.reset_seeding_time.option).toISOString().split("T")[0]}}))}))}e(),setInterval(e,20)}(),await async function(){console.log("Initializing WL List Caches");const e=await W(),s=await e.collection("lists").find().toArray();for(let e of s){const s=`/${e.output_path}/?false`;await n(e.output_path,"",!1);let t=null!=F.get(s);console.log(` > "${e.title}": ${t?"CACHED":"NOT CACHED"}`)}}(),setInterval(l,1e3*E.other.lists_cache_refresh_seconds),U((async()=>{if(async function(e=null){let s=null;console.log("Starting SquadJS WebSockets");for(let o in E.squadjs){subcomponent_status.squadjs[o]=!1;const n=E.squadjs[o];if(n.websocket&&""!=n.websocket.token&&""!=n.websocket.host){const i=setTimeout((()=>{console.error(` > Connection ${+o+1} timed out. Check your SquadJS WebSocket configuration.`),console.log(` > Proceding without SquadJS WebSocket ${+o+1}.`)}),1e4),r=(await k(n.websocket.host)).address;async function t(e,s=5e3){W((async t=>{const n=[{$match:{steamid64:e.player.steamID}},{$lookup:{from:"groups",localField:"id_group",foreignField:"_id",as:"group_full_data"}}];t.collection("whitelists").aggregate(n).toArray((async(n,i)=>{n?J(null,n):t.collection("players").findOne({steamid64:e.player.steamID},(async(n,i)=>{if(n)J(null,n);else{const n=(await t.collection("configs").findOne({category:"seeding_tracker"})).config,r=n.reward_needed_time.value*(n.reward_needed_time.option/1e3/60),a=Math.floor(100*i.seeding_points/r)||0;let l="Welcome "+e.player.name+"\n\n";if(subcomponent_status.squadjs){let s=(await z(e.player.steamID)).filter((e=>e.approved));if(s.length>0){l+="Groups:\n";for(let e of s)l+=` - ${e.name}`,e.expiration&&(l+=": "+(((e.expiration-new Date)/1e3/60/60).toFixed(1)+"h left")),l+="\n"}}if(subcomponent_status.discord_bot){let e="";if(i&&i.discord_user_id&&""!=i.discord_user_id){const s=await U.users.fetch(i.discord_user_id);e=s.username+(s.discriminator?"#"+s.discriminator:"")}"true"==n.reward_enabled&&(l+="\nSeeding Reward: "+a+"%"),l+="\nDiscord Username: "+(""!=e?e:"Not linked")}subcomponent_status.squadjs&&setTimeout((()=>{subcomponent_data.squadjs[o].socket.emit("rcon.warn",e.player.steamID,l,(e=>{})),console.log(l)}),s)}}))}))}))}subcomponent_data.squadjs[o]||(subcomponent_data.squadjs[o]={}),subcomponent_data.squadjs[o].socket=b(`ws://${r}:${n.websocket.port}`,{auth:{token:n.websocket.token},autoUnref:!0}),subcomponent_data.squadjs[o].socket.on("connect",(async()=>{clearTimeout(i),console.log(`SquadJS Websocket ${+o+1} Connected`),clearInterval(s),subcomponent_status.squadjs[o]=!0,P.initDone||(P.initDone=!0)})),subcomponent_data.squadjs[o].socket.on("disconnect",(async()=>{subcomponent_status.squadjs[o]=!1,console.log("SquadJS WebSocket\n > Disconnected\n > Trying to reconnect"),s=setInterval((()=>{subcomponent_status.squadjs||subcomponent_data.squadjs[o].connect()}),1e4)})),subcomponent_data.squadjs[o].socket.on("PLAYER_CONNECTED",(async e=>{try{e&&e.player&&e.player.steamID&&(W((async s=>{s.collection("players").updateOne({steamid64:e.player.steamID},{$set:{username:e.player.name}},{upsert:!0})})),setTimeout((()=>{t(e)}),1e4))}catch(e){console.error("PLAYER_CONNECTED ERROR",e)}})),subcomponent_data.squadjs[o].socket.on("CHAT_MESSAGE",(async e=>{switch(e.message.toLowerCase().replace(/^(!|\/)/,"")){case"test":break;case"playerinfo":console.log(e);const s=await W(),n=await s.collection("players").findOne({steamid64:e.player.steamID},{projection:{_id:0,seeding_points:1}});console.log("olddata",n);break;case"profile":t(e,0);break;default:6!=e.message.length||e.message.includes(" ")||W((async s=>{s.collection("profilesLinking").findOne({code:e.message},(async(t,n)=>{if(t)J(null,t);else if(n)if(n.expiration>new Date){const t=await U.users.fetch(n.discordUserId),i=t.username+(t.discriminator?"#"+t.discriminator:""),r=await s.collection("players").findOne({steamid64:e.player.steamID},{projection:{_id:0,seeding_points:1}});s.collection("players").updateOne({discord_user_id:n.discordUserId},{$set:{steamid64:e.player.steamID,username:e.player.name,discord_user_id:n.discordUserId,discord_username:i,...r}},{upsert:!0},((r,a)=>{s.collection("players").deleteOne({steamid64:e.player.steamID,discord_user_id:{$exists:!1}},((r,a)=>{if(r)return J(null,r);s.collection("profilesLinking").deleteOne({_id:n._id}),r?J(null,r):(subcomponent_data.squadjs[o].socket.emit("rcon.warn",e.steamID,"Linked Discord profile: "+i,(e=>{})),t.send({embeds:[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle("Profile Linked").setDescription("Your Discord profile has been linked to a Steam profile").addFields({name:"Steam Username",value:e.name,inline:!0},{name:"SteamID",value:v.hyperlink(e.steamID,"https://steamcommunity.com/profiles/"+e.steamID),inline:!0})]}))}))}))}else s.collection("profilesLinking").deleteOne({_id:n._id})}))}))}}))}else console.log(` > ${+o+1} Not configured. Skipping.`),e&&e()}}(),async function(){console.log("Seeding Tracker: Starting");const e=.2;async function s(){const e=await W(),s=await e.collection("configs").findOne({category:"seeding_tracker"}),t=s.config;if(!t)return void console.log("Seeding tracker configuration not set, unable to proceed.");const o=t.reward_needed_time.value*(t.reward_needed_time.option/1e3/60),n=[],i=[];for(let e in subcomponent_data.squadjs){if(!subcomponent_status.squadjs[e])continue;let s;i[e]=!1;try{s=await G(subcomponent_data.squadjs[e].socket,"rcon.getListPlayers",{})}catch(e){continue}for(let t of s)t.sqJsConnectionIndex=e,n.push(t);s&&s.length>=(t.seeding_start_player_count||2)&&s.length<=t.seeding_player_threshold&&(i[e]=!0)}firstStart=!1,i.includes(!0)&&W((async e=>{if(n&&n.length>0){if("incremental"==s.config.tracking_mode){let t=0;"point_minute"==s.config.time_deduction.option?t=s.config.time_deduction.value:"perc_minute"==s.config.time_deduction.option&&(t=s.config.time_deduction.value*o/100),await e.collection("players").updateMany({steamid64:{$nin:n.map((e=>e.steamID))},seeding_points:{$gt:t}},{$inc:{seeding_points:-t}})}for(let r of n){if(!i[r.sqJsConnectionIndex])continue;const n=await e.collection("players").findOne({steamid64:r.steamID});e.collection("players").findOneAndUpdate({steamid64:r.steamID},{$set:{steamid64:r.steamID,username:r.name},$inc:{seeding_points:1}},{upsert:!0,returnDocument:"after"},(async(i,a)=>{if(i)J(null,i);else if("true"==t.reward_enabled){const i=Math.min(Math.floor(10*n?.seeding_points/o),10)||0,l=Math.min(Math.floor(10*a.value?.seeding_points/o),10)||0,c=10*l;if(l>0&&l>i)if(c<100){subcomponent_data.squadjs[r.sqJsConnectionIndex].socket.emit("rcon.warn",r.steamID,`Seeding Reward: \n\n${c}% completed`,(e=>{}));const e={embeds:[{color:v.resolveColor(E.app_personalization.accent_color),title:`${r.name}`,url:H(r.steamID),fields:[{name:"Score",value:c+"%",inline:!0},{name:"SteamID",value:v.hyperlink(r.steamID,H(r.steamID)),inline:!0},{name:"Discord User",value:a.value.discord_user_id?v.userMention(a.value.discord_user_id):"Not Linked",inline:!1}],footer:{text:new Array(10).fill("◼",0,l).fill("◻",l,10).join("")+` ${c}%`,icon_url:E.app_personalization.favicon||E.app_personalization.logo_url},thumbnail:{url:E.app_personalization.logo_url},timestamp:(new Date).toISOString()}],ephemeral:!1};U.channels.cache.get(t.discord_seeding_score_channel)?.send(e)}else if(100==c){const o=await e.collection("groups").findOne({_id:d(s.config.reward_group_id)});let n=`Seeding Reward Completed!\n\nYou have received: ${o.group_name}\n`;if("fixed_reset"==s.config.tracking_mode?n+=`Active until: ${new Date(s.config.next_reset).toLocaleDateString()}`:"incremental"==s.config.tracking_mode&&(n+="Don't drop below 100% to keep your reward!"),subcomponent_data.squadjs[r.sqJsConnectionIndex].socket.emit("rcon.warn",r.steamID,n,(e=>{})),subcomponent_status.discord_bot){const e=[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle(`${r.name} received the Seeding Reward!`).setURL(H(r.steamID)).addFields({name:"Username",value:r.name,inline:!0},{name:"SteamID",value:v.hyperlink(r.steamID,"https://steamcommunity.com/profiles/"+r.steamID),inline:!0},{name:"Discord User",value:a.value.discord_user_id?v.userMention(a.value.discord_user_id):"Not Linked",inline:!1},{name:"Reward Group",value:o.group_name,inline:!0}).setThumbnail(E.app_personalization.logo_url).setFooter({text:new Array(10).fill("◼",0,10).join("")+" 100%",iconURL:E.app_personalization.favicon||E.app_personalization.logo_url}).setTimestamp(new Date)];U.channels.cache.get(t.discord_seeding_reward_channel)?.send({embeds:e})}}}}))}}}))}s(),console.log("Seeding Tracker: Started"),setInterval(s,60*e*1e3)}(),I){const t=__dirname+"/ALTERNATIVE PORTS.txt";s.removeSync(t);const n=["certificates/certificate.crt","certificates/fullchain.pem","certificates/default.crt"];let i=X(["certificates/certificate.key","certificates/privkey.pem","certificates/default.key"]),a=X(n);const l=null,u=null,p=process.env.HTTPS_PORT,_=l?parseInt(l):u?parseInt(u):E.web_server.http_port,m=p?parseInt(p):E.web_server.https_port;function e(e,o){if(e!=o){const n="!!! WARNING !!! Port "+e+" is not available! Closest free port found: "+o+"\n";console.log(n),s.writeFileSync(t,n,{flag:"a+"})}}ee(_,(t=>{ee(m,(n=>{if(t?R.http=r.listen(t,E.web_server.bind_ip,(function(){var s=R.http.address().address;console.log("HTTP server listening at http://%s:%s",s,t),R.configs.http.port=t,e(E.web_server.http_port,t)})):console.error("Couldn't start HTTP server"),i&&a&&("true"!==process.env.HTTPS_SERVER_DISABLED&&"1"!==process.env.HTTPS_SERVER_DISABLED||!process.env.HTTPS_SERVER_DISABLED)){console.log("Using Certificate:",a,i);const t={key:s.readFileSync(i),cert:s.readFileSync(a)};R.https=o.createServer(t,r),n?(r.set("forceSSLOptions",{httpsPort:n}),R.configs.https.port=n,R.https.listen(n),console.log("HTTPS server listening at https://%s:%s",E.web_server.bind_ip,n),e(E.web_server.https_port,n)):console.error("Couldn't start HTTPS server"),async function(){}()}}))})),setInterval(c,6e4)}}))})),r.use(m()),r.set("etag",!1),r.use("/",p.json()),r.use("/",p.urlencoded({extended:!0})),r.use(_()),r.use((function(e,s,t){if(!E.web_server.force_https)return t();y(e,s,t)})),r.use("/",(function(e,s,t=null){const o=e.cookies;null!=o.stok&&""!=o.stok?W((n=>{n.collection("sessions").findOne({token:o.stok},{projection:{_id:0}},((o,i)=>{o?s.sendStatus(500):null!=i&&i.session_expiration>new Date?(e.userSession=i,n.collection("users").findOne({_id:i.id_user},{projection:{_id:0}},((o,n)=>{null!=n?(e.userSession={...e.userSession,...n},t&&t()):s.send({status:"login_required"}).status(401)}))):t&&t()}))})):t()})),r.use((function(e,s,t){const o=e.get("host");L[o]?L[o]++:L[o]=1;t()})),r.post("/api/changepassword",((e,s,t)=>{const o=e.body;W((t=>{const n=u.createHash("sha512").update(o.new_password).digest("hex"),i=u.createHash("sha512").update(o.old_password).digest("hex");t.collection("users").updateOne({_id:e.userSession.id_user,password:i},{$set:{password:n}},((e,t)=>{e?J(500,e):s.send(t)}))}))})),r.post("/api/login",((e,s,t)=>{const o=e.body;W((e=>{let t=u.createHash("sha512").update(o.password).digest("hex");e.collection("users").findOne({$or:[{username_lower:o.username.toLowerCase()},{username:o.username}],password:t},((e,t)=>{if(e)s.sendStatus(500),console.error(e);else if(null==t)s.sendStatus(401);else{const e=60*E.web_server.session_duration_hours*60*1e3;let o,n={login_date:new Date,session_expiration:new Date(Date.now()+e),id_user:t._id};do{o=!1,n.token=Q(128),W((e=>{e.collection("sessions").findOne({token:n.token},((t,i)=>{t?(s.sendStatus(500),console.error(t)):null==i?e.collection("sessions").insertOne(n,((e,t)=>{e?(s.sendStatus(500),console.error(e)):(s.cookie("stok",n.token,{expires:n.session_expiration}),s.cookie("uid",n.id_user,{expires:n.session_expiration}),s.send({status:"login_ok",userDt:n}))})):o=!0}))}))}while(o)}}))}))})),r.post("/api/signup",((e,s,t)=>{const o=e.body;let n,i={username:o.username,username_lower:o.username.toLowerCase(),password:u.createHash("sha512").update(o.password).digest("hex"),access_level:subcomponent_data.database.root_user_registered?100:0,clan_code:o.clan_code,registration_date:new Date,discord_username:o.discord_username};0!=i.access_level||subcomponent_data.database.root_user_registered||(subcomponent_data.database.root_user_registered=!0);const r=60*E.web_server.session_duration_hours*60*1e3;let a={...i};a.login_date=new Date,a.session_expiration=new Date(Date.now()+r),W((e=>{e.collection("users").findOne({username_lower:o.username.toLowerCase()},((t,o)=>{t?(s.sendStatus(500),console.error(t)):null==o?e.collection("users").insertOne(i,((e,t)=>{e?(s.sendStatus(500),console.error(e)):(console.log("\n\n\n\n\ninserted id=>",t.insertedId,"=>",i,"\n\n\n\n\n\n"),n=!1,a.token=Q(128),s.redirect(307,"/api/login"))})):s.status(401).send({message:"Username already exists",field:"username"})}))}))})),r.use("/",(function(e,s,t){if(!R.logging.requests)return t();const o=Object.keys(e.query).length>0,n=o?e.query:e.body,i=g(e);console.log("\nREQ: "+i+"\nSESSION: ",e.userSession,"\nPARM "+(o?"GET":"POST")+": ",n),t()})),r.get("/api/getVersion",((s,t,o)=>{t.send(e)})),r.use("/",i.static(__dirname+"/dist")),r.get("/api/getAppPersonalization",(function(e,s,t){s.send(E.app_personalization)})),r.get("/api/getTabs",((e,s,t)=>{const o=[subcomponent_data.database.root_user_registered?{}:{name:"Root User Registration",order:0,type:"tab",max_access_level:null},{name:"Clans",order:5,type:"tab",max_access_level:5},{name:"Whitelist",order:10,type:"tab",max_access_level:100},{name:"Groups",order:15,type:"tab",max_access_level:100},{name:"Approvals",order:25,type:"tab",max_access_level:30},{name:"Seeding",order:27,type:"tab",max_access_level:30},{name:"Users and Roles",order:30,type:"tab",max_access_level:5},{name:"Configuration",order:40,type:"tab",max_access_level:5}];let n;n=subcomponent_data.updater.updating?[{name:"Updating",order:0,type:"tab",max_access_level:null}]:o.filter((s=>null==s.max_access_level&&!e.userSession||e.userSession&&s.max_access_level&&e.userSession.access_level<=s.max_access_level)),s.send({tabs:n})})),r.get("/api/getContextMenu",((e,s,t)=>{let o=[{name:"",action:"",url:"",method:"",order:0}];$(e)&&(o=o.concat([])),s.send(o)})),r.get("/:basePath/:clan_code?",(async(e,s,t)=>{await c();const o=await n(e.params.basePath,e.params.clan_code,e.query?.usernamesOnly||!1);if(!o)return t();s.type("text/plain"),s.send(o)})),r.get("/dsTest",((e,s,t)=>{s.type("text/plain");const o=[{$match:{steamid64:{$ne:null},discord_roles_ids:{$exists:!0}}},{$lookup:{from:"groups",let:{pl_roles:"$discord_roles_ids"},pipeline:[{$addFields:{int_r:{$setIntersection:["$discord_roles","$$pl_roles"]}}},{$match:{int_r:{$ne:[]}}}],as:"groups"}},{$project:{discord_roles_ids:0,"groups.discord_roles":0,"groups.intersection_roles":0,"groups.int_r":0,"groups.require_appr":0}}];W((e=>{e.collection("players").aggregate(o).toArray(((e,t)=>{if(e)s.sendStatus(500),console.error(e);else{let e="";for(let s of t)for(let t of s.groups)e+="Admin="+s.steamid64+":"+t.group_name+" // [Discord Role] "+s.username+(null!=s.discord_username?" "+s.discord_username:"")+"\n";s.send(e)}}))}))})),r.get("/api/checkSession",((e,s,t)=>{e.userSession?s.send({status:"session_valid",userSession:e.userSession}):s.send({status:"login_required"}).status(401)})),r.use("/",(function(e,s,t=null){Object.keys(e.query).length>0?e.query:e.body,g(e);e.userSession?t():s.send({status:"login_required"}).status(401)})),r.use("/api/restart",((e,s,t)=>{s.send({status:"restarting"}),restartProcess(0,0,h)})),r.use("/api/logout",((e,s,t)=>{s.clearCookie("stok"),s.clearCookie("uid"),W((t=>{t.collection("sessions").deleteOne({token:e.userSession.token},((e,t)=>{e?J(s,e):s.send({status:"logout_ok"})}))}))})),r.use("/api/users/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=5&&t()})),r.get("/api/users/read/getAll",((e,s,t)=>{e.query;W((t=>{const o=[{$match:{...e.userSession.access_level>=100?{clan_code:e.userSession.clan_code}:{}}},{$lookup:{from:"clans",localField:"clan_code",foreignField:"clan_code",as:"clan_data"}},{$sort:{username:1}}];t.collection("users").aggregate(o).toArray(((e,t)=>{e?(s.sendStatus(500),console.error(e)):s.send(t)}))}))})),r.post("/api/users/write/remove",((e,s,t)=>{const o=e.body,n=h.demo?{username:{$ne:"demoadmin"}}:{};W((e=>{e.collection("users").deleteOne({_id:d(o._id),...n,access_level:{$gt:1}},((e,t)=>{e?(s.sendStatus(500),console.error(e)):s.send(t)}))}))})),r.post("/api/users/write/updateAccessLevel",((e,s,t)=>{const o=e.body,n=h.demo?{username:{$ne:"demoadmin"}}:{};console.log("\nFilter\n",n),e.userSession.access_level<=parseInt(o.upd)&&W((e=>{e.collection("users").updateOne({_id:d(o._id),...n,access_level:{$gt:1}},{$set:{access_level:parseInt(o.upd)}},((e,t)=>{e?(s.sendStatus(500),console.error(e)):s.send(t)}))}))})),r.use("/api/roles/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=5&&t()})),r.get("/api/roles/read/getAll",((e,s,t)=>{s.send({0:{name:"Root",access_level:0},5:{name:"Admin",access_level:5},30:{name:"Approver",access_level:30},100:{name:"User",access_level:100}})})),r.use("/api/api_keys/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=5&&t()})),r.get("/api/api_keys/read/getAll",((e,s,t)=>{s.send([])})),r.post("/api/api_keys/write/create",((e,s,t)=>null)),r.get("/api/subcomponent/read/:subComp/status",(async(e,s,t)=>{s.send(subcomponent_status[e.params.subComp])})),r.use("/api/config/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=5&&t()})),r.get("/api/config/read/getFull",(async(e,s,t)=>{let o={...E};if(h.demo&&e.userSession.access_level>0&&(o.discord_bot.token="hidden"),E.app_personalization.favicon=E.app_personalization.favicon||E.app_personalization.logo_url,process.env.HIDDEN_CONFIG_TABS)for(let e of process.env.HIDDEN_CONFIG_TABS.split(";"))try{delete o[e]}catch(e){}s.send(o)})),r.use("/api/config/write",((e,s,t)=>{h.demo&&0!=e.userSession.access_level?s.sendStatus(403):t()})),r.post("/api/config/write/update",(async(e,t,o)=>{const n=e.body;let i={};process.env.HIDDEN_CONFIG_TABS&&process.env.HIDDEN_CONFIG_TABS.split(";").includes(n.category)?i.status="config_rejected":(E[n.category]=n.config,s.writeFileSync(O+".bak",s.readFileSync(O)),s.writeFileSync(O,JSON.stringify(E,null,"\t")),i.status="config_updated"),i.action="reload",t.send(i),restartProcess(0,0,h)})),r.use("/api/dbconfig/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=5&&t()})),r.get("/api/dbconfig/read/getFull",(async(e,s,t)=>{e.body;W((e=>{e.collection("configs").find({config:{$exists:!0,$ne:null},category:{$exists:!0,$ne:null}}).toArray(((e,t)=>{e?J(s,e):t&&s.send(t)}))}))})),r.get("/api/dbconfig/read/:category",(async(e,s,t)=>{e.body;W((t=>{t.collection("configs").findOne({category:e.params.category},((e,t)=>{e?J(s,e):t&&s.send(t.config)}))}))})),r.use("/api/dbconfig/write",((e,s,t)=>{h.demo&&0!=e.userSession.access_level?s.sendStatus(403):t()})),r.post("/api/dbconfig/write/update",(async(e,s,t)=>{const o=e.body;W((e=>{e.collection("configs").updateOne({category:o.category},{$set:{config:o.config}},{upsert:!0},((e,t)=>{e?J(s,e):s.send({status:"config_updated",action:"reload"})}))}))})),r.use("/api/lists/read/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=100&&t()})),r.get("/api/lists/read/getAll",((e,s,t)=>{W((t=>{let o=e.userSession.access_level<100?{}:{hidden_managers:!1};t.collection("lists").find(o).toArray(((e,t)=>{e?J(s,e):s.send(t)}))}))})),r.use("/api/lists/write/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=10?t():s.sendStatus(401)})),r.use("/api/lists/write/checkPerm",(async(e,s,t)=>{s.send({status:"permission_granted"})})),r.post("/api/lists/write/addNewList",((e,s,t)=>{const o=e.body;W((e=>{const t={title:o.title,output_path:o.output_path,hidden_managers:o.hidden_managers,require_appr:o.require_appr,discord_roles:o.discord_roles};e.collection("lists").insertOne(t,((e,t)=>{e?J(s,e):s.send({status:"inserted_new_list",...t})}))}))})),r.post("/api/lists/write/deleteList",((e,s,t)=>{const o=e.body;W((e=>{e.collection("whitelists").deleteMany({id_list:d(o.sel_list_id)},((t,n)=>{t?J(s,t):e.collection("lists").deleteMany({_id:d(o.sel_list_id)},((e,t)=>{e?J(s,e):s.send({status:"removed_list",...t})}))}))}))})),r.post("/api/lists/write/editList",((e,s,t)=>{const o=e.body;W((e=>{const t={title:o.title,output_path:o.output_path,hidden_managers:o.hidden_managers,require_appr:o.require_appr,discord_roles:o.discord_roles};e.collection("lists").updateOne({_id:d(o.sel_list_id)},{$set:t},((e,t)=>{e?J(s,e):s.send({status:"edited_list",...t})}))}))})),r.use("/api/whitelist/read/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=100&&t()})),r.get("/api/whitelist/read/getAllClans",((e,s,t)=>{e.query;W((t=>{const o=[{$match:e.userSession.access_level>=100?{clan_code:e.userSession.clan_code}:{}},{$lookup:{from:"whitelists",localField:"_id",foreignField:"id_clan",as:"clan_whitelist"}},{$addFields:{player_count:{$size:"$clan_whitelist"}}},{$project:{clan_whitelist:0}},{$sort:{full_name:1}}];t.collection("clans").aggregate(o).toArray(((e,t)=>{e?(s.sendStatus(500),console.error(e)):s.send(t)}))}))})),r.get("/api/whitelist/read/getAll",((e,s,t)=>{const o=e.query;W((e=>{let t=o.sel_clan_id?{id_clan:d(o.sel_clan_id)}:{};const n=[{$match:{id_list:d(o.sel_list_id),...t}},{$lookup:{from:"groups",localField:"id_group",foreignField:"_id",as:"group_full_data"}},{$lookup:{from:"users",localField:"inserted_by",foreignField:"_id",as:"inserted_by"}},{$lookup:{from:"players",localField:"steamid64",foreignField:"steamid64",as:"serverData"}},{$sort:{id_clan:1,approved:-1,id_group:1,username:1}}];e.collection("whitelists").aggregate(n).toArray(((e,t)=>{e?(s.sendStatus(500),console.error(e)):s.send(t)}))}))})),r.get("/api/whitelist/read/getPendingApprovalClans",((e,s,t)=>{e.query;W((t=>{const o=[{$match:e.userSession.access_level>=100?{clan_code:e.userSession.clan_code}:{}},{$lookup:{from:"whitelists",let:{id_clan:"$_id"},pipeline:[{$match:{$expr:{$eq:["$id_clan","$$id_clan"]},approved:!1}}],as:"whitelists"}},{$sort:{whitelists_data:-1}},{$match:{whitelists:{$exists:!0,$ne:[]}}}];t.collection("clans").aggregate(o).toArray(((e,t)=>{e?(s.sendStatus(500),console.error(e)):(console.log(t),s.send(t))}))}))})),r.get("/api/whitelist/read/getPendingApproval",((e,s,t)=>{const o=e.query;W((e=>{const t=[{$lookup:{from:"whitelists",let:{id_list:"$_id"},pipeline:[{$match:{$expr:{$eq:["$id_list","$$id_list"]},approved:!1,id_clan:d(o.sel_clan_id)}},{$lookup:{from:"groups",localField:"id_group",foreignField:"_id",as:"group_full_data"}},{$lookup:{from:"users",localField:"inserted_by",foreignField:"_id",as:"inserted_by"}},{$sort:{id_clan:1,approved:-1,id_group:1,username:1}}],as:"wl_data"}},{$match:{wl_data:{$exists:!0,$ne:[]}}}];e.collection("lists").aggregate(t).toArray(((e,t)=>{e?(s.sendStatus(500),console.error(e)):(s.send(t),console.log("\n\n\n",t,"\n\n\n"))}))}))})),r.use("/api/whitelist/write/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<30?t():W(((o,n)=>{let i=e.userSession.access_level>=100?{clan_code:e.userSession.clan_code,admins:e.userSession.id_user.toString()}:{};o.collection("clans").findOne(i,((o,n)=>{o?J(s,o):null!=n?(console.log("authorizing",e.userSession.username,"=>",n),t()):(console.log("blocking",n),s.sendStatus(401))}))}))})),r.use("/api/whitelist/write/checkPerm",(async(e,s,t)=>{s.send({status:"permission_granted"})})),r.post("/api/whitelist/write/addPlayer",((e,s,t)=>{const o=e.body;W((t=>{const n=[{$match:e.userSession.access_level>=100?{clan_code:e.userSession.clan_code,admins:e.userSession.id_user.toString()}:{_id:d(o.sel_clan_id)}},{$lookup:{from:"whitelists",localField:"_id",foreignField:"id_clan",as:"clan_whitelist"}},{$addFields:{player_count:{$size:"$clan_whitelist"}}},{$project:{clan_whitelist:0}}];t.collection("clans").aggregate(n).toArray(((n,i)=>{console.log("====>",i);let r=i[0];if(n)console.log("error",n);else if(null!=r)if(""==r.player_limit||r.player_count{o?J(s,o):e.userSession.access_level<100||!a.hidden_managers?t.collection("groups").findOne(n.id_group,((o,l)=>{o?console.log("error",o):null!=l?(n.approved=!(l.require_appr||r.confirmation_ovrd||a.require_appr)||e.userSession.access_level<=30,t.collection("whitelists").insertOne(n,((t,o)=>{if(t)console.log("ERR",t);else if(s.send({status:"inserted_new_player",player:{...n,inserted_by:[{username:e.userSession.username}]},...o}),subcomponent_status.discord_bot){let s,t=[];n.approved?s={}:(s=(new v.ActionRowBuilder).addComponents((new v.ButtonBuilder).setCustomId("approval:approve:"+n._id).setLabel("Approve").setStyle(v.ButtonStyle.Success),(new v.ButtonBuilder).setCustomId("approval:reject:"+n._id).setLabel("Reject").setStyle(v.ButtonStyle.Danger)),t.push(s));const o=[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle("Whitelist Update").addFields({name:"Username",value:n.username,inline:!0},{name:"SteamID",value:v.hyperlink(n.steamid64,"https://steamcommunity.com/profiles/"+n.steamid64),inline:!0},{name:"Clan",value:i[0].full_name},{name:"Group",value:l.group_name,inline:!0})];n.expiration&&o[0].addFields({name:"Expiration",value:v.time(n.expiration,"R"),inline:!0}),o[0].addFields({name:"Manager",value:e.userSession.username},{name:"List",value:a.title},{name:"Approval",value:n.approved?":white_check_mark: Approved":":hourglass: Pending",inline:!0}),U.channels.cache.get(E.discord_bot.whitelist_updates_channel_id)?.send({embeds:o,components:t})}}))):s.send({status:"not_inserted",reason:"could find corresponding id"})})):s.sendStatus(402)}))}else s.send({status:"not_inserted",reason:"Player limit reached"});else s.sendStatus(401)}))}))})),r.post("/api/whitelist/write/removePlayer",((e,s,t)=>{const o=e.body;W((e=>{e.collection("whitelists").deleteOne({_id:d(o._id)},((e,t)=>{e?J(s,e):s.send({status:"removing_ok",...t})}))}))})),r.post("/api/whitelist/write/clearList",((e,s,t)=>{const o=e.body;W((e=>{e.collection("whitelists").deleteMany({id_clan:d(o.sel_clan_id),id_list:d(o.sel_list_id)},((e,t)=>{e?J(s,e):s.send({status:"clearing_ok",...t})}))}))})),r.use("/api/seeding/read/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<30?t():s.sendStatus(401)})),r.get("/api/seeding/read/getPlayers",((e,s,t)=>{W((e=>{e.collection("players").find({seeding_points:{$gte:1},username:{$ne:null}}).toArray(((e,t)=>{e?J(s,e):s.send(t)}))}))})),r.use("/api/players/read/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<30?t():s.sendStatus(401)})),r.get("/api/players/read/from/steamId/:id",((e,s,t)=>{W((t=>{t.collection("players").findOne({steamid64:e.params.id},((e,t)=>{e?J(s,e):s.send(t)}))}))})),r.get("/api/players/read/from/discordUserId/:id",((e,s,t)=>{W((t=>{t.collection("players").findOne({discord_user_id:e.params.id},((e,t)=>{e?J(s,e):s.send(t)}))}))})),r.use("/api/approval/write/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<30?t():s.sendStatus(401)})),r.use("/api/approval/write/setApprovedStatus",((e,s,t)=>{K(e.body,s)})),r.use("/api/gameGroups/write/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<10?t():s.sendStatus(401)})),r.use("/api/gameGroups/write/checkPerm",(async(e,s,t)=>{s.send({status:"permission_granted"})})),r.post("/api/gameGroups/write/newGroup",((e,s,t)=>{const o=e.body;W((e=>{e.collection("groups").insertOne(o,((e,t)=>{e?J(s,e):s.send({status:"group_created",data:o,dbRes:{...t}})}))}))})),r.post("/api/gameGroups/write/editGroup",((e,s,t)=>{let o={...e.body};delete o._id,W((t=>{t.collection("groups").updateOne({_id:d(e.body._id)},{$set:o},((o,n)=>{o?J(s,o):t.collection("groups").findOne({_id:d(e.body._id)},((e,t)=>{s.send({status:"edit_ok",...t})}))}))}))})),r.post("/api/gameGroups/write/remove",((e,s,t)=>{W((t=>{t.collection("groups").deleteOne({_id:d(e.body._id)},((e,t)=>{e?J(s,e):s.send({status:"removing_ok",...t})}))}))})),r.get("/api/gameGroups/read/getAllGroups",((e,s,t)=>{W((t=>{let o={};function n(){t.collection("groups").find(o).sort({group_name:1}).toArray(((e,t)=>{e?J(s,e):s.send(t)}))}e.userSession&&e.userSession.access_level>=100?t.collection("clans").findOne({clan_code:e.userSession.clan_code},((e,t)=>{if(e)J(s,e);else{let e=[];if(t)for(let s of t.available_groups)e.push(d(s));o={_id:{$in:e}},n()}})):n()}))})),r.use("/api/discord/*",((...e)=>{Z(30,...e)})),r.use("/api/discord/write",((...e)=>{Z(10,...e)})),r.get("/api/discord/read/getStatus",((e,s,t)=>{s.send(subcomponent_status.discord_bot)})),r.get("/api/discord/read/getRoles",((e,s,t)=>{e.query;if(subcomponent_status.discord_bot){const e=U.guilds.cache.find((e=>e.id==E.discord_bot.server_id));let t=[];for(let s of e.roles.cache)"@everyone"!==s[1].name.toLowerCase()&&t.push({id:s[1].id,name:s[1].name});s.send(t)}else s.sendStatus(404)})),r.get("/api/discord/read/getServers",((e,s,t)=>{e.query;if(subcomponent_status.discord_bot){let e=[];for(let s of U.guilds.cache)e.push({id:s[1].id,name:s[1].name});s.send(e)}else s.sendStatus(404)})),r.get("/api/discord/read/getChannels",(async(e,s,t)=>{e.query;if(subcomponent_status.discord_bot){s.send((await U.guilds.fetch(E.discord_bot.server_id)).channels.cache.sort(((e,s)=>e.rawPosition-s.rawPosition)).filter((e=>4!=e.type)))}else s.sendStatus(404)})),r.get("/api/discord/read/inviteLink",(async(e,s,t)=>{s.send({url:subcomponent_data.discord_bot.invite_link})})),r.use("/api/clans*",((e,s,t)=>{e.userSession&&e.userSession.access_level<10&&t()})),r.get("/api/clans/getAllClans",((e,s,t)=>{W((e=>{e.collection("clans").find().sort({full_name:1,tag:1}).toArray(((e,t)=>{s.send(t)}))}))})),r.post("/api/clans/removeClan",((e,s,t)=>{W((t=>{t.collection("clans").deleteOne({_id:d(e.body._id)},((e,t)=>{e?J(s,e):s.send({status:"removing_ok",...t})}))}))})),r.post("/api/clans/editClan",((e,s,t)=>{let o={...e.body};delete o._id,W((t=>{t.collection("clans").updateOne({_id:d(e.body._id)},{$set:o},((o,n)=>{o?J(s,o):t.collection("clans").findOne({_id:d(e.body._id)},((e,t)=>{s.send({status:"edit_ok",...t})}))}))}))})),r.get("/api/clans/getClanUsers",((e,s,t)=>{const o=e.query;W((e=>{e.collection("users").find({clan_code:o.clan_code}).toArray(((e,t)=>{s.send(t)}))}))})),r.get("/api/clans/getClanAdmins",((e,s,t)=>{const o=e.query;W((e=>{e.collection("clans").findOne({_id:d(o._id)},{projection:{admins:1}},((e,t)=>{if(e)J(s,e);else{let e=t.admins?t.admins:[];s.send(e)}}))}))})),r.post("/api/clans/editClanAdmins",((e,s,t)=>{const o=e.body;W((t=>{t.collection("clans").updateOne({_id:d(e.body._id)},{$set:{admins:o.clan_admins}},((e,t)=>{s.send({status:"edit_ok",...t})}))}))})),r.post("/api/clans/newClan",((e,s,t)=>{const o=e.body;let n;do{let e=Q(8);n=!1,W((t=>{t.collection("clans").findOne({full_name_lower:o.full_name.toLowerCase()},((i,r)=>{let a={...o,clan_code:e};a.full_name_lower=o.full_name.toLowerCase(),i?(s.sendStatus(500),console.error(i)):null==r?t.collection("clans").findOne({clan_code:e},((e,o)=>{e?(s.sendStatus(500),console.error(e)):null==o?t.collection("clans").insertOne(a,((e,t)=>{e?(s.sendStatus(500),console.error(e)):(s.send({status:"clan_created",clan_data:a}),console.log("New clan created:",a))})):n=!0})):(s.send({status:"clan_already_registered"}).status(409),console.log("Trying to register an already registered clan:",a))}))}))}while(n)})),r.use("/admin*",w),r.use("/admin",(function(e,s,t){i.static("admin")(e,s,t)})),r.use("/api/admin*",w),r.get("/api/admin",((e,s,t)=>{s.send({status:"Ok"})})),r.get("/api/admin/getConfig",((e,s,t)=>{s.send(E)})),r.get("/api/admin/checkInstallUpdate",((e,s,t)=>{s.send({status:"Ok"}),S(!0)})),r.get("/api/admin/restartApplication",((e,s,t)=>{s.send({status:"Ok"}),restartProcess(e.query.delay?e.query.delay:0,0,h)})),r.use(((e,s,t)=>{s.redirect(404,"/")}))}function U(e=null){if(console.log("Discord BOT"),E.discord_bot&&""!=E.discord_bot.token){const o=new v.Client({intents:[v.GatewayIntentBits.Guilds,v.GatewayIntentBits.GuildMessages,v.GatewayIntentBits.GuildMembers]});o.login(E.discord_bot.token);const n=setTimeout((()=>{console.error(" > Connection timed out. Check your discord_bot configuration."),console.log(" > Proceding without discord bot."),e()}),1e4),i=[{name:"ping",description:"Replies with Pong!"},{name:"listclans",description:"Gives a full list of clans with corresponding info"},{name:"topseed",description:"Gives a list of seeders"},{name:"profile",description:"Links the Discord profile to the Steam profile",options:[{name:"user",description:"Leave empty to get info of yourself, or fill to get info of a specific user",type:6,required:!1}]}];async function s(e){try{const s=await o.guilds.cache.get(E.discord_bot.server_id).members.cache.find((s=>s.id==e));if(s){const t=s.user,o=s._roles;try{W((s=>{s.collection("players").updateOne({discord_user_id:e},{$set:{discord_user_id:e,discord_username:t.username+"#"+t.discriminator,discord_roles_ids:o}},{upsert:!0})}))}catch(e){console.error(e)}}else W((s=>{s.collection("players").updateOne({discord_user_id:e},{$set:{discord_roles_ids:[]}})}))}catch(e){console.error(e)}}async function t(e,s,t=0){e.id;W((async e=>{let o=await e.collection("players").find({seeding_points:{$gte:1}}).skip(10*t).limit(11).sort({seeding_points:-1}).toArray();const n=(await e.collection("configs").findOne({category:"seeding_tracker"})).config,i=n.reward_needed_time.value*(n.reward_needed_time.option/1e3/60),r=o.splice(0,10).map(((e,s)=>[`**${10*t+s+1})**`,`${v.hyperlink(e.username,H(e.steamid64))}`,e.discord_user_id?v.userMention(e.discord_user_id):null,`*${Math.floor(100*(e.seeding_points||0)/i)}%*`].filter((e=>null!=e)).join(" "))),a={embeds:[{color:v.resolveColor(E.app_personalization.accent_color),title:"Top 10 Seeders",description:r.join("\n")}],components:[(new v.ActionRowBuilder).addComponents((new v.ButtonBuilder).setCustomId("topseed:page:"+(+t-1)).setLabel("⮜").setStyle(v.ButtonStyle.Success).setDisabled(t-1<0),(new v.ButtonBuilder).setCustomId("topseed:page:"+(+t+1)).setLabel("⮞").setStyle(v.ButtonStyle.Success).setDisabled(0==o.length))],ephemeral:!1};s.isButton()?(await s.deferUpdate(),await s.message.edit(a)):await s.reply(a)}))}o.on("ready",(async()=>{clearTimeout(n),U=new Proxy(o,{});subcomponent_data.discord_bot.invite_link=`https://discord.com/api/oauth2/authorize?client_id=${o.user.id}&permissions=268564544&scope=bot%20applications.commands`,console.log(" > Logged-in!"),console.log(` > Tag: ${o.user.tag}`),console.log(` > ID: ${o.user.id}`),console.log(` > Invite: ${subcomponent_data.discord_bot.invite_link}`),e();new v.REST({version:"10"}).setToken(E.discord_bot.token).put(v.Routes.applicationCommands(o.user.id),{body:i});let t=[];if(o.guilds)for(let e of o.guilds.cache)t.push({id:e[1].id,name:e[1].name});async function r(){const e=await o.guilds.cache.get(E.discord_bot.server_id);await e.members.fetch();W((e=>{e.collection("players").find({discord_user_id:{$exists:!0}}).toArray(((e,t)=>{for(let e of t)s(e.discord_user_id)}))}))}""==E.discord_bot.server_id&&t.length>0&&(E.discord_bot.server_id=t[0].id),""!=E.discord_bot.server_id&&(subcomponent_status.discord_bot=!0,r(),setInterval(r,3e5))})),o.on("raw",(async e=>{const s=e.d?.user?.id||"";switch(e.t){case"GUILD_MEMBER_UPDATE":let t=e.d.roles;W((o=>{o.collection("players").updateOne({discord_user_id:s},{$set:{discord_user_id:s,discord_username:e.d.user.username+"#"+e.d.user.discriminator,discord_roles_ids:t}},{upsert:!0})}));break;case"GUILD_MEMBER_REMOVE":dbo.collection("players").updateOne({discord_user_id:s},{$set:{discord_roles_ids:[]}})}})),o.on("interactionCreate",(async e=>{const n=e.member?e.member.user:e.user,i=`${n.id}`;if(e.isChatInputCommand()){switch(e.commandName){case"ping":await e.deferReply(),await e.followUp("Pong!");break;case"listclans":await e.deferReply(),W((s=>{s.collection("lists").find().toArray(((t,n)=>{s.collection("clans").aggregate([{$lookup:{from:"whitelists",localField:"_id",foreignField:"id_clan",as:"clan_whitelist"}},{$addFields:{uniqueSteamids:{$setUnion:"$clan_whitelist.steamid64"}}},{$addFields:{unique_players:{$size:"$uniqueSteamids"}}},{$project:{_id:0,admins:0,available_groups:0,clan_whitelist:0,uniqueSteamids:0}}]).toArray(((s,t)=>{s&&console.error(s);let r=[],a=!0;for(let s of t){let t=[];for(let e of["tag","clan_code","player_limit","unique_players"])"player_limit"==e&&""==s[e]&&(s[e]="ထ"),t.push({name:Y(e.replace(/\_/g," ")),value:s[e].toString(),inline:"full_name"!=e});const l=L.sort(),c=Object.keys(l)[0];if(l[c]){let e=[];for(let t of n){const o="https://"+c+":"+R.configs.https.port+"/"+t.output_path+"/"+s.clan_code;e.push(v.hyperlink(t.title,o))}t.push({name:"Whitelist",value:e.join(" - "),inline:!1})}r.push((new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle(Y(s.full_name.replace(/\_/g," "))).addFields(...t)),r.length%10==0&&(a?(a=!1,e.reply({content:v.userMention(i),embeds:r})):o.channels.cache.get(e.channelId).send({embeds:r}),r=[])}r.length>0&&o.channels.cache.get(e.channelId).followUp({embeds:r})}))}))}));break;case"profile":let s=null!=e.options.getUser("user"),r=s?e.options.getUser("user"):n;const a=!s;await e.deferReply({ephemeral:a}),W((t=>{t.collection("players").findOne({discord_user_id:s?r.id:i},(async(s,o)=>{if(s)J(null,s);else{let s=[{name:"Steam "+(o&&o.steamid64?"Username ":""),value:o&&o.steamid64?o.username:"*Not linked*",inline:!0}];if(o&&o.steamid64){s.push({name:"SteamID",value:v.hyperlink(o.steamid64,"https://steamcommunity.com/profiles/"+o.steamid64),inline:!0});const e=(await t.collection("configs").findOne({category:"seeding_tracker"})).config,n=e.reward_needed_time.value*(e.reward_needed_time.option/1e3/60),i=Math.floor(100*(o.seeding_points||0)/n);"true"==e.reward_enabled&&s.push({name:"Seeding Reward",value:`${i}%`,inline:!1});const r=await z(o.steamid64);for(let e of r.filter((e=>e.approved))){let t="Unlimited";e.expiration&&(t=`Expired ${v.time(e.expiration,"R")}`),s.push({name:e.name,value:t,inline:!0})}}let n=await e.followUp({content:v.userMention(r.id),embeds:[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setAuthor({name:r.username,iconURL:r.avatarURL()}).setTitle("Linked Profiles").addFields(...s)],components:[(new v.ActionRowBuilder).addComponents(o&&o.steamid64?(new v.ButtonBuilder).setCustomId("profilelink:steam:unlink").setLabel("Unlink Steam").setStyle(v.ButtonStyle.Danger):(new v.ButtonBuilder).setCustomId("profilelink:steam:link").setLabel("Link Steam").setStyle(v.ButtonStyle.Success))],ephemeral:a});n.interaction.ephemeral||setTimeout((async()=>{try{const e=await(n?.interaction?.webhook?.fetchMessage());e&&e.edit({components:[]})}catch(e){console.error(e)}}),3e4)}}))}));break;case"topseed":t(n,e)}s(i)}else if(e.isButton()){const s=e.customId.split(":");switch(s[0]){case"approval":const o="approve"==s[1];K({_id:s[2],approve_update:o}),e.reply({content:"Done",ephemeral:!0}),e.message.edit({components:[]});let r=e.message.embeds[0];r.fields.filter((e=>"Approval"==e.name))[0].value=o?":white_check_mark: Approved":":x: Rejected",r.fields.push({name:(o?"Approved":"Rejected")+" by",value:v.userMention(n.id),inline:!0}),e.message.edit({embeds:[r]});break;case"profilelink":if(e.message.mentions.users.find((e=>e.id==i))||e.message.ephemeral){if("steam"===s[1])switch(s[2]){case"link":let t;do{let s=Q(6);t=!1;const o=3e5,n=new Date(Date.now()+o);W((r=>{r.collection("profilesLinking").deleteOne({discordUserId:i},((a,l)=>{r.collection("profilesLinking").findOne({code:s},((a,l)=>{a?(res.sendStatus(500),console.error(a)):null==l?r.collection("profilesLinking").insertOne({source:"Discord",discordUserId:i,code:s,expiration:n},((t,a)=>{t?(res.sendStatus(500),console.error(t)):(e.reply({content:v.userMention(i),embeds:[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle("Link Steam Profile").setDescription("Join our Squad server and send in any chat the following code (case-sensitive)").addFields({name:"Linking Code",value:s,inline:!1},{name:"Expiration",value:v.time(n,"R"),inline:!1})],ephemeral:!0}),setInterval((async()=>{r.collection("profilesLinking").deleteOne({_id:a.insertedId})}),o))})):t=!0}))}))}))}while(t);break;case"unlink":if(s[3]){if("confirm"===s[3])try{W((s=>{s.collection("players").updateOne({discord_user_id:i},{$unset:{discord_user_id:1}},((s,t)=>{s?J(null,s):(console.log(t),1==t.modifiedCount?e.reply({content:v.userMention(i)+"\nYour Steam account has been unlinked",ephemeral:!0}):e.reply({content:v.userMention(i)+"\nYou don't have a Steam account to unlink",ephemeral:!0}))}))}))}catch(s){e.reply({content:"An error occurred and this interaction cannot be completed",ephemeral:!0}),console.error(s)}}else await e.reply({content:v.userMention(i),embeds:[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle("Unlink Steam").setDescription("Do you really want to unlink your steam profile?")],components:[(new v.ActionRowBuilder).addComponents((new v.ButtonBuilder).setCustomId("profilelink:steam:unlink:confirm").setLabel("Confirm").setStyle(v.ButtonStyle.Danger))],ephemeral:!0})}}else e.reply({content:v.userMention(i),embeds:[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle("Unauthorized").setDescription("Only the owner of the profile can use this action")],ephemeral:!0});break;case"topseed":"page"==s[1]&&t(n,e,s[2])}}else e.isModalSubmit()&&e.reply({content:"Modal received",ephemeral:!0})}))}else console.log(" > Not configured. Skipping."),e()}function G(e,s,t,o=2){return new Promise(((n,i)=>{setTimeout((()=>i("Timed out")),1e3*o),e.emit(s,t,(e=>{e.error?i(new Error(e.error)):n(e)}))}))}async function z(e){const s=await W(),t=await s.collection("groups").find().toArray(),o=await s.collection("configs").findOne({category:"seeding_tracker"}),n=o.config,i=n.reward_needed_time.value*(n.reward_needed_time.option/1e3/60);let r;console.log("CREATING objIdRewardGroup from value:",o.config.reward_group_id);try{r=d(o.config.reward_group_id)}catch(e){r=null,console.log("FAILED TO CREATE objIdRewardGroup from value:",o.config.reward_group_id,"\n",n)}let a,l=[];r&&(a=o.config.reward_group_id?await s.collection("groups").findOne({_id:r}):null),l.push(...(await s.collection("whitelists").find({steamid64:e}).toArray()).map((e=>{const s=t.find((s=>s._id.toString()==e.id_group.toString()));let o={};return o.id=e.id_group.toString(),o.name=s.group_name,o.expiration=e.expiration,o.approved=e.approved,o.source="Whitelists",o})));const c=[{$match:{steamid64:e,discord_roles_ids:{$exists:!0}}},{$lookup:{from:"groups",let:{pl_roles:"$discord_roles_ids"},pipeline:[{$addFields:{int_r:{$setIntersection:["$discord_roles","$$pl_roles"]}}},{$match:{int_r:{$ne:[]}}}],as:"groups"}},{$project:{discord_roles_ids:0,"groups.discord_roles":0,"groups.intersection_roles":0,"groups.int_r":0,"groups.require_appr":0}},{$match:{lists:{$ne:[]}}}],u=await s.collection("players").aggregate(c).toArray();for(let e of u){Math.round(100*e.seeding_points/i)>=100&&a&&l.push({id:a._id?.toString(),name:a.group_name,expiration:"fixed_reset"==n.tracking_mode&&new Date(n.next_reset),approved:"true"==n.reward_enabled,source:"Seeding"});for(let s of e.groups)l.push({id:s._id,name:s.group_name,expiration:!1,approved:!0,source:"Discord"})}return l}function H(e){return"https://steamcommunity.com/profiles/"+e}function W(e=(()=>{}),s=!1){return new Promise(((t,o)=>{if(!N||s){let s,n;process.env.MONGODB_CONNECTION_STRING?s=process.env.MONGODB_CONNECTION_STRING:(s=E.database.mongo.host.includes("://")?E.database.mongo.host:"mongodb://"+E.database.mongo.host+":"+E.database.mongo.port,n=E.database.mongo.database);c.connect(s,(function(s,i){if(s)o(s);else{var r=n?i.db(n):i.db();e(r),t(r)}}))}else e(j),t(j)}))}function J(e,s){e&&e.sendStatus(500),console.error(s)}function Y(e){return e.charAt(0).toUpperCase()+e.slice(1)}function V(e,s){console.log("upgrading conf");for(let t in s)if(void 0!==e[t])if(Array.isArray(s[t])){Array.isArray(e[t])||(e[t]=[e[t]]);for(let o=0;o=e[t].length?e[t].push(JSON.parse(JSON.stringify(s[t][o]))):V(e[t][o],s[t][o])}else"object"==typeof s[t]?("object"==typeof e[t]&&null!==e[t]||(e[t]={}),V(e[t],s[t])):typeof e[t]!=typeof s[t]&&("number"!=typeof s[t]||isNaN(+e[t])?e[t]=s[t]:e[t]=+e[t]);else e[t]=JSON.parse(JSON.stringify(s[t]))}function K(e,s=null){W((t=>{!e.approve_update||1!=e.approve_update&&"true"!=e.approve_update?t.collection("whitelists").deleteOne({_id:d(e._id)},((e,t)=>{e?J(s,e):s&&s.send({status:"rejected",...t})})):t.collection("whitelists").updateOne({_id:d(e._id)},{$set:{approved:!0}},((e,t)=>{e?J(s,e):s&&s.send({status:"approved",...t})}))}))}function Q(e=64){const s=u.randomBytes(e).toString("base64").slice(0,e);return s.match(/^[a-zA-Z\d]{1,}$/)?s:Q(e)}function Z(e,s,t,o){s.userSession&&s.userSession.access_level<=e?o():t.sendStatus(401)}function X(e,t=0){if(t>=e.length)return null;return s.existsSync(e[t])?e[t]:X(e,++t)}function ee(e,s=(()=>{}),t=15){console.log("Looking for free port close to "+e);const o=E?.web_server?.bind_ip||"0.0.0.0";try{w(e,o,(async function a(l,c){r.push(c);let d=c,u=n.createServer(),p=!1;await u.listen(d,o).on("error",(s=>{console.error(" > Failed",s.port),p=!0;let o=(443==e?4443:8080)+100*i;if(++i Couldn't find a free port.\n > Terminating process..."),process.exit(1)})),u.close(),p||(console.log(" > Found free port: "+d),s(d))}))}catch(e){}let i=0,r=[]}!function(e){console.log("Current dir: ",__dirname),console.log(`Configuration file path: ${O}`);let t={web_server:{bind_ip:"0.0.0.0",http_port:80,https_port:443,force_https:!1,session_duration_hours:168},database:{mongo:{host:process.env.MONGODB_CONNECTION_STRING||"127.0.0.1",port:27017,database:"Whitelister"}},app_personalization:{name:"Whitelister",favicon:"",accent_color:"#ffc40b",logo_url:"https://joinsquad.com/wp-content/themes/squad/img/logo.png",logo_border_radius:"10",title_hidden_in_header:!1},discord_bot:{token:"",server_id:"",whitelist_updates_channel_id:""},squadjs:[{websocket:{host:"",port:3e3,token:""}}],other:{automatic_updates:!0,update_check_interval_seconds:3600,whitelist_developers:!0,install_beta_versions:!1,logs_max_file_count:10,lists_cache_refresh_seconds:60}};if(s.existsSync(O)){var o={...JSON.parse(s.readFileSync(O,"utf-8").toString())};V(o,t),s.writeFileSync(O,JSON.stringify(o,null,"\t")),e()}else console.log(`Creating config file at path: ${O}`),ee(t.web_server.http_port,(function(o){console.log(`Found for free HTTP port: ${o}`),t.web_server.http_port=o,console.log(`Set HTTP port: ${t.web_server.http_port}`),ee(t.web_server.https_port,(function(o){t.web_server.https_port=o,console.log('Configuration file created, set your parameters and run again "node server".\nTerminating execution...'),s.writeFileSync(O,JSON.stringify(t,null,"\t")),process.env.PROCESS_MANAGER_TYPE&&"DOCKER"==process.env.PROCESS_MANAGER_TYPE.toUpperCase()?e():process.exit(0)}))}))}((()=>{console.log=(...e)=>{C(...e),T.trace(...e)},console.error=(...e)=>{x(...e),T.error(...e)},s.readdir(a.join(__dirname,"logs"),{withFileTypes:!0},((e,t)=>{(E&&E.other&&t.length>E.other.logs_max_file_count||t.length>10)&&(t=t.slice(0,t.length-E.other.logs_max_file_count)).forEach((e=>{s.remove(a.join(__dirname,"logs",e.name))}))})),console.log("ARGS:",h),console.log("ENV:",process.env),s.watchFile(O,((e,t)=>{console.log("Reloading configuration");let o,n=!1;try{o=JSON.parse(s.readFileSync(O,"utf-8").toString())}catch(e){console.log("Error found in conf.json file. Couldn't reload configuration."),n=!0}n||(E=o,console.log("Reloaded configuration.",E))})),E=JSON.parse(s.readFileSync(O,"utf-8").toString()),console.log(E),function(e=null){if(N){console.log("MongoDB connection");const s=setTimeout((()=>{console.error(" > Connection failed. Check your Database configuration."),restartProcess(0,1,h)}),1e4);W((t=>{t?console.log(" > Successfully connected"):(console.error(" > Unable to connect"),restartProcess(5,1,null,!0)),j=t,clearTimeout(s),e&&e()}),!0)}}((()=>{!function(e){const s={title:"Main",output_path:"wl",hidden_managers:!1,require_appr:!1,discord_roles:[]},t={category:"seeding_tracker",config:{reset_seeding_time:{value:1,option:864e5},reward_needed_time:{value:1,option:36e5},reward_group_id:"",next_reset:"",seeding_player_threshold:50,seeding_start_player_count:2,reward_enabled:"false",discord_seeding_reward_channel:"",discord_seeding_score_channel:"",tracking_mode:"incremental",time_deduction:{value:1,option:"perc_minute"}}};W((async o=>{function n(e){o.listCollections({name:"lists"}).next(((s,t)=>{null==t?i(e):o.collection("lists").countDocuments({},((s,t)=>{0===t?i(e):e()}))}))}function i(e){o.collection("lists").insertOne(s,((s,t)=>{s?J(res,s):(console.log("Collection 'lists' created.\n",t),o.collection("whitelists").updateMany({id_list:{$exists:!1}},{$set:{id_list:t.insertedId}},((s,t)=>{s?J(res,s):(console.log("Updated references"),e())})))}))}async function r(e){let t=!1;const n=Object.keys(s);async function i(r){const a=n[r];await o.collection("lists").updateMany({[a]:{$exists:!1}},{$set:{[a]:s[a]}},(async(s,o)=>{s?console.error(s):(o.modifiedCount>0&&(t||(t=!0,console.log("Repairing Lists format"))),r{})){let s=!1;const n=Object.keys(t.config);async function i(r){const a=`config.${n[r]}`;await o.collection("configs").updateOne({category:"seeding_tracker",[a]:{$exists:!1}},{$set:{[a]:t.config[n[r]]}},(async(t,o)=>{t?console.error(t):(o.modifiedCount>0&&(s||(s=!0,console.log("Repairing SD Config format"))),r Syncing collection "${s}"`);for(let t of e[s]){let e=!1;await o.collection(s).createIndex({[t]:1}).catch((s=>{e=!0,console.log(` > "${t}": Fail. Error: ${s}`)})),e||console.log(` > "${t}": Success`)}}}subcomponent_data.database.root_user_registered=!!await o.collection("users").findOne({access_level:0}),h.demo&&o.collection("users").updateOne({username:"demoadmin"},{$set:{password:u.createHash("sha512").update("demo").digest("hex"),access_level:5}},{upsert:!0}),await l(),o.collection("players").deleteMany({discord_user_id:{$exists:!0},steamid64:{$exists:!1}}),await o.collection("configs").findOne({category:"seeding_tracker",config:{$exists:!0}})||o.collection("configs").updateOne({category:"seeding_tracker"},{$set:{config:{tracking_mode:"incremental"}}},{upsert:!0}),await a(),n((()=>{r(e)}))}))}(B)}))})),process.on("uncaughtException",(function(e){console.error("Uncaught Exception",e.message,e.stack),++D>=(h["self-pm"],5)&&(console.error("Too many errors occurred during the current run. Terminating execution..."),restartProcess(0,1,h))}))}function restartProcess(e=0,s=0,t=null,o=!1){function n(e){e&&e()}t&&t["self-pm"]&&1==t["self-pm"]||o?(process.on("exit",(function(){console.log("Process terminated\nStarting new process"),require("child_process").spawn(process.argv.shift(),process.argv,{cwd:process.cwd(),detached:!0,stdio:"inherit"})})),setTimeout((()=>{n((()=>{process.exit(s)}))}),e)):setTimeout((()=>{console.log("Terminating execution. Process manager will restart me."),n((()=>{process.exit(s)}))}),e)}function terminateAndSpawnChildProcess(e=0,s=0){process.on("exit",(function(){console.log("Process terminated\nStarting new process"),require("child_process").spawn(process.argv.shift(),process.argv,{cwd:process.cwd(),detached:!0,stdio:"inherit"})})),setTimeout((()=>{process.exit(e)}),s)}init(); \ No newline at end of file +const{match:match}=require("assert"),cp=require("child_process");var installingDependencies=!1;const irequire=async e=>{try{require.resolve(e)}catch(e){installingDependencies||(installingDependencies=!0,console.log("INSTALLING DEPENDENCIES...\nTHIS PROCESS MAY TAKE SOME TIME. PLEASE WAIT")),cp.execSync("npm install"),await setImmediate((()=>{})),console.log("DEPENDECIES INSTALLED")}console.log(`Requiring "${e}"`);try{return require(e)}catch(s){console.log(`Could not include "${e}". Restart the script`),restartProcess(0,1)}};installUpdateDependencies=async()=>{console.log("INSTALLING/UPDATING DEPENDENCIES...\nTHIS PROCESS MAY TAKE SOME TIME. PLEASE WAIT"),cp.execSync("npm install")};var subcomponent_status={discord_bot:!1,squadjs:[]},subcomponent_data={discord_bot:{invite_link:""},squadjs:[],database:{root_user_registered:!1},updater:{updating:!1}};async function init(){const e=(await irequire("./package.json")).version,s=await irequire("fs-extra"),t=await irequire("node-stream-zip"),o=await irequire("https"),n=await irequire("http"),i=await irequire("express"),r=i(),a=await irequire("path"),l=await irequire("mongodb"),c=l.MongoClient,d=l.ObjectID,u=await irequire("crypto"),p=await irequire("body-parser"),_=await irequire("cookie-parser"),m=await irequire("nocache"),g=await irequire("log4js"),f=await irequire("axios"),h=(await irequire("minimist"))(process.argv.slice(2)),y=(await irequire("node-run-cmd"),await irequire("express-force-ssl")),w=await irequire("find-free-port"),{mainModule:S}=await irequire("process"),v=await irequire("discord.js"),{io:b}=await irequire("socket.io-client"),$=await irequire("dns"),k=require("util").promisify($.lookup);try{(await irequire("dotenv")).config()}catch(e){console.error(e)}const I=!0;var D=0;const O=h.c||"conf.json",C=console.log,x=console.error;let q=new Date;const A=a.join(__dirname,"logs",q.toISOString().replace(/T/g,"_").replace(/(:|-|\.|Z)/g,"")+".log");s.existsSync("logs")||s.mkdirSync("logs"),s.existsSync(A)||s.writeFileSync(A,""),g.configure({appenders:{App:{type:"file",filename:A}},categories:{default:{appenders:["App"],level:"all"}}});const T=g.getLogger("App");console.log("Log-file:",A);var E,R={http:void 0,https:void 0,configs:{https:{port:void 0},http:{port:void 0}},logging:{requests:!1}},P={ws:null,initDone:!1},L=[];const j=!0;var N,U;const F=new Map,M=new Map;function B(){async function n(e,s="",t=!1,o=!1){const n=`/${e}/${s}?${t}`;let i,r=!1;const a=F.get(n);a&&(r=!0,i=a);const l=Date.now();if(a&&!o||(i=await function(e,s="",t=!1){let o=new Promise(((o,n)=>{W((n=>{n.collection("lists").findOne({output_path:e},((i,r)=>{if(i)J(res,i);else if(null!=r){let i=s&&""!=s?{clan_code:s}:{},a="",l=[],c=[],d=[],u=[],p=[];const _=Q(6);n.collection("clans").find(i).toArray(((i,m)=>{for(let e of m)c[e._id.toString()]=e,d.push(e._id);n.collection("groups").find().sort({group_name:1}).toArray(((i,m)=>{for(let e of m)l[e._id.toString()]=e;l[_]={group_name:_,group_permissions:["reserve"]};const g=[{$match:{approved:!0,id_clan:{$in:d},id_list:r._id}},{$lookup:{from:"players",let:{steamid64:"$steamid64"},pipeline:[{$match:{$expr:{$eq:["$steamid64","$$steamid64"]},discord_user_id:{$exists:!0}}}],as:"serverPlayerData"}},{$sort:{id_clan:1,id_group:1,username_l:1}}];n.collection("whitelists").aggregate(g).toArray(((i,r)=>{if(i)J(res,i);else if(null!=r){for(let g of r){let f=(g.serverPlayerData&&g.serverPlayerData[0]?g.serverPlayerData[0].discord_username:null)||g.discord_username||"";p.push({username:g.username,steamid64:g.steamid64,groupId:g.id_group,clanTag:c[g.id_clan].tag,discordUsername:f})}if(s)m();else{const h=[{$match:{steamid64:{$ne:null},discord_roles_ids:{$exists:!0}}},{$lookup:{from:"lists",let:{pl_roles:"$discord_roles_ids"},pipeline:[{$match:{output_path:e}},{$addFields:{int_r:{$setIntersection:["$discord_roles","$$pl_roles"]}}},{$match:{int_r:{$ne:[]}}}],as:"lists"}},{$lookup:{from:"groups",let:{pl_roles:"$discord_roles_ids"},pipeline:[{$addFields:{int_r:{$setIntersection:["$discord_roles","$$pl_roles"]}}},{$match:{int_r:{$ne:[]}}}],as:"groups"}},{$project:{discord_roles_ids:0,"groups.discord_roles":0,"groups.intersection_roles":0,"groups.int_r":0,"groups.require_appr":0}},{$match:{lists:{$ne:[]}}}];n.collection("players").aggregate(h).toArray(((e,s)=>{if(e)res.sendStatus(500),console.error(e);else for(let e of s)if(t)a+=e.username+"\n";else for(let s of e.groups)p.push({username:e.username,steamid64:e.steamid64,groupId:s._id,clanTag:"Discord Role",discordUsername:null!=e.discord_username?e.discord_username:""});n.collection("configs").findOne({category:"seeding_tracker"},((e,s)=>{if(s&&s.config.reward_enabled&&"true"==s.config.reward_enabled){const e=s.config,t=e.reward_needed_time.value*e.reward_needed_time.option/1e3/60;n.collection("players").find({steamid64:{$ne:null},seeding_points:{$gte:t}}).toArray(((s,t)=>{s&&J(res,s);const o=t.map((s=>({username:s.username,steamid64:s.steamid64,groupId:e.reward_group_id,clanTag:"Seeder",discordUsername:null!=s.discord_username?s.discord_username:""})));p.push(...o),m()}))}else m()}))}))}function d(){for(let e of p)l[e.groupId]?(e.groupId=`${e.groupId}`,""==e.discordUsername||e.discordUsername.startsWith("@")||(e.discordUsername="@"+e.discordUsername),a+=`Admin=${e.steamid64}:${l[e.groupId].group_name} // [${e.clanTag}] ${e.username} ${e.discordUsername}\n`,u.includes(e.groupId)||u.push(e.groupId)):(console.log("Could not find group with id",e.groupId,l[e.groupId]),n.collection("whitelists").deleteMany({id_group:e.groupId}));a="\n"+a;for(let e of u){const s=l[e];a=`Group=${s.group_name}:${s.group_permissions.join(",")}\n`+a}}function m(){!E.other.whitelist_developers||t||s||p.push({username:"JetDave",steamid64:"76561198419229279",groupId:_,clanTag:"SQUAD Whitelister Developer",discordUsername:"@=BIA=JetDave#1001"}),d(),o(a)}}else o("")}))}))}))}else o(null)}))}))}));return o}(e,s,t)),!i)return null;if((Date.now()-l>1e3&&!r||o)&&(F.set(n,i),M.set(n,new Date)),r){i=`//////////////////////////////////////////////\n // Cached\n // Last update: ${M.get(n).toLocaleString()}\n //////////////////////////////////////////////\n `.replace(/^\s*/gm,"")+"\n"+i}return i}async function l(){const e=F.keys();for(let s of e){const e=s.match(/\/(.+)\/(.*)\?(true|false)/i);await n(e[1],e[2],"true"==e[3],!0)}}function c(e=null){return new Promise(((s,t)=>{W((o=>{o.collection("whitelists").deleteOne({expiration:{$lte:new Date}},((o,n)=>{o&&(console.error("removeExpiredPlayers",o),t(o)),e&&e(),s(n)}))}))}))}function g(e,s){const t=e.originalUrl.replace(/\?.*$/,"");let o=[];for(let e of o)if(t.startsWith(e))return e;return t.endsWith("/")?t.substring(0,t.length-1):t}function w(e,s,t){$(e)?t():s.redirect("/")}function S(o=!1,n=null){let i=new Date;console.log("Current version: ",e,"\n > Checking for updates",i.toLocaleString()),f.get("https://api.github.com/repos/fantinodavide/Squad_Whitelister/releases").then((i=>{const r=i.data[0],l=r.tag_name.toUpperCase().replace("V","").split("."),c=e.toString().split("."),d=E.other.install_beta_versions&&r.prerelease||!r.prerelease,u=parseInt(c[0]) Update found: "+r.tag_name,r.name),o?function(e){const o=e.assets.filter((e=>"release.zip"==e.name))[0].browser_download_url;console.log(" > Downloading update: "+e.tag_name,e.name,o);const n=a.resolve(__dirname,"tmp_update"),i=a.resolve(n,"gitupd.zip");s.existsSync(n)||s.mkdirSync(n);const r=s.createWriteStream(i);f({method:"get",url:o,responseType:"stream"}).then((e=>{e.data.pipe(r)})),r.on("finish",(e=>{setTimeout((()=>{!function(e,o,n){const i=new t({file:o,storeEntries:!0,skipEntryNameValidation:!0});i.on("ready",(()=>{s.remove(__dirname+"/dist",(()=>{i.extract("release/",__dirname,(async(t,o)=>{i.close(),await installUpdateDependencies(),console.log(" > Extracted",o,"files"),s.remove(e,(()=>{console.log(`${e} folder deleted`);const s=5e3;console.log(" > Restart in",s/1e3,"seconds"),restartProcess(s,0,h)}))}))}))}))}(n,i)}),1e3)})),r.on("error",(e=>{console.error(e)}))}(r):n&&n()):(console.log(" > No updates found"),n&&n())})).catch((e=>{console.error(" > Couldn't check for updates. Proceding startup",e),n&&n()}))}function $(e){return e.userSession&&e.userSession.access_level<=5}S(E.other.automatic_updates,(async()=>{console.log(" > Starting up"),setInterval((()=>{S(E.other.automatic_updates)}),1e3*E.other.update_check_interval_seconds),function(){function e(){W((async e=>{const s=await e.collection("configs").findOne({category:"seeding_tracker"});if(!s)return;const t=s.config;"fixed_reset"==t.tracking_mode&&t.reset_seeding_time&&t.next_reset&&new Date>new Date(t.next_reset)&&(e.collection("players").updateMany({},{$set:{seeding_points:0}}),e.collection("configs").updateOne({category:"seeding_tracker"},{$set:{"config.next_reset":new Date((new Date).valueOf()+t.reset_seeding_time.value*t.reset_seeding_time.option).toISOString().split("T")[0]}}))}))}e(),setInterval(e,20)}(),await async function(){console.log("Initializing WL List Caches");const e=await W(),s=await e.collection("lists").find().toArray();for(let e of s){const s=`/${e.output_path}/?false`;await n(e.output_path,"",!1);let t=null!=F.get(s);console.log(` > "${e.title}": ${t?"CACHED":"NOT CACHED"}`)}}(),setInterval(l,1e3*E.other.lists_cache_refresh_seconds),U((async()=>{if(await async function(e=null){console.log("Starting SquadJS WebSockets");for(let s in E.squadjs){subcomponent_status.squadjs[s]=!1;const t=E.squadjs[s];await new Promise((async(o,n)=>{if(console.error(" > Connection "+(+s+1)),t.websocket&&""!=t.websocket.token&&""!=t.websocket.host){const a=setTimeout((()=>{console.error(" > Timed out. Check your SquadJS WebSocket configuration."),i(s),o(!1)}),3e3),l=(await k(t.websocket.host)).address;function i(e,s=10){subcomponent_data.squadjs[e].reconnect_int=setInterval((()=>{const t=++subcomponent_data.squadjs[e].failedReconnections,o=Math.min(120,10*(Math.floor(t/5)+1));if(s{const n=[{$match:{steamid64:e.player.steamID}},{$lookup:{from:"groups",localField:"id_group",foreignField:"_id",as:"group_full_data"}}];o.collection("whitelists").aggregate(n).toArray((async(n,i)=>{n?J(null,n):o.collection("players").findOne({steamid64:e.player.steamID},(async(n,i)=>{if(n)J(null,n);else{const n=(await o.collection("configs").findOne({category:"seeding_tracker"})).config,r=n.reward_needed_time.value*(n.reward_needed_time.option/1e3/60),a=Math.floor(100*i.seeding_points/r)||0;let l="Welcome "+e.player.name+"\n\n";if(subcomponent_status.squadjs){let s=(await z(e.player.steamID)).filter((e=>e.approved));if(s.length>0){l+="Groups:\n";for(let e of s)l+=` - ${e.name}`,e.expiration&&(l+=": "+(((e.expiration-new Date)/1e3/60/60).toFixed(1)+"h left")),l+="\n"}}if(subcomponent_status.discord_bot){let e="";if(i&&i.discord_user_id&&""!=i.discord_user_id){const s=await U.users.fetch(i.discord_user_id);e=s.username+(s.discriminator?"#"+s.discriminator:"")}"true"==n.reward_enabled&&(l+="\nSeeding Reward: "+a+"%"),l+="\nDiscord Username: "+(""!=e?e:"Not linked")}subcomponent_status.squadjs&&setTimeout((()=>{subcomponent_data.squadjs[s].socket.emit("rcon.warn",e.player.steamID,l,(e=>{}))}),t)}}))}))}))}subcomponent_data.squadjs[s]||(subcomponent_data.squadjs[s]={socket:null,failedReconnections:0,reconnect_int:null}),subcomponent_data.squadjs[s].socket=b(`ws://${l}:${t.websocket.port}`,{auth:{token:t.websocket.token},autoUnref:!0}),subcomponent_data.squadjs[s].socket.on("connect",(async()=>{clearTimeout(a),console.log(" > Connected"),clearInterval(subcomponent_data.squadjs[s].reconnect_int),subcomponent_status.squadjs[s]=!0,P.initDone||(P.initDone=!0),o(!0)})),subcomponent_data.squadjs[s].socket.on("disconnect",(async()=>{subcomponent_status.squadjs[s]=!1,console.log("SquadJS WebSocket\n > Disconnected\n > Trying to reconnect"),i(s)})),subcomponent_data.squadjs[s].socket.on("PLAYER_CONNECTED",(async e=>{try{e&&e.player&&e.player.steamID&&(W((async s=>{s.collection("players").updateOne({steamid64:e.player.steamID},{$set:{username:e.player.name}},{upsert:!0})})),setTimeout((()=>{r(e)}),1e4))}catch(e){console.error("PLAYER_CONNECTED ERROR",e)}})),subcomponent_data.squadjs[s].socket.on("CHAT_MESSAGE",(async e=>{switch(e.message.toLowerCase().replace(/^(!|\/)/,"")){case"test":break;case"playerinfo":const t=await W();await t.collection("players").findOne({steamid64:e.player.steamID},{projection:{_id:0,seeding_points:1}});break;case"profile":r(e,0);break;default:6!=e.message.length||e.message.includes(" ")||W((async t=>{t.collection("profilesLinking").findOne({code:e.message},(async(o,n)=>{if(o)J(null,o);else if(n)if(n.expiration>new Date){const o=await U.users.fetch(n.discordUserId),i=o.username+(o.discriminator?"#"+o.discriminator:""),r=await t.collection("players").findOne({steamid64:e.player.steamID},{projection:{_id:0,seeding_points:1}});t.collection("players").updateOne({discord_user_id:n.discordUserId},{$set:{steamid64:e.player.steamID,username:e.player.name,discord_user_id:n.discordUserId,discord_username:i,...r}},{upsert:!0},((r,a)=>{t.collection("players").deleteOne({steamid64:e.player.steamID,discord_user_id:{$exists:!1}},((r,a)=>{if(r)return J(null,r);t.collection("profilesLinking").deleteOne({_id:n._id}),r?J(null,r):(subcomponent_data.squadjs[s].socket.emit("rcon.warn",e.steamID,"Linked Discord profile: "+i,(e=>{})),o.send({embeds:[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle("Profile Linked").setDescription("Your Discord profile has been linked to a Steam profile").addFields({name:"Steam Username",value:e.name,inline:!0},{name:"SteamID",value:v.hyperlink(e.steamID,"https://steamcommunity.com/profiles/"+e.steamID),inline:!0})]}))}))}))}else t.collection("profilesLinking").deleteOne({_id:n._id})}))}))}}))}else console.log(` > ${+s+1} Not configured. Skipping.`),e&&e()}))}return!0}(),async function(){console.log("Seeding Tracker: Starting");const e=1;async function s(){const e=await W(),s=await e.collection("configs").findOne({category:"seeding_tracker"}),t=s.config;if(!t)return void console.log("Seeding tracker configuration not set, unable to proceed.");const o=t.reward_needed_time.value*(t.reward_needed_time.option/1e3/60),n=[],i=[];for(let e in subcomponent_data.squadjs){if(!subcomponent_status.squadjs[e])continue;let s;i[e]=!1;try{s=await G(subcomponent_data.squadjs[e].socket,"rcon.getListPlayers",{})}catch(s){console.error(`Seeding tracker (${e}): ${s}`);continue}for(let t of s)t.sqJsConnectionIndex=e,n.push(t);s&&s.length>=(t.seeding_start_player_count||2)&&s.length<=t.seeding_player_threshold&&(i[e]=!0)}firstStart=!1,i.includes(!0)&&W((async e=>{if(n&&n.length>0){if("incremental"==s.config.tracking_mode){let t=0;"point_minute"==s.config.time_deduction.option?t=s.config.time_deduction.value:"perc_minute"==s.config.time_deduction.option&&(t=s.config.time_deduction.value*o/100),await e.collection("players").updateMany({steamid64:{$nin:n.map((e=>e.steamID))},seeding_points:{$gt:t}},{$inc:{seeding_points:-t}})}for(let r of n){if(!i[r.sqJsConnectionIndex])continue;const n=await e.collection("players").findOne({steamid64:r.steamID});e.collection("players").findOneAndUpdate({steamid64:r.steamID},{$set:{steamid64:r.steamID,username:r.name},$inc:{seeding_points:1}},{upsert:!0,returnDocument:"after"},(async(i,a)=>{if(i)J(null,i);else if("true"==t.reward_enabled){const i=Math.min(Math.floor(10*n?.seeding_points/o),10)||0,l=Math.min(Math.floor(10*a.value?.seeding_points/o),10)||0,c=10*l;if(l>0&&l>i)if(c<100){subcomponent_data.squadjs[r.sqJsConnectionIndex].socket.emit("rcon.warn",r.steamID,`Seeding Reward: \n\n${c}% completed`,(e=>{}));const e={embeds:[{color:v.resolveColor(E.app_personalization.accent_color),title:`${r.name}`,url:H(r.steamID),fields:[{name:"Score",value:c+"%",inline:!0},{name:"SteamID",value:v.hyperlink(r.steamID,H(r.steamID)),inline:!0},{name:"Discord User",value:a.value.discord_user_id?v.userMention(a.value.discord_user_id):"Not Linked",inline:!1}],footer:{text:new Array(10).fill("◼",0,l).fill("◻",l,10).join("")+` ${c}%`,icon_url:E.app_personalization.favicon||E.app_personalization.logo_url},thumbnail:{url:E.app_personalization.logo_url},timestamp:(new Date).toISOString()}],ephemeral:!1};U.channels.cache.get(t.discord_seeding_score_channel)?.send(e)}else if(100==c){const o=await e.collection("groups").findOne({_id:d(s.config.reward_group_id)});let n=`Seeding Reward Completed!\n\nYou have received: ${o.group_name}\n`;if("fixed_reset"==s.config.tracking_mode?n+=`Active until: ${new Date(s.config.next_reset).toLocaleDateString()}`:"incremental"==s.config.tracking_mode&&(n+="Don't drop below 100% to keep your reward!"),subcomponent_data.squadjs[r.sqJsConnectionIndex].socket.emit("rcon.warn",r.steamID,n,(e=>{})),subcomponent_status.discord_bot){const e=[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle(`${r.name} received the Seeding Reward!`).setURL(H(r.steamID)).addFields({name:"Username",value:r.name,inline:!0},{name:"SteamID",value:v.hyperlink(r.steamID,"https://steamcommunity.com/profiles/"+r.steamID),inline:!0},{name:"Discord User",value:a.value.discord_user_id?v.userMention(a.value.discord_user_id):"Not Linked",inline:!1},{name:"Reward Group",value:o.group_name,inline:!0}).setThumbnail(E.app_personalization.logo_url).setFooter({text:new Array(10).fill("◼",0,10).join("")+" 100%",iconURL:E.app_personalization.favicon||E.app_personalization.logo_url}).setTimestamp(new Date)];U.channels.cache.get(t.discord_seeding_reward_channel)?.send({embeds:e})}}}}))}}}))}s(),console.log("Seeding Tracker: Started"),setInterval(s,60*e*1e3)}(),I){const t=__dirname+"/ALTERNATIVE PORTS.txt";s.removeSync(t);const n=["certificates/certificate.crt","certificates/fullchain.pem","certificates/default.crt"];let i=X(["certificates/certificate.key","certificates/privkey.pem","certificates/default.key"]),a=X(n);const l=null,u=null,p=process.env.HTTPS_PORT,_=l?parseInt(l):u?parseInt(u):E.web_server.http_port,m=p?parseInt(p):E.web_server.https_port;function e(e,o){if(e!=o){const n="!!! WARNING !!! Port "+e+" is not available! Closest free port found: "+o+"\n";console.log(n),s.writeFileSync(t,n,{flag:"a+"})}}ee(_,(t=>{ee(m,(n=>{if(t?R.http=r.listen(t,E.web_server.bind_ip,(function(){var s=R.http.address().address;console.log("HTTP server listening at http://%s:%s",s,t),R.configs.http.port=t,e(E.web_server.http_port,t)})):console.error("Couldn't start HTTP server"),i&&a&&("true"!==process.env.HTTPS_SERVER_DISABLED&&"1"!==process.env.HTTPS_SERVER_DISABLED||!process.env.HTTPS_SERVER_DISABLED)){console.log("Using Certificate:",a,i);const t={key:s.readFileSync(i),cert:s.readFileSync(a)};R.https=o.createServer(t,r),n?(r.set("forceSSLOptions",{httpsPort:n}),R.configs.https.port=n,R.https.listen(n),console.log("HTTPS server listening at https://%s:%s",E.web_server.bind_ip,n),e(E.web_server.https_port,n)):console.error("Couldn't start HTTPS server"),async function(){}()}}))})),setInterval(c,6e4)}}))})),r.use(m()),r.set("etag",!1),r.use("/",p.json()),r.use("/",p.urlencoded({extended:!0})),r.use(_()),r.use((function(e,s,t){if(!E.web_server.force_https)return t();y(e,s,t)})),r.use("/",(function(e,s,t=null){const o=e.cookies;null!=o.stok&&""!=o.stok?W((n=>{n.collection("sessions").findOne({token:o.stok},{projection:{_id:0}},((o,i)=>{o?s.sendStatus(500):null!=i&&i.session_expiration>new Date?(e.userSession=i,n.collection("users").findOne({_id:i.id_user},{projection:{_id:0}},((o,n)=>{null!=n?(e.userSession={...e.userSession,...n},t&&t()):s.send({status:"login_required"}).status(401)}))):t&&t()}))})):t()})),r.use((function(e,s,t){const o=e.get("host");L[o]?L[o]++:L[o]=1;t()})),r.post("/api/changepassword",((e,s,t)=>{const o=e.body;W((t=>{const n=u.createHash("sha512").update(o.new_password).digest("hex"),i=u.createHash("sha512").update(o.old_password).digest("hex");t.collection("users").updateOne({_id:e.userSession.id_user,password:i},{$set:{password:n}},((e,t)=>{e?J(500,e):s.send(t)}))}))})),r.post("/api/login",((e,s,t)=>{const o=e.body;W((e=>{let t=u.createHash("sha512").update(o.password).digest("hex");e.collection("users").findOne({$or:[{username_lower:o.username.toLowerCase()},{username:o.username}],password:t},((e,t)=>{if(e)s.sendStatus(500),console.error(e);else if(null==t)s.sendStatus(401);else{const e=60*E.web_server.session_duration_hours*60*1e3;let o,n={login_date:new Date,session_expiration:new Date(Date.now()+e),id_user:t._id};do{o=!1,n.token=Q(128),W((e=>{e.collection("sessions").findOne({token:n.token},((t,i)=>{t?(s.sendStatus(500),console.error(t)):null==i?e.collection("sessions").insertOne(n,((e,t)=>{e?(s.sendStatus(500),console.error(e)):(s.cookie("stok",n.token,{expires:n.session_expiration}),s.cookie("uid",n.id_user,{expires:n.session_expiration}),s.send({status:"login_ok",userDt:n}))})):o=!0}))}))}while(o)}}))}))})),r.post("/api/signup",((e,s,t)=>{const o=e.body;let n,i={username:o.username,username_lower:o.username.toLowerCase(),password:u.createHash("sha512").update(o.password).digest("hex"),access_level:subcomponent_data.database.root_user_registered?100:0,clan_code:o.clan_code,registration_date:new Date,discord_username:o.discord_username};0!=i.access_level||subcomponent_data.database.root_user_registered||(subcomponent_data.database.root_user_registered=!0);const r=60*E.web_server.session_duration_hours*60*1e3;let a={...i};a.login_date=new Date,a.session_expiration=new Date(Date.now()+r),W((e=>{e.collection("users").findOne({username_lower:o.username.toLowerCase()},((t,o)=>{t?(s.sendStatus(500),console.error(t)):null==o?e.collection("users").insertOne(i,((e,t)=>{e?(s.sendStatus(500),console.error(e)):(console.log("\n\n\n\n\ninserted id=>",t.insertedId,"=>",i,"\n\n\n\n\n\n"),n=!1,a.token=Q(128),s.redirect(307,"/api/login"))})):s.status(401).send({message:"Username already exists",field:"username"})}))}))})),r.use("/",(function(e,s,t){if(!R.logging.requests)return t();const o=Object.keys(e.query).length>0,n=o?e.query:e.body,i=g(e);console.log("\nREQ: "+i+"\nSESSION: ",e.userSession,"\nPARM "+(o?"GET":"POST")+": ",n),t()})),r.get("/api/getVersion",((s,t,o)=>{t.send(e)})),r.use("/",i.static(__dirname+"/dist")),r.get("/api/getAppPersonalization",(function(e,s,t){s.send(E.app_personalization)})),r.get("/api/getTabs",((e,s,t)=>{const o=[subcomponent_data.database.root_user_registered?{}:{name:"Root User Registration",order:0,type:"tab",max_access_level:null},{name:"Clans",order:5,type:"tab",max_access_level:5},{name:"Whitelist",order:10,type:"tab",max_access_level:100},{name:"Groups",order:15,type:"tab",max_access_level:100},{name:"Approvals",order:25,type:"tab",max_access_level:30},{name:"Seeding",order:27,type:"tab",max_access_level:30},{name:"Users and Roles",order:30,type:"tab",max_access_level:5},{name:"Configuration",order:40,type:"tab",max_access_level:5}];let n;n=subcomponent_data.updater.updating?[{name:"Updating",order:0,type:"tab",max_access_level:null}]:o.filter((s=>null==s.max_access_level&&!e.userSession||e.userSession&&s.max_access_level&&e.userSession.access_level<=s.max_access_level)),s.send({tabs:n})})),r.get("/api/getContextMenu",((e,s,t)=>{let o=[{name:"",action:"",url:"",method:"",order:0}];$(e)&&(o=o.concat([])),s.send(o)})),r.get("/:basePath/:clan_code?",(async(e,s,t)=>{await c();const o=await n(e.params.basePath,e.params.clan_code,e.query?.usernamesOnly||!1);if(!o)return t();s.type("text/plain"),s.send(o)})),r.get("/dsTest",((e,s,t)=>{s.type("text/plain");const o=[{$match:{steamid64:{$ne:null},discord_roles_ids:{$exists:!0}}},{$lookup:{from:"groups",let:{pl_roles:"$discord_roles_ids"},pipeline:[{$addFields:{int_r:{$setIntersection:["$discord_roles","$$pl_roles"]}}},{$match:{int_r:{$ne:[]}}}],as:"groups"}},{$project:{discord_roles_ids:0,"groups.discord_roles":0,"groups.intersection_roles":0,"groups.int_r":0,"groups.require_appr":0}}];W((e=>{e.collection("players").aggregate(o).toArray(((e,t)=>{if(e)s.sendStatus(500),console.error(e);else{let e="";for(let s of t)for(let t of s.groups)e+="Admin="+s.steamid64+":"+t.group_name+" // [Discord Role] "+s.username+(null!=s.discord_username?" "+s.discord_username:"")+"\n";s.send(e)}}))}))})),r.get("/api/checkSession",((e,s,t)=>{e.userSession?s.send({status:"session_valid",userSession:e.userSession}):s.send({status:"login_required"}).status(401)})),r.use("/",(function(e,s,t=null){Object.keys(e.query).length>0?e.query:e.body,g(e);e.userSession?t():s.send({status:"login_required"}).status(401)})),r.use("/api/restart",((e,s,t)=>{s.send({status:"restarting"}),restartProcess(0,0,h)})),r.use("/api/logout",((e,s,t)=>{s.clearCookie("stok"),s.clearCookie("uid"),W((t=>{t.collection("sessions").deleteOne({token:e.userSession.token},((e,t)=>{e?J(s,e):s.send({status:"logout_ok"})}))}))})),r.use("/api/users/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=5&&t()})),r.get("/api/users/read/getAll",((e,s,t)=>{e.query;W((t=>{const o=[{$match:{...e.userSession.access_level>=100?{clan_code:e.userSession.clan_code}:{}}},{$lookup:{from:"clans",localField:"clan_code",foreignField:"clan_code",as:"clan_data"}},{$sort:{username:1}}];t.collection("users").aggregate(o).toArray(((e,t)=>{e?(s.sendStatus(500),console.error(e)):s.send(t)}))}))})),r.post("/api/users/write/remove",((e,s,t)=>{const o=e.body,n=h.demo?{username:{$ne:"demoadmin"}}:{};W((e=>{e.collection("users").deleteOne({_id:d(o._id),...n,access_level:{$gt:1}},((e,t)=>{e?(s.sendStatus(500),console.error(e)):s.send(t)}))}))})),r.post("/api/users/write/updateAccessLevel",((e,s,t)=>{const o=e.body,n=h.demo?{username:{$ne:"demoadmin"}}:{};console.log("\nFilter\n",n),e.userSession.access_level<=parseInt(o.upd)&&W((e=>{e.collection("users").updateOne({_id:d(o._id),...n,access_level:{$gt:1}},{$set:{access_level:parseInt(o.upd)}},((e,t)=>{e?(s.sendStatus(500),console.error(e)):s.send(t)}))}))})),r.use("/api/roles/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=5&&t()})),r.get("/api/roles/read/getAll",((e,s,t)=>{s.send({0:{name:"Root",access_level:0},5:{name:"Admin",access_level:5},30:{name:"Approver",access_level:30},100:{name:"User",access_level:100}})})),r.use("/api/api_keys/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=5&&t()})),r.get("/api/api_keys/read/getAll",((e,s,t)=>{s.send([])})),r.post("/api/api_keys/write/create",((e,s,t)=>null)),r.get("/api/subcomponent/read/:subComp/status",(async(e,s,t)=>{s.send(subcomponent_status[e.params.subComp])})),r.use("/api/config/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=5&&t()})),r.get("/api/config/read/getFull",(async(e,s,t)=>{let o={...E};if(h.demo&&e.userSession.access_level>0&&(o.discord_bot.token="hidden"),E.app_personalization.favicon=E.app_personalization.favicon||E.app_personalization.logo_url,process.env.HIDDEN_CONFIG_TABS)for(let e of process.env.HIDDEN_CONFIG_TABS.split(";"))try{delete o[e]}catch(e){}s.send(o)})),r.use("/api/config/write",((e,s,t)=>{h.demo&&0!=e.userSession.access_level?s.sendStatus(403):t()})),r.post("/api/config/write/update",(async(e,t,o)=>{const n=e.body;let i={};process.env.HIDDEN_CONFIG_TABS&&process.env.HIDDEN_CONFIG_TABS.split(";").includes(n.category)?i.status="config_rejected":(E[n.category]=n.config,s.writeFileSync(O+".bak",s.readFileSync(O)),s.writeFileSync(O,JSON.stringify(E,null,"\t")),i.status="config_updated"),i.action="reload",t.send(i),restartProcess(0,0,h)})),r.use("/api/dbconfig/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=5&&t()})),r.get("/api/dbconfig/read/getFull",(async(e,s,t)=>{e.body;W((e=>{e.collection("configs").find({config:{$exists:!0,$ne:null},category:{$exists:!0,$ne:null}}).toArray(((e,t)=>{e?J(s,e):t&&s.send(t)}))}))})),r.get("/api/dbconfig/read/:category",(async(e,s,t)=>{e.body;W((t=>{t.collection("configs").findOne({category:e.params.category},((e,t)=>{e?J(s,e):t&&s.send(t.config)}))}))})),r.use("/api/dbconfig/write",((e,s,t)=>{h.demo&&0!=e.userSession.access_level?s.sendStatus(403):t()})),r.post("/api/dbconfig/write/update",(async(e,s,t)=>{const o=e.body;W((e=>{e.collection("configs").updateOne({category:o.category},{$set:{config:o.config}},{upsert:!0},((e,t)=>{e?J(s,e):s.send({status:"config_updated",action:"reload"})}))}))})),r.use("/api/lists/read/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=100&&t()})),r.get("/api/lists/read/getAll",((e,s,t)=>{W((t=>{let o=e.userSession.access_level<100?{}:{hidden_managers:!1};t.collection("lists").find(o).toArray(((e,t)=>{e?J(s,e):s.send(t)}))}))})),r.use("/api/lists/write/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=10?t():s.sendStatus(401)})),r.use("/api/lists/write/checkPerm",(async(e,s,t)=>{s.send({status:"permission_granted"})})),r.post("/api/lists/write/addNewList",((e,s,t)=>{const o=e.body;W((e=>{const t={title:o.title,output_path:o.output_path,hidden_managers:o.hidden_managers,require_appr:o.require_appr,discord_roles:o.discord_roles};e.collection("lists").insertOne(t,((e,t)=>{e?J(s,e):s.send({status:"inserted_new_list",...t})}))}))})),r.post("/api/lists/write/deleteList",((e,s,t)=>{const o=e.body;W((e=>{e.collection("whitelists").deleteMany({id_list:d(o.sel_list_id)},((t,n)=>{t?J(s,t):e.collection("lists").deleteMany({_id:d(o.sel_list_id)},((e,t)=>{e?J(s,e):s.send({status:"removed_list",...t})}))}))}))})),r.post("/api/lists/write/editList",((e,s,t)=>{const o=e.body;W((e=>{const t={title:o.title,output_path:o.output_path,hidden_managers:o.hidden_managers,require_appr:o.require_appr,discord_roles:o.discord_roles};e.collection("lists").updateOne({_id:d(o.sel_list_id)},{$set:t},((e,t)=>{e?J(s,e):s.send({status:"edited_list",...t})}))}))})),r.use("/api/whitelist/read/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<=100&&t()})),r.get("/api/whitelist/read/getAllClans",((e,s,t)=>{e.query;W((t=>{const o=[{$match:e.userSession.access_level>=100?{clan_code:e.userSession.clan_code}:{}},{$lookup:{from:"whitelists",localField:"_id",foreignField:"id_clan",as:"clan_whitelist"}},{$addFields:{player_count:{$size:"$clan_whitelist"}}},{$project:{clan_whitelist:0}},{$sort:{full_name:1}}];t.collection("clans").aggregate(o).toArray(((e,t)=>{e?(s.sendStatus(500),console.error(e)):s.send(t)}))}))})),r.get("/api/whitelist/read/getAll",((e,s,t)=>{const o=e.query;W((e=>{let t=o.sel_clan_id?{id_clan:d(o.sel_clan_id)}:{};const n=[{$match:{id_list:d(o.sel_list_id),...t}},{$lookup:{from:"groups",localField:"id_group",foreignField:"_id",as:"group_full_data"}},{$lookup:{from:"users",localField:"inserted_by",foreignField:"_id",as:"inserted_by"}},{$lookup:{from:"players",localField:"steamid64",foreignField:"steamid64",as:"serverData"}},{$sort:{id_clan:1,approved:-1,id_group:1,username:1}}];e.collection("whitelists").aggregate(n).toArray(((e,t)=>{e?(s.sendStatus(500),console.error(e)):s.send(t)}))}))})),r.get("/api/whitelist/read/getPendingApprovalClans",((e,s,t)=>{e.query;W((t=>{const o=[{$match:e.userSession.access_level>=100?{clan_code:e.userSession.clan_code}:{}},{$lookup:{from:"whitelists",let:{id_clan:"$_id"},pipeline:[{$match:{$expr:{$eq:["$id_clan","$$id_clan"]},approved:!1}}],as:"whitelists"}},{$sort:{whitelists_data:-1}},{$match:{whitelists:{$exists:!0,$ne:[]}}}];t.collection("clans").aggregate(o).toArray(((e,t)=>{e?(s.sendStatus(500),console.error(e)):(console.log(t),s.send(t))}))}))})),r.get("/api/whitelist/read/getPendingApproval",((e,s,t)=>{const o=e.query;W((e=>{const t=[{$lookup:{from:"whitelists",let:{id_list:"$_id"},pipeline:[{$match:{$expr:{$eq:["$id_list","$$id_list"]},approved:!1,id_clan:d(o.sel_clan_id)}},{$lookup:{from:"groups",localField:"id_group",foreignField:"_id",as:"group_full_data"}},{$lookup:{from:"users",localField:"inserted_by",foreignField:"_id",as:"inserted_by"}},{$sort:{id_clan:1,approved:-1,id_group:1,username:1}}],as:"wl_data"}},{$match:{wl_data:{$exists:!0,$ne:[]}}}];e.collection("lists").aggregate(t).toArray(((e,t)=>{e?(s.sendStatus(500),console.error(e)):s.send(t)}))}))})),r.use("/api/whitelist/write/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<30?t():W(((o,n)=>{let i=e.userSession.access_level>=100?{clan_code:e.userSession.clan_code,admins:e.userSession.id_user.toString()}:{};o.collection("clans").findOne(i,((o,n)=>{o?J(s,o):null!=n?(console.log("authorizing",e.userSession.username,"=>",n),t()):(console.log("blocking",n),s.sendStatus(401))}))}))})),r.use("/api/whitelist/write/checkPerm",(async(e,s,t)=>{s.send({status:"permission_granted"})})),r.post("/api/whitelist/write/addPlayer",((e,s,t)=>{const o=e.body;W((t=>{const n=[{$match:e.userSession.access_level>=100?{clan_code:e.userSession.clan_code,admins:e.userSession.id_user.toString()}:{_id:d(o.sel_clan_id)}},{$lookup:{from:"whitelists",localField:"_id",foreignField:"id_clan",as:"clan_whitelist"}},{$addFields:{player_count:{$size:"$clan_whitelist"}}},{$project:{clan_whitelist:0}}];t.collection("clans").aggregate(n).toArray(((n,i)=>{console.log("====>",i);let r=i[0];if(n)console.log("error",n);else if(null!=r)if(""==r.player_limit||r.player_count{o?J(s,o):e.userSession.access_level<100||!a.hidden_managers?t.collection("groups").findOne(n.id_group,((o,l)=>{o?console.log("error",o):null!=l?(n.approved=!(l.require_appr||r.confirmation_ovrd||a.require_appr)||e.userSession.access_level<=30,t.collection("whitelists").insertOne(n,((t,o)=>{if(t)console.log("ERR",t);else if(s.send({status:"inserted_new_player",player:{...n,inserted_by:[{username:e.userSession.username}]},...o}),subcomponent_status.discord_bot){let s,t=[];n.approved?s={}:(s=(new v.ActionRowBuilder).addComponents((new v.ButtonBuilder).setCustomId("approval:approve:"+n._id).setLabel("Approve").setStyle(v.ButtonStyle.Success),(new v.ButtonBuilder).setCustomId("approval:reject:"+n._id).setLabel("Reject").setStyle(v.ButtonStyle.Danger)),t.push(s));const o=[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle("Whitelist Update").addFields({name:"Username",value:n.username,inline:!0},{name:"SteamID",value:v.hyperlink(n.steamid64,"https://steamcommunity.com/profiles/"+n.steamid64),inline:!0},{name:"Clan",value:i[0].full_name},{name:"Group",value:l.group_name,inline:!0})];n.expiration&&o[0].addFields({name:"Expiration",value:v.time(n.expiration,"R"),inline:!0}),o[0].addFields({name:"Manager",value:e.userSession.username},{name:"List",value:a.title},{name:"Approval",value:n.approved?":white_check_mark: Approved":":hourglass: Pending",inline:!0}),U.channels.cache.get(E.discord_bot.whitelist_updates_channel_id)?.send({embeds:o,components:t})}}))):s.send({status:"not_inserted",reason:"could find corresponding id"})})):s.sendStatus(402)}))}else s.send({status:"not_inserted",reason:"Player limit reached"});else s.sendStatus(401)}))}))})),r.post("/api/whitelist/write/removePlayer",((e,s,t)=>{const o=e.body;W((e=>{e.collection("whitelists").deleteOne({_id:d(o._id)},((e,t)=>{e?J(s,e):s.send({status:"removing_ok",...t})}))}))})),r.post("/api/whitelist/write/clearList",((e,s,t)=>{const o=e.body;W((e=>{e.collection("whitelists").deleteMany({id_clan:d(o.sel_clan_id),id_list:d(o.sel_list_id)},((e,t)=>{e?J(s,e):s.send({status:"clearing_ok",...t})}))}))})),r.use("/api/seeding/read/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<30?t():s.sendStatus(401)})),r.get("/api/seeding/read/getPlayers",((e,s,t)=>{W((e=>{e.collection("players").find({seeding_points:{$gte:1},username:{$ne:null}}).toArray(((e,t)=>{e?J(s,e):s.send(t)}))}))})),r.use("/api/players/read/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<30?t():s.sendStatus(401)})),r.get("/api/players/read/from/steamId/:id",((e,s,t)=>{W((t=>{t.collection("players").findOne({steamid64:e.params.id},((e,t)=>{e?J(s,e):s.send(t)}))}))})),r.get("/api/players/read/from/discordUserId/:id",((e,s,t)=>{W((t=>{t.collection("players").findOne({discord_user_id:e.params.id},((e,t)=>{e?J(s,e):s.send(t)}))}))})),r.use("/api/approval/write/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<30?t():s.sendStatus(401)})),r.use("/api/approval/write/setApprovedStatus",((e,s,t)=>{K(e.body,s)})),r.use("/api/gameGroups/write/*",((e,s,t)=>{e.userSession&&e.userSession.access_level<10?t():s.sendStatus(401)})),r.use("/api/gameGroups/write/checkPerm",(async(e,s,t)=>{s.send({status:"permission_granted"})})),r.post("/api/gameGroups/write/newGroup",((e,s,t)=>{const o=e.body;W((e=>{e.collection("groups").insertOne(o,((e,t)=>{e?J(s,e):s.send({status:"group_created",data:o,dbRes:{...t}})}))}))})),r.post("/api/gameGroups/write/editGroup",((e,s,t)=>{let o={...e.body};delete o._id,W((t=>{t.collection("groups").updateOne({_id:d(e.body._id)},{$set:o},((o,n)=>{o?J(s,o):t.collection("groups").findOne({_id:d(e.body._id)},((e,t)=>{s.send({status:"edit_ok",...t})}))}))}))})),r.post("/api/gameGroups/write/remove",((e,s,t)=>{W((t=>{t.collection("groups").deleteOne({_id:d(e.body._id)},((e,t)=>{e?J(s,e):s.send({status:"removing_ok",...t})}))}))})),r.get("/api/gameGroups/read/getAllGroups",((e,s,t)=>{W((t=>{let o={};function n(){t.collection("groups").find(o).sort({group_name:1}).toArray(((e,t)=>{e?J(s,e):s.send(t)}))}e.userSession&&e.userSession.access_level>=100?t.collection("clans").findOne({clan_code:e.userSession.clan_code},((e,t)=>{if(e)J(s,e);else{let e=[];if(t)for(let s of t.available_groups)e.push(d(s));o={_id:{$in:e}},n()}})):n()}))})),r.use("/api/discord/*",((...e)=>{Z(30,...e)})),r.use("/api/discord/write",((...e)=>{Z(10,...e)})),r.get("/api/discord/read/getStatus",((e,s,t)=>{s.send(subcomponent_status.discord_bot)})),r.get("/api/discord/read/getRoles",((e,s,t)=>{e.query;if(subcomponent_status.discord_bot){const e=U.guilds.cache.find((e=>e.id==E.discord_bot.server_id));let t=[];for(let s of e.roles.cache)"@everyone"!==s[1].name.toLowerCase()&&t.push({id:s[1].id,name:s[1].name});s.send(t)}else s.sendStatus(404)})),r.get("/api/discord/read/getServers",((e,s,t)=>{e.query;if(subcomponent_status.discord_bot){let e=[];for(let s of U.guilds.cache)e.push({id:s[1].id,name:s[1].name});s.send(e)}else s.sendStatus(404)})),r.get("/api/discord/read/getChannels",(async(e,s,t)=>{e.query;if(subcomponent_status.discord_bot){s.send((await U.guilds.fetch(E.discord_bot.server_id)).channels.cache.sort(((e,s)=>e.rawPosition-s.rawPosition)).filter((e=>4!=e.type)))}else s.sendStatus(404)})),r.get("/api/discord/read/inviteLink",(async(e,s,t)=>{s.send({url:subcomponent_data.discord_bot.invite_link})})),r.use("/api/clans*",((e,s,t)=>{e.userSession&&e.userSession.access_level<10&&t()})),r.get("/api/clans/getAllClans",((e,s,t)=>{W((e=>{e.collection("clans").find().sort({full_name:1,tag:1}).toArray(((e,t)=>{s.send(t)}))}))})),r.post("/api/clans/removeClan",((e,s,t)=>{W((t=>{t.collection("clans").deleteOne({_id:d(e.body._id)},((e,t)=>{e?J(s,e):s.send({status:"removing_ok",...t})}))}))})),r.post("/api/clans/editClan",((e,s,t)=>{let o={...e.body};delete o._id,W((t=>{t.collection("clans").updateOne({_id:d(e.body._id)},{$set:o},((o,n)=>{o?J(s,o):t.collection("clans").findOne({_id:d(e.body._id)},((e,t)=>{s.send({status:"edit_ok",...t})}))}))}))})),r.get("/api/clans/getClanUsers",((e,s,t)=>{const o=e.query;W((e=>{e.collection("users").find({clan_code:o.clan_code}).toArray(((e,t)=>{s.send(t)}))}))})),r.get("/api/clans/getClanAdmins",((e,s,t)=>{const o=e.query;W((e=>{e.collection("clans").findOne({_id:d(o._id)},{projection:{admins:1}},((e,t)=>{if(e)J(s,e);else{let e=t.admins?t.admins:[];s.send(e)}}))}))})),r.post("/api/clans/editClanAdmins",((e,s,t)=>{const o=e.body;W((t=>{t.collection("clans").updateOne({_id:d(e.body._id)},{$set:{admins:o.clan_admins}},((e,t)=>{s.send({status:"edit_ok",...t})}))}))})),r.post("/api/clans/newClan",((e,s,t)=>{const o=e.body;let n;do{let e=Q(8);n=!1,W((t=>{t.collection("clans").findOne({full_name_lower:o.full_name.toLowerCase()},((i,r)=>{let a={...o,clan_code:e};a.full_name_lower=o.full_name.toLowerCase(),i?(s.sendStatus(500),console.error(i)):null==r?t.collection("clans").findOne({clan_code:e},((e,o)=>{e?(s.sendStatus(500),console.error(e)):null==o?t.collection("clans").insertOne(a,((e,t)=>{e?(s.sendStatus(500),console.error(e)):(s.send({status:"clan_created",clan_data:a}),console.log("New clan created:",a))})):n=!0})):(s.send({status:"clan_already_registered"}).status(409),console.log("Trying to register an already registered clan:",a))}))}))}while(n)})),r.use("/admin*",w),r.use("/admin",(function(e,s,t){i.static("admin")(e,s,t)})),r.use("/api/admin*",w),r.get("/api/admin",((e,s,t)=>{s.send({status:"Ok"})})),r.get("/api/admin/getConfig",((e,s,t)=>{s.send(E)})),r.get("/api/admin/checkInstallUpdate",((e,s,t)=>{s.send({status:"Ok"}),S(!0)})),r.get("/api/admin/restartApplication",((e,s,t)=>{s.send({status:"Ok"}),restartProcess(e.query.delay?e.query.delay:0,0,h)})),r.use(((e,s,t)=>{s.redirect(404,"/")}))}function U(e=null){if(console.log("Discord BOT"),E.discord_bot&&""!=E.discord_bot.token){const o=new v.Client({intents:[v.GatewayIntentBits.Guilds,v.GatewayIntentBits.GuildMessages,v.GatewayIntentBits.GuildMembers]});o.login(E.discord_bot.token);const n=setTimeout((()=>{console.error(" > Connection timed out. Check your discord_bot configuration."),console.log(" > Proceding without discord bot."),e()}),1e4),i=[{name:"ping",description:"Replies with Pong!"},{name:"listclans",description:"Gives a full list of clans with corresponding info"},{name:"topseed",description:"Gives a list of seeders"},{name:"profile",description:"Links the Discord profile to the Steam profile",options:[{name:"user",description:"Leave empty to get info of yourself, or fill to get info of a specific user",type:6,required:!1}]}];async function s(e){try{const s=await o.guilds.cache.get(E.discord_bot.server_id).members.cache.find((s=>s.id==e));if(s){const t=s.user,o=s._roles;try{W((s=>{s.collection("players").updateOne({discord_user_id:e},{$set:{discord_user_id:e,discord_username:t.username+"#"+t.discriminator,discord_roles_ids:o}},{upsert:!0})}))}catch(e){console.error("updateUserRoles_1",e)}}else W((s=>{s.collection("players").updateOne({discord_user_id:e},{$set:{discord_roles_ids:[]}})}))}catch(e){console.error("updateUserRoles_2",e)}}async function t(e,s,t=0){e.id;W((async e=>{let o=await e.collection("players").find({seeding_points:{$gte:1}}).skip(10*t).limit(11).sort({seeding_points:-1}).toArray();const n=(await e.collection("configs").findOne({category:"seeding_tracker"})).config,i=n.reward_needed_time.value*(n.reward_needed_time.option/1e3/60),r=o.splice(0,10).map(((e,s)=>[`**${10*t+s+1})**`,`${v.hyperlink(e.username,H(e.steamid64))}`,e.discord_user_id?v.userMention(e.discord_user_id):null,`*${Math.floor(100*(e.seeding_points||0)/i)}%*`].filter((e=>null!=e)).join(" "))),a={embeds:[{color:v.resolveColor(E.app_personalization.accent_color),title:"Top 10 Seeders",description:r.join("\n")}],components:[(new v.ActionRowBuilder).addComponents((new v.ButtonBuilder).setCustomId("topseed:page:"+(+t-1)).setLabel("⮜").setStyle(v.ButtonStyle.Success).setDisabled(t-1<0),(new v.ButtonBuilder).setCustomId("topseed:page:"+(+t+1)).setLabel("⮞").setStyle(v.ButtonStyle.Success).setDisabled(0==o.length))],ephemeral:!1};s.isButton()?(await s.deferUpdate(),await s.message.edit(a)):await s.reply(a)}))}o.on("ready",(async()=>{clearTimeout(n),U=new Proxy(o,{});subcomponent_data.discord_bot.invite_link=`https://discord.com/api/oauth2/authorize?client_id=${o.user.id}&permissions=268564544&scope=bot%20applications.commands`,console.log(" > Logged-in!"),console.log(` > Tag: ${o.user.tag}`),console.log(` > ID: ${o.user.id}`),console.log(` > Invite: ${subcomponent_data.discord_bot.invite_link}`),e();new v.REST({version:"10"}).setToken(E.discord_bot.token).put(v.Routes.applicationCommands(o.user.id),{body:i});let t=[];if(o.guilds)for(let e of o.guilds.cache)t.push({id:e[1].id,name:e[1].name});async function r(){const e=await o.guilds.cache.get(E.discord_bot.server_id);await e.members.fetch();W((e=>{e.collection("players").find({discord_user_id:{$exists:!0}}).toArray(((e,t)=>{for(let e of t)s(e.discord_user_id)}))}))}""==E.discord_bot.server_id&&t.length>0&&(E.discord_bot.server_id=t[0].id),""!=E.discord_bot.server_id&&(subcomponent_status.discord_bot=!0,r(),setInterval(r,3e5))})),o.on("raw",(async e=>{const s=e.d?.user?.id||"";switch(e.t){case"GUILD_MEMBER_UPDATE":let t=e.d.roles;W((o=>{o.collection("players").updateOne({discord_user_id:s},{$set:{discord_user_id:s,discord_username:e.d.user.username+"#"+e.d.user.discriminator,discord_roles_ids:t}},{upsert:!0})}));break;case"GUILD_MEMBER_REMOVE":dbo.collection("players").updateOne({discord_user_id:s},{$set:{discord_roles_ids:[]}})}})),o.on("interactionCreate",(async e=>{const n=e.member?e.member.user:e.user,i=`${n.id}`;if(e.isChatInputCommand()){switch(e.commandName){case"ping":await e.deferReply(),await e.followUp("Pong!");break;case"listclans":await e.deferReply(),W((s=>{s.collection("lists").find().toArray(((t,n)=>{s.collection("clans").aggregate([{$lookup:{from:"whitelists",localField:"_id",foreignField:"id_clan",as:"clan_whitelist"}},{$addFields:{uniqueSteamids:{$setUnion:"$clan_whitelist.steamid64"}}},{$addFields:{unique_players:{$size:"$uniqueSteamids"}}},{$project:{_id:0,admins:0,available_groups:0,clan_whitelist:0,uniqueSteamids:0}}]).toArray(((s,t)=>{s&&console.error(s);let r=[],a=!0;for(let s of t){let t=[];for(let e of["tag","clan_code","player_limit","unique_players"])"player_limit"==e&&""==s[e]&&(s[e]="ထ"),t.push({name:Y(e.replace(/\_/g," ")),value:s[e].toString(),inline:"full_name"!=e});const l=L.sort(),c=Object.keys(l)[0];if(l[c]){let e=[];for(let t of n){const o="https://"+c+":"+R.configs.https.port+"/"+t.output_path+"/"+s.clan_code;e.push(v.hyperlink(t.title,o))}t.push({name:"Whitelist",value:e.join(" - "),inline:!1})}r.push((new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle(Y(s.full_name.replace(/\_/g," "))).addFields(...t)),r.length%10==0&&(a?(a=!1,e.reply({content:v.userMention(i),embeds:r})):o.channels.cache.get(e.channelId).send({embeds:r}),r=[])}r.length>0&&o.channels.cache.get(e.channelId).followUp({embeds:r})}))}))}));break;case"profile":let s=null!=e.options.getUser("user"),r=s?e.options.getUser("user"):n;const a=!s;await e.deferReply({ephemeral:a}),W((t=>{t.collection("players").findOne({discord_user_id:s?r.id:i},(async(s,o)=>{if(s)J(null,s);else{let s=[{name:"Steam "+(o&&o.steamid64?"Username ":""),value:o&&o.steamid64?o.username:"*Not linked*",inline:!0}];if(o&&o.steamid64){s.push({name:"SteamID",value:v.hyperlink(o.steamid64,"https://steamcommunity.com/profiles/"+o.steamid64),inline:!0});const e=(await t.collection("configs").findOne({category:"seeding_tracker"})).config,n=e.reward_needed_time.value*(e.reward_needed_time.option/1e3/60),i=Math.floor(100*(o.seeding_points||0)/n);"true"==e.reward_enabled&&s.push({name:"Seeding Reward",value:`${i}%`,inline:!1});const r=await z(o.steamid64);for(let e of r.filter((e=>e.approved))){let t="Unlimited";e.expiration&&(t=`Expired ${v.time(e.expiration,"R")}`),s.push({name:e.name,value:t,inline:!0})}}let n=await e.followUp({content:v.userMention(r.id),embeds:[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setAuthor({name:r.username,iconURL:r.avatarURL()}).setTitle("Linked Profiles").addFields(...s)],components:[(new v.ActionRowBuilder).addComponents(o&&o.steamid64?(new v.ButtonBuilder).setCustomId("profilelink:steam:unlink").setLabel("Unlink Steam").setStyle(v.ButtonStyle.Danger):(new v.ButtonBuilder).setCustomId("profilelink:steam:link").setLabel("Link Steam").setStyle(v.ButtonStyle.Success))],ephemeral:a});n.interaction.ephemeral||setTimeout((async()=>{try{const e=await(n?.interaction?.webhook?.fetchMessage());e&&e.edit({components:[]})}catch(e){console.error(e)}}),3e4)}}))}));break;case"topseed":t(n,e)}s(i)}else if(e.isButton()){const s=e.customId.split(":");switch(s[0]){case"approval":const o="approve"==s[1];K({_id:s[2],approve_update:o}),e.reply({content:"Done",ephemeral:!0}),e.message.edit({components:[]});let r=e.message.embeds[0];r.fields.filter((e=>"Approval"==e.name))[0].value=o?":white_check_mark: Approved":":x: Rejected",r.fields.push({name:(o?"Approved":"Rejected")+" by",value:v.userMention(n.id),inline:!0}),e.message.edit({embeds:[r]});break;case"profilelink":if(e.message.mentions.users.find((e=>e.id==i))||e.message.ephemeral){if("steam"===s[1])switch(s[2]){case"link":let t;do{let s=Q(6);t=!1;const o=3e5,n=new Date(Date.now()+o);W((r=>{r.collection("profilesLinking").deleteOne({discordUserId:i},((a,l)=>{r.collection("profilesLinking").findOne({code:s},((a,l)=>{a?(res.sendStatus(500),console.error(a)):null==l?r.collection("profilesLinking").insertOne({source:"Discord",discordUserId:i,code:s,expiration:n},((t,a)=>{t?(res.sendStatus(500),console.error(t)):(e.reply({content:v.userMention(i),embeds:[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle("Link Steam Profile").setDescription("Join our Squad server and send in any chat the following code (case-sensitive)").addFields({name:"Linking Code",value:s,inline:!1},{name:"Expiration",value:v.time(n,"R"),inline:!1})],ephemeral:!0}),setInterval((async()=>{r.collection("profilesLinking").deleteOne({_id:a.insertedId})}),o))})):t=!0}))}))}))}while(t);break;case"unlink":if(s[3]){if("confirm"===s[3])try{W((s=>{s.collection("players").updateOne({discord_user_id:i},{$unset:{discord_user_id:1}},((s,t)=>{s?J(null,s):1==t.modifiedCount?e.reply({content:v.userMention(i)+"\nYour Steam account has been unlinked",ephemeral:!0}):e.reply({content:v.userMention(i)+"\nYou don't have a Steam account to unlink",ephemeral:!0})}))}))}catch(s){e.reply({content:"An error occurred and this interaction cannot be completed",ephemeral:!0}),console.error(s)}}else await e.reply({content:v.userMention(i),embeds:[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle("Unlink Steam").setDescription("Do you really want to unlink your steam profile?")],components:[(new v.ActionRowBuilder).addComponents((new v.ButtonBuilder).setCustomId("profilelink:steam:unlink:confirm").setLabel("Confirm").setStyle(v.ButtonStyle.Danger))],ephemeral:!0})}}else e.reply({content:v.userMention(i),embeds:[(new v.EmbedBuilder).setColor(E.app_personalization.accent_color).setTitle("Unauthorized").setDescription("Only the owner of the profile can use this action")],ephemeral:!0});break;case"topseed":"page"==s[1]&&t(n,e,s[2])}}else e.isModalSubmit()&&e.reply({content:"Modal received",ephemeral:!0})}))}else console.log(" > Not configured. Skipping."),e()}function G(e,s,t,o=2){Date.now();return new Promise(((n,i)=>{e.timeout(1e3*o).emit(s,t,((e,s)=>{if(e){Date.now();return i(e)}n(s)}))}))}async function z(e){const s=await W(),t=await s.collection("groups").find().toArray(),o=await s.collection("configs").findOne({category:"seeding_tracker"}),n=o.config,i=n.reward_needed_time.value*(n.reward_needed_time.option/1e3/60);let r;try{r=d(o.config.reward_group_id)}catch(e){r=null,console.log("FAILED TO CREATE objIdRewardGroup from value:",o.config.reward_group_id,"\n",n)}let a,l=[];r&&(a=o.config.reward_group_id?await s.collection("groups").findOne({_id:r}):null),l.push(...(await s.collection("whitelists").find({steamid64:e}).toArray()).map((e=>{const s=t.find((s=>s._id.toString()==e.id_group.toString()));let o={};return o.id=e.id_group.toString(),o.name=s.group_name,o.expiration=e.expiration,o.approved=e.approved,o.source="Whitelists",o})));const c=[{$match:{steamid64:e,discord_roles_ids:{$exists:!0}}},{$lookup:{from:"groups",let:{pl_roles:"$discord_roles_ids"},pipeline:[{$addFields:{int_r:{$setIntersection:["$discord_roles","$$pl_roles"]}}},{$match:{int_r:{$ne:[]}}}],as:"groups"}},{$project:{discord_roles_ids:0,"groups.discord_roles":0,"groups.intersection_roles":0,"groups.int_r":0,"groups.require_appr":0}},{$match:{lists:{$ne:[]}}}],u=await s.collection("players").aggregate(c).toArray();for(let e of u){Math.round(100*e.seeding_points/i)>=100&&a&&l.push({id:a._id?.toString(),name:a.group_name,expiration:"fixed_reset"==n.tracking_mode&&new Date(n.next_reset),approved:"true"==n.reward_enabled,source:"Seeding"});for(let s of e.groups)l.push({id:s._id,name:s.group_name,expiration:!1,approved:!0,source:"Discord"})}return l}function H(e){return"https://steamcommunity.com/profiles/"+e}function W(e=(()=>{}),s=!1){return new Promise(((t,o)=>{if(!j||s){let s,n;process.env.MONGODB_CONNECTION_STRING?s=process.env.MONGODB_CONNECTION_STRING:(s=E.database.mongo.host.includes("://")?E.database.mongo.host:"mongodb://"+E.database.mongo.host+":"+E.database.mongo.port,n=E.database.mongo.database);c.connect(s,(function(s,i){if(s)o(s);else{var r=n?i.db(n):i.db();e(r),t(r)}}))}else e(N),t(N)}))}function J(e,s){e&&e.sendStatus(500),console.error(s)}function Y(e){return e.charAt(0).toUpperCase()+e.slice(1)}function V(e,s){console.log("upgrading conf");for(let t in s)if(void 0!==e[t])if(Array.isArray(s[t])){Array.isArray(e[t])||(e[t]=[e[t]]);for(let o=0;o=e[t].length?e[t].push(JSON.parse(JSON.stringify(s[t][o]))):V(e[t][o],s[t][o])}else"object"==typeof s[t]?("object"==typeof e[t]&&null!==e[t]||(e[t]={}),V(e[t],s[t])):typeof e[t]!=typeof s[t]&&("number"!=typeof s[t]||isNaN(+e[t])?e[t]=s[t]:e[t]=+e[t]);else e[t]=JSON.parse(JSON.stringify(s[t]))}function K(e,s=null){W((t=>{!e.approve_update||1!=e.approve_update&&"true"!=e.approve_update?t.collection("whitelists").deleteOne({_id:d(e._id)},((e,t)=>{e?J(s,e):s&&s.send({status:"rejected",...t})})):t.collection("whitelists").updateOne({_id:d(e._id)},{$set:{approved:!0}},((e,t)=>{e?J(s,e):s&&s.send({status:"approved",...t})}))}))}function Q(e=64){const s=u.randomBytes(e).toString("base64").slice(0,e);return s.match(/^[a-zA-Z\d]{1,}$/)?s:Q(e)}function Z(e,s,t,o){s.userSession&&s.userSession.access_level<=e?o():t.sendStatus(401)}function X(e,t=0){if(t>=e.length)return null;return s.existsSync(e[t])?e[t]:X(e,++t)}function ee(e,s=(()=>{}),t=15){console.log("Looking for free port close to "+e);const o=E?.web_server?.bind_ip||"0.0.0.0";try{w(e,o,(async function a(l,c){r.push(c);let d=c,u=n.createServer(),p=!1;await u.listen(d,o).on("error",(s=>{console.error(" > Failed",s.port),p=!0;let o=(443==e?4443:8080)+100*i;if(++i Couldn't find a free port.\n > Terminating process..."),process.exit(1)})),u.close(),p||(console.log(" > Found free port: "+d),s(d))}))}catch(e){}let i=0,r=[]}!function(e){console.log("Current dir: ",__dirname),console.log(`Configuration file path: ${O}`);let t={web_server:{bind_ip:"0.0.0.0",http_port:80,https_port:443,force_https:!1,session_duration_hours:168},database:{mongo:{host:process.env.MONGODB_CONNECTION_STRING||"127.0.0.1",port:27017,database:"Whitelister"}},app_personalization:{name:"Whitelister",favicon:"",accent_color:"#ffc40b",logo_url:"https://joinsquad.com/wp-content/themes/squad/img/logo.png",logo_border_radius:"10",title_hidden_in_header:!1},discord_bot:{token:"",server_id:"",whitelist_updates_channel_id:""},squadjs:[{websocket:{host:"",port:3e3,token:""}}],other:{automatic_updates:!0,update_check_interval_seconds:3600,whitelist_developers:!0,install_beta_versions:!1,logs_max_file_count:10,lists_cache_refresh_seconds:60}};if(s.existsSync(O)){var o={...JSON.parse(s.readFileSync(O,"utf-8").toString())};V(o,t),s.writeFileSync(O,JSON.stringify(o,null,"\t")),e()}else console.log(`Creating config file at path: ${O}`),ee(t.web_server.http_port,(function(o){console.log(`Found for free HTTP port: ${o}`),t.web_server.http_port=o,console.log(`Set HTTP port: ${t.web_server.http_port}`),ee(t.web_server.https_port,(function(o){t.web_server.https_port=o,console.log('Configuration file created, set your parameters and run again "node server".\nTerminating execution...'),s.writeFileSync(O,JSON.stringify(t,null,"\t")),process.env.PROCESS_MANAGER_TYPE&&"DOCKER"==process.env.PROCESS_MANAGER_TYPE.toUpperCase()?e():process.exit(0)}))}))}((()=>{console.log=(...e)=>{C(...e),T.trace(...e)},console.error=(...e)=>{x(...e),T.error(...e)},s.readdir(a.join(__dirname,"logs"),{withFileTypes:!0},((e,t)=>{(E&&E.other&&t.length>E.other.logs_max_file_count||t.length>10)&&(t=t.slice(0,t.length-E.other.logs_max_file_count)).forEach((e=>{s.remove(a.join(__dirname,"logs",e.name))}))})),console.log("ARGS:",h),console.log("ENV:",process.env),s.watchFile(O,((e,t)=>{console.log("Reloading configuration");let o,n=!1;try{o=JSON.parse(s.readFileSync(O,"utf-8").toString())}catch(e){console.log("Error found in conf.json file. Couldn't reload configuration."),n=!0}n||(E=o,console.log("Reloaded configuration.",E))})),E=JSON.parse(s.readFileSync(O,"utf-8").toString()),console.log(E),function(e=null){if(j){console.log("MongoDB connection");const s=setTimeout((()=>{console.error(" > Connection failed. Check your Database configuration."),restartProcess(0,1,h)}),1e4);W((t=>{t?console.log(" > Successfully connected"):(console.error(" > Unable to connect"),restartProcess(5,1,null,!0)),N=t,clearTimeout(s),e&&e()}),!0)}}((()=>{!function(e){const s={title:"Main",output_path:"wl",hidden_managers:!1,require_appr:!1,discord_roles:[]},t={category:"seeding_tracker",config:{reset_seeding_time:{value:1,option:864e5},reward_needed_time:{value:1,option:36e5},reward_group_id:"",next_reset:"",seeding_player_threshold:50,seeding_start_player_count:2,reward_enabled:"false",discord_seeding_reward_channel:"",discord_seeding_score_channel:"",tracking_mode:"incremental",time_deduction:{value:1,option:"perc_minute"}}};W((async o=>{function n(e){o.listCollections({name:"lists"}).next(((s,t)=>{null==t?i(e):o.collection("lists").countDocuments({},((s,t)=>{0===t?i(e):e()}))}))}function i(e){o.collection("lists").insertOne(s,((s,t)=>{s?J(res,s):(console.log("Collection 'lists' created.\n",t),o.collection("whitelists").updateMany({id_list:{$exists:!1}},{$set:{id_list:t.insertedId}},((s,t)=>{s?J(res,s):(console.log("Updated references"),e())})))}))}async function r(e){let t=!1;const n=Object.keys(s);async function i(r){const a=n[r];await o.collection("lists").updateMany({[a]:{$exists:!1}},{$set:{[a]:s[a]}},(async(s,o)=>{s?console.error(s):(o.modifiedCount>0&&(t||(t=!0,console.log("Repairing Lists format"))),r{})){let s=!1;const n=Object.keys(t.config);async function i(r){const a=`config.${n[r]}`;await o.collection("configs").updateOne({category:"seeding_tracker",[a]:{$exists:!1}},{$set:{[a]:t.config[n[r]]}},(async(t,o)=>{t?console.error(t):(o.modifiedCount>0&&(s||(s=!0,console.log("Repairing SD Config format"))),r Syncing collection "${s}"`);for(let t of e[s]){let e=!1;await o.collection(s).createIndex({[t]:1}).catch((s=>{e=!0,console.log(` > "${t}": Fail. Error: ${s}`)})),e||console.log(` > "${t}": Success`)}}}subcomponent_data.database.root_user_registered=!!await o.collection("users").findOne({access_level:0}),h.demo&&o.collection("users").updateOne({username:"demoadmin"},{$set:{password:u.createHash("sha512").update("demo").digest("hex"),access_level:5}},{upsert:!0}),await l(),o.collection("players").deleteMany({discord_user_id:{$exists:!0},steamid64:{$exists:!1}}),await o.collection("configs").findOne({category:"seeding_tracker",config:{$exists:!0}})||o.collection("configs").updateOne({category:"seeding_tracker"},{$set:{config:{tracking_mode:"incremental"}}},{upsert:!0}),await a(),n((()=>{r(e)}))}))}(B)}))})),process.on("uncaughtException",(function(e){console.error("Uncaught Exception",e.message,e.stack),++D>=(h["self-pm"],5)&&(console.error("Too many errors occurred during the current run. Terminating execution..."),restartProcess(0,1,h))}))}function restartProcess(e=0,s=0,t=null,o=!1){function n(e){e&&e()}t&&t["self-pm"]&&1==t["self-pm"]||o?(process.on("exit",(function(){console.log("Process terminated\nStarting new process"),require("child_process").spawn(process.argv.shift(),process.argv,{cwd:process.cwd(),detached:!0,stdio:"inherit"})})),setTimeout((()=>{n((()=>{process.exit(s)}))}),e)):setTimeout((()=>{console.log("Terminating execution. Process manager will restart me."),n((()=>{process.exit(s)}))}),e)}function terminateAndSpawnChildProcess(e=0,s=0){process.on("exit",(function(){console.log("Process terminated\nStarting new process"),require("child_process").spawn(process.argv.shift(),process.argv,{cwd:process.cwd(),detached:!0,stdio:"inherit"})})),setTimeout((()=>{process.exit(e)}),s)}init(); \ No newline at end of file diff --git a/server.js b/server.js index 63c4c63..7abcfdb 100644 --- a/server.js +++ b/server.js @@ -199,7 +199,7 @@ async function init() { setInterval(refreshWlCaches, config.other.lists_cache_refresh_seconds * 1000) discordBot(async () => { - SquadJSWebSocket(); + await SquadJSWebSocket(); seedingTimeTracking(); @@ -1329,7 +1329,7 @@ async function init() { console.error(err) } else { res.send(dbRes); - console.log("\n\n\n", dbRes, "\n\n\n"); + // console.log("\n\n\n", dbRes, "\n\n\n"); } }) }) @@ -1824,7 +1824,7 @@ async function init() { mongoConn((dbo) => { dbo.collection("whitelists").deleteOne({ expiration: { $lte: new Date() } }, (err, dbRes) => { if (err) { - console.error(err) + console.error('removeExpiredPlayers', err) reject(err); } if (next) next(); @@ -2389,7 +2389,6 @@ async function init() { dbo.collection("players").updateOne({ discord_user_id: sender_id }, { $unset: { discord_user_id: 1 } }, (err, dbRes) => { if (err) serverError(null, err); else { - console.log(dbRes) if (dbRes.modifiedCount == 1) interaction.reply({ content: Discord.userMention(sender_id) + "\nYour Steam account has been unlinked", ephemeral: true }); else interaction.reply({ content: Discord.userMention(sender_id) + "\nYou don't have a Steam account to unlink", ephemeral: true }) } @@ -2440,7 +2439,7 @@ async function init() { dbo.collection("players").updateOne({ discord_user_id: member_id }, { $set: { discord_user_id: member_id, discord_username: user.username + "#" + user.discriminator, discord_roles_ids: user_roles } }, { upsert: true }) }) } catch (error) { - console.error(error) + console.error('updateUserRoles_1', error) } } else { mongoConn((dbo) => { @@ -2448,7 +2447,7 @@ async function init() { }) } } catch (error) { - console.error(error) + console.error('updateUserRoles_2', error) } } @@ -2514,7 +2513,6 @@ async function init() { } async function SquadJSWebSocket(cb = null) { - let reconnect_int = null; let conns = []; console.log("Starting SquadJS WebSockets") @@ -2522,207 +2520,227 @@ async function init() { subcomponent_status.squadjs[ sqJsK ] = false; const sqJsConn = config.squadjs[ sqJsK ]; - if (sqJsConn.websocket && sqJsConn.websocket.token != "" && sqJsConn.websocket.host != "") { - // conns[ sqJsK ] = new Promise((resolve, reject) =>{ + await new Promise(async (res, rej) => { - // }) + console.error(` > Connection ${+sqJsK + 1}`); - const tm = setTimeout(() => { - console.error(` > Connection ${+sqJsK + 1} timed out. Check your SquadJS WebSocket configuration.`); - console.log(` > Proceding without SquadJS WebSocket ${+sqJsK + 1}.`); - // conns[ sqJsK ].resolve(true); - }, 10000) + if (sqJsConn.websocket && sqJsConn.websocket.token != "" && sqJsConn.websocket.host != "") { - const res_ip = (await lookup(sqJsConn.websocket.host)).address - // console.log(`Lookup ${ config.squadjs.websocket.host } => `, res_ip) - if (!subcomponent_data.squadjs[ sqJsK ]) subcomponent_data.squadjs[ sqJsK ] = {} + const tm = setTimeout(() => { + console.error(` > Timed out. Check your SquadJS WebSocket configuration.`); + // console.log(` > Proceding without SquadJS WebSocket ${+sqJsK + 1}.`); + startReconnectionInterval(sqJsK); + res(false); + // conns[ sqJsK ].resolve(true); + }, 3000) - subcomponent_data.squadjs[ sqJsK ].socket = io(`ws://${res_ip}:${sqJsConn.websocket.port}`, { - auth: { - token: sqJsConn.websocket.token - }, - autoUnref: true - }) - subcomponent_data.squadjs[ sqJsK ].socket.on("connect", async () => { - // conns[ sqJsK ].resolve(true); - clearTimeout(tm); - console.log(`SquadJS Websocket ${+sqJsK + 1} Connected`); - - // subcomponent_data.squadjs[ sqJsK ].socket.emit("rcon.warn", "76561198419229279", "Whitelister Test Connected", () => { }) - clearInterval(reconnect_int); - subcomponent_status.squadjs[ sqJsK ] = true; - - if (!squadjs.initDone) { - squadjs.initDone = true; - // seedingTimeTracking(); - } - }); + const res_ip = (await lookup(sqJsConn.websocket.host)).address + // console.log(`Lookup ${ config.squadjs.websocket.host } => `, res_ip) + if (!subcomponent_data.squadjs[ sqJsK ]) + subcomponent_data.squadjs[ sqJsK ] = { + socket: null, + failedReconnections: 0, + reconnect_int: null + } - // subcomponent_data.squadjs[ sqJsK ].socket.on("newListener", async (dt) => { - // console.log(dt) - // }) - // subcomponent_data.squadjs[ sqJsK ].socket.onAny(async (dt) => { - // console.log(dt) - // }) - subcomponent_data.squadjs[ sqJsK ].socket.on("disconnect", async () => { - subcomponent_status.squadjs[ sqJsK ] = false; - console.log("SquadJS WebSocket\n > Disconnected\n > Trying to reconnect") - reconnect_int = setInterval(() => { - if (!subcomponent_status.squadjs) subcomponent_data.squadjs[ sqJsK ].connect() - }, 10 * 1000) - }); - subcomponent_data.squadjs[ sqJsK ].socket.on("PLAYER_CONNECTED", async (dt) => { - // console.log("Player connected: ", dt) - // if (dt.player.steamID == "76561198419229279") { - // setTimeout(() => { - // subcomponent_data.squadjs[ sqJsK ].socket.emit("rcon.warn", "76561198419229279", "This server is using the Whitelister tool", (d) => { - // console.log(d) - // }) - // }, 5000) - // } - try { - if (dt && dt.player && dt.player.steamID) { - mongoConn(async (dbo) => { - dbo.collection("players").updateOne({ steamid64: dt.player.steamID }, { $set: { username: dt.player.name } }, { upsert: true }) - }) - setTimeout(() => { - welcomeMessage(dt) - }, 10000) + subcomponent_data.squadjs[ sqJsK ].socket = io(`ws://${res_ip}:${sqJsConn.websocket.port}`, { + auth: { + token: sqJsConn.websocket.token + }, + autoUnref: true + }) + subcomponent_data.squadjs[ sqJsK ].socket.on("connect", async () => { + // conns[ sqJsK ].resolve(true); + clearTimeout(tm); + console.log(` > Connected`); + + // subcomponent_data.squadjs[ sqJsK ].socket.emit("rcon.warn", "76561198419229279", "Whitelister Test Connected", () => { }) + clearInterval(subcomponent_data.squadjs[ sqJsK ].reconnect_int); + subcomponent_status.squadjs[ sqJsK ] = true; + + if (!squadjs.initDone) { + squadjs.initDone = true; + // seedingTimeTracking(); } - } catch (error) { - console.error("PLAYER_CONNECTED ERROR", error) + + res(true) + }); + + // subcomponent_data.squadjs[ sqJsK ].socket.on("newListener", async (dt) => { + // console.log(dt) + // }) + // subcomponent_data.squadjs[ sqJsK ].socket.onAny(async (dt) => { + // console.log(dt) + // }) + subcomponent_data.squadjs[ sqJsK ].socket.on("disconnect", async () => { + subcomponent_status.squadjs[ sqJsK ] = false; + console.log("SquadJS WebSocket\n > Disconnected\n > Trying to reconnect") + startReconnectionInterval(sqJsK); + }); + + function startReconnectionInterval(sqJsK, intervalSeconds = 10) { + subcomponent_data.squadjs[ sqJsK ].reconnect_int = setInterval(() => { + const failedReconnections = ++subcomponent_data.squadjs[ sqJsK ].failedReconnections; + const intervalSecondsOverride = Math.min(120, (Math.floor(failedReconnections / 5) + 1) * 10); + if (intervalSeconds < intervalSecondsOverride) { + clearInterval(subcomponent_data.squadjs[ sqJsK ].reconnect_int); + return startReconnectionInterval(sqJsK, intervalSecondsOverride); + } + + if (!subcomponent_status.squadjs) subcomponent_data.squadjs[ sqJsK ].connect() + }, intervalSeconds * 1000) } - }) - // subcomponent_data.squadjs[ sqJsK ].socket.on("PLAYER_DISCONNECTED", async (dt) => { - // console.log("Player disconnected: ", dt) - // }) - subcomponent_data.squadjs[ sqJsK ].socket.on("CHAT_MESSAGE", async (dt) => { - // console.log(`Message from connection ${sqJsK}`, dt) - switch (dt.message.toLowerCase().replace(/^(!|\/)/, '')) { - case 'test': - break; - case 'playerinfo': - console.log(dt); - const dbo = await mongoConn(); - const oldPlayerData = await dbo.collection("players").findOne({ steamid64: dt.player.steamID }, { projection: { _id: 0, seeding_points: 1 } }) - console.log("olddata", oldPlayerData) - break; - case 'profile': - welcomeMessage(dt, 0) - break; - default: - if (dt.message.length == 6 && !dt.message.includes(' ')) { - // console.log(dt); + subcomponent_data.squadjs[ sqJsK ].socket.on("PLAYER_CONNECTED", async (dt) => { + // console.log("Player connected: ", dt) + // if (dt.player.steamID == "76561198419229279") { + // setTimeout(() => { + // subcomponent_data.squadjs[ sqJsK ].socket.emit("rcon.warn", "76561198419229279", "This server is using the Whitelister tool", (d) => { + // console.log(d) + // }) + // }, 5000) + // } + try { + if (dt && dt.player && dt.player.steamID) { mongoConn(async (dbo) => { - dbo.collection("profilesLinking").findOne({ code: dt.message }, async (err, dbRes) => { - if (err) serverError(null, err); - else if (dbRes) { - if (dbRes.expiration > new Date()) { - const discordUser = await discordBot.users.fetch(dbRes.discordUserId); - const discordUsername = discordUser.username + (discordUser.discriminator ? "#" + discordUser.discriminator : ''); - const oldPlayerData = await dbo.collection("players").findOne({ steamid64: dt.player.steamID }, { projection: { _id: 0, seeding_points: 1 } }) - dbo.collection("players").updateOne({ discord_user_id: dbRes.discordUserId }, { $set: { steamid64: dt.player.steamID, username: dt.player.name, discord_user_id: dbRes.discordUserId, discord_username: discordUsername, ...oldPlayerData } }, { upsert: true }, (err, dbResU) => { - dbo.collection("players").deleteOne({ steamid64: dt.player.steamID, discord_user_id: { $exists: false } }, (err, dbResRem) => { - if (err) return serverError(null, err) - - dbo.collection("profilesLinking").deleteOne({ _id: dbRes._id }) - if (err) serverError(null, err); - else { - subcomponent_data.squadjs[ sqJsK ].socket.emit("rcon.warn", dt.steamID, "Linked Discord profile: " + discordUsername, (d) => { }) - discordUser.send({ - embeds: [ - new Discord.EmbedBuilder() - .setColor(config.app_personalization.accent_color) - .setTitle("Profile Linked") - .setDescription("Your Discord profile has been linked to a Steam profile") - .addFields( - { name: "Steam Username", value: dt.name, inline: true }, - { name: 'SteamID', value: Discord.hyperlink(dt.steamID, "https://steamcommunity.com/profiles/" + dt.steamID), inline: true }) - ] - }) - } - }); - }) - } else { - dbo.collection("profilesLinking").deleteOne({ _id: dbRes._id }) - } - } - }) + dbo.collection("players").updateOne({ steamid64: dt.player.steamID }, { $set: { username: dt.player.name } }, { upsert: true }) }) + setTimeout(() => { + welcomeMessage(dt) + }, 10000) } - break; - } - }) - async function welcomeMessage(dt, timeoutDelay = 5000) { - // console.log('Sending welcome message', dt) - mongoConn(async dbo => { - const pipeline = [ - { $match: { steamid64: dt.player.steamID } }, - { - $lookup: { - from: "groups", - localField: "id_group", - foreignField: "_id", - as: "group_full_data" - } - } - ] - dbo.collection("whitelists").aggregate(pipeline).toArray(async (err, dbRes) => { - if (err) serverError(null, err); - else { - dbo.collection("players").findOne({ steamid64: dt.player.steamID }, async (err, dbResP) => { - if (err) serverError(null, err); - else { - const st = await dbo.collection('configs').findOne({ category: 'seeding_tracker' }) - const stConf = st.config; - const requiredPoints = stConf.reward_needed_time.value * (stConf.reward_needed_time.option / 1000 / 60) - const percentageCompleted = Math.floor(100 * dbResP.seeding_points / requiredPoints) || 0; - // const reward_group = await dbo.collection('groups').findOne({ _id: ObjectID(st.config.reward_group_id) }) - - let msg = "Welcome " + dt.player.name + "\n\n"; - - if (subcomponent_status.squadjs) { - let groups = (await getPlayerGroups(dt.player.steamID)).filter(e => e.approved); - - if (groups.length > 0) { - msg += `Groups:\n` - for (let g of groups) { - msg += ` - ${g.name}` - if (g.expiration) msg += `: ${((g.expiration - new Date()) / 1000 / 60 / 60).toFixed(1) + "h left"}` - msg += '\n' + } catch (error) { + console.error("PLAYER_CONNECTED ERROR", error) + } + }) + // subcomponent_data.squadjs[ sqJsK ].socket.on("PLAYER_DISCONNECTED", async (dt) => { + // console.log("Player disconnected: ", dt) + // }) + subcomponent_data.squadjs[ sqJsK ].socket.on("CHAT_MESSAGE", async (dt) => { + // console.log(`Message from connection ${sqJsK}`, dt) + switch (dt.message.toLowerCase().replace(/^(!|\/)/, '')) { + case 'test': + break; + case 'playerinfo': + const dbo = await mongoConn(); + const oldPlayerData = await dbo.collection("players").findOne({ steamid64: dt.player.steamID }, { projection: { _id: 0, seeding_points: 1 } }) + + break; + case 'profile': + welcomeMessage(dt, 0) + break; + default: + if (dt.message.length == 6 && !dt.message.includes(' ')) { + // console.log(dt); + mongoConn(async (dbo) => { + dbo.collection("profilesLinking").findOne({ code: dt.message }, async (err, dbRes) => { + if (err) serverError(null, err); + else if (dbRes) { + if (dbRes.expiration > new Date()) { + const discordUser = await discordBot.users.fetch(dbRes.discordUserId); + const discordUsername = discordUser.username + (discordUser.discriminator ? "#" + discordUser.discriminator : ''); + const oldPlayerData = await dbo.collection("players").findOne({ steamid64: dt.player.steamID }, { projection: { _id: 0, seeding_points: 1 } }) + dbo.collection("players").updateOne({ discord_user_id: dbRes.discordUserId }, { $set: { steamid64: dt.player.steamID, username: dt.player.name, discord_user_id: dbRes.discordUserId, discord_username: discordUsername, ...oldPlayerData } }, { upsert: true }, (err, dbResU) => { + dbo.collection("players").deleteOne({ steamid64: dt.player.steamID, discord_user_id: { $exists: false } }, (err, dbResRem) => { + if (err) return serverError(null, err) + + dbo.collection("profilesLinking").deleteOne({ _id: dbRes._id }) + if (err) serverError(null, err); + else { + subcomponent_data.squadjs[ sqJsK ].socket.emit("rcon.warn", dt.steamID, "Linked Discord profile: " + discordUsername, (d) => { }) + discordUser.send({ + embeds: [ + new Discord.EmbedBuilder() + .setColor(config.app_personalization.accent_color) + .setTitle("Profile Linked") + .setDescription("Your Discord profile has been linked to a Steam profile") + .addFields( + { name: "Steam Username", value: dt.name, inline: true }, + { name: 'SteamID', value: Discord.hyperlink(dt.steamID, "https://steamcommunity.com/profiles/" + dt.steamID), inline: true }) + ] + }) + } + }); + }) + } else { + dbo.collection("profilesLinking").deleteOne({ _id: dbRes._id }) } } - } - if (subcomponent_status.discord_bot) { - let discordUsername = ""; - if (dbResP && dbResP.discord_user_id && dbResP.discord_user_id != "") { - const discordUser = await discordBot.users.fetch(dbResP.discord_user_id); - discordUsername = discordUser.username + (discordUser.discriminator ? "#" + discordUser.discriminator : ''); + }) + }) + } + break; + } + }) + async function welcomeMessage(dt, timeoutDelay = 5000) { + // console.log('Sending welcome message', dt) + mongoConn(async dbo => { + const pipeline = [ + { $match: { steamid64: dt.player.steamID } }, + { + $lookup: { + from: "groups", + localField: "id_group", + foreignField: "_id", + as: "group_full_data" + } + } + ] + dbo.collection("whitelists").aggregate(pipeline).toArray(async (err, dbRes) => { + if (err) serverError(null, err); + else { + dbo.collection("players").findOne({ steamid64: dt.player.steamID }, async (err, dbResP) => { + if (err) serverError(null, err); + else { + const st = await dbo.collection('configs').findOne({ category: 'seeding_tracker' }) + const stConf = st.config; + const requiredPoints = stConf.reward_needed_time.value * (stConf.reward_needed_time.option / 1000 / 60) + const percentageCompleted = Math.floor(100 * dbResP.seeding_points / requiredPoints) || 0; + // const reward_group = await dbo.collection('groups').findOne({ _id: ObjectID(st.config.reward_group_id) }) + + let msg = "Welcome " + dt.player.name + "\n\n"; + + if (subcomponent_status.squadjs) { + let groups = (await getPlayerGroups(dt.player.steamID)).filter(e => e.approved); + + if (groups.length > 0) { + msg += `Groups:\n` + for (let g of groups) { + msg += ` - ${g.name}` + if (g.expiration) msg += `: ${((g.expiration - new Date()) / 1000 / 60 / 60).toFixed(1) + "h left"}` + msg += '\n' + } + } } + if (subcomponent_status.discord_bot) { + let discordUsername = ""; + if (dbResP && dbResP.discord_user_id && dbResP.discord_user_id != "") { + const discordUser = await discordBot.users.fetch(dbResP.discord_user_id); + discordUsername = discordUser.username + (discordUser.discriminator ? "#" + discordUser.discriminator : ''); + } - if (stConf.reward_enabled == 'true') msg += "\nSeeding Reward: " + percentageCompleted + "%" - msg += "\nDiscord Username: " + (discordUsername != "" ? discordUsername : "Not linked") - } + if (stConf.reward_enabled == 'true') msg += "\nSeeding Reward: " + percentageCompleted + "%" + msg += "\nDiscord Username: " + (discordUsername != "" ? discordUsername : "Not linked") + } - if (subcomponent_status.squadjs) { - setTimeout(() => { - subcomponent_data.squadjs[ sqJsK ].socket.emit("rcon.warn", dt.player.steamID, msg, (d) => { }) - console.log(msg); - }, timeoutDelay) + if (subcomponent_status.squadjs) { + setTimeout(() => { + subcomponent_data.squadjs[ sqJsK ].socket.emit("rcon.warn", dt.player.steamID, msg, (d) => { }) + }, timeoutDelay) + } } - } - }) - } + }) + } + }) }) - }) + } + } else { + console.log(` > ${+sqJsK + 1} Not configured. Skipping.`); + if (cb) cb(); } - } else { - console.log(` > ${+sqJsK + 1} Not configured. Skipping.`); - if (cb) cb(); - } + }) } // return await Promise.all(conns) @@ -2730,21 +2748,24 @@ async function init() { } function emitPromise(socket, event, data, timeout = 2) { + const initial = Date.now(); return new Promise((resolve, reject) => { - setTimeout(() => reject("Timed out"), timeout * 1000) - socket.emit(event, data, (response) => { - if (response.error) { - reject(new Error(response.error)); - } else { + socket.timeout(timeout * 1000).emit(event, data, + (err, response) => { + if (err) { + const fin = Date.now() + const elapsed = +fin - +initial; + return reject(err); + } resolve(response); } - }); + ); }); } async function seedingTimeTracking() { console.log('Seeding Tracker: Starting') - const checkIntervalMinutes = 0.2; + const checkIntervalMinutes = 1; _check() console.log('Seeding Tracker: Started') @@ -2763,7 +2784,6 @@ async function init() { const activeSeedingConnections = [] for (let sqJsK in subcomponent_data.squadjs) { - // console.log(`Seeding tracker (${sqJsK}) status: ${subcomponent_status.squadjs[ sqJsK ]}`) if (!subcomponent_status.squadjs[ sqJsK ]) continue; // const singleServerPlayers = (await util.promisify(subcomponent_data.squadjs[ sqJsK ].socket.emit)("rcon.getListPlayers")) @@ -2773,7 +2793,7 @@ async function init() { try { singleServerPlayers = await emitPromise(subcomponent_data.squadjs[ sqJsK ].socket, "rcon.getListPlayers", {}) } catch (err) { - // console.error(`Seeding tracker (${sqJsK}): ${err}`) + console.error(`Seeding tracker (${sqJsK}): ${err}`) continue; } @@ -2788,99 +2808,101 @@ async function init() { // console.log('Online Players', players) firstStart = false; - if (activeSeedingConnections.includes(true)) { - mongoConn(async dbo => { - if (players && players.length > 0) { - if (st.config.tracking_mode == 'incremental') { - let deduction_points = 0; + if (!activeSeedingConnections.includes(true)) return; - if (st.config.time_deduction.option == 'point_minute') deduction_points = st.config.time_deduction.value - else if (st.config.time_deduction.option == 'perc_minute') deduction_points = st.config.time_deduction.value * requiredPoints / 100; + mongoConn(async dbo => { - await dbo.collection("players").updateMany({ steamid64: { $nin: players.map(p => p.steamID) }, seeding_points: { $gt: deduction_points } }, { $inc: { seeding_points: -deduction_points } }) - } + if (players && players.length > 0) { + if (st.config.tracking_mode == 'incremental') { + let deduction_points = 0; - for (let p of players) { - if (!activeSeedingConnections[ p.sqJsConnectionIndex ]) continue; - - const oldPlayerData = await dbo.collection("players").findOne({ steamid64: p.steamID }); - dbo.collection("players").findOneAndUpdate({ steamid64: p.steamID }, { $set: { steamid64: p.steamID, username: p.name }, $inc: { seeding_points: 1 } }, { upsert: true, returnDocument: 'after' }, async (err, dbRes) => { - if (err) serverError(null, err) - else if (stConf.reward_enabled == "true") { - const stepOld = Math.min(Math.floor(10 * oldPlayerData?.seeding_points / requiredPoints), 10) || 0; - const percentageCompletedOld = stepOld * 10; - const step = Math.min(Math.floor(10 * dbRes.value?.seeding_points / requiredPoints), 10) || 0; - const percentageCompleted = step * 10 - - if (step > 0 && step > stepOld) { - if (percentageCompleted < 100) { - subcomponent_data.squadjs[ p.sqJsConnectionIndex ].socket.emit("rcon.warn", p.steamID, `Seeding Reward: \n\n${percentageCompleted}% completed`, (d) => { }) - // new Array(10).fill('■',0,1).fill('□',1,10).join('') - - const messageContent = { - embeds: [ { - color: Discord.resolveColor(config.app_personalization.accent_color), - title: `${p.name}`, - url: steamProfileUrl(p.steamID), - fields: [ - { name: 'Score', value: percentageCompleted + "%", inline: true }, - { name: 'SteamID', value: Discord.hyperlink(p.steamID, steamProfileUrl(p.steamID)), inline: true }, - { name: 'Discord User', value: dbRes.value.discord_user_id ? Discord.userMention(dbRes.value.discord_user_id) : 'Not Linked', inline: false }, - ], - footer: { - text: new Array(10).fill('◼', 0, step).fill('◻', step, 10).join('') + ` ${percentageCompleted}%`, - icon_url: config.app_personalization.favicon || config.app_personalization.logo_url, - }, - thumbnail: { - url: config.app_personalization.logo_url, - }, - timestamp: new Date().toISOString(), - } ], - ephemeral: false - } - discordBot.channels.cache.get(stConf.discord_seeding_score_channel)?.send(messageContent) + if (st.config.time_deduction.option == 'point_minute') deduction_points = st.config.time_deduction.value + else if (st.config.time_deduction.option == 'perc_minute') deduction_points = st.config.time_deduction.value * requiredPoints / 100; - } else if (percentageCompleted == 100) { - const reward_group = await dbo.collection('groups').findOne({ _id: ObjectID(st.config.reward_group_id) }) - let message = - `Seeding Reward Completed!\n\nYou have received: ${reward_group.group_name}\n` - if (st.config.tracking_mode == 'fixed_reset') message += `Active until: ${(new Date(st.config.next_reset)).toLocaleDateString()}` - else if (st.config.tracking_mode == 'incremental') message += `Don't drop below 100% to keep your reward!` + await dbo.collection("players").updateMany({ steamid64: { $nin: players.map(p => p.steamID) }, seeding_points: { $gt: deduction_points } }, { $inc: { seeding_points: -deduction_points } }) + } - subcomponent_data.squadjs[ p.sqJsConnectionIndex ].socket.emit("rcon.warn", p.steamID, message, (d) => { }) - if (subcomponent_status.discord_bot) { - const embeds = [ - new Discord.EmbedBuilder() - .setColor(config.app_personalization.accent_color) - .setTitle(`${p.name} received the Seeding Reward!`) - .setURL(steamProfileUrl(p.steamID)) - // .setDescription(formatEmbed("Manager", ) + formatEmbed("List", dbResList.title)), - .addFields( - { name: 'Username', value: p.name, inline: true }, - { name: 'SteamID', value: Discord.hyperlink(p.steamID, "https://steamcommunity.com/profiles/" + p.steamID), inline: true }, - { name: 'Discord User', value: dbRes.value.discord_user_id ? Discord.userMention(dbRes.value.discord_user_id) : 'Not Linked', inline: false }, - { name: 'Reward Group', value: reward_group.group_name, inline: true } - // { name: 'Expiration', value: reward_group.group_name, inline: true } - ) - .setThumbnail(config.app_personalization.logo_url) - .setFooter({ - text: new Array(10).fill('◼', 0, 10).join('') + " 100%", - iconURL: config.app_personalization.favicon || config.app_personalization.logo_url, - }) - .setTimestamp(new Date()) - ] - discordBot.channels.cache.get(stConf.discord_seeding_reward_channel)?.send({ embeds: embeds }) - } + for (let p of players) { + if (!activeSeedingConnections[ p.sqJsConnectionIndex ]) continue; + + const oldPlayerData = await dbo.collection("players").findOne({ steamid64: p.steamID }); + dbo.collection("players").findOneAndUpdate({ steamid64: p.steamID }, { $set: { steamid64: p.steamID, username: p.name }, $inc: { seeding_points: 1 } }, { upsert: true, returnDocument: 'after' }, async (err, dbRes) => { + if (err) serverError(null, err) + else if (stConf.reward_enabled == "true") { + const stepOld = Math.min(Math.floor(10 * oldPlayerData?.seeding_points / requiredPoints), 10) || 0; + const percentageCompletedOld = stepOld * 10; + const step = Math.min(Math.floor(10 * dbRes.value?.seeding_points / requiredPoints), 10) || 0; + const percentageCompleted = step * 10 + + if (step > 0 && step > stepOld) { + if (percentageCompleted < 100) { + subcomponent_data.squadjs[ p.sqJsConnectionIndex ].socket.emit("rcon.warn", p.steamID, `Seeding Reward: \n\n${percentageCompleted}% completed`, (d) => { }) + // new Array(10).fill('■',0,1).fill('□',1,10).join('') + + const messageContent = { + embeds: [ { + color: Discord.resolveColor(config.app_personalization.accent_color), + title: `${p.name}`, + url: steamProfileUrl(p.steamID), + fields: [ + { name: 'Score', value: percentageCompleted + "%", inline: true }, + { name: 'SteamID', value: Discord.hyperlink(p.steamID, steamProfileUrl(p.steamID)), inline: true }, + { name: 'Discord User', value: dbRes.value.discord_user_id ? Discord.userMention(dbRes.value.discord_user_id) : 'Not Linked', inline: false }, + ], + footer: { + text: new Array(10).fill('◼', 0, step).fill('◻', step, 10).join('') + ` ${percentageCompleted}%`, + icon_url: config.app_personalization.favicon || config.app_personalization.logo_url, + }, + thumbnail: { + url: config.app_personalization.logo_url, + }, + timestamp: new Date().toISOString(), + } ], + ephemeral: false + } + discordBot.channels.cache.get(stConf.discord_seeding_score_channel)?.send(messageContent) + + } else if (percentageCompleted == 100) { + const reward_group = await dbo.collection('groups').findOne({ _id: ObjectID(st.config.reward_group_id) }) + let message = + `Seeding Reward Completed!\n\nYou have received: ${reward_group.group_name}\n` + if (st.config.tracking_mode == 'fixed_reset') message += `Active until: ${(new Date(st.config.next_reset)).toLocaleDateString()}` + else if (st.config.tracking_mode == 'incremental') message += `Don't drop below 100% to keep your reward!` + + subcomponent_data.squadjs[ p.sqJsConnectionIndex ].socket.emit("rcon.warn", p.steamID, message, (d) => { }) + if (subcomponent_status.discord_bot) { + const embeds = [ + new Discord.EmbedBuilder() + .setColor(config.app_personalization.accent_color) + .setTitle(`${p.name} received the Seeding Reward!`) + .setURL(steamProfileUrl(p.steamID)) + // .setDescription(formatEmbed("Manager", ) + formatEmbed("List", dbResList.title)), + .addFields( + { name: 'Username', value: p.name, inline: true }, + { name: 'SteamID', value: Discord.hyperlink(p.steamID, "https://steamcommunity.com/profiles/" + p.steamID), inline: true }, + { name: 'Discord User', value: dbRes.value.discord_user_id ? Discord.userMention(dbRes.value.discord_user_id) : 'Not Linked', inline: false }, + { name: 'Reward Group', value: reward_group.group_name, inline: true } + // { name: 'Expiration', value: reward_group.group_name, inline: true } + ) + .setThumbnail(config.app_personalization.logo_url) + .setFooter({ + text: new Array(10).fill('◼', 0, 10).join('') + " 100%", + iconURL: config.app_personalization.favicon || config.app_personalization.logo_url, + }) + .setTimestamp(new Date()) + ] + discordBot.channels.cache.get(stConf.discord_seeding_reward_channel)?.send({ embeds: embeds }) } } } - }) - } - + } + }) } - }) - } + + } + }) + } } @@ -2891,7 +2913,7 @@ async function init() { const st = await dbo.collection('configs').findOne({ category: 'seeding_tracker' }) const stConf = st.config; const requiredPoints = stConf.reward_needed_time.value * (stConf.reward_needed_time.option / 1000 / 60) - console.log('CREATING objIdRewardGroup from value:', st.config.reward_group_id) + // console.log('CREATING objIdRewardGroup from value:', st.config.reward_group_id) let objIdRewardGroup;// = ObjectID(st.config.reward_group_id) try { objIdRewardGroup = ObjectID(st.config.reward_group_id)