
ทำเควสเล่นเกม Discord ง่ายๆ (PC)
🎮 วิธีทำเควส Discord ง่ายๆ รับแต้มแลกกรอบโปรไฟล์สุดปัง!
💡 ทำไมต้องใช้ตัวช่วย? (Why use this tool?): ปัญหาหลักของการทำเควส Discord คือบางทีเราไม่ได้เล่นเกมนั้นๆ อยู่แล้ว การจะให้มานั่งดาวน์โหลดเกมเต็มขนาดหลายสิบกิ๊ก (Gigabytes) เพียงเพื่อเอาเวลาเล่นแค่ 15 นาที มันทั้งเสียเวลาโหลดและเปลืองพื้นที่จัดเก็บข้อมูลในเครื่องแบบสุดๆ! เราจึงต้องใช้ โปรแกรมตัวช่วย เพื่อข้ามขั้นตอนนี้ไปเลยครับ
🖥️ วิธีทำ (เฉพาะบนคอมพิวเตอร์)
“ใช้สำหรับรันสคริปต์ดูวิดีโอ + เล่นเกมให้ครบเงื่อนไข
1️⃣ เข้าไปที่โฟลเดอร์ Discord
- กด
Win + Rเพื่อเปิดหน้าต่าง Run - พิมพ์
%appdata%แล้วกดEnter - หาโฟลเดอร์ชื่อ
Discordแล้วดับเบิ้ลคลิกเข้าไปเลย
2️⃣ แก้ไขไฟล์ settings.json
- เมื่ออยู่ในโฟลเดอร์
Discordแล้ว - หาไฟล์ที่ชื่อว่า
settings.json - คลิกขวา → เลือก
Open with→Notepad(หรือ Text Editor อื่นๆ ก็ได้) คุณจะเห็นโค้ดหน้าตาประมาณด้านล่างนี้:
json1{ 2 "BACKGROUND_COLOR": "#2c2d32", 3 "audioSubsystem": "experimental", 4 "offloadAdmControls": true, 5 "chromiumSwitches": {}, 6 "IS_MAXIMIZED": false, 7 "IS_MINIMIZED": false, 8 "WINDOW_BOUNDS": { 9 "x": 21, 10 "y": 0, 11 "width": 1463, 12 "height": 1392 13 }, 14 "asyncVideoInputDeviceInit": true 15}
3️⃣ แทรกข้อความลงในไฟล์
ให้นำข้อความนี้: "DANGEROUS_ENABLE_DEVTOOLS_ONLY_ENABLE_IF_YOU_KNOW_WHAT_YOURE_DOING": true,
ไปแทรกเพิ่มเข้าไปในไฟล์ให้เรียบร้อย (ดูตัวอย่างรูปด้านล่างเป็นแนวทาง)
json1{ 2 "BACKGROUND_COLOR": "#2c2d32", 3 "audioSubsystem": "experimental", 4 "offloadAdmControls": true, 5 "DANGEROUS_ENABLE_DEVTOOLS_ONLY_ENABLE_IF_YOU_KNOW_WHAT_YOURE_DOING": true, 6 "chromiumSwitches": {}, 7 "IS_MAXIMIZED": false, 8 "IS_MINIMIZED": false, 9 "WINDOW_BOUNDS": { 10 "x": 21, 11 "y": 0, 12 "width": 1463, 13 "height": 1392 14 }, 15 "asyncVideoInputDeviceInit": true 16}
4️⃣ รีสตาร์ท Discord
- ปิด Discord ให้หมดจด (คลิกขวาที่ไอคอนมุมขวาล่าง System Tray แล้วกด Quit Discord ด้วยนะ)
- เปิดโปรแกรม Discord ขึ้นมาใหม่อีกครั้ง
5️⃣ เปิดหน้า Quest และรัน Script
- เข้าไปที่หน้า Quest ใน Discord ของคุณ
- กดปุ่มลูกเล่นนักพัฒนา:
Ctrl + Shift + IหรือF12 - คลิกไปที่แท็บ Console
- วาง Script ที่เราเตรียมไว้ให้ลงไป (ปล. ถ้าระบบไม่อนุญาตให้วาง ให้พิมพ์คำว่า
allow pastingแล้วกด Enter นำร่องไปก่อน 1 รอบ)
Script เปิดเพื่อดู
javascript1/** 2 * ██████╗ ██╗ ██╗███╗ ██╗███╗ ██╗ 3 * ██╔══██╗██║ ██║████╗ ██║████╗ ██║ 4 * ██████╔╝██║ ██║██╔██╗ ██║██╔██╗ ██║ 5 * ██╔═══╝ ██║ ██║██║╚██╗██║██║╚██╗██║ 6 * ██║ ╚██████╔╝██║ ╚████║██║ ╚████║ 7 * ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═══╝ 8 * 9 * PUNN Quest Engine v2.0 10 * Discord Quest Auto-Completer 11 * 12 * วิธีใช้: Copy ทั้งหมด → Paste ลง Console (F12) 13 * กด Shift+. เพื่อ Hide/Show 14 */ 15(async () => { 16 "use strict"; 17 18 const CONFIG = { 19 NAME: "PUNN", 20 VERSION: "v2.0", 21 C1: "#b4befe", 22 C2: "#cba6f7", 23 C3: "#f5c2e7", 24 OK: "#a6e3a1", 25 WARN: "#f9e2af", 26 ERR: "#f38ba8", 27 VIDEO_SPEED: 7, 28 VIDEO_MAX_FUTURE: 10, 29 HIDE_ACTIVITY: false, 30 GAME_CONCURRENCY: 10, 31 REQUEST_DELAY: 1500, 32 REMOVE_DELAY: 3000, 33 HEARTBEAT_STAGGER: 3000, 34 RUNNING: true, 35 MAX_TASK_TIME: 30 * 60 * 1000, 36 }; 37 38 if (window.__punnLock) { 39 const ui = document.getElementById("punn-root"); 40 if (ui) ui.style.display = "flex"; 41 return console.warn(`[${CONFIG.NAME}] Already running.`); 42 } 43 window.__punnLock = true; 44 45 const isApp = typeof DiscordNative !== "undefined"; 46 const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); 47 const rnd = (a, b) => Math.floor(Math.random() * (b - a + 1)) + a; 48 const fmt = (s) => { 49 const m = Math.floor(s / 60); 50 const sec = s % 60; 51 return m > 0 ? `${m}m ${sec}s` : `${sec}s`; 52 }; 53 54 // ──────────────────────────── ICONS ──────────────────────────── 55 const I = { 56 BOLT: `<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M11 21h-1l1-7H7.5c-.58 0-.57-.32-.29-.62L14.5 3h1l-1 7h3.5c.58 0 .57.32.29.62L11 21z"/></svg>`, 57 PLAY: `<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>`, 58 GAME: `<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-10 7H8v3H6v-3H3v-2h3V8h2v3h3v2zm4.5 2c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm4-3c-.83 0-1.5-.67-1.5-1.5S18.67 9 19.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/></svg>`, 59 STREAM: `<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/></svg>`, 60 ACTIVITY: `<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2a10 10 0 100 20 10 10 0 000-20zm0 14.5a4.5 4.5 0 110-9 4.5 4.5 0 010 9zm0-5.5a1 1 0 100 2 1 1 0 000-2z"/></svg>`, 61 CHECK: `<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>`, 62 CLOCK: `<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/><path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"/></svg>`, 63 STOP: `<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M6 6h12v12H6z"/></svg>`, 64 WARN: `<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>`, 65 MINIMIZE: `<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M19 13H5v-2h14v2z"/></svg>`, 66 }; 67 68 // ──────────────────────────── STORAGE ──────────────────────────── 69 const Store = { 70 set(k, v) { try { localStorage.setItem(`punn_${k}`, JSON.stringify(v)); } catch {} }, 71 get(k) { try { const v = localStorage.getItem(`punn_${k}`); return v ? JSON.parse(v) : null; } catch { return null; } }, 72 }; 73 74 // ──────────────────────────── UI ENGINE ──────────────────────────── 75 const UI = { 76 el: null, 77 tasks: new Map(), 78 logLines: [], 79 collapsed: false, 80 81 init() { 82 document.getElementById("punn-root")?.remove(); 83 document.getElementById("punn-style")?.remove(); 84 const pos = Store.get("pos") || { top: "20px", right: "20px", left: "auto" }; 85 86 const css = document.createElement("style"); 87 css.id = "punn-style"; 88 css.textContent = ` 89 90 @keyframes pIn { from{transform:translateY(-12px);opacity:0;} to{transform:translateY(0);opacity:1;} } 91 @keyframes pOut { 0%{opacity:1;max-height:80px;} 100%{opacity:0;max-height:0;margin:0;padding:0;} } 92 @keyframes pPulse { 0%,100%{opacity:1;} 50%{opacity:0.5;} } 93 94 #punn-root { 95 position:fixed; top:${pos.top}; left:${pos.left}; right:${pos.right}; 96 width:360px; background:#1e1e2e; color:#cdd6f4; 97 border-radius:16px; font-family:'Inter',system-ui,-apple-system,sans-serif; 98 z-index:99999; border:1px solid #313244; 99 box-shadow: 0 8px 32px rgba(0,0,0,0.4), 0 2px 8px rgba(0,0,0,0.2); 100 overflow:hidden; animation:pIn 0.35s ease-out; 101 display:flex; flex-direction:column; 102 } 103 104 #punn-head { 105 padding:14px 16px; background:#181825; 106 display:flex; justify-content:space-between; align-items:center; 107 border-bottom:1px solid #313244; cursor:grab; user-select:none; 108 } 109 #punn-head:active { cursor:grabbing; } 110 111 .punn-brand { display:flex; align-items:center; gap:11px; } 112 .punn-logo { 113 width:32px; height:32px; border-radius:10px; 114 background:linear-gradient(135deg, ${CONFIG.C1}, ${CONFIG.C2}, ${CONFIG.C3}); 115 display:flex; align-items:center; justify-content:center; 116 font-weight:800; font-size:13px; color:#1e1e2e; letter-spacing:1px; 117 box-shadow: 0 2px 8px rgba(180,190,254,0.25); 118 } 119 .punn-title-wrap { display:flex; flex-direction:column; } 120 .punn-title { 121 font-weight:800; font-size:15px; letter-spacing:2px; color:#cdd6f4; 122 } 123 .punn-sub { 124 font-size:9px; color:#585b70; font-weight:500; 125 letter-spacing:0.5px; margin-top:1px; 126 } 127 .punn-env { 128 font-size:8px; padding:2px 8px; border-radius:8px; font-weight:700; 129 letter-spacing:0.5px; 130 } 131 .punn-env-app { background:rgba(166,227,161,0.1); color:${CONFIG.OK}; } 132 .punn-env-web { background:rgba(249,226,175,0.1); color:${CONFIG.WARN}; } 133 134 .punn-ctrl { display:flex; gap:6px; align-items:center; } 135 .punn-btn { 136 cursor:pointer; opacity:0.4; transition:all 0.2s ease; display:flex; 137 align-items:center; justify-content:center; border-radius:8px; 138 width:28px; height:28px; 139 } 140 .punn-btn:hover { opacity:1; background:#313244; } 141 .punn-btn-stop { 142 opacity:0.6; color:${CONFIG.ERR}; 143 font-size:9px; font-weight:700; gap:3px; padding:0 10px; width:auto; 144 letter-spacing:0.5px; border-radius:8px; 145 } 146 .punn-btn-stop:hover { background:rgba(243,139,168,0.1); opacity:1; } 147 148 #punn-body { 149 padding:8px 10px; max-height:380px; overflow-y:auto; flex-grow:1; 150 transition:max-height 0.3s ease; 151 } 152 #punn-body.collapsed { max-height:0; padding:0 10px; overflow:hidden; } 153 #punn-body::-webkit-scrollbar { width:4px; } 154 #punn-body::-webkit-scrollbar-track { background:transparent; } 155 #punn-body::-webkit-scrollbar-thumb { background:#45475a; border-radius:4px; } 156 157 .punn-empty { 158 text-align:center; padding:28px; color:#585b70; 159 font-size:12px; font-weight:500; 160 } 161 162 .punn-card { 163 display:flex; gap:11px; padding:10px 13px; margin-bottom:4px; 164 background:#181825; border-radius:12px; 165 border:1px solid #313244; 166 transition:all 0.2s ease; position:relative; overflow:hidden; 167 } 168 .punn-card:hover { background:#1e1e2e; border-color:#45475a; } 169 .punn-card::before { 170 content:''; position:absolute; left:0; top:0; bottom:0; width:3px; 171 background:${CONFIG.C1}; border-radius:3px 0 0 3px; 172 } 173 .punn-card.s-done::before { background:${CONFIG.OK}; } 174 .punn-card.s-done { border-color:rgba(166,227,161,0.15); } 175 .punn-card.s-queue { opacity:0.4; } 176 .punn-card.s-queue::before { background:#585b70; } 177 .punn-card.s-warn::before { background:${CONFIG.WARN}; } 178 .punn-card.s-warn { border-color:rgba(249,226,175,0.15); } 179 .punn-card.s-rm { animation:pOut 0.4s ease forwards; } 180 181 .p-ico { 182 min-width:36px; height:36px; border-radius:10px; 183 display:flex; align-items:center; justify-content:center; 184 background:#1e1e2e; color:${CONFIG.C1}; 185 border:1px solid #313244; 186 } 187 .punn-card.s-done .p-ico { color:${CONFIG.OK}; border-color:rgba(166,227,161,0.2); } 188 .punn-card.s-queue .p-ico { color:#585b70; border-color:#313244; } 189 .punn-card.s-warn .p-ico { color:${CONFIG.WARN}; border-color:rgba(249,226,175,0.2); } 190 191 .p-body { flex:1; min-width:0; } 192 .p-row1 { display:flex; justify-content:space-between; align-items:center; margin-bottom:3px; } 193 .p-name { 194 font-size:12px; font-weight:600; color:#cdd6f4; 195 white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:165px; 196 } 197 .p-badge { 198 font-size:8px; font-weight:700; padding:2px 8px; border-radius:6px; 199 letter-spacing:0.5px; display:flex; align-items:center; gap:4px; 200 } 201 .p-b-live { 202 background:rgba(180,190,254,0.12); color:${CONFIG.C1}; 203 } 204 .p-b-live::before { 205 content:''; width:5px; height:5px; border-radius:50%; 206 background:${CONFIG.C1}; animation:pPulse 2s ease infinite; 207 } 208 .p-b-done { background:rgba(166,227,161,0.12); color:${CONFIG.OK}; } 209 .p-b-queue { background:rgba(88,91,112,0.2); color:#585b70; } 210 .p-b-warn { background:rgba(249,226,175,0.12); color:${CONFIG.WARN}; } 211 212 .p-row2 { 213 display:flex; justify-content:space-between; align-items:center; 214 font-size:10px; color:#585b70; margin-bottom:6px; font-weight:500; 215 } 216 .p-pct { font-weight:600; font-size:10px; color:${CONFIG.C1}; font-family:'JetBrains Mono',monospace; } 217 .punn-card.s-done .p-pct { color:${CONFIG.OK}; } 218 219 .p-track { 220 height:3px; background:#313244; border-radius:3px; 221 overflow:hidden; 222 } 223 .p-fill { 224 height:100%; border-radius:3px; transition:width 0.5s ease; 225 background:linear-gradient(90deg, ${CONFIG.C1}, ${CONFIG.C2}); 226 } 227 .punn-card.s-done .p-fill { background:${CONFIG.OK}; } 228 229 #punn-log { 230 padding:8px 14px; background:#11111b; max-height:100px; 231 overflow-y:auto; border-top:1px solid #313244; 232 font-family:'JetBrains Mono','Consolas',monospace; 233 font-size:10px; color:#585b70; scroll-behavior:smooth; 234 transition:max-height 0.3s ease; 235 } 236 #punn-log.collapsed { max-height:0; padding:0 14px; overflow:hidden; } 237 #punn-log::-webkit-scrollbar { width:3px; } 238 #punn-log::-webkit-scrollbar-thumb { background:#313244; border-radius:3px; } 239 240 .p-log { 241 margin-bottom:1px; display:flex; gap:8px; line-height:1.6; 242 padding:1px 0; 243 } 244 .p-ts { opacity:0.4; min-width:46px; font-size:9px; } 245 .p-l-info { color:${CONFIG.C1}; } 246 .p-l-ok { color:${CONFIG.OK}; } 247 .p-l-warn { color:${CONFIG.WARN}; } 248 .p-l-err { color:${CONFIG.ERR}; } 249 .p-l-dim { color:#45475a; } 250 251 #punn-foot { 252 padding:6px; text-align:center; font-size:8px; letter-spacing:1.5px; 253 color:#45475a; font-weight:600; 254 background:#11111b; border-top:1px solid #313244; 255 } 256 `; 257 document.head.appendChild(css); 258 259 this.el = document.createElement("div"); 260 this.el.id = "punn-root"; 261 const envTag = isApp 262 ? `<span class="punn-env punn-env-app">⚡ APP</span>` 263 : `<span class="punn-env punn-env-web">🌐 WEB</span>`; 264 265 this.el.innerHTML = ` 266 <div id="punn-head"> 267 <div class="punn-brand"> 268 <div class="punn-logo">P</div> 269 <div class="punn-title-wrap"> 270 <div class="punn-title">${CONFIG.NAME}</div> 271 <div class="punn-sub">${CONFIG.VERSION} · Quest Engine</div> 272 </div> 273 ${envTag} 274 </div> 275 <div class="punn-ctrl"> 276 <div class="punn-btn punn-btn-stop" id="punn-stop">${I.STOP} STOP</div> 277 <div class="punn-btn" id="punn-min" title="Minimize">${I.MINIMIZE}</div> 278 </div> 279 </div> 280 <div id="punn-body"><div class="punn-empty">◈ Initializing...</div></div> 281 <div id="punn-log"></div> 282 <div id="punn-foot">PUNN · QUEST ENGINE</div> 283 `; 284 document.body.appendChild(this.el); 285 286 // ── Drag ── 287 const head = document.getElementById("punn-head"); 288 let drag = false, sx, sy, ix, iy; 289 head.onmousedown = (e) => { 290 if (e.target.closest(".punn-btn")) return; 291 drag = true; sx = e.clientX; sy = e.clientY; 292 const r = this.el.getBoundingClientRect(); 293 ix = r.left; iy = r.top; 294 this.el.style.right = "auto"; 295 e.preventDefault(); 296 }; 297 document.onmousemove = (e) => { 298 if (!drag) return; 299 this.el.style.left = `${ix + (e.clientX - sx)}px`; 300 this.el.style.top = `${iy + (e.clientY - sy)}px`; 301 }; 302 document.onmouseup = () => { 303 if (drag) { 304 drag = false; 305 Store.set("pos", { top: this.el.style.top, left: this.el.style.left, right: "auto" }); 306 } 307 }; 308 309 document.getElementById("punn-min").onclick = () => this.toggleCollapse(); 310 document.getElementById("punn-stop").onclick = () => this.shutdown(); 311 document.addEventListener("keydown", (e) => { 312 if (e.key === ">" || (e.shiftKey && e.key === ".")) { 313 this.el.style.display = this.el.style.display === "none" ? "flex" : "none"; 314 } 315 }); 316 317 try { if (Notification.permission === "default") Notification.requestPermission(); } catch {} 318 }, 319 320 toggleCollapse() { 321 this.collapsed = !this.collapsed; 322 document.getElementById("punn-body")?.classList.toggle("collapsed", this.collapsed); 323 document.getElementById("punn-log")?.classList.toggle("collapsed", this.collapsed); 324 }, 325 326 shutdown() { 327 if (!CONFIG.RUNNING) return; 328 CONFIG.RUNNING = false; 329 this.log("Shutting down...", "warn"); 330 Patcher.clean(); 331 setTimeout(() => { this.el?.remove(); document.getElementById("punn-style")?.remove(); window.__punnLock = false; }, 1000); 332 }, 333 334 setTask(id, data) { 335 this.tasks.set(id, data); 336 this.render(); 337 }, 338 339 removeTask(id) { 340 const t = this.tasks.get(id); 341 if (t) { t._removing = true; this.render(); setTimeout(() => { this.tasks.delete(id); this.render(); }, 500); } 342 }, 343 344 log(msg, type = "info") { 345 const colors = { info: CONFIG.C1, ok: CONFIG.OK, warn: CONFIG.WARN, err: CONFIG.ERR, dim: "#555" }; 346 console.log(`%c[PUNN] %c${msg}`, `color:${CONFIG.C1};font-weight:bold`, `color:${colors[type]||colors.info}`); 347 const box = document.getElementById("punn-log"); 348 if (!box) return; 349 const el = document.createElement("div"); 350 el.className = `p-log p-l-${type}`; 351 el.innerHTML = `<span class="p-ts">${new Date().toLocaleTimeString().split(" ")[0]}</span><span>${msg}</span>`; 352 box.appendChild(el); 353 box.scrollTop = box.scrollHeight; 354 while (box.children.length > 50) box.firstChild.remove(); 355 }, 356 357 render() { 358 const body = document.getElementById("punn-body"); 359 if (!body) return; 360 if (!this.tasks.size) return (body.innerHTML = `<div class="punn-empty">◈ Waiting for quests...</div>`); 361 362 body.innerHTML = ""; 363 const sorted = [...this.tasks.entries()].sort((a, b) => { 364 const order = (t) => t.status === "done" ? 3 : t.status === "warn" ? 2 : t.status === "queue" ? 1 : 0; 365 return order(a[1]) - order(b[1]); 366 }); 367 368 for (const [id, t] of sorted) { 369 const pct = t.max > 0 ? Math.min(100, (t.cur / t.max) * 100).toFixed(1) : 0; 370 let icon = I.BOLT, badge = "", sc = ""; 371 372 if (t.status === "done") { 373 icon = I.CHECK; badge = `<span class="p-badge p-b-done">✓ DONE</span>`; sc = "s-done"; 374 } else if (t.status === "warn") { 375 icon = I.WARN; badge = `<span class="p-badge p-b-warn">SKIP</span>`; sc = "s-warn"; 376 } else if (t.status === "queue") { 377 icon = I.CLOCK; badge = `<span class="p-badge p-b-queue">QUEUE</span>`; sc = "s-queue"; 378 } else { 379 badge = `<span class="p-badge p-b-live">LIVE</span>`; 380 if (t.type === "VIDEO") icon = I.PLAY; 381 else if (t.type === "GAME") icon = I.GAME; 382 else if (t.type === "STREAM") icon = I.STREAM; 383 else if (t.type === "ACTIVITY") icon = I.ACTIVITY; 384 } 385 386 const eta = t.status === "run" && t.cur > 0 387 ? `~${fmt(Math.ceil(t.max - t.cur))}` 388 : `${Math.floor(t.cur)}/${t.max}s`; 389 390 body.innerHTML += ` 391 <div class="punn-card ${sc} ${t._removing ? 's-rm' : ''}"> 392 <div class="p-ico">${icon}</div> 393 <div class="p-body"> 394 <div class="p-row1"> 395 <div class="p-name" title="${t.name}">${t.name}</div> 396 ${badge} 397 </div> 398 <div class="p-row2"> 399 <span>${t.type || ''}</span> 400 <span class="p-pct">${pct > 0 ? Math.floor(pct) + '%' : eta}</span> 401 </div> 402 <div class="p-track"><div class="p-fill" style="width:${pct}%"></div></div> 403 </div> 404 </div>`; 405 } 406 }, 407 }; 408 409 // ──────────────────────────── TRAFFIC QUEUE ──────────────────────────── 410 const Traffic = { 411 q: [], busy: false, 412 async send(url, body) { 413 if (!CONFIG.RUNNING) throw "Stopped"; 414 return new Promise((ok, fail) => { this.q.push({ url, body, ok, fail }); this.run(); }); 415 }, 416 async run() { 417 if (this.busy || !this.q.length) return; 418 this.busy = true; 419 while (this.q.length) { 420 if (!CONFIG.RUNNING) { this.q = []; this.busy = false; return; } 421 const r = this.q.shift(); 422 try { 423 r.ok(await Mods.API.post({ url: r.url, body: r.body })); 424 } catch (e) { 425 if (e.status === 429) { 426 const wait = (e.body?.retry_after || 5) * 1000; 427 UI.log(`⏱ Rate limit — waiting ${(wait/1000).toFixed(0)}s`, "warn"); 428 this.q.unshift(r); 429 await sleep(wait + 1000); 430 } else r.fail(e); 431 } 432 await sleep(CONFIG.REQUEST_DELAY); 433 } 434 this.busy = false; 435 }, 436 }; 437 438 // ──────────────────────────── MODULES ──────────────────────────── 439 const EVT = { HB: "QUESTS_SEND_HEARTBEAT_SUCCESS", GAME: "RUNNING_GAMES_CHANGE", RPC: "LOCAL_ACTIVITY_UPDATE" }; 440 let Mods = {}; 441 442 function loadModules() { 443 try { 444 const wp = webpackChunkdiscord_app.push([[Symbol()], {}, (r) => r]); 445 webpackChunkdiscord_app.pop(); 446 447 const find = (fn) => Object.values(wp.c).find((m) => { try { return fn(m?.exports); } catch { return false; } })?.exports; 448 const pick = (fns) => { for (const f of fns) { try { const r = f(); if (r) return r; } catch {} } return null; }; 449 450 Mods = { 451 StreamStore: pick([ 452 () => find(e => e?.A?.__proto__?.getStreamerActiveStreamMetadata)?.A, 453 () => find(e => e?.Z?.__proto__?.getStreamerActiveStreamMetadata)?.Z, 454 () => find(e => e?.default?.__proto__?.getStreamerActiveStreamMetadata)?.default, 455 ]), 456 RunStore: pick([ 457 () => find(e => e?.Ay?.getRunningGames)?.Ay, 458 () => find(e => e?.ZP?.getRunningGames)?.ZP, 459 () => find(e => e?.default?.getRunningGames)?.default, 460 ]), 461 QuestStore: pick([ 462 () => find(e => e?.A?.__proto__?.getQuest)?.A, 463 () => find(e => e?.Z?.__proto__?.getQuest)?.Z, 464 () => find(e => e?.default?.__proto__?.getQuest)?.default, 465 ]), 466 ChanStore: pick([ 467 () => find(e => e?.A?.__proto__?.getAllThreadsForParent)?.A, 468 () => find(e => e?.Z?.__proto__?.getAllThreadsForParent)?.Z, 469 () => find(e => e?.default?.__proto__?.getAllThreadsForParent)?.default, 470 ]), 471 GuildChanStore: pick([ 472 () => find(e => e?.Ay?.getSFWDefaultChannel)?.Ay, 473 () => find(e => e?.ZP?.getSFWDefaultChannel)?.ZP, 474 () => find(e => e?.default?.getSFWDefaultChannel)?.default, 475 ]), 476 Dispatcher: pick([ 477 () => find(e => e?.h?.__proto__?.flushWaitQueue)?.h, 478 () => find(e => e?.Z?.__proto__?.flushWaitQueue)?.Z, 479 () => find(e => e?.default?.__proto__?.flushWaitQueue)?.default, 480 ]), 481 API: pick([ 482 () => find(e => e?.Bo?.get)?.Bo, 483 () => find(e => e?.tn?.get)?.tn, 484 () => find(e => e?.default?.get && e?.default?.post)?.default, 485 () => { 486 const mod = find(e => Object.keys(e || {}).some(k => e[k]?.get && e[k]?.post && e[k]?.patch)); 487 if (mod) { const k = Object.keys(mod).find(k => mod[k]?.get && mod[k]?.post); return mod[k]; } 488 }, 489 ]), 490 }; 491 492 const status = Object.entries(Mods).map(([k, v]) => `${k}:${v ? "✓" : "✗"}`).join(" "); 493 UI.log(`Modules ${status}`, Mods.QuestStore && Mods.API ? "ok" : "err"); 494 495 if (!Mods.QuestStore || !Mods.API || !Mods.Dispatcher) throw "Core modules missing"; 496 Patcher.init(Mods.RunStore); 497 return true; 498 } catch (e) { 499 UI.log(`Module load failed: ${e}`, "err"); 500 return false; 501 } 502 } 503 504 // ──────────────────────────── GAME PATCHER ──────────────────────────── 505 const Patcher = { 506 games: [], origGet: null, origPID: null, on: false, 507 init(store) { 508 if (!store) return; 509 this.origGet = store.getRunningGames; 510 this.origPID = store.getGameForPID; 511 }, 512 patch() { 513 if (!Mods.RunStore || this.on) return; 514 Mods.RunStore.getRunningGames = () => [...this.origGet.call(Mods.RunStore), ...this.games]; 515 Mods.RunStore.getGameForPID = (pid) => this.games.find(g => g.pid === pid) || this.origPID.call(Mods.RunStore, pid); 516 this.on = true; 517 }, 518 unpatch() { 519 if (!this.on) return; 520 Mods.RunStore.getRunningGames = this.origGet; 521 Mods.RunStore.getGameForPID = this.origPID; 522 this.on = false; 523 }, 524 add(g) { 525 this.games.push(g); 526 this.patch(); 527 Mods.Dispatcher.dispatch({ type: EVT.GAME, added: [g], removed: [], games: Mods.RunStore.getRunningGames() }); 528 }, 529 remove(g) { 530 this.games = this.games.filter(x => x.pid !== g.pid); 531 Mods.Dispatcher.dispatch({ type: EVT.GAME, added: [], removed: [g], games: Mods.RunStore.getRunningGames() }); 532 if (!this.games.length) this.unpatch(); 533 }, 534 clean() { this.games = []; this.unpatch(); }, 535 }; 536 537 // ──────────────────────────── TASK RUNNERS ──────────────────────────── 538 const Quester = { 539 clean(n) { return n.replace(/[^a-zA-Z0-9 ]/g, "").trim().replace(/\s+/g, " "); }, 540 541 async getAppInfo(id, name) { 542 try { 543 const r = await Mods.API.get({ url: `/applications/public?application_ids=${id}` }); 544 const a = r.body[0]; 545 const exe = a?.executables?.find(x => x.os === "win32")?.name?.replace(">", "") || `${this.clean(name).replace(/\s+/g, "")}.exe`; 546 const cn = this.clean(a?.name || name); 547 return { name: a?.name || name, icon: a?.icon, exe, cmd: `C:\\Program Files\\${cn}\\${exe}`, path: `c:/program files/${cn.toLowerCase()}/${exe}`, id }; 548 } catch { 549 const cn = this.clean(name); 550 const exe = `${cn.replace(/\s+/g, "")}.exe`; 551 return { name, exe, cmd: `C:\\Program Files\\${cn}\\${exe}`, path: `c:/program files/${cn.toLowerCase()}/${exe}`, id }; 552 } 553 }, 554 555 // ── VIDEO ── 556 async doVideo(quest, task, userStatus) { 557 let cur = userStatus.progress?.[task.taskKey]?.value ?? 0; 558 const t0 = new Date(userStatus.enrolledAt).getTime(); 559 UI.setTask(quest.id, { name: task.name, type: "VIDEO", cur, max: task.target, status: "run" }); 560 561 const started = Date.now(); 562 let done = false; 563 564 while (cur < task.target && CONFIG.RUNNING) { 565 const maxOk = Math.floor((Date.now() - t0) / 1000) + CONFIG.VIDEO_MAX_FUTURE; 566 const next = cur + CONFIG.VIDEO_SPEED; 567 568 if (maxOk - cur >= CONFIG.VIDEO_SPEED) { 569 try { 570 const r = await Traffic.send(`/quests/${quest.id}/video-progress`, { 571 timestamp: Math.min(task.target, next + Math.random()), 572 }); 573 done = r.body.completed_at != null; 574 cur = Math.min(task.target, next); 575 if (done) break; 576 } catch {} 577 } 578 579 if (next >= task.target) break; 580 UI.setTask(quest.id, { name: task.name, type: "VIDEO", cur, max: task.target, status: "run" }); 581 if (Date.now() - started > CONFIG.MAX_TASK_TIME) { UI.log(`⏰ Video timeout: ${task.name}`, "err"); break; } 582 await sleep(1000); 583 } 584 585 if (!done && CONFIG.RUNNING) { 586 try { await Traffic.send(`/quests/${quest.id}/video-progress`, { timestamp: task.target }); } catch {} 587 } 588 if (CONFIG.RUNNING) this.complete(quest, task); 589 }, 590 591 // ── GAME (PLAY_ON_DESKTOP) ── 592 async doGame(quest, task, userStatus) { 593 if (!isApp) { 594 UI.log(`⚠ ${task.name} — Desktop App only`, "warn"); 595 UI.setTask(quest.id, { name: task.name, type: "GAME", cur: 0, max: task.target, status: "warn" }); 596 return; 597 } 598 return this.doDesktop(quest, task, "GAME", "PLAY_ON_DESKTOP", userStatus); 599 }, 600 601 // ── STREAM ── 602 async doStream(quest, task, userStatus) { 603 if (!isApp) { 604 UI.log(`⚠ ${task.name} — Desktop App only`, "warn"); 605 UI.setTask(quest.id, { name: task.name, type: "STREAM", cur: 0, max: task.target, status: "warn" }); 606 return; 607 } 608 return this.doDesktop(quest, task, "STREAM", "STREAM_ON_DESKTOP", userStatus); 609 }, 610 611 // ── GENERIC DESKTOP (Game/Stream) ── 612 async doDesktop(quest, task, type, key, userStatus) { 613 if (!CONFIG.RUNNING) return; 614 const app = await this.getAppInfo(task.appId, task.name); 615 const pid = rnd(10000, 50000); 616 617 const game = { 618 id: app.id, name: app.name, icon: app.icon, 619 pid, pidPath: [pid], processName: app.name, 620 start: Date.now(), exeName: app.exe, exePath: app.path, cmdLine: app.cmd, 621 executables: [{ os: "win32", name: app.exe, is_launcher: false }], 622 windowHandle: 0, fullscreenType: 0, overlay: true, sandboxed: false, 623 hidden: false, isLauncher: false, 624 }; 625 626 let cleanup; 627 if (type === "STREAM") { 628 const orig = Mods.StreamStore.getStreamerActiveStreamMetadata; 629 Mods.StreamStore.getStreamerActiveStreamMetadata = () => ({ id: app.id, pid, sourceName: app.name }); 630 cleanup = () => { Mods.StreamStore.getStreamerActiveStreamMetadata = orig; }; 631 } else { 632 Patcher.add(game); 633 cleanup = () => Patcher.remove(game); 634 } 635 636 UI.setTask(quest.id, { name: task.name, type, cur: 0, max: task.target, status: "run" }); 637 UI.log(`🎮 Spoofed: ${app.name} [PID:${pid}]`, "dim"); 638 639 return new Promise((resolve) => { 640 let staleCount = 0; 641 let lastProg = -1; 642 643 const timer = setTimeout(() => { 644 UI.log(`⏰ Timeout: ${task.name}`, "err"); 645 done(); resolve(); 646 }, CONFIG.MAX_TASK_TIME); 647 648 const onHB = (d) => { 649 if (!CONFIG.RUNNING) { clearTimeout(timer); done(); resolve(); return; } 650 if (d.questId !== quest.id) return; 651 652 let prog; 653 if (quest.config.configVersion === 1) { 654 prog = d.userStatus.streamProgressSeconds ?? 0; 655 } else { 656 prog = d.userStatus.progress?.[key]?.value ?? 0; 657 } 658 659 // ── Stale detection: ถ้า progress ไม่ขยับ 5 รอบ ให้แจ้ง ── 660 if (prog === lastProg) { 661 staleCount++; 662 if (staleCount >= 5 && prog === 0) { 663 UI.log(`⚠ ${task.name}: progress 0 — อาจต้องรอ heartbeat`, "warn"); 664 staleCount = 0; 665 } 666 } else { 667 staleCount = 0; 668 lastProg = prog; 669 } 670 671 UI.setTask(quest.id, { name: task.name, type, cur: prog, max: task.target, status: "run" }); 672 673 if (prog >= task.target) { 674 clearTimeout(timer); 675 done(); 676 this.complete(quest, task); 677 resolve(); 678 } 679 }; 680 681 const done = () => { cleanup(); Mods.Dispatcher.unsubscribe(EVT.HB, onHB); }; 682 Mods.Dispatcher.subscribe(EVT.HB, onHB); 683 }); 684 }, 685 686 // ── ACTIVITY ── 687 async doActivity(quest, task) { 688 const chan = Mods.ChanStore?.getSortedPrivateChannels()[0]?.id 689 ?? Object.values(Mods.GuildChanStore?.getAllGuilds() || {}).find(g => g?.VOCAL?.length)?.VOCAL[0]?.channel?.id; 690 if (!chan) return UI.log(`❌ No channel for ${task.name}`, "err"); 691 692 const sKey = `call:${chan}:${rnd(1000, 9999)}`; 693 let cur = 0; 694 UI.setTask(quest.id, { name: task.name, type: "ACTIVITY", cur, max: task.target, status: "run" }); 695 696 const t0 = Date.now(); 697 while (cur < task.target && CONFIG.RUNNING) { 698 try { 699 const r = await Traffic.send(`/quests/${quest.id}/heartbeat`, { stream_key: sKey, terminal: false }); 700 cur = r.body.progress?.PLAY_ACTIVITY?.value ?? cur + 20; 701 UI.setTask(quest.id, { name: task.name, type: "ACTIVITY", cur, max: task.target, status: "run" }); 702 if (cur >= task.target) { 703 await Traffic.send(`/quests/${quest.id}/heartbeat`, { stream_key: sKey, terminal: true }); 704 break; 705 } 706 } catch {} 707 if (Date.now() - t0 > CONFIG.MAX_TASK_TIME) { UI.log(`⏰ Activity timeout`, "err"); break; } 708 await sleep(20000); 709 } 710 if (CONFIG.RUNNING && cur >= task.target) this.complete(quest, task); 711 }, 712 713 complete(quest, task) { 714 UI.setTask(quest.id, { name: task.name, type: task.type, cur: task.target, max: task.target, status: "done" }); 715 UI.log(`✅ ${task.name}`, "ok"); 716 try { if (Notification.permission === "granted") new Notification(`${CONFIG.NAME}: Done!`, { body: task.name }); } catch {} 717 setTimeout(() => UI.removeTask(quest.id), CONFIG.REMOVE_DELAY); 718 }, 719 }; 720 721 // ──────────────────────────── CONCURRENCY ──────────────────────────── 722 async function runPool(tasks, limit) { 723 const running = []; 724 for (const fn of tasks) { 725 if (!CONFIG.RUNNING) break; 726 const p = fn().then(() => running.splice(running.indexOf(p), 1)); 727 running.push(p); 728 // ── เว้นช่วงระหว่างเกม เพื่อให้ Discord register heartbeat ── 729 await sleep(CONFIG.HEARTBEAT_STAGGER); 730 if (running.length >= limit) await Promise.race(running); 731 } 732 return Promise.all(running); 733 } 734 735 // ──────────────────────────── MAIN ──────────────────────────── 736 const TASK_KEYS = ["WATCH_VIDEO", "WATCH_VIDEO_ON_MOBILE", "PLAY_ON_DESKTOP", "STREAM_ON_DESKTOP", "PLAY_ACTIVITY"]; 737 738 async function main() { 739 UI.init(); 740 UI.log(`${isApp ? "⚡ Desktop App" : "🌐 Browser"} detected`, isApp ? "ok" : "warn"); 741 742 if (!loadModules()) return UI.log("Module load failed — try refreshing Discord", "err"); 743 744 let cycle = 1; 745 while (CONFIG.RUNNING) { 746 UI.log(`── Cycle ${cycle} ──`, "info"); 747 748 const getQ = () => Mods.QuestStore.quests instanceof Map ? [...Mods.QuestStore.quests.values()] : Object.values(Mods.QuestStore.quests); 749 let quests = getQ(); 750 const now = Date.now(); 751 752 // ── Auto-enroll ── 753 const toEnroll = quests.filter(q => !q.userStatus?.completedAt && new Date(q.config.expiresAt).getTime() > now && !q.userStatus?.enrolledAt); 754 if (toEnroll.length) { 755 UI.log(`📝 Enrolling ${toEnroll.length} quests...`, "warn"); 756 for (const q of toEnroll) { 757 if (!CONFIG.RUNNING) break; 758 try { await Traffic.send(`/quests/${q.id}/enroll`, { location: 1 }); UI.log(` ✓ ${q.config.messages.questName}`, "ok"); } catch {} 759 } 760 await sleep(2000); 761 quests = getQ(); 762 } 763 764 // ── Filter active ── 765 const active = quests.filter(q => 766 !q.userStatus?.completedAt && 767 q.userStatus?.enrolledAt && 768 new Date(q.config.expiresAt).getTime() > now 769 ); 770 771 if (!active.length) { UI.log("✓ No active quests — waiting 30s...", "dim"); await sleep(30000); cycle++; continue; } 772 773 const videos = [], games = []; 774 775 for (const q of active) { 776 const cfg = q.config.taskConfig ?? q.config.taskConfigV2; 777 const keys = Object.keys(cfg.tasks); 778 const taskKey = TASK_KEYS.find(k => keys.includes(k)); 779 780 if (!taskKey && q.config.application?.id) { 781 // fallback to game 782 const target = cfg.tasks[keys[0]]?.target; 783 if (target) { 784 const t = { id: q.id, appId: q.config.application.id, name: q.config.messages.questName, target, type: "GAME", taskKey: keys[0] }; 785 if (!UI.tasks.has(q.id) || !["run", "done"].includes(UI.tasks.get(q.id).status)) { 786 UI.setTask(q.id, { name: t.name, type: "GAME", cur: 0, max: target, status: "queue" }); 787 games.push(() => Quester.doGame(q, t, q.userStatus)); 788 } 789 } 790 continue; 791 } 792 if (!taskKey) { UI.log(`❓ Unknown task: ${q.config.messages.questName}`, "warn"); continue; } 793 794 const target = cfg.tasks[taskKey].target; 795 const prog = q.userStatus?.progress?.[taskKey]?.value ?? 0; 796 if (prog >= target) continue; 797 798 const type = taskKey.includes("VIDEO") ? "VIDEO" 799 : taskKey === "PLAY_ON_DESKTOP" ? "GAME" 800 : taskKey === "STREAM_ON_DESKTOP" ? "STREAM" 801 : "ACTIVITY"; 802 803 const t = { id: q.id, appId: q.config.application.id, name: q.config.messages.questName, target, type, taskKey }; 804 805 if (UI.tasks.has(q.id) && ["run", "done"].includes(UI.tasks.get(q.id).status)) continue; 806 807 UI.setTask(q.id, { name: t.name, type, cur: prog, max: target, status: "queue" }); 808 809 const runner = () => { 810 switch (type) { 811 case "VIDEO": return Quester.doVideo(q, t, q.userStatus); 812 case "GAME": return Quester.doGame(q, t, q.userStatus); 813 case "STREAM": return Quester.doStream(q, t, q.userStatus); 814 case "ACTIVITY": return Quester.doActivity(q, t); 815 } 816 }; 817 818 (type === "VIDEO" ? videos : games).push(runner); 819 } 820 821 if (videos.length + games.length > 0) { 822 UI.log(`📦 ${videos.length} video + ${games.length} game/stream`, "info"); 823 await Promise.all([ 824 runPool(games, CONFIG.GAME_CONCURRENCY), 825 runPool(videos, 3), 826 ]); 827 } else { 828 if (!active.length) break; 829 await sleep(5000); 830 } 831 832 if (!CONFIG.RUNNING) break; 833 UI.log(`Cycle ${cycle} done — rescanning...`, "ok"); 834 await sleep(3000); 835 cycle++; 836 } 837 838 UI.shutdown(); 839 } 840 841 main().catch((e) => { 842 console.error(e); 843 UI.log(e.message || "Fatal error", "err"); 844 UI.shutdown(); 845 }); 846})();
กดปุ่ม Enter เพื่อรันคำสั่ง แล้วรอดูเวทมนตร์ได้เลย!
⏳ ขั้นตอนสุดท้าย
- รออย่างใจเย็นจนกว่าแถบ Quest จะวิ่งครบสมบูรณ์
- ข้อห้ามชี้เป็นชี้ตาย: ห้ามกากบาทปิดหน้าต่าง Console หรือปิด Discord ระหว่างการรันเด็ดขาด!
Have Fun & Enjoy Your Rewards! 🎉
เมื่อเปอร์เซ็นต์วิ่งครบ 100% ก็ถึงเวลากดรับรางวัล (Claim) แล้วเอาแต้ม Discord Orbs ไปช้อปปิ้งในร้านค้าได้เลย! ไม่ว่าจะเป็นกรอบโปรไฟล์สุดเท่, เอฟเฟกต์ตกแต่ง, หรือลูกเล่นใหม่ๆ ก็สามารถแลกมาใช้ได้ฟรีๆ แบบไม่ต้องเสียเงินเติมสักบาท
หวังว่าเครื่องมือและทริคเล็กๆ น้อยๆ นี้ จะช่วยให้ทุกคนสนุกกับการแต่งโปรไฟล์ Discord กันมากขึ้นนะครับ ขอให้มีความสุขกับของรางวัลเด็ดๆ ครับ! ✨

แชร์บทความนี้
ช่วยแชร์ความรู้ให้เพื่อนๆ ได้อ่านกัน


