diff --git a/twitch/Main.py b/twitch/Main.py
deleted file mode 100644
index 03460f7..0000000
--- a/twitch/Main.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import PIL.Image, PIL.ImageDraw, PIL.ImageFont
-import os
-import sys
-
-
-font = PIL.ImageFont.truetype(os.path.join(os.path.dirname(__file__), './customAlertsMedia/ibmplex.ttf'), 20)
-nFrames = 60
-width = 500
-height = 500
-if __name__ == "__main__":
- tpe = sys.argv[1]
- username = sys.argv[2]
- description = sys.argv[3]
- target = sys.argv[4]
-
- animated_gif = Image.open("./customAlertsMedia/"+tpe+".gif")
-
- frames = []
- for frame in ImageSequence.Iterator(animated_gif):
- frame = frame.convert('RGB')
- d = PIL.ImageDraw.Draw(frame)
- d.text((0, 0), description, font=font, fill=(255, 255, 255))
- d.text((0, 100), username, font=font, fill=(255, 255, 255))
- frames.append(frame)
- frames[0].save(target+tpe+".gif", save_all=True, append_images=frames[1:], duration=100, loop=0, optimize=0, comment="")
- os.popen('cp ./customAlertsMedia/'+tpe+'.mp3 '+target+tpe+'.mp3')
\ No newline at end of file
diff --git a/twitch/customAlerts.js b/twitch/customAlerts.js
index 7f2a8ae..7320f06 100644
--- a/twitch/customAlerts.js
+++ b/twitch/customAlerts.js
@@ -1,47 +1,92 @@
(function() {
- function alert(username, desc, tpe) {
- var pb = new java.lang.ProcessBuilder("/usr/bin/python3", "./Main.py", tpe, username, desc, "/opt/PhantomBot/config/gif-alerts/");
- var proc = pb.start();
- proc.waitFor();
-
- $.panelsocketserver.alertImage(tpe+".gif?noise=" + Math.floor(Math.random() * 1000 + 1));
- }
- $.bind('twitchFollow', function(event) {
- var follower = event.getFollower();
- alert(follower.toUpperCase(), "NEW FOLLOWER", "follow");
+ function sendData(tpe, d) {
+ $.panelsocketserver.sendJSONToAll(JSON.stringify({
+ 'eventFamily': 'calert',
+ 'eventType': tpe,
+ 'data': d
+ }));
+ }
+
+ $.bind('twitchFollow', function(event) {
+ sendData('follow',{ 'user': event.getFollower()});
+ });
+ $.bind('twitchSubscriber', function(event) {
+ sendData('subscribe', {
+ 'user': event.getSubscriber(),
+ 'isReSub': false,
+ 'months': 0,
+ 'tier': event.getPlan() / 1000,
+ 'message': event.getMessage()
});
- $.bind('twitchSubscriber', function(event) {
- var subscriber = event.getSubscriber();
- alert(subscriber.toUpperCase(), "NEW SUBSCRIBER", "subscribe");
+ });
+ $.bind('twitchReSubscriber', function(event) {
+ sendData('subscribe', {
+ 'user': event.getReSubscriber(),
+ 'isReSub': true,
+ 'months': event.getMonths(),
+ 'tier': event.getPlan() / 1000,
+ 'message': event.getMessage()
+ });
+ });
+ $.bind('twitchSubscriptionGift', function(event) {
+ sendData('gift', {
+ 'recipient': event.getRecipient(),
+ 'user': event.getUsername(),
+ 'months': event.getMonths(),
+ 'isReSub': (parseInt(event.getMonths()) > 1),
+ 'tier': event.getPlan() / 1000
});
-
- $.bind('command', function(event) {
- const sender = "" + event.getSender().toLowerCase(),
- command = event.getCommand(),
- args = event.getArgs(),
- action = args[0];
-
- if (command.equalsIgnoreCase('calert')) {
- if (!action) {
- $.say($.whisperPrefix(sender) + $.lang.get('calert.help', ' Use "!calert [follow | subsribe | donation] user" to trigger alert.'));
- } else if (action.equalsIgnoreCase('follow')) {
- alert(args[1].toUpperCase(), "NEW FOLLOWER", "follow");
- } else if (action.equalsIgnoreCase('subscribe')) {
- alert(args[1].toUpperCase(), "NEW SUBSCRIBER", "subscribe");
- } else if (action.equalsIgnoreCase('donation')) {
- alert(args[1].toUpperCase(), "NEW DONATION", "donation");
- } else {
- $.say($.whisperPrefix(sender) + $.lang.get('calert.help', ' Use "!calert [follow | subsribe | donation] user" to trigger alert.'));
- }
- }
});
- $.bind('initReady', function() {
- $.registerChatCommand('./custom/custom/customAlerts.js', 'calert');
- $.registerChatSubcommand('calert', 'follow', 2);
- $.registerChatSubcommand('calert', 'subscribe', 2);
- $.registerChatSubcommand('calert', 'donation', 2);
+ $.bind('twitchMassSubscriptionGifted', function(event) {
+ sendData('mgift', {
+ 'user': event.getUsername(),
+ 'amount': event.getAmount(),
+ 'tier': event.getPlan() / 1000
});
- })();
-
\ No newline at end of file
+ });
+
+ $.bind('twitchRaid', function(event) {
+ sendData('raid',{
+ 'user': event.getUsername(),
+ 'viewers': event.getViewers(),
+ });
+ });
+
+ $.bind('twitchBits', function (event) {
+ sendData('bits', {
+ 'user': event.getUsername(),
+ 'amount': event.getBits(),
+ 'message': event.getMessage()
+ });
+ });
+
+ $.bind('command', function(event) {
+ const sender = "" + event.getSender().toLowerCase(),
+ command = event.getCommand(),
+ args = event.getArgs(),
+ action = args[0];
+
+ if (command.equalsIgnoreCase('calert')) {
+ if (!action) {
+ $.say($.whisperPrefix(sender) + $.lang.get('calert.help', ' Use "!calert [follow | subsribe | donation] user" to trigger alert.'));
+ } else if (action.equalsIgnoreCase('follow')) {
+ alert(args[1].toUpperCase(), "NEW FOLLOWER", "follow");
+ } else if (action.equalsIgnoreCase('subscribe')) {
+ alert(args[1].toUpperCase(), "NEW SUBSCRIBER", "subscribe");
+ } else if (action.equalsIgnoreCase('donation')) {
+ alert(args[1].toUpperCase(), "NEW DONATION", "donation");
+ } else {
+ $.say($.whisperPrefix(sender) + $.lang.get('calert.help', ' Use "!calert [follow | subsribe | donation] user" to trigger alert.'));
+ }
+ }
+});
+
+ $.bind('initReady', function() {
+ $.registerChatCommand('./custom/custom/customAlerts.js', 'calert');
+ $.registerChatSubcommand('calert', 'follow', 2);
+ $.registerChatSubcommand('calert', 'subscribe', 2);
+ $.registerChatSubcommand('calert', 'donation', 2);
+ });
+})();
diff --git a/twitch/customAlertsMedia/subscribe.gif b/twitch/customAlertsMedia/subscribe.gif
deleted file mode 100644
index d6ce6fe..0000000
Binary files a/twitch/customAlertsMedia/subscribe.gif and /dev/null differ
diff --git a/web/alert/index.html b/web/alert/index.html
new file mode 100644
index 0000000..f41510d
--- /dev/null
+++ b/web/alert/index.html
@@ -0,0 +1,62 @@
+
+
+
+
+ CustomAlerts
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/alert/index.js b/web/alert/index.js
new file mode 100644
index 0000000..9a68d65
--- /dev/null
+++ b/web/alert/index.js
@@ -0,0 +1,157 @@
+const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
+
+const volume = parseInt((new URLSearchParams(window.location.search)).get('vol')) || 100
+
+$(async function () {
+ const webSocket = window.socket;
+ const announceQueue = [];
+
+ const playAudio = (src) =>{
+ return new Promise(resolve =>{
+ var audio = new Audio();
+ $(audio).on("loadedmetadata", function() {
+ resolve(audio);
+ });
+ audio.volume = volume/100.0;
+ audio.src = src;
+ audio.play().catch((err)=>{
+ if(err.toString().startsWith('NotAllowedError')){
+ $('body').append($('', {
+ 'html': 'Enable Audio Hook',
+ 'style': 'top:80%;left:50%; position: absolute; font-size: 30px; transform: translateX(-50%) translateY(-50%);'
+ }).on('click', function(){$(this).remove()}));
+ }
+ });
+ });
+ }
+
+
+ const renderEl = async (pic, music, message, detail) => {
+ let node = document.getElementById('main');
+ let nodeIm = document.getElementById('img');
+ node.innerHTML = message;
+ nodeIm.src = pic
+ let audio = await playAudio(music);
+ node.style.opacity = 1;
+ nodeIm.style.opacity = 1;
+ await sleep(audio.duration*1000 - 1000);
+ node.style.opacity = 0;
+ nodeIm.style.opacity = 0;
+ }
+
+ const renderStart = async (an) => {
+ await renderEl('','./media/sonar.ogg',
+ `The Stream Started`, `...`)
+ }
+
+ const renderFollow = async (an) => {
+ await renderEl('./media/donate.gif','./media/sonar.ogg',
+ `${an.user} joined the squad !`, `...`)
+ }
+
+ const renderSubscribe = async (an) => {
+ // TTS: an.details.message
+ let v = an.details.isReSub ? 'resubscribed' : 'subscribed'
+ let detail = an.details.months == 0 ? '' : `for ${an.details.months} months `
+ let message = `${an.user} ${v} ${detail}!`
+ await renderEl('./media/follow.gif','./media/follow.mp3',
+ message,
+ `...`)
+ }
+
+ const renderGift = async (an) => {
+ await renderEl('./media/follow.gif','./media/follow.mp3',
+ `${an.user} gifted ${an.details.recipient} a sub !`,
+ `...`)
+ }
+
+ const renderMassGift = async (an) => {
+ await renderEl('./media/follow.gif','./media/follow.mp3',
+ `${an.user} gifted ${an.details.amount} subs to the community !`,
+ `...`)
+ }
+
+ const renderRaid = async (an) => {
+ await renderEl('./media/raid.gif','./media/subscribe.mp3',
+ `${an.user} is Raiding with a party of ${an.details.viewers} !`,
+ `...`)
+ }
+
+ const renderBits = async (an) => {
+ // TTS: an.details.message
+ await renderEl('','',
+ `${an.user} cheered ${an.details.amount} bitties!`,
+ `...`)
+ }
+
+ const renderAnnouncement = async (an) => {
+ if(an.tpe=='follow') await renderFollow(an)
+ else if(an.tpe=='subscribe') await renderSubscribe(an)
+ else if(an.tpe=='gift') await renderGift(an)
+ else if(an.tpe=='mgift') await renderMassGift(an)
+ else if(an.tpe=='raid') await renderRaid(an)
+ else if(an.tpe=='bits') await renderBits(an)
+ else if(an.tpe=='start') await renderStart(an)
+ }
+
+ class Announcement {
+ constructor(data){
+ this.user = data.user;
+ this.tpe = data.tpe;
+ this.details = data.details || {};
+ }
+ }
+
+
+ announceQueue.push(new Announcement({user:"-",tpe:"start"}));
+ const main = async () => {
+ while(true){
+ if(announceQueue.length>0)
+ await renderAnnouncement(announceQueue.shift());
+ await sleep(1000);
+ }
+ }
+ main();
+
+ const handleSocketMessage = async (e)=>{
+ try {
+ let rawMessage = e.data,
+ message = JSON.parse(rawMessage);
+
+ if(!message.hasOwnProperty('eventFamily') || message.eventFamily != 'calert' ||
+ !message.hasOwnProperty('eventType') || !message.hasOwnProperty('data'))
+ return;
+ if(message.eventType == 'follow') {
+ announceQueue.push(new Announcement({user:message.data.user, tpe:'follow'}))
+ }else if(message.eventType == 'subscribe') {
+ announceQueue.push(new Announcement({user:message.data.user, tpe:'subscribe', details:message.data.details}))
+ }else if(message.eventType == 'gift') {
+ announceQueue.push(new Announcement({user:message.data.user, tpe:'gift', details:message.data.details}))
+ }else if(message.eventType == 'mgift') {
+ announceQueue.push(new Announcement({user:message.data.user, tpe:'mgift', details:message.data.details}))
+ }else if(message.eventType == 'raid') {
+ announceQueue.push(new Announcement({user:message.data.user, tpe:'raid', details:message.data.details}))
+ }else if(message.eventType == 'bits') {
+ announceQueue.push(new Announcement({user:message.data.user, tpe:'bits'}))
+ }else{
+ console.log("unhandled:", message.eventType)
+ }
+ } catch (ex) {
+ console.log(ex)
+ }
+ };
+
+
+ jQuery(async ()=>{
+
+ try{
+ socket.addFamilyHandler("calert", handleSocketMessage);
+ if(socket){
+ while(socket.getReadyState() === 0){
+ await new Promise(r => setTimeout(r, 500));
+ }
+ }
+ }catch(e) {console.log(e)}
+ })
+
+});
\ No newline at end of file
diff --git a/twitch/customAlertsMedia/donate.gif b/web/alert/media/donate.gif
similarity index 100%
rename from twitch/customAlertsMedia/donate.gif
rename to web/alert/media/donate.gif
diff --git a/twitch/customAlertsMedia/follow.gif b/web/alert/media/follow.gif
similarity index 100%
rename from twitch/customAlertsMedia/follow.gif
rename to web/alert/media/follow.gif
diff --git a/twitch/customAlertsMedia/follow.mp3 b/web/alert/media/follow.mp3
similarity index 100%
rename from twitch/customAlertsMedia/follow.mp3
rename to web/alert/media/follow.mp3
diff --git a/twitch/customAlertsMedia/ibmplex.ttf b/web/alert/media/ibmplex.ttf
similarity index 100%
rename from twitch/customAlertsMedia/ibmplex.ttf
rename to web/alert/media/ibmplex.ttf
diff --git a/twitch/customAlertsMedia/raid.gif b/web/alert/media/raid.gif
similarity index 100%
rename from twitch/customAlertsMedia/raid.gif
rename to web/alert/media/raid.gif
diff --git a/web/alert/media/sonar.ogg b/web/alert/media/sonar.ogg
new file mode 100644
index 0000000..e758f53
Binary files /dev/null and b/web/alert/media/sonar.ogg differ
diff --git a/twitch/customAlertsMedia/subscribe.mp3 b/web/alert/media/subscribe.mp3
similarity index 100%
rename from twitch/customAlertsMedia/subscribe.mp3
rename to web/alert/media/subscribe.mp3
diff --git a/web/chatguessr/SRC b/web/chatguessr/SRC
deleted file mode 100644
index 0c9f640..0000000
--- a/web/chatguessr/SRC
+++ /dev/null
@@ -1,1752 +0,0 @@
-let deps = {
- "axios": "^0.22.0",
- "codegrid-js": "git+https://github.com/Tzhf/codegrid-js.git",
- "dotenv": "^8.2.0",
- "electron-store": "^8.0.1",
- "electron-updater": "^4.3.9",
- "jquery": "^3.5.1",
- "tmi.js": "^1.8.5"
- }
-
-const Game = require("./Classes/Game");
-const GameHelper = require("./utils/GameHelper");
-
-const tmi = require("./Classes/tmi");
-
-const game = new Game();
-
-class GameHandler {
- constructor(win, settingsWindow) {
- this.win = win;
- this.settingsWindow = settingsWindow;
- this.initTmi();
- this.init();
- }
-
- init = () => {
- game.init(this.win, settings);
-
- // Browser Listening
- this.win.webContents.on("did-navigate-in-page", (e, url) => {
- if (isGameURL(url)) {
- game.start(url, settings.isMultiGuess).then(() => {
- this.win.webContents.send("game-started", game.isMultiGuess);
- TMI.action(`${game.round == 1 ? "🌎 A new seed of " + game.mapName : "🌎 Round " + game.round} has started`);
- openGuesses();
- });
- } else {
- game.outGame();
- this.win.webContents.send("game-quitted");
- }
- });
-
- this.win.webContents.on("did-frame-finish-load", () => {
- if (!game.isInGame) return;
- this.win.webContents.send("refreshed-in-game", settings.noCompass);
- // Checks and update seed when the game has refreshed
- // update the current location if it was skipped
- // if the streamer has guessed returns scores
- game.refreshSeed().then((scores) => {
- if (scores) showResults(scores.location, scores.scores);
- });
-
- this.win.webContents.executeJavaScript(`
- window.nextRoundBtn = document.querySelector('[data-qa="close-round-result"]');
- if(window.nextRoundBtn) {
- nextRoundBtn.addEventListener("click", () => {
- nextRoundBtn.setAttribute('disabled', 'disabled');
- ipcRenderer.send('next-round-click');
- });
- }
- `);
- });
-
- const showResults = (location, scores) => {
- const round = game.seed.state === "finished" ? game.round : game.round - 1;
- this.win.webContents.send("show-round-results", round, location, scores);
- TMI.action(`🌎 Round ${round} has finished. Congrats ${GameHelper.toEmojiFlag(scores[0].flag)} ${scores[0].username} !`);
- };
-
- ipcMain.on("next-round-click", () => nextRound());
-
- const nextRound = () => {
- game.nextRound();
- if (game.seed.state === "finished") {
- processTotalScores();
- } else {
- this.win.webContents.send("next-round", game.isMultiGuess);
- TMI.action(`🌎 Round ${game.round} has started`);
- openGuesses();
- }
- };
-
- TMI.client.on("guess", async (from, userstate, message, self) => {
- const msg = message.split("!g")[1].trim();
- if (!GameHelper.isCoordinates(msg)) return;
-
- const location = { lat: parseFloat(msg.split(",")[0]), lng: parseFloat(msg.split(",")[1]) };
- game.handleUserGuess(userstate, location).then((res) => {
- if (res === "alreadyGuessed") return TMI.say(`${userstate["display-name"]} you already guessed`);
- const { user, guess } = res;
- this.win.webContents.send("render-guess", guess, game.nbGuesses);
- if (settings.showHasGuessed) return TMI.say(`${GameHelper.toEmojiFlag(user.flag)} ${userstate["display-name"]} guessed`);
-
- });
- });
-}
-
-module.exports = GameHandler;
-
-const GameHandler = require("./GameHandler");
-
-const Scoreboard = require("./Classes/Scoreboard");
-const Store = require("./utils/Store");
-
-window.addEventListener("DOMContentLoaded", () => {
- window.ipcRenderer = require("electron").ipcRenderer;
- window.$ = window.jQuery = require("jquery");
- window.MAP = null;
-
- hijackMap();
-
- const head = document.getElementsByTagName("head")[0];
-
- const styles = document.createElement("link");
- styles.rel = "stylesheet";
- styles.type = "text/css";
- styles.href = `${path.join(__dirname, "./public/styles.css")}`;
- head.appendChild(styles);
-
- const scoreboardContainer = document.createElement("div");
- scoreboardContainer.setAttribute("id", "scoreboardContainer");
- scoreboardContainer.innerHTML = `
-
-
-
-
-
- # |
- Player |
- Streak |
- Distance |
- Score |
-
-
-
-
-
`;
- document.body.appendChild(scoreboardContainer);
-
- const flagIcon = document.createElement("link");
- flagIcon.rel = "stylesheet";
- flagIcon.type = "text/css";
- flagIcon.href = `${path.join(__dirname, "./public/flag-icon.min.css")}`;
- head.appendChild(flagIcon);
-
- const jqueryUI = document.createElement("script");
- jqueryUI.type = "text/javascript";
- jqueryUI.src = `${path.join(__dirname, "./public/jquery-ui.min.js")}`;
- jqueryUI.addEventListener("load", () => loadDatatables());
- document.body.appendChild(jqueryUI);
-
- const loadDatatables = () => {
- const datatables = document.createElement("script");
- datatables.type = "text/javascript";
- datatables.src = `${path.join(__dirname, "./public/datatables.bundle.min.js")}`;
- datatables.addEventListener("load", () => init());
- document.body.appendChild(datatables);
- };
-
- const init = () => {
- const markerRemover = document.createElement("style");
- markerRemover.innerHTML = ".map-pin{display:none}";
-
- const settingsIcon = document.createElement("div");
- settingsIcon.setAttribute("title", "Settings (ctrl+p)");
- settingsIcon.id = "settingsIcon";
- settingsIcon.innerHTML = "⚙️";
- settingsIcon.addEventListener("click", () => {
- ipcRenderer.send("openSettings");
- });
- document.body.appendChild(settingsIcon);
-
- const scoreboard = new Scoreboard();
-
- const showScoreboard = document.createElement("div");
- showScoreboard.setAttribute("title", "Show scoreboard");
- showScoreboard.id = "showScoreboard";
- showScoreboard.innerHTML = "👁️🗨️";
- showScoreboard.addEventListener("click", () => {
- scoreboard.setVisibility();
- });
-
- ipcRenderer.on("game-started", (e, isMultiGuess) => {
- document.body.appendChild(showScoreboard);
- scoreboard.checkVisibility();
- scoreboard.reset(isMultiGuess);
- });
-
- ipcRenderer.on("refreshed-in-game", (e, noCompass) => {
- document.body.appendChild(showScoreboard);
- scoreboard.checkVisibility();
- drParseNoCompass(noCompass);
- });
-
- ipcRenderer.on("game-quitted", () => {
- scoreboard.hide();
- if ($("#showScoreboard")) $("#showScoreboard").remove();
- markerRemover.remove();
- clearMarkers();
- });
-
- ipcRenderer.on("render-guess", (e, guess, nbGuesses) => {
- scoreboard.setTitle(`GUESSES (${nbGuesses})`);
- scoreboard.renderGuess(guess);
- });
-
- ipcRenderer.on("render-multiguess", (e, guesses, nbGuesses) => {
- scoreboard.setTitle(`GUESSES (${nbGuesses})`);
- scoreboard.renderMultiGuess(guesses);
- });
-
- ipcRenderer.on("pre-round-results", () => document.body.appendChild(markerRemover));
-
- ipcRenderer.on("show-round-results", (e, round, location, scores) => {
- scoreboard.show();
- scoreboard.setTitle(`ROUND ${round} RESULTS`);
- scoreboard.displayScores(scores);
- scoreboard.showSwitch(false);
- populateMap(location, scores);
- });
-
- ipcRenderer.on("show-final-results", (e, totalScores) => {
- document.body.appendChild(markerRemover);
- scoreboard.show();
- scoreboard.setTitle("HIGHSCORES");
- scoreboard.showSwitch(false);
- scoreboard.displayScores(totalScores, true);
- clearMarkers();
- });
-
- ipcRenderer.on("next-round", (e, isMultiGuess) => {
- scoreboard.checkVisibility();
- scoreboard.reset(isMultiGuess);
- scoreboard.showSwitch(true);
- setTimeout(() => {
- markerRemover.remove();
- clearMarkers();
- }, 1000);
- });
-
- ipcRenderer.on("switch-on", () => scoreboard.switchOn(true));
- ipcRenderer.on("switch-off", () => scoreboard.switchOn(false));
-
- ipcRenderer.on("game-settings-change", (e, noCompass) => drParseNoCompass(noCompass));
- };
-});
-
-let markers = [];
-let polylines = [];
-function populateMap(location, scores) {
- const infowindow = new google.maps.InfoWindow();
- const icon = {
- path: `M13.04,41.77c-0.11-1.29-0.35-3.2-0.99-5.42c-0.91-3.17-4.74-9.54-5.49-10.79c-3.64-6.1-5.46-9.21-5.45-12.07
- c0.03-4.57,2.77-7.72,3.21-8.22c0.52-0.58,4.12-4.47,9.8-4.17c4.73,0.24,7.67,3.23,8.45,4.07c0.47,0.51,3.22,3.61,3.31,8.11
- c0.06,3.01-1.89,6.26-5.78,12.77c-0.18,0.3-4.15,6.95-5.1,10.26c-0.64,2.24-0.89,4.17-1,5.48C13.68,41.78,13.36,41.78,13.04,41.77z
- `,
- fillColor: "#de3e3e",
- fillOpacity: 0.7,
- scale: 1.2,
- strokeColor: "#000000",
- strokeWeight: 1,
- anchor: new google.maps.Point(14, 43),
- labelOrigin: new google.maps.Point(13.5, 15),
- };
-
- const locationMarker = new google.maps.Marker({
- position: location,
- url: `http://maps.google.com/maps?q=&layer=c&cbll=${location.lat},${location.lng}`,
- icon: icon,
- map: MAP,
- });
- google.maps.event.addListener(locationMarker, "click", () => {
- window.open(locationMarker.url, "_blank");
- });
- markers.push(locationMarker);
-
- icon.scale = 1;
- scores.forEach((score, index) => {
- const color = index == 0 ? "#E3BB39" : index == 1 ? "#C9C9C9" : index == 2 ? "#A3682E" : score.color;
- icon.fillColor = color;
-
- const guessMarker = new google.maps.Marker({
- position: score.position,
- icon: icon,
- map: MAP,
- label: { color: "#000", fontWeight: "bold", fontSize: "16px", text: `${index + 1}` },
- });
- google.maps.event.addListener(guessMarker, "mouseover", () => {
- infowindow.setContent(`
-
- ${score.flag ? `` : ""}${score.username}
- ${score.distance >= 1 ? parseFloat(score.distance.toFixed(1)) + "km" : parseInt(score.distance * 1000) + "m"}
- ${score.score}
-
- `);
- infowindow.open(MAP, guessMarker);
- });
- google.maps.event.addListener(guessMarker, "mouseout", () => {
- infowindow.close();
- });
- markers.push(guessMarker);
-
- polylines.push(
- new google.maps.Polyline({
- strokeColor: color,
- strokeWeight: 4,
- strokeOpacity: 0.6,
- geodesic: true,
- map: MAP,
- path: [score.position, location],
- })
- );
- });
-}
-
-function clearMarkers() {
- while (markers[0]) {
- markers.pop().setMap(null);
- }
- while (polylines[0]) {
- polylines.pop().setMap(null);
- }
-}
-
-function hijackMap() {
- const MAPS_API_URL = "https://maps.googleapis.com/maps/api/js?";
- const GOOGLE_MAPS_PROMISE = new Promise((resolve, reject) => {
- let scriptObserver = new MutationObserver((mutations) => {
- for (const mutation of mutations) {
- for (const node of mutation.addedNodes) {
- if (node.tagName === "SCRIPT" && node.src.startsWith(MAPS_API_URL)) {
- node.onload = () => {
- scriptObserver.disconnect();
- scriptObserver = undefined;
- resolve();
- };
- }
- }
- }
- });
-
- let bodyDone = false;
- let headDone = false;
-
- new MutationObserver((_, observer) => {
- if (!bodyDone && document.body) {
- if (scriptObserver) {
- scriptObserver.observe(document.body, {
- childList: true,
- });
- bodyDone = true;
- }
- }
- if (!headDone && document.head) {
- if (scriptObserver) {
- scriptObserver.observe(document.head, {
- childList: true,
- });
- headDone = true;
- }
- }
- if (headDone && bodyDone) {
- observer.disconnect();
- }
- }).observe(document.documentElement, {
- childList: true,
- subtree: true,
- });
- });
-
- function runAsClient(f) {
- const script = document.createElement("script");
- script.type = "text/javascript";
- script.text = `(${f.toString()})()`;
- document.body.appendChild(script);
- }
-
- GOOGLE_MAPS_PROMISE.then(() => {
- runAsClient(() => {
- const google = window.google;
- const isGamePage = () => location.pathname.startsWith("/results/") || location.pathname.startsWith("/game/");
- const onMapUpdate = (map) => {
- try {
- if (!isGamePage()) return;
- MAP = map;
- } catch (error) {
- console.error("GeoguessrHijackMap Error:", error);
- }
- };
-
- const oldMap = google.maps.Map;
- google.maps.Map = Object.assign(
- function (...args) {
- const res = oldMap.apply(this, args);
- this.addListener("idle", () => {
- if (MAP != null) return;
- onMapUpdate(this);
- });
- return res;
- },
- {
- prototype: Object.create(oldMap.prototype),
- }
- );
- });
- });
-}
-
-function drParseNoCompass(noCompass) {
- const style = document.getElementById("noCompass");
- if (noCompass) {
- if (!style) {
- const style = document.createElement("style");
- style.id = "noCompass";
- style.innerHTML = ".compass { display: none }.game-layout__compass{display: none}";
- document.head.appendChild(style);
- }
- } else {
- if (style) style.remove();
- }
-}
-
-function drParseNoCar() {
- if (!noCar) return;
-
- const OPTIONS = { colorR: 0.5, colorG: 0.5, colorB: 0.5 };
- const vertexOld =
- "const float f=3.1415926;varying vec3 a;uniform vec4 b;attribute vec3 c;attribute vec2 d;uniform mat4 e;void main(){vec4 g=vec4(c,1);gl_Position=e*g;a=vec3(d.xy*b.xy+b.zw,1);a*=length(c);}";
- const fragOld =
- "precision highp float;const float h=3.1415926;varying vec3 a;uniform vec4 b;uniform float f;uniform sampler2D g;void main(){vec4 i=vec4(texture2DProj(g,a).rgb,f);gl_FragColor=i;}";
- const vertexNew = `
- const float f=3.1415926;
- varying vec3 a;
- varying vec3 potato;
- uniform vec4 b;
- attribute vec3 c;
- attribute vec2 d;
- uniform mat4 e;
- void main(){
- vec4 g=vec4(c,1);
- gl_Position=e*g;
- a = vec3(d.xy * b.xy + b.zw,1);
- a *= length(c);
- potato = vec3(d.xy, 1.0) * length(c);
- }
- `;
-
- const fragNew = `
- precision highp float;
- const float h=3.1415926;
- varying vec3 a;
- varying vec3 potato;
- uniform vec4 b;
- uniform float f;
- uniform sampler2D g;
- void main(){
-
- vec2 aD = potato.xy / a.z;
- float thetaD = aD.y;
-
- float thresholdD1 = 0.6;
- float thresholdD2 = 0.7;
-
- float x = aD.x;
- float y = abs(4.0*x - 2.0);
- float phiD = smoothstep(0.0, 1.0, y > 1.0 ? 2.0 - y : y);
-
- vec4 i = vec4(thetaD > mix(thresholdD1, thresholdD2, phiD)
- ? vec3(float(${OPTIONS.colorR}), float(${OPTIONS.colorG}), float(${OPTIONS.colorB})) // texture2DProj(g,a).rgb * 0.25
- : texture2DProj(g,a).rgb,f);
- gl_FragColor=i;
- }
- `;
-
- function installShaderSource(ctx) {
- const g = ctx.shaderSource;
- function shaderSource() {
- if (typeof arguments[1] === "string") {
- let glsl = arguments[1];
- if (glsl === vertexOld) glsl = vertexNew;
- else if (glsl === fragOld) glsl = fragNew;
- return g.call(this, arguments[0], glsl);
- }
- return g.apply(this, arguments);
- }
- shaderSource.bestcity = "bintulu";
- ctx.shaderSource = shaderSource;
- }
-
- function installGetContext(el) {
- const g = el.getContext;
- el.getContext = function () {
- if (arguments[0] === "webgl" || arguments[0] === "webgl2") {
- const ctx = g.apply(this, arguments);
- if (ctx && ctx.shaderSource && ctx.shaderSource.bestcity !== "bintulu") {
- installShaderSource(ctx);
- }
- return ctx;
- }
- return g.apply(this, arguments);
- };
- }
-
- const f = document.createElement;
- document.createElement = function () {
- if (arguments[0] === "canvas" || arguments[0] === "CANVAS") {
- const el = f.apply(this, arguments);
- installGetContext(el);
- return el;
- }
- return f.apply(this, arguments);
- };
-}
-const path = require("path");
-require("dotenv").config({ path: path.join(__dirname, "../../.env") });
-
-const axios = require("axios");
-
-const CG = require("codegrid-js").CodeGrid();
-const countryCodes = require("./countryCodes");
-const countryCodesNames = require("./countryCodesNames");
-
-class GameHelper {
- /**
- * Checks if '/game/' is in the URL
- * @param {string} url Game URL
- * @return {boolean}
- */
- static isGameURL = (url) => url.includes("/game/");
-
- /**
- * Gets the Game ID from a game URL
- * Checks if ID is 16 characters in length
- * @param {string} url Game URL
- * @return {string|boolean} id or false
- */
- static getGameId = (url) => {
- const id = url.substring(url.lastIndexOf("/") + 1);
- if (id.length == 16) {
- return id;
- } else {
- return false;
- }
- };
-
- /**
- * Fetch a game seed
- * @param {string} url
- * @return {Promise} Seed Promise
- */
- static fetchSeed = async (url) => {
- return axios
- .get(`https://www.geoguessr.com/api/v3/games/${url.substring(url.lastIndexOf("/") + 1)}`)
- .then((res) => res.data)
- .catch((error) => console.log(error));
- };
-
- /**
- * Returns a country code
- * @param {Object} location {lat, lng}
- * @return {Promise} Country code Promise
- */
- static getCountryCode = async (location) => {
- return axios
- .get(`https://api.bigdatacloud.net/data/reverse-geocode?latitude=${location.lat}&longitude=${location.lng}&key=${process.env.BDC_KEY}`)
- .then((res) => countryCodes[res.data.countryCode])
- .catch((error) => {
- // if BDC returns an error use CodeGrid
- return new Promise((resolve, reject) => {
- CG.getCode(location.lat, location.lng, (error, code) => {
- resolve(code);
- reject(new Error(error));
- });
- }).then((code) => countryCodes[code.toUpperCase()]);
- });
- };
-
- /**
- * Returns a country code
- * It uses CodeGrid first and then BDC if needed
- * @param {Object} location {lat, lng}
- * @return {Promise} Country code Promise
- */
- static getCountryCodeLocally = async (location) => {
- return new Promise((resolve, reject) => {
- let coordinates = this.getSurroundings(location);
- let promises = [];
- coordinates.forEach((coord) => {
- promises.push(this.getCountryCG(coord));
- });
- Promise.all(promises).then((values) => {
- let unique = new Set(values);
- if (unique.size === 1) {
- console.log(unique.values().next().value);
- } else {
- this.getCountryBDC(location).then((data) => resolve(data));
- }
- });
- });
- };
-
- /**
- * Returns a country code (Only using BDC)
- * Do not use externally - Used by getCountryCodeLocally
- * Ultimately we will call our own API here and remove/
- * replace getCountryCode
- * @param {Object} location {lat, lng}
- * @return {Promise} Country code Promise
- */
- static getCountryBDC = async (location) => {
- return axios
- .get(`https://api.bigdatacloud.net/data/reverse-geocode?latitude=${location.lat}&longitude=${location.lng}&key=${process.env.BDC_KEY}`)
- .then((res) => countryCodes[res.data.countryCode])
- .catch((error) => error);
- };
-
- /**
- * Returns a country code (Only using CodeGrid)
- * Do not use externally - Used by getCountryCodeLocally
- * @param {Object} location {lat, lng}
- * @return {Promise} Country code Promise
- */
- static getCountryCG = (location) => {
- return new Promise((resolve, reject) => {
- CG.getCode(location.lat, location.lng, (error, code) => {
- if (error) {
- reject(new Error(error));
- } else {
- resolve(countryCodes[code.toUpperCase()]);
- }
- });
- });
- };
-
- /**
- * Returns an array of 9 coodinates as objects.
- * Each coordinate is 100 meters aways from the given
- * coordinate y angles from 0 to 315
- * The first coordinate is the original passed
- * @param {Object} location {lat, lng}
- * @return {Array} Coordinates [{lat, lng}, {lat, lng}] x 8
- */
- static getSurroundings = (location) => {
- const meters = 100;
- const R_EARTH = 6378.137;
- const M = 1 / (((2 * Math.PI) / 360) * R_EARTH) / 1000;
-
- function moveFrom(coords, angle, distance) {
- let radianAngle = (angle * Math.PI) / 180;
- let x = 0 + distance * Math.cos(radianAngle);
- let y = 0 + distance * Math.sin(radianAngle);
- let newLat = coords.lat + y * M;
- let newLng = coords.lng + (x * M) / Math.cos(coords.lat * (Math.PI / 180));
- return { lat: newLat, lng: newLng };
- }
- let coordinates = [location];
- for (let angle = 0; angle < 360; angle += 45) {
- coordinates.push(moveFrom({ lat: location.lat, lng: location.lng }, angle, meters));
- }
- return coordinates;
- };
-
- /**
- * Check if the param is coordinates
- * @param {string} coordinates
- * @return {boolean}
- */
- static isCoordinates = (coordinates) => {
- const regex = /^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/g;
- return regex.test(coordinates);
- };
-
- /**
- * Returns map scale
- * @param {Object} bounds map bounds
- * @return {number} map scale
- */
- static calculateScale = (bounds) =>
- GameHelper.haversineDistance({ lat: bounds.min.lat, lng: bounds.min.lng }, { lat: bounds.max.lat, lng: bounds.max.lng }) / 7.458421;
-
- /**
- * Returns distance in km between two coordinates
- * @param {Object} mk1 {lat, lng}
- * @param {Object} mk2 {lat, lng}
- * @return {number} km
- */
- static haversineDistance = (mk1, mk2) => {
- const R = 6371.071;
- const rlat1 = mk1.lat * (Math.PI / 180);
- const rlat2 = mk2.lat * (Math.PI / 180);
- const difflat = rlat2 - rlat1;
- const difflon = (mk2.lng - mk1.lng) * (Math.PI / 180);
- const km =
- 2 *
- R *
- Math.asin(Math.sqrt(Math.sin(difflat / 2) * Math.sin(difflat / 2) + Math.cos(rlat1) * Math.cos(rlat2) * Math.sin(difflon / 2) * Math.sin(difflon / 2)));
- return km;
- };
-
- /**
- * Returns score based on distance and scale
- * @param {number} distance
- * @param {number} scale
- * @return {number} score
- */
- static calculateScore = (distance, scale) => Math.round(5000 * Math.pow(0.99866017, (distance * 1000) / scale));
-
- /**
- * Returns guesses sorted by distance ASC
- * @param {array} guesses
- * @return {array} guesses
- */
- static sortByDistance = (guesses) => guesses.sort((a, b) => a.distance - b.distance);
-
- /**
- * Returns guesses sorted by score DESC
- * @param {array} guesses
- * @return {array} guesses
- */
- static sortByScore = (guesses) => guesses.sort((a, b) => b.score - a.score);
-
- /** Converts a country code into an emoji flag
- * @param {String} value
- */
- static toEmojiFlag = (value) => {
- if (value.length == 2) {
- return value.toUpperCase().replace(/./g, (char) => String.fromCodePoint(char.charCodeAt(0) + 127397));
- } else {
- const flag = value
- .toUpperCase()
- .substring(0, 2)
- .replace(/./g, (char) => String.fromCodePoint(char.charCodeAt(0) + 127397));
- const region = value
- .toUpperCase()
- .substring(2)
- .replace(/./g, (char) => String.fromCodePoint(char.charCodeAt(0) + 127397) + " ");
- return `${flag} ${region}`.trim();
- }
- };
-
- /** Replace special chars
- * @param {String} val
- */
- static normalize = (val) => val.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
-
- /** Matches words above 3 letters
- * @param {String} input
- * @param {String} key
- */
- static isMatch = (input, key) => input.length >= 3 && key.includes(input) && input.length <= key.length;
-
- /** Find country by code or name
- * @param {String} input
- * @return {Object} countryCodesNames
- */
- static findCountry = (input) => {
- const normalized = GameHelper.normalize(input);
- return countryCodesNames.find((country) => country.code === normalized || GameHelper.isMatch(normalized, country.names.toLowerCase()));
- };
-
- /** Return a random country code
- * @return {String}
- */
- static getRandomFlag = () => countryCodesNames[Math.floor(Math.random() * countryCodesNames.length)].code;
-
- /** Make game summary link
- * @param {string} streamer
- * @param {string} mapName
- * @param {Object} mode
- * @param {Object[]} locations
- * @param {Object[]} scores
- * @return {Promise} link
- */
- static makeLink = (streamer, mapName, mode, locations, totalScores) => {
- const players = totalScores.map((guess) => {
- return { username: guess.username, flag: guess.flag, score: guess.score, rounds: guess.rounds };
- });
-
- return axios
- .post(`${process.env.API_URL}/game`, {
- streamer: streamer,
- map: mapName,
- mode: mode,
- locations: locations,
- players: players,
- })
- .then((res) => {
- return `${process.env.BASE_URL}/game/${res.data.code}`;
- })
- .catch((err) => {
- console.log(err);
- });
- };
-}
-
-module.exports = GameHelper;
-const GameHelper = require("./GameHelper");
-
-
-
-function mainWindow() {
- let win = new BrowserWindow({
- show: false,
- webPreferences: {
- preload: path.join(__dirname, "../preload.js"),
- enableRemoteModule: true,
- contextIsolation: false,
- webSecurity: false,
- devTools: false,
- },
- });
- win.setMenuBarVisibility(false);
- win.loadURL("https://www.geoguessr.com/classic");
-
- win.webContents.on("new-window", (e, link) => {
- e.preventDefault();
- shell.openExternal(link);
- });
-
- win.on("closed", () => {
- win = null;
- });
-
- return win;
-}
-
-module.exports = mainWindow();
-const path = require("path");
-const { BrowserWindow } = require("electron");
-
-function updateWindow() {
- const win = new BrowserWindow({
- width: 400,
- minWidth: 400,
- height: 240,
- minHeight: 240,
- frame: false,
- transparent: true,
- webPreferences: {
- nodeIntegration: true,
- contextIsolation: false,
- devTools: false,
- },
- });
- win.setMenuBarVisibility(false);
- win.loadURL(path.join(__dirname, "./update.html"));
-
- return win;
-}
-
-module.exports = updateWindow();
-
-const ipcRenderer = require("electron").ipcRenderer;
-
-const message = document.getElementById("message");
-const restartButton = document.getElementById("restart-button");
-
-ipcRenderer.on("download_progress", () => {
- ipcRenderer.removeAllListeners("download_progress");
- message.innerHTML = `Download in progress...`;
-});
-
-ipcRenderer.on("update_downloaded", () => {
- ipcRenderer.removeAllListeners("update_downloaded");
- message.innerHTML = `
- Update downloaded successfully. It will be installed on restart.
- Restart now ?
- `;
- restartButton.classList.remove("hidden");
-});
-
-ipcRenderer.on("update_error", (err) => {
- ipcRenderer.removeAllListeners("update_error");
- message.innerHTML = "An error occured.";
-});
-
-function closeWindow() {
- ipcRenderer.send("close_update_window");
-}
-function restartApp() {
- ipcRenderer.send("restart_app");
-}
-const path = require("path");
-const { BrowserWindow, shell } = require("electron");
-
-function settingsWindow() {
- const win = new BrowserWindow({
- width: 600,
- minWidth: 600,
- height: 500,
- minHeight: 500,
- show: false,
- frame: false,
- transparent: true,
- webPreferences: {
- nodeIntegration: true,
- contextIsolation: false,
- devTools: false,
- },
- });
- win.setMenuBarVisibility(false);
- win.loadURL(path.join(__dirname, "./settings.html"));
-
- win.webContents.on("new-window", (e, link) => {
- e.preventDefault();
- shell.openExternal(link);
- });
-
- return win;
-}
-
-
-
-const openTab = (e, tab) => {
- const tabcontent = document.getElementsByClassName("tabcontent");
- for (let i = 0; i < tabcontent.length; i++) {
- tabcontent[i].style.display = "none";
- }
- const tablinks = document.getElementsByClassName("tablinks");
- for (let i = 0; i < tablinks.length; i++) {
- tablinks[i].className = tablinks[i].className.replace(" active", "");
- }
- document.getElementById(tab).style.display = "block";
- e.currentTarget.className += " active";
-};
-document.getElementById("defaultOpen").click();
-const GameHelper = require("../utils/GameHelper");
-const Store = require("../utils/Store");
-const Guess = require("./Guess");
-
-class Game {
- constructor() {
- this.win;
- this.settings;
- this.url;
- this.seed;
- this.mapScale;
- this.location;
- this.country;
- this.isInGame = false;
- this.guessesOpen = false;
- this.isMultiGuess = false;
- this.guesses = [];
- this.total = [];
- this.lastLocation = {};
- }
-
- init = (win, settings) => {
- this.win = win;
- this.settings = settings;
- this.lastLocation = Store.get("lastLocation", {});
- };
-
- start = async (url, isMultiGuess) => {
- this.isInGame = true;
- this.isMultiGuess = isMultiGuess;
- if (this.url === url) {
- this.refreshSeed();
- } else {
- this.url = url;
- this.seed = await this.getSeed();
- this.mapScale = GameHelper.calculateScale(this.seed.bounds);
- this.getCountry();
- this.clearGuesses();
- }
- };
-
- outGame = () => {
- this.isInGame = false;
- this.closeGuesses();
- };
-
- streamerHasguessed = (newSeed) => newSeed.player.guesses.length != this.seed.player.guesses.length;
-
- locHasChanged = (newSeed) => JSON.stringify(newSeed.rounds[newSeed.rounds.length - 1]) != JSON.stringify(this.getLocation());
-
- refreshSeed = async () => {
- const newSeed = await this.getSeed();
- // If a guess has been comitted, process streamer guess then return scores
- if (this.streamerHasguessed(newSeed)) {
- this.win.webContents.send("pre-round-results");
- this.closeGuesses();
- this.seed = newSeed;
-
- const location = this.location;
- const scores = await this.makeGuess().then(() => this.getRoundScores());
- return { location, scores };
- // Else, if only the loc has changed, the location was skipped, replace current loc
- } else if (this.locHasChanged(newSeed)) {
- this.seed = newSeed;
- this.getCountry();
- return false;
- }
- };
-
- getSeed = async () => await GameHelper.fetchSeed(this.url);
-
- getCountry = async () => {
- this.location = this.getLocation();
- this.country = await GameHelper.getCountryCode(this.location);
- };
-
- makeGuess = async () => {
- this.seed = await this.getSeed();
-
- if (this.isMultiGuess) await this.processMultiGuesses();
- const streamerGuess = await this.processStreamerGuess();
-
- this.guesses.push(streamerGuess);
- this.guesses.forEach((guess) => this.pushToTotal(guess));
-
- this.lastLocation = { lat: this.location.lat, lng: this.location.lng };
- Store.set("lastLocation", this.lastLocation);
-
- if (this.seed.state != "finished") {
- this.getCountry();
- }
- };
-
- processMultiGuesses = () => {
- let promises = [];
- this.guesses.forEach(async (guess, index) => {
- promises.push(
- new Promise(async (resolve, reject) => {
- const guessedCountry = await GameHelper.getCountryCode(guess.position);
- guessedCountry === this.country ? guess.streak++ : (guess.streak = 0);
- this.guesses[index].streak = guess.streak;
- Store.setUserStreak(guess.user, guess.streak);
- resolve();
- })
- );
- });
- return Promise.all(promises);
- };
-
- processStreamerGuess = async () => {
- const index = this.seed.state === "finished" ? 1 : 2;
- const streamerGuess = this.seed.player.guesses[this.seed.round - index];
- const location = { lat: streamerGuess.lat, lng: streamerGuess.lng };
-
- const streamer = Store.getOrCreateUser(this.settings.channelName, this.settings.channelName);
- const guessedCountry = await GameHelper.getCountryCode(location);
- guessedCountry === this.country ? streamer.addStreak() : streamer.setStreak(0);
-
- const distance = GameHelper.haversineDistance(location, this.location);
- const score = streamerGuess.timedOut ? 0 : GameHelper.calculateScore(distance, this.mapScale);
- if (score == 5000) streamer.perfects++;
- streamer.calcMeanScore(score);
-
- streamer.nbGuesses++;
- Store.saveUser(this.settings.channelName, streamer);
-
- return new Guess(streamer.username, streamer.username, "#FFF", streamer.flag, location, streamer.streak, distance, score);
- };
-
- handleUserGuess = async (userstate, location) => {
- const index = this.hasGuessedThisRound(userstate.username);
-
- if (!this.isMultiGuess && index != -1) return "alreadyGuessed";
-
- const user = Store.getOrCreateUser(userstate.username, userstate["display-name"]);
- if (this.hasPastedPreviousGuess(user.previousGuess, location)) return "pastedPreviousGuess";
-
- if (JSON.stringify(user.lastLocation) != JSON.stringify(this.lastLocation)) user.setStreak(0);
-
- if (!this.isMultiGuess) {
- const guessedCountry = await GameHelper.getCountryCode(location);
- guessedCountry === this.country ? user.addStreak() : user.setStreak(0);
- }
-
- const distance = GameHelper.haversineDistance(location, this.location);
- const score = GameHelper.calculateScore(distance, this.mapScale);
- if (score == 5000) user.perfects++;
- user.calcMeanScore(score);
-
- const guess = new Guess(userstate.username, userstate["display-name"], userstate.color, user.flag, location, user.streak, distance, score);
-
- // Modify guess or push it
- if (this.isMultiGuess && index != -1) {
- this.guesses[index] = guess;
- this.guesses[index].modified = true;
- } else {
- user.nbGuesses++;
- this.guesses.push(guess);
- }
-
- user.setLastLocation({ lat: this.location.lat, lng: this.location.lng });
- user.setPreviousGuess(location);
-
- Store.saveUser(userstate.username, user);
-
- return { user: user, guess: guess };
- };
-
- nextRound = () => {
- this.guesses = [];
- if (this.seed.state != "finished") {
- this.win.webContents.send("next-round", this.isMultiGuess);
- } else {
- this.win.webContents.send("final-results");
- }
- };
-
- getLocation = () => this.seed.rounds[this.seed.round - 1];
-
- getLocations = () => {
- return this.seed.rounds.map((round) => {
- return {
- lat: round.lat,
- lng: round.lng,
- heading: Math.round(round.heading),
- pitch: Math.round(round.pitch),
- };
- });
- };
-
- openGuesses = () => {
- this.guessesOpen = true;
- };
-
- closeGuesses = () => {
- this.guessesOpen = false;
- };
-
- clearGuesses = () => {
- this.guesses = [];
- this.total = [];
- };
-
- /**
- * @param {string} user
- * @return {Number} index
- */
- hasGuessedThisRound = (user) => this.guesses.findIndex((e) => e.user === user);
-
- /**
- * @param {Object} previousGuess
- * @param {Object} location {lat, lng}
- * @return {boolean}
- */
- hasPastedPreviousGuess = (previousGuess, location) => {
- if (previousGuess === null) return false;
- return previousGuess.lat === location.lat && previousGuess.lng === location.lng;
- };
-
- /**
- * @param {Object} guess
- */
- pushToTotal = (guess) => {
- const index = this.total.findIndex((e) => e.user === guess.user);
- if (index != -1) {
- this.total[index].scores.push({ round: this.seed.round - 1, score: guess.score });
- this.total[index].score += guess.score;
- this.total[index].distance += guess.distance;
- this.total[index].streak = guess.streak;
- this.total[index].color = guess.color;
- this.total[index].flag = guess.flag;
- this.total[index].rounds++;
- } else {
- this.total.push({ scores: [{ round: this.seed.round, score: guess.score }], ...guess, rounds: 1 });
- }
- };
-
- /**
- * @return {Guess[]} sorted guesses by Distance
- */
- getRoundScores = () => GameHelper.sortByDistance(this.guesses);
-
- /**
- * @return {Guess[]} sorted guesses by Score
- */
- getTotalScores() {
- const scores = GameHelper.sortByScore(this.total);
- // TODO: Remember to check equality
- Store.userAddVictory(scores[0].user);
- return scores;
- }
-
- get mapName() {
- return this.seed.mapName;
- }
-
- get mode() {
- return { noMove: this.seed.forbidMoving, noPan: this.seed.forbidRotating, noZoom: this.seed.forbidZooming };
- }
-
- get round() {
- return this.seed.round;
- }
-
- get nbGuesses() {
- return this.guesses.length;
- }
-}
-
-module.exports = Game;
-class Guess {
- /**
- * @param {String} user
- * @param {String} username
- * @param {String} color
- * @param {String} flag
- * @param {Object} position {lat, lng}
- * @param {Number} streak
- * @param {Number} distance
- * @param {Number} score
- * @param {Boolean} modified
- */
- constructor(user, username, color, flag, position, streak, distance, score, modified = false) {
- this.user = user;
- this.username = username;
- this.color = color === null ? "#FFF" : color;
- this.flag = flag;
- this.position = position;
- this.streak = streak;
- this.distance = distance;
- this.score = score;
- this.modified = modified;
- }
-}
-
-module.exports = Guess;
-const Store = require("../../utils/Store");
-
-class Scoreboard {
- constructor() {
- this.visibility;
- this.position;
- this.container;
- this.scoreboard;
- this.title;
- this.switchContainer;
- this.switchBtn;
- this.table;
- this.columnState;
- this.isMultiGuess = false;
- this.isResults = false;
- this.isScrolling = false;
- this.speed = 50;
- this.init();
- }
-
- init() {
- this.visibility = this.getCookie("visibility", true);
- this.position = this.getCookie("scoreboard_position", { top: 20, left: 5, width: 380, height: 180 });
- this.container = $("#scoreboardContainer");
- this.title = $("#scoreboardTitle");
- this.switchContainer = $("#switchContainer");
- this.switchBtn = $("#switchBtn");
-
- this.scoreboard = $("#scoreboard");
- this.scoreboard.css("top", this.position.top);
- this.scoreboard.css("left", this.position.left);
- this.scoreboard.css("width", this.position.width);
- this.scoreboard.css("height", this.position.height);
- this.scoreboard
- .resizable({
- handles: "n, e, s, w, ne, se, sw, nw",
- containment: "#scoreboardContainer",
- })
- .draggable({
- containment: "#scoreboardContainer",
- })
- .mouseup(() => {
- const currentPosition = this.getPosition();
- if (JSON.stringify(this.position) !== JSON.stringify(currentPosition)) {
- this.setPosition(currentPosition);
- this.setCookie("scoreboard_position", JSON.stringify(currentPosition));
- }
- });
-
- this.switchBtn.on("change", () => {
- if (this.switchBtn.is(":checked")) {
- ipcRenderer.send("open-guesses");
- } else {
- ipcRenderer.send("close-guesses");
- }
- });
-
- this.table = $("#datatable").DataTable({
- info: false,
- searching: false,
- paging: false,
- scrollY: 100,
- scrollResize: true,
- scrollCollapse: true,
- language: { zeroRecords: " " },
- dom: "Bfrtip",
- buttons: [
- {
- extend: "colvis",
- text: "⚙️",
- className: "colvis-btn",
- columns: ":not(.noVis)",
- },
- ],
- columns: [
- { data: "Position" },
- { data: "Player" },
- { data: "Streak" },
- {
- data: "Distance",
- render: (data, type) => {
- if (type === "display" || type === "filter") {
- return this.toMeter(data);
- }
- return data;
- },
- },
- { data: "Score" },
- ],
- columnDefs: [
- { targets: 0, width: "35px", className: "noVis" },
- { targets: 1, width: "auto", className: "noVis" },
- { targets: 2, width: "55px" },
- { targets: 3, width: "100px" },
- { targets: 4, width: "75px", type: "natural" },
- ],
- });
-
- // Column Visisbility
- this.columnState = this.getCookie("CG_ColVis", [
- { column: 0, state: true },
- { column: 2, state: true },
- { column: 3, state: true },
- { column: 4, state: true },
- ]);
-
- // Handle ColVis change
- this.table.on("column-visibility.dt", (e, settings, column, state) => {
- if (this.isResults || this.isMultiGuess) return;
-
- const i = this.columnState.findIndex((o) => o.column === column);
- if (this.columnState[i]) {
- this.columnState[i] = { column, state };
- } else {
- this.columnState.push({ column, state });
- }
-
- this.setCookie("CG_ColVis", JSON.stringify(this.columnState));
- });
-
- // SCROLLER
- const sliderElem = ``;
- $(".dt-buttons").append(sliderElem);
-
- const slider = document.getElementById("scrollSpeedSlider");
-
- slider.oninput = (e) => {
- this.speed = e.currentTarget.value;
- this.scroller(".dataTables_scrollBody");
- };
-
- const scrollBtn = `
-
-
-
- `;
- $(".dt-buttons").prepend(scrollBtn);
-
- $("#scrollBtn").on("change", (e) => {
- if (e.currentTarget.checked != true) {
- this.isScrolling = $(e.currentTarget).is(":checked");
- this.stop(".dataTables_scrollBody");
- slider.style.display = "none";
- } else {
- this.isScrolling = $(e.currentTarget).is(":checked");
- this.scroller(".dataTables_scrollBody");
- slider.style.display = "inline";
- }
- });
- }
-
- /**
- * @param {boolean} isMultiGuess
- */
- reset = (isMultiGuess) => {
- this.isMultiGuess = isMultiGuess;
- this.setColVis();
- this.isResults = false;
- this.setTitle("GUESSES (0)");
- this.showSwitch(true);
- this.table.clear().draw();
- };
-
- setVisibility = () => {
- this.visibility = !this.visibility;
- this.setCookie("visibility", this.visibility);
- this.checkVisibility();
- };
-
- checkVisibility = () => {
- if (this.visibility) {
- this.show();
- } else {
- this.hide();
- }
- };
-
- show = () => {
- this.container.show();
- };
-
- hide = () => {
- this.container.hide();
- };
-
- renderGuess = (guess) => {
- const row = {
- Position: "",
- Player: `${guess.flag ? `` : ""}${
- guess.username
- }`,
- Streak: guess.streak,
- Distance: guess.distance,
- Score: guess.score,
- };
-
- const rowNode = this.table.row.add(row).node();
- rowNode.classList.add("expand");
- setTimeout(() => {
- rowNode.classList.remove("expand");
- }, 200);
-
- this.table.order([3, "asc"]).draw(false);
- this.table
- .column(0)
- .nodes()
- .each((cell, i) => {
- cell.innerHTML = i + 1;
- });
- };
-
- renderMultiGuess = (guesses) => {
- const rows = guesses.map((guess) => {
- return {
- Position: "",
- Player: `${guess.flag ? `` : ""}${
- guess.username
- }`,
- Streak: "",
- Distance: "",
- Score: "",
- };
- });
-
- this.table.clear().draw();
- this.table.rows.add(rows).draw();
- };
-
- displayScores = (scores, isTotal = false) => {
- this.isResults = true;
- if (scores[0]) scores[0].color = "#E3BB39";
- if (scores[1]) scores[1].color = "#C9C9C9";
- if (scores[2]) scores[2].color = "#A3682E";
- const rows = scores.map((score) => {
- return {
- Position: "",
- Player: `${score.flag ? `` : ""}${
- score.username
- }`,
- Streak: score.streak,
- Distance: score.distance,
- Score: `${score.score}${isTotal ? " [" + score.rounds + "]" : ""}`,
- };
- });
-
- this.table.clear().draw();
- this.table.rows.add(rows);
-
- this.table.order([4, "desc"]).draw(false);
-
- let content;
- this.table
- .column(0)
- .nodes()
- .each((cell, i) => {
- content = i + 1;
- if (isTotal) {
- if (i == 0) content = "🏆";
- else if (i == 1) content = "🥈";
- else if (i == 2) content = "🥉";
- }
-
- cell.innerHTML = content;
- });
-
- // Restore columns visibility
- this.table.columns().visible(true);
- this.toTop(".dataTables_scrollBody");
- };
-
- scroller = (elem) => {
- const div = $(elem);
-
- const loop = () => {
- if (!this.isScrolling) return;
- div.stop().animate({ scrollTop: div[0].scrollHeight }, (div[0].scrollHeight - div.scrollTop() - 84) * this.speed, "linear", () => {
- setTimeout(() => {
- div.stop().animate({ scrollTop: 0 }, 1000, "swing", () => {
- setTimeout(() => {
- loop();
- }, 3000);
- });
- }, 1000);
- });
- };
- loop();
- };
-
- toTop = (elem) => {
- this.stop(elem);
- setTimeout(() => {
- this.scroller(elem);
- }, 3000);
- };
-
- stop(elem) {
- $(elem).stop();
- }
-
- setColVis = () => {
- if (this.isMultiGuess) {
- this.table.columns([0, 2, 3, 4]).visible(false);
- } else {
- this.columnState.forEach((column) => {
- this.table.column(column.column).visible(column.state);
- });
- }
- };
-
- getPosition = () => ({
- top: this.scoreboard.position().top,
- left: this.scoreboard.position().left,
- width: this.scoreboard.width(),
- height: this.scoreboard.height(),
- });
-
- setPosition = (position) => (this.position = position);
-
- setTitle = (title) => this.title.text(title);
-
- showSwitch = (state) => this.switchContainer.css("display", state ? "block" : "none");
-
- switchOn = (state) => this.switchBtn.prop("checked", state);
-
- toMeter = (distance) => (distance >= 1 ? parseFloat(distance.toFixed(1)) + "km" : parseInt(distance * 1000) + "m");
-
- // ColVis Cookies
- setCookie = (name, value, exdays = 60) => {
- const d = new Date();
- d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
- const expires = "expires=" + d.toUTCString();
- document.cookie = name + "=" + value + ";" + expires + ";path=/";
- };
-
- getCookie = (name, defaultValue = {}) => {
- const cname = name + "=";
- var decodedCookie = decodeURIComponent(document.cookie);
- var ca = decodedCookie.split(";");
- for (var i = 0; i < ca.length; i++) {
- var c = ca[i];
- while (c.charAt(0) == " ") {
- c = c.substring(1);
- }
- if (c.indexOf(cname) == 0) {
- return JSON.parse(c.substring(cname.length, c.length));
- }
- }
- return defaultValue;
- };
-}
-
-module.exports = Scoreboard;
-class Settings {
- /**
- * @param {String} channelName=""
- * @param {String} botUsername=""
- * @param {String} token=""
- * @param {String} cgCmd="!cg"
- * @param {String} cgMsg="To play along, go to this link, pick a location, and paste the whole command into chat: "
- * @param {String} userGetStatsCmd="!me"
- * @param {String} userClearStatsCmd="!clear"
- * @param {String} setStreakCmd="!setstreak"
- * @param {Boolean} showHasGuessed=true
- * @param {Boolean} isMultiGuess=false
- * @param {Boolean} noCar=false
- * @param {Boolean} noCompass=false
- */
- constructor(
- channelName = "",
- botUsername = "",
- token = "",
- cgCmd = "!cg",
- cgMsg = "To play along, go to this link, pick a location, and paste the whole command into chat: ",
- userGetStatsCmd = "!me",
- userClearStatsCmd = "!clear",
- setStreakCmd = "!setstreak",
- showHasGuessed = true,
- isMultiGuess = false,
- noCar = false,
- noCompass = false
- ) {
- this.channelName = channelName;
- this.botUsername = botUsername;
- this.token = token;
- this.cgCmd = cgCmd;
- this.cgMsg = cgMsg;
- this.userGetStatsCmd = userGetStatsCmd;
- this.userClearStatsCmd = userClearStatsCmd;
- this.setStreakCmd = setStreakCmd;
- this.showHasGuessed = showHasGuessed;
- this.isMultiGuess = isMultiGuess;
- this.noCar = noCar;
- this.noCompass = noCompass;
- }
-
- /**
- * @param {boolean} noCar
- * @param {boolean} noCompass
- */
- setGameSettings(isMultiGuess, noCar, noCompass) {
- this.isMultiGuess = isMultiGuess;
- this.noCar = noCar;
- this.noCompass = noCompass;
- }
-
- /**
- * @param {Object} commands
- */
- setTwitchCommands(commands) {
- this.cgCmd = commands.cgCmdd;
- this.cgMsg = commands.cgMsgg;
- this.userGetStatsCmd = commands.userGetStats;
- this.userClearStatsCmd = commands.userClearStats;
- this.setStreakCmd = commands.setStreak;
- this.showHasGuessed = commands.showHasGuessed;
- }
-
- /**
- * @param {string} channelName
- * @param {string} botUsername
- * @param {string} token
- */
- setTwitchSettings(channelName, botUsername, token) {
- this.channelName = channelName;
- this.botUsername = botUsername;
- this.token = token;
- }
-}
-
-module.exports = Settings;
-class User {
- /**
- * @param {String} user
- * @param {String} username
- * @param {String} flag=""
- * @param {Number} streak=0
- * @param {Number} bestStreak=0
- * @param {Number} correctGuesses=0
- * @param {Number} nbGuesses=0
- * @param {Number} perfects=0
- * @param {Number} victories=0
- * @param {Number} meanScore=null
- * @param {Object} previousGuess={}
- * @param {Object} lastLocation=null
- */
- constructor(
- user,
- username,
- flag = "",
- streak = 0,
- bestStreak = 0,
- correctGuesses = 0,
- nbGuesses = 0,
- perfects = 0,
- victories = 0,
- meanScore = null,
- previousGuess = {},
- lastLocation = null
- ) {
- this.user = user;
- this.username = username;
- this.flag = flag;
- this.streak = streak;
- this.bestStreak = bestStreak;
- this.correctGuesses = correctGuesses;
- this.nbGuesses = nbGuesses;
- this.perfects = perfects;
- this.victories = victories;
- this.meanScore = meanScore;
- this.previousGuess = previousGuess;
- this.lastLocation = lastLocation;
- }
-
- /* Add 1 to streak and correctGuesses. */
- addStreak() {
- this.streak++;
- this.correctGuesses++;
- if (this.streak > this.bestStreak) this.bestStreak = this.streak;
- }
-
- /** Set user streak
- * @param {Number} number
- */
- setStreak(number) {
- this.streak = number;
- if (this.streak > this.bestStreak) this.bestStreak = this.streak;
- }
-
- /** Set last location
- * @param {Object} location
- */
- setLastLocation(location) {
- this.lastLocation = location;
- }
-
- /** Set previous guess
- * @param {Object} location
- */
- setPreviousGuess(location) {
- this.previousGuess = location;
- }
-
- /** Set a country flag
- * @param {String} flag country code
- */
- setFlag(flag) {
- this.flag = flag;
- }
-
- /** Calculate mean score
- * @param {Number} score
- */
- calcMeanScore(score) {
- if (this.meanScore === null) {
- this.meanScore = score;
- } else {
- this.meanScore = (this.meanScore * this.nbGuesses + score) / (this.nbGuesses + 1);
- }
- }
-}
-
diff --git a/web/emotewall/index.js b/web/emotewall/index.js
index b9cdd80..4be0258 100644
--- a/web/emotewall/index.js
+++ b/web/emotewall/index.js
@@ -136,7 +136,7 @@ $(async function () {
class Particle {
constructor(emote,data){
- this.size = 64;
+ this.size = 56;
this.data = data;
this.setPhysics(randRange(0,canvas.width),
randRange(0,canvas.height),
@@ -299,7 +299,7 @@ $(async function () {
const createParticule = (partP, emote, data, args) => {
let a = new partP(emote,data,args)
particles.push(a);
- setTimeout(()=>{particles.pop()}, drt*1000+2000);
+ setTimeout(()=>{particles.shift()}, drt*1000+2000);
}
const genExplosion = (x,y,ang,em) =>{