// ==UserScript== // @name eRepublik #SPRINGBREAK Mission Tracker v3.11 (v41) // @namespace http://tampermonkey.net/ // @version 3.11 // @description See all your eRepublik #SPRINGBREAK mission progress in real time — at a glance, in a clean, draggable, collapsible panel. Fully local, privacy-first, player-friendly, and bot-free. Tracks missions, not players. Made by a gamer, for gamers. // @author Janko Fran // @match https://www.erepublik.com/* // @grant none // @license Custom License - Personal, Non-Commercial Use Only // @run-at document-idle // @homepageURL https://greasyfork.org/en/users/1461808-janko-fran // @supportURL https://www.erepublik.com/en/main/messages-compose/2103304 // @downloadURL none // ==/UserScript== /* jshint esversion: 11 */ /* License: This script is provided free of charge for personal, non-commercial use. // You are granted a perpetual, royalty-free license to use this script on your own eRepublik account. // No part of this script may be modified, redistributed, or used for commercial purposes without the express written permission of the author, Janko Fran. // Donations are welcome to support future improvements. For details, see the Info modal or documentation. // // Donation Links: // • eRepublik Donations: https://www.erepublik.com/en/economy/donate-money/2103304 // • Satoshi Donations: janko7ea63e4e@zbd.gg // For custom scripts or financial donations, please contact: // https://www.erepublik.com/en/main/messages-compose/2103304 */ (async function() { 'use strict'; /** * ────────────────────────────────────────────────────────────────────────────── * SECTION: HTML CONTENT BUILDERS * • sectionTemplate * • renderSection * • techItemTemplate * • buildTechStackParagraph * ────────────────────────────────────────────────────────────────────────────── */ /** * sectionTemplate - A small template for any “Section” with a heading and a paragraph. * @param {string} title * @param {string} bodyHtml */ const sectionTemplate = (title, bodyHtml) => `
${bodyHtml}
`; /** * renderSection - Wraps sectionTemplate for clarity and future customization. * @param {string} title * @param {string} bodyHtml */ function renderSection(title, bodyHtml) { return sectionTemplate(title, bodyHtml); } /** * techItemTemplate - Renders a single tech‐stack item. * @param {string} name —technology name * @param {string} description—its description * @returns {string} —bold name + italic desc */ const techItemTemplate = (name, description) => `${name}: ${description}`; /** * buildTechStackParagraph - Builds a paragraph listing all tech‐stack items. * @param {Array<{name:string,desc:string}>} items * @returns {string} — `…
` with `
This script was developed using the following technologies:
${items.map(({ name, desc }) => techItemTemplate(name, desc)).join('
')}
Since official development of eRepublik has slowed significantly in recent years, I decided to improve the player experience myself. This project began as a personal tool, and I’m sharing it for the benefit of other active players who still enjoy the game. In many ways, this is how the company workflow should have worked from the beginning. This project is a small contribution toward keeping eRepublik fun, efficient, and rewarding.
`; const SECTION_FEATURES = `Shows live mission progress, time-elapsed and overall average for the #SPRINGBREAK event. It runs entirely in your browser—no data is sent or stored externally.
`; const techItems = [ { name: "Tampermonkey", desc: "A browser extension that safely runs custom scripts to improve your experience, without tracking or ads, respecting eRepublik’s gameplay rules." }, { name: "JavaScript (ES5)", desc: "Ensures legacy browser support and eRepublik’s frontend compatibility." }, { name: "HTML & CSS", desc: "All UI is built natively into the DOM." }, { name: "ChatGPT Plus", desc: "Assisted during rapid prototyping & testing." } ]; // ────────────────────────────────────────────────────────────────────────── // SECTION: COLOR PALETTE – raw color swatches // ────────────────────────────────────────────────────────────────────────── const COLOR_PALETTE = { GREEN_500 : '#0f0', ORANGE_500: '#ffa500', CYAN_400 : '#6cf', GRAY_400 : '#ccc', GRAY_200 : '#eee', GRAY_100 : '#333', BLACK_900 : '#000' }; /** * ────────────────────────────────────────────────────────────────────────────── * SECTION: CONFIGURATION * • CONFIG constants * • CONFIG.textLabels assignments * ────────────────────────────────────────────────────────────────────────────── */ const CONFIG = { VERSION: '3.11', EVENT_LENGTH: 14, // days TOTAL_MISSIONS: 10, REWARD_ICON: 'https://www.erepublik.net/images/modules/popups/dyeHard/currency.png', RESET_POSITION: { top: '60px', left: '20px' }, DONATE_URL: 'https://www.erepublik.com/en/economy/donate-money/2103304', FONTS: { title: '13px', base: '12px', small: '11px', tiny: '10px' }, /* ────────────────────────────────────────────────────────────────────────────── * SECTION: SEMANTIC COLOR TOKENS (what the colour *means* in UI) * ────────────────────────────────────────────────────────────────────────────── */ COLORS: { OK: COLOR_PALETTE.GREEN_500, // ✔︎ bright lime WARN: COLOR_PALETTE.ORANGE_500, // ⚠︎ warm orange TEXT: COLOR_PALETTE.CYAN_400, // ℹ︎ light cyan MUTED: COLOR_PALETTE.GRAY_400, // mid-light grey SURFACE: COLOR_PALETTE.GRAY_200, // bright grey DIVIDER : COLOR_PALETTE.GRAY_100 // dark gray }, BORDERS: { DIVIDER: `0.25px solid ${COLOR_PALETTE.GRAY_100}` }, HTML : { hr() { return `Anyone juggling all 10 daily missions will save time and clicks by seeing everything at a glance, with real-time updates and a draggable, collapsible panel.
This script does not automate any part of gameplay—you still need to manually click “Work,” “Fight,” or travel to yourself. It simply provides a clear, live overview of your mission progress.
This script is free, transparent, and built entirely with players in mind. There are no trackers, no ads, and no hidden behavior. It was created with genuine passion for the game and a commitment to fair, efficient, and enjoyable gameplay.
For personal, non-commercial use only. Redistribution or commercial use is not permitted without the author's written consent.
If this script has saved you time or made company management easier, please consider supporting future improvements of this and other scripts. Donations help cover development time, testing, and enhancements, and are a much-appreciated motivation to keep going.
For feedback, bug reports, suggestions, or custom script requests, feel free to send me a message.
Sincerely Yours,
Janko Fran
Script version: v${CONFIG.VERSION}
`; /*** DRAGGABLE ***/ function makeDraggable(panel, handle) { handle.style.cursor = 'move'; let sx, sy, px, py; handle.addEventListener('mousedown', e => { e.preventDefault(); sx = e.clientX; sy = e.clientY; px = panel.offsetLeft; py = panel.offsetTop; function onMove(e) { panel.style.left = px + (e.clientX - sx) + 'px'; panel.style.top = py + (e.clientY - sy) + 'px'; } document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', () => { document.removeEventListener('mousemove', onMove); }, { once: true }); }); } function waitFor(conditionFn, interval = CONFIG.pollIntervalMs, timeout = CONFIG.maxWaitTimeMs) { return new Promise((resolve, reject) => { const intervalId = setInterval(() => { try { const result = conditionFn(); if (result) { clearInterval(intervalId); clearTimeout(timeoutHandle); resolve(result); } } catch (e) { clearInterval(intervalId); clearTimeout(timeoutHandle); reject(e); } }, interval); const timeoutHandle = setTimeout(() => { clearInterval(intervalId); reject(new Error('Timeout waiting for condition')); }, timeout); }); } /*** WAIT FOR missionsJSON ***/ function waitForMissionsData() { return waitFor(() => { return Array.isArray(window.missionsJSON) && window.missionsJSON.length? window.missionsJSON.slice(): null; }); } async function fetchDetailedMissions(culture, token, host, delay = CONFIG.fetchDelayMs) { const missions = await waitForMissionsData(); const detailedMissions = []; for (let mission of missions) { detailedMissions.push(await fetchMission(mission, culture, token, host)); await new Promise(resolve => setTimeout(resolve, delay)); } return detailedMissions; } /*** FETCH LIVE DATA ***/ async function fetchMission(m, culture, token, host) { try { const url = `${location.protocol}//${host}/${culture}/main/mission-check?missionId=${m.id}&_token=${token}`; const res = await fetch(url, { credentials: 'same-origin' }); const j = await res.json(); if (Array.isArray(j.conditions)) m.liveConditions = j.conditions; if (Array.isArray(j.rewards)) m.rewards = j.rewards; } catch (e) { console.error('fetchMission', m.id, e); } return m; } /*** MAP REWARDS ***/ const mapReward = cat => ({ springBreakTokens: 'Springcoins', spring_break_tokens: 'Springcoins', gold: 'Gold', currency: 'Currency', vehicle_blueprint: 'Blueprint' }[cat] || cat); /*** HELPER FUNCTIONS ***/ function getServerTimeFromScriptTag() { const scripts = document.querySelectorAll('script'); for (let script of scripts) { const text = script.textContent; if (text.includes('SERVER_DATA') && text.includes('serverTime')) { const match = text.match(/SERVER_DATA\s*=\s*({.*?})\s*[,;]\s*ErpkShop/s); if (match && match[1]) { try { const serverData = JSON.parse(match[1]); return serverData.serverTime || null; } catch (e) { console.error("SERVER_DATA JSON parsing failed", e); } } } } return null; } /** * ────────────────────────────────────────────────────────────────────────────── * SECTION: UTILITY FUNCTIONS * * • htmlToDiv * • getEventProgress * • getEventProgressFromServer * ────────────────────────────────────────────────────────────────────────────── */ /** * htmlToDiv * --------- * Turn a raw HTML string into a detached