Sleazy Fork is available in English.
Gets Tags/Details/json for Tachiyomi/Suwayomi/Mihon from nhentai, hitomi, imhentai, e-hentai
// ==UserScript==
// @name TSM Tag Catcher
// @namespace https://sleazyfork.org/en/users/1261593-john-doe4
// @version 1.5
// @description Gets Tags/Details/json for Tachiyomi/Suwayomi/Mihon from nhentai, hitomi, imhentai, e-hentai
// @author john doe4
// @match *://nhentai.net/g/*
// @match *://hitomi.la/doujinshi/*
// @match *://imhentai.xxx/gallery/*
// @match *://e-hentai.org/g/*/*
// @exclude *://nhentai.net/g/
// @exclude *://hitomi.la/doujinshi/
// @exclude *://imhentai.xxx/gallery/
// @exclude *://e-hentai.org/g/
// @icon https://docs.google.com/uc?export=download&id=1jRJOg4mZUI04_NMatSMFFkKidKrtuPce
// @grant GM_setClipboard
// @license GPLv3
// ==/UserScript==
(function() {
'use strict';
var domObserver = null;
var reinjectTimer = null;
var pageLoadDownloadFired = false;
var host = window.location.hostname.replace('www.', '');
function initPreferences() {
if (localStorage.getItem('nhTheme') === null) localStorage.setItem('nhTheme', 'light');
if (localStorage.getItem('nhStatus') === null) localStorage.setItem('nhStatus', '2');
}
initPreferences();
function getPreference(key) {
return localStorage.getItem('nh' + key) === 'true';
}
function setPreference(key, value) {
localStorage.setItem('nh' + key, value.toString());
if (key === 'AutoDownload' && value) {
localStorage.setItem('nhPageLoadDownload', 'false');
var el = document.getElementById('page-load-download');
if (el) el.checked = false;
} else if (key === 'PageLoadDownload' && value) {
localStorage.setItem('nhAutoDownload', 'false');
var el = document.getElementById('auto-download');
if (el) el.checked = false;
}
}
function getTheme() { return localStorage.getItem('nhTheme') || 'light'; }
function setTheme(theme) {
localStorage.setItem('nhTheme', theme);
applyTheme();
updateThemeLabel();
updateStatusButtonsStyle();
updateButtonStyles();
updateDescriptionBox();
}
function applyThemeClass() {
if (getTheme() === 'dark') document.body.classList.add('dark-theme');
else document.body.classList.remove('dark-theme');
}
function updateThemeLabel() {
var el = document.getElementById('theme-label');
if (el) el.textContent = getTheme() === 'dark' ? 'Dark' : 'Light';
}
function updateStatusButtonsStyle() {
var theme = getTheme();
var selected = localStorage.getItem('nhStatus') || '2';
document.querySelectorAll('.status-label').forEach(function(label) {
var radio = label.parentElement.querySelector('input[type="radio"]');
if (radio && radio.value === selected) {
label.style.backgroundColor = '#4CAF50';
label.style.color = 'white';
} else {
label.style.backgroundColor = theme === 'dark' ? '#3d3d3d' : '#f0f0f0';
label.style.color = theme === 'dark' ? '#ffffff' : '#333333';
}
});
}
function updateButtonStyles() {
var theme = getTheme();
['copy-btn', 'download-btn', 'catch-tags-btn'].forEach(function(id) {
var btn = document.getElementById(id);
if (btn) {
btn.style.backgroundColor = theme === 'dark' ? '#3a3a3a' : '#f0f0f0';
btn.style.color = theme === 'dark' ? '#ffffff' : '#333333';
btn.style.borderColor = theme === 'dark' ? '#555' : '#bbb';
}
});
}
function updateDescriptionBox() {
var el = document.getElementById('nh-description');
if (el) el.style.borderColor = getTheme() === 'dark' ? '#555' : '#bbb';
}
function applyTheme() {
var theme = getTheme();
var container = document.getElementById('tachiyomi-json');
if (!container) return;
var textarea = container.querySelector('textarea#json-output');
if (!textarea) return;
if (theme === 'dark') {
container.style.backgroundColor = '#2d2d2d';
container.style.color = '#ffffff';
container.style.borderColor = '#444';
textarea.style.backgroundColor = '#1a1a1a';
textarea.style.color = '#ffffff';
textarea.style.borderColor = '#555';
} else {
container.style.backgroundColor = '#f5f5f5';
container.style.color = '#333333';
container.style.borderColor = '#e0e0e0';
textarea.style.backgroundColor = '#ffffff';
textarea.style.color = '#333333';
textarea.style.borderColor = '#bbb';
}
updateStatusButtonsStyle();
updateButtonStyles();
updateDescriptionBox();
}
var buttonCooldowns = {};
function copyToClipboard() {
var jsonOutput = document.getElementById('json-output');
if (!jsonOutput || !jsonOutput.value) return;
GM_setClipboard(jsonOutput.value, 'text');
var btn = document.getElementById('copy-btn');
if (btn && !buttonCooldowns['copy']) {
var orig = btn.textContent;
btn.textContent = 'Copied!';
buttonCooldowns['copy'] = true;
setTimeout(function() { btn.textContent = orig; buttonCooldowns['copy'] = false; }, 1500);
}
}
function copyTagsToClipboard() {
var tags = getSiteData().genre.join(', ');
if (!tags) return;
GM_setClipboard(tags, 'text');
var btn = document.getElementById('catch-tags-btn');
if (btn && !buttonCooldowns['tags']) {
var orig = btn.textContent;
btn.textContent = 'Tags Copied!';
buttonCooldowns['tags'] = true;
setTimeout(function() { btn.textContent = orig; buttonCooldowns['tags'] = false; }, 1500);
}
}
function downloadJson() {
var jsonOutput = document.getElementById('json-output');
if (!jsonOutput || !jsonOutput.value) return;
var blob = new Blob([jsonOutput.value], { type: 'application/json' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'details.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function cleanText(str) {
return str.replace(/[♀♂]/g, '').trim();
}
function getDirectText(el) {
var text = '';
el.childNodes.forEach(function(node) {
if (node.nodeType === Node.TEXT_NODE) text += node.textContent;
});
return text.trim();
}
function getSiteData() {
var data = { title: '', author: '', artist: '', genre: [], status: localStorage.getItem('nhStatus') || '2' };
var descEl = document.getElementById('nh-description');
data.description = descEl ? descEl.value : '';
if (host === 'nhentai.net') {
data.title = getNhentaiTitle();
data.author = getNhentaiAuthor();
data.artist = data.author;
data.genre = getNhentaiTags('Tags:');
} else if (host === 'hitomi.la') {
data.title = getHitomiTitle();
data.author = getHitomiArtist();
data.artist = data.author;
data.genre = getHitomiTags();
} else if (host === 'imhentai.xxx') {
data.title = getImhentaiTitle();
data.author = getImhentaiField('Artists:');
data.artist = data.author;
data.genre = getImhentaiTags();
} else if (host === 'e-hentai.org') {
data.title = getEhentaiTitle();
data.author = getEhentaiUploader() || getEhentaiField('artist');
data.artist = data.author;
data.genre = getEhentaiTags();
}
var statusEl = document.querySelector('input[name="nhstatus"]:checked');
if (statusEl) data.status = statusEl.value;
return data;
}
function getNhentaiTitle() {
var el = document.querySelector('.title .pretty');
if (el) return el.textContent.trim();
var h1 = document.querySelector('h1.title');
if (h1) { var p = h1.querySelector('.pretty'); return p ? p.textContent.trim() : h1.textContent.trim(); }
return '';
}
function getNhentaiTagContainerByLabel(label) {
var containers = document.querySelectorAll('#tags .tag-container');
for (var i = 0; i < containers.length; i++) {
if (containers[i].textContent.trim().startsWith(label)) return containers[i];
}
return null;
}
function getNhentaiTags(label) {
var container = getNhentaiTagContainerByLabel(label);
if (!container) return [];
return Array.from(container.querySelectorAll('.name')).map(function(el) { return el.textContent.trim(); });
}
function getNhentaiAuthor() {
var artists = getNhentaiTags('Artists:');
if (artists.length > 0) return artists[0];
var container = getNhentaiTagContainerByLabel('Groups:');
if (container) {
var link = container.querySelector('a[href*="/group/"]');
if (link) {
var match = link.getAttribute('href').match(/\/group\/([^/]+)\//);
if (match) return match[1].replace(/-/g, ' ');
}
}
var gid = document.getElementById('gallery_id');
return gid ? gid.textContent.replace('#', '').trim() : '';
}
function getHitomiTitle() {
var el = document.querySelector('#gallery-brand a');
return el ? el.textContent.trim() : '';
}
function getHitomiArtist() {
var el = document.querySelector('#artists ul li a');
if (el) return el.textContent.trim();
var group = document.querySelector('#groups ul li a');
return group ? group.textContent.trim() : '';
}
function getHitomiTags() {
var items = document.querySelectorAll('#tags li a');
return Array.from(items).map(function(el) { return cleanText(el.textContent); });
}
function getImhentaiTitle() {
var el = document.querySelector('.right_details h1');
return el ? el.textContent.trim() : '';
}
function getImhentaiField(label) {
var items = document.querySelectorAll('.galleries_info li');
for (var i = 0; i < items.length; i++) {
var span = items[i].querySelector('.tags_text');
if (span && span.textContent.trim() === label) {
var link = items[i].querySelector('a.tag');
return link ? getDirectText(link) : '';
}
}
return '';
}
function getImhentaiTags() {
var items = document.querySelectorAll('.galleries_info li');
for (var i = 0; i < items.length; i++) {
var span = items[i].querySelector('.tags_text');
if (span && span.textContent.trim() === 'Tags:') {
return Array.from(items[i].querySelectorAll('a.tag')).map(function(el) {
return getDirectText(el);
});
}
}
return [];
}
function getEhentaiTitle() {
var el = document.getElementById('gn');
return el ? el.textContent.trim() : '';
}
function getEhentaiUploader() {
var el = document.querySelector('#gdn a');
return el ? el.textContent.trim() : '';
}
function getEhentaiField(category) {
var rows = document.querySelectorAll('#taglist table tr');
for (var i = 0; i < rows.length; i++) {
var tc = rows[i].querySelector('td.tc');
if (tc && tc.textContent.replace(':', '').trim() === category) {
var links = rows[i].querySelectorAll('td:last-child a');
if (links.length > 0) return links[0].textContent.trim();
}
}
return '';
}
function getEhentaiTags() {
var tags = [];
var skipCategories = ['parody', 'character', 'group', 'language'];
var rows = document.querySelectorAll('#taglist table tr');
rows.forEach(function(row) {
var tc = row.querySelector('td.tc');
if (!tc) return;
var cat = tc.textContent.replace(':', '').trim();
if (skipCategories.indexOf(cat) !== -1) return;
var links = row.querySelectorAll('td:last-child a');
links.forEach(function(a) { tags.push(a.textContent.trim()); });
});
return tags;
}
function getEhentaiButtonAnchor() {
var rows = document.querySelectorAll('#taglist table tr');
for (var i = 0; i < rows.length; i++) {
var tc = rows[i].querySelector('td.tc');
if (tc && tc.textContent.replace(':', '').trim() === 'other') {
return rows[i].querySelector('td:last-child');
}
}
var lastRow = rows.length > 0 ? rows[rows.length - 1] : null;
return lastRow ? lastRow.querySelector('td:last-child') : null;
}
function updateJsonData() {
var jsonOutput = document.getElementById('json-output');
if (!jsonOutput) return;
try {
var data = getSiteData();
jsonOutput.value = JSON.stringify({
title: data.title,
author: data.author,
artist: data.artist,
description: data.description,
genre: data.genre,
status: data.status
}, null, 4);
} catch (e) {
console.error('TSM Tag Catcher: error building JSON', e);
}
}
function generateJson() {
var jsonDiv = document.getElementById('tachiyomi-json');
if (!jsonDiv) return;
if (jsonDiv.style.display === 'none') {
jsonDiv.style.display = 'block';
} else {
jsonDiv.style.display = 'none';
return;
}
var isDark = getTheme() === 'dark';
jsonDiv.innerHTML = '<div style="padding: 15px; border-radius: 6px; width: 100%; box-sizing: border-box;">' +
'<textarea id="json-output" style="width: 100%; height: 200px; padding: 10px; border-radius: 4px; margin-bottom: 10px; font-family: monospace; border: 1px solid #bbb; font-size: 14px; resize: vertical; box-sizing: border-box;"></textarea>' +
'<div style="display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-start; width: 100%;">' +
'<div style="display: flex; flex-direction: column; gap: 5px; flex: 1; min-width: 200px;">' +
'<div style="display: flex; flex-wrap: wrap; gap: 8px;">' +
'<button id="copy-btn" style="padding: 7px 14px; min-width: 120px; font-size: 13px; flex: 1; border-radius: 4px; border: 1px solid #bbb; cursor: pointer; font-weight: 600; letter-spacing: 0.02em;">Copy to Clipboard</button>' +
'<button id="download-btn" style="padding: 7px 14px; min-width: 120px; font-size: 13px; flex: 1; border-radius: 4px; border: 1px solid #bbb; cursor: pointer; font-weight: 600; letter-spacing: 0.02em;">Download JSON</button>' +
'<button id="catch-tags-btn" style="padding: 7px 14px; min-width: 100px; font-size: 13px; flex: 1; border-radius: 4px; border: 1px solid #bbb; cursor: pointer; font-weight: 600; letter-spacing: 0.02em;">Catch Tags</button>' +
'</div>' +
'<div style="display: flex; flex-wrap: wrap; gap: 15px; margin-top: 5px;">' +
'<label style="display: flex; align-items: center; gap: 5px; font-size: 13px; cursor: pointer;">' +
'<input type="checkbox" id="auto-download" ' + (getPreference('AutoDownload') ? 'checked' : '') + ' style="margin: 0; accent-color: #4CAF50;">Auto Download</label>' +
'<label style="display: flex; align-items: center; gap: 5px; font-size: 13px; cursor: pointer;">' +
'<input type="checkbox" id="page-load-download" ' + (getPreference('PageLoadDownload') ? 'checked' : '') + ' style="margin: 0; accent-color: #4CAF50;">Download on Load</label>' +
'</div>' +
'</div>' +
'<div style="display: flex; align-items: center; gap: 6px; margin-left: auto; align-self: flex-end;">' +
'<span style="font-size: 13px;">Theme:</span>' +
'<label class="tsm-switch"><input type="checkbox" id="theme-toggle" ' + (isDark ? 'checked' : '') + '><span class="tsm-slider"></span></label>' +
'<span id="theme-label" style="font-size: 13px; min-width: 36px;">' + (isDark ? 'Dark' : 'Light') + '</span>' +
'</div>' +
'</div>' +
'</div>';
applyTheme();
updateJsonData();
document.getElementById('copy-btn').addEventListener('click', copyToClipboard);
document.getElementById('download-btn').addEventListener('click', downloadJson);
document.getElementById('catch-tags-btn').addEventListener('click', copyTagsToClipboard);
document.getElementById('auto-download').addEventListener('change', function() { setPreference('AutoDownload', this.checked); });
document.getElementById('page-load-download').addEventListener('change', function() { setPreference('PageLoadDownload', this.checked); });
document.getElementById('theme-toggle').addEventListener('change', function() { setTheme(this.checked ? 'dark' : 'light'); });
var descEl = document.getElementById('nh-description');
if (descEl) descEl.addEventListener('input', updateJsonData);
if (getPreference('AutoDownload')) setTimeout(downloadJson, 100);
}
function initStatusButtons() {
document.querySelectorAll('.status-btn input[type="radio"]').forEach(function(radio) {
radio.addEventListener('change', function() {
if (this.checked) {
localStorage.setItem('nhStatus', this.value);
updateStatusButtonsStyle();
updateJsonData();
}
});
});
updateStatusButtonsStyle();
}
function buildControlsHTML() {
var savedStatus = localStorage.getItem('nhStatus') || '2';
return '<div id="nh-controls" style="margin-top: 10px;">' +
'<div style="margin-bottom: 10px;">' +
'<div class="status-buttons" style="display: flex; gap: 10px; margin-top: 5px;">' +
'<label class="status-btn" style="flex: 1; position: relative;">' +
'<input type="radio" name="nhstatus" value="1" ' + (savedStatus === '1' ? 'checked' : '') + ' style="position: absolute; opacity: 0; width: 0; height: 0;">' +
'<span class="status-label" style="display: flex; align-items: center; justify-content: center; height: 100%; padding: 6px; text-align: center; border-radius: 4px; font-size: 0.9em; cursor: pointer;">Ongoing</span>' +
'</label>' +
'<label class="status-btn" style="flex: 1; position: relative;">' +
'<input type="radio" name="nhstatus" value="2" ' + (savedStatus === '2' ? 'checked' : '') + ' style="position: absolute; opacity: 0; width: 0; height: 0;">' +
'<span class="status-label" style="display: flex; align-items: center; justify-content: center; height: 100%; padding: 6px; text-align: center; border-radius: 4px; font-size: 0.9em; cursor: pointer;">Completed</span>' +
'</label>' +
'</div>' +
'</div>' +
'<div style="margin-bottom: 15px;">' +
'<textarea id="nh-description" placeholder="Add description" style="width: 100%; padding: 8px; border-radius: 4px; border: 1px solid #bbb; margin-top: 5px; background: inherit; font-size: 14px; box-sizing: border-box;"></textarea>' +
'</div>' +
'</div>';
}
function makeCatcherButton() {
var btn = document.createElement('button');
btn.id = 'nh-catcher-btn';
btn.textContent = 'Catcher';
btn.style.cssText = 'margin: 4px 0 0 8px; padding: 6px 16px; font-size: 13px; font-weight: 700; letter-spacing: 0.04em; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; border-radius: 4px; border: 2px solid #4CAF50; background: #4CAF50; color: #fff; transition: background 0.2s, border-color 0.2s;';
btn.addEventListener('mouseover', function() {
this.style.background = '#43a047';
this.style.borderColor = '#43a047';
});
btn.addEventListener('mouseout', function() {
this.style.background = '#4CAF50';
this.style.borderColor = '#4CAF50';
});
btn.addEventListener('click', generateJson);
return btn;
}
function isUIPresent() {
return !!document.getElementById('nh-catcher-btn');
}
function injectUI() {
if (isUIPresent()) return;
var config = getSiteConfig();
if (!config) return;
var anchorEl = typeof config.buttonAnchor === 'function' ? config.buttonAnchor() : document.querySelector(config.buttonAnchor);
var containerEl = document.querySelector(config.jsonContainer);
var controlsAnchorEl = config.controlsAfterJson ? true : document.querySelector(config.controlsAnchor);
if (!anchorEl || !containerEl || !controlsAnchorEl) return;
var catcherBtn = makeCatcherButton();
if (config.buttonInsertMode === 'append') {
anchorEl.appendChild(catcherBtn);
} else if (config.buttonInsertMode === 'prepend') {
anchorEl.insertBefore(catcherBtn, anchorEl.firstChild);
} else {
anchorEl.parentNode.insertBefore(catcherBtn, anchorEl.nextSibling);
}
var jsonDiv = document.createElement('div');
jsonDiv.id = 'tachiyomi-json';
jsonDiv.style.cssText = 'display:none;margin-top:15px;width:100%;clear:both;';
if (config.jsonInsertMode === 'after') {
var panelWidth = containerEl.getBoundingClientRect().width;
var panelWrap = document.createElement('div');
panelWrap.style.cssText = 'width:' + panelWidth + 'px;max-width:100%;margin-left:auto;margin-right:auto;box-sizing:border-box;';
containerEl.parentNode.insertBefore(panelWrap, containerEl.nextSibling);
panelWrap.appendChild(jsonDiv);
} else {
containerEl.appendChild(jsonDiv);
}
var controlsDiv = document.createElement('div');
controlsDiv.innerHTML = buildControlsHTML();
if (config.controlsAfterJson) {
jsonDiv.parentNode.insertBefore(controlsDiv, jsonDiv.nextSibling);
} else {
controlsAnchorEl.appendChild(controlsDiv);
}
initStatusButtons();
updateStatusButtonsStyle();
applyThemeClass();
if (getPreference('PageLoadDownload') && !pageLoadDownloadFired) {
pageLoadDownloadFired = true;
setTimeout(function() {
generateJson();
var jd = document.getElementById('tachiyomi-json');
if (jd && jd.style.display === 'block') downloadJson();
}, 500);
}
}
function getSiteConfig() {
if (host === 'nhentai.net') {
return {
buttonAnchor: '.buttons',
buttonInsertMode: 'append',
jsonContainer: '#info',
jsonInsertMode: 'append',
controlsAnchor: '#tags',
readySelectors: ['.buttons', '#info', '#tags']
};
}
if (host === 'hitomi.la') {
return {
buttonAnchor: '#tags',
buttonInsertMode: 'after',
jsonContainer: '.gallery-info',
jsonInsertMode: 'append',
controlsAnchor: '.gallery-info',
readySelectors: ['#tags', '.gallery-info']
};
}
if (host === 'imhentai.xxx') {
return {
buttonAnchor: '.g_buttons',
buttonInsertMode: 'append',
jsonContainer: '.right_details',
jsonInsertMode: 'append',
controlsAnchor: '.galleries_info',
readySelectors: ['.g_buttons', '.right_details', '.galleries_info']
};
}
if (host === 'e-hentai.org') {
return {
buttonAnchor: getEhentaiButtonAnchor,
buttonInsertMode: 'append',
jsonContainer: '.gm',
jsonInsertMode: 'after',
controlsAfterJson: true,
readySelectors: ['#taglist', '.gm']
};
}
return null;
}
function scheduleReinject() {
if (reinjectTimer) clearTimeout(reinjectTimer);
reinjectTimer = setTimeout(function() {
reinjectTimer = null;
injectUI();
}, 300);
}
function startObserver() {
if (domObserver) return;
domObserver = new MutationObserver(function(mutations) {
for (var i = 0; i < mutations.length; i++) {
var mutation = mutations[i];
if (mutation.type !== 'childList' || mutation.removedNodes.length === 0) continue;
for (var j = 0; j < mutation.removedNodes.length; j++) {
var node = mutation.removedNodes[j];
if (node.id === 'nh-catcher-btn' ||
node.id === 'tachiyomi-json' ||
node.id === 'nh-controls' ||
(node.querySelector && (
node.querySelector('#nh-catcher-btn') ||
node.querySelector('#nh-controls')
))) {
scheduleReinject();
return;
}
}
}
});
domObserver.observe(document.body, { childList: true, subtree: true });
}
var style = document.createElement('style');
style.textContent = '.tsm-switch{position:relative;display:inline-block;width:50px;height:24px}' +
'.tsm-switch input{opacity:0;width:0;height:0}' +
'.tsm-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#ccc;transition:.4s;border-radius:24px}' +
'.tsm-slider:before{position:absolute;content:"";height:16px;width:16px;left:4px;bottom:4px;background-color:white;transition:.4s;border-radius:50%}' +
'input:checked+.tsm-slider{background-color:#4CAF50}' +
'input:checked+.tsm-slider:before{transform:translateX(26px)}' +
'.status-btn span{transition:all 0.3s ease}' +
'.status-btn:hover span{filter:brightness(0.9)}' +
'#copy-btn,#download-btn,#catch-tags-btn{display:flex!important;align-items:center!important;justify-content:center!important;transition:background-color 0.2s,border-color 0.2s;white-space:nowrap}' +
'#json-output::-webkit-scrollbar{width:8px}' +
'#json-output::-webkit-scrollbar-track{background:#f1f1f1;border-radius:4px}' +
'#json-output::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:4px}' +
'.dark-theme #json-output::-webkit-scrollbar-track{background:#3d3d3d}' +
'.dark-theme #json-output::-webkit-scrollbar-thumb{background:#666}' +
'@media(max-width:600px){#json-output{height:150px!important;font-size:13px!important}' +
'.status-buttons{flex-direction:column!important}' +
'#copy-btn,#download-btn,#catch-tags-btn{padding:8px 10px!important;min-width:100px!important;font-size:12px!important}' +
'.status-label{padding:8px!important}}';
document.head.appendChild(style);
function waitForReady(callback, maxWait) {
var config = getSiteConfig();
if (!config) return;
var selectors = config.readySelectors;
var waited = 0;
var interval = setInterval(function() {
var allFound = selectors.every(function(sel) { return !!document.querySelector(sel); });
if (allFound) {
clearInterval(interval);
callback();
} else {
waited += 100;
if (waited >= (maxWait || 10000)) clearInterval(interval);
}
}, 100);
}
waitForReady(function() {
injectUI();
startObserver();
}, 10000);
})();