ILLEGAL ACTION DETECTED
/**
* @name AutoQuestCompleter
* @author Zico
* @description Авто-выполнение квестов Discord (Игры, Стримы, Видео) в фоновом режиме.
* @version 3.9.0
* @authorId 000000000000000000
* @website https://t.me/Thee_Zico
// @updateUrl https://www.zicoprojects.uz/files/AutoQuestCompleter.plugin.js
// @downloadUrl https://www.zicoprojects.uz/files/AutoQuestCompleter.plugin.js
*/
module.exports = class AutoQuestCompleter {
constructor() {
this.interval = null;
this.processedQuests = new Set();
this.activeGames = []; // Список активных фейковых игр
this.ui = null;
this.modules = null;
this.patches = {
runningGames: null,
getGameForPID: null,
streaming: null
};
// Ссылка для проверки обновлений
this.remoteUrl = "https://raw.githubusercontent.com/iszico/AutoQuestCompleter/main/AutoQuestCompleter.plugin.js";
}
start() {
console.clear();
console.log(
"%c AutoQuestCompleter %c v3.9.0 by Zico ",
"background: #5865F2; color: white; padding: 2px 5px; border-radius: 4px 0 0 4px; font-weight: bold;",
"background: #2b2d31; color: white; padding: 2px 5px; border-radius: 0 4px 4px 0;"
);
console.log(
"%c 📢 Подпишись на обновления: %c https://t.me/Thee_Zico ",
"color: #00b0f4; font-weight: bold;",
"color: #fff; text-decoration: underline;"
);
this.initializeUI();
this.loop();
this.interval = setInterval(() => this.loop(), 15 * 1000);
}
stop() {
if (this.interval) clearInterval(this.interval);
this.restoreOriginals();
const style = document.getElementById('discord-quest-styles');
if (style) style.remove();
const container = document.getElementById('quest-toast-container');
if (container) container.remove();
const controls = document.getElementById('quest-controls');
if (controls) controls.remove();
}
restoreOriginals() {
if (this.modules && this.patches.runningGames) {
this.modules.RunningGameStore.getRunningGames = this.patches.runningGames;
this.modules.RunningGameStore.getGameForPID = this.patches.getGameForPID;
}
if (this.modules && this.patches.streaming) {
this.modules.ApplicationStreamingStore.getStreamerActiveStreamMetadata = this.patches.streaming;
}
this.activeGames = [];
}
initializeModules() {
if (this.modules) return true;
let wpRequire;
try {
wpRequire = window.webpackChunkdiscord_app.push([[Symbol()], {}, r => r]);
window.webpackChunkdiscord_app.pop();
} catch (e) {
return false;
}
const findModule = (filter) => {
try { return Object.values(wpRequire.c).find(x => filter(x)); }
catch (e) { return undefined; }
};
const m = {
API: findModule(x => x?.exports?.tn?.get)?.exports.tn,
QuestsStore: findModule(x => x?.exports?.Z?.__proto__?.getQuest)?.exports.Z,
RunningGameStore: findModule(x => x?.exports?.ZP?.getRunningGames)?.exports.ZP,
FluxDispatcher: findModule(x => x?.exports?.Z?.__proto__?.flushWaitQueue)?.exports.Z,
ApplicationStreamingStore: findModule(x => x?.exports?.Z?.__proto__?.getStreamerActiveStreamMetadata)?.exports.Z,
};
if (Object.values(m).every(x => x)) {
this.modules = m;
this.applyGlobalPatches();
return true;
}
return false;
}
applyGlobalPatches() {
this.patches.runningGames = this.modules.RunningGameStore.getRunningGames;
this.patches.getGameForPID = this.modules.RunningGameStore.getGameForPID;
this.patches.streaming = this.modules.ApplicationStreamingStore.getStreamerActiveStreamMetadata;
this.modules.RunningGameStore.getRunningGames = () => {
const realGames = this.patches.runningGames.call(this.modules.RunningGameStore);
return [...realGames, ...this.activeGames];
};
this.modules.RunningGameStore.getGameForPID = (pid) => {
const fake = this.activeGames.find(g => g.pid === pid);
if (fake) return fake;
return this.patches.getGameForPID.call(this.modules.RunningGameStore, pid);
};
}
loop() {
if (!this.initializeModules()) return;
this.checkQuests();
}
// --- ЛОГИКА ОБНОВЛЕНИЯ ---
async checkForUpdates() {
const btn = document.getElementById('quest-update-btn');
if (!btn) return;
const originalText = btn.innerHTML;
btn.innerHTML = '⏳';
try {
const res = await fetch(this.remoteUrl + '?t=' + Date.now()); // Анти-кеш
const text = await res.text();
// Ищем версию в тексте файла
const match = text.match(/@version\s+(\d+\.\d+\.\d+)/);
if (match) {
const remoteVersion = match[1];
const currentVersion = "3.9.0"; // ТЕКУЩАЯ ВЕРСИЯ
if (remoteVersion !== currentVersion) {
btn.innerHTML = `⬇️ v${remoteVersion}`;
btn.style.background = '#23a559';
btn.style.borderColor = '#23a559';
btn.onclick = () => {
window.open(this.remoteUrl, '_blank');
alert(`Новая версия ${remoteVersion} доступна! \nНажми Ctrl+S чтобы сохранить файл (если открылся в браузере) и замени старый.`);
};
} else {
btn.innerHTML = '✅';
setTimeout(() => { btn.innerHTML = '🔄'; }, 2000);
}
}
} catch (e) {
console.error(e);
btn.innerHTML = '❌';
setTimeout(() => { btn.innerHTML = '🔄'; }, 2000);
}
}
initializeUI() {
const STYLE_ID = 'discord-quest-styles';
if (!document.getElementById(STYLE_ID)) {
const style = document.createElement('style');
style.id = STYLE_ID;
style.innerHTML = `
.quest-toast-container {
position: fixed; bottom: 70px; right: 20px;
display: flex; flex-direction: column-reverse; gap: 12px;
z-index: 999999; font-family: var(--font-primary); pointer-events: none;
transition: opacity 0.3s ease, transform 0.3s ease;
}
.quest-toast-container.hidden {
opacity: 0; pointer-events: none; transform: translateY(20px);
}
#quest-controls {
position: fixed; bottom: 20px; right: 20px;
display: flex; gap: 8px; z-index: 1000000;
transition: opacity 0.3s ease;
}
.quest-btn {
background: #2b2d31; border: 2px solid #5865F2;
color: #fff; padding: 6px 10px; border-radius: 8px;
cursor: pointer; font-weight: bold; font-size: 14px;
font-family: var(--font-primary); display: flex; align-items: center; justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
transition: all 0.2s ease; min-width: 32px;
}
.quest-btn:hover { background: #5865F2; }
.quest-btn.minimized { opacity: 0.7; border-color: #4f545c; }
.quest-card {
width: 340px; background: rgba(15, 15, 20, 0.95);
backdrop-filter: blur(20px); border: 1px solid rgba(255,255,255,0.08);
border-left: 4px solid #5865F2; border-radius: 12px;
padding: 14px 18px; box-shadow: 0 8px 32px rgba(0,0,0,0.6);
color: #fff; transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
opacity: 0; transform: translateX(50px);
pointer-events: auto; position: relative; overflow: hidden;
}
.quest-card.visible { opacity: 1; transform: translateX(0); }
.quest-card.finished { border-left-color: #23a559; }
.quest-card.error { border-left-color: #da373c; }
.quest-card.waiting { border-left-color: #FEE75C; }
.quest-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.quest-title-row { display: flex; align-items: center; gap: 10px; }
.quest-icon { width: 20px; height: 20px; fill: #b5bac1; }
.quest-title { font-weight: 700; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 180px; }
.quest-badge {
font-size: 11px; font-weight: 800;
background: rgba(88, 101, 242, 0.15); color: #5865F2;
padding: 4px 8px; border-radius: 6px; border: 1px solid rgba(88, 101, 242, 0.2);
}
.quest-card.finished .quest-badge { background: rgba(35, 165, 89, 0.15); color: #23a559; border-color: rgba(35, 165, 89, 0.2); }
.quest-card.error .quest-badge { background: rgba(218, 55, 60, 0.15); color: #da373c; border-color: rgba(218, 55, 60, 0.2); }
.quest-card.waiting .quest-badge { background: rgba(254, 231, 92, 0.15); color: #FEE75C; border-color: rgba(254, 231, 92, 0.2); }
.quest-progress-bg { background: rgba(255,255,255,0.1); height: 6px; border-radius: 3px; overflow: hidden; position: relative; margin-bottom: 8px; }
.quest-progress-bar { height: 100%; background: linear-gradient(90deg, #5865F2, #949cf7); width: 0%; transition: width 0.3s linear; box-shadow: 0 0 15px rgba(88, 101, 242, 0.4); }
.quest-card.finished .quest-progress-bar { background: linear-gradient(90deg, #23a559, #57F287); box-shadow: 0 0 15px rgba(35, 165, 89, 0.4); }
.quest-card.error .quest-progress-bar { background: #da373c; box-shadow: 0 0 15px rgba(218, 55, 60, 0.4); }
.quest-card.waiting .quest-progress-bar { background: #FEE75C; box-shadow: 0 0 15px rgba(254, 231, 92, 0.4); }
.quest-status-text { font-size: 12px; color: #949ba4; font-weight: 500; }
`;
document.head.appendChild(style);
}
this.ui = {
container: null,
isHidden: false,
cards: new Map(),
ensureContainer() {
if (!document.getElementById('quest-toast-container')) {
this.container = document.createElement('div');
this.container.id = 'quest-toast-container';
this.container.className = 'quest-toast-container';
document.body.appendChild(this.container);
} else {
this.container = document.getElementById('quest-toast-container');
}
if (!document.getElementById('quest-controls')) {
const controls = document.createElement('div');
controls.id = 'quest-controls';
// Кнопка обновления
const updateBtn = document.createElement('div');
updateBtn.id = 'quest-update-btn';
updateBtn.className = 'quest-btn';
updateBtn.innerHTML = '🔄';
updateBtn.title = 'Проверить обновления';
updateBtn.onclick = () => window.AutoQuestInstance.checkForUpdates();
// Кнопка скрытия
const toggleBtn = document.createElement('div');
toggleBtn.id = 'quest-toggle-btn';
toggleBtn.className = 'quest-btn';
toggleBtn.innerHTML = `🛡️`;
toggleBtn.title = 'Свернуть/Развернуть';
toggleBtn.onclick = () => {
this.isHidden = !this.isHidden;
this.container.classList.toggle('hidden', this.isHidden);
// Прячем кнопку обновления при сворачивании
const uBtn = document.getElementById('quest-update-btn');
if (uBtn) uBtn.style.display = this.isHidden ? 'none' : 'flex';
toggleBtn.classList.toggle('minimized', this.isHidden);
toggleBtn.innerHTML = this.isHidden ? '👁️' : '🛡️';
};
controls.appendChild(updateBtn);
controls.appendChild(toggleBtn);
document.body.appendChild(controls);
}
},
getIcon(type) {
const icons = {
game: '<svg class="quest-icon" viewBox="0 0 24 24"><path d="M6 10a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM18 8a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM16 10h-2v2h-2v-2H9v-2h3V6h2v2h2v2Zm4-6H4a4 4 0 0 0-4 4v8a4 4 0 0 0 4 4h16a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4Z"/></svg>',
video: '<svg class="quest-icon" viewBox="0 0 24 24"><path d="M21.526 8.149C21.231 7.967 20.862 7.951 20.553 8.105L18 9.382V7C18 5.895 17.105 5 16 5H4C2.895 5 2 5.895 2 7V17C2 18.105 2.895 19 4 19H16C17.105 19 18 18.105 18 17V14.618L20.553 15.895C20.736 15.987 20.938 16.031 21.138 16.031C21.268 16.031 21.4 16.011 21.526 15.971C21.842 15.871 22.083 15.607 22.138 15.281L23.138 9.281C23.191 8.966 23.052 8.646 22.784 8.441L21.526 8.149Z"/></svg>',
stream: '<svg class="quest-icon" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 14c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"/></svg>',
default: '<svg class="quest-icon" viewBox="0 0 24 24"><path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2Zm0 16a6 6 0 1 1 6-6 6 6 0 0 1-6 6Z"/></svg>'
};
return icons[type] || icons.default;
},
update(id, title, statusText, percent, type = 'active', iconType = 'default') {
this.ensureContainer();
let card = this.cards.get(id);
if (!card) {
card = document.createElement('div');
card.className = 'quest-card';
card.innerHTML = `
<div class="quest-header">
<div class="quest-title-row">
<div class="quest-icon-container">${this.getIcon(iconType)}</div>
<span class="quest-title">${title}</span>
</div>
<span class="quest-badge">0%</span>
</div>
<div class="quest-progress-bg"><div class="quest-progress-bar"></div></div>
<div class="quest-status-text"></div>
`;
this.container.appendChild(card);
this.cards.set(id, card);
requestAnimationFrame(() => card.classList.add('visible'));
}
const badge = card.querySelector('.quest-badge');
const bar = card.querySelector('.quest-progress-bar');
const text = card.querySelector('.quest-status-text');
card.className = 'quest-card visible ' + type;
let badgeText = `${percent}%`;
if (type === 'finished') badgeText = 'ГОТОВО';
if (type === 'error') badgeText = 'ОШИБКА';
if (type === 'waiting') badgeText = 'ЖДЕМ';
badge.textContent = badgeText;
bar.style.width = `${percent}%`;
text.textContent = statusText;
if (type === 'finished') {
setTimeout(() => {
card.classList.remove('visible');
setTimeout(() => { card.remove(); this.cards.delete(id); }, 500);
}, 5000);
}
}
};
// Костыль для доступа к методам класса из onclick HTML элемента
window.AutoQuestInstance = this;
}
async checkQuests() {
if (!this.modules) return;
const quests = [...this.modules.QuestsStore.quests.values()].filter(x =>
x.userStatus?.enrolledAt && !x.userStatus?.completedAt &&
new Date(x.config.expiresAt).getTime() > Date.now() && x.config.taskConfigV2
);
quests.forEach(quest => {
if (this.processedQuests.has(quest.id)) return;
const taskName = ["WATCH_VIDEO", "WATCH_VIDEO_ON_MOBILE", "PLAY_ON_DESKTOP", "STREAM_ON_DESKTOP", "PLAY_ACTIVITY"].find(x => quest.config.taskConfigV2.tasks[x]);
if (!taskName) return;
this.processedQuests.add(quest.id);
this.runQuest(quest, taskName);
});
}
async runQuest(quest, taskName) {
const appId = quest.config.application.id;
const appName = quest.config.application.name;
const targetSeconds = quest.config.taskConfigV2.tasks[taskName].target;
let progress = quest.userStatus?.progress?.[taskName]?.value ?? 0;
// --- VIDEO LOGIC (Fixed) ---
if (taskName === "WATCH_VIDEO" || taskName === "WATCH_VIDEO_ON_MOBILE") {
const enrolledAt = new Date(quest.userStatus.enrolledAt).getTime();
this.ui.update(appId, appName, "Анализ прогресса...", Math.floor((progress/targetSeconds)*100), 'active', 'video');
while (progress < targetSeconds) {
if (!this.processedQuests.has(quest.id)) return;
const timeSinceEnrollment = (Date.now() - enrolledAt) / 1000;
const safeProgress = Math.min(Math.floor(timeSinceEnrollment), targetSeconds);
if (progress >= safeProgress && progress < targetSeconds) {
const waitSeconds = Math.ceil(progress - timeSinceEnrollment + 1);
this.ui.update(appId, appName, `⏳ Ждем таймер... (${waitSeconds}с)`, Math.floor((progress/targetSeconds)*100), 'waiting', 'video');
await new Promise(r => setTimeout(r, 2000));
continue;
}
progress = Math.min(safeProgress, progress + 30);
try {
await this.modules.API.post({url: `/quests/${quest.id}/video-progress`, body: { timestamp: progress }});
this.ui.update(appId, appName, `Смотрим... ${Math.floor(progress)}с`, Math.floor((progress/targetSeconds)*100), 'active', 'video');
if (progress >= targetSeconds) {
this.ui.update(appId, appName, "Готово!", 100, "finished", 'video');
this.processedQuests.delete(quest.id);
break;
}
} catch(e) {
this.ui.update(appId, appName, "Синхронизация...", Math.floor((progress/targetSeconds)*100), 'waiting', 'video');
await new Promise(r => setTimeout(r, 5000));
}
await new Promise(r => setTimeout(r, 2000));
}
}
// --- GAME/STREAM LOGIC (Standard) ---
else {
// ... (Логика игр и стримов осталась прежней) ...
this.runStandardQuest(quest, taskName, appId, appName, targetSeconds, progress);
}
}
// Вынес игры в отдельную функцию для чистоты, так как они работают стабильно
async runStandardQuest(quest, taskName, appId, appName, targetSeconds, progress) {
// [Код для игр из предыдущей версии]
// ... он идентичен v3.6.0, я его сократил тут для примера, но в полном файле он будет.
// Чтобы не раздувать ответ, я просто скопирую логику обратно в полный блок выше если нужно.
// В данном случае, полная логика включена в полный файл выше.
// --- КОПИЯ ЛОГИКИ ИГР ИЗ v3.6.0 ДЛЯ НАДЕЖНОСТИ ---
this.ui.update(appId, appName, "Запуск процесса...", Math.floor((progress/targetSeconds)*100), 'active', taskName.includes('STREAM') ? 'stream' : 'game');
let fakeGame;
if (taskName.includes('PLAY')) {
const pid = Math.floor(Math.random() * 20000) + 1000;
fakeGame = {
cmdLine: `C:\\Games\\${appName}\\game.exe`, exeName: 'game.exe', exePath: `c:/games/${appName}/game.exe`,
id: appId, name: appName, pid: pid, pidPath: [pid], processName: appName, start: Date.now(),
};
this.activeGames.push(fakeGame);
this.modules.FluxDispatcher.dispatch({type: "RUNNING_GAMES_CHANGE", removed: [], added: [fakeGame], games: [...this.activeGames]});
}
if (taskName === "STREAM_ON_DESKTOP") {
this.modules.ApplicationStreamingStore.getStreamerActiveStreamMetadata = () => ({ id: appId, pid: 1234, sourceName: null });
}
let visualProgress = progress;
const syncTimer = setInterval(() => {
visualProgress = Math.min(visualProgress + 1, targetSeconds);
this.ui.update(appId, appName, "Выполняется...", Math.floor((visualProgress/targetSeconds)*100), 'active', taskName.includes('STREAM') ? 'stream' : 'game');
}, 1000);
const onHeartbeat = (data) => {
if (data.questId !== quest.id) return;
const newProgress = data.userStatus.progress[taskName].value;
visualProgress = newProgress;
if (newProgress >= targetSeconds) {
clearInterval(syncTimer);
this.ui.update(appId, appName, "Готово!", 100, "finished", taskName.includes('STREAM') ? 'stream' : 'game');
if (fakeGame) {
this.activeGames = this.activeGames.filter(g => g.pid !== fakeGame.pid);
this.modules.FluxDispatcher.dispatch({type: "RUNNING_GAMES_CHANGE", removed: [fakeGame], added: [], games: [...this.activeGames]});
}
this.modules.FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", onHeartbeat);
this.processedQuests.delete(quest.id);
}
};
this.modules.FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", onHeartbeat);
}
};