Skip to content

Instantly share code, notes, and snippets.

@zaheer-exe
Last active January 25, 2021 06:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zaheer-exe/ce21c4427623a5a7e3c5380bbf1ae2d6 to your computer and use it in GitHub Desktop.
Save zaheer-exe/ce21c4427623a5a7e3c5380bbf1ae2d6 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name burningseries-autoplay
// @namespace https://github.com/zaheer-exe/burningseries-autoplay
// @version 3.9
// @description Autoplay für Burningseries
// @author zaheer-exe
// @match https://bs.to/*
// @match https://*.vivo.sx/*
// @match https://burningseries.co/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_openInTab
// @grant window.close
// @grant GM_xmlhttpRequest
// @require http://code.jquery.com/jquery-latest.js
// @require https://greasyfork.org/scripts/401626-notify-library/code/Notify%20Library.js?version=817875
// @license Apache License
// ==/UserScript==
function createElementFromHTML(htmlString) {
var div = document.createElement('div');
div.innerHTML = htmlString.trim();
return div.firstChild;
}
function waitForElem(selector) {
return new Promise(resolve => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector));
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
class SiteHandler {
constructor() {
this.dataHandler = new DataHandler();
this.url = new URL(document.location.href);
this.settings = this.dataHandler.getSettings();
}
}
class VivoHandler extends SiteHandler {
constructor() {
super();
this.data = this.dataHandler.getEpisodeData(this.settings.lastBsUrl);
if(this.settings.vivoEnabled && this.isVivo()) {
this.play();
}
if(this.settings.vivoEnabled && this.isVivoVideo()){
this.resize();
this.trackWatchedState();
if(this.settings.autonextEnabled) {
this.onEndPlayNext();
}
}
if(this.settings.vivoButtonsEnabled && this.isVivoVideo()){
this.loadButtons();
}
if(this.settings.autoresumeEnabled) {
this.resume();
}
if(this.isVivoVideo()) {
if(this.data.isFiller) {
new Notify({
text: 'Jetzt kommt eine Filler Folge!',
type: 'warn',
timeout: 5000
}).show();
}
}
}
isVivo() {
const vivoDomains = ['vivo.sx'];
const currentUrl = new URL(document.location.href);
return vivoDomains.includes(currentUrl.host);
}
isVivoVideo() {
if(document.querySelector('video') && window.location.href.includes('vivo') && window.location.href.includes('--')) {
return true;
}
return false;
}
loadButtons() {
const button = `
<div>
<svg style="width:70px;height:70px" viewBox="0 0 24 24">
<path fill="currentColor" d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z" />
</svg>
</div>`
if(this.data.prev) {
let prevButton = createElementFromHTML(button)
prevButton.style.border = "10px solid rgba(0, 0, 0, 0.3)";
prevButton.style.borderRadius = "30px";
prevButton.style.position = 'absolute';
prevButton.style.backgroundColor = 'lightgray';
prevButton.style.left = '10px';
prevButton.style.top = '50%';
prevButton.style.transform = "rotate(180deg)";
prevButton.style.visibility = 'hidden';
prevButton.addEventListener('click', ()=>{
window.location.href = this.data.prev;
});
prevButton.classList.add('autoplay-button');
document.body.append(prevButton);
}
if(this.data.next) {
let nextButton = createElementFromHTML(button);
nextButton.style.border = "10px solid rgba(0, 0, 0, 0.3)";
nextButton.style.borderRadius = "30px";
nextButton.style.position = 'absolute';
nextButton.style.backgroundColor = 'lightgray';
nextButton.style.top = '50%';
nextButton.style.marginRight = "10px";
nextButton.style.visibility = 'hidden';
document.body.append(nextButton);
nextButton.style.left = 'calc(100% - ' + (nextButton.offsetWidth + 10) + 'px)';
nextButton.addEventListener('click', ()=>{
window.location.href = this.data.next;
});
nextButton.classList.add('autoplay-button');
}
let timer = null;
document.body.addEventListener('mousemove', () => {
if(timer) {
clearTimeout(timer);
}
document.querySelectorAll('.autoplay-button').forEach(button => {
button.style.visibility = 'visible';
});
timer = setTimeout(() => {
document.querySelectorAll('.autoplay-button').forEach(button => {
button.style.visibility = 'hidden';
});
}, 1000);
});
}
trackWatchedState() {
let videoElem = document.querySelector('video');
if (videoElem) {
videoElem.addEventListener('progress', (event) => {
let current = videoElem.currentTime;
let duration = videoElem.duration;
if (current && duration) {
this.data.currentTime = current;
this.data.maxTime = duration;
this.dataHandler.setEpisodeData(this.settings.lastBsUrl, this.data);
}
});
}
}
onEndPlayNext() {
let videoElem = document.querySelector("video");
videoElem.onended = () => {
let current = videoElem.currentTime;
let duration = videoElem.duration;
if (current && duration) {
this.data.currentTime = current;
this.data.maxTime = duration;
this.dataHandler.setEpisodeData(this.settings.lastBsUrl, this.data);
}
let nextEpisode = this.data.next;
window.location.href = nextEpisode;
}
}
resize() {
let video = document.querySelector("video");
video.style.width = "100%";
video.style.height = "100%";
document.body.style.margin = "0px";
}
play() {
// code by https://greasyfork.org/de/scripts/28779-zu-vivo-video-navigieren
// Thank you!
var source = document.getElementsByTagName('body')[0].innerHTML;
if (source != null) {
source = source.replace(/(?:.|\n)+Core\.InitializeStream\s*\(\s*\{[^)}]*source\s*:\s*'(.*?)'(?:.|\n)+/, "$1");
var toNormalize = decodeURIComponent(source);
var url = ""
for (var i = 0; i < toNormalize.length; i++) {
var c = toNormalize.charAt(i);
if (c != ' ') {
var t = (function (c) { return c.charCodeAt == null ? c : c.charCodeAt(0); })(c) + '/'.charCodeAt(0);
if (126 < t) {
t -= 94;
}
url += String.fromCharCode(t);
}
}
if (!url.toLowerCase().startsWith("http")) {
alert("Vivo-Script Defect!");
return;
}
}
window.location.href = url;
}
resume() {
let videoElem = document.querySelector('video');
if ((this.data.currentTime !== this.data.maxTime) && videoElem) {
videoElem.currentTime = this.data.currentTime;
}
}
}
class BsHandler extends SiteHandler {
constructor() {
super();
if(this.isBs()) {
this.settings = this.dataHandler.getSettings();
this.loadMenu();
if(this.dataHandler.getSettings().bsEnabled) {
this.main();
}
}
}
isBs() {
const bsDomains = ['bs.to', 'burningseries.co'];
const currentUrl = new URL(document.location.href);
return bsDomains.includes(currentUrl.host);
}
loadMenu() {
let css = document.createElement('style');
let lastText = this.settings.lastVivoUrl ? `<h3><span>Weiterschauen: </span><a target="_blank" href="`+ this.settings.lastVivoUrl + `">` + this.settings.lastName + `</a></h3>` : '';
css.innerHTML =
`
.bs-autoplay-menu {
color: white;
position: absolute;
top: 0;
z-index: 999;
background: rgba(0,0,0,0.8);
margin: 10px;
padding: 5px;
transition: 0.2s;
overflow: hidden;
white-space: nowrap;
}
.bs-autoplay-menu svg {
transition: 0.2s;
fill: gray;
}
.rotate {
transform: rotate(90deg);
}
.title {
display: inline-flex;
transition: 0.2s;
}
.title h1 {
transition: 0.2s;
width: 100%;
}
.title > * {
padding: 5px;
display: inline;
}
.bs-autoplay-menu .cb {
display: inline-block;
}
.bs-autoplay-menu a {
color: red;
}
`
let html = document.createElement('div');
html.innerHTML =
`
<div class="bs-autoplay-menu">
<div class="title">
<svg viewBox="0 0 100 80" width="40" height="40">
<rect width="100" height="20"></rect>
<rect y="30" width="100" height="20"></rect>
<rect y="60" width="100" height="20"></rect>
</svg>
<h1>Autoplay</h1>
</div>
`+lastText+`
<div>
<div>
<span>autoplay bs: </span>
<input type="checkbox" class="cb toggle-button-bs">
</div>
<div>
<span>autoplay vivo: </span>
<input type="checkbox" class="cb toggle-button-vivo">
</div>
<div>
<span>automatisch nächste Folge abspielen: </span>
<input type="checkbox" class="cb toggle-button-autonext">
</div>
<div>
<span>fortsetzen wo du aufgehört hast: </span>
<input type="checkbox" class="cb toggle-button-autoresume">
</div>
<div>
<span>Buttons im Video anzeigen: </span>
<input type="checkbox" class="cb toggle-button-vivobuttons">
</div>
</div>
</div>
`
//<div>
// <span>Anime Filler Folgen automatisch überspringen: </span>
// <input type="checkbox" class="cb toggle-button-filler">
// </div>
html.querySelector('.toggle-button-bs').checked = this.settings.bsEnabled;
html.querySelector('.toggle-button-bs').addEventListener('click', () => {
this.settings.bsEnabled = !this.settings.bsEnabled;
this.dataHandler.setSettings(this.settings);
location.reload();
});
html.querySelector('.toggle-button-vivo').checked = this.settings.vivoEnabled;
html.querySelector('.toggle-button-vivo').addEventListener('click', () => {
this.settings.vivoEnabled = !this.settings.vivoEnabled;
this.dataHandler.setSettings(this.settings);
location.reload();
});
html.querySelector('.toggle-button-autonext').checked = this.settings.autonextEnabled;
html.querySelector('.toggle-button-autonext').addEventListener('click', () => {
this.settings.autonextEnabled = !this.settings.autonextEnabled;
this.dataHandler.setSettings(this.settings);
location.reload();
});
html.querySelector('.toggle-button-autoresume').checked = this.settings.autoresumeEnabled;
html.querySelector('.toggle-button-autoresume').addEventListener('click', () => {
this.settings.autoresumeEnabled = !this.settings.autoresumeEnabled;
this.dataHandler.setSettings(this.settings);
location.reload();
});
html.querySelector('.toggle-button-vivobuttons').checked = this.settings.vivoButtonsEnabled;
html.querySelector('.toggle-button-vivobuttons').addEventListener('click', () => {
this.settings.vivoButtonsEnabled = !this.settings.vivoButtonsEnabled;
this.dataHandler.setSettings(this.settings);
location.reload();
});
//html.querySelector('.toggle-button-filler').checked = this.settings.fillerEnabled;
//html.querySelector('.toggle-button-filler').addEventListener('click', () => {
// this.settings.fillerEnabled = !this.settings.fillerEnabled;
// this.dataHandler.setSettings(this.settings);
// location.reload();
//});
document.body.append(css);
document.body.append(html);
let title = html.querySelector('.bs-autoplay-menu .title')
let height = title.parentElement.offsetHeight;
let width = title.parentElement.offsetWidth;
let heightTitle = title.offsetHeight;
let widthTitle = title.offsetWidth;
let menu = html.querySelector('.bs-autoplay-menu');
if(window.location.hash != '#open') {
menu.style.width = widthTitle + 'px';
menu.style.height = heightTitle + 'px';
} else {
menu.querySelector('svg').classList.toggle('rotate');
menu.style.width = width + 'px';
menu.style.height = height + 'px';
title.style.width = width + 'px';
}
title.addEventListener('click', () => {
menu.querySelector('svg').classList.toggle('rotate');
if(window.location.hash != '#open') {
window.location.hash = 'open';
menu.style.width = width + 'px';
title.style.width = width + 'px';
menu.style.height = height + 'px';
} else {
window.location.hash = 'closed';
menu.style.width = widthTitle + 'px';
title.style.width = widthTitle + 'px';
menu.style.height = heightTitle + 'px';
}
});
}
loadAutoplayButtons() {
let episodeRows = document.querySelectorAll('.episodes tr');
episodeRows.forEach((episode) => {
let target = episode.querySelector('[title=\'vivo\']');
if (!target) {
return;
}
target = target.parentElement;
let buttonElem = document.createElement('button');
buttonElem.innerHTML = 'Auto Play';
buttonElem.addEventListener('click', () => {
let url = episode.querySelector('a').href;
window.open(url + "#autoplay");
})
target.prepend(buttonElem);
})
}
async setPrevAndNextEpisode() {
await waitForElem("#episodes .active");
let next = document.querySelector('#episodes .active').nextElementSibling;
let prev = document.querySelector('#episodes .active').previousElementSibling;
let data = this.dataHandler.getEpisodeData(this.url.pathname) || {};
if(next) {
next = next.querySelector('a').href;
}
if(prev) {
prev = prev.querySelector('a').href;
}
data.prev = prev || false;
data.next = next || false;
this.dataHandler.setEpisodeData(this.url.pathname, data);
}
play() {
// setTimeout needed because loading time of js libs
setTimeout(() => {
let playerElem = document.querySelector('section.serie .hoster-player');
if(playerElem) {
let clickEvent = new Event('click');
clickEvent.which = 1;
clickEvent.pageX = 1;
clickEvent.pageY = 1;
playerElem.dispatchEvent(clickEvent);
waitForElem('.hoster-player a').then((elem) => {
let data = this.dataHandler.getEpisodeData(this.url.pathname) || {};
let url = elem.href;
data.vivourl = url;
this.settings.lastVivoUrl = url;
this.settings.lastBsUrl = this.url.pathname;
this.settings.lastName = document.querySelector('section.serie h2').innerText;
this.dataHandler.setSettings(this.settings);
this.dataHandler.setEpisodeData(this.url.pathname, data);
window.close();
})
}
}, 1000);
}
addProgessBars() {
let elements = document.querySelectorAll('.episodes tr');
elements.forEach((episodeRowElem) => {
let url = new URL(episodeRowElem.querySelector('a').href);
let path = url.pathname;
let data = this.dataHandler.getEpisodeData(path);
let percentage = data.currentTime * 100 / data.maxTime;
if (percentage) {
let episodeProgressbarElem = document.createElement("meter");
episodeProgressbarElem.value = percentage;
episodeProgressbarElem.max = 100;
episodeProgressbarElem.style.width = "100%";
episodeRowElem.appendChild(episodeProgressbarElem);
}
});
}
main() {
this.loadAutoplayButtons();
this.addProgessBars();
this.setPrevAndNextEpisode();
this.play();
}
}
class DataHandler {
constructor() {
}
setSettings(data) {
try {
let d = JSON.stringify(data);
GM_setValue('settings', d);
} catch(e) {
return false;
}
return true;
}
getSettings() {
let data = GM_getValue('settings') || '{}';
try {
data = JSON.parse(data);
} catch(e) {
return false;
}
return data;
}
setEpisodeData(url, data) {
try {
let d = JSON.stringify(data);
GM_setValue(url, d);
} catch(e) {
return false;
}
return true;
}
getEpisodeData(url) {
let data = GM_getValue(url) || {};
try {
data = JSON.parse(data);
} catch(e) {
return false;
}
return data;
}
}
class AnimeFillerHandler extends SiteHandler {
constructor() {
super();
this.manualMapping = {
'Steins-Gate': 'steinsgate',
'Dragonball': 'dragon-ball',
'Dragonball-GT': 'Dragon-Ball-GT',
'Dragonball-Super': 'Dragon-Ball-Super',
'Dragonball-Z': 'Dragon-Ball-Z',
'Dragonball-Z-Kai': 'Dragon-Ball-Z-Kai',
'Hunter-x-Hunter': 'hunter-x-hunter-1999',
'Hunter-x-Hunter-2011': 'hunter-x-hunter',
'Detektiv-Conan': 'detective-conan',
'Assassination-Classroom': 'ansatsu-kyoushitsu-assassination-classroom',
'Nanatsu-no-Taizai-The-Seven-Deadly-Sins': 'nanatsu-no-taizai',
'Sword-Art-Online-Alternative-Gun-Gale-Online': 'sword-art-online-alternative-ggo',
'Shingeki-no-Kyojin-Attack-on-Titan': 'attack-titan',
'Boku-no-Hero-Academia-My-Hero-Academia': 'my-hero-academia'
}
if(this.settings.fillerEnabled) {
// TODO
}
this.loadButton();
this.markFillerEpisodes();
}
markFillerEpisodes() {
let episodeRows = document.querySelectorAll('.episodes tr');
episodeRows.forEach((episode) => {
let url = new URL(episode.querySelector('a').href);
let path = url.pathname;
let data = this.dataHandler.getEpisodeData(path);
if(data.isFiller) {
episode.style.setProperty("background-color", "red", "important");
}
});
}
async getFiller() {
let name = this.getName();
let data = await this.getReq("https://www.animefillerlist.com/shows/" + name)
return new Promise((resolve, reject) => {
data = this.extractFillerEpisode(data);
resolve(data);
});
}
extractFillerEpisode(dom) {
let extracted = [];
let fillerEpisodes = dom.querySelector('div.filler > .Episodes').innerText;
fillerEpisodes = fillerEpisodes.split(',');
fillerEpisodes.forEach((episode) => {
let e = episode.split('-');
if (e.length > 1) {
let from = e[0];
let to = e[1];
for(let i = from; i <= to; i++) {
extracted.push(i);
}
} else {
extracted.push(e[0]);
}
})
return extracted;
}
getName() {
let url = new URL(window.location.href);
let path = url.pathname.split('/');
return this.manualMapping[path[2]] ?? path[2]
}
async getReq(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function(response) {
let parser = new DOMParser();
let doc = parser.parseFromString(response.responseText, "text/html");
resolve(doc);
}
});
});
}
saveFiller(filler, doc = document, last = 0) {
let episodes = doc.querySelectorAll('.episodes tr');
let lastEpisode = last + episodes.length;
filler.forEach((fillerNum) => {
let episode = episodes[fillerNum - 1 - last];
if(episode) {
let url = new URL(episode.querySelector('a').href);
let path = url.pathname;
let data = this.dataHandler.getEpisodeData(path) || {};
data.isFiller = true;
this.dataHandler.setEpisodeData(path, data);
}
});
return lastEpisode;
}
async sync() {
let filler = await this.getFiller();
let seasons = document.querySelectorAll('#seasons li a')
let last = 0;
let counter = 0;
for(let i = 0; i < seasons.length; i++) {
if(seasons[i].innerText == "Specials") {
continue;
}
let doc = await this.getReq(seasons[i].href);
last = this.saveFiller(filler, doc, last);
}
return;
}
async fillerExists() {
let name = this.getName();
let doc = await this.getReq("https://www.animefillerlist.com/shows/" + name);
if(doc.querySelector('div.filler > .Episodes')) {
return true;
}
return false;
}
async loadButton() {
if(!await this.fillerExists()) {
return;
}
let button = document.createElement('button');
button.innerText = 'Filler Laden';
button.style.backgroundColor = 'orange';
button.style.padding = '5px';
button.style.border = '2px solid black';
button.style.fontWeight = 'bold';
button.style.fontSize = "15px";
button.addEventListener('click', async () => {
button.innerText = 'loading ...';
try {
await this.sync();
new Notify({
text: 'Erfolgreich geladen',
type: 'success',
timeout: 3000
}).show();
setTimeout(() => {
location.reload();
}, 2000);
} catch {
new Notify({
text: 'Konnte nichts für diese Serie finden :(',
type: 'error',
timeout: 3000
}).show();
}
});
let target = await waitForElem('.seasons');
target.prepend(button);
}
}
new BsHandler();
new VivoHandler();
new AnimeFillerHandler();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment