- // ==UserScript==
- // @name Elethor General Purpose
- // @description Provides some general additions to Elethor
- // @namespace https://www.elethor.com/
- // @version 1.7.17
- // @author Xortrox
- // @contributor Kidel
- // @contributor Saya
- // @contributor Archeron
- // @contributor Hito
- // @match https://elethor.com/*
- // @match https://www.elethor.com/*
- // @run-at document-start
- // @grant none
- // ==/UserScript==
-
- (function() {
- const currentUserData = {};
-
- const moduleName = 'Elethor General Purpose';
- const version = '1.7.9';
-
- const profileURL = '/profile/';
-
- function initializeXHRHook() {
- let rawSend = XMLHttpRequest.prototype.send;
-
- XMLHttpRequest.prototype.send = function() {
- if (!this._hooked) {
- this._hooked = true;
-
- this.addEventListener('readystatechange', function() {
- if (this.readyState === XMLHttpRequest.DONE) {
- setupHook(this);
- }
- }, false);
- }
- rawSend.apply(this, arguments);
- }
-
- function setupHook(xhr) {
- if (window.elethorGeneralPurposeOnXHR) {
- const e = new Event('EGPXHR');
- e.xhr = xhr;
-
- window.elethorGeneralPurposeOnXHR.dispatchEvent(e);
- }
- }
- window.elethorGeneralPurposeOnXHR = new EventTarget();
-
- console.log(`[${moduleName} v${version}] XHR Hook initialized.`);
- }
-
- function initializeUserLoadListener() {
- elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', function (e) {
- console.log('user load?:', e?.xhr?.responseURL);
- if (e && e.xhr
- && e.xhr.responseURL
- && e.xhr.responseURL.endsWith
- && e.xhr.responseURL.endsWith('/game/user')
- ) {
- try {
- const userData = JSON.parse(e.xhr.responseText);
-
- if (userData) {
- for (const key of Object.keys(userData)) {
- currentUserData[key] = userData[key];
- }
- }
- } catch (e) {
- console.log(`[${moduleName} v${version}] Error parsing userData:`, e);
- }
-
- }
- });
-
- console.log(`[${moduleName} v${version}] User Load Listener initialized.`);
- }
-
- function initializeToastKiller() {
- document.addEventListener('click', function(e) {
- if (e.target
- && e.target.className
- && e.target.className.includes
- && e.target.className.includes('toasted toasted-primary')
- ) {
- e.target.remove();
- }
- });
-
- console.log(`[${moduleName} v${version}] Toast Killer initialized.`);
- }
-
- async function forceLoadUser() {
- const userLoad = await getUserSelf();
- }
-
- function initializeInventoryStatsLoadListener() {
- elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', function (e) {
- if (e && e.xhr
- && e.xhr.responseURL
- && e.xhr.responseURL.endsWith
- && e.xhr.responseURL.endsWith('/game/inventory/stats')
- ) {
- setTimeout(async () => {
- if (Object.keys(currentUserData).length === 0) {
- await forceLoadUser();
- }
-
- updateEquipmentPercentageSummary();
-
- setTimeout(updateInventoryStatsPercentages, 1000);
- });
- }
- });
-
- console.log(`[${moduleName} v${version}] Inventory Stats Load Listener initialized.`);
- }
-
- function updateEquipmentPercentageSummary() {
- document.querySelector('.contains-equipment>div>div:nth-child(1)').setAttribute('style', 'width: 50%');
- document.querySelector('.contains-equipment>div>div:nth-child(2)').setAttribute('style', 'width: 25%');
- let percentagesTable = document.querySelector('#egpPercentagesSummary')
- if (!percentagesTable){
- percentagesTable = document.querySelector('.contains-equipment>div>div:nth-child(2)').cloneNode(true);
- percentagesTable.setAttribute('style', 'width: 25%');
- percentagesTable.id='egpPercentagesSummary';
- document.querySelector('.contains-equipment>div').appendChild(percentagesTable);
-
- for (const child of percentagesTable.children[0].children) {
- if (child && child.children && child.children[0]) {
- child.children[0].remove();
- }
- }
-
- document.querySelector('#egpPercentagesSummary>table>tr:nth-child(8)').setAttribute('style', 'height:43px');
- }
- }
-
- function getStatSummary(equipment) {
- const summary = {
- base: {},
- energizements: {}
- };
-
- if (equipment) {
- for (const key of Object.keys(equipment)) {
- const item = equipment[key];
-
- /**
- * Sums base attributes by name
- * */
- if (item && item.attributes) {
- for (const attributeName of Object.keys(item.attributes)) {
- const attributeValue = item.attributes[attributeName];
-
- if (!summary.base[attributeName]) {
- summary.base[attributeName] = 0;
- }
-
- summary.base[attributeName] += attributeValue;
- }
- }
-
- /**
- * Sums energizements by stat name
- * */
- if (item && item.upgrade && item.upgrade.energizements) {
- for (const energizement of item.upgrade.energizements) {
- if (!summary.energizements[energizement.stat]) {
- summary.energizements[energizement.stat] = 0;
- }
-
- summary.energizements[energizement.stat] += Number(energizement.boost);
- }
- }
- }
- }
-
- return summary;
- }
-
- function updateInventoryStatsPercentages() {
- let percentagesTable = document.querySelector('#egpPercentagesSummary')
- if (percentagesTable && currentUserData && currentUserData.equipment){
- const statSummary = getStatSummary(currentUserData.equipment);
-
- const baseKeys = Object.keys(statSummary.base);
- const energizementKeys = Object.keys(statSummary.energizements);
-
- let allKeys = baseKeys.concat(energizementKeys);
- const filterUniques = {};
- for (const key of allKeys){
- filterUniques[key] = true;
- }
- allKeys = Object.keys(filterUniques);
- allKeys.sort();
-
- allKeys.push('actions');
-
- const tableRows = percentagesTable.children[0].children;
-
- for(const row of tableRows) {
- if (row
- && row.children
- && row.children[0]
- && row.children[0].children[0]
- ) {
- const rowText = row.children[0].children[0];
- rowText.innerText = '';
- }
- }
-
- let rowIndex = 0;
- for (const key of allKeys) {
- if (key === 'puncture') {
- continue;
- }
-
- const row = tableRows[rowIndex];
- if (row
- && row.children
- && row.children[0]
- && row.children[0].children[0]
- ) {
- const rowText = row.children[0].children[0];
-
- const rowBase = statSummary.base[key] || 0;
- const rowEnergizement = (statSummary.energizements[key] || 0);
- const rowEnergizementPercentage = (statSummary.energizements[key] || 0) * 100;
-
- if (key.startsWith('+')) {
- rowText.innerText = `${key} per 10 levels: ${rowEnergizement}`;
- } else if (key === 'actions') {
- const actions = currentUserData.user.bonus_actions || 0;
- rowText.innerText = `Bonus Actions: ${actions}`;
- } else {
- rowText.innerText = `${key}: ${rowBase} (${rowEnergizementPercentage.toFixed(0)}%)`;
- }
-
- rowIndex++;
- }
- }
- }
- }
-
- function initializeLocationChangeListener() {
- let previousLocation = window.location.href;
-
- window.elethorGeneralPurposeOnLocationChange = new EventTarget();
-
- window.elethorLocationInterval = setInterval(() => {
- if (previousLocation !== window.location.href) {
- previousLocation = window.location.href;
-
- const e = new Event('EGPLocation');
- e.newLocation = window.location.href;
- window.elethorGeneralPurposeOnLocationChange.dispatchEvent(e);
- }
-
- }, 500);
-
- console.log(`[${moduleName} v${version}] Location Change Listener initialized.`);
- }
-
- function getProfileCombatElement() {
- const skillElements = document.querySelectorAll('.is-round-skill .progressbar-text>div>p:first-child');
- const skillElements2 = document.querySelectorAll('.is-round-skill .progressbar-text>div>p:nth-child(2)');
-
- let index = 0;
- for (const skillElement of skillElements2) {
- if (skillElement.innerText?.toLowerCase().includes('combat')) {
- return skillElements[index].parentElement?.parentElement?.parentElement?.parentElement?.parentElement;
- }
- index++;
- }
- }
-
- function getProfileMiningElement() {
- const skillElements = document.querySelectorAll('.is-round-skill .progressbar-text>div>p:first-child');
- const skillElements2 = document.querySelectorAll('.is-round-skill .progressbar-text>div>p:nth-child(2)');
-
- let index = 0;
- for (const skillElement of skillElements2) {
- if (skillElement.innerText?.toLowerCase().includes('mining')) {
- return skillElements[index].parentElement?.parentElement?.parentElement?.parentElement?.parentElement;
- }
- index++;
- }
- }
-
- function updateXPTracker(difference) {
- const combatElement = getProfileCombatElement();
- if (combatElement) {
- if (difference.combat > 0) {
- combatElement.setAttribute('data-combat-experience-ahead', `(+${formatNormalNumber(difference.combat)})`);
- combatElement.setAttribute('style', `color:lime`);
- } else {
- combatElement.setAttribute('data-combat-experience-ahead', `(${formatNormalNumber(difference.combat)})`);
- combatElement.setAttribute('style', `color:red`);
- }
- }
-
- const miningElement = getProfileMiningElement();
- if (difference.mining > 0) {
- miningElement.setAttribute('data-mining-experience-ahead', `(+${formatNormalNumber(difference.mining)})`);
- miningElement.setAttribute('style', `color:lime`);
- } else {
- miningElement.setAttribute('data-mining-experience-ahead', `(${formatNormalNumber(difference.mining)})`);
- miningElement.setAttribute('style', `color:red`);
- }
- }
-
- function initializeProfileLoadListener() {
- let css = '[data-combat-experience-ahead]::after { content: attr(data-combat-experience-ahead); padding: 12px;}';
- css += '[data-mining-experience-ahead]::after { content: attr(data-mining-experience-ahead); padding: 12px;}';
-
- appendCSS(css);
-
- window.elethorGeneralPurposeOnLocationChange.addEventListener('EGPLocation', async function (e) {
- if (e && e.newLocation) {
- if(e.newLocation.includes('/profile/')) {
- console.log('Profile view detected:', e);
- const url = e.newLocation;
- const path = url.substr(url.indexOf(profileURL));
-
- // We know we have a profile lookup, and not user-data load if the length differs.
- if (path.length > profileURL.length) {
- const userId = Number(path.substr(path.lastIndexOf('/') + 1));
-
- const difference = await getExperienceDifference(userId, currentUserData.user.id);
-
- updateXPTracker(difference);
- }
- }
- }
- });
-
- console.log(`[${moduleName} v${version}] Profile Load Listener initialized.`);
- }
-
- async function getUser(id) {
- const result = await window.axios.get(`/game/user/${id}?egpIgnoreMe=true`);
- return result.data;
- }
-
- window.getUser = getUser;
-
- async function getUserSelf() {
- const result = await window.axios.get(`/game/user`);
- return result.data;
- }
-
- window.getUserSelf = getUserSelf;
-
- async function getUserStats() {
- const result = await window.axios.get(`/game/inventory/stats`);
- return result.data;
- }
-
- window.getUserStats = getUserStats;
-
- async function getUserStatsJSON(pretty) {
- const stats = await getUserStats();
-
- if (pretty) {
- return JSON.stringify(stats, null, 2);
- }
-
- return JSON.stringify(stats);
- }
-
- window.getUserStatsJSON = getUserStatsJSON;
-
- function getUserCombatStats(user) {
- for (const skill of user.skills) {
- if (skill.name === 'combat') {
- return skill.pivot;
- }
- }
- }
-
- function getMiningStats(user) {
- for (const skill of user.skills) {
- if (skill.id === 1) {
- return skill.pivot;
- }
- }
-
- return 0;
- }
-
- async function getExperienceDifference(userId1, userId2) {
- const [user1, user2] = await Promise.all([
- getUser(userId1),
- getUser(userId2)
- ]);
-
- const combatStats1 = getUserCombatStats(user1);
- const miningStats1 = getMiningStats(user1);
-
- const combatStats2 = getUserCombatStats(user2);
- const miningStats2 = getMiningStats(user2);
-
- return {
- combat: combatStats2.experience - combatStats1.experience,
- mining: miningStats2.experience - miningStats1.experience,
- };
- }
-
- (async function run() {
- await waitForField(window, 'axios');
- forceLoadUser();
- initializeToastKiller();
- initializeXHRHook();
- initializeUserLoadListener();
- initializeInventoryStatsLoadListener();
- initializeLocationChangeListener();
- initializeProfileLoadListener();
- loadMarketRecyclobotVisualizer();
-
- console.log(`[${moduleName} v${version}] Loaded.`);
- })();
-
- (async function loadRerollDisableButtonModule() {
- async function waitForEcho() {
- return new Promise((resolve, reject) => {
- const interval = setInterval(() => {
- if (window.Echo) {
- clearInterval(interval);
- resolve();
- }
- }, 100);
- });
- }
-
- await waitForEcho();
- await waitForUser();
-
- elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', async function (e) {
- if (e && e.xhr && e.xhr.responseURL) {
- if(e.xhr.responseURL.includes('/game/energize')) {
- const itemID = e.xhr.responseURL.substr(e.xhr.responseURL.lastIndexOf('/')+1);
- window.lastEnergizeID = Number(itemID);
- }
- }
- });
- })();
-
- (async function loadResourceNodeUpdater() {
- await waitForField(currentUserData, 'user');
- const user = await getUser(currentUserData.user.id);
-
- function updateExperienceRates() {
- document.querySelectorAll('#nodeContainer>div:not(:first-child)').forEach(async (node) => {
- visualizeResourceNodeExperienceRates(node, user)
- });
-
- function visualizeResourceNodeExperienceRates(node, user) {
- const purity = getNodePurityPercentage(node, user);
- const density = getNodeDensityPercentage(node, user);
- const experience = getNodeExperience(node, density, user);
- const ore = 16;
- const experienceRate = experience?.toFixed(2);
- const oreRate = getOreRate(density, purity, ore);
-
- node.children[0].children[0].setAttribute('data-after', `${experienceRate} xp/h ${oreRate} ore/h`);
- }
-
- function getNodePurityPercentage(node, user) {
- const column = node.children[0].children[0].children[2];
- if (!column || !column.querySelectorAll('.font-bold')[0]) return;
- const label = column.querySelectorAll('.font-bold')[0].parentElement;
- let percentage = Number(label.innerText.replace('%','').split(':')[1]);
-
- let miningLevel = getMiningLevel(user);
- percentage = percentage + (miningLevel * 0.1);
-
- return percentage;
- }
-
- function getNodeDensityPercentage(node, user) {
- const column = node.children[0].children[0].children[1];
- if (!column || !column.querySelectorAll('.font-bold')[0]) return;
- const label = column.querySelectorAll('.font-bold')[0].parentElement;
- let percentage = Number(label.innerText.replace('%','').split(':')[1]);
-
- let miningLevel = getMiningLevel(user);
- percentage = percentage + (miningLevel * 0.1);
-
- return percentage;
- }
-
- function getNodeExperience(node, density, user) {
- /** Gets the Exp: label */
- const column = node.children[0].children[0].children[3];
- if (!column || !column.querySelectorAll('.font-bold')[0]) return;
- const label = column.querySelectorAll('.font-bold')[0].parentElement;
- let value = Number(label.innerText.replace('%','').split(':')[1]);
-
- const skilledExtraction = getSkilledExtractionLevel(user);
- const knowledgeExtraction = getKnowledgeExtractionLevel(user) / 100;
-
- const ore = getActiveMining();
- const expertiseLevel = getExpertiseLevel(user);
- const expertiseGain = getExpertiseXPGainByOre(ore);
- const additionalMiningGain = expertiseLevel / 5 * expertiseGain;
- console.log('additionalMiningGain:', additionalMiningGain);
-
- value += additionalMiningGain;
-
- const actionsPerHour = getActionsPerHour(density);
- const experienceBase = value * actionsPerHour;
- const experienceSkilled = actionsPerHour * skilledExtraction;
- const experienceKnowledge = value * knowledgeExtraction;
-
- value = experienceBase + experienceSkilled + experienceKnowledge;
-
- const vip = isUserVIP(user);
-
- value *= vip ? 1.1 : 1;
-
- return value;
- }
-
- function getActionsPerHour(density) {
- return 3600 / (60 / (density / 100))
- }
-
- function getExperienceRate(density, experience) {
- return Number((3600 / (60 / (density / 100)) * experience).toFixed(2));
- }
-
- function getOreRate(density, purity, ore) {
- return Number((3600 / (60 / (density / 100)) * (purity / 100) * ore).toFixed(2));
- }
- }
-
- function isUserVIP(user) {
- return new Date(user.vip_expires) > new Date();
- }
-
- updateExperienceRates();
- window.elethorResourceInterval = setInterval(updateExperienceRates, 500);
- initializeResourceNodeView();
-
- async function initializeResourceNodeView() {
- await waitForField(document, 'head');
-
- let css = '[data-after]::after { content: attr(data-after); padding: 12px;}';
-
- appendCSS(css);
- }
- })();
-
- async function loadMarketRecyclobotVisualizer() {
- if (!window.egpItemPoints) {
- const companions = await getCompanions();
- const recyclobot = companions.recyclobot;
- window.egpItemPoints = recyclobot.itemPoints;
-
- console.log(`[${moduleName} v${version}] Companion Data initialized.`);
- }
-
- window.elethorGeneralPurposeOnLocationChange.addEventListener('EGPLocation', async function (e) {
- if (e && e.newLocation) {
- window.egpRecyclobotOnMarket = e.newLocation.includes('/market/');
-
- if (!window.egpRecyclobotOnMarket) {
- return;
- }
-
- if (!window.egpItemPoints) {
- console.warn(`[${moduleName} v${version}] Entered market page but had no recyclobot item points list.`);
- return;
- }
-
- const marketObjects = getMarketListingObjects();
-
- for (const marketListing of marketObjects) {
- for (const recyclobotEntry of window.egpItemPoints) {
- if (recyclobotEntry.item_name === marketListing.name) {
- //marketListing.element.setAttribute('data-points-recyclobot',`R: ${recyclobotEntry.quantity}:${recyclobotEntry.points}`);
- break;
- }
- }
- }
- }
- });
-
- function getMarketListingObjects() {
- return Array.prototype.slice.call(document.querySelectorAll('.is-market-side-menu ul li')).map((e) => {return {element: e, name: e.innerText}});
- }
-
- async function getCompanions() {
- return (await window.axios.get('/game/companions')).data;
- }
- };
-
- function setPackAmount(amount) {
- window.packAmount = amount;
-
- if (window.packAmount < 0) {
- window.packAmount = 0;
- }
- }
-
- window.setPackAmount = setPackAmount;
-
- function formatNormalNumber(num){
- return num.toLocaleString();
- }
-
- function appendCSS(css) {
- let head = document.head || document.getElementsByTagName('head')[0];
- let style = document.createElement('style');
-
- head.appendChild(style);
-
- style.type = 'text/css';
- if (style.styleSheet){
- // This is required for IE8 and below.
- style.styleSheet.cssText = css;
- } else {
- style.appendChild(document.createTextNode(css));
- }
- }
-
- function getMiningLevel(user) {
- for (const skill of user.skills) {
- if (skill.id === 1) {
- return skill.pivot.level;
- }
- }
-
- return 0;
- }
-
- function getSkilledExtractionLevel(user) {
- for (const skill of user.skills) {
- if (skill.id === 17) {
- return skill.pivot.level;
- }
- }
-
- return 0;
- }
-
- function getKnowledgeExtractionLevel(user) {
- for (const skill of user.skills) {
- if (skill.id === 18) {
- return skill.pivot.level;
- }
- }
-
- return 0;
- }
-
- function getExpertiseLevel(user) {
- for (const skill of user.skills) {
- if (skill.id === 38) {
- return skill.pivot.level;
- }
- }
-
- return 0;
- }
-
- expertiseXPGainByOre = {
- 'Orthoclase': 780,
- 'Anorthite': 825,
- 'Ferrisum': 600,
- 'Rhenium': 465,
- 'Jaspil': 480,
- 'Skasix': 0,
- };
-
- function getExpertiseXPGainByOre(ore) {
- return expertiseXPGainByOre[ore];
- }
-
- function getActiveMining() {
- const activeMiningButtons = document.querySelectorAll('.buttons .button.is-success');
-
- if (!activeMiningButtons[0] ||
- !activeMiningButtons[0].children[0] ||
- !activeMiningButtons[0].children[0].innerText ||
- !activeMiningButtons[0].children[0].innerText.trim
- ) {
- return '';
- }
-
- return activeMiningButtons[0].children[0].innerText.trim();
- }
-
- function getPackLevel(user) {
- for (const skill of user.skills) {
- if (skill.id === 22) {
- return skill.pivot.level;
- }
- }
-
- return 0;
- }
-
- function getCarnageLevel(user) {
- for (const skill of user.skills) {
- if (skill.id === 23) {
- return skill.pivot.level;
- }
- }
-
- return 0;
- }
-
- function getAttackSpeed(speed) {
- return 50 - (50 * speed / (speed + 400));
- }
-
- function getHealth(fortitude) {
- return 100000 * fortitude / (fortitude + 4000);
- }
-
- async function waitForField(target, field) {
- return new Promise((resolve, reject) => {
- const interval = setInterval(() => {
- if (target[field] !== undefined) {
- clearInterval(interval);
- resolve();
- }
- }, 100);
- });
- }
-
- async function waitForUser() {
- return new Promise((resolve, reject) => {
- const interval = setInterval(() => {
- if (currentUserData.user && currentUserData.user.id !== undefined) {
- clearInterval(interval);
- resolve();
- }
- }, 100);
- });
- }
-
- (async function loadAlarms() {
- await waitForField(window, 'Echo');
- await waitForField(document, 'head');
- await waitForUser();
-
-
- const meta1 = document.createElement('meta');
- meta1.setAttribute('content', "media-src https://furious.no/elethor/sound/");
- meta1.setAttribute('http-equiv', 'Content-Security-Policy');
- document.getElementsByTagName('head')[0].appendChild(meta1);
-
- /**
- * You can add an alternative audio by setting the local storage keys:
- * localStorage.setItem('egpSoundOutOfActions', 'your-url')
- * localStorage.setItem('egpSoundQuestCompleted', 'your-url')
- * */
-
- // Alternative: https://furious.no/elethor/sound/elethor-actions-have-ran-out.mp3
- window.egpSoundOutOfActions =
- localStorage.getItem('egpSoundOutOfActions') || 'https://furious.no/elethor/sound/out-of-actions.wav';
-
- // Alternative: https://furious.no/elethor/sound/elethor-quest-completed.mp3
- window.egpSoundQuestCompleted =
- localStorage.getItem('egpSoundQuestCompleted') || 'https://furious.no/elethor/sound/quest-complete.wav';
-
- const masterAudioLevel = 0.3;
-
- window.Echo.private(`App.User.${currentUserData.user.id}`).listen(".App\\Domain\\Monster\\Events\\FightingAgain", handleActionData);
- window.Echo.private(`App.User.${currentUserData.user.id}`).listen(".App\\Domain\\ResourceNode\\Events\\GatheringAgain", handleActionData);
- window.Echo.private(`App.User.${currentUserData.user.id}`).listen(".App\\Domain\\Quest\\Events\\UpdateQuestProgress", handleQuestData);
-
- let hasWarnedAboutActions = false;
- let hasWarnedAboutQuest = false;
-
- /**
- * Warns once when action runs out.
- * */
- function handleActionData(data) {
- if (data && data.action) {
- if (data.action.remaining <= 0 && !hasWarnedAboutActions) {
- playOutOfActions();
- hasWarnedAboutActions = true;
- } else if (data.action.remaining > 0 && hasWarnedAboutActions) {
- hasWarnedAboutActions = false;
- }
- }
- }
- window.handleActionData = handleActionData;
-
- /**
- * Warns once when quest completes.
- * */
- function handleQuestData(data) {
- if (data
- && data.step
- && data.step.tasks) {
- if (data.step.progress >= data.step.tasks[0].quantity && !hasWarnedAboutQuest) {
- playQuestCompleted();
- hasWarnedAboutQuest = true;
- } else if (data.step.progress < data.step.tasks[0].quantity && hasWarnedAboutQuest) {
- hasWarnedAboutQuest = false;
- }
- }
- }
- window.handleQuestData = handleQuestData;
-
- function playOutOfActions() {
- playSound(window.egpSoundOutOfActions);
- }
- window.playOutOfActions = playOutOfActions;
-
- function playQuestCompleted() {
- playSound(window.egpSoundQuestCompleted);
- }
- window.playQuestCompleted = playQuestCompleted;
-
- function playSound(sound, volume = masterAudioLevel){
- const audio = new Audio(sound);
-
- audio.volume = volume;
- audio.play();
- }
- })();
- })();