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); } })