Skip to content

Instantly share code, notes, and snippets.

@Kuju29
Last active July 20, 2025 00:58
Show Gist options
  • Save Kuju29/a34cf4e362e7342991b763433b8529ca to your computer and use it in GitHub Desktop.
Save Kuju29/a34cf4e362e7342991b763433b8529ca to your computer and use it in GitHub Desktop.
เล่นวีดีโอถัดไปเฉพาะคลิปสั้นเท่านั้น
// ==UserScript==
// @name YouTube Auto Play
// @version 25.07.20.1
// @description Automatically pick and play short, high‑view videos (with optional language matching) when a video ends, falling back to endscreen if sidebar fails.
// @match *://www.youtube.com/*
// @updateURL https://gist.githubusercontent.com/Kuju29/a34cf4e362e7342991b763433b8529ca/raw/YouTube%2520Auto%2520Play%2520Short%2520Videos.user.js
// @downloadURL https://gist.githubusercontent.com/Kuju29/a34cf4e362e7342991b763433b8529ca/raw/YouTube%2520Auto%2520Play%2520Short%2520Videos.user.js
// @grant GM_registerMenuCommand
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
/* =========================
* CONFIG / CONSTANTS
* ========================= */
const settingsKey = "youtubeAutoPlaySettings";
const defaultSettings = {
maxDuration: 600,
minViews: 1_000_000,
maxAgeYears: 99,
neverwatched: true,
detectLanguage: true,
removeWhenReload: false,
removewhanreload: false,
debugDrops: false
};
const UNWATCHED_WEIGHT = 10;
const STORAGE_KEYS = { playedIds: "playedVideoIds" };
const SELECTORS = {
settingsPanel: '#settings-panel',
videoTitle: 'h1.title.ytd-watch-metadata, h1.title.style-scope.ytd-video-primary-info-renderer',
sidebarItems: [
'ytd-compact-video-renderer',
'yt-lockup-view-model.ytd-item-section-renderer.lockup'
].join(','),
old: {
title: '#video-title',
channel: '#channel-name #text',
metaItems: '#metadata-line span.inline-metadata-item',
duration: 'ytd-thumbnail-overlay-time-status-renderer .badge-shape-wiz__text',
thumbLink: 'a#thumbnail',
resumePlayback: '.style-scope ytd-thumbnail-overlay-resume-playback-renderer'
},
modern: {
titleLink: 'a.yt-lockup-metadata-view-model-wiz__title',
contentImageLink: 'a.yt-lockup-view-model-wiz__content-image',
titleSpan: '.yt-lockup-metadata-view-model-wiz__title span.yt-core-attributed-string',
metadataContainer: '.yt-lockup-metadata-view-model-wiz__metadata',
metadataRow: '.yt-content-metadata-view-model-wiz__metadata-row',
badgeDuration: '.yt-thumbnail-badge-view-model-wiz .badge-shape-wiz__text',
watchedBar: '.ytThumbnailOverlayProgressBarHostWatchedProgressBarHostWatchedProgressBarSegment,' +
'.ytThumbnailOverlayProgressBarHostWatchedProgressBarSegment'
},
autoplayToggle: '.ytp-autonav-toggle-button-container .ytp-autonav-toggle-button',
nextButton: '.ytp-next-button[aria-disabled="false"]',
endscreenItem: '.html5-endscreen .ytp-videowall-still',
endscreenTitle: '.ytp-videowall-still-info-title',
endscreenAuthor: '.ytp-videowall-still-info-author',
endscreenDuration: '.ytp-videowall-still-info-duration'
};
const EVENTS_TO_BLOCK = [
"visibilitychange","webkitvisibilitychange","blur","hasFocus",
"mouseleave","mouseout","mozvisibilitychange","msvisibilitychange"
];
const BLUR_WHITELIST = [HTMLInputElement, HTMLAnchorElement, HTMLSpanElement, HTMLParagraphElement];
const HOVER_BLACKLIST = [HTMLIFrameElement, HTMLHtmlElement, HTMLBodyElement, HTMLHeadElement, HTMLFrameSetElement, HTMLFrameElement];
const REGEX = {
videoId: /[?&]v=([^&]+)/,
viewsSuffix: /(views?|)/i,
durationSplit: /:/,
ageYear: /(\d+)\s*(|year|years)/i,
ageMonth: /(\d+)\s*(|month|months)/i,
ageWeek: /(\d+)\s*(|week|weeks)/i,
ageDay: /(\d+)\s*(|day|days)/i,
ageHour: /(\d+)\s*(|hour|hours)/i,
ageMinute: /(\d+)\s*(|minute|minutes)/i
};
const LANG_PATTERNS = {
thai: /[\u0E00-\u0E7F]/,
lao: /[\u0E80-\u0EFF]/,
korean: /[\uAC00-\uD7AF]/,
japanese: /[\u3040-\u30FF]/,
cjk: /[\u4E00-\u9FFF]/
};
/* =========================
* SETTINGS UI
* ========================= */
function loadSettings() {
const saved = localStorage.getItem(settingsKey);
if (!saved) return;
try {
const parsed = JSON.parse(saved);
Object.assign(defaultSettings, parsed);
if (parsed.removewhanreload && !parsed.removeWhenReload) {
defaultSettings.removeWhenReload = true;
}
} catch(e){ console.error("Failed to parse settings:", e); }
}
function saveSettingsFromUI() {
defaultSettings.maxDuration = parseInt(document.getElementById('setting-maxDuration').value, 10);
defaultSettings.minViews = parseInt(document.getElementById('setting-minViews').value, 10);
defaultSettings.maxAgeYears = parseInt(document.getElementById('setting-maxAgeYears').value, 10);
defaultSettings.neverwatched = document.getElementById('setting-neverwatched').checked;
defaultSettings.detectLanguage= document.getElementById('setting-detectLanguage').checked;
defaultSettings.removeWhenReload = document.getElementById('setting-removeWhenReload').checked;
defaultSettings.debugDrops = document.getElementById('setting-debugDrops').checked;
localStorage.setItem(settingsKey, JSON.stringify(defaultSettings));
console.log("Settings saved:", defaultSettings);
}
function createSettingsUI() {
if (document.querySelector(SELECTORS.settingsPanel)) return;
const container = document.createElement('div');
container.id='settings-panel';
Object.assign(container.style,{
position:'fixed',top:'0',right:'0',width:'300px',
background:'linear-gradient(135deg, rgb(24 24 25) 0%, rgb(84 27 141) 100%)',
color:'#fff',borderRadius:'8px 0 0 8px',boxShadow:'0 2px 10px rgba(0,0,0,0.3)',
padding:'20px',fontFamily:'sans-serif',fontSize:'14px',opacity:'0.95',zIndex:9999
});
container.innerHTML = `
<div style="text-align:right;"><button id="close-settings">X</button></div>
<h3 style="margin:0 0 10px 0;">Auto Short Play Settings</h3>
<div style="margin-bottom:5px;">
<label>Max Duration (sec)</label>
<input type="number" id="setting-maxDuration" value="${defaultSettings.maxDuration}" style="width:100%;" />
</div>
<div style="margin-bottom:5px;">
<label>Min Views</label>
<input type="number" id="setting-minViews" value="${defaultSettings.minViews}" style="width:100%;" />
</div>
<div style="margin-bottom:5px;">
<label>Max Age (years)</label>
<input type="number" id="setting-maxAgeYears" value="${defaultSettings.maxAgeYears}" style="width:100%;" />
</div>
<div style="margin-bottom:5px;">
<label><input type="checkbox" id="setting-neverwatched" ${defaultSettings.neverwatched?'checked':''}/> Prioritize Unwatched</label>
</div>
<div style="margin-bottom:5px;">
<label><input type="checkbox" id="setting-detectLanguage" ${defaultSettings.detectLanguage?'checked':''}/> Language Match</label>
</div>
<div style="margin-bottom:5px;">
<label><input type="checkbox" id="setting-removeWhenReload" ${defaultSettings.removeWhenReload?'checked':''}/> Reset Played on Reload</label>
</div>
<div style="margin-bottom:5px;">
<label><input type="checkbox" id="setting-debugDrops" ${defaultSettings.debugDrops?'checked':''}/> Debug Dropped Items</label>
</div>
<div style="text-align:right;margin-top:10px;">
<button id="save-settings">Save</button>
</div>`;
document.body.appendChild(container);
container.querySelector('#close-settings').onclick=()=>{ container.style.display='none'; };
container.querySelector('#save-settings').onclick=()=>{ saveSettingsFromUI(); container.style.display='none'; };
}
function showSettingsUI(){ loadSettings(); if(!document.querySelector(SELECTORS.settingsPanel)) createSettingsUI(); else document.querySelector(SELECTORS.settingsPanel).style.display='block'; }
if (typeof GM_registerMenuCommand!=='undefined') GM_registerMenuCommand("Short Play Settings", showSettingsUI);
/* =========================
* UTILITIES
* ========================= */
const safeDefine=(o,k,d)=>{try{Object.defineProperty(o,k,d);}catch(e){}};
function getVideoIdFromUrl(url){ const m=url.match(REGEX.videoId); return m?m[1]:null; }
function parseDuration(str){
if(!str) return 0;
return str.trim().split(':').reverse().map(Number)
.reduce((acc,v,i)=>acc+ (Number.isFinite(v)?v:0)*60**i,0);
}
function extractDurationFallbackFromAria(linkEl){
if(!linkEl) return 0;
const aria = linkEl.getAttribute('aria-label') || '';
// Thai pattern: "4 นาที และ 13 วินาที"
let m = aria.match(/(\d+)\s*(?:\s*\s*(\d+)\s*)?/);
if(m){
const min = parseInt(m[1],10);
const sec = m[2]?parseInt(m[2],10):0;
return min*60+sec;
}
// Generic mm:ss
m = aria.match(/(\d+):(\d{2})/);
if(m) return parseInt(m[1],10)*60+parseInt(m[2],10);
return 0;
}
function shuffleArray(a){for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]];}return a;}
function pickRandom(a){ if(!a.length) return null; return a[Math.floor(Math.random()*a.length)]; }
function detectLanguage(text){
if(!defaultSettings.detectLanguage) return 'unknown';
const t=String(text||'');
if(LANG_PATTERNS.thai.test(t)) return 'thai';
if(LANG_PATTERNS.lao.test(t)) return 'lao';
if(LANG_PATTERNS.korean.test(t)) return 'korean';
if(LANG_PATTERNS.japanese.test(t)) return 'japanese';
if(LANG_PATTERNS.cjk.test(t)) return 'chinese';
return 'unknown';
}
function getCurrentVideoLanguage(){
const el=document.querySelector(SELECTORS.videoTitle);
return el?detectLanguage(el.textContent.trim()):'unknown';
}
/* =========================
* PARSERS
* ========================= */
function parseViews(text){
if(!text) return 0;
let t=text.replace(/,/g,'').trim();
t=t.replace(//g,'').replace(//g,'').replace(/views?/ig,'').trim();
let factor=1;
if (//.test(t)) { factor=100000; t=t.replace(//,'').trim(); }
else if (//.test(t)) { factor=10000; t=t.replace(//,'').trim(); }
else if (//.test(t)) { factor=1e9; t=t.replace(//,'').trim(); }
else if (//.test(t)) { factor=1e6; t=t.replace(//,'').trim(); }
else if (//.test(t)) { factor=1e3; t=t.replace(//,'').trim(); }
if (/b$/i.test(t)) { factor=1e9; t=t.replace(/b$/i,''); }
else if (/m$/i.test(t)) { factor=1e6; t=t.replace(/m$/i,''); }
else if (/k$/i.test(t)) { factor=1e3; t=t.replace(/k$/i,''); }
const num=parseFloat(t.replace(/[^\d\.]/g,''));
if(!isFinite(num)) return 0;
return Math.round(num*factor);
}
function parseUploadAge(text){
if(!text) return 0;
const t=text.toLowerCase().trim();
if (/(||\b)/.test(t)) return 0;
const mYear=t.match(REGEX.ageYear); if(mYear) return +mYear[1];
const mMonth=t.match(REGEX.ageMonth); if(mMonth) return Math.floor(+mMonth[1]/12);
const mWeek=t.match(REGEX.ageWeek); if(mWeek) return Math.floor(+mWeek[1]/52);
const mDay=t.match(REGEX.ageDay); if(mDay) return Math.floor(+mDay[1]/365);
const mHour=t.match(REGEX.ageHour); if(mHour) return 0;
const mMin=t.match(REGEX.ageMinute); if(mMin) return 0;
return 0;
}
function isModernItem(el){
return el.tagName.toLowerCase()==='yt-lockup-view-model';
}
function getVideoInfo(item){
if(!item) return null;
if (isModernItem(item)) {
const titleLink = item.querySelector(SELECTORS.modern.titleLink) ||
item.querySelector(SELECTORS.modern.contentImageLink);
const titleSpan = item.querySelector(SELECTORS.modern.titleSpan);
const title = titleSpan?.textContent.trim() ||
titleLink?.getAttribute('title') ||
titleLink?.ariaLabel || '';
const rows = Array.from(item.querySelectorAll(SELECTORS.modern.metadataRow));
let views = 0, age = 0;
for (const r of rows) {
const txt = r.textContent.trim();
if (/|views?/i.test(txt)) {
const parts = txt.split(//).map(s=>s.trim());
if (parts.length>=1) views = parseViews(parts[0]);
if (parts.length>=2) age = parseUploadAge(parts[1]);
} else if (!views && /|||||views?/i.test(txt)) {
views = parseViews(txt);
} else if (!age && /(|||||||month|year|week|day|hour|min)/i.test(txt)) {
age = parseUploadAge(txt);
}
}
let durationText = item.querySelector(SELECTORS.modern.badgeDuration)?.textContent.trim() || '';
let duration = parseDuration(durationText);
if(!duration) {
duration = extractDurationFallbackFromAria(titleLink);
}
const progress = !!item.querySelector(SELECTORS.modern.watchedBar);
const href = titleLink?.getAttribute('href') || '';
return { title, views, age, duration, progress, href };
} else {
const titleEl = item.querySelector(SELECTORS.old.title);
const title = titleEl?.getAttribute('title')?.trim() || titleEl?.textContent?.trim() || '';
const meta = item.querySelectorAll(SELECTORS.old.metaItems);
const views = parseViews(meta[0]?.textContent||'');
const age = parseUploadAge(meta[1]?.textContent||'');
const durationText = item.querySelector(SELECTORS.old.duration)?.textContent?.trim() || '';
const duration = parseDuration(durationText);
const progress = !!item.querySelector(SELECTORS.old.resumePlayback);
const href = item.querySelector(SELECTORS.old.thumbLink)?.getAttribute('href') || '';
return { title, views, age, duration, progress, href };
}
}
/* =========================
* ENDSCREEN FALLBACK
* ========================= */
function parseViewsSimple(v){ return parseViews(v); }
function getEndscreenData(node){
const url=node.getAttribute('href')||'';
const title=node.querySelector(SELECTORS.endscreenTitle)?.textContent.trim()||'';
const author=node.querySelector(SELECTORS.endscreenAuthor)?.textContent.trim()||'';
const durText=node.querySelector(SELECTORS.endscreenDuration)?.textContent.trim()||'0:00';
const [channel, viewsStr]=author.split('•').map(s=>s.trim());
return { url, title, channel:channel||'', views:parseViewsSimple(viewsStr||''), duration:parseDuration(durText) };
}
function fallbackToNextButton(){
const btn=document.querySelector(SELECTORS.nextButton);
if(!btn){ console.log("[AutoShort] No next button"); return; }
btn.click();
}
function pickVideoFromEndscreen(){
const items=document.querySelectorAll(SELECTORS.endscreenItem);
if(!items.length){ fallbackToNextButton(); return; }
const candidates=[];
items.forEach(v=>{
if(!v.closest('.html5-endscreen')) return;
const d=getEndscreenData(v);
if(d.duration<defaultSettings.maxDuration && d.views>=defaultSettings.minViews){
candidates.push({duration:d.duration,views:d.views,age:0,lang:detectLanguage(d.title),title:d.title,element:v});
}
});
if(!candidates.length){ fallbackToNextButton(); return; }
pickRandom(candidates)?.element?.click();
}
/* =========================
* AUTOPLAY CORE
* ========================= */
let playedVideoIds=[];
try{
const stored=sessionStorage.getItem(STORAGE_KEYS.playedIds);
if(stored) playedVideoIds=JSON.parse(stored);
}catch(e){}
function savePlayedVideoIds(){
sessionStorage.setItem(STORAGE_KEYS.playedIds, JSON.stringify(playedVideoIds));
}
function matchesLanguage(videoLang, currentLang) {
if (!defaultSettings.detectLanguage) return true;
if (currentLang === 'unknown') return true;
return videoLang === currentLang;
}
function mainAutoPlay(){
console.log("== [AutoShort mainAutoPlay] ==");
const autoplayBtn=document.querySelector(SELECTORS.autoplayToggle);
if(!autoplayBtn) return;
if(autoplayBtn.getAttribute('aria-checked')!=='false'){ return; }
if(location.href.includes('list=')) return; // Skip playlists
const sidebarItems=Array.from(document.querySelectorAll(SELECTORS.sidebarItems));
if(!sidebarItems.length){
console.log("[AutoShort] No sidebar items -> using endscreen");
pickVideoFromEndscreen(); return;
}
const videoData = sidebarItems.map(item=>{
const info=getVideoInfo(item);
if(!info || !info.title) return null;
const videoId=getVideoIdFromUrl(info.href||'');
const lang=detectLanguage(info.title);
return {
duration:info.duration,
views:info.views,
age:info.age,
lang,
title:info.title,
videoId,
element:(isModernItem(item)
? (item.querySelector(SELECTORS.modern.titleLink) || item.querySelector(SELECTORS.modern.contentImageLink))
: item.querySelector(SELECTORS.old.thumbLink)),
progress:info.progress
};
}).filter(Boolean);
console.log("[AutoShort] Raw items:", videoData);
const currentLang=getCurrentVideoLanguage();
if (defaultSettings.debugDrops) {
videoData.forEach(v=>{
const reasons=[];
if (v.duration >= defaultSettings.maxDuration) reasons.push('duration');
if (v.views < defaultSettings.minViews) reasons.push('views');
if (!matchesLanguage(v.lang,currentLang)) reasons.push('lang');
if (!v.videoId) reasons.push('noId');
if (playedVideoIds.includes(v.videoId)) reasons.push('played');
if (v.age > defaultSettings.maxAgeYears) reasons.push('age');
if (reasons.length) console.debug('[DROP]', v.title, reasons.join('|'), v);
});
}
const candidates=videoData.filter(v =>
v.duration < defaultSettings.maxDuration &&
v.views >= defaultSettings.minViews &&
matchesLanguage(v.lang, currentLang) &&
v.videoId &&
!playedVideoIds.includes(v.videoId) &&
v.age <= defaultSettings.maxAgeYears
);
if(!candidates.length){
console.log("[AutoShort] No candidates => endscreen fallback");
pickVideoFromEndscreen(); return;
}
const weighted = defaultSettings.neverwatched
? candidates.flatMap(v => Array(v.progress ? 1 : UNWATCHED_WEIGHT).fill(v))
: candidates;
const picked=pickRandom(weighted);
if(!picked){ pickVideoFromEndscreen(); return; }
console.log("[AutoShort] Filtered:", candidates);
console.log("[AutoShort] Playing:", picked.element?.href || picked.element?.getAttribute('href'));
playedVideoIds.push(picked.videoId);
savePlayedVideoIds();
picked.element?.click();
}
/* =========================
* VISIBILITY BLOCK
* ========================= */
(function overrideVisibility(){
if (typeof unsafeWindow!=='undefined'){
unsafeWindow.onblur=null; unsafeWindow.blurred=false;
unsafeWindow.document.hasFocus=()=>true;
unsafeWindow.window.onFocus=()=>true;
} else {
window.onblur=null;
document.hasFocus=()=>true;
window.onFocus=()=>true;
}
['hidden','mozHidden','msHidden','webkitHidden'].forEach(k=>safeDefine(document,k,{get:()=>false}));
safeDefine(document,'visibilityState',{get:()=> 'visible'});
safeDefine(document,'webkitVisibilityState',{get:()=> 'visible'});
if (typeof unsafeWindow!=='undefined') unsafeWindow.document.onvisibilitychange=undefined;
else document.onvisibilitychange=undefined;
const handler=e=>{
if(e.type==='blur' && (BLUR_WHITELIST.some(t=>e.target instanceof t) ||
(e.target.classList && e.target.classList.contains('ql-editor')))) return;
if(['mouseleave','mouseout'].includes(e.type) && !HOVER_BLACKLIST.some(t=>e.target instanceof t)) return;
e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
};
EVENTS_TO_BLOCK.forEach(ev=>{
window.addEventListener(ev,handler,true);
document.addEventListener(ev,handler,true);
});
})();
/* =========================
* INIT & OBSERVER
* ========================= */
let lastUrl=location.href;
function shouldRunScript(){
return location.hostname==='www.youtube.com' &&
location.href.includes('watch?') &&
!location.href.includes('&list=');
}
function init(){
if(!shouldRunScript()) return;
loadSettings();
const currentId=getVideoIdFromUrl(location.href);
if(currentId && !playedVideoIds.includes(currentId)){
playedVideoIds.push(currentId); savePlayedVideoIds();
}
const video=document.querySelector('video');
if(video && !video.dataset.autoPlayEventAdded){
video.dataset.autoPlayEventAdded='true';
video.addEventListener('ended', ()=> mainAutoPlay());
}
}
const observer=new MutationObserver(()=>{
if(location.href!==lastUrl){
lastUrl=location.href;
setTimeout(()=>{ if(shouldRunScript()) init(); },1500);
}
});
function startObserver(){
if(document.body) observer.observe(document.body,{childList:true,subtree:true});
else document.addEventListener('DOMContentLoaded',()=>observer.observe(document.body,{childList:true,subtree:true}));
}
function delayedInit(){ setTimeout(init,500); }
if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',delayedInit);
else delayedInit();
startObserver();
if(defaultSettings.removeWhenReload || defaultSettings.removewhanreload){
window.addEventListener('beforeunload',()=>sessionStorage.removeItem(STORAGE_KEYS.playedIds));
}
})();
@Kuju29
Copy link
Author

Kuju29 commented May 8, 2025

image
image

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