// ==UserScript==
// @name Drawaria Message All Friends
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Professional and stable tool with profiles, variables, filters, batches, advanced exclusion lists, and progress bar.
// @author YouTubeDrawaria
// @match *://*.drawaria.online/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// ==/UserScript==
(function() {
'use strict';
// --- 1. STYLES AND HTML FOR THE MENU ---
GM_addStyle(`
#mass-msg-container { position: fixed; top: 10px; right: 10px; width: 360px; background-color: #fff; border: 1px solid #d3d3d3; border-radius: 8px; z-index: 9999; box-shadow: 0 8px 16px rgba(0,0,0,0.2); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 14px; }
#mass-msg-header { padding: 12px; cursor: move; z-index: 10000; background-color: #007bff; color: #fff; border-top-left-radius: 7px; border-top-right-radius: 7px; text-align: center; font-weight: bold; }
#mass-msg-toggle { padding: 4px 0; background-color: #f8f9fa; text-align: center; cursor: pointer; border-bottom: 1px solid #d3d3d3; user-select: none; }
#mass-msg-toggle:hover { background-color: #e2e6ea; }
#mass-msg-body { padding: 15px; overflow: hidden; transition: all 0.3s ease; }
#mass-msg-container.collapsed #mass-msg-body { display: none; }
#mass-msg-container.collapsed #mass-msg-toggle { border-bottom-left-radius: 7px; border-bottom-right-radius: 7px; border-bottom: none; }
body.mass-msg-dragging, body.mass-msg-dragging * { cursor: move !important; user-select: none !important; }
.section-header { display: flex; align-items: center; justify-content: space-between; }
.info-button { font-size: 14px; font-weight: bold; color: #007bff; cursor: pointer; border: 1px solid #007bff; border-radius: 50%; width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center; transition: background-color 0.2s, color 0.2s; }
.info-button:hover { background-color: #007bff; color: #fff; }
.section-toggle { background: none; border: none; color: #007bff; cursor: pointer; padding: 8px 0; margin-top: 10px; font-weight: bold; text-align: left; flex-grow: 1; }
.collapsible-section { display: none; border-top: 1px solid #ddd; padding-top: 10px; margin-top: 5px; }
#mass-msg-body label { display: block; margin: 12px 0 5px 0; font-weight: 600; color: #333; }
#mass-msg-text, input, select { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }
#mass-msg-text { resize: vertical; min-height: 80px; }
.input-grid { display: flex; gap: 10px; margin-top: 12px; }
.grid-item { flex: 1; min-width: 0; }
.grid-item label { margin-top: 0; }
#profile-manager { display: flex; gap: 5px; align-items: center; }
#profile-manager select { flex-grow: 1; }
#profile-manager button { padding: 8px; white-space: nowrap; }
#exclusion-controls { display: flex; gap: 5px; margin: 8px 0; }
#exclusion-controls button { flex: 1; padding: 5px; font-size: 11px; }
.button-container { display: flex; gap: 10px; margin-top: 15px; }
#start-mass-msg, #stop-mass-msg { flex-grow: 1; padding: 10px; color: white; border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.2s; }
#start-mass-msg { background-color: #28a745; }
#stop-mass-msg { background-color: #dc3545; display: none; }
#start-mass-msg:disabled { background-color: #aaa; cursor: not-allowed; }
#progress-container { margin-top: 15px; display: none; }
#progress-bar { width: 100%; height: 20px; background-color: #e9ecef; border-radius: 4px; overflow: hidden; }
#progress-bar-inner { width: 0%; height: 100%; background-color: #28a745; transition: width 0.3s ease; }
#progress-text { text-align: center; font-size: 12px; margin-top: 4px; }
#mass-msg-log { margin-top: 10px; padding: 8px; background-color: #fff; border: 1px solid #ddd; height: 120px; overflow-y: auto; font-size: 12px; border-radius: 4px; line-height: 1.5; }
.log-info { color: #555; } .log-success { color: #28a745; font-weight: bold; } .log-error { color: #dc3545; font-weight: bold; } .log-pause { color: #ff8c00; font-style: italic; }
.exclusion-container { border: 1px solid #e0e0e0; border-radius: 4px; background: #fff; }
#exclusion-list { max-height: 150px; overflow-y: auto; padding: 5px; }
.exclusion-item { display: flex; align-items: center; padding: 4px; border-bottom: 1px solid #f0f0f0; }
.exclusion-item:last-child { border-bottom: none; }
.exclusion-item label { margin: 0 0 0 8px; font-weight: normal; }
#exclusion-search { border-radius: 4px 4px 0 0; border-bottom: none; }
`);
const profileHelpText = `Saves and loads all your configurations (message, filters, delays, etc.) for easy reuse.\n\n--- HOW TO USE ---\n\n1. SAVE:\n - Configure everything to your liking.\n - Enter a name for the profile.\n - Click on 'Save Current Profile'.\n\n2. LOAD:\n - Select a profile from the dropdown menu.\n\n3. DELETE:\n - Load a profile and click the trash can icon (🗑️).`;
const menuHTML = `
<div id="mass-msg-container">
<div id="mass-msg-header">✉️ Drawaria Message All Friends</div>
<div id="mass-msg-toggle">▼</div>
<div id="mass-msg-body">
<label for="mass-msg-text">Message (use {name} to personalize):</label>
<textarea id="mass-msg-text" placeholder="Hello, {name}! How are you?"></textarea>
<div class="section-header"> <button class="section-toggle" data-target="profile-section">Profile Manager ▼</button> <span id="profile-info-btn" class="info-button">ⓘ</span> </div>
<div id="profile-section" class="collapsible-section">
<div id="profile-manager"> <select id="profile-select"><option value="">--- Load Profile ---</option></select> <button id="delete-profile-btn" title="Delete Selected Profile">🗑️</button> </div>
<input type="text" id="profile-name" placeholder="New profile name..." style="margin-top: 5px;"> <button id="save-profile-btn" style="width:100%; margin-top:5px;">Save Current Profile</button>
</div>
<div class="input-grid">
<div class="grid-item"><label for="mass-msg-delay">Delay (ms):</label><input type="number" id="mass-msg-delay" value="1500" min="500" step="100"></div>
<div class="grid-item"><label for="lang-filter">Send to:</label><select id="lang-filter"><option value="all">All / Todos</option><option value="es">Spanish</option><option value="en">English</option><option value="ru">Russian</option><option value="ar">Arabic</option></select></div>
<div class="grid-item"><label for="count-limit">Limit:</label><input type="number" id="count-limit" placeholder="All"></div>
</div>
<label for="exclusion-search">Skip these people:</label>
<div id="exclusion-controls"> <button id="select-all-exclude">All</button> <button id="deselect-all-exclude">None</button> <button id="invert-exclude">Invert</button> </div>
<div class="exclusion-container"> <input type="text" id="exclusion-search" placeholder="Search friend to skip..."> <div id="exclusion-list">Open friends list to populate.</div> </div>
<div class="section-header"> <button class="section-toggle" data-target="advanced-options">Advanced Options (Batches) ▼</button> </div>
<div id="advanced-options" class="collapsible-section">
<label for="batch-size">Batch Size (e.g. 50):</label> <input type="number" id="batch-size" placeholder="Leave empty to not use batches"> <small>There will be a 60-second pause between each batch.</small>
</div>
<div class="button-container"> <button id="start-mass-msg">Send</button> <button id="stop-mass-msg">Stop Sending</button> </div>
<div id="progress-container"> <div id="progress-bar"><div id="progress-bar-inner"></div></div> <div id="progress-text"></div> </div>
<div id="mass-msg-log">Waiting for instructions...</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', menuHTML);
// --- 2. VARIABLE AND UI ELEMENT DEFINITIONS ---
let isSending = false;
const BATCH_PAUSE_SECONDS = 60;
const ui = {
container: document.getElementById('mass-msg-container'), header: document.getElementById('mass-msg-header'),
toggleButton: document.getElementById('mass-msg-toggle'), body: document.getElementById('mass-msg-body'),
startButton: document.getElementById('start-mass-msg'), stopButton: document.getElementById('stop-mass-msg'),
messageInput: document.getElementById('mass-msg-text'), delayInput: document.getElementById('mass-msg-delay'),
logPanel: document.getElementById('mass-msg-log'), langFilter: document.getElementById('lang-filter'),
countLimit: document.getElementById('count-limit'), exclusionList: document.getElementById('exclusion-list'),
exclusionSearch: document.getElementById('exclusion-search'), batchSizeInput: document.getElementById('batch-size'),
profileSelect: document.getElementById('profile-select'), profileNameInput: document.getElementById('profile-name'),
saveProfileButton: document.getElementById('save-profile-btn'), deleteProfileButton: document.getElementById('delete-profile-btn'),
selectAllButton: document.getElementById('select-all-exclude'), deselectAllButton: document.getElementById('deselect-all-exclude'),
invertButton: document.getElementById('invert-exclude'), progressContainer: document.getElementById('progress-container'),
progressBarInner: document.getElementById('progress-bar-inner'), progressText: document.getElementById('progress-text'),
profileInfoButton: document.getElementById('profile-info-btn'),
};
// --- 3. FUNCTION DEFINITIONS ---
function logToPanel(message, type = 'info') { const timestamp = new Date().toLocaleTimeString(); ui.logPanel.innerHTML += `<div class="log-${type}">[${timestamp}] ${message}</div>`; ui.logPanel.scrollTop = ui.logPanel.scrollHeight; }
function setMenuCollapsed(collapsed) { if (collapsed) { ui.container.classList.add('collapsed'); ui.toggleButton.textContent = '▲'; } else { ui.container.classList.remove('collapsed'); ui.toggleButton.textContent = '▼'; } }
function saveCurrentSettings() { return { message: ui.messageInput.value, delay: ui.delayInput.value, lang: ui.langFilter.value, limit: ui.countLimit.value, batchSize: ui.batchSizeInput.value, }; }
function loadSettings(settings) { ui.messageInput.value = settings.message || ''; ui.delayInput.value = settings.delay || 1500; ui.langFilter.value = settings.lang || 'all'; ui.countLimit.value = settings.limit || ''; ui.batchSizeInput.value = settings.batchSize || ''; }
function populateProfileDropdown() { const profiles = GM_getValue('savedProfiles', {}); ui.profileSelect.innerHTML = '<option value="">--- Load Profile ---</option>'; for (const name in profiles) { const option = document.createElement('option'); option.value = name; option.textContent = name; ui.profileSelect.appendChild(option); } }
function setAllCheckboxes(checked) { ui.exclusionList.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = checked); }
function stopSending(finished = false) { if (finished) { logToPanel('--- Process finished! ---', 'success'); } isSending = false; ui.startButton.style.display = 'block'; ui.stopButton.style.display = 'none'; ui.startButton.disabled = false; ui.progressContainer.style.display = 'none'; }
function filterByLanguage(elements, lang) {
if (lang === 'all') return elements;
const patterns = {
es: /[ñáéíóúü¡¿]/i,
ru: /[а-яА-Я]/,
ar: /[\u0600-\u06FF]/,
en: /^[a-zA-Z0-9\s!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~`]*$/
};
const otherLangsPattern = /([ñáéíóúü¡¿]|[а-яА-Я]|[\u0600-\u06FF])/i;
return elements.filter(el => {
const username = el.querySelector('.playername')?.textContent || '';
// For English, ensure no characters from other specified languages are present
return lang === 'en' ? !otherLangsPattern.test(username) : (patterns[lang] && patterns[lang].test(username));
});
}
function populateExclusionList() { const friendElements = document.querySelectorAll('#friends-tabfriendlist .content .tabrow'); if (friendElements.length === 0) return; ui.exclusionList.innerHTML = ''; friendElements.forEach((el, index) => { const uid = el.dataset.playeruid, name = el.querySelector('.playername')?.textContent || uid; ui.exclusionList.insertAdjacentHTML('beforeend', `<div class="exclusion-item" data-name="${name.toLowerCase()}"><input type="checkbox" id="exclude-${index}" data-uid="${uid}"><label for="exclude-${index}">${name}</label></div>`); }); }
function updateProgress(current, total) { const percentage = total > 0 ? (current / total) * 100 : 0; ui.progressBarInner.style.width = `${percentage}%`; const etaText = calculateETA(current, total); ui.progressText.textContent = `Sent ${current} / ${total} ${etaText}`; }
function formatTime(seconds) { if (seconds < 60) return `${Math.round(seconds)}s`; const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.round(seconds % 60); return `${minutes}m ${remainingSeconds}s`; }
function calculateETA(current, total) { if (current >= total) return ''; const remaining = total - current; const delay = (parseInt(ui.delayInput.value, 10) || 1500) / 1000; const batchSize = parseInt(ui.batchSizeInput.value, 10) || 0; let estimatedSeconds = remaining * delay; if (batchSize > 0) { const batchesLeft = Math.floor((total - 1) / batchSize) - Math.floor((current - 1) / batchSize); estimatedSeconds += batchesLeft * BATCH_PAUSE_SECONDS; } return `| ETA: ~${formatTime(estimatedSeconds)}`; }
function applyFilters(elements) {
logToPanel("Applying filters...");
const lang = ui.langFilter.value;
const friendsByLang = filterByLanguage(elements, lang);
logToPanel(`Filtered by language '${lang}': ${friendsByLang.length} friends.`);
const excludedUIDs = Array.from(ui.exclusionList.querySelectorAll('input:checked')).map(cb => cb.dataset.uid);
const friendsAfterExclusion = friendsByLang.filter(el => !excludedUIDs.includes(el.dataset.playeruid));
logToPanel(`After exclusion: ${friendsAfterExclusion.length} friends to send.`);
const limit = parseInt(ui.countLimit.value, 10);
const finalFriendList = (limit > 0) ? friendsAfterExclusion.slice(0, limit) : friendsAfterExclusion;
if (limit > 0) logToPanel(`Limit applied: Sending to the first ${finalFriendList.length}.`);
return finalFriendList;
}
function dragElement(elmnt, dragHandle) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; dragHandle.onmousedown = dragMouseDown; function dragMouseDown(e) { e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.body.classList.add('mass-msg-dragging'); document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; } function closeDragElement() { document.body.classList.remove('mass-msg-dragging'); document.onmouseup = null; document.onmousemove = null; } }
async function startSending() {
if (!ui.messageInput.value.trim()) { alert('Message cannot be empty.'); return; }
isSending = true; ui.logPanel.innerHTML = ''; logToPanel('--- Starting script ---'); ui.startButton.style.display = 'none'; ui.stopButton.style.display = 'block'; ui.startButton.disabled = true; ui.progressContainer.style.display = 'block';
const allFriends = Array.from(document.querySelectorAll('#friends-tabfriendlist .content .tabrow')); if (allFriends.length === 0) { logToPanel('Error: Friends list not found.', 'error'); stopSending(false); return; }
const finalFriendList = applyFilters(allFriends); const totalToSend = finalFriendList.length; updateProgress(0, totalToSend);
const batchSize = parseInt(ui.batchSizeInput.value, 10) || 0;
for (let i = 0; i < totalToSend; i++) {
if (!isSending) { logToPanel('Sending stopped by user.', 'error'); break; }
if (batchSize > 0 && i > 0 && i % batchSize === 0) {
logToPanel(`Batch completed. Pausing for ${BATCH_PAUSE_SECONDS} seconds...`, 'pause');
for (let s = 0; s < BATCH_PAUSE_SECONDS; s++) { if (!isSending) break; await new Promise(resolve => setTimeout(resolve, 1000)); }
if (!isSending) { logToPanel('Sending stopped during pause.', 'error'); break; } logToPanel(`Resuming sending...`, 'pause');
}
const friendElement = finalFriendList[i]; const uid = friendElement.dataset.playeruid; const name = friendElement.querySelector('.playername')?.textContent || uid; const delay = parseInt(ui.delayInput.value, 10) || 1500;
const personalizedMessage = ui.messageInput.value.replace(/{name}/g, name); // Changed {nombre} to {name} for consistency
logToPanel(`(${i + 1}/${totalToSend}) Sending to: ${name}`, 'info');
try { await $.post("/friendsapi/sendmessage", { uid, message: personalizedMessage }); logToPanel(`✔ Message sent to ${name}`, 'success'); } catch (error) { logToPanel(`✖ Failed to send to ${name}.`, 'error'); console.error(`Error sending to ${name} (UID: ${uid})`, error); }
updateProgress(i + 1, totalToSend);
if (isSending && i < totalToSend - 1 && !(batchSize > 0 && (i + 1) % batchSize === 0)) { await new Promise(resolve => setTimeout(resolve, delay)); }
}
stopSending(true);
}
// --- 4. INITIALIZATION ---
(function init() {
// Load saved state
const isCollapsed = GM_getValue('menuCollapsed', false);
setMenuCollapsed(isCollapsed);
ui.messageInput.value = GM_getValue('savedMessage', 'Hello, {name}!'); // Changed default message
ui.delayInput.value = GM_getValue('savedDelay', 1500);
ui.langFilter.value = GM_getValue('savedLang', 'all');
ui.countLimit.value = GM_getValue('savedCount', '');
ui.batchSizeInput.value = GM_getValue('savedBatchSize', '');
populateProfileDropdown();
// Assign events
ui.toggleButton.addEventListener('click', () => { const currentlyCollapsed = ui.container.classList.toggle('collapsed'); setMenuCollapsed(currentlyCollapsed); GM_setValue('menuCollapsed', currentlyCollapsed); });
document.querySelectorAll('.section-toggle').forEach(button => { button.addEventListener('click', () => { const target = document.getElementById(button.dataset.target); const isVisible = target.style.display === 'block'; target.style.display = isVisible ? 'none' : 'block'; button.textContent = button.textContent.includes('▼') ? button.textContent.replace('▼', '▲') : button.textContent.replace('▲', '▼'); }); });
ui.messageInput.addEventListener('input', () => GM_setValue('savedMessage', ui.messageInput.value));
ui.delayInput.addEventListener('input', () => GM_setValue('savedDelay', ui.delayInput.value));
ui.langFilter.addEventListener('change', () => GM_setValue('savedLang', ui.langFilter.value));
ui.countLimit.addEventListener('input', () => GM_setValue('savedCount', ui.countLimit.value));
ui.batchSizeInput.addEventListener('input', () => GM_setValue('savedBatchSize', ui.batchSizeInput.value));
ui.profileInfoButton.addEventListener('click', () => alert(profileHelpText));
ui.saveProfileButton.addEventListener('click', () => { const name = ui.profileNameInput.value.trim(); if (!name) { alert('Please enter a profile name.'); return; } const profiles = GM_getValue('savedProfiles', {}); profiles[name] = saveCurrentSettings(); GM_setValue('savedProfiles', profiles); ui.profileNameInput.value = ''; populateProfileDropdown(); alert(`Profile '${name}' saved.`); });
ui.profileSelect.addEventListener('change', () => { const name = ui.profileSelect.value; if (!name) return; const profiles = GM_getValue('savedProfiles', {}); if (profiles[name]) { loadSettings(profiles[name]); logToPanel(`Profile '${name}' loaded.`); } });
ui.deleteProfileButton.addEventListener('click', () => { const name = ui.profileSelect.value; if (!name) { alert('Select a profile to delete.'); return; } if (confirm(`Are you sure you want to delete profile '${name}'?`)) { const profiles = GM_getValue('savedProfiles', {}); delete profiles[name]; GM_setValue('savedProfiles', profiles); populateProfileDropdown(); alert(`Profile '${name}' deleted.`); } });
ui.selectAllButton.addEventListener('click', () => setAllCheckboxes(true));
ui.deselectAllButton.addEventListener('click', () => setAllCheckboxes(false));
ui.invertButton.addEventListener('click', () => { ui.exclusionList.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = !cb.checked); });
ui.exclusionSearch.addEventListener('input', e => { const searchTerm = e.target.value.toLowerCase(); ui.exclusionList.querySelectorAll('.exclusion-item').forEach(item => { item.style.display = item.dataset.name.includes(searchTerm) ? 'flex' : 'none'; }); });
ui.startButton.addEventListener('click', startSending);
ui.stopButton.addEventListener('click', () => { isSending = false; });
// DOM observer for the friends list
const observer = new MutationObserver(() => { if (document.querySelector('#friends-tabfriendlist .content .tabrow')) { populateExclusionList(); } });
const friendsWg = document.getElementById('friends-wg'); if (friendsWg) { observer.observe(friendsWg, { childList: true, subtree: true }); }
// Start dragging
dragElement(ui.container, ui.header);
})();
})();