diff --git a/api/clues/getClue.js b/api/clues/getClue.js index a64a32e8..dd5df7a9 100644 --- a/api/clues/getClue.js +++ b/api/clues/getClue.js @@ -1,5 +1,5 @@ -import Clue from '@/models/Clue'; -import User from '@/models/User'; +import Clue from '../../models/Clue.js'; +import User from '../../models/User.js'; async function handler(req, res) { if (req.method === 'GET') { diff --git a/api/clues/getCluesCount.js b/api/clues/getCluesCount.js index db7021f9..85ecd39c 100644 --- a/api/clues/getCluesCount.js +++ b/api/clues/getCluesCount.js @@ -1,4 +1,4 @@ -import Clue from '@/models/Clue'; +import Clue from '../../models/Clue.js'; let clueCountCache = { count: 0, diff --git a/api/clues/makeClue.js b/api/clues/makeClue.js index 09fc2a18..5d2ff3a1 100644 --- a/api/clues/makeClue.js +++ b/api/clues/makeClue.js @@ -1,9 +1,8 @@ -import { NextApiRequest, NextApiResponse } from 'next'; import formidable from 'formidable'; // import { OpenAI } from 'openai'; import fs from 'fs'; -import User from '@/models/User'; -import Clue from '@/models/Clue'; +import User from '../../models/User.js'; +import Clue from '../../models/Clue.js'; // const openai = new OpenAI({ // apiKey: process.env.OPENAI_API_KEY, diff --git a/api/clues/rateClue.js b/api/clues/rateClue.js index 560c5346..7f8a0e15 100644 --- a/api/clues/rateClue.js +++ b/api/clues/rateClue.js @@ -1,6 +1,5 @@ -import { NextApiRequest, NextApiResponse } from 'next'; -import Clue from '@/models/Clue'; -import User from '@/models/User'; +import Clue from '../../models/Clue.js'; +import User from '../../models/User.js'; async function handler(req, res) { if (req.method === 'POST') { @@ -11,7 +10,7 @@ async function handler(req, res) { if (typeof secret !== 'string') { return res.status(400).json({ message: 'Invalid input' }); } - + if (!clueId || !rating || !secret) { return res.status(400).json({ message: 'Missing clueId, rating, or secret' }); } diff --git a/api/map/approveRejectMap.js b/api/map/approveRejectMap.js index eacbb3f6..c4c6fb99 100644 --- a/api/map/approveRejectMap.js +++ b/api/map/approveRejectMap.js @@ -1,5 +1,5 @@ -import Map from "@/models/Map"; -import User from "@/models/User"; +import Map from "../../models/Map.js"; +import User from "../../models/User.js"; export default async function handler(req, res) { // only allow POST diff --git a/api/map/delete.js b/api/map/delete.js index 642f5338..c5f5bdea 100644 --- a/api/map/delete.js +++ b/api/map/delete.js @@ -1,5 +1,5 @@ -import Map from "@/models/Map"; -import User from "@/models/User"; +import Map from "../../models/Map.js"; +import User from "../../models/User.js"; export default async function handler(req, res) { // only allow DELETE diff --git a/api/map/heartMap.js b/api/map/heartMap.js index 4d6d7e7a..6a9f8ce6 100644 --- a/api/map/heartMap.js +++ b/api/map/heartMap.js @@ -1,5 +1,5 @@ -import Map from '@/models/Map'; -import User from '@/models/User'; +import Map from '../../models/Map.js'; +import User from '../../models/User.js'; const HEART_COOLDOWN = 500; let recentHearts = {} @@ -16,7 +16,7 @@ async function handler(req, res) { if (typeof secret !== 'string') { return res.status(400).json({ message: 'Invalid input' }); } - + if (!mapId || !secret) { return res.status(400).json({ message: 'Missing values' }); diff --git a/api/map/mapHome.js b/api/map/mapHome.js index 604aca8f..ed5e8054 100644 --- a/api/map/mapHome.js +++ b/api/map/mapHome.js @@ -1,10 +1,7 @@ -import mapConst from "@/components/maps/mapConst"; -import parseMapData from "@/components/utils/parseMapData"; -import sendableMap from "@/components/utils/sendableMap"; -import generateSlug from "@/components/utils/slugGenerator"; -import Map from "@/models/Map"; -import User from "@/models/User"; -import officialCountryMaps from '@/public/officialCountryMaps.json'; +import sendableMap from "../../components/utils/sendableMap.js"; +import Map from "../../models/Map.js"; +import User from "../../models/User.js"; +import officialCountryMaps from '../../public/officialCountryMaps.json' with { type: "json" }; let mapCache = { popular: { diff --git a/api/map/publicData.js b/api/map/publicData.js new file mode 100644 index 00000000..5b173b53 --- /dev/null +++ b/api/map/publicData.js @@ -0,0 +1,53 @@ +import { getServerSession } from "../../components/auth/serverAuth.js"; +import officialCountryMaps from "../../public/officialCountryMaps.json" with { type: "json" }; +import Map from "../../models/Map.js"; +import User from "../../models/User.js"; +import msToTime from "../../components/msToTime.js"; + +export default async function handler(req, res) { + const slug = req.query.slug; + console.log("Getting map data for", slug); + const session = await getServerSession(req); + + // Check if map is an official country map + const cntryMap = Object.values(officialCountryMaps).find(map => map.slug === slug); + if (cntryMap) { + return res.json({ + mapData: { + ...cntryMap, + description_short: cntryMap.shortDescription, + description_long: cntryMap.longDescription, + created_by: "WorldGuessr", + in_review: false, + rejected: false + } + }); + } + + // If map is not official, check user-created maps + const map = await Map.findOne({ slug }) + .select({ 'data': { $slice: 10 } }) // Slice the data to limit to 10 items + .lean(); + + if (!map) { + return res.status(404).json({ message: 'Map not found' }); + } + + const authorId = map.created_by; + const authorUser = await User.findById(authorId).lean(); + const authorSecret = authorUser?.secret; + const staff = session?.token?.staff; + + const isCreatorOrStaff = session && (authorSecret === session?.token?.secret || staff); + + if (!map.accepted && !isCreatorOrStaff) { + return res.status(404).json({ message: 'Map not accepted or no permission to view' }); + } + + map.created_by = authorUser?.username; + map.created_at = msToTime(Date.now() - map.created_at); + + return res.json({ + mapData: map + }); +} \ No newline at end of file diff --git a/api/map/searchMap.js b/api/map/searchMap.js index a6414733..5b9dbb20 100644 --- a/api/map/searchMap.js +++ b/api/map/searchMap.js @@ -1,10 +1,7 @@ -import mapConst from "@/components/maps/mapConst"; -import parseMapData from "@/components/utils/parseMapData"; -import sendableMap from "@/components/utils/sendableMap"; -import generateSlug from "@/components/utils/slugGenerator"; -import Map from "@/models/Map"; -import User from "@/models/User"; -import officialCountryMaps from '@/public/officialCountryMaps.json'; + +import sendableMap from "../../components/utils/sendableMap.js"; +import Map from "../../models/Map.js"; +import User from "../../models/User.js"; export default async function searchMaps(req, res) { // only allow POST diff --git a/components/Map.js b/components/Map.js index 136775a0..c0771216 100644 --- a/components/Map.js +++ b/components/Map.js @@ -43,8 +43,12 @@ function MapPlugin({ pinPoint, setPinPoint, answerShown, dest, gameOptions, ws, } // play sound playSound(); + // if point is outside bounds, pan back const bounds = L.latLngBounds([-90, -180], [90, 180]); + + if(!bounds.contains(e.latlng)) { const center = e.target.panInsideBounds(bounds, { animate: true }); + } } }, }); diff --git a/components/accountModal.js b/components/accountModal.js index a278bb07..26ce6afe 100644 --- a/components/accountModal.js +++ b/components/accountModal.js @@ -11,7 +11,7 @@ export default function AccountModal({ session, shown, setAccountModalOpen, inCr useEffect(() => { if(shown) { const fetchData = async () => { - const response = await fetch('/api/publicAccount', { + const response = await fetch(window.cConfig.apiUrl+'/api/publicAccount', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/components/auth/auth.js b/components/auth/auth.js index 3ed533f0..19d289f3 100644 --- a/components/auth/auth.js +++ b/components/auth/auth.js @@ -26,10 +26,4 @@ export function useSession() { return { data: session } -} - -export function getServerSession(req, res, authOptions) { - console.log("Getting server session", req, res, authOptions); - - return null; } \ No newline at end of file diff --git a/components/auth/serverAuth.js b/components/auth/serverAuth.js new file mode 100644 index 00000000..f7f3cfc3 --- /dev/null +++ b/components/auth/serverAuth.js @@ -0,0 +1,5 @@ +export function getServerSession(req, res, authOptions) { + console.log("Getting server session"); + + return null; +} \ No newline at end of file diff --git a/components/clueBanner.js b/components/clueBanner.js index 9ad4b67b..c9a7394e 100644 --- a/components/clueBanner.js +++ b/components/clueBanner.js @@ -73,7 +73,7 @@ halfFillMode='svg' ...ratedIndexes, [`rate${index}`]: value }); - fetch('/api/clues/rateClue', { + fetch(window.cConfig.apiUrl+'/api/clues/rateClue', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/components/explanationModal.js b/components/explanationModal.js index 6af13606..188fefac 100644 --- a/components/explanationModal.js +++ b/components/explanationModal.js @@ -27,7 +27,7 @@ export default function ExplanationModal({ lat, long, session, shown, onClose }) setSending(true); - const response = await fetch('/api/clues/makeClue', { + const response = await fetch(window.cConfig.apiUrl+'/api/clues/makeClue', { method: 'POST', headers: { // 'Content-Type': 'application/json', diff --git a/components/findCountry.js b/components/findCountry.js index 1b139e7a..e8bf9b65 100644 --- a/components/findCountry.js +++ b/components/findCountry.js @@ -1,7 +1,7 @@ export default async function findCountry({lat, lon}) { let data = null; try { - const resp = await fetch(`/api/country?lat=${lat}&lon=${lon}`); // fetch data from OSM + const resp = await fetch(window.cConfig.apiUrl+`/api/country?lat=${lat}&lon=${lon}`); // fetch data from OSM data = await resp.json(); } catch (e) { data = { address: { country: "Unknown" }}; // default to unknown diff --git a/components/gameUI.js b/components/gameUI.js index 93e1397e..cce42c66 100644 --- a/components/gameUI.js +++ b/components/gameUI.js @@ -172,7 +172,7 @@ export default function GameUI({ singlePlayerRound, setSinglePlayerRound, showDi if(window.location.search.includes("learn=true")) { console.log("fetching clue") - fetch('/api/clues/getClue'+(latLong ? `?lat=${latLong.lat}&lng=${latLong.long}` : '')).then(res => res.json()).then(data => { + fetch(window.cConfig.apiUrl+'/api/clues/getClue'+(latLong ? `?lat=${latLong.lat}&lng=${latLong.long}` : '')).then(res => res.json()).then(data => { if(data.error) { console.error(data.error); @@ -336,7 +336,7 @@ export default function GameUI({ singlePlayerRound, setSinglePlayerRound, showDi if(multiplayerState?.inGame) return; if(xpEarned > 0 && session?.token?.secret && gameOptions.official) { - fetch('/api/storeGame', { + fetch(window.cConfig.apiUrl+'/api/storeGame', { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/components/home.js b/components/home.js new file mode 100644 index 00000000..6570b11b --- /dev/null +++ b/components/home.js @@ -0,0 +1,1926 @@ +import HeadContent from "@/components/headContent"; +import { Jockey_One, Roboto } from 'next/font/google'; +import { FaDiscord, FaGithub } from "react-icons/fa"; +import { FaGear,FaRankingStar, FaYoutube } from "react-icons/fa6"; +import { signOut, useSession } from "@/components/auth/auth"; +import 'react-responsive-modal/styles.css'; +import { useEffect, useState, useRef } from "react"; +import Navbar from "@/components/ui/navbar"; +import GameUI from "@/components/gameUI"; +import BannerText from "@/components/bannerText"; +import findLatLongRandom from "@/components/findLatLong"; +import Link from "next/link"; +import MultiplayerHome from "@/components/multiplayerHome"; +import AccountModal from "@/components/accountModal"; +import SetUsernameModal from "@/components/setUsernameModal"; +import ChatBox from "@/components/chatBox"; +import React from "react"; +import countryMaxDists from '../public/countryMaxDists.json'; +import { useTranslation } from '@/components/useTranslations' +import useWindowDimensions from "@/components/useWindowDimensions"; +import Ad from "@/components/bannerAd"; +import Script from "next/script"; +import SettingsModal from "@/components/settingsModal"; +import sendEvent from "@/components/utils/sendEvent"; +import initWebsocket from "@/components/utils/initWebsocket"; +import 'react-toastify/dist/ReactToastify.css'; + +import NextImage from "next/image"; +import OnboardingText from "@/components/onboardingText"; +import RoundOverScreen from "@/components/roundOverScreen"; +import msToTime from "@/components/msToTime"; +import SuggestAccountModal from "@/components/suggestAccountModal"; +import FriendsModal from "@/components/friendModal"; +import { toast, ToastContainer } from "react-toastify"; +import InfoModal from "@/components/infoModal"; +import { inIframe, isForbiddenIframe } from "@/components/utils/inIframe"; +import moment from 'moment-timezone'; +import MapsModal from "@/components/maps/mapsModal"; +import { useRouter } from "next/router"; +import { fromLonLat } from "ol/proj"; +import { boundingExtent } from "ol/extent"; + +import countries from "@/public/countries.json"; +import officialCountryMaps from "@/public/officialCountryMaps.json"; + +import fixBranding from "@/components/utils/fixBranding"; +import gameStorage from "@/components/utils/localStorage"; +import DiscordModal from "@/components/discordModal"; +import MerchModal from "@/components/merchModal"; +import clientConfig from "@/clientConfig"; + +const jockey = Jockey_One({ subsets: ['latin'], weight: "400", style: 'normal' }); +const roboto = Roboto({ subsets: ['cyrillic'], weight: "400", style: 'normal' }); +const initialMultiplayerState = { + connected: false, + connecting: false, + shouldConnect: false, + gameQueued: false, + inGame: false, + nextGameQueued: false, + creatingGame: false, + enteringGameCode: false, + createOptions: { + rounds: 5, + timePerRound: 30, + location: "all", + displayLocation: "All countries", + progress: false + }, + joinOptions: { + gameCode: null, + progress: false, + error: false + } +} + +export default function Home({ }) { + const { width, height } = useWindowDimensions(); + + const [session, setSession] = useState(null); + const { data: mainSession } = useSession(); + const [accountModalOpen, setAccountModalOpen] = useState(false); + const [screen, setScreen] = useState("home"); + const [loading, setLoading] = useState(false); + // game state + const [latLong, setLatLong] = useState({ lat: 0, long: 0 }) + const [streetViewShown, setStreetViewShown] = useState(false) + const [gameOptionsModalShown, setGameOptionsModalShown] = useState(false); + // location aka map slug + const [gameOptions, setGameOptions] = useState({ location: "all", maxDist: 20000, official: true, countryMap: false, communityMapName: "", extent: null }); + const [showAnswer, setShowAnswer] = useState(false) + const [pinPoint, setPinPoint] = useState(null) + const [hintShown, setHintShown] = useState(false) + const [xpEarned, setXpEarned] = useState(0) + const [countryStreak, setCountryStreak] = useState(0) + const [settingsModal, setSettingsModal] = useState(false) + const [mapModal, setMapModal] = useState(false) + const [friendsModal, setFriendsModal] = useState(false) + const [merchModal, setMerchModal] = useState(false) + const [timeOffset, setTimeOffset] = useState(0) + const [loginQueued, setLoginQueued] = useState(false); + const [options, setOptions] = useState({ + }); + + const [isApp, setIsApp] = useState(false); + const [inCrazyGames, setInCrazyGames] = useState(false); + const [maintenance, setMaintenance] = useState(false); + + const [legacyMapLoader, setLegacyMapLoader] = useState(false); + + useEffect(() => { + if (mainSession && !inCrazyGames) { + setSession(mainSession) + } + }, [JSON.stringify(mainSession), inCrazyGames]) + + const [config, setConfig] = useState(null); + + useEffect(() => { + const clientConfigData = clientConfig(); + setConfig(clientConfigData); + window.cConfig = clientConfigData; + + if(window.location.search.includes("app=true")) { + setIsApp(true); + } + if(window.location.search.includes("instantJoin=true")) { + // crazygames + } + + + async function crazyAuthListener() { + const user = await window.CrazyGames.SDK.user.getUser(); + if(user) { + const token = await window.CrazyGames.SDK.user.getUserToken(); + if(token && user.username) { + // /api/crazyAuth + fetch(clientConfigData.apiUrl+"/api/crazyAuth", { + method: "POST", + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ token, username: user.username }) + }).then((res) => res.json()).then((data) => { + console.log("crazygames auth", token, user, data) + try { + window.CrazyGames.SDK.game.loadingStop(); + } catch(e) {} + if(data.secret && data.username) { + setSession({ token: { secret: data.secret, username: data.username } }) + // verify the ws + console.log("sending verify", ws) + setWs((prev) => { + if(prev) { + prev.send(JSON.stringify({ type: "verify", secret: data.secret, username: data.username })) + } + return prev; + }); + } else { + toast.error("CrazyGames auth failed") + } + }).catch((e) => { + try { + window.CrazyGames.SDK.game.loadingStop(); + } catch(e) {} + console.error("crazygames auth failed", e) + }); + + } + } + } + + function finish() { + const onboardingCompletedd = gameStorage.getItem("onboarding"); + console.log("onboarding", onboardingCompletedd) + if(onboardingCompletedd !== "done") startOnboarding(); + else setOnboardingCompleted(true) + } + if(window.location.search.includes("crazygames")) { + setInCrazyGames(true); + window.inCrazyGames = true; + // initialize the sdk + try { + console.log("init crazygames sdk") + setLoading(true) + + window.CrazyGames.SDK.init().then(async () => { + console.log("sdk initialized") + setLoading(false) + try { + window.CrazyGames.SDK.game.loadingStart(); + } catch(e) {} + + crazyAuthListener().then(() => { + // check if onboarding is done + finish() + }) + + + window.CrazyGames.SDK.user.addAuthListener(crazyAuthListener); + + }).catch((e) => { + finish() + console.error("crazygames sdk init failed", e) + setLoading(false) + }) + } catch(e) {} + } + initialMultiplayerState.createOptions.displayLocation = text("allCountries") + + return () => { + try { + window.CrazyGames.SDK.user.removeAuthListener(crazyAuthListener); + } catch(e){} + } + + }, []); + + const [onboarding, setOnboarding] = useState(null); + const [onboardingCompleted, setOnboardingCompleted] = useState(null); + const [otherOptions, setOtherOptions] = useState([]); // for country guesser + const [showCountryButtons, setShowCountryButtons] = useState(true); + const [countryGuesserCorrect, setCountryGuesserCorrect] = useState(false); + + const [showSuggestLoginModal, setShowSuggestLoginModal] = useState(false); + const [showDiscordModal, setShowDiscordModal] = useState(false); + const [singlePlayerRound, setSinglePlayerRound] = useState(null); + + useEffect(() => { + if(screen === "singleplayer") { + // start the single player game + setSinglePlayerRound({ + round: 1, + totalRounds: 5, + locations: [], + }) + } + }, [screen]) + + const [allLocsArray, setAllLocsArray] = useState([]); + function startOnboarding() { + + if(inCrazyGames) { + // make sure its not an invite link + const code = window.CrazyGames.SDK.game.getInviteParam("code") + if(code && code.length === 6) { + return; + } + } + +setScreen("onboarding") + +let onboardingLocations = [ + { lat: 40.7598687, long: -73.9764681, country: "US", otherOptions: ["GB", "JP"] }, +{ lat: 27.1719752, long: 78.0422793, country: "IN", otherOptions: ["ZA", "FR"] }, +{ lat: 51.5080896, long: -0.087694, country: "GB", otherOptions: ["US", "DE"] }, + { lat: 55.7495807, long: 37.616477, country: "RU", otherOptions: ["CN", "PL"] }, + // pyramid of giza 29.9773337,31.1321796 + { lat: 29.9773337, long: 31.1321796, country: "EG", otherOptions: ["TR", "BR"] }, + // eiffel tower 48.8592946,2.2927855 + { lat: 48.8592946, long: 2.2927855, country: "FR", otherOptions: ["IT", "ES"] }, + // statue of liberty 40.6909253,-74.0552998 + { lat: 40.6909253, long: -74.0552998, country: "US", otherOptions: ["CA", "AU"] }, + // brandenburg gate 52.5161999,13.3756414 + { lat: 52.5161999, long: 13.3756414, country: "DE", otherOptions: ["RU", "JP"] }, + +] + +// pick 5 random locations no repeats +const locations = []; +while (locations.length < 5) { + const loc = onboardingLocations[Math.floor(Math.random() * onboardingLocations.length)] + if (!locations.find((l) => l.country === loc.country)) { + locations.push(loc) + } +} + +setOnboarding({ + round: 1, + locations: locations, + startTime: Date.now(), +}) +sendEvent("tutorial_begin") +setShowCountryButtons(false) +} + function openMap(mapSlug) { + const country = countries.find((c) => c === mapSlug.toUpperCase()); + let officialCountryMap = null; + if(country) { + officialCountryMap = officialCountryMaps.find((c) => c.countryCode === mapSlug); + } + setAllLocsArray([]) + + if(!country && mapSlug !== gameOptions.location) { + if( ((window?.lastPlayTrack||0) + 20000 < Date.now())) { + + fetch(config.apiUrl+`/mapPlay/${mapSlug}`, {method: "POST"}) + } + + try { + window.lastPlayTrack = Date.now(); + } catch(e) {} + } + + setGameOptions((prev) => ({ + ...prev, + location: mapSlug, + official: country ? true : false, + countryMap: country, + maxDist: country ? countryMaxDists[country] : 20000, + extent: country && officialCountryMap && officialCountryMap.extent ? officialCountryMap.extent : null + })) + } + + useEffect(() => { + if(onboarding?.round >1) { + loadLocation() + } + }, [onboarding?.round]) + + useEffect(() => { + if(onboarding?.completed) { + setOnboardingCompleted(true) + } + }, [onboarding?.completed]) + useEffect(() => { + try { + const onboarding = gameStorage.getItem("onboarding"); + // check url + const specifiedMapSlug = window.location.search.includes("map="); + console.log("onboarding", onboarding, specifiedMapSlug) + // make it false just for testing + // gameStorage.setItem("onboarding", null) + if(onboarding && onboarding === "done") setOnboardingCompleted(true) + else if(specifiedMapSlug) setOnboardingCompleted(true) + else setOnboardingCompleted(false) + } catch(e) { + console.error(e, "onboard"); + setOnboardingCompleted(true); + } + // setOnboardingCompleted(false) + }, []) + + + + useEffect(() => { + + // check if pirated + if(isForbiddenIframe() && !window.blocked) { + // display a message + window.blocked = true; + document.write(` + + + + + + Play WorldGuessr + + + +
+

Welcome to WorldGuessr!

+ + + +
+ + +`) + sendEvent("blocked_iframe") + } + // check if learn mode + if(window.location.search.includes("learn=true")) { + window.learnMode = true; + + // immediately open single player + setScreen("singleplayer") + } + // check if from map screen + if(window.location.search.includes("map=")) { + // get map slug map=slug from url + const params = new URLSearchParams(window.location.search); + const mapSlug = params.get("map"); + setScreen("singleplayer") + + openMap(mapSlug) + } + + if(window.location.search.includes("createPrivateGame=true")) { + } + }, []) + + useEffect(() => { + +// check if learn mode + if(window.location.search.includes("learn=true")) { + setOnboardingCompleted(true) + } + + + if(onboardingCompleted === false) { + if(onboardingCompleted===null)return; + if (!loading) { + + + // const isPPC = window.location.search.includes("cpc=true"); + if(inIframe() && window.adBreak && !inCrazyGames) { + console.log("trying to show preroll") + window.onboardPrerollEnd = false; + setLoading(true) + window.adBreak({ + type: "preroll", + adBreakDone: function(e) { + if(window.onboardPrerollEnd) return; + setLoading(false) + window.onboardPrerollEnd = true; + sendEvent("interstitial", { type: "preroll", ...e }) + startOnboarding() + } + }) + + setTimeout(() => { + if(!window.onboardPrerollEnd) { + window.onboardPrerollEnd = true; + console.log("preroll timeout") + setLoading(false) + startOnboarding() + } + }, 3000) + } else if(!inCrazyGames) { + + startOnboarding() + } + } + } + }, [onboardingCompleted]) + + useEffect(() => { + if(session && session.token && session.token.username && !inCrazyGames) { + setOnboardingCompleted(true) + try { + gameStorage.setItem("onboarding", 'done') + } catch(e) {} + } + }, [session]) + + useEffect(() => { + if(!options?.language) return; + try { + window.localStorage.setItem("lang", options?.language) + window.language = options?.language; + console.log("set lang", options?.language) + + const location = `/${options?.language !== "en" ? options?.language : ""}` + if(!window.location.pathname.includes(location)) { + window.location.href = location; + } + if(options?.language === "en" && ["es", "fr", "de", "ru"].includes(window.location.pathname.split("/")[1])) { + window.location.href = "/"; + } + } catch(e) {} + }, [options?.language]); + + const loadOptions =async () => { + + // try to fetch options from localstorage + try { + const options = gameStorage.getItem("options"); + + + if (options) { + setOptions(JSON.parse(options)) + } else { + let json; + + try { + const res = await fetch("https://ipapi.co/json/"); + json = await res.json(); + }catch(e){} + + const countryCode = json?.country_code; + let system = "metric"; + if(countryCode && ["US", "LR", "MM", "UK"].includes(countryCode)) system = "imperial"; + + + setOptions({ + units: system, + lang: "en", + mapType: "m" //m for normal + }) + } + } catch(e) {} + + } + useEffect(()=>{loadOptions()}, []) + + useEffect(() => { + if(options && options.units && options.mapType) { + try { + gameStorage.setItem("options", JSON.stringify(options)) + } catch(e) {} + } + }, [options]) + + useEffect(() => { + window.disableVideoAds = options?.disableVideoAds; + }, [options?.disableVideoAds]); + + // multiplayer stuff + const [ws, setWs] = useState(null); + const [multiplayerState, setMultiplayerState] = useState( + initialMultiplayerState + ); + const [multiplayerChatOpen, setMultiplayerChatOpen] = useState(false); + const [multiplayerChatEnabled, setMultiplayerChatEnabled] = useState(false); + + const { t: text } = useTranslation("common"); + + useEffect(( ) => { + + if(multiplayerState?.joinOptions?.error) { + setTimeout(() => { + setMultiplayerState((prev) => ({ ...prev, joinOptions: { ...prev.joinOptions, error: null } })) + }, 1000) + } + + }, [multiplayerState?.joinOptions?.error]); + + + function handleMultiplayerAction(action, ...args) { + if (!ws || !multiplayerState.connected || multiplayerState.gameQueued || multiplayerState.connecting) return; + + if (action === "publicDuel") { + setScreen("multiplayer") + setMultiplayerState((prev) => ({ + ...prev, + gameQueued: "publicDuel", + nextGameQueued: false + })) + sendEvent("multiplayer_request_duel") + ws.send(JSON.stringify({ type: "publicDuel" })) + } + + if (action === "joinPrivateGame") { + + if (args[0]) { + setScreen("multiplayer") + + setMultiplayerState((prev) => ({ + ...prev, + joinOptions: { + ...prev.joinOptions, + error: false, + progress: true + } + })); + // join private game + ws.send(JSON.stringify({ type: "joinPrivateGame", gameCode: args[0] })) + sendEvent("multiplayer_join_private_game", { gameCode: args[0] }) + + } else { + setScreen("multiplayer") + setMultiplayerState((prev) => { + return { + ...initialMultiplayerState, + connected: true, + enteringGameCode: true, + playerCount: prev.playerCount, + guestName: prev.guestName + } + + }) + } + } + + if (action === "createPrivateGame") { + if (!args[0]) { + setScreen("multiplayer") + + setMultiplayerState((prev) => { + return { + ...initialMultiplayerState, + connected: true, + creatingGame: true, + playerCount: prev.playerCount, + guestName: prev.guestName + } + }) + } else { + + const maxDist = args[0].location === "all" ? 20000 : countryMaxDists[args[0].location]; + // setMultiplayerState((prev) => ({ + // ...prev, + // createOptions: { + // ...prev.createOptions, + // progress: 0 + // } + // })); + // (async () => { + // const locations = []; + // for (let i = 0; i < args[0].rounds; i++) { + + // const loc = await findLatLongRandom({ location: multiplayerState.createOptions.location }); + // locations.push(loc) + // setMultiplayerState((prev) => ({ + // ...prev, + // createOptions: { + // ...prev.createOptions, + // progress: i + 1 + // } + // })) + // } + + setMultiplayerState((prev) => ({ + ...prev, + createOptions: { + ...prev.createOptions, + progress: true + } + })); + + // send ws + // ws.send(JSON.stringify({ type: "createPrivateGame", rounds: args[0].rounds, timePerRound: args[0].timePerRound, locations, maxDist })) + ws.send(JSON.stringify({ type: "createPrivateGame", rounds: args[0].rounds, timePerRound: args[0].timePerRound, location: args[0].location, maxDist })) + sendEvent("multiplayer_create_private_game", { rounds: args[0].rounds, timePerRound: args[0].timePerRound, location: args[0].location, maxDist }) + // })() + } + } + + if (action === 'startGameHost' && multiplayerState?.inGame && multiplayerState?.gameData?.host && multiplayerState?.gameData?.state === "waiting") { + ws.send(JSON.stringify({ type: "startGameHost" })) + sendEvent("multiplayer_start_game_host") + } + + if(action === 'screen') { + ws.send(JSON.stringify({ type: "screen", screen: args[0] })) + } + + + } + + useEffect(() => { + (async() => { + + + if (!ws && !multiplayerState.connecting && !multiplayerState.connected && !window?.dontReconnect) { + + setMultiplayerState((prev) => ({ + ...prev, + connecting: true, + shouldConnect: false + })) + const ws = await initWebsocket(clientConfig().websocketUrl, null, 5000, 20) + if(ws && ws.readyState === 1) { + setWs(ws) + setMultiplayerState((prev)=>({ + ...prev, + error: false + })) + + + if(!inCrazyGames && !window.location.search.includes("crazygames")) { + + const tz = moment.tz.guess(); + let secret = "not_logged_in"; + if(session?.token?.secret) { + secret = session.token.secret; + } + + console.log("sending verify with secret", secret) + ws.send(JSON.stringify({ type: "verify", secret, tz})) + } else { + + + } + } else { + alert("could not connect to server") + } + + } + })(); + }, [multiplayerState, ws, screen]) + + useEffect(() => { + + if(inCrazyGames) { + if(screen === "home") { + try { + window.CrazyGames.SDK.game.gameplayStop(); + } catch(e) {} + } else { + try { + window.CrazyGames.SDK.game.gameplayStart(); + } catch(e) {} + } + } + }, [screen, inCrazyGames]) + + useEffect(() => { + if(multiplayerState?.connected && inCrazyGames) { + + // check if joined via invite link + try { + let code = window.CrazyGames.SDK.game.getInviteParam("code") + let instantJoin = window.location.search.includes("instantJoin"); + + + + if(code || instantJoin) { + + if(typeof code === "string") { + try { + code = parseInt(code) + } catch(e) { + } + } + + setOnboardingCompleted(true) + setOnboarding(null) + setLoading(false) + setScreen("home") + if(code) { + + // join private game + handleMultiplayerAction("joinPrivateGame") + // set the code + setMultiplayerState((prev) => ({ + ...prev, + joinOptions: { + ...prev.joinOptions, + gameCode: code, + progress: true + } + })) + // press go + setTimeout(() => { + handleMultiplayerAction("joinPrivateGame", code) + }, 1000) + } else { + // create private game + handleMultiplayerAction("createPrivateGame") + } + + } + + } catch(e) {} + + } + }, [multiplayerState?.connected, inCrazyGames]) + + useEffect(() => { + if (multiplayerState?.inGame && multiplayerState?.gameData?.state === "end") { + // save the final players + setMultiplayerState((prev) => ({ + ...prev, + gameData: { + ...prev.gameData, + finalPlayers: prev.gameData.players + } + })) + } + }, [multiplayerState?.gameData?.state]) + + useEffect(() => { + if (!multiplayerState?.inGame) { + setMultiplayerChatEnabled(false) + setMultiplayerChatOpen(false) + } + if (!ws) return; + + + ws.onmessage = (msg) => { + const data = JSON.parse(msg.data); + + if(data.type === "restartQueued") { + setMaintenance(data.value ? true : false) + if(data.value) { + toast.info(text("maintenanceModeStarted")) + } else if(!data.value && window.maintenance) { + toast.info(text("maintenanceModeEnded")) + } + window.maintenance = data.value ? true : false; + + } + if (data.type === "t") { + const offset = data.t - Date.now(); + if (Math.abs(offset) > 1000 && ((Math.abs(offset) < Math.abs(timeOffset)) || !timeOffset)) { + setTimeOffset(offset) + } + } + + if (data.type === "cnt") { + setMultiplayerState((prev) => ({ + ...prev, + playerCount: data.c + })) + } else if (data.type === "verify") { + setMultiplayerState((prev) => ({ + ...prev, + connected: true, + connecting: false, + guestName: data.guestName + })) + } else if (data.type === "error") { + setMultiplayerState((prev) => ({ + ...prev, + connecting: false, + connected: false, + shouldConnect: false, + error: data.message + })) + // disconnect + if(data.message === "uac") + { + window.dontReconnect = true; + } + if(data.failedToLogin) { + window.dontReconnect = true; + // logout + signOut() + + } + ws.close(); + + toast(data.message==='uac'?text('userAlreadyConnected'):data.message, { type: 'error' }); + + } else if (data.type === "game") { + setScreen("multiplayer") + setMultiplayerState((prev) => { + + + try { + if(data.state === "waiting" && inCrazyGames && data.host) { + const link = window.CrazyGames.SDK.game.showInviteButton({ code: data.code }); + } else { + window.CrazyGames.SDK.game.hideInviteButton(); + } + } catch(e) {} + + + if (data.state === "getready") { + setMultiplayerChatEnabled(true) + + if(data.map !== "all" && !countries.map((c) => c?.toLowerCase()).includes(data.map?.toLowerCase()) && !gameOptions?.extent) { + // calculate extent + + fetch(`/mapLocations/${data.map}`).then((res) => res.json()).then((data) => { + if(data.ready) { + + const mappedLatLongs = data.locations.map((l) => fromLonLat([l.lng, l.lat], "EPSG:4326")); + let extent = boundingExtent(mappedLatLongs); + console.log("extent", extent) + + setGameOptions((prev) => ({ + ...prev, + extent + })) + + } + }) + } + + } else if (data.state === "guess") { + const didIguess = (data.players ?? prev.gameData?.players)?.find((p) => p.id === prev.gameData?.myId)?.final; + if (didIguess) { + setMultiplayerChatEnabled(true) + } else { + setMultiplayerChatEnabled(false) + } + } + + if ((!prev.gameData || (prev?.gameData?.state === "getready")) && data.state === "guess") { + setPinPoint(null) + if (!prev?.gameData?.locations && data.locations) { + setLatLong(data.locations[data.curRound - 1]) + + } else { + setLatLong(prev?.gameData?.locations[data.curRound - 1]) + } + } + + return { + ...prev, + gameQueued: false, + inGame: true, + gameData: { + ...prev.gameData, + ...data, + type: undefined + }, + enteringGameCode: false, + creatingGame: false, + joinOptions: initialMultiplayerState.joinOptions, + createOptions: initialMultiplayerState.createOptions, + } + }) + + if (data.state === "getready") { + setStreetViewShown(false) + } else if (data.state === "guess") { + setStreetViewShown(true) + } + } else if(data.type === "maxDist") { + const maxDist = data.maxDist; + console.log("got new max dist", maxDist) + setMultiplayerState((prev) => ({ + ...prev, + gameData: { + ...prev.gameData, + maxDist + } + })) + + } else if (data.type === "player") { + if (data.action === "remove") { + setMultiplayerState((prev) => ({ + ...prev, + gameData: { + ...prev.gameData, + players: prev.gameData.players.filter((p) => p.id !== data.id) + } + })) + } else if (data.action === "add") { + setMultiplayerState((prev) => ({ + ...prev, + gameData: { + ...prev.gameData, + players: [...prev.gameData.players, data.player] + } + })) + } + } else if (data.type === "place") { + const id = data.id; + if (id === multiplayerState.gameData.myId) { + setMultiplayerChatEnabled(true) + } + + const player = multiplayerState.gameData.players.find((p) => p.id === id); + if (player) { + player.final = data.final; + player.latLong = data.latLong; + } + } else if (data.type === "gameOver") { + setLatLong(null) + setGameOptions((prev) => ({ + ...prev, + extent: null + })) + + } else if (data.type === "gameShutdown") { + setScreen("home") + setMultiplayerState((prev) => { + return { + ...initialMultiplayerState, + connected: true, + nextGameQueued: prev.nextGameQueued, + playerCount: prev.playerCount, + guestName: prev.guestName + } + }); + setGameOptions((prev) => ({ + ...prev, + extent: null + })) + } else if (data.type === "gameJoinError" && multiplayerState.enteringGameCode) { + setMultiplayerState((prev) => { + return { + ...prev, + joinOptions: { + ...prev.joinOptions, + error: data.error, + progress: false + } + } + }) + } else if(data.type === 'generating') { + // location generation before round + setMultiplayerState((prev) => { + return { + ...prev, + gameData: { + ...prev.gameData, + generated: data.generated + } + } + }) + } else if(data.type === "friendReq") { + const from = data.name; + const id = data.id; + const toAccept = (closeToast) => { + ws.send(JSON.stringify({ type: 'acceptFriend', id })) + closeToast() + } + const toDecline = (closeToast) => { + ws.send(JSON.stringify({ type: 'declineFriend', id })) + closeToast() + } + const toastComponent = function({closeToast}){ + return ( +
+ {text("youGotFriendReq", { from })} + + +   + +
+ ) + } + + toast(toastComponent, { type: 'info', theme: "dark" }) + + + } else if(data.type === 'toast') { + toast(text(data.key, data), { type: data.toastType ?? 'info', theme: "dark", closeOnClick: data.closeOnClick ?? false, autoClose: data.autoClose ?? 5000 }) + } else if(data.type === 'invite') { + // code, invitedByName, invitedById + const { code, invitedByName, invitedById } = data; + + const toAccept = (closeToast) => { + ws.send(JSON.stringify({ type: 'acceptInvite', code, invitedById })) + closeToast() + } + const toDecline = (closeToast) => { + closeToast() + } + const toastComponent = function({closeToast}){ + return ( +
+ {text("youGotInvite", { from: invitedByName })} + + +   + +
+ ) + } + + toast(toastComponent, { type: 'info', theme: "dark", autoClose: 10000 }) + } else if(data.type === 'streak') { + const streak = data.streak; + + if(streak === 0) { + toast(text("streakLost"), { type: 'info', theme: "dark", autoClose: 5000, closeOnClick: true }) + } else if(streak === 1) { + toast(text("streakStarted"), { type: 'info', theme: "dark", autoClose: 5000, closeOnClick: true }) + } else { + toast(text("streakGained", { streak }), { type: 'info', theme: "dark", autoClose: 5000, closeOnClick: true }) + } + } + } + + // ws on disconnect + ws.onclose = () => { + setWs(null) + console.log("ws closed") + sendEvent("multiplayer_disconnect") + + setMultiplayerState((prev) => ({ + ...initialMultiplayerState, + error: text("connectionLost") + })); + } + + ws.onerror = () => { + setWs(null) + console.log("ws error") + sendEvent("multiplayer_disconnect") + + setMultiplayerState((prev) => ({ + ...initialMultiplayerState, + error: text("connectionLost") + })); + } + + + return () => { + ws.onmessage = null; + } + }, [ws, multiplayerState, timeOffset, gameOptions?.extent]); + + useEffect(() => { + if (multiplayerState?.connected && !multiplayerState?.inGame && multiplayerState?.nextGameQueued) { + handleMultiplayerAction("publicDuel"); + } + }, [multiplayerState, timeOffset]) + + useEffect(() => { + if (multiplayerState?.connected) { + handleMultiplayerAction("screen", screen); + } + }, [screen]); + + + // useEffect(() => { + // if (multiplayerState.inGame && multiplayerState.gameData?.state === "guess" && pinPoint) { + // // send guess + // console.log("pinpoint1", pinPoint) + // const pinpointLatLong = [pinPoint.lat, pinPoint.lng]; + // ws.send(JSON.stringify({ type: "place", latLong: pinpointLatLong, final: false })) + // } + // }, [multiplayerState, pinPoint]) + + function guessMultiplayer(send) { + if (!send) return; + if (!multiplayerState.inGame || multiplayerState.gameData?.state !== "guess" || !pinPoint) return; + const pinpointLatLong = [pinPoint.lat, pinPoint.lng]; + + ws.send(JSON.stringify({ type: "place", latLong: pinpointLatLong, final: true })) + } + + function sendInvite(id) { + if(!ws || !multiplayerState?.connected) return; + ws.send(JSON.stringify({ type: 'inviteFriend', friendId: id })) + } + + useEffect(() => { + try { + const streak = gameStorage.getItem("countryStreak"); + if (streak) { + setCountryStreak(parseInt(streak)) + } + + // preload/cache src.png and dest.png + const img = new Image(); + img.src = "/src.png"; + const img2 = new Image(); + img2.src = "/dest.png"; + } catch(e) {} + + }, []) + + function reloadBtnPressed() { + if(window.reloadLoc) { + window.reloadLoc() + } + } + + function crazyMidgame(adFinished = () => {}) { + if(window.inCrazyGames && window.CrazyGames.SDK.environment !== "disabled") { + try { + const callbacks = { + adFinished: () => adFinished(), + adError: (error) => adFinished(), + adStarted: () => console.log("Start midgame ad"), + }; + window.CrazyGames.SDK.ad.requestAd("midgame", callbacks); + } catch(e) {} + } else { + adFinished() + } + } + + function backBtnPressed(queueNextGame = false) { + + + + if (loading) setLoading(false); + + if(window.learnMode) { + // redirect to home + window.location.href = "/" + return; + } + + if(screen === "onboarding") { + setScreen("home") + setOnboarding(null) + setOnboardingCompleted(true) + gameStorage.setItem("onboarding", 'done') + + crazyMidgame() + + return; + } + + setStreetViewShown(false) + + if (multiplayerState?.inGame) { + ws.send(JSON.stringify({ + type: 'leaveGame' + })) + + if(inCrazyGames) { + try { + window.CrazyGames.SDK.game.hideInviteButton(); + } catch(e) {} + } + + setMultiplayerState((prev) => { + return { + ...prev, + nextGameQueued: queueNextGame === true + } + }) + setScreen("home") + + if(["getready", "guess"].includes(multiplayerState?.gameData?.state)) { + crazyMidgame() + } + + } else if ((multiplayerState?.creatingGame || multiplayerState?.enteringGameCode) && multiplayerState?.connected) { + + setMultiplayerState((prev) => { + return { + ...initialMultiplayerState, + connected: true, + playerCount: prev.playerCount, + guestName: prev.guestName + + } + }) + setScreen("home") + + } else if(multiplayerState?.gameQueued) { + console.log("gameQueued") + ws.send(JSON.stringify({ type: "leaveQueue" })) + + setMultiplayerState((prev) => { + return { + ...prev, + gameQueued: false + } + }); + setScreen("home") + + } else { + setScreen("home"); + setGameOptions((prev) => ({ + ...prev, + extent: null + })) + clearLocation(); + + if(screen === "singleplayer") { + crazyMidgame() + + } + } + } + + function clearLocation() { + setLatLong({ lat: 0, long: 0 }) + setStreetViewShown(false) + setShowAnswer(false) + setPinPoint(null) + setHintShown(false) + } + + function loadLocation() { + if (loading) return; + + console.log("loading location") + setLoading(true) + setShowAnswer(false) + setPinPoint(null) + setLatLong(null) + setHintShown(false) + if(screen === "onboarding") { + setLatLong(onboarding.locations[onboarding.round - 1]); + let options = JSON.parse(JSON.stringify(onboarding.locations[onboarding.round - 1].otherOptions)); + options.push(onboarding.locations[onboarding.round - 1].country) + // shuffle + options = options.sort(() => Math.random() - 0.5) + setOtherOptions(options) + } else { + function defaultMethod() { + findLatLongRandom(gameOptions).then((latLong) => { + setLatLong(latLong) + }); + } + function fetchMethod() { + console.log("fetching") + fetch(window.cConfig.apiUrl+((gameOptions.location==="all")?`/${window?.learnMode ? 'clue': 'all'}Countries.json`:`/mapLocations/${gameOptions.location}`)).then((res) => res.json()).then((data) => { + if(data.ready) { + // this uses long for lng + for(let i = 0; i < data.locations.length; i++) { + if(data.locations[i].lng && !data.locations[i].long) { + data.locations[i].long = data.locations[i].lng; + delete data.locations[i].lng; + } + } + + setAllLocsArray(data.locations) + + if(gameOptions.location === "all") { + const loc = data.locations[0] + setLatLong(loc) + } else { + let loc = data.locations[Math.floor(Math.random() * data.locations.length)]; + + while(loc.lat === latLong.lat && loc.long === latLong.long) { + loc = data.locations[Math.floor(Math.random() * data.locations.length)]; + } + + setLatLong(loc) + if(data.name) { + + // calculate extent (for openlayers) + const mappedLatLongs = data.locations.map((l) => fromLonLat([l.long, l.lat], 'EPSG:4326')); + let extent = boundingExtent(mappedLatLongs); + console.log("extent", extent) + // convert extent from EPSG:4326 to EPSG:3857 (for openlayers) + + setGameOptions((prev) => ({ + ...prev, + communityMapName: data.name, + official: data.official ?? false, + maxDist: data.maxDist ?? 20000, + extent: extent + })) + + } + } + + } else { + if(gameOptions.location !== "all") { + toast(text("errorLoadingMap"), { type: 'error' }) + } + defaultMethod() + } + }).catch((e) => { + console.error(e) + toast(text("errorLoadingMap"), { type: 'error' }) + defaultMethod() + }); + } + if(gameOptions.countryMap && gameOptions.official) { + defaultMethod() + } else { + if(allLocsArray.length===0) { + fetchMethod() + } else if(allLocsArray.length>0) { + const locIndex = allLocsArray.findIndex((l) => l.lat === latLong.lat && l.long === latLong.long); + if((locIndex === -1) || allLocsArray.length === 1) { + console.log("could not find location in array", locIndex, allLocsArray) + fetchMethod() + } else { + if(gameOptions.location === "all") { + const loc = allLocsArray[locIndex+1] ?? allLocsArray[0]; + setLatLong(loc) + } else { + // prevent repeats: remove the prev location from the array + setAllLocsArray((prev) => { + const newArr = prev.filter((l) => l.lat !== latLong.lat && l.long !== latLong.long) + + + // community maps are randomized + const loc = newArr[Math.floor(Math.random() * newArr.length)]; + + + setLatLong(loc) + return newArr; + }) + + } + } + + } +} + } + + } + + function onNavbarLogoPress() { + if(screen === "onboarding") return; + + if (screen !== "home" && !loading) { + if (screen==="multiplayer" && multiplayerState?.connected && !multiplayerState?.inGame) { + return; + } + if (!multiplayerState?.inGame) loadLocation() + else if (multiplayerState?.gameData?.state === "guess") { + + } + } + } + + const ChatboxMemo = React.useMemo(() => setMultiplayerChatOpen(!multiplayerChatOpen)} enabled={multiplayerChatEnabled} myId={multiplayerState?.gameData?.myId} inGame={multiplayerState?.inGame} />, [multiplayerChatOpen, multiplayerChatEnabled, ws, multiplayerState?.gameData?.myId, multiplayerState?.inGame]) + + // Send pong every 10 seconds if websocket is connected + useEffect(() => { + const pongInterval = setInterval(() => { + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: 'pong' })); + } + }, 10000); // Send pong every 10 seconds + + return () => clearInterval(pongInterval); + }, [ws]); + const panoramaRef = useRef(null); + const [showPanoOnResult, setShowPanoOnResult] = useState(false); + + useEffect(() => { + // const map = new google.maps.Map(document.getElementById("map"), { + // center: fenway, + // zoom: 14, + // }); + if(legacyMapLoader) { + setLoading(false) + + // kill the element that has div[dir="ltr"] + const elem = document.querySelector('div[dir="ltr"]'); + console.log("elem", elem) + + return; + } + if(!latLong || (latLong.lat === 0 && latLong.long === 0)) return; + if(!document.getElementById("googlemaps")) return; + + if(!panoramaRef.current) { + + + + panoramaRef.current = new google.maps.StreetViewPanorama( + document.getElementById("googlemaps"), + { + position: { lat: latLong.lat, lng: latLong.long }, + pov: { + heading: 0, + pitch: 0, + }, + motionTracking: false, + linksControl: gameOptions?.nm ? false:true, + clickToGo: gameOptions?.nm ? false:true, + + panControl: gameOptions?.npz ? false:true, + zoomControl: gameOptions?.npz ? false:true, + showRoadLabels: gameOptions?.showRoadName===true?true:false, + disableDefaultUI: true, + }, + ); + + window.reloadLoc = () => { + panoramaRef.current.setPosition({ lat: latLong.lat, lng: latLong.long }); + } + window.panorama = panoramaRef.current; + } else { + + panoramaRef.current.setPosition({ lat: latLong.lat, lng: latLong.long }); + + window.reloadLoc = () => { + panoramaRef.current.setPosition({ lat: latLong.lat, lng: latLong.long }); + } + } + + + // pano onload + + function fixPitch() { + // point towards road + + panoramaRef.current.setPov(panoramaRef.current.getPhotographerPov()); + panoramaRef.current.setZoom(0); + + // if localhost log the location + if(window.location.hostname === "localhost") { + console.log("[DEV] country:", latLong.country); + } + } + + + let loaded = false; + function onChangeListener(e) { + if(loaded) return; + loaded = true; + + setTimeout(() => { + setLoading(false) + setStreetViewShown(true) + // Select all tags with the attribute http-equiv="origin-trial" + const metaTags = document.querySelectorAll('meta[http-equiv="origin-trial"]'); + + // Loop through the NodeList and remove each tag + metaTags.forEach(meta => meta.remove()); + }, 500) + fixBranding(); + + fixPitch(); + } + panoramaRef.current.addListener("pano_changed", onChangeListener); + + + return () => { + if(!panoramaRef.current) return; + google.maps.event.clearListeners(panoramaRef.current, 'pano_changed'); + } + + + }, [latLong, gameOptions?.nm, gameOptions?.npz, gameOptions?.showRoadName, legacyMapLoader]) + +// useEffect(() => { +// //!(latLong && multiplayerState?.gameData?.state !== 'end')) || (!streetViewShown || loading || (showAnswer && !showPanoOnResult) || (multiplayerState?.gameData?.state === 'getready') || !latLong) +// // debug this condition: +// console.log("isHidden", !(latLong && multiplayerState?.gameData?.state !== 'end')) || (!streetViewShown || loading || (showAnswer && !showPanoOnResult) || (multiplayerState?.gameData?.state === 'getready') || !latLong) + +// console.log("latLong", latLong) +// console.log("multiplayerState?.gameData?.state", multiplayerState?.gameData?.state) +// console.log("streetViewShown", streetViewShown) +// console.log("loading", loading) +// console.log("showAnswer", showAnswer) +// console.log("showPanoOnResult", showPanoOnResult) + + +// }, [latLong, multiplayerState?.gameData?.state, streetViewShown, loading, showAnswer, showPanoOnResult]) + + useEffect(() => { + + if(panoramaRef.current) { + panoramaRef.current.setOptions({ + linksControl: gameOptions?.nm ? false:true, + clickToGo: gameOptions?.nm ? false:true, + + panControl: true, + + zoomControl: gameOptions?.npz ? false:true, + showRoadLabels: gameOptions?.showRoadName===true?true:false, + }) + } + }, [gameOptions?.nm, gameOptions?.npz, gameOptions?.showRoadName]) + + + return ( + <> + + + + + + + setMerchModal(false)} session={session} /> + + {ChatboxMemo} + + +
+
+
+

{text("videoAdThanks")}
{text("enjoyGameplay")}

+
+
+
+
+ +{screen === "home" && !mapModal && !merchModal && !friendsModal && !accountModalOpen && ( +
+
+ { !isApp && ( + <> + { !inCrazyGames && ( + <> + + + + + )} + + + + + )} + + +
+
+ )} + +
+ +
+ + +
+ { latLong && latLong?.lat && latLong?.long && legacyMapLoader ? ( +<> + + + +{/* put something in the top left to cover the address */} +
+ + + + + ) : ( +
+ )} + + + + + setFriendsModal(true)} loginQueued={loginQueued} setLoginQueued={setLoginQueued} inGame={multiplayerState?.inGame || screen === "singleplayer"} openAccountModal={() => setAccountModalOpen(true)} session={session} shown={true} reloadBtnPressed={reloadBtnPressed} backBtnPressed={backBtnPressed} setGameOptionsModalShown={setGameOptionsModalShown} onNavbarPress={() => onNavbarLogoPress()} gameOptions={gameOptions} screen={screen} multiplayerState={multiplayerState} /> + +{/* merch button */} +{screen === "home" && !mapModal && session && session?.token?.secret && !inCrazyGames && !session?.token?.supporter && ( + +)} + + +
+ + + { onboardingCompleted===null ? ( + <> + + + ) : ( + <> + +
+ { onboardingCompleted && ( +

WorldGuessr

+ )} + +
+ + { onboardingCompleted && ( + + <> +
+ + {/* { + if (!loading) setScreen("singleplayer") + }} /> */} + + {/* {text("playOnline")} */} + + + + {/* {text("playFriends")} */} +
+ + +
+
+ +
+{/* { !isApp && ( + <> + + + + + )} + + */} + { !inCrazyGames && ( + + )} +
+ + + )} +
+ +
+
+ { !loading && screen === "home" && !inCrazyGames &&(!session?.token?.supporter) && ( + + )} +
+
+
+ + )} +
+ + +
+ + + {setMapModal(false);setGameOptionsModalShown(false)}} text={text} + customChooseMapCallback={(gameOptionsModalShown&&screen==="singleplayer")?(map)=> { + console.log("map", map) + openMap(map.countryMap||map.slug); + setGameOptionsModalShown(false) + }:null} + showAllCountriesOption={(gameOptionsModalShown&&screen==="singleplayer")} + singleplayer={screen==="singleplayer"} + gameOptions={gameOptions} setGameOptions={setGameOptions} /> + + setSettingsModal(false)} /> + + setFriendsModal(false)} session={session} canSendInvite={ + // send invite if in a private multiplayer game, dont need to be host or in game waiting just need to be in a private game + multiplayerState?.inGame && !multiplayerState?.gameData?.public + } sendInvite={sendInvite} /> + + {screen === "singleplayer" &&
+ +
} + + {screen === "onboarding" && onboarding?.round &&
+ +
} + + {screen === "onboarding" && onboarding?.completed &&
+
+ { + try { + gameStorage.setItem("onboarding", 'done') + } catch(e) {} + setOnboarding((prev)=>{ + return { + ...prev, + finalOnboardingShown: true + } + }) + }} shown={!onboarding?.finalOnboardingShown} /> + { + if(onboarding) sendEvent("tutorial_end"); + + setOnboarding(null) + if(!window.location.search.includes("app=true") && !inCrazyGames) { + setShowSuggestLoginModal(true) + } + setScreen("home") + }}/> +
+
+} + + {screen === "multiplayer" &&
+ +
} + + {multiplayerState.inGame && ["guess", "getready", "end"].includes(multiplayerState.gameData?.state) && ( + { }} gameOptions={{ location: "all", maxDist: 20000, extent: gameOptions?.extent }} setGameOptions={() => { }} showAnswer={(multiplayerState?.gameData?.curRound !== 1) && multiplayerState?.gameData?.state === 'getready'} setShowAnswer={guessMultiplayer} /> + )} + + + + +
+ + ) +} + diff --git a/components/localizedHome.js b/components/localizedHome.js new file mode 100644 index 00000000..bdfc08f5 --- /dev/null +++ b/components/localizedHome.js @@ -0,0 +1,54 @@ +import Home from "@/components/home"; +import { useEffect } from "react"; + +export default function LocalizedHome({ path }) { + + useEffect(() => { + + let language = "en"; + const langs = ["en", "es", "fr", "de", "ru"]; + if(typeof window !== "undefined") { + + try { + var userLang = navigator.language || navigator.userLanguage; + // convert to 2 letter code + userLang = userLang.split("-")[0]; + if(langs.includes(userLang)){ + language = userLang; + } + + } catch(e) { + console.error(e); + } + + try{ + let lang = window.localStorage.getItem("lang"); + if(lang && langs.includes(lang)) { + language = lang; + } + } catch(e) { + console.error(e); + } + + if(path === "auto") { + if(language !== "en") { + console.log("Redirecting to", language); + window.location.href = `/${language}`; + } + } else { + if(path !== language) { + window.location.href = `/${language}`; + } + } + } + + + + + }, []); + return ( + + ) +} + + diff --git a/components/maps/mapTile.js b/components/maps/mapTile.js index ca67cc45..fd7c737d 100644 --- a/components/maps/mapTile.js +++ b/components/maps/mapTile.js @@ -37,7 +37,7 @@ export default function MapTile({ onPencilClick, showEditControls, map, onHeart, if(reject_reason === null) return; } - fetch(`/api/map/approveRejectMap`, { + fetch(window.cConfig.apiUrl+`/api/map/approveRejectMap`, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -72,7 +72,7 @@ export default function MapTile({ onPencilClick, showEditControls, map, onHeart, e.stopPropagation(); if (confirm("Are you sure you want to delete this map?")) { - fetch(`/api/map/delete`, { + fetch(window.cConfig.apiUrl+`/api/map/delete`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' @@ -180,7 +180,7 @@ export default function MapTile({ onPencilClick, showEditControls, map, onHeart, - - - - -`) - sendEvent("blocked_iframe") - } - // check if learn mode - if(window.location.search.includes("learn=true")) { - window.learnMode = true; - - // immediately open single player - setScreen("singleplayer") - } - // check if from map screen - if(window.location.search.includes("map=")) { - // get map slug map=slug from url - const params = new URLSearchParams(window.location.search); - const mapSlug = params.get("map"); - setScreen("singleplayer") - - openMap(mapSlug) - } - - if(window.location.search.includes("createPrivateGame=true")) { - } - }, []) - - useEffect(() => { - -// check if learn mode - if(window.location.search.includes("learn=true")) { - setOnboardingCompleted(true) - } - - - if(onboardingCompleted === false) { - if(onboardingCompleted===null)return; - if (!loading) { - - - // const isPPC = window.location.search.includes("cpc=true"); - if(inIframe() && window.adBreak && !inCrazyGames) { - console.log("trying to show preroll") - window.onboardPrerollEnd = false; - setLoading(true) - window.adBreak({ - type: "preroll", - adBreakDone: function(e) { - if(window.onboardPrerollEnd) return; - setLoading(false) - window.onboardPrerollEnd = true; - sendEvent("interstitial", { type: "preroll", ...e }) - startOnboarding() - } - }) - - setTimeout(() => { - if(!window.onboardPrerollEnd) { - window.onboardPrerollEnd = true; - console.log("preroll timeout") - setLoading(false) - startOnboarding() - } - }, 3000) - } else if(!inCrazyGames) { - - startOnboarding() - } - } - } - }, [onboardingCompleted]) - - useEffect(() => { - if(session && session.token && session.token.username && !inCrazyGames) { - setOnboardingCompleted(true) - try { - gameStorage.setItem("onboarding", 'done') - } catch(e) {} - } - }, [session]) - - const loadOptions =async () => { - - // try to fetch options from localstorage - try { - const options = gameStorage.getItem("options"); - - - if (options) { - setOptions(JSON.parse(options)) - } else { - let json; - - try { - const res = await fetch("https://ipapi.co/json/"); - json = await res.json(); - }catch(e){} - - const countryCode = json?.country_code; - let system = "metric"; - if(countryCode && ["US", "LR", "MM", "UK"].includes(countryCode)) system = "imperial"; - - - setOptions({ - units: system, - mapType: "m" //m for normal - }) - } - } catch(e) {} - - } - useEffect(()=>{loadOptions()}, []) - - useEffect(() => { - if(options && options.units && options.mapType) { - try { - gameStorage.setItem("options", JSON.stringify(options)) - } catch(e) {} - } - }, [options]) - - useEffect(() => { - window.disableVideoAds = options?.disableVideoAds; - }, [options?.disableVideoAds]); - - // multiplayer stuff - const [ws, setWs] = useState(null); - const [multiplayerState, setMultiplayerState] = useState( - initialMultiplayerState - ); - const [multiplayerChatOpen, setMultiplayerChatOpen] = useState(false); - const [multiplayerChatEnabled, setMultiplayerChatEnabled] = useState(false); - - const { t: text } = useTranslation("common"); - - useEffect(( ) => { - - if(multiplayerState?.joinOptions?.error) { - setTimeout(() => { - setMultiplayerState((prev) => ({ ...prev, joinOptions: { ...prev.joinOptions, error: null } })) - }, 1000) - } - - }, [multiplayerState?.joinOptions?.error]); - - - function handleMultiplayerAction(action, ...args) { - if (!ws || !multiplayerState.connected || multiplayerState.gameQueued || multiplayerState.connecting) return; - - if (action === "publicDuel") { - setScreen("multiplayer") - setMultiplayerState((prev) => ({ - ...prev, - gameQueued: "publicDuel", - nextGameQueued: false - })) - sendEvent("multiplayer_request_duel") - ws.send(JSON.stringify({ type: "publicDuel" })) - } - - if (action === "joinPrivateGame") { - - if (args[0]) { - setScreen("multiplayer") - - setMultiplayerState((prev) => ({ - ...prev, - joinOptions: { - ...prev.joinOptions, - error: false, - progress: true - } - })); - // join private game - ws.send(JSON.stringify({ type: "joinPrivateGame", gameCode: args[0] })) - sendEvent("multiplayer_join_private_game", { gameCode: args[0] }) - - } else { - setScreen("multiplayer") - setMultiplayerState((prev) => { - return { - ...initialMultiplayerState, - connected: true, - enteringGameCode: true, - playerCount: prev.playerCount, - guestName: prev.guestName - } - - }) - } - } - - if (action === "createPrivateGame") { - if (!args[0]) { - setScreen("multiplayer") - - setMultiplayerState((prev) => { - return { - ...initialMultiplayerState, - connected: true, - creatingGame: true, - playerCount: prev.playerCount, - guestName: prev.guestName - } - }) - } else { - - const maxDist = args[0].location === "all" ? 20000 : countryMaxDists[args[0].location]; - // setMultiplayerState((prev) => ({ - // ...prev, - // createOptions: { - // ...prev.createOptions, - // progress: 0 - // } - // })); - // (async () => { - // const locations = []; - // for (let i = 0; i < args[0].rounds; i++) { - - // const loc = await findLatLongRandom({ location: multiplayerState.createOptions.location }); - // locations.push(loc) - // setMultiplayerState((prev) => ({ - // ...prev, - // createOptions: { - // ...prev.createOptions, - // progress: i + 1 - // } - // })) - // } - - setMultiplayerState((prev) => ({ - ...prev, - createOptions: { - ...prev.createOptions, - progress: true - } - })); - - // send ws - // ws.send(JSON.stringify({ type: "createPrivateGame", rounds: args[0].rounds, timePerRound: args[0].timePerRound, locations, maxDist })) - ws.send(JSON.stringify({ type: "createPrivateGame", rounds: args[0].rounds, timePerRound: args[0].timePerRound, location: args[0].location, maxDist })) - sendEvent("multiplayer_create_private_game", { rounds: args[0].rounds, timePerRound: args[0].timePerRound, location: args[0].location, maxDist }) - // })() - } - } - - if (action === 'startGameHost' && multiplayerState?.inGame && multiplayerState?.gameData?.host && multiplayerState?.gameData?.state === "waiting") { - ws.send(JSON.stringify({ type: "startGameHost" })) - sendEvent("multiplayer_start_game_host") - } - - if(action === 'screen') { - ws.send(JSON.stringify({ type: "screen", screen: args[0] })) - } - - - } - - useEffect(() => { - (async() => { - - - if (!ws && !multiplayerState.connecting && !multiplayerState.connected && !window?.dontReconnect) { - - setMultiplayerState((prev) => ({ - ...prev, - connecting: true, - shouldConnect: false - })) - const ws = await initWebsocket(clientConfig().websocketUrl, null, 5000, 20) - if(ws && ws.readyState === 1) { - setWs(ws) - setMultiplayerState((prev)=>({ - ...prev, - error: false - })) - - - if(!inCrazyGames && !window.location.search.includes("crazygames")) { - - const tz = moment.tz.guess(); - let secret = "not_logged_in"; - if(session?.token?.secret) { - secret = session.token.secret; - } - - console.log("sending verify with secret", secret) - ws.send(JSON.stringify({ type: "verify", secret, tz})) - } else { - - - } - } else { - alert("could not connect to server") - } - - } - })(); - }, [multiplayerState, ws, screen]) - - useEffect(() => { - - if(inCrazyGames) { - if(screen === "home") { - try { - window.CrazyGames.SDK.game.gameplayStop(); - } catch(e) {} - } else { - try { - window.CrazyGames.SDK.game.gameplayStart(); - } catch(e) {} - } - } - }, [screen, inCrazyGames]) - - useEffect(() => { - if(multiplayerState?.connected && inCrazyGames) { - - // check if joined via invite link - try { - let code = window.CrazyGames.SDK.game.getInviteParam("code") - let instantJoin = window.location.search.includes("instantJoin"); - - - - if(code || instantJoin) { - - if(typeof code === "string") { - try { - code = parseInt(code) - } catch(e) { - } - } - - setOnboardingCompleted(true) - setOnboarding(null) - setLoading(false) - setScreen("home") - if(code) { - - // join private game - handleMultiplayerAction("joinPrivateGame") - // set the code - setMultiplayerState((prev) => ({ - ...prev, - joinOptions: { - ...prev.joinOptions, - gameCode: code, - progress: true - } - })) - // press go - setTimeout(() => { - handleMultiplayerAction("joinPrivateGame", code) - }, 1000) - } else { - // create private game - handleMultiplayerAction("createPrivateGame") - } - - } - - } catch(e) {} - - } - }, [multiplayerState?.connected, inCrazyGames]) - - useEffect(() => { - if (multiplayerState?.inGame && multiplayerState?.gameData?.state === "end") { - // save the final players - setMultiplayerState((prev) => ({ - ...prev, - gameData: { - ...prev.gameData, - finalPlayers: prev.gameData.players - } - })) - } - }, [multiplayerState?.gameData?.state]) - - useEffect(() => { - if (!multiplayerState?.inGame) { - setMultiplayerChatEnabled(false) - setMultiplayerChatOpen(false) - } - if (!ws) return; - - - ws.onmessage = (msg) => { - const data = JSON.parse(msg.data); - - if(data.type === "restartQueued") { - setMaintenance(data.value ? true : false) - if(data.value) { - toast.info(text("maintenanceModeStarted")) - } else if(!data.value && window.maintenance) { - toast.info(text("maintenanceModeEnded")) - } - window.maintenance = data.value ? true : false; - - } - if (data.type === "t") { - const offset = data.t - Date.now(); - if (Math.abs(offset) > 1000 && ((Math.abs(offset) < Math.abs(timeOffset)) || !timeOffset)) { - setTimeOffset(offset) - } - } - - if (data.type === "cnt") { - setMultiplayerState((prev) => ({ - ...prev, - playerCount: data.c - })) - } else if (data.type === "verify") { - setMultiplayerState((prev) => ({ - ...prev, - connected: true, - connecting: false, - guestName: data.guestName - })) - } else if (data.type === "error") { - setMultiplayerState((prev) => ({ - ...prev, - connecting: false, - connected: false, - shouldConnect: false, - error: data.message - })) - // disconnect - if(data.message === "uac") - { - window.dontReconnect = true; - } - if(data.failedToLogin) { - window.dontReconnect = true; - // logout - signOut() - - } - ws.close(); - - toast(data.message==='uac'?text('userAlreadyConnected'):data.message, { type: 'error' }); - - } else if (data.type === "game") { - setScreen("multiplayer") - setMultiplayerState((prev) => { - - - try { - if(data.state === "waiting" && inCrazyGames && data.host) { - const link = window.CrazyGames.SDK.game.showInviteButton({ code: data.code }); - } else { - window.CrazyGames.SDK.game.hideInviteButton(); - } - } catch(e) {} - - - if (data.state === "getready") { - setMultiplayerChatEnabled(true) - - if(data.map !== "all" && !countries.map((c) => c?.toLowerCase()).includes(data.map?.toLowerCase()) && !gameOptions?.extent) { - // calculate extent - - fetch(`/mapLocations/${data.map}`).then((res) => res.json()).then((data) => { - if(data.ready) { - - const mappedLatLongs = data.locations.map((l) => fromLonLat([l.lng, l.lat], "EPSG:4326")); - let extent = boundingExtent(mappedLatLongs); - console.log("extent", extent) - - setGameOptions((prev) => ({ - ...prev, - extent - })) - - } - }) - } - - } else if (data.state === "guess") { - const didIguess = (data.players ?? prev.gameData?.players)?.find((p) => p.id === prev.gameData?.myId)?.final; - if (didIguess) { - setMultiplayerChatEnabled(true) - } else { - setMultiplayerChatEnabled(false) - } - } - - if ((!prev.gameData || (prev?.gameData?.state === "getready")) && data.state === "guess") { - setPinPoint(null) - if (!prev?.gameData?.locations && data.locations) { - setLatLong(data.locations[data.curRound - 1]) - - } else { - setLatLong(prev?.gameData?.locations[data.curRound - 1]) - } - } - - return { - ...prev, - gameQueued: false, - inGame: true, - gameData: { - ...prev.gameData, - ...data, - type: undefined - }, - enteringGameCode: false, - creatingGame: false, - joinOptions: initialMultiplayerState.joinOptions, - createOptions: initialMultiplayerState.createOptions, - } - }) - - if (data.state === "getready") { - setStreetViewShown(false) - } else if (data.state === "guess") { - setStreetViewShown(true) - } - } else if(data.type === "maxDist") { - const maxDist = data.maxDist; - console.log("got new max dist", maxDist) - setMultiplayerState((prev) => ({ - ...prev, - gameData: { - ...prev.gameData, - maxDist - } - })) - - } else if (data.type === "player") { - if (data.action === "remove") { - setMultiplayerState((prev) => ({ - ...prev, - gameData: { - ...prev.gameData, - players: prev.gameData.players.filter((p) => p.id !== data.id) - } - })) - } else if (data.action === "add") { - setMultiplayerState((prev) => ({ - ...prev, - gameData: { - ...prev.gameData, - players: [...prev.gameData.players, data.player] - } - })) - } - } else if (data.type === "place") { - const id = data.id; - if (id === multiplayerState.gameData.myId) { - setMultiplayerChatEnabled(true) - } - - const player = multiplayerState.gameData.players.find((p) => p.id === id); - if (player) { - player.final = data.final; - player.latLong = data.latLong; - } - } else if (data.type === "gameOver") { - setLatLong(null) - setGameOptions((prev) => ({ - ...prev, - extent: null - })) - - } else if (data.type === "gameShutdown") { - setScreen("home") - setMultiplayerState((prev) => { - return { - ...initialMultiplayerState, - connected: true, - nextGameQueued: prev.nextGameQueued, - playerCount: prev.playerCount, - guestName: prev.guestName - } - }); - setGameOptions((prev) => ({ - ...prev, - extent: null - })) - } else if (data.type === "gameJoinError" && multiplayerState.enteringGameCode) { - setMultiplayerState((prev) => { - return { - ...prev, - joinOptions: { - ...prev.joinOptions, - error: data.error, - progress: false - } - } - }) - } else if(data.type === 'generating') { - // location generation before round - setMultiplayerState((prev) => { - return { - ...prev, - gameData: { - ...prev.gameData, - generated: data.generated - } - } - }) - } else if(data.type === "friendReq") { - const from = data.name; - const id = data.id; - const toAccept = (closeToast) => { - ws.send(JSON.stringify({ type: 'acceptFriend', id })) - closeToast() - } - const toDecline = (closeToast) => { - ws.send(JSON.stringify({ type: 'declineFriend', id })) - closeToast() - } - const toastComponent = function({closeToast}){ - return ( -
- {text("youGotFriendReq", { from })} - - -   - -
- ) - } - - toast(toastComponent, { type: 'info', theme: "dark" }) - - - } else if(data.type === 'toast') { - toast(text(data.key, data), { type: data.toastType ?? 'info', theme: "dark", closeOnClick: data.closeOnClick ?? false, autoClose: data.autoClose ?? 5000 }) - } else if(data.type === 'invite') { - // code, invitedByName, invitedById - const { code, invitedByName, invitedById } = data; - - const toAccept = (closeToast) => { - ws.send(JSON.stringify({ type: 'acceptInvite', code, invitedById })) - closeToast() - } - const toDecline = (closeToast) => { - closeToast() - } - const toastComponent = function({closeToast}){ - return ( -
- {text("youGotInvite", { from: invitedByName })} - - -   - -
- ) - } - - toast(toastComponent, { type: 'info', theme: "dark", autoClose: 10000 }) - } else if(data.type === 'streak') { - const streak = data.streak; - - if(streak === 0) { - toast(text("streakLost"), { type: 'info', theme: "dark", autoClose: 5000, closeOnClick: true }) - } else if(streak === 1) { - toast(text("streakStarted"), { type: 'info', theme: "dark", autoClose: 5000, closeOnClick: true }) - } else { - toast(text("streakGained", { streak }), { type: 'info', theme: "dark", autoClose: 5000, closeOnClick: true }) - } - } - } - - // ws on disconnect - ws.onclose = () => { - setWs(null) - console.log("ws closed") - sendEvent("multiplayer_disconnect") - - setMultiplayerState((prev) => ({ - ...initialMultiplayerState, - error: text("connectionLost") - })); - } - - ws.onerror = () => { - setWs(null) - console.log("ws error") - sendEvent("multiplayer_disconnect") - - setMultiplayerState((prev) => ({ - ...initialMultiplayerState, - error: text("connectionLost") - })); - } - - - return () => { - ws.onmessage = null; - } - }, [ws, multiplayerState, timeOffset, gameOptions?.extent]); - - useEffect(() => { - if (multiplayerState?.connected && !multiplayerState?.inGame && multiplayerState?.nextGameQueued) { - handleMultiplayerAction("publicDuel"); - } - }, [multiplayerState, timeOffset]) - - useEffect(() => { - if (multiplayerState?.connected) { - handleMultiplayerAction("screen", screen); - } - }, [screen]); - - - // useEffect(() => { - // if (multiplayerState.inGame && multiplayerState.gameData?.state === "guess" && pinPoint) { - // // send guess - // console.log("pinpoint1", pinPoint) - // const pinpointLatLong = [pinPoint.lat, pinPoint.lng]; - // ws.send(JSON.stringify({ type: "place", latLong: pinpointLatLong, final: false })) - // } - // }, [multiplayerState, pinPoint]) - - function guessMultiplayer(send) { - if (!send) return; - if (!multiplayerState.inGame || multiplayerState.gameData?.state !== "guess" || !pinPoint) return; - const pinpointLatLong = [pinPoint.lat, pinPoint.lng]; - - ws.send(JSON.stringify({ type: "place", latLong: pinpointLatLong, final: true })) - } - - function sendInvite(id) { - if(!ws || !multiplayerState?.connected) return; - ws.send(JSON.stringify({ type: 'inviteFriend', friendId: id })) - } - - useEffect(() => { - try { - const streak = gameStorage.getItem("countryStreak"); - if (streak) { - setCountryStreak(parseInt(streak)) - } - - // preload/cache src.png and dest.png - const img = new Image(); - img.src = "/src.png"; - const img2 = new Image(); - img2.src = "/dest.png"; - } catch(e) {} - - }, []) - - function reloadBtnPressed() { - if(window.reloadLoc) { - window.reloadLoc() - } - } - - function crazyMidgame(adFinished = () => {}) { - if(window.inCrazyGames && window.CrazyGames.SDK.environment !== "disabled") { - try { - const callbacks = { - adFinished: () => adFinished(), - adError: (error) => adFinished(), - adStarted: () => console.log("Start midgame ad"), - }; - window.CrazyGames.SDK.ad.requestAd("midgame", callbacks); - } catch(e) {} - } else { - adFinished() - } - } - - function backBtnPressed(queueNextGame = false) { - - - - if (loading) setLoading(false); - - if(window.learnMode) { - // redirect to home - window.location.href = "/" - return; - } - - if(screen === "onboarding") { - setScreen("home") - setOnboarding(null) - setOnboardingCompleted(true) - gameStorage.setItem("onboarding", 'done') - - crazyMidgame() - - return; - } - - setStreetViewShown(false) - - if (multiplayerState?.inGame) { - ws.send(JSON.stringify({ - type: 'leaveGame' - })) - - if(inCrazyGames) { - try { - window.CrazyGames.SDK.game.hideInviteButton(); - } catch(e) {} - } - - setMultiplayerState((prev) => { - return { - ...prev, - nextGameQueued: queueNextGame === true - } - }) - setScreen("home") - - if(["getready", "guess"].includes(multiplayerState?.gameData?.state)) { - crazyMidgame() - } - - } else if ((multiplayerState?.creatingGame || multiplayerState?.enteringGameCode) && multiplayerState?.connected) { - - setMultiplayerState((prev) => { - return { - ...initialMultiplayerState, - connected: true, - playerCount: prev.playerCount, - guestName: prev.guestName - - } - }) - setScreen("home") - - } else if(multiplayerState?.gameQueued) { - console.log("gameQueued") - ws.send(JSON.stringify({ type: "leaveQueue" })) - - setMultiplayerState((prev) => { - return { - ...prev, - gameQueued: false - } - }); - setScreen("home") - - } else { - setScreen("home"); - setGameOptions((prev) => ({ - ...prev, - extent: null - })) - clearLocation(); - - if(screen === "singleplayer") { - crazyMidgame() - - } - } - } - - function clearLocation() { - setLatLong({ lat: 0, long: 0 }) - setStreetViewShown(false) - setShowAnswer(false) - setPinPoint(null) - setHintShown(false) - } - - function loadLocation() { - if (loading) return; - - console.log("loading location") - setLoading(true) - setShowAnswer(false) - setPinPoint(null) - setLatLong(null) - setHintShown(false) - if(screen === "onboarding") { - setLatLong(onboarding.locations[onboarding.round - 1]); - let options = JSON.parse(JSON.stringify(onboarding.locations[onboarding.round - 1].otherOptions)); - options.push(onboarding.locations[onboarding.round - 1].country) - // shuffle - options = options.sort(() => Math.random() - 0.5) - setOtherOptions(options) - } else { - function defaultMethod() { - findLatLongRandom(gameOptions).then((latLong) => { - setLatLong(latLong) - }); - } - function fetchMethod() { - console.log("fetching") - fetch((gameOptions.location==="all")?`/${window?.learnMode ? 'clue': 'all'}Countries.json`:`/mapLocations/${gameOptions.location}`).then((res) => res.json()).then((data) => { - if(data.ready) { - // this uses long for lng - for(let i = 0; i < data.locations.length; i++) { - if(data.locations[i].lng && !data.locations[i].long) { - data.locations[i].long = data.locations[i].lng; - delete data.locations[i].lng; - } - } - - setAllLocsArray(data.locations) - - if(gameOptions.location === "all") { - const loc = data.locations[0] - setLatLong(loc) - } else { - let loc = data.locations[Math.floor(Math.random() * data.locations.length)]; - - while(loc.lat === latLong.lat && loc.long === latLong.long) { - loc = data.locations[Math.floor(Math.random() * data.locations.length)]; - } - - setLatLong(loc) - if(data.name) { - - // calculate extent (for openlayers) - const mappedLatLongs = data.locations.map((l) => fromLonLat([l.long, l.lat], 'EPSG:4326')); - let extent = boundingExtent(mappedLatLongs); - console.log("extent", extent) - // convert extent from EPSG:4326 to EPSG:3857 (for openlayers) - - setGameOptions((prev) => ({ - ...prev, - communityMapName: data.name, - official: data.official ?? false, - maxDist: data.maxDist ?? 20000, - extent: extent - })) - - } - } - - } else { - if(gameOptions.location !== "all") { - toast(text("errorLoadingMap"), { type: 'error' }) - } - defaultMethod() - } - }).catch((e) => { - console.error(e) - toast(text("errorLoadingMap"), { type: 'error' }) - defaultMethod() - }); - } - if(gameOptions.countryMap && gameOptions.official) { - defaultMethod() - } else { - if(allLocsArray.length===0) { - fetchMethod() - } else if(allLocsArray.length>0) { - const locIndex = allLocsArray.findIndex((l) => l.lat === latLong.lat && l.long === latLong.long); - if((locIndex === -1) || allLocsArray.length === 1) { - console.log("could not find location in array", locIndex, allLocsArray) - fetchMethod() - } else { - if(gameOptions.location === "all") { - const loc = allLocsArray[locIndex+1] ?? allLocsArray[0]; - setLatLong(loc) - } else { - // prevent repeats: remove the prev location from the array - setAllLocsArray((prev) => { - const newArr = prev.filter((l) => l.lat !== latLong.lat && l.long !== latLong.long) - - - // community maps are randomized - const loc = newArr[Math.floor(Math.random() * newArr.length)]; - - - setLatLong(loc) - return newArr; - }) - - } - } - - } -} - } - - } - - function onNavbarLogoPress() { - if(screen === "onboarding") return; - - if (screen !== "home" && !loading) { - if (screen==="multiplayer" && multiplayerState?.connected && !multiplayerState?.inGame) { - return; - } - if (!multiplayerState?.inGame) loadLocation() - else if (multiplayerState?.gameData?.state === "guess") { - - } - } - } - - const ChatboxMemo = React.useMemo(() => setMultiplayerChatOpen(!multiplayerChatOpen)} enabled={multiplayerChatEnabled} myId={multiplayerState?.gameData?.myId} inGame={multiplayerState?.inGame} />, [multiplayerChatOpen, multiplayerChatEnabled, ws, multiplayerState?.gameData?.myId, multiplayerState?.inGame]) - - // Send pong every 10 seconds if websocket is connected - useEffect(() => { - const pongInterval = setInterval(() => { - if (ws && ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify({ type: 'pong' })); - } - }, 10000); // Send pong every 10 seconds - - return () => clearInterval(pongInterval); - }, [ws]); - const panoramaRef = useRef(null); - const [showPanoOnResult, setShowPanoOnResult] = useState(false); - - useEffect(() => { - // const map = new google.maps.Map(document.getElementById("map"), { - // center: fenway, - // zoom: 14, - // }); - if(legacyMapLoader) { - setLoading(false) - - // kill the element that has div[dir="ltr"] - const elem = document.querySelector('div[dir="ltr"]'); - console.log("elem", elem) - - return; - } - if(!latLong || (latLong.lat === 0 && latLong.long === 0)) return; - if(!document.getElementById("googlemaps")) return; - - if(!panoramaRef.current) { - - - - panoramaRef.current = new google.maps.StreetViewPanorama( - document.getElementById("googlemaps"), - { - position: { lat: latLong.lat, lng: latLong.long }, - pov: { - heading: 0, - pitch: 0, - }, - motionTracking: false, - linksControl: gameOptions?.nm ? false:true, - clickToGo: gameOptions?.nm ? false:true, - - panControl: gameOptions?.npz ? false:true, - zoomControl: gameOptions?.npz ? false:true, - showRoadLabels: gameOptions?.showRoadName===true?true:false, - disableDefaultUI: true, - }, - ); - - window.reloadLoc = () => { - panoramaRef.current.setPosition({ lat: latLong.lat, lng: latLong.long }); - } - window.panorama = panoramaRef.current; - } else { - - panoramaRef.current.setPosition({ lat: latLong.lat, lng: latLong.long }); - - window.reloadLoc = () => { - panoramaRef.current.setPosition({ lat: latLong.lat, lng: latLong.long }); - } - } - - - // pano onload - - function fixPitch() { - // point towards road - - panoramaRef.current.setPov(panoramaRef.current.getPhotographerPov()); - panoramaRef.current.setZoom(0); - - // if localhost log the location - if(window.location.hostname === "localhost") { - console.log("[DEV] country:", latLong.country); - } - } - - - let loaded = false; - function onChangeListener(e) { - if(loaded) return; - loaded = true; - - setTimeout(() => { - setLoading(false) - setStreetViewShown(true) - // Select all tags with the attribute http-equiv="origin-trial" - const metaTags = document.querySelectorAll('meta[http-equiv="origin-trial"]'); - - // Loop through the NodeList and remove each tag - metaTags.forEach(meta => meta.remove()); - }, 500) - fixBranding(); - - fixPitch(); - } - panoramaRef.current.addListener("pano_changed", onChangeListener); - - - return () => { - if(!panoramaRef.current) return; - google.maps.event.clearListeners(panoramaRef.current, 'pano_changed'); - } - - - }, [latLong, gameOptions?.nm, gameOptions?.npz, gameOptions?.showRoadName, legacyMapLoader]) - -// useEffect(() => { -// //!(latLong && multiplayerState?.gameData?.state !== 'end')) || (!streetViewShown || loading || (showAnswer && !showPanoOnResult) || (multiplayerState?.gameData?.state === 'getready') || !latLong) -// // debug this condition: -// console.log("isHidden", !(latLong && multiplayerState?.gameData?.state !== 'end')) || (!streetViewShown || loading || (showAnswer && !showPanoOnResult) || (multiplayerState?.gameData?.state === 'getready') || !latLong) - -// console.log("latLong", latLong) -// console.log("multiplayerState?.gameData?.state", multiplayerState?.gameData?.state) -// console.log("streetViewShown", streetViewShown) -// console.log("loading", loading) -// console.log("showAnswer", showAnswer) -// console.log("showPanoOnResult", showPanoOnResult) - - -// }, [latLong, multiplayerState?.gameData?.state, streetViewShown, loading, showAnswer, showPanoOnResult]) - - useEffect(() => { - - if(panoramaRef.current) { - panoramaRef.current.setOptions({ - linksControl: gameOptions?.nm ? false:true, - clickToGo: gameOptions?.nm ? false:true, - - panControl: true, - - zoomControl: gameOptions?.npz ? false:true, - showRoadLabels: gameOptions?.showRoadName===true?true:false, - }) - } - }, [gameOptions?.nm, gameOptions?.npz, gameOptions?.showRoadName]) - - - return ( - <> - - - - - - - setMerchModal(false)} session={session} /> - - {ChatboxMemo} - - -
-
-
-

{text("videoAdThanks")}
{text("enjoyGameplay")}

-
-
-
-
- -{screen === "home" && !mapModal && !merchModal && !friendsModal && !accountModalOpen && ( -
-
- { !isApp && ( - <> - { !inCrazyGames && ( - <> - - - - - )} - - - - - )} - - -
-
- )} - -
- -
- - -
- { latLong && latLong?.lat && latLong?.long && legacyMapLoader ? ( -<> - - - -{/* put something in the top left to cover the address */} -
- - - - - ) : ( -
- )} - - - - - setFriendsModal(true)} loginQueued={loginQueued} setLoginQueued={setLoginQueued} inGame={multiplayerState?.inGame || screen === "singleplayer"} openAccountModal={() => setAccountModalOpen(true)} session={session} shown={true} reloadBtnPressed={reloadBtnPressed} backBtnPressed={backBtnPressed} setGameOptionsModalShown={setGameOptionsModalShown} onNavbarPress={() => onNavbarLogoPress()} gameOptions={gameOptions} screen={screen} multiplayerState={multiplayerState} /> - -{/* merch button */} -{screen === "home" && !mapModal && session && session?.token?.secret && !inCrazyGames && !session?.token?.supporter && ( - -)} - - -
- - - { onboardingCompleted===null ? ( - <> - - - ) : ( - <> - -
- { onboardingCompleted && ( -

WorldGuessr

- )} - -
- - { onboardingCompleted && ( - - <> -
- - {/* { - if (!loading) setScreen("singleplayer") - }} /> */} - - {/* {text("playOnline")} */} - - - - {/* {text("playFriends")} */} -
- - -
-
- -
-{/* { !isApp && ( - <> - - - - - )} - - */} - { !inCrazyGames && ( - - )} -
- - - )} -
- -
-
- { !loading && screen === "home" && !inCrazyGames &&(!session?.token?.supporter) && ( - - )} -
-
-
- - )} -
- - -
- - - {setMapModal(false);setGameOptionsModalShown(false)}} text={text} - customChooseMapCallback={(gameOptionsModalShown&&screen==="singleplayer")?(map)=> { - console.log("map", map) - openMap(map.countryMap||map.slug); - setGameOptionsModalShown(false) - }:null} - showAllCountriesOption={(gameOptionsModalShown&&screen==="singleplayer")} - singleplayer={screen==="singleplayer"} - gameOptions={gameOptions} setGameOptions={setGameOptions} /> - - setSettingsModal(false)} /> - - setFriendsModal(false)} session={session} canSendInvite={ - // send invite if in a private multiplayer game, dont need to be host or in game waiting just need to be in a private game - multiplayerState?.inGame && !multiplayerState?.gameData?.public - } sendInvite={sendInvite} /> - - {screen === "singleplayer" &&
- -
} - - {screen === "onboarding" && onboarding?.round &&
- -
} - - {screen === "onboarding" && onboarding?.completed &&
-
- { - try { - gameStorage.setItem("onboarding", 'done') - } catch(e) {} - setOnboarding((prev)=>{ - return { - ...prev, - finalOnboardingShown: true - } - }) - }} shown={!onboarding?.finalOnboardingShown} /> - { - if(onboarding) sendEvent("tutorial_end"); - - setOnboarding(null) - if(!window.location.search.includes("app=true") && !inCrazyGames) { - setShowSuggestLoginModal(true) - } - setScreen("home") - }}/> -
-
-} - - {screen === "multiplayer" &&
- -
} - - {multiplayerState.inGame && ["guess", "getready", "end"].includes(multiplayerState.gameData?.state) && ( - { }} gameOptions={{ location: "all", maxDist: 20000, extent: gameOptions?.extent }} setGameOptions={() => { }} showAnswer={(multiplayerState?.gameData?.curRound !== 1) && multiplayerState?.gameData?.state === 'getready'} setShowAnswer={guessMultiplayer} /> - )} - - - - -
- - ) -} +import LocalizedHome from "@/components/localizedHome"; +export default function IndexPage() { + return ; +} \ No newline at end of file diff --git a/pages/leaderboard/index.js b/pages/leaderboard/index.js index 3dcd7554..a4ea035a 100644 --- a/pages/leaderboard/index.js +++ b/pages/leaderboard/index.js @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import Head from 'next/head'; import { useSession } from '@/components/auth/auth'; import { useTranslation } from '@/components/useTranslations' +import config from '@/clientConfig'; const Leaderboard = ({ }) => { const { t: text } = useTranslation("common"); @@ -17,6 +18,7 @@ const Leaderboard = ({ }) => { }, []); useEffect(() => { + const configData = config(); const fetchData = async () => { try { const params = { @@ -24,7 +26,7 @@ const Leaderboard = ({ }) => { pastDay: pastDay ? true : undefined }; const queryParams = new URLSearchParams(params).toString(); - const response = await fetch(`/api/leaderboard${queryParams ? `?${queryParams}` : ''}`); + const response = await fetch(configData.apiUrl+`/api/leaderboard${queryParams ? `?${queryParams}` : ''}`); const data = await response.json(); setLeaderboardData(data); } catch (error) { diff --git a/pages/learn.js b/pages/learn.js index 533444a5..94abb657 100644 --- a/pages/learn.js +++ b/pages/learn.js @@ -7,13 +7,15 @@ const roboto = Roboto({ subsets: ['cyrillic'], weight: "400", style: 'normal' }) import NextImage from "next/image"; import Link from 'next/link'; import Head from 'next/head'; +import config from '@/clientConfig'; export default function Learn({ locale }) { const [clueCnt, setClueCnt] = React.useState(0); const [displayCount, setDisplayCount] = React.useState(0); React.useEffect(() => { - fetch('/api/clues/getCluesCount').then(res => res.json()).then(data => { + const configData = config(); + fetch(configData.apiUrl+'/api/clues/getCluesCount').then(res => res.json()).then(data => { setClueCnt(data.count); }); }, []); diff --git a/pages/map/[slug].js b/pages/map.js similarity index 76% rename from pages/map/[slug].js rename to pages/map.js index 6a7af3fa..2250add9 100644 --- a/pages/map/[slug].js +++ b/pages/map.js @@ -4,6 +4,7 @@ import { useRouter } from 'next/router'; import styles from '@/styles/MapPage.module.css'; // Import CSS module for styling import Navbar from '@/components/ui/navbar'; import { useTranslation } from '@/components/useTranslations' +import config from '@/clientConfig'; // export async function getServerSideProps(context) { @@ -68,17 +69,45 @@ export default function MapPage({ }) { const [locationUrls, setLocationUrls] = useState([]); const [fadeClass, setFadeClass] = useState(styles.iframe); const { t: text } = useTranslation('common'); + const [mapData, setMapData] = useState({}); + + // const mapData = { + // name: "United States", + // description_short: "Explore the United States of America", + // description_long: "Explore the United States of America on WorldGuessr, a free GeoGuessr clone. This map features locations from all 50 states, including landmarks, cities, and natural wonders.", + // created_by: "WorldGuessr", + // created_at: "1 year", + // in_review: false, + // rejected: false, + // countryCode: "US", + // }; + + useEffect(() => { + const {apiUrl} = config() + // slug can either be in two forms + // /map/:slug (path param) + // /map?s=slug (query param) + + const queryParams = new URLSearchParams(window.location.search); + const slug = router.query.s || router.query.slug || queryParams.get('s') || queryParams.get('slug'); + + if (!slug) return; + + console.log('fetching map data for', slug); + fetch(apiUrl+`/api/map/publicData?slug=${slug}`).then(async res => { + if (res.ok) { + const data = await res.json(); + console.log('fetched map data:', data); + setMapData(data.mapData); + } else { + console.error('Failed to fetch map data:', res); + if(res.status === 404) { + router.push('/404'); + } + } + }); + }, []); - const mapData = { - name: "United States", - description_short: "Explore the United States of America", - description_long: "Explore the United States of America on WorldGuessr, a free GeoGuessr clone. This map features locations from all 50 states, including landmarks, cities, and natural wonders.", - created_by: "WorldGuessr", - created_at: "1 year", - in_review: false, - rejected: false, - countryCode: "US", - }; useEffect(() => { if (!mapData.data) return; @@ -106,8 +135,8 @@ export default function MapPage({ }) { return (
- {mapData.name + " - Play Free on WorldGuessr"} - + {mapData?.name + " - Play Free on WorldGuessr"} + @@ -122,6 +151,9 @@ export default function MapPage({ }) {
+ {mapData?.name && ( + <> + {mapData.in_review && (

⏳ This map is currently under review.

@@ -134,6 +166,9 @@ export default function MapPage({ }) {
)} + + )} +

WorldGuessr

@@ -143,6 +178,16 @@ export default function MapPage({ }) {
+ {!mapData.name && ( +
+
+

Loading map...

+
+
+ )} + + + { mapData.name && (
{locationUrls.length > 0 && ( @@ -166,6 +211,10 @@ export default function MapPage({ }) {

{mapData.description_short}

+ )} + +{ mapData?.name && ( + <> @@ -202,6 +251,8 @@ export default function MapPage({ }) { )}

+ +)} ); diff --git a/pages/maps.js b/pages/maps.js index 910aaad5..ab4368cb 100644 --- a/pages/maps.js +++ b/pages/maps.js @@ -1,14 +1,22 @@ // pages/maps.js -import React from "react"; +import React, { useEffect } from "react"; import { useRouter } from "next/router"; import MapView from "@/components/maps/mapView"; import { useTranslation } from '@/components/useTranslations' +import config from "@/clientConfig"; + +import { useSession } from "@/components/auth/auth"; +import Head from "next/head"; export default function MapsPage({ }) { const router = useRouter(); const { t: text } = useTranslation("common"); const { data: session, status } = useSession(); + useEffect(() => { + window.cConfig = config(); + }, []); + const handleMapClick = (map) => { router.push(`/map/${map.slug}`); }; @@ -56,6 +64,3 @@ const styles = { }, }; - -import { useSession } from "@/components/auth/auth"; -import Head from "next/head"; \ No newline at end of file diff --git a/pages/ru.js b/pages/ru.js new file mode 100644 index 00000000..9d319fee --- /dev/null +++ b/pages/ru.js @@ -0,0 +1,5 @@ +import LocalizedHome from "@/components/localizedHome"; + +export default function IndexPage() { + return ; +} \ No newline at end of file diff --git a/server.js b/server.js index bd23d8db..da4cd949 100644 --- a/server.js +++ b/server.js @@ -51,23 +51,58 @@ import validateSecret from './components/utils/validateSecret.js'; import express from 'express'; var app = express(); +// disable cors +import cors from 'cors'; +app.use(cors()); + app.use(express.json()); // Setup your /api routes const apiFolder = path.join(__dirname, 'api'); -fs.readdirSync(apiFolder).forEach(file => { - // make sure tis not a directory - if(fs.lstatSync(path.join(apiFolder, file)).isDirectory()) { - return; - } - console.log('Loading API route', file); +let directories = []; + +// fs.readdirSync(apiFolder).forEach(file => { +// // make sure tis not a directory +// if(fs.lstatSync(path.join(apiFolder, file)).isDirectory()) { +// directories.push(file); +// return; +// } +// console.log('Loading API route', file); + +// const routePath = './api/' + file.split('.')[0]+'.js'; +// const webPath = '/api/' + file.split('.')[0]; +// console.log('Loading', routePath, 'at', webPath); +// import(routePath).then(module => { +// app.all(webPath, module.default); // Use .default because ES module exports default +// }); +// }); - const routePath = './api/' + file.split('.')[0]+'.js'; - const webPath = '/api/' + file.split('.')[0]; - import(routePath).then(module => { - app.all(webPath, module.default); // Use .default because ES module exports default +function loadFolder(folder, subdir = '') { + fs.readdirSync(folder).forEach(file => { + const filePath = path.join(folder, file); + if(fs.lstatSync(filePath).isDirectory()) { + loadFolder(filePath, subdir + file + '/'); + return; + } + if(!file.endsWith('.js')) { + return; + } + + const routePath = './api/' + subdir + file.split('.')[0]+'.js'; + const webPath = '/api/' + subdir + file.split('.')[0]; + import(routePath).then(module => { + app.all(webPath, ( req, res ) => { + console.log('Handling', webPath, req.method); + const handlerResponse = module.default(req, res); + // console.log('Handler response', handlerResponse); + // return res.status(400).json({ message: 'Invalid input' }); + }); + + }); }); -}); +} + +loadFolder(apiFolder); const filter = new Filter(); filter.removeWords('damn') @@ -268,7 +303,7 @@ const generateMainLocations = async () => { try { const batchResults = await Promise.all(batchPromises); allLocations.push(...batchResults); - console.log('Generated', allLocations.length, '/', locationCnt); + if(allLocations.length % 100 === 0) console.log('Generated', allLocations.length, '/', locationCnt); if(allLocations.length === locationCnt) { console.log('Finished generating all locations'); while (true) { @@ -355,6 +390,60 @@ app.get('/clueCountries.json', (req, res) => { } }); + // // check if in format /mapLocations/:slug + // const mapLocMatch = pathname.includes('/mapLocations/'); + // if (mapLocMatch) { + // const slug = pathname.split('/mapLocations/')[1].split('?')[0]; + // const map = await MapModel.findOne({ slug }); + // if (!map) { + // return app.render(req, res, '/404', query); + // } + // res.end(JSON.stringify({ + // ready: true, + // locations: map.data, + // name: map.name, + // official: map.official, + // maxDist: map.maxDist + // })); + // return; + // } + + // if(!httpEnabled) { + // // send 404 + // return app.render(req, res, '/404', query); + // } + + // const mapPlayMatch = pathname.includes('/mapPlay/'); + // if (mapPlayMatch && req.method === 'POST') { + // // make suere POST + // const slug = pathname.split('/mapPlay/')[1].split('?')[0]; + // recentPlays[slug] = (recentPlays[slug] || 0) + 1; + // res.end('ok'); + // } + + app.get('/mapLocations/:slug', async (req, res) => { + const slug = req.params.slug; + const map = await MapModel.findOne({ slug }); + if (!map) { + return res.status(404).json({ message: 'Map not found' }); + } + res.json({ + ready: true, + locations: map.data, + name: map.name, + official: map.official, + maxDist: map.maxDist + }); + }); + + app.post('/mapPlay/:slug', async (req, res) => { + const slug = req.params.slug; + recentPlays[slug] = (recentPlays[slug] || 0) + 1; + res.send('ok'); + }); + + + function make6DigitCode() {