Skip to content

Instantly share code, notes, and snippets.

@Ap0dexMe0
Last active March 17, 2026 00:31
Show Gist options
  • Select an option

  • Save Ap0dexMe0/1ae689a076096f0a512b9605bd365428 to your computer and use it in GitHub Desktop.

Select an option

Save Ap0dexMe0/1ae689a076096f0a512b9605bd365428 to your computer and use it in GitHub Desktop.
Automation complete recent Discord Quest
(function() {
'use strict';
class DiscordAutoQuest {
constructor() {
this.ELECTRON_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Discord/1.0.0 Chrome/120.0.0.0 Electron/28.0.0 Safari/537.36';
this.HEARTBEAT_INTERVAL = 30000;
this.VIDEO_INTERVAL = 3000;
this.WEBPACK_MAX_ATTEMPTS = 100;
this.WEBPACK_CHECK_INTERVAL = 100;
this.SUPPORTED_TASKS = new Set([
'WATCH_VIDEO',
'PLAY_ON_DESKTOP',
'STREAM_ON_DESKTOP',
'PLAY_ACTIVITY',
'WATCH_VIDEO_ON_MOBILE'
]);
this.STYLES = {
header: 'color: #5865F2; font-weight: bold; font-size: 14px;',
subheader: 'color: #57F287; font-weight: bold;',
success: 'color: #57F287; font-weight: bold;',
warning: 'color: #FEE75C; font-weight: bold;',
error: 'color: #ED4245; font-weight: bold;',
info: 'color: #5865F2;',
quest: 'color: #EB459E; font-weight: bold;',
questProgress: 'color: #5865F2; font-weight: bold;',
questComplete: 'color: #57F287; font-weight: bold;',
questVideo: 'color: #EB459E;',
questHeartbeat: 'color: #57F287;',
progressBar: 'color: #EB459E; font-weight: bold;',
progressComplete: 'color: #57F287; font-weight: bold;',
technical: 'color: #99AAB5;',
webpack: 'color: #FEE75C;',
time: 'color: #5865F2;',
waiting: 'color: #FEE75C;'
};
this.stores = null;
this.isRunning = false;
this.activeQuests = new Map();
this.questStatusCache = new Map();
this.videoHandlers = new Map();
this.init();
}
init() {
this.printHeader();
this.log('Initializing Discord Auto Quest...', 'info');
try {
this.overrideUserAgent();
this.waitForWebpack();
} catch (error) {
this.log(`Initialization failed: ${error.message}`, 'error', error);
}
}
printHeader() {
console.log(
`%c╔════════════════════════════════════════╗\n` +
`%c║ ║\n` +
`%c║ %cDiscord Auto Quest %c ║\n` +
`%c║ %cAutomated Quest Completer %c ║\n` +
`%c║ ║\n` +
`%c╚════════════════════════════════════════╝`,
this.STYLES.header, this.STYLES.header,
this.STYLES.header, this.STYLES.success, this.STYLES.header,
this.STYLES.header, this.STYLES.info, this.STYLES.header,
this.STYLES.header, this.STYLES.header
);
}
log(message, style = 'info', data = null) {
const timestamp = new Date().toLocaleTimeString();
const prefix = `%c[${timestamp}]%c [Discord Auto Quest]`;
const mainStyle = this.STYLES.time;
const messageStyle = this.STYLES[style] || this.STYLES.info;
if (data) {
console.log(`${prefix} ${message}`, mainStyle, messageStyle, data);
} else {
console.log(`${prefix} ${message}`, mainStyle, messageStyle);
}
}
logQuest(questName, message, type = 'quest') {
const timestamp = new Date().toLocaleTimeString();
const prefix = `%c[${timestamp}]%c [Quest]`;
const questStyle = this.STYLES[type];
const timeStyle = this.STYLES.time;
console.log(`${prefix} %c"${questName}"%c: ${message}`,
timeStyle, this.STYLES.info, questStyle, this.STYLES.info);
}
logProgress(questName, current, total, type = 'progress') {
const percent = Math.min(100, Math.floor((current / total) * 100));
const barLength = 20;
const filled = Math.floor((percent / 100) * barLength);
const empty = barLength - filled;
const bar = '█'.repeat(filled) + '░'.repeat(empty);
const timestamp = new Date().toLocaleTimeString();
const prefix = `%c[${timestamp}]%c [Progress]`;
console.log(
`${prefix} %c"${questName}"%c: [%c${bar}%c] %c${Math.round(current)}/${Math.round(total)}s %c(${percent}%)`,
this.STYLES.time, this.STYLES.info,
this.STYLES.quest,
this.STYLES.info,
this.STYLES.progressBar,
this.STYLES.info,
this.STYLES.questProgress,
percent === 100 ? this.STYLES.progressComplete : this.STYLES.info
);
}
overrideUserAgent() {
const descriptors = {
userAgent: { value: this.ELECTRON_USER_AGENT },
platform: { value: 'Win32' },
appVersion: { value: this.ELECTRON_USER_AGENT }
};
let overridden = 0;
Object.entries(descriptors).forEach(([prop, descriptor]) => {
try {
Object.defineProperty(navigator, prop, {
get: () => descriptor.value,
configurable: true
});
overridden++;
} catch (error) {
this.log(`Failed to override ${prop}`, 'warning');
}
});
if (overridden > 0) {
this.log(`User-Agent override active (${overridden}/3 properties)`, 'success');
}
}
waitForWebpack() {
let attempts = 0;
this.log('Waiting for Discord webpack modules...', 'webpack');
const check = () => {
if (attempts >= this.WEBPACK_MAX_ATTEMPTS) {
this.log('Webpack not found after maximum attempts', 'error');
return;
}
if (typeof window.webpackChunkdiscord_app === 'undefined') {
attempts++;
if (attempts % 10 === 0) {
this.log(`Still waiting for webpack... (attempt ${attempts}/${this.WEBPACK_MAX_ATTEMPTS})`, 'waiting');
}
setTimeout(check, this.WEBPACK_CHECK_INTERVAL);
return;
}
try {
this.loadWebpackModules();
} catch (error) {
this.log(`Webpack loading attempt ${attempts + 1} failed: ${error.message}`, 'warning', error);
attempts++;
setTimeout(check, this.WEBPACK_CHECK_INTERVAL);
}
};
check();
}
loadWebpackModules() {
const originalJQuery = window.$;
delete window.$;
try {
const webpackRequire = window.webpackChunkdiscord_app.push([
[Symbol()],
{},
(require) => require
]);
window.webpackChunkdiscord_app.pop();
if (!webpackRequire?.c || Object.keys(webpackRequire.c).length < 10) {
throw new Error('Insufficient webpack modules loaded');
}
const moduleCount = Object.keys(webpackRequire.c).length;
this.log(`Webpack loaded successfully with ${moduleCount} modules`, 'webpack');
this.stores = this.loadStores(webpackRequire);
if (this.stores) {
this.log('All required stores loaded successfully', 'success');
this.startQuestAutomation();
}
} finally {
if (originalJQuery) {
window.$ = originalJQuery;
}
}
}
findModule(webpackRequire, predicate) {
const modules = Object.values(webpackRequire.c);
for (const module of modules) {
if (!module?.exports) continue;
const exports = module.exports;
const candidates = [
exports.A,
exports.Ay,
exports,
exports.default
].filter(Boolean);
for (const candidate of candidates) {
if (predicate(candidate)) {
return candidate;
}
}
}
return null;
}
loadStores(webpackRequire) {
const requiredStores = [
{ name: 'ApplicationStreamingStore', predicate: m => m.__proto__?.getStreamerActiveStreamMetadata },
{ name: 'RunningGameStore', predicate: m => m.getRunningGames },
{ name: 'QuestsStore', predicate: m => m.__proto__?.getQuest },
{ name: 'ChannelStore', predicate: m => m.__proto__?.getAllThreadsForParent },
{ name: 'GuildChannelStore', predicate: m => m.getSFWDefaultChannel },
{ name: 'FluxDispatcher', predicate: m => m.h?.__proto__?.flushWaitQueue },
{ name: 'api', predicate: m => m.Bo?.get }
];
const stores = {};
const loaded = [];
const missing = [];
requiredStores.forEach(({ name, predicate }) => {
let store = this.findModule(webpackRequire, predicate);
if (name === 'FluxDispatcher' && store?.h) {
store = store.h;
}
if (name === 'api' && store?.Bo) {
store = store.Bo;
}
if (store) {
stores[name] = store;
loaded.push(name);
} else {
missing.push(name);
}
});
if (missing.length > 0) {
this.log(`Missing critical stores: ${missing.join(', ')}`, 'error');
this.log(`Loaded stores: ${loaded.join(', ')}`, 'technical');
throw new Error(`Missing stores: ${missing.join(', ')}`);
}
this.log(`Loaded ${loaded.length} stores successfully`, 'success');
loaded.forEach(storeName => {
this.log(`✓ ${storeName}`, 'technical');
});
return stores;
}
async startQuestAutomation() {
if (this.isRunning) {
this.log('Automation already running', 'warning');
return;
}
this.isRunning = true;
this.log('Starting quest automation...', 'subheader');
try {
this.setupVideoHandlers();
const quests = this.getActiveQuests();
if (quests.length === 0) {
this.log('No active quests found', 'warning');
this.log('Make sure you have enrolled in quests and they are not completed or expired', 'info');
this.isRunning = false;
return;
}
this.log(`Found ${quests.length} active quest(s)`, 'success');
const questStates = quests.map(quest => this.createQuestState(quest));
this.activeQuests = new Map(questStates.map(state => [state.quest.id, state]));
questStates.forEach(state => {
const typeColor = state.taskType.startsWith('WATCH_VIDEO') ?
this.STYLES.questVideo : this.STYLES.questHeartbeat;
console.log(
`%c•%c ${state.questName} %c(${state.taskType})%c - ${state.appName}`,
this.STYLES.success,
this.STYLES.quest,
typeColor,
this.STYLES.info
);
});
await this.processQuests(questStates);
} catch (error) {
this.log(`Automation failed: ${error.message}`, 'error', error);
this.isRunning = false;
}
}
setupVideoHandlers() {
const handleVideoPlayback = (videoElement) => {
if (!videoElement || this.videoHandlers.has(videoElement)) return;
const handler = {
element: videoElement,
isPlaying: false,
retryCount: 0,
maxRetries: 3,
play: async () => {
try {
if (this.handler.isPlaying) return;
// Set volume rendah untuk menghindari suara
videoElement.volume = 0.01;
videoElement.muted = true;
const playPromise = videoElement.play();
if (playPromise !== undefined) {
await playPromise;
this.handler.isPlaying = true;
this.handler.retryCount = 0;
this.log('Video playback started successfully', 'technical');
}
} catch (error) {
this.handler.retryCount++;
if (error.name === 'AbortError') {
this.log('Video playback aborted, will retry...', 'warning');
if (this.handler.retryCount < this.handler.maxRetries) {
setTimeout(() => this.handler.play(), 1000);
}
} else {
this.log(`Video playback error: ${error.name}`, 'warning');
}
}
},
pause: () => {
if (this.handler.isPlaying) {
videoElement.pause();
this.handler.isPlaying = false;
}
}
};
this.videoHandlers.set(videoElement, handler);
videoElement.addEventListener('abort', () => {
this.log('Video aborted by browser', 'warning');
handler.isPlaying = false;
});
videoElement.addEventListener('error', () => {
this.log('Video error occurred', 'warning');
handler.isPlaying = false;
});
videoElement.addEventListener('loadeddata', () => {
this.log('Video data loaded', 'technical');
});
};
const videos = document.querySelectorAll('video');
videos.forEach(handleVideoPlayback);
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeName === 'VIDEO') {
handleVideoPlayback(node);
} else if (node.querySelectorAll) {
node.querySelectorAll('video').forEach(handleVideoPlayback);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
this.videoObserver = observer;
}
getActiveQuests() {
const { QuestsStore } = this.stores;
return [...(QuestsStore.quests?.values() || [])].filter(quest => {
if (!quest?.config || !quest.userStatus) return false;
const isExpired = new Date(quest.config.expiresAt).getTime() <= Date.now();
const isCompleted = !!quest.userStatus.completedAt;
const isEnrolled = !!quest.userStatus.enrolledAt;
if (!isEnrolled || isCompleted || isExpired) return false;
const taskConfig = quest.config.taskConfig ?? quest.config.taskConfigV2;
if (!taskConfig?.tasks) return false;
return Array.from(this.SUPPORTED_TASKS).some(type =>
taskConfig.tasks[type] != null
);
});
}
createQuestState(quest) {
const taskConfig = quest.config.taskConfig ?? quest.config.taskConfigV2;
const taskType = Array.from(this.SUPPORTED_TASKS).find(
type => taskConfig.tasks[type] != null
);
const taskData = taskConfig.tasks[taskType] || {};
const secondsNeeded = taskData.target || 0;
const currentProgress = quest.userStatus?.progress?.[taskType]?.value
?? quest.userStatus?.streamProgressSeconds
?? 0;
return {
quest,
taskType,
secondsNeeded,
currentProgress,
completed: currentProgress >= secondsNeeded,
lastUpdate: 0,
enrolledAt: new Date(quest.userStatus.enrolledAt).getTime(),
appName: quest.config.application?.name || 'Unknown',
appId: quest.config.application?.id || '',
questName: quest.config.messages?.questName || 'Unknown Quest',
attempts: 0,
maxAttempts: 3,
lastError: null,
safeMode: false
};
}
async processQuests(questStates) {
const videoQuests = questStates.filter(s =>
s.taskType.startsWith('WATCH_VIDEO') && !s.completed
);
const heartbeatQuests = questStates.filter(s =>
!s.taskType.startsWith('WATCH_VIDEO') && !s.completed
);
this.log(`Processing ${videoQuests.length} video quest(s) and ${heartbeatQuests.length} heartbeat quest(s)`, 'subheader');
const promises = [];
if (videoQuests.length > 0) {
// Gunakan safe mode untuk video quests
videoQuests.forEach(state => state.safeMode = true);
promises.push(this.processVideoQuestsSafely(videoQuests));
}
if (heartbeatQuests.length > 0) {
promises.push(this.processHeartbeatQuests(heartbeatQuests));
}
if (promises.length === 0) {
this.log('All quests are already completed!', 'success');
this.isRunning = false;
return;
}
try {
await Promise.allSettled(promises);
this.log('All quests have been processed!', 'success');
this.printCompletionMessage();
} catch (error) {
this.log('Quest processing failed', 'error', error);
} finally {
this.isRunning = false;
// Cleanup
this.cleanupVideoHandlers();
}
}
async processVideoQuestsSafely(videoStates) {
this.log(`Starting SAFE video quest processing...`, 'questVideo');
while (videoStates.some(state => !state.completed)) {
const now = Date.now();
for (const state of videoStates) {
if (state.completed) continue;
if (now - state.lastUpdate >= this.VIDEO_INTERVAL) {
await this.safeUpdateVideoProgress(state);
state.lastUpdate = now;
}
}
videoStates = videoStates.filter(state => {
if (state.completed) {
this.logQuest(state.questName, 'Video quest completed!', 'questComplete');
return false;
}
return true;
});
if (videoStates.length === 0) {
break;
}
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
async safeUpdateVideoProgress(state) {
if (state.attempts >= state.maxAttempts) {
this.logQuest(state.questName, 'Max attempts reached, entering safe mode', 'warning');
state.safeMode = true;
return;
}
const { quest, secondsNeeded, enrolledAt, currentProgress, questName } = state;
const MAX_FUTURE = 15;
const SAFE_INCREMENT = 5;
const maxAllowed = Math.floor((Date.now() - enrolledAt) / 1000) + MAX_FUTURE;
if (maxAllowed <= currentProgress) {
this.logQuest(questName, `Waiting for time allowance (${currentProgress}/${maxAllowed}s)`, 'waiting');
return;
}
const remaining = secondsNeeded - currentProgress;
const increment = Math.min(SAFE_INCREMENT, remaining);
const nextProgress = Math.min(
secondsNeeded,
currentProgress + increment
);
try {
this.logQuest(questName, `Sending safe progress: ${Math.round(nextProgress)}/${secondsNeeded}s`, 'questVideo');
const response = await this.stores.api.post({
url: `/quests/${quest.id}/video-progress`,
body: { timestamp: Math.round(nextProgress) },
timeout: 10000
}).catch(err => {
throw new Error(`API Error: ${err.message}`);
});
state.currentProgress = nextProgress;
state.attempts = 0;
state.lastError = null;
if (response && response.body) {
const serverProgress = response.body.progress?.[state.taskType]?.value ?? nextProgress;
if (serverProgress > state.currentProgress) {
state.currentProgress = serverProgress;
this.logQuest(questName, `Server progress updated to: ${serverProgress}s`, 'technical');
}
if (response.body.completed_at != null || state.currentProgress >= secondsNeeded) {
await this.sendFinalUpdate(state);
state.completed = true;
return;
}
}
this.logProgress(questName, state.currentProgress, secondsNeeded, 'questVideo');
} catch (error) {
state.attempts++;
state.lastError = error.message;
if (error.message.includes('AbortError') || error.message.includes('play()')) {
this.logQuest(questName, 'Video playback interrupted, will retry...', 'warning');
await new Promise(resolve => setTimeout(resolve, 3000));
} else {
this.logQuest(questName, `Update failed: ${error.message}`, 'error');
if (error.status === 400 || error.status === 403 || error.status === 429) {
await new Promise(resolve => setTimeout(resolve, 5000));
if (error.status === 429) {
this.logQuest(questName, 'Rate limited, waiting 30 seconds...', 'warning');
await new Promise(resolve => setTimeout(resolve, 30000));
}
}
}
}
}
async sendFinalUpdate(state) {
try {
await this.stores.api.post({
url: `/quests/${state.quest.id}/video-progress`,
body: { timestamp: state.secondsNeeded }
});
this.logQuest(state.questName, 'Final update sent successfully', 'success');
} catch (error) {
this.logQuest(state.questName, 'Final update failed (ignored)', 'technical');
}
}
async processHeartbeatQuests(heartbeatStates) {
this.log(`Starting heartbeat quest processing...`, 'questHeartbeat');
while (heartbeatStates.some(state => !state.completed)) {
const now = Date.now();
for (const state of heartbeatStates) {
if (state.completed) continue;
if (now - state.lastUpdate >= this.HEARTBEAT_INTERVAL) {
await this.sendHeartbeat(state);
state.lastUpdate = now;
}
}
heartbeatStates = heartbeatStates.filter(state => {
if (state.completed) {
this.logQuest(state.questName, 'Heartbeat quest completed!', 'questComplete');
return false;
}
return true;
});
if (heartbeatStates.length === 0) {
break;
}
this.log(`Waiting ${Math.ceil(this.HEARTBEAT_INTERVAL / 1000)}s for next heartbeat...`, 'waiting');
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
async sendHeartbeat(state) {
if (state.attempts >= state.maxAttempts) {
this.logQuest(state.questName, 'Max attempts reached, skipping', 'warning');
return;
}
const { quest, taskType, secondsNeeded, questName } = state;
const { api, ChannelStore, GuildChannelStore } = this.stores;
let streamKey = `call:${quest.id}:1`;
if (taskType === 'PLAY_ACTIVITY') {
const channelId = this.findVoiceChannelId(ChannelStore, GuildChannelStore);
if (channelId) {
streamKey = `call:${channelId}:1`;
} else {
this.logQuest(questName, 'No voice channel found for activity quest', 'warning');
return;
}
}
try {
this.logQuest(questName, 'Sending heartbeat...', 'questHeartbeat');
const response = await api.post({
url: `/quests/${quest.id}/heartbeat`,
body: {
stream_key: streamKey,
terminal: state.currentProgress >= secondsNeeded - 5
}
});
const serverProgress = response.body?.progress?.[taskType]?.value ?? state.currentProgress;
state.currentProgress = serverProgress;
state.attempts = 0;
this.logProgress(questName, state.currentProgress, secondsNeeded, 'questHeartbeat');
if (state.currentProgress >= secondsNeeded || response.body?.completed_at != null) {
try {
await api.post({
url: `/quests/${quest.id}/heartbeat`,
body: { stream_key: streamKey, terminal: true }
});
} catch (e) {
// Ignore
}
state.completed = true;
return;
}
} catch (error) {
state.attempts++;
this.logQuest(questName, `Heartbeat failed: ${error.message}`, 'error');
if (error.status === 400 || error.status === 403 || error.status === 429) {
this.logQuest(questName, 'API error, waiting before retry...', 'warning');
await new Promise(resolve => setTimeout(resolve, 10000));
}
}
}
findVoiceChannelId(ChannelStore, GuildChannelStore) {
const privateChannels = ChannelStore.getSortedPrivateChannels?.() || [];
let channelId = privateChannels[0]?.id;
if (!channelId) {
const guilds = Object.values(GuildChannelStore.getAllGuilds?.() || {});
const guildWithVoice = guilds.find(guild =>
guild?.VOCAL?.length > 0
);
if (guildWithVoice) {
channelId = guildWithVoice.VOCAL[0]?.channel?.id;
}
}
return channelId;
}
cleanupVideoHandlers() {
if (this.videoObserver) {
this.videoObserver.disconnect();
}
this.videoHandlers.forEach(handler => {
if (handler.isPlaying) {
handler.pause();
}
});
this.videoHandlers.clear();
}
printCompletionMessage() {
console.log(
`%c╔════════════════════════════════════════╗\n` +
`%c║ ║\n` +
`%c║ %c🎉 All Quests Complete! %c║\n` +
`%c║ %c✅ Automation Finished %c ║\n` +
`%c║ %c✨ Happy Questing! %c ║\n` +
`%c║ ║\n` +
`%c╚════════════════════════════════════════╝`,
this.STYLES.success, this.STYLES.success,
this.STYLES.success, this.STYLES.header, this.STYLES.success,
this.STYLES.success, this.STYLES.success, this.STYLES.success,
this.STYLES.success, this.STYLES.success, this.STYLES.success,
this.STYLES.success, this.STYLES.success
);
}
}
try {
new DiscordAutoQuest();
} catch (error) {
console.error('Fatal error starting Discord Auto Quest:', error);
}
})();
@SayloorMooon

Copy link
Copy Markdown

works

@Ap0dexMe0

Copy link
Copy Markdown
Author

works

perfect

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment