Авто-принятие, авто-коннект, кастомные звуки и мультиязычный интерфейс
// ==UserScript==
// @name FACEIT Auto Tool v6.0 (Multi-Language)
// @namespace http://tampermonkey.net/
// @version 6.0
// @description Авто-принятие, авто-коннект, кастомные звуки и мультиязычный интерфейс
// @author Evre1pro
// @match https://www.faceit.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_notification
// @grant window.focus
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- КОНФИГУРАЦИЯ ---
const CONFIG_KEY = 'faceit_tool_config_v6';
const POS_KEY = 'faceit_tool_pos_v6';
// Словарь переводов
const TRANSLATIONS = {
ru: {
title: 'FACEIT AUTO TOOL',
header: 'FACEIT AUTO TOOL',
sec_automation: 'Автоматизация',
lbl_accept: 'Авто-Принятие',
lbl_connect: 'Авто-Коннект (1 клик)',
desc_connect: '* Коннект срабатывает 1 раз при появлении кнопки.',
sec_sounds: 'Звуки и Оповещения',
lbl_sound_on: 'Включить звуки',
lbl_notify: 'Оповещения (свернутый)',
lbl_sound_match: 'Звук "Матч найден"',
lbl_sound_connect: 'Звук "Коннект"',
lbl_volume: 'Громкость',
sec_settings: 'Настройки',
lbl_language: 'Язык / Language',
lbl_style: 'Вид кнопки',
opt_floating: 'Плавающая',
opt_native: 'Встроить в меню',
btn_test: 'Тест',
btn_upload: 'Загр.',
footer: 'Меню можно перетаскивать | F8 для скрытия',
notif_match: 'МАТЧ НАЙДЕН! Принимаю...',
notif_ready: 'СЕРВЕР ГОТОВ! Подключаюсь...',
notif_clicked: 'Кнопка подключения нажата.',
alert_big_file: 'Файл > 1МБ. Используйте короткий звук.',
alert_saved: 'Звук сохранен!',
log_accept: '[FACEIT Tool] Матч принят',
log_connect: '[FACEIT Tool] Коннект нажат'
},
en: {
title: 'FACEIT AUTO TOOL',
header: 'FACEIT AUTO TOOL',
sec_automation: 'Automation',
lbl_accept: 'Auto-Accept',
lbl_connect: 'Auto-Connect (One-Tap)',
desc_connect: '* Connect clicks once when button appears.',
sec_sounds: 'Sounds & Notifications',
lbl_sound_on: 'Enable Sounds',
lbl_notify: 'Desktop Notifications',
lbl_sound_match: 'Sound "Match Found"',
lbl_sound_connect: 'Sound "Connect"',
lbl_volume: 'Volume',
sec_settings: 'Settings',
lbl_language: 'Language',
lbl_style: 'Button Style',
opt_floating: 'Floating',
opt_native: 'Native (Sidebar)',
btn_test: 'Test',
btn_upload: 'Upld',
footer: 'Drag header to move | F8 to hide',
notif_match: 'MATCH FOUND! Accepting...',
notif_ready: 'SERVER READY! Connecting...',
notif_clicked: 'Connect button clicked.',
alert_big_file: 'File > 1MB. Use short audio.',
alert_saved: 'Sound saved!',
log_accept: '[FACEIT Tool] Match Accepted',
log_connect: '[FACEIT Tool] Connect Clicked'
},
de: {
title: 'FACEIT AUTO TOOL',
header: 'FACEIT AUTO TOOL',
sec_automation: 'Automatisierung',
lbl_accept: 'Auto-Akzeptieren',
lbl_connect: 'Auto-Verbinden',
desc_connect: '* Klickt einmal, wenn Button erscheint.',
sec_sounds: 'Töne & Benachrichtigungen',
lbl_sound_on: 'Töne aktivieren',
lbl_notify: 'Desktop-Benachricht.',
lbl_sound_match: 'Ton "Match gefunden"',
lbl_sound_connect: 'Ton "Verbinden"',
lbl_volume: 'Lautstärke',
sec_settings: 'Einstellungen',
lbl_language: 'Sprache / Language',
lbl_style: 'Button-Stil',
opt_floating: 'Schwebend',
opt_native: 'Im Menü',
btn_test: 'Test',
btn_upload: 'Laden',
footer: 'Kopfzeile ziehen zum Bewegen | F8',
notif_match: 'MATCH GEFUNDEN! Akzeptiere...',
notif_ready: 'SERVER BEREIT! Verbinde...',
notif_clicked: 'Verbinden geklickt.',
alert_big_file: 'Datei > 1MB. Kurz halten.',
alert_saved: 'Ton gespeichert!',
log_accept: '[FACEIT Tool] Match akzeptiert',
log_connect: '[FACEIT Tool] Verbindung geklickt'
},
zh: {
title: 'FACEIT 自动工具',
header: 'FACEIT 自动工具',
sec_automation: '自动化',
lbl_accept: '自动接受',
lbl_connect: '自动连接 (单次)',
desc_connect: '* 按钮出现时自动点击一次。',
sec_sounds: '声音和通知',
lbl_sound_on: '启用声音',
lbl_notify: '桌面通知',
lbl_sound_match: '声音 "找到比赛"',
lbl_sound_connect: '声音 "连接"',
lbl_volume: '音量',
sec_settings: '设置',
lbl_language: '语言 / Language',
lbl_style: '按钮样式',
opt_floating: '悬浮球',
opt_native: '嵌入菜单',
btn_test: '测试',
btn_upload: '上传',
footer: '拖动标题移动 | 按 F8 隐藏',
notif_match: '找到比赛!正在接受...',
notif_ready: '服务器就绪!正在连接...',
notif_clicked: '已点击连接按钮。',
alert_big_file: '文件 > 1MB。请使用短音频。',
alert_saved: '声音已保存!',
log_accept: '[FACEIT Tool] 已接受比赛',
log_connect: '[FACEIT Tool] 已点击连接'
}
};
const DEFAULT_CONFIG = {
lang: 'ru', // Язык по умолчанию
autoAccept: true,
autoConnect: true,
soundEnabled: true,
volume: 0.5,
buttonStyle: 'floating',
acceptSoundData: null,
connectSoundData: null,
notifications: true
};
let config = { ...DEFAULT_CONFIG, ...GM_getValue(CONFIG_KEY, {}) };
let menuPos = GM_getValue(POS_KEY, { top: '15%', left: '85%' });
let isMenuOpen = false;
let t = TRANSLATIONS[config.lang]; // Текущий словарь
// Состояние
let state = {
hasAccepted: false,
hasConnected: false,
currentUrl: window.location.href
};
// Иконки
const ICONS = {
robot: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="24" height="24"><rect x="3" y="11" width="18" height="10" rx="2" /><circle cx="12" cy="5" r="2" /><path d="M12 7v4" /><line x1="8" y1="16" x2="8" y2="16" /><line x1="16" y1="16" x2="16" y2="16" /></svg>`,
gear: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1Z"/></svg>`,
play: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>`,
upload: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>`
};
// Стили
GM_addStyle(`
.f-tool-btn-floating {
position: fixed; top: 15%; right: 20px; z-index: 9999;
background: #FF5500; color: white; border: none;
border-radius: 50%; width: 50px; height: 50px;
cursor: pointer; box-shadow: 0 4px 15px rgba(255, 85, 0, 0.4);
display: flex; align-items: center; justify-content: center;
transition: transform 0.2s;
}
.f-tool-btn-floating:hover { transform: scale(1.1); }
.f-tool-btn-native {
display: flex; align-items: center; justify-content: center;
width: 40px; height: 40px; margin: 10px auto;
border-radius: 50%; cursor: pointer;
background: transparent; border: 1px solid #333;
color: #888; transition: all 0.2s;
}
.f-tool-btn-native:hover, .f-tool-btn-native.active {
color: #FF5500; border-color: #FF5500; background: rgba(255, 85, 0, 0.1);
}
.f-tool-modal {
position: fixed; z-index: 10000;
width: 360px; background: #1f1f1f;
border-radius: 8px; box-shadow: 0 10px 50px rgba(0,0,0,0.9);
border: 1px solid #333; color: #fff; font-family: "Play", sans-serif;
display: none; opacity: 0; transition: opacity 0.2s;
user-select: none;
}
.f-tool-modal.active { display: block; opacity: 1; }
.f-tool-header {
padding: 12px 15px; border-bottom: 1px solid #333;
display: flex; justify-content: space-between; align-items: center;
background: #161616; border-radius: 8px 8px 0 0;
cursor: grab;
}
.f-tool-header:active { cursor: grabbing; }
.f-tool-body { padding: 15px; }
.f-tool-group {
background: #262626; padding: 12px; border-radius: 6px;
margin-bottom: 10px; border: 1px solid #333;
}
.f-tool-group h4 { margin: 0 0 10px 0; color: #FF5500; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; }
.f-tool-row {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 10px; font-size: 13px;
}
.f-switch { position: relative; display: inline-block; width: 36px; height: 18px; }
.f-switch input { opacity: 0; width: 0; height: 0; }
.f-slider {
position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
background-color: #444; transition: .3s; border-radius: 18px;
}
.f-slider:before {
position: absolute; content: ""; height: 14px; width: 14px;
left: 2px; bottom: 2px; background-color: white;
transition: .3s; border-radius: 50%;
}
input:checked + .f-slider { background-color: #FF5500; }
input:checked + .f-slider:before { transform: translateX(18px); }
.f-btn {
background: #333; color: white; border: 1px solid #444;
padding: 4px 8px; border-radius: 4px; cursor: pointer;
font-size: 11px; display: flex; align-items: center; gap: 5px;
}
.f-btn:hover { background: #444; border-color: #555; }
.f-select {
background: #333; color: white; border: 1px solid #444;
padding: 4px; border-radius: 4px; font-size: 12px; outline: none;
}
.f-volume-slider { width: 100%; accent-color: #FF5500; margin-top: 8px; height: 4px; }
.f-close { cursor: pointer; font-size: 20px; color: #888; line-height: 1; }
.f-close:hover { color: white; }
`);
// --- СИСТЕМА ---
function setLanguage(langCode) {
if (!TRANSLATIONS[langCode]) return;
config.lang = langCode;
t = TRANSLATIONS[langCode];
GM_setValue(CONFIG_KEY, config);
// Перерисовываем только контент модального окна
updateModalContent();
}
function makeDraggable(el) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
const header = el.querySelector('.f-tool-header');
if (header) header.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
el.style.top = (el.offsetTop - pos2) + "px";
el.style.left = (el.offsetLeft - pos1) + "px";
el.style.transform = 'none';
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
GM_setValue(POS_KEY, { top: el.style.top, left: el.style.left });
}
}
function playSound(type) {
if (!config.soundEnabled) return;
if (document.hidden && config.notifications) {
GM_notification({
title: 'FACEIT Auto Tool',
text: type === 'accept' ? t.notif_match : t.notif_ready,
timeout: 4000,
onclick: () => window.focus()
});
}
const customData = type === 'accept' ? config.acceptSoundData : config.connectSoundData;
if (customData) {
const audio = new Audio(customData);
audio.volume = config.volume;
audio.play().catch(e => console.error(e));
return;
}
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
gain.gain.value = config.volume * 0.2;
if (type === 'accept') {
osc.type = 'square';
osc.frequency.setValueAtTime(500, ctx.currentTime);
osc.frequency.linearRampToValueAtTime(1000, ctx.currentTime + 0.1);
osc.start();
osc.stop(ctx.currentTime + 0.4);
} else {
osc.type = 'sine';
osc.frequency.setValueAtTime(800, ctx.currentTime);
osc.frequency.linearRampToValueAtTime(1200, ctx.currentTime + 0.2);
osc.start();
osc.stop(ctx.currentTime + 0.5);
}
}
function findBtnByText(textArray, context = document) {
const buttons = context.querySelectorAll('button');
for (let btn of buttons) {
if (btn.disabled || btn.offsetParent === null) continue;
const btnText = btn.innerText.toUpperCase();
if (textArray.some(t => btnText.includes(t))) return btn;
}
return null;
}
function automationLoop() {
if (window.location.href !== state.currentUrl) {
state.currentUrl = window.location.href;
state.hasAccepted = false;
state.hasConnected = false;
}
if (config.autoAccept && !state.hasAccepted) {
const modal = document.querySelector('div[data-dialog-type="MODAL"]');
if (modal) {
const btn = findBtnByText(['ACCEPT', 'ПРИНЯТЬ', 'CHECK IN', 'ANNEHMEN', '接受'], modal);
if (btn) {
playSound('accept');
state.hasAccepted = true;
setTimeout(() => btn.click(), 200);
console.log(t.log_accept);
}
}
}
if (config.autoConnect && !state.hasConnected) {
if (window.location.href.includes('/room/')) {
const btn = findBtnByText(['CONNECT', 'ПОДКЛЮЧИТЬСЯ', 'COPY IP', 'СКОПИРОВАТЬ', 'VERBINDEN', '连接']);
if (btn) {
const txt = btn.innerText.toUpperCase();
if (!txt.includes('COPIED') && !txt.includes('ЗАПУСК') && !txt.includes('СКОПИРОВАНО')) {
btn.click();
state.hasConnected = true;
playSound('connect');
console.log(t.log_connect);
if (config.notifications) {
GM_notification({title: 'FACEIT Tool', text: t.notif_clicked, timeout: 2000});
}
}
}
}
}
}
// --- UI РЕНДЕР ---
function toggleMenu() {
const modal = document.querySelector('.f-tool-modal');
isMenuOpen = !isMenuOpen;
if (isMenuOpen) modal.classList.add('active');
else modal.classList.remove('active');
}
function renderButtons() {
document.querySelectorAll('.f-tool-btn-floating, .f-tool-btn-native').forEach(e => e.remove());
if (config.buttonStyle === 'native') {
const sidebar = document.querySelector('div[class*="NavigationSidebar"] div[class*="Scrollable"]');
if (sidebar) {
const container = document.createElement('div');
container.style.display = 'contents';
const btn = document.createElement('button');
btn.className = 'f-tool-btn-native';
btn.innerHTML = ICONS.robot;
btn.onclick = toggleMenu;
sidebar.appendChild(container);
container.appendChild(btn);
} else {
renderFloatingBtn();
}
} else {
renderFloatingBtn();
}
}
function renderFloatingBtn() {
const btn = document.createElement('button');
btn.className = 'f-tool-btn-floating';
btn.innerHTML = ICONS.gear;
btn.onclick = toggleMenu;
document.body.appendChild(btn);
}
// Обновление внутреннего HTML модалки (для смены языка)
function updateModalContent() {
const modal = document.querySelector('.f-tool-modal');
if (!modal) return;
// Сохраняем старый заголовок для перетаскивания, меняем только innerHTML
const html = `
<div class="f-tool-header">
<span style="font-weight:bold; color:#FF5500; display:flex; gap:8px; align-items:center">
${ICONS.robot} ${t.header}
</span>
<span class="f-close" id="f-tool-close">×</span>
</div>
<div class="f-tool-body">
<div class="f-tool-group">
<h4>${t.sec_automation}</h4>
<div class="f-tool-row">
<span>${t.lbl_accept}</span>
<label class="f-switch">
<input type="checkbox" id="chk-accept" ${config.autoAccept ? 'checked' : ''}>
<span class="f-slider"></span>
</label>
</div>
<div class="f-tool-row">
<span>${t.lbl_connect}</span>
<label class="f-switch">
<input type="checkbox" id="chk-connect" ${config.autoConnect ? 'checked' : ''}>
<span class="f-slider"></span>
</label>
</div>
<div style="font-size:10px; color:#888; margin-top:5px;">
${t.desc_connect}
</div>
</div>
<div class="f-tool-group">
<h4>${t.sec_sounds}</h4>
<div class="f-tool-row">
<span>${t.lbl_sound_on}</span>
<label class="f-switch">
<input type="checkbox" id="chk-sound" ${config.soundEnabled ? 'checked' : ''}>
<span class="f-slider"></span>
</label>
</div>
<div class="f-tool-row">
<span>${t.lbl_notify}</span>
<label class="f-switch">
<input type="checkbox" id="chk-notify" ${config.notifications ? 'checked' : ''}>
<span class="f-slider"></span>
</label>
</div>
<div class="f-tool-row" style="margin-top:10px">
<span>${t.lbl_sound_match}</span>
<div style="display:flex; gap:5px">
<button class="f-btn" id="btn-test-accept" title="${t.btn_test}">${ICONS.play}</button>
<label class="f-btn" title="${t.btn_upload}">
${ICONS.upload}
<input type="file" id="file-accept" accept="audio/*" style="display:none">
</label>
</div>
</div>
<div class="f-tool-row">
<span>${t.lbl_sound_connect}</span>
<div style="display:flex; gap:5px">
<button class="f-btn" id="btn-test-connect" title="${t.btn_test}">${ICONS.play}</button>
<label class="f-btn" title="${t.btn_upload}">
${ICONS.upload}
<input type="file" id="file-connect" accept="audio/*" style="display:none">
</label>
</div>
</div>
<div class="f-tool-row" style="flex-direction:column; align-items:flex-start; gap:5px;">
<span>${t.lbl_volume}</span>
<input type="range" class="f-volume-slider" id="rng-volume" min="0" max="1" step="0.1" value="${config.volume}">
</div>
</div>
<div class="f-tool-group">
<h4>${t.sec_settings}</h4>
<div class="f-tool-row">
<span>${t.lbl_language}</span>
<select id="sel-lang" class="f-select">
<option value="ru" ${config.lang === 'ru' ? 'selected' : ''}>Русский</option>
<option value="en" ${config.lang === 'en' ? 'selected' : ''}>English</option>
<option value="de" ${config.lang === 'de' ? 'selected' : ''}>Deutsch</option>
<option value="zh" ${config.lang === 'zh' ? 'selected' : ''}>中文</option>
</select>
</div>
<div class="f-tool-row">
<span>${t.lbl_style}</span>
<select id="sel-style" class="f-select">
<option value="floating" ${config.buttonStyle === 'floating' ? 'selected' : ''}>${t.opt_floating}</option>
<option value="native" ${config.buttonStyle === 'native' ? 'selected' : ''}>${t.opt_native}</option>
</select>
</div>
</div>
<div style="text-align:center; font-size:10px; color:#555; margin-top:5px;">${t.footer}</div>
</div>
`;
modal.innerHTML = html;
bindEvents(); // Перепривязываем события после обновления HTML
makeDraggable(modal); // Обновляем драг-хендлер
}
function bindEvents() {
document.getElementById('f-tool-close').onclick = toggleMenu;
['autoAccept', 'autoConnect', 'soundEnabled', 'notifications'].forEach(key => {
const id = 'chk-' + (key === 'soundEnabled' ? 'sound' : key === 'notifications' ? 'notify' : key === 'autoAccept' ? 'accept' : 'connect');
document.getElementById(id).onchange = (e) => {
config[key] = e.target.checked;
GM_setValue(CONFIG_KEY, config);
};
});
document.getElementById('rng-volume').oninput = (e) => {
config.volume = parseFloat(e.target.value);
GM_setValue(CONFIG_KEY, config);
};
document.getElementById('sel-style').onchange = (e) => {
config.buttonStyle = e.target.value;
GM_setValue(CONFIG_KEY, config);
renderButtons();
};
// Смена языка
document.getElementById('sel-lang').onchange = (e) => {
setLanguage(e.target.value);
};
// Аудио
const handleUpload = (id, key) => {
document.getElementById(id).onchange = (e) => {
const file = e.target.files[0];
if (!file) return;
if (file.size > 1024 * 1024) {
alert(t.alert_big_file);
return;
}
const reader = new FileReader();
reader.onload = (evt) => {
config[key] = evt.target.result;
GM_setValue(CONFIG_KEY, config);
alert(t.alert_saved);
};
reader.readAsDataURL(file);
};
};
handleUpload('file-accept', 'acceptSoundData');
handleUpload('file-connect', 'connectSoundData');
document.getElementById('btn-test-accept').onclick = () => playSound('accept');
document.getElementById('btn-test-connect').onclick = () => playSound('connect');
}
function init() {
const modal = document.createElement('div');
modal.className = 'f-tool-modal';
modal.style.top = menuPos.top;
modal.style.left = menuPos.left;
if(menuPos.top !== '50%') modal.style.transform = 'none';
document.body.appendChild(modal);
updateModalContent(); // Инициализация контента
renderButtons();
setInterval(() => {
if (config.buttonStyle === 'native' && !document.querySelector('.f-tool-btn-native')) {
renderButtons();
}
}, 2000);
setInterval(automationLoop, 1000);
document.addEventListener('keydown', (e) => {
if (e.code === 'F8') toggleMenu();
});
}
init();
console.log("FACEIT Auto Tool v6.0 Loaded");
})();