348 lines
13 KiB
JavaScript
348 lines
13 KiB
JavaScript
// ─── Safe Mode (Recording Mode for Streamers/Bloggers) ─────────────────────
|
||
// Replaces gambling terms with YouTube-safe synonyms
|
||
|
||
const SafeMode = (() => {
|
||
const STORAGE_KEY = 'safe_mode';
|
||
|
||
const DICT = [
|
||
// Currency
|
||
[/₽(?=\s*\d)/g, 'тугриков'],
|
||
[/(\d+)\s*₽/g, (_, n) => {
|
||
const num = parseInt(n);
|
||
const last = num % 10;
|
||
const lastTwo = num % 100;
|
||
if (lastTwo >= 11 && lastTwo <= 19) return n + ' тугриков';
|
||
if (last === 1) return n + ' тугрик';
|
||
if (last >= 2 && last <= 4) return n + ' тугрика';
|
||
return n + ' тугриков';
|
||
}],
|
||
[/₽(?!\d)/g, 'тугриков'],
|
||
|
||
// Cases
|
||
[/\b(открыть|открывай|открыл)\s+кейс/gi, (m) => m.replace(/кейс/i, 'бокс')],
|
||
[/\bкейс[а-я]*\b/gi, (m) => {
|
||
const lower = m.toLowerCase();
|
||
if (lower === 'кейс') return 'бокс';
|
||
if (lower === 'кейса') return 'бокса';
|
||
if (lower === 'кейсу') return 'боксу';
|
||
if (lower === 'кейсом') return 'боксом';
|
||
if (lower === 'кейсе') return 'боксе';
|
||
if (lower === 'кейсы') return 'боксы';
|
||
if (lower === 'кейсов') return 'боксов';
|
||
if (lower === 'кейсам') return 'боксам';
|
||
if (lower === 'кейсами') return 'боксами';
|
||
if (lower === 'кейсах') return 'боксах';
|
||
return m;
|
||
}],
|
||
|
||
// Case (English)
|
||
[/\bcase\b/gi, 'box'],
|
||
|
||
// Skins
|
||
[/\bскин[а-я]*\b/gi, (m) => {
|
||
const lower = m.toLowerCase();
|
||
if (lower === 'скин') return 'облик';
|
||
if (lower === 'скина') return 'облика';
|
||
if (lower === 'скину') return 'облику';
|
||
if (lower === 'скином') return 'обликом';
|
||
if (lower === 'скине') return 'облике';
|
||
if (lower === 'скины' || lower === 'скин') return 'облики';
|
||
if (lower === 'скинов') return 'обликов';
|
||
if (lower === 'скинам') return 'обликам';
|
||
if (lower === 'скинами') return 'обликами';
|
||
if (lower === 'скинах') return 'обликах';
|
||
return m;
|
||
}],
|
||
|
||
// Contract
|
||
[/\bконтракт[а-я]*\b/gi, (m) => {
|
||
const lower = m.toLowerCase();
|
||
if (lower === 'контракт') return 'сделка';
|
||
if (lower === 'контракта') return 'сделки';
|
||
if (lower === 'контракту') return 'сделке';
|
||
if (lower === 'контрактом') return 'сделкой';
|
||
if (lower === 'контракте') return 'сделке';
|
||
if (lower === 'контракты' || lower === 'контракт') return 'сделки';
|
||
if (lower === 'контрактов') return 'сделок';
|
||
if (lower === 'контрактам') return 'сделкам';
|
||
if (lower === 'контрактами') return 'сделками';
|
||
if (lower === 'контрактах') return 'сделках';
|
||
if (lower === 'контрактная' || lower === 'контрактный' || lower === 'контрактное') return 'договорная';
|
||
return m;
|
||
}],
|
||
|
||
// Upgrade
|
||
[/\bапгрейд[а-я]*\b/gi, (m) => {
|
||
if (m.toLowerCase() === 'апгрейд') return 'улучшение';
|
||
if (m.toLowerCase() === 'апгрейда') return 'улучшения';
|
||
if (m.toLowerCase() === 'апгрейду') return 'улучшению';
|
||
if (m.toLowerCase() === 'апгрейдом') return 'улучшением';
|
||
if (m.toLowerCase() === 'апгрейде') return 'улучшении';
|
||
if (m.toLowerCase() === 'апгрейды') return 'улучшения';
|
||
if (m.toLowerCase() === 'апгрейдов') return 'улучшений';
|
||
return m;
|
||
}],
|
||
[/\bupgrade\b/gi, 'upgrade'],
|
||
|
||
// Float
|
||
[/\bfloat\b/gi, 'состояние'],
|
||
|
||
// Rarity
|
||
[/\bредкост[ьи][а-я]*\b/gi, (m) => {
|
||
const lower = m.toLowerCase();
|
||
if (lower === 'редкость') return 'класс';
|
||
if (lower === 'редкости') return 'классы';
|
||
if (lower === 'редкостью') return 'классом';
|
||
if (lower === 'редкостей') return 'классов';
|
||
if (lower === 'редкостям') return 'классам';
|
||
return m;
|
||
}],
|
||
|
||
// Chance
|
||
[/\bшанс[а-я]*\b/gi, (m) => {
|
||
const lower = m.toLowerCase();
|
||
if (lower === 'шанс') return 'вероятность';
|
||
if (lower === 'шанса') return 'вероятности';
|
||
if (lower === 'шансу') return 'вероятности';
|
||
if (lower === 'шансом') return 'вероятностью';
|
||
if (lower === 'шансе') return 'вероятности';
|
||
if (lower === 'шансы') return 'вероятности';
|
||
if (lower === 'шансов') return 'вероятностей';
|
||
return m;
|
||
}],
|
||
|
||
// Bet
|
||
[/\bставк[а-я]*\b/gi, (m) => {
|
||
const lower = m.toLowerCase();
|
||
if (lower === 'ставка') return 'вход';
|
||
if (lower === 'ставки') return 'входы';
|
||
if (lower === 'ставку') return 'вход';
|
||
if (lower === 'ставкой') return 'входом';
|
||
if (lower === 'ставок') return 'входов';
|
||
if (lower === 'ставкам') return 'входам';
|
||
return m;
|
||
}],
|
||
|
||
// Win / Lose
|
||
[/\bвыигрыш[а-я]*\b/gi, (m) => {
|
||
if (m.toLowerCase() === 'выигрыш' || m.toLowerCase() === 'выигрыша') return 'приз';
|
||
if (m.toLowerCase() === 'выигрышу') return 'призу';
|
||
if (m.toLowerCase() === 'выигрышем') return 'призом';
|
||
if (m.toLowerCase() === 'выигрыше') return 'призе';
|
||
return m;
|
||
}],
|
||
[/\b(выиграл|выиграла|выиграли)\b/gi, (m) => {
|
||
if (m.toLowerCase() === 'выиграл' || m.toLowerCase() === 'выиграла') return 'получил(a)';
|
||
if (m.toLowerCase() === 'выиграли') return 'получили';
|
||
return m;
|
||
}],
|
||
|
||
// Lose
|
||
[/\bпроиграл[а-я]*\b/gi, 'не получилось'],
|
||
|
||
// Crash game
|
||
[/\b(краш|crash)\b/gi, 'риск-игра'],
|
||
|
||
// Open case
|
||
[/\bоткры[тв][а-я]+\s+кейс/gi, (m) => m.replace(/кейс/i, 'бокс')],
|
||
|
||
// Wear
|
||
[/\bизнос\b/gi, 'состояние'],
|
||
|
||
// Probability display
|
||
[/\bвероятность\b/gi, 'шанс'],
|
||
|
||
// Quick mode tooltips
|
||
[/\bбыстрый режим\b/gi, 'турбо-режим'],
|
||
|
||
// Balance
|
||
[/\bбаланс\b/gi, 'счёт'],
|
||
|
||
// Sell
|
||
[/\bпрода[жв][а-я]*\b/gi, (m) => {
|
||
const lower = m.toLowerCase();
|
||
if (lower === 'продажа' || lower === 'продать') return 'обменять';
|
||
if (lower === 'продал') return 'обменял';
|
||
if (lower === 'продайте') return 'обменяйте';
|
||
return m;
|
||
}],
|
||
|
||
// Inventory
|
||
[/\bинвентар[ьи][а-я]*\b/gi, (m) => {
|
||
const lower = m.toLowerCase();
|
||
if (lower === 'инвентарь') return 'коллекция';
|
||
if (lower === 'инвентаря') return 'коллекции';
|
||
if (lower === 'инвентаре') return 'коллекции';
|
||
if (lower === 'инвентарём') return 'коллекцией';
|
||
return m;
|
||
}],
|
||
|
||
// Trade up
|
||
[/\btrade.?up\b/gi, 'treyd-ap'],
|
||
|
||
// Premium
|
||
[/\bпремиум\b/gi, 'эксклюзив'],
|
||
|
||
// Rare names in English
|
||
[/\bcovert\b/gi, 'covert'],
|
||
[/\bclassified\b/gi, 'classified'],
|
||
[/\bconsumers?\b/gi, 'consumer'],
|
||
[/\bindustrials?\b/gi, 'industrial'],
|
||
[/\bmil-spec\b/gi, 'mil-spec'],
|
||
[/\brestricted\b/gi, 'restricted'],
|
||
];
|
||
|
||
let observer = null;
|
||
let active = false;
|
||
|
||
function isActive() {
|
||
return localStorage.getItem(STORAGE_KEY) === 'true';
|
||
}
|
||
|
||
function setActive(val) {
|
||
localStorage.setItem(STORAGE_KEY, val ? 'true' : 'false');
|
||
active = val;
|
||
}
|
||
|
||
function replaceText(node) {
|
||
if (node.nodeType === Node.TEXT_NODE) {
|
||
let text = node.textContent;
|
||
let changed = false;
|
||
for (const [pattern, replacement] of DICT) {
|
||
const newText = text.replace(pattern, replacement);
|
||
if (newText !== text) {
|
||
text = newText;
|
||
changed = true;
|
||
}
|
||
}
|
||
if (changed) {
|
||
node.textContent = text;
|
||
}
|
||
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||
// Don't process script, style, input, textarea, canvas elements
|
||
const tag = node.tagName.toLowerCase();
|
||
if (tag === 'script' || tag === 'style' || tag === 'input' ||
|
||
tag === 'textarea' || tag === 'canvas' || tag === 'svg' ||
|
||
tag === 'code' || tag === 'pre') {
|
||
return;
|
||
}
|
||
// Process title and alt attributes
|
||
if (node.hasAttribute('title')) {
|
||
let t = node.getAttribute('title');
|
||
for (const [pattern, replacement] of DICT) {
|
||
t = t.replace(pattern, replacement);
|
||
}
|
||
node.setAttribute('title', t);
|
||
}
|
||
if (node.hasAttribute('alt')) {
|
||
let a = node.getAttribute('alt');
|
||
for (const [pattern, replacement] of DICT) {
|
||
a = a.replace(pattern, replacement);
|
||
}
|
||
node.setAttribute('alt', a);
|
||
}
|
||
// Process children
|
||
for (let i = 0; i < node.childNodes.length; i++) {
|
||
replaceText(node.childNodes[i]);
|
||
}
|
||
}
|
||
}
|
||
|
||
function apply() {
|
||
replaceText(document.body);
|
||
// Update document title
|
||
if (document.title) {
|
||
let t = document.title;
|
||
for (const [pattern, replacement] of DICT) {
|
||
t = t.replace(pattern, replacement);
|
||
}
|
||
document.title = t;
|
||
}
|
||
// Update safe mode button appearance
|
||
const btn = document.getElementById('safeModeToggle');
|
||
if (btn) {
|
||
btn.textContent = active ? '🛡️' : '🎙️';
|
||
btn.title = active ? 'Режим записи активен' : 'Режим записи';
|
||
btn.style.borderColor = active ? 'var(--success-color)' : '';
|
||
}
|
||
}
|
||
|
||
function startObserver() {
|
||
if (observer) observer.disconnect();
|
||
observer = new MutationObserver((mutations) => {
|
||
for (const mutation of mutations) {
|
||
for (const node of mutation.addedNodes) {
|
||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||
replaceText(node);
|
||
} else if (node.nodeType === Node.TEXT_NODE) {
|
||
replaceText(node);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
observer.observe(document.body, {
|
||
childList: true,
|
||
subtree: true,
|
||
characterData: true
|
||
});
|
||
}
|
||
|
||
function toggle() {
|
||
active = !isActive();
|
||
setActive(active);
|
||
if (active) {
|
||
apply();
|
||
startObserver();
|
||
} else {
|
||
if (observer) {
|
||
observer.disconnect();
|
||
observer = null;
|
||
}
|
||
// Reload to restore original text
|
||
location.reload();
|
||
}
|
||
return active;
|
||
}
|
||
|
||
function init() {
|
||
active = isActive();
|
||
if (active) {
|
||
// Wait for DOM to be ready
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
apply();
|
||
startObserver();
|
||
});
|
||
} else {
|
||
apply();
|
||
startObserver();
|
||
}
|
||
}
|
||
// Set initial button state
|
||
const btn = document.getElementById('safeModeToggle');
|
||
if (btn) {
|
||
btn.textContent = active ? '🛡️' : '🎙️';
|
||
btn.title = active ? 'Режим записи активен' : 'Режим записи';
|
||
if (active) btn.style.borderColor = 'var(--success-color)';
|
||
}
|
||
}
|
||
|
||
return {
|
||
init,
|
||
toggle,
|
||
isActive
|
||
};
|
||
})();
|
||
|
||
// Global toggle function for onclick in navbars
|
||
function toggleSafeMode() {
|
||
SafeMode.toggle();
|
||
}
|
||
|
||
// Auto-init
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', () => SafeMode.init());
|
||
} else {
|
||
SafeMode.init();
|
||
}
|