Untitled

 avatar
unknown
plain_text
2 years ago
44 kB
3
Indexable
//Import modules
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const webPush = require('web-push');
const socket = require('socket.io');
const admin = require('firebase-admin');
const helmet = require('helmet');
const Authenticate = require('./routes/authentication/auth.js');
const User = require('./models/user.js');
const router = require('./routes/index.js');
const Token = require('./models/jwt.js');
const Challenge = require('./models/challenge.js');
const changeBallance = require('./routes/admin/changeballance.js');
const History = require('./models/history.js');
const Setting = require('./models/setting.js');
const Log = require('./models/log');
const BlockedIps = require('./models/blockedIp');
const GameResult = require('./models/gameResult.js');
const validateUserBalance = require('./helper/validateUserBalance');
const { RateLimiterMemory } = require('rate-limiter-flexible');
const getRoomCode = require('./helper/getRoomCode.js');
const { leftGame, cancelGame } = require('./routes/admin/settleChallenge.js');

require('dotenv').config();

const {
	logBadBallance,
	logToChannel,
	logFrequentLost,
	logCORSDefaulter,
	logRateLimit,
	logToChannelIdisUndefined,
} = require('./helper/tgBot');
const {
	PUBLIC_VAPID_KEY,
	PRIVATE_VAPID_KEY,
	DB_URI_LOCAL,
	DB_URI_ATLAS,
	ROOM_SRV_URL,
	IS_DEV,
} = require('./config.js');
const createHistory = require('./helper/createHistory.js');
const { verifyFromPGWebHook } = require('./routes/user/buyChipsPG.js');
const logThisToDb = require('./helper/logThisToDb.js');
const { getSiteStatus } = require('./routes/siteStatesCache.js');

const DEVLOPMENT = IS_DEV;
const ENABLE_REQUEST_LOG = true;

//setting vapid details
//test comment
//test 2
webPush.setVapidDetails(
	'mailto:test@test.com',
	PUBLIC_VAPID_KEY,
	PRIVATE_VAPID_KEY
);

//constants
const PORT = 5000;

//Initializing express app
const app = express();

app.use(helmet());
app.use(
	express.urlencoded({
		limit: '50mb',
		extended: true,
		parameterLimit: 50000,
	})
);
// parse application/json
app.use(express.json({ limit: '50mb' }));
app.options('*', cors());

app.post(
	'/pg-webhook',
	cors({
		origin: '*',
	}),
	verifyFromPGWebHook()
);

app.post(
	'/whfbweghfebrhebfehwgrwhfbehjfehfvrh',
	cors({
		origin: '*',
	}),
	leftGame()
);

app.get(
	'/logout-user/:id',
	cors({
		origin: '*',
	}),
	async (req, res) => {
		io.to(req.params.id).emit('logout');
		return res.send('OK');
	}
);

var whitelist = [
	'https://ludoplayers.com',
	'https://admin.ludoplayers.com',
	'https://test.ludoplayers.com',
	'https://www.ludoplayers.com',
	'https://uberki.com',
	'https://admin.uberki.com',
	'https://test.uberki.com',
	'https://www.uberki.com',
];

var corsOptions = DEVLOPMENT
	? {
			origin: '*',
	  }
	: {
			origin: function (origin, callback) {
				if (whitelist.indexOf(origin) !== -1) {
					callback(null, true);
				} else {
					callback(`Not allowed by CORS: ${origin}`);
				}
			},
	  };

let BLOCKED_IPS = [];
let connectedUsers = [];
app.get('/get-connected-users', (req, res) => {
	res.json({
		'No of connectedUsers': connectedUsers.length,
		'No of connections': io.engine.clientsCount,
		connectedUsers,
		connections: io.sockets.sockets.keys(),
	});
});

async function getBlockedIps() {
	const res = await BlockedIps.findOne({});
	BLOCKED_IPS = res.ip;
	return BLOCKED_IPS;
}
getBlockedIps();

app.get('/refresh-blocked-ip', async (req, res) => {
	BLOCKED_IPS = await getBlockedIps();
	res.json(BLOCKED_IPS);
});

if (ENABLE_REQUEST_LOG) {
	app.use(
		require('morgan')(function (tokens, req, res) {
			const ip =
				req.headers['x-forwarded-for'] || req.connection.remoteAddress;
			const id = req.headers['id'];
			const userPhone = req.headers['user-phone'];
			return [
				tokens.method(req, res),
				tokens.url(req, res),
				tokens.status(req, res),
				'-',
				tokens['response-time'](req, res),
				'ms',
				'-',
				userPhone,
				'-',
				ip,
			].join(' ');
		})
	);
}

app.use((req, res, next) => {
	if (DEVLOPMENT) {
		return next();
	}
	const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
	const url = req?.baseUrl + req?.path;
	if (BLOCKED_IPS.includes(ip)) {
		console.log('[BLOCKED_IPS]', ip + ' ' + url);
		res.status(403).send('Forbidden');
		return;
	}
	var origin = req.get('origin');
	if (whitelist.indexOf(origin) == -1) {
		const shouldAllow =
			url.includes('redirect') ||
			url.includes('logo') ||
			url.includes('favicon') ||
			url.includes('robots');

		if (!shouldAllow) {
			logCORSDefaulter(req.headers['id'], {
				url,
				origin,
				ip,
			});
			return res.status(403).send('Forbidden');
		}
	}
	next();
});

app.get('/redirect/:ref', cors(), (req, res) => {
	res.redirect(`https://ludoplayers.com/#/register/${req.params.ref}`);
});

app.get('/redirect/', cors(), (req, res) => {
	res.redirect(`https://ludoplayers.com/#/register/`);
});

app.use(cors(corsOptions));

const dev = true;
if (dev) {
	mongoose
		.connect(DB_URI_LOCAL, {
			useNewUrlParser: true,
			useUnifiedTopology: true,
			useFindAndModify: false,
		})
		.catch((err) => {
			console.error('107@index.js', err.message);
		});
} else {
	mongoose
		.connect(DB_URI_ATLAS, {
			useNewUrlParser: true,
			useUnifiedTopology: true,
			user: 'ludoplayers',
			pass: '3aiMsQ61WtlmFtCR',
			authMechanism: 'SCRAM-SHA-1',
		})
		.catch((err) => {
			console.error('123@index.js', err.message);
		});
}

let db = mongoose.connection;

db.on('error', (err) => console.error('Line 96:', err));

//Using coustom middlewares
app.use(Authenticate(User));

//Connected to database
db.once('open', async (err) => {
	if (err) {
		console.error(err);
	}
});

//not responding to all the users that are blocked
app.use((req, res, next) => {
	if (req.user) {
		if (req.user.isBlocked) {
			console.log(`User is blocked ${req.user._id} ${req.user.username}`);
			res.json({ err: 'something went wrong' });
			return;
		}
	}
	next();
});

//Setting express router
app.use('/api', router);

const server = app.listen(PORT, (err) => {
	if (!err) {
		console.info(`server started at port ${PORT}`);
		return;
	}
	console.error('error starting server on port:' + PORT);
});

//passing server to socket.io
var io = socket(server, {
	cors: {
		origin: DEVLOPMENT
			? '*'
			: ['https://ludoplayers.com', 'https://test.ludoplayers.com'],
		methods: ['GET', 'POST'],
	},
});

const rateLimiter = new RateLimiterMemory({
	points: 120,
	duration: 60,
});

io.use(async (socket, next) => {
	if (DEVLOPMENT) {
		return next();
	}
	const { id, key, now, userPhone } = socket.handshake.query;
	const ip =
		socket?.request?.headers['x-forwarded-for'] ||
		socket?.request?.connection.remoteAddress;
	if (BLOCKED_IPS.includes(ip)) {
		return socket.disconnect(true);
	}
	if (ENABLE_REQUEST_LOG) {
		console.log('socket init by phone:', userPhone);
	}
	if (!now) {
		console.log('connecting socket outside website', ip);
		logToChannel(
			`connecting to socket outside website! blocking this IP ${ip}`,
			true
		);
		const newBlockedIps = await BlockedIps.findOneAndUpdate(
			{},
			{
				$push: {
					ip: ip,
				},
			},
			{
				new: true,
			}
		);
		BLOCKED_IPS = newBlockedIps.ip;
		return socket.disconnect(true);
	}
	let userToken = false;
	if (!id) {
		console.log('no id', ip);
		logToChannel(`no id ${ip}`, true);
		return socket.disconnect(true);
	} else if (id === 'undefined' || id === 'null') {
		console.log('id is undefined', ip);
		logToChannelIdisUndefined(ip);
		return socket.disconnect(true);
	} else {
		try {
			userToken = await Token.findById(id).populate('userId');
		} catch (err) {
			console.error('io connection error:', err);
		}
	}
	if (!userToken) {
		console.error('token not found in db', ip);
		// logToChannel(`token not found in db ${ip}`, true);
		socket.emit('logout');
		return socket.disconnect(true);
	}
	try {
		await rateLimiter.consume(ip);
	} catch (err) {
		console.error(
			`Rate limit exceeded for ip: ${ip} ${userToken?.userId?.username}`
		);
		// if (userToken?.userId?.isBlocked) {
		// 	const newBlockedIps = await BlockedIps.findOneAndUpdate(
		// 		{},
		// 		{
		// 			$push: {
		// 				ip: ip,
		// 			},
		// 		},
		// 		{
		// 			new: true,
		// 		}
		// 	);
		// 	BLOCKED_IPS = newBlockedIps.ip;
		// }
	}

	const alreadyConnected = connectedUsers.find(
		(user) => user?.id == userToken?.userId?._id.toString()
	);
	if (alreadyConnected) {
		try {
			if (io.sockets.sockets.has(alreadyConnected.id)) {
				console.log('disconnecting socket', alreadyConnected.username);
				io.sockets.sockets[alreadyConnected.socketId].disconnect();
			}
			connectedUsers = connectedUsers.filter(
				(user) => user.id !== userToken?.userId?._id.toString()
			);
		} catch (err) {
			console.error('error disconnecting socket:', err);
		}
	}
	connectedUsers.push({
		id: userToken?.userId?._id.toString(),
		username: userToken?.userId?.username,
		socketId: socket.id,
	});
	next();
});

const scammers = ['62dd01ee8be85abd773ba8b0'];

io.on('connection', async (socket) => {
	let user;
	//initialization event
	let inited = false;
	socket.on('terminate', async () => {
		socket.disconnect(true);
	});
	socket.on('init', async (data) => {
		inited = true;
		setTimeout(() => {
			inited = false;
		}, 50 * 1000);
		//authentication
		const token = await Token.findById(data.id);
		if (!token) {
			return;
		}
		user = await User.findById(token.userId);

		//delete challenges
		await DeleteChallenges();

		//join this user to a room that is named after his id so we can uniqely send messages to him
		socket.join(token.userId.toString());

		//sending the initialization your challenges
		sendAllNonPlayingChallenges(socket);

		//all running challenge
		sendPlayableChallanges(socket);

		//running challenges
		sendRunningChallenges(socket);
	});

	//create challenge event
	socket.on('create', async ({ amount, test }) => {
		if (!user) return;

		if (
			Number(amount) % 50 != 0 ||
			Number(amount) < 49 ||
			Number(amount) > 10001
		) {
			return;
		}

		const isPlayEnabled = await getSiteStatus();
		if (
			!test &&
			(isPlayEnabled?.playPage == false ||
				isPlayEnabled?.playPage == 'false')
		) {
			socket.emit('error', {
				message:
					'Playing Challenge is Currently Disabled as Website is Under Maintenance. Please try again later',
			});
			return;
		}

		//check if user has enough ballance
		let fuckingUser = await User.findById(user._id);

		if (scammers.includes(fuckingUser.id)) {
			logToChannel(
				`[Scammer] ${fuckingUser.username} tried creating a game!`
			);
			return;
		}

		if (fuckingUser.isBlocked) {
			socket.emit('error', { message: 'You are banned' });
			return;
		}
		if (fuckingUser.wallet - amount < 0) {
			socket.emit('error', {
				message: "You don't have Enough Chips ",
			});
			return;
		}

		const userOtherchallneges = await Challenge.find({
			creator: user._id,
			state: { $in: ['open', 'requested'] },
		}).countDocuments();

		if (userOtherchallneges === 3) {
			socket.emit('error', {
				message: 'You can Set Maximum 3 Challenge at a time',
			});
			return;
		}

		//check if same amount challenge exists
		let challenge = await Challenge.findOne({
			creator: user._id,
			amount: amount,
			state: { $in: ['open', 'requested'] },
		});
		if (challenge) {
			//emmit an error event
			socket.emit('error', {
				message: 'Same Amount Challenge Already exists',
			});
			return;
		}

		//check if user hasn't updated results in a challenge
		challenge = await Challenge.findOne({
			$or: [
				{ creator: fuckingUser._id, 'results.creator': null },
				{ player: fuckingUser._id, 'results.player': null },
			],
			state: { $in: ['playing', 'hold'] },
		});
		if (challenge) {
			socket.emit('error', {
				message: 'Update Your Result In Previous Match First',
			});
			return;
		}

		//create a challenge
		challenge = new Challenge();
		challenge.creator = user._id;
		challenge.creatorUsername = user.username;
		challenge.amount = amount;
		await challenge.save();

		//send non playing challenges in other words challenges that are in state open or requested
		//I dont fucking know why I named this function sendAllNonPlayingChallenges :|
		sendAllNonPlayingChallenges(socket);

		//send challenges of state open or requested but with a different category :|
		sendPlayableChallanges(io);

		//send notification to everyone
		try {
			if (!test) {
				await admin.messaging().send({
					topic: 'ludoplayers_user',
					webpush: {
						notification: {
							title: `Ludo Players`,
							body: `New Challenge Of ₹${amount}`,
							icon: 'https://ludoplayers.com/favicon.ico',
							//image: data.image,
						},
						fcmOptions: {
							link: 'http://www.ludoplayers.com',
						},
					},
				});
			}
		} catch (err) {
			console.error('326@index.js' + err);
		}
	});

	//delete event
	socket.on('delete', async (id) => {
		try {
			if (!user) {
				return;
			}
			if (scammers.includes(user.id)) {
				logToChannel(
					`[Scammer] ${user.username} tried deleting a game!`
				);
				return;
			}

			let challenge = await Challenge.findById(id);
			//check if the user is actually the creator
			if (String(challenge?.creator) !== String(user._id)) {
				socket.emit('error', {
					message: 'Error',
				});
				return;
			}

			const { state } = challenge;
			console.log(`deleteing challenge ${id} with state ${state}`);
			if (['playing', 'hold', 'cancelled', 'resolved'].includes(state)) {
				logToChannel(
					`user ${user.username} tried deleting a game in state ${state}`
				);
				return;
			}
			//delete the challenge
			//comment added
			await Challenge.deleteOne({ _id: id });
			sendAllNonPlayingChallenges(socket);
			sendPlayableChallanges(io);
		} catch (err) {
			console.error('socket delete event' + err.message);
		}
	});

	socket.on('request', async (id) => {
		try {
			if (!user) {
				return;
			}
			if (scammers.includes(user.id)) {
				logToChannel(
					`[Scammer] ${user.username} tried requesting a game!`
				);
				return;
			}

			let challenge = await Challenge.findOne({
				$or: [
					{ creator: user._id, 'results.creator': null },
					{ player: user._id, 'results.player': null },
				],
				state: { $in: ['playing', 'hold'] },
			});
			if (challenge) {
				socket.emit('error', {
					message: 'Update Your Result in Previous Match First',
				});
				return;
			}

			//check if theres any other challenge on requested state of that player
			challenge = await Challenge.findOne({
				$or: [{ creator: user._id }, { player: user._id }],
				state: 'requested',
			});
			if (challenge && challenge._id.toString() === id) {
				//do nothing
				return;
			}

			if (challenge) {
				//now the max request error needs to be given in a different way
				socket.emit('max-request', id);
				return;
			}

			//validate the id
			challenge = await Challenge.findById(id);
			if (!challenge) {
				console.error('Invalid Id has been passed:', id);
				return;
			}

			//check if its already challenged
			if (challenge.player) {
				//socket.emit("error", { message: "Someone already challanged" });
				return;
			}

			//check if user has enough ballance
			let latestUser = await User.findById(user._id);
			if (latestUser.wallet - challenge.amount < 0) {
				socket.emit('low-balance', id);
				return;
			}

			// check if user is the creator of the challenge
			if (String(challenge.creator) === String(user._id)) {
				socket.emit('error', {
					message: 'Error: You cannot challenge yourself',
				});
				return;
			}

			//update the challenge
			challenge.player = user._id;
			challenge.playerUsername = user.username;
			challenge.state = 'requested';
			await challenge.save();

			//refresh the challengers your-challenges
			sendAllNonPlayingChallenges(io);

			//refresg everyone's playable-challenges
			sendPlayableChallanges(io);

			//play audio of the creator
			io.to(challenge.creator.toString()).emit('play-audio');
		} catch (err) {
			console.error('error in request event:', err.message);
		}
	});

	socket.on('cancel', async (id) => {
		try {
			if (!user) {
				console.error('cancel event: user is not defined');
				return;
			}
			if (scammers.includes(user.id)) {
				logToChannel(
					`[Scammer] ${user.username} tried cancelling a game!`
				);
				return;
			}

			//check if user is either creator or player
			let challenge = await Challenge.findOneAndUpdate(
				{
					_id: id,
					state: 'requested',
					$or: [{ creator: user._id }, { player: user._id }],
				},
				{ player: undefined, playerUsername: undefined, state: 'open' }
			);

			if (!challenge) {
				console.error('cancel event: challenge not found');
				return;
			}

			//refresh everyones cancelers playable-challenges
			sendPlayableChallanges(io);

			//refresh the creators your-challenges
			sendAllNonPlayingChallenges(io);

			//emmit the rejected and or canceled event
			if (challenge.creator.toString() === user._id.toString()) {
				io.to(challenge.player.toString()).emit(
					'rejected',
					challenge._id
				);
			} else {
				//io.to(challenge.creator.toString()).emit("cancelled", challenge._id);
				io.to(challenge.player.toString()).emit(
					'cancelled',
					challenge._id
				);
			}
		} catch (err) {
			console.error('error in cancel event:', err);
		}
	});

	socket.on('play', async (id) => {
		//check if the challenge is valid and playable
		if (!user) {
			return;
		}
		if (scammers.includes(user.id)) {
			logToChannel(`[Scammer] ${user.username} tried playing a game!`);
			return;
		}
		let challenge = await Challenge.findOne({
			$or: [
				{ creator: user._id, 'results.creator': null },
				{ player: user._id, 'results.player': null },
			],
			state: { $in: ['playing', 'hold'] },
		});
		if (challenge) {
			socket.emit('error', {
				message: 'Update Your Result in Previous Match First',
			});
			return;
		}
		challenge = await Challenge.findOneAndUpdate(
			{ _id: id, creator: user._id, state: 'requested' },
			{ state: 'playing' }
		);
		if (!challenge || !user) {
			return;
		}

		//check for both users ballance
		let creator = await User.findById(challenge.creator);
		let player = await User.findById(challenge.player);
		if (
			creator.wallet - challenge.amount < 0 ||
			player.wallet - challenge.amount < 0
		) {
			await Challenge.updateOne({ _id: id }, { state: 'requested' });
			return;
		}

		//check if opoponent is already in a game
		let opponentsChallenge = await Challenge.findOne({
			_id: { $ne: id },
			$or: [
				{ creator: challenge.player, 'results.creator': null },
				{ player: challenge.player, 'results.player': null },
			],
			state: { $in: ['playing', 'hold'] },
		});
		if (opponentsChallenge) {
			await Challenge.updateOne({ _id: id }, { state: 'requested' });
			return;
		}

		//update the challenge
		await Challenge.updateOne({ _id: id }, { state: 'playing' });

		await logThisToDb({
			scope: '[Wallet balance change]',
			idx1: challenge.amount,
			challengeId: challenge._id,
			user: user._id,
			msg: `on play event - dec`,
			info: `${challenge.creator} - ${challenge.player}`,
		});
		//decrease creators ballance
		await User.updateMany(
			{
				$or: [{ _id: challenge.creator }, { _id: challenge.player }],
			},
			{ $inc: { wallet: -challenge.amount } }
		);
		// REMOVED FOR NOW
		// const isCreaterValid = await validateUserBalance(challenge.creator);
		// const isPlayerValid = await validateUserBalance(challenge.player);
		// if (!isCreaterValid[0] || !isPlayerValid[0]) {
		// 	if (!isCreaterValid[0]) {
		// 		logBadBallance(
		// 			challenge.creator,
		// 			'requesting challenge',
		// 			isCreaterValid[1]
		// 		);
		// 	}
		// 	if (!isPlayerValid[0]) {
		// 		console.log(`${challenge.player} is not valid`);
		// 		logBadBallance(
		// 			challenge.player,
		// 			'requesting challenge',
		// 			isPlayerValid[1]
		// 		);
		// 	}
		// }

		io.to(challenge.player.toString()).emit('play-audio');
		io.to(challenge.player.toString()).emit('start-button', id);

		//send a update event to everyone for category running-challenges
		sendRunningChallenges(io);
		sendAllNonPlayingChallenges(socket);
		sendPlayableChallanges(io.to(challenge.player));
		io.to(challenge.creator.toString()).emit('redirect', {
			user: 'everyone',
			to: `/game/${id}`,
		});

		//get the room code
		try {
			let roomCode = await getRoomCode();
			if (!roomCode) {
				logToChannel('room code did not came', true);
				roomCode = await getRoomCode();
			}
			if (!roomCode) {
				logToChannel('room code did not came in second try');
			}
			if (roomCode.length != 8) {
				logToChannel(
					'room code is not 8 chars long, cancelling this game',
					true
				);
				await cancelGame(id, 'no room code');
				return;
			}
			try {
				const gameResult = new GameResult({
					_id: roomCode,
					gameStartedAt: new Date(),
					state: 'created',
				});
				await gameResult.save();
			} catch (error) {
				// if duplicate key error
				if (error.code === 11000) {
					console.log('duplicate key error');
					const copyGame = await GameResult.findById(roomCode).lean();
					await GameResult.findByIdAndUpdate(roomCode, {
						gameStartedAt: new Date(),
						state: 'created',
						duplicate: true,
						$push: {
							prevGame: copyGame,
						},
						playerOne: {
							name: '',
							result: 0,
							tokensOut: -1,
							endResult: '',
						},
						playerTwo: {
							name: '',
							result: 0,
							tokensOut: -1,
							endResult: '',
						},
						gameUpdatedAt: new Date(),
						gameFinishedAt: new Date(),
					});
				} else {
					console.error(`[GameResult-created] error`);
					console.error(error);
					logToChannel(
						`[GameResult-created] error: ${error.message}`,
						true
					);
				}
			}

			// logToChannel(
			// 	`room code is ${roomCode} for ${creator.username} vs ${player.username}`
			// );
			await Challenge.updateOne(
				{
					_id: id,
					$or: [
						{ creator: user._id, 'results.creator': null },
						{ player: user._id, 'results.player': null },
					],
				},
				{ code: roomCode }
			);
			io.to(id).emit('code', roomCode);
			io.to(String(creator._id)).emit(
				'update_wallet',
				creator.wallet - challenge.amount
			);
			return;
		} catch (err) {
			console.error('570@index.js' + err);
		}
	});

	socket.on('view', async (id) => {
		let challenge = await Challenge.findById(id);
		const isCreator = challenge.creator.toString() === user?._id.toString();
		const isPlayer = challenge.player.toString() === user?._id.toString();
		if (
			(isCreator && challenge.results.creator) ||
			(isPlayer && challenge.results.player)
		) {
			socket.emit('redirect', {
				user: user._id,
				to: `/view/${id}`,
				viewInfo: {
					code: challenge.code,
					result: isCreator
						? challenge.results.creator
						: challenge.results.player,
					resultScreenshoot: isCreator
						? challenge.winnerScreenshoot.creator
						: challenge.winnerScreenshoot.player,
					challengeText: `${challenge.creatorUsername} vs ${challenge.playerUsername} for ₹${challenge.amount}`,
				},
			});
			return;
		}
		if (user) {
			socket.emit('redirect', { user: user._id, to: `/game/${id}` });
		}
	});

	socket.on('refresh', (category) => {
		if (category === 'your-challenges') {
			sendAllNonPlayingChallenges(socket);
		}
		if (category === 'playable-challenges') {
			sendPlayableChallanges(io);
		}
	});

	//send specefic group of challenges
	async function sendAllNonPlayingChallenges(localSocket) {
		try {
			//requested challenges
			const challenges = await Challenge.find({
				state: {
					$in: ['requested', 'open'],
				},
			})
				.select(
					'-winnerScreenshoot -code -s3WinnerScreenshoot -deleted -isAdminClear -isSus'
				)
				.sort({ createdAt: -1 });

			//emit the things and stuffs
			localSocket.emit('update', {
				category: 'your-challenges',
				challenges,
			});
		} catch (err) {
			console.error('sending all non playing challenges' + err);
		}
	}

	async function sendPlayableChallanges(localSocket) {
		try {
			//all playable challenges
			let challenges = await Challenge.find({
				state: { $in: ['open', 'requested'] },
			})
				.select(
					'-winnerScreenshoot -code -deleted -isAdminClear -isSus -s3WinnerScreenshoot'
				)
				.sort({ createdAt: -1 });
			localSocket.emit('update', {
				category: 'playable-challenges',
				challenges: challenges,
			});
		} catch (err) {
			console.error('sending playable challenges: ' + err);
		}
	}

	async function sendRunningChallenges(localSocket) {
		try {
			let challenges = await Challenge.find({
				state: { $in: ['playing', 'hold'] },
			}).select(
				'-winnerScreenshoot -code -s3WinnerScreenshoot -deleted -isAdminClear -isSus'
			);
			localSocket.emit('update', {
				category: 'running-challenges',
				challenges: challenges,
			});
		} catch (err) {
			console.error('sending running challenges: ' + err);
		}
	}

	//all the room related shits
	socket.on('joinRoom', async (id) => {
		if (!user) {
			socket.emit('try-again');
			return;
		}

		let challenge = await Challenge.findById(id);

		//make sure this user is either the creator or the player
		if (
			challenge?.creator?.toString() !== String(user._id) &&
			challenge?.player?.toString() !== user._id.toString()
		) {
			return;
		}

		//emmit redirect_error if the user has updated his result
		let userState = 'creator';
		if (challenge.player.toString() === user._id.toString()) {
			userState = 'player';
		}

		if (challenge.state === 'resolved') {
			socket.emit('redirect', {
				to: '/',
			});
		}

		if (challenge.results[userState] && challenge.state === 'hold') {
			socket.emit('redirect_error', {
				to: '/',
				message:
					'आपका रिज़ल्ट अपलोड हो चुका है ॥ गेम clear होने का 2-5 मिनट इंतज़ार करे ।\n\nYour Result is submitted. Please wait until the Game is Cleared within 2-5 Minutes.',
			});
			return;
		}

		//join the socket to a room
		socket.join(id);

		//emit some info
		socket.emit('creator', challenge.creator);
		socket.emit(
			'challenge_text',
			`${challenge.creatorUsername} vs ${challenge.playerUsername} for ₹${challenge.amount}`
		);

		//check if theres code if there is then emit code otherwise the codeChangable
		if (challenge.code) {
			socket.emit('code', challenge.code);
		}
		try {
			// const logThisToDb = new Log({
			// 	scope: 'joinRoom',
			// 	msg: `${challenge.creatorUsername} vs ${challenge.playerUsername} for ₹${challenge.amount} room code ${challenge.code}`,
			// 	cleared: true,
			// });
			// await logThisToDb.save();
		} catch (err) {
			console.error('joinRoom: ' + err);
		}
	});

	async function resolveChallange({
		id,
		challenge,
		userRole,
		oponentRole,
		result,
	}) {
		const checkWon = await Challenge.findById(id);
		if (
			checkWon.winnerScreenshoot.player &&
			checkWon.winnerScreenshoot.creator
		) {
			return;
		}

		if (checkWon.adminHold) {
			console.log('admin hold');
			return;
		}

		//get the latest challenge
		const challenegExists = await Challenge.findOneAndUpdate(
			{
				_id: id,
				state: 'hold',
				$or: [
					{ 'results.creator': undefined },
					{ 'results.player': undefined },
				],
			},
			{ state: 'resolved' }
		);
		//run change ballace funtion for the player and his opoponent as this player is winner

		//change this player's ballance (winner)
		if (challenegExists) {
			const userResult = challenge.results[userRole]
				? ` (Your Result: ${challenge.results[userRole]})`
				: null;
			const oponentResult = challenge.results[oponentRole]
				? ` (Your Result: ${challenge.results[oponentRole]})`
				: null;
			await changeBallance(
				challenge[userRole],
				{
					amount: challenge.amount,
					reason: 'win',
					naration: `Won against ${
						challenge[oponentRole + 'Username']
					}${userResult ?? ''}`,
					uniqueId: `${challenge.id}-${
						challenge.code || 'no-code'
					}-win`,
				},
				`${result}@index.js`,
				id
			);
			//change opponent's ballance (looser)
			await changeBallance(
				challenge[oponentRole],
				{
					amount: challenge.amount,
					reason: 'lose',
					winner: await User.findById(challenge[userRole]),
					naration: `lost against ${
						challenge[userRole + 'Username']
					}${oponentResult ?? ''}`,
					uniqueId: `${challenge.id}-${
						challenge.code || 'no-code'
					}-lose`,
				},
				`${result}@index.js`,
				id
			);
		}
		let tempUser = await User.findById(challenge[userRole]);

		//update users ballance
		io.to(String(challenge[userRole])).emit(
			'update_wallet',
			tempUser.wallet
		);

		//referesh running challanges of all players
		sendRunningChallenges(io);
		return;
	}

	async function cancelChallange({
		id,
		challenge,
		userRole,
		oponentRole,
		result,
	}) {
		let challenge_on_resolve_time = await Challenge.findById(id);
		if (
			challenge_on_resolve_time.results[oponentRole] ||
			challenge_on_resolve_time.state === 'resolved'
		) {
			return;
		}

		//resolve the challange
		await Challenge.findByIdAndUpdate(id, { state: 'resolved' });

		//giving the amount of the challenge back to creator and player because it was dedducted when match started
		// await User.updateMany(
		// 	{
		// 		$or: [{ _id: challenge.creator }, { _id: challenge.player }],
		// 	},
		// 	{ $inc: { wallet: challenge.amount } }
		// );
		const creatorUniqueId = `${challenge.id}-${
			challenge.code || 'no-code'
		}-cancel-creator`;
		//cancled history for player and creator
		const creatorHistory = await createHistory(
			{
				user: challenge.creator,
				amount: challenge.amount,
				historyType: 'cancel',
				naration: `Cancelled Against ${challenge.playerUsername} (Your Result: ${challenge.results.creator})`,
				uniqueId: creatorUniqueId,
			},
			'index.js@cancelChallangeCreator',
			id
		);
		if (creatorHistory) {
			await logThisToDb({
				scope: '[Wallet balance change]',
				idx1: challenge.amount,
				challengeId: challenge._id,
				user: challenge.creator,
				msg: `on cancel - inc`,
			});
			await User.findByIdAndUpdate(challenge.creator, {
				$inc: { wallet: challenge.amount },
			});
		} else {
			logToChannel(
				`skipping changing wallet balance for player ${challenge.creator} of ${challenge.amount} because of double hisory creation. uniqueId: ${creatorUniqueId}`,
				true
			);
		}

		const playerUniqueId = `${challenge.id}-${
			challenge.code || 'no-code'
		}-cancel-player`;
		const playerHistory = await createHistory(
			{
				user: challenge.player,
				amount: challenge.amount,
				historyType: 'cancel',
				naration: `Canceled against ${challenge.creatorUsername} (Your Result: ${challenge.results.player})`,
				uniqueId: playerUniqueId,
			},
			'index.js@cancelChallangePlayer',
			id
		);
		if (playerHistory) {
			await logThisToDb({
				scope: '[Wallet balance change]',
				idx1: challenge.amount,
				challengeId: challenge._id,
				user: challenge.player,
				msg: `on cancel - inc`,
			});
			await User.findByIdAndUpdate(challenge.player, {
				$inc: { wallet: challenge.amount },
			});
		} else {
			logToChannel(
				`skipping changing wallet balance for player ${challenge.creator} of ${challenge.amount} because of double hisory creation. uniqueId: ${playerUniqueId}`,
				true
			);
		}

		//referesh running challanges of all players
		sendRunningChallenges(io);
	}

	socket.on('update_result', async ({ id, result, cancellationReason }) => {
		try {
			await logThisToDb(
				{
					scope: '[update_result] event',
					challengeId: id,
					msg: result,
					user: user ? user._id : 'null',
				},
				'index.js@update_result'
			);
			if (!user) {
				await logThisToDb(
					{
						scope: '[update_result] user not found',
						challengeResult: result,
						challengeId: id,
					},
					'index.js@update_result'
				);

				return;
			}

			// const isPlayerValid = await validateUserBalance(user._id);
			// if (!isPlayerValid[0]) {
			// 	logBadBallance(user._id, 'update result', isPlayerValid[1]);
			// }
			let ogChallenge = await Challenge.findById(id);
			if (!ogChallenge) {
				await logThisToDb(
					{
						scope: '[update_result] ogChallenge not found',
						user: user._id,
						challengeId: id,
						challengeResult: result,
					},
					'index.js@update_result'
				);

				return;
			}
			await logThisToDb(
				{
					scope: '[update_result] ogChallenge found',
					user: user._id,
					challengeId: id,
					challengeResult: result,
					info: `INIT STATE: ${JSON.stringify(
						ogChallenge.results
					)} ${JSON.stringify(ogChallenge.state)}}`,
				},
				'index.js@update_result'
			);

			//make sure this user is either creator orplayer
			if (
				ogChallenge.player.toString() !== String(user._id) &&
				ogChallenge.creator.toString() !== user._id.toString()
			) {
				await logThisToDb(
					{
						scope: '[update_result] user is not creator or player',
						user: user._id,
						challengeId: id,
						challengeResult: result,
						challengeCode: ogChallenge.code,
					},
					'index.js@update_result'
				);
				if (ogChallenge.state === 'resolved') {
					await logThisToDb(
						{
							scope: '[update_result] user is not creator or player and challenge is already resolved',
							user: user._id,
							challengeId: id,
							challengeResult: result,
							challengeCode: ogChallenge.code,
						},
						'index.js@update_result'
					);
				} else {
					ogChallenge.state = 'hold';
				}
				await ogChallenge.save();
				return;
			}

			//get the user role
			let userRole = 'creator';
			let oponentRole = 'player';
			if (ogChallenge.player.toString() == user._id.toString()) {
				userRole = 'player';
				oponentRole = 'creator';
			}

			//check if user already updated
			if (ogChallenge.results[userRole]) {
				await logThisToDb(
					{
						scope: '[update_result] user already updated result',
						challengeId: id,
						user: user._id,
						challengeResult: result,
						challengeCode: ogChallenge.code,
					},
					'index.js@update_result'
				);
				return;
			}
			let challenge;
			let ogSus = false;
			if (result === 'lost') {
				let updateQuery = {};
				if (
					ogChallenge.createdAt >
					new Date().getTime() - 1000 * 60 * 7
				) {
					console.log('challenge is less than 7 minutes old');
					updateQuery = { state: 'hold', isSus: true };
					ogSus = true;
				} else {
					updateQuery = { state: 'resolved' };
				}
				if (userRole == 'creator') {
					updateQuery.creatorResultTime = new Date();
					updateQuery.isSus = ogSus || ogChallenge.isSus;
				} else {
					updateQuery.playerResultTime = new Date();
					updateQuery.isSus = ogSus || ogChallenge.isSus;
				}
				challenge = await Challenge.findOneAndUpdate(
					{
						_id: id,
						$or: [{ state: 'playing' }, { state: 'hold' }],
					},
					updateQuery
				);
				await logThisToDb(
					{
						scope: '[update_result] lost premautre update',
						challengeId: id,
						user: user._id,
						challengeResult: result,
						challengeCode: ogChallenge.code,
						info: JSON.stringify(updateQuery),
					},
					'index.js@update_result'
				);
			} else {
				challenge = await Challenge.findOne({
					_id: id,
					$or: [{ state: 'playing' }, { state: 'hold' }],
				});
			}
			if (!challenge || challenge.state === 'resolved') {
				const scope = !challenge
					? 'Redirecting because challenge not found'
					: 'Redirecting because challenge is resolved';
				await logThisToDb(
					{
						scope: `[update_result] ${scope}`,
						challengeId: id,
						user: user._id,
						challengeResult: result,
						challengeCode: ogChallenge.code,
					},
					'index.js@update_result'
				);
				socket.emit('redirect', { user: user._id, to: '/' });

				return;
			}

			let isSus = false || ogSus;
			let updationQuery = { results: challenge.results };
			updationQuery.results[userRole] = result;
			if (result === 'won' && challenge.state !== 'resolved') {
				updationQuery.state = 'hold';
			} else if (
				result === 'cancel' &&
				challenge.state !== 'resolved' &&
				challenge.results[oponentRole] !== 'cancel'
			) {
				updationQuery.state = 'hold';
			} else if (result === 'lost') {
				if (
					ogChallenge.createdAt >
						new Date().getTime() - 1000 * 60 * 7 ||
					ogChallenge.results[oponentRole] === 'cancel'
				) {
					console.log('challenge is less than 7 minutes old');
					updationQuery.state = 'hold';
					isSus = true;
				} else {
					updationQuery.state = 'resolved';
				}
			}

			if (
				updationQuery.results.creator === 'cancel' &&
				updationQuery.results.player === 'cancel'
			) {
				updationQuery.state = 'resolved';
			}

			if (result === 'cancel') {
				updationQuery.cancellationReasons =
					challenge.cancellationReasons || {};
				updationQuery.cancellationReasons[userRole] =
					cancellationReason;
			}
			if (userRole == 'creator') {
				updationQuery.creatorResultTime = new Date();
				updationQuery.isSus = isSus || challenge.isSus;
			} else {
				updationQuery.playerResultTime = new Date();
				updationQuery.isSus = isSus || challenge.isSus;
			}

			await Challenge.updateOne({ _id: id }, updationQuery);
			await logThisToDb(
				{
					scope: '[update_result] updationQuery',
					challengeId: id,
					user: user._id,
					challengeResult: result,
					challengeCode: challenge.code,
					msg: `BEFORE UPDATE: ${JSON.stringify(
						challenge.results
					)} ${JSON.stringify(challenge.state)}}`,
					info: JSON.stringify(updationQuery),
				},
				'index.js@update_result'
			);

			//determine who fuck what
			if (result === 'lost') {
				//change ballace for this palyer
				// const getLastLost = await History.find({
				// 	user: user._id,
				// 	historyType: 'lose',
				// })
				// 	.sort({ createdAt: -1 })
				// 	.limit(1);
				// const lastLost = getLastLost[0];
				// if (lastLost) {
				// 	const lastLostDate = new Date(lastLost?.createdAt);
				// 	const currentDate = new Date();
				// 	const diff = currentDate.getTime() - lastLostDate.getTime();
				// 	if (lastLost && diff < 4 * 60 * 1000) {
				// 		console.log(user._id, ' last lost less than 4 mins');
				// 		logFrequentLost(
				// 			user._id,
				// 			`last lost was ${(diff / 60000).toFixed(
				// 				2
				// 			)} minutes ago`
				// 		);
				// 	}
				// }
				if (!isSus) {
					const userResult = challenge.results[userRole]
						? ` (Your Result: ${challenge.results[userRole]})`
						: null;
					const oponentResult = challenge.results[oponentRole]
						? ` (Your Result: ${challenge.results[oponentRole]})`
						: null;
					await changeBallance(
						user._id,
						{
							amount: challenge.amount,
							reason: 'lose',
							naration: `Lost against ${
								challenge[oponentRole + 'Username']
							}${userResult ?? ''}`,
							winner: await User.findById(challenge[oponentRole]),
							uniqueId: `${challenge.id}-${
								challenge.code || 'no-code'
							}-lose`,
						},
						`${result}@index.js`,
						id
					);
					//change the ballace of the oponentRole (which automatically adds a history)

					await changeBallance(
						challenge[oponentRole],
						{
							amount: challenge.amount,
							reason: 'win',
							naration: `Won against ${
								challenge[userRole + 'Username']
							}${oponentResult ?? ''}`,
							uniqueId: `${challenge.id}-${
								challenge.code || 'no-code'
							}-win`,
						},
						`${result}@index.js`,
						id
					);
				}
				//redirect oponent to homepage
				io.to(String(challenge[oponentRole])).emit('redirect', {
					id: String(challenge[oponentRole]),
					to: '/',
				});

				let tempUser = await User.findById(challenge[oponentRole]);

				//update users ballance
				io.to(String(challenge[oponentRole])).emit(
					'update_wallet',
					tempUser.wallet
				);
			}

			//give them their ballace back if both cancels
			// TODO
			if (
				updationQuery.results.creator === 'cancel' &&
				updationQuery.results.player === 'cancel'
			) {
				// await User.updateMany(
				// 	{
				// 		$or: [
				// 			{ _id: challenge.creator },
				// 			{ _id: challenge.player },
				// 		],
				// 	},
				// 	{ $inc: { wallet: challenge.amount } }
				// );
				//add two history's
				const creatorUniqueId = `${challenge.id}-${
					challenge.code || 'no-code'
				}-cancel-creator`;
				const creatorHistory = await createHistory(
					{
						user: challenge.creator,
						amount: challenge.amount,
						historyType: 'cancel',
						naration: `Cancelled Against ${challenge.playerUsername} (Your Result: ${challenge.results.creator})`,
						uniqueId: creatorUniqueId,
					},
					'index.js@cancel-creator',
					id
				);
				if (creatorHistory) {
					await logThisToDb({
						scope: '[Wallet balance change]',
						idx1: challenge.amount,
						challengeId: challenge._id,
						user: challenge.creator,
						msg: `on cancel - inc`,
					});
					await User.findByIdAndUpdate(challenge.creator, {
						$inc: { wallet: challenge.amount },
					});
				} else {
					logToChannel(
						`skipping changing wallet balance for player ${challenge.creator} of ${challenge.amount} because of double hisory creation. uniqueId: ${creatorUniqueId}`,
						true
					);
				}

				const playerUniqueId = `${challenge.id}-${
					challenge.code || 'no-code'
				}-cancel-player`;
				const playerHistory = await createHistory(
					{
						user: challenge.player,
						amount: challenge.amount,
						historyType: 'cancel',
						naration: `Cancelled against ${challenge.creatorUsername} (Your Result: ${challenge.results.player})`,
						uniqueId: playerUniqueId,
					},
					'index.js@cancel-player',
					id
				);
				if (playerHistory) {
					await logThisToDb({
						scope: '[Wallet balance change]',
						idx1: challenge.amount,
						challengeId: challenge._id,
						user: challenge.player,
						msg: `on cancel - inc`,
					});
					await User.findByIdAndUpdate(challenge.player, {
						$inc: { wallet: challenge.amount },
					});
				} else {
					logToChannel(
						`skipping changing wallet balance for player ${challenge.creator} of ${challenge.amount} because of double hisory creation. uniqueId: ${playerUniqueId}`,
						true
					);
				}
			}

			socket.emit('redirect', { id: user._id, to: '/' });
			sendRunningChallenges(io);

			//some other shits
			// if (result === 'won') {
			// 	setTimeout(() => {
			// 		resolveChallange({
			// 			id,
			// 			challenge,
			// 			userRole,
			// 			oponentRole,
			// 			result,
			// 		});
			// 	}, 15 * 60 * 1000);
			// }
			//this function is meant to be called when this player updated cancel and the opponent havent updated result for x mins
			// if (result === 'cancel') {
			// 	setTimeout(() => {
			// 		cancelChallange({
			// 			id,
			// 			challenge,
			// 			userRole,
			// 			oponentRole,
			// 			result,
			// 		});
			// 	}, 15 * 60 * 1000);
			// }
		} catch (err) {
			console.error('976@index.js' + err);
		}
	});

	async function DeleteChallenges() {
		if (!user) {
			return;
		}
		//delete all open or requested challenges of this user
		let deletionQuery = {
			creator: user._id,
			state: { $in: ['open', ''] },
		};
		let response = await Challenge.deleteMany(deletionQuery);
		//update all the challenges that this user is a player on
		let secondResponse = await Challenge.updateMany(
			{ state: 'requested', player: user._id },
			{ state: 'open', player: undefined, playerUsername: undefined }
		);
		//emmit refresh events
		if (response.deletedCount > 0 || secondResponse.nModified > 0) {
			sendPlayableChallanges(io);
			sendAllNonPlayingChallenges(io);
		}
	}

	socket.on('page_change', async () => {
		await DeleteChallenges();
	});

	socket.on('disconnect', async () => {
		if (inited) {
			return;
		}
		connectedUsers = connectedUsers.filter(
			(_user) => _user.id !== user?._id.toString()
		);
		await DeleteChallenges();
	});
});
Editor is loading...