NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name 4anime
// @namespace Violentmonkey Scripts
// @match https://4anime.to/*
// @grant none
// @license MIT
// @require https://cdn.jsdelivr.net/npm/@nano-sql/core@2.3.7/dist/nano-sql.min.js
// @require https://cdn.jsdelivr.net/npm/fasy
// @require https://cdn.jsdelivr.net/gh/kenwheeler/slick@1.8.1/slick/slick.min.js
// ==/UserScript==
var table = '4animeTo';
const validLocations = Object.freeze({
home: 'home',
anime: 'anime',
episode: 'episode',
nowhere: 'nowhere'
});
function whereAmI() {
if (window.location.pathname == '/') {
return validLocations.home;
} else if (window.location.pathname.split('/')[1] == "anime") {
return validLocations.anime;
} else if (document.getElementsByTagName('video').length > 0) {
return validLocations.episode;
} else {
return validLocations.nowhere;
}
}
async function getAllAnimes() {
var promise = await new Promise((resolve, reject) => {
nSQL(table).query("select").exec().then((rows) => {
resolve(rows);
}).catch((err) => {
throw err;
})
})
.catch(err => {
throw err
});
return promise;
//return nSQL(table).query("select").exec();
}
async function getAnime(animeSlug) {
var promise = await new Promise((resolve, reject) => {
nSQL(table).query("select").where(["animeSlug", "=", animeSlug]).exec().then((rows) => {
// console.log(rows);
resolve(rows[0])
}).catch((err) => {
throw err;
});
})
.catch(err => {
throw err
});
return promise;
}
async function doesAnimeExistinDB(animeSlug) {
var promise = await new Promise((resolve, reject) => {
nSQL(table).query("select").where(["animeSlug", "=", animeSlug]).exec().then((rows) => {
if (rows.length > 0) {
resolve(true);
} else {
resolve(false);
}
}).catch((err) => {
throw err;
})
})
.catch(err => {
throw err
});
return promise;
}
async function setData(animeSlug, episodeID, lastWatchedEpisode, lastWatchedEpisodeTime, episodeURL, animeTitle, animePosterURL) {
console.log(animeTitle);
var animeExists = await doesAnimeExistinDB(animeSlug);
if (!animeExists) {
fetch(document.querySelector('#titleleft').href).then(r=>r.text()).
then(html=>{
var doc = new DOMParser().parseFromString(html, "text/html");
var animePosterURL = doc.querySelector("#details img").src;
nSQL(table).query("upsert", {
animeSlug,
episodeID,
lastWatchedEpisode,
lastWatchedEpisodeTime,
episodeURL,
animePosterURL
}).exec().catch((err) => {
console.log(err)
});
});
} else {
var anime = await getAnime(animeSlug);
var animeID = anime.id;
nSQL(table)
.query("upsert", {
id:animeID,
animeSlug,
episodeID,
lastWatchedEpisode,
lastWatchedEpisodeTime,
episodeURL,
animeTitle
})
.exec();
}
}
function insertAfter(newNode, referenceNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
function createBtn(text){
var btn = document.createElement("BUTTON");
btn.style.backgroundColor='#221f1f';
btn.style.border='none';
btn.style.color='#747474';
btn.style.padding='20px';
btn.style.marginTop='20px';
btn.style.borderRadius='.25em';
btn.style.cursor='pointer';
btn.onmouseover = function() {
this.style.backgroundColor = "#444040";
}
btn.onmouseout = function() {
this.style.backgroundColor = "#221f1f";
}
btn.innerHTML = text;
insertAfter(btn, document.querySelector('#description-mob p'));
return btn;
}
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
const wrapAll = (target, wrapper = document.createElement('div')) => {
;
[...target.childNodes].forEach(child => wrapper.appendChild(child))
target.appendChild(wrapper)
return wrapper
}
async function start() {
var currentLocation = whereAmI();
if (currentLocation == validLocations.episode) {
// figure out what anime is playing
var animeSlug = document.querySelector("meta[property='og:url']").content.split("/")[3].split('-episode')[0];
// what ep
var urlParams = new URLSearchParams(document.querySelector("meta[property='og:url']").content.split("/").slice(-1)[0]);
var episodeID = urlParams.get('id');
var episodeNumber = document.querySelector('.active .active').innerHTML;
// what time
var videoTime;
var videoElement = document.querySelector('video');
//if currently playing anime exists and lastWatchedEpisode matches current episode resume
var anime = await getAnime(animeSlug);
if(anime) {
var {lastWatchedEpisode} = anime;
//resume
if(lastWatchedEpisode == episodeNumber) {
videoElement.currentTime = anime.lastWatchedEpisodeTime;
}
}
window.setInterval(function() {
if (videoElement.playing) {
videoTime = videoElement.currentTime;
setData(animeSlug, episodeID, episodeNumber, videoTime, window.location.href, document.querySelector("meta[property='og:title']").content.split('Epsiode')[0].trim());
}
}, 1000);
} else if(currentLocation == validLocations.anime) {
var animeSlug = document.querySelector("meta[property='og:url']").content.split("/").slice(-1)[0];
var anime = await getAnime(animeSlug);
//anime.episodeURL
if(anime) {
var btn = createBtn("Continue watching");
var url = anime.episodeURL;
} else {
var btn = createBtn("Start Watching");
var url = document.querySelector('#servers li:nth-child(1) a').href;
}
btn.addEventListener("click", ()=>{
window.location.href = url;
});
} else if(currentLocation == validLocations.home) {
var animes = await getAllAnimes();
var wrapper= document.createElement('div');
insertAfter(wrapper, document.querySelector('.billboard'));
var animeE= document.createElement('div');
var content = `<div style="width:1174px; margin:auto;"> <a>Continue Watching</a> <div style="display:flex;flex-wrap: wrap; margin-top:10px;">
<style>
.cw:nth-child(7n+1){margin-right:0 !important}
</style>`;
console.log(animeE.innerHTML);
animes.forEach((anime, i) => {
var marginLeft = '15px';
content= `
${content}
<div class="cw" style="display:flex; flex-direction: column; width:154.717px; margin-right:${marginLeft}; padding-bottom:10px; white-space: nowrap; overflow: hidden;">
<a href="${anime.episodeURL}" style="height:219.25px;">
<img src="${anime.animePosterURL}" alt="" style="object-fit:cover; width: 100%; height: 100%">
<center>
<a href="${anime.episodeURL}">
${anime.animeTitle}
</a>
</center>
<center>
<a href="${anime.episodeURL}" style="color: #888888;">
Episode ${anime.lastWatchedEpisode}
</a>
</center>
</a>
</div>
`;
});
content = `${content} </div></div>`;
animeE.innerHTML = content;
insertAfter(animeE, wrapper);
}
}
nSQL().createDatabase({
id: "4Anime",
mode: "PERM",
tables: [{
name: table,
model: {
"id:uuid": {
pk: true
},
"animeSlug:string": {},
"animeTitle:string": {},
"episodeID:int": {},
"episodeURL:string": {},
"lastWatchedEpisode:int": {},
"lastWatchedEpisodeTime:number": {},
"animePosterURL:string": {},
}
}]
}).then(() => {
start();
}).catch((err) => {
console.log("erroe starting db", err);
})
Object.defineProperty(HTMLMediaElement.prototype, 'playing', {
get: function() {
return !!(this.currentTime > 0 && !this.paused && !this.ended && this.readyState > 2);
}
})