diff --git a/.eslintrc.yaml b/.eslintrc.yaml new file mode 100644 index 0000000..5fca8a4 --- /dev/null +++ b/.eslintrc.yaml @@ -0,0 +1,88 @@ +# This is a baseline set of rules intended for usage in any ES6-based project. +# +# Be sure to add your project's specific environment(s) as needed via external configuration. +# See: http://eslint.org/docs/user-guide/configuring#specifying-environments + +--- +env: + es6: true + browser: true + +# http://eslint.org/docs/rules +rules: + + # Possible Errors + + valid-jsdoc: [1] + + # Best Practices + curly: [2] + dot-notation: [2] + eqeqeq: [1] + guard-for-in: [2] + no-div-regex: [2] + no-eval: [2] + no-extend-native: [2] + no-floating-decimal: [2] + no-implied-eval: [2] + no-labels: [2] + no-lone-blocks: [2] + no-loop-func: [2] + no-multi-spaces: [2] + no-native-reassign: [2] + no-new-wrappers: [2] + no-new: [2] + no-redeclare: [2] + no-return-assign: [2] + no-self-compare: [2] + radix: [2] + wrap-iife: [2, "inside"] + yoda: [2, "never"] + + # Strict Mode + + # strict: [2, "global"] + + # Variables + + no-shadow: [2] + no-use-before-define: [2] + + # Stylistic Issues + + block-spacing: [2, "always"] + brace-style: [2, "1tbs", allowSingleLine: true] + camelcase: [2, properties: "always"] + comma-style: [2, "last"] + consistent-this: [2, "self"] + eol-last: [2] + indent: [2, 2, {"VariableDeclarator": { "var": 2, "let": 2, "const": 3}}] + linebreak-style: [2, "unix"] + newline-after-var: [2] + no-lonely-if: [2] + no-trailing-spaces: [2] + no-unneeded-ternary: [2] + quotes: [2, "double"] + semi: [2, "always"] + spaced-comment: [2, "always"] + wrap-regex: [2] + + # ES6 + prefer-arrow-callback: [1] + no-console: [0] + + # Overrides to `eslint:recommended` rule set + + +extends: "eslint:recommended" + + +parserOptions: + arrowFunctions: true + blockBindings: true + defaultParams: true + destructuring: true + forOf: true + spread: true + templateStrings: true + ecmaVersion: 2017 diff --git a/madokami-list-files/madokami-list-files.meta.js b/madokami-list-files/madokami-list-files.meta.js new file mode 100644 index 0000000..e69de29 diff --git a/madokami-list-files/madokami-list-files.user.js b/madokami-list-files/madokami-list-files.user.js new file mode 100644 index 0000000..01c88e2 --- /dev/null +++ b/madokami-list-files/madokami-list-files.user.js @@ -0,0 +1,94 @@ +"use strict"; + +/* + * update html + */ +function updateLinks(anchors = []) { + let linksText = document.getElementById("linksText"); + let links = anchors.map((anchor) => decodeURIComponent(anchor.href)); + + linksText.innerHTML = links.sort().join("\n"); +} + +/* + * Recursively parse download links from a page. + * + * Returns an Array containing anchor elements of download links + */ +function parsePage(page, anchors = []) { + + /* + * Make an XMLHttpRequest + */ + function makeRequest (method = "GET", href) { + + let xhr = new XMLHttpRequest(); + + xhr.open(method, href, true); + xhr.responseType = "document"; + xhr.onload = function() { + parsePage(this.responseXML, anchors); + updateLinks(anchors); + }; + xhr.onerror = function () { + console.error("failed with" , this.status , xhr.statusText); + }; + xhr.send(); + } + + let re = /^https?:\/\/.*(?!(reader).)*(\.cbr|\.cbz|\.rar|\.zip)$/; + + Array.prototype.forEach.call(page.getElementsByTagName("a"), (anchor) => { + if (anchor.href.startsWith(window.location.href) && re.test(anchor.href)) { + anchors.push(anchor); + } else if (anchor.href.startsWith(page.URL) && anchor.innerText.endsWith("/") && !(anchor.href === page.URL)) { + makeRequest("GET", anchor.href, anchors); + } + }); + return anchors; +} + +function getDownloads() { + let downloadAnchors = parsePage(document, []); + + return downloadAnchors.map((anchorElement) => decodeURIComponent(anchorElement.href)); +} + + +/* + * Create a file containing data + */ +function createFile(data) { + let file = new Blob([data], {"type": "text/plain"}); + + return URL.createObjectURL(file); +} + +/* + * Create new DOM elements and fill them in + */ +function initElements() { + let indexContainer = document.getElementsByClassName("table-outer"); + let linksContainer = document.createElement("div"); + let linksText = document.createElement("textarea"); + + linksText.setAttribute("style", "min-width: 100%; margin: 30px 0; min-height: 400px"); + linksText.id = "linksText"; + indexContainer[0].appendChild(linksContainer).appendChild(linksText); + + let links = getDownloads(); + + linksText.innerHTML = links.sort().join("\n"); + + let fileLink = document.createElement("a"); + + fileLink.classList.add("button"); + fileLink.innerText = "Download to file"; + fileLink.download = "urls"; + fileLink.addEventListener("click", () => { fileLink.href = createFile(linksText.value); }); + + linksContainer.appendChild(fileLink); +} + +initElements(); + diff --git a/update-metablocks b/update-metablocks index 0314417..3438ad6 100755 --- a/update-metablocks +++ b/update-metablocks @@ -30,7 +30,7 @@ SOFTWARE. HELPMSG=" ################################################################### ## ## -## ${PROGRAM} ${VERSION}: ## +## ${PROGRAM} ${VERSION}: ## ## ## ## writes userscript metablock data to *.meta.js files. ## ## ## @@ -56,12 +56,12 @@ DRYRUN=true; INPUTFILE=""; OUTPUTFILE=""; OUTPUTFILEBOOL=false; -DATE=`date +%d-%m-%Y`; +DATE=`date --iso-8601=date`; writeoutput () { if $DRYRUN; then if [[ $(egrep "\@date" ${INPUTFILE}) ]]; then - cat ${INPUTFILE} | sed -r "s/[0-9]{2}\-[0-9]{2}\-[0-9]{4}/$DATE/"| \ + cat ${INPUTFILE} | sed -r "s/[0-9]{2,4}\\-[0-9]{2,4}\\-[0-9]{2,4}/$DATE/"| \ egrep -B 100 "==/UserScript==" ; echo ""; else @@ -73,7 +73,7 @@ writeoutput () { echo "Writing metablock from \"${INPUTFILE}\" into \"${OUTPUTFILE}\""; if [[ $(egrep "\@date" ${INPUTFILE}) ]]; then - sed -i -r "s/[0-9]{2}\-[0-9]{2}\-[0-9]{4}/$DATE/" "${INPUTFILE}"; + sed -i -r "s/[0-9]{2,4}\\-[0-9]{2,4}\\-[0-9]{2,4}/$DATE/" "${INPUTFILE}"; else sed -i -r "/\@version/a // @date $DATE \ " "${INPUTFILE}"; fi diff --git a/whyfalalala/whyfalalala.meta.js b/whyfalalala/whyfalalala.meta.js new file mode 100644 index 0000000..a42822b --- /dev/null +++ b/whyfalalala/whyfalalala.meta.js @@ -0,0 +1,12 @@ +// ==UserScript== +// @name Whyfalalala download WordPress gallery images +// @namespace https://github.com/ToostInc/userscripts +// @description Adds download links to posts containing a gallery +// @include /^https:\/\/whyfalalala\.wordpress\.com\/(\d{4}\/\d{2}\/\d{2}\/\S*\/(\#more-\d{4})?|page\/\d{1,}\/)?$/ +// @author Joost Bremmer < contact@madeofmagicandwires.online > +// @copyright 2018, Joost Bremmer +// @license MIT +// @version 1.3 +// @date 2018-07-17 +// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js +// ==/UserScript== diff --git a/whyfalalala/whyfalalala.user.js b/whyfalalala/whyfalalala.user.js new file mode 100644 index 0000000..8c2ce49 --- /dev/null +++ b/whyfalalala/whyfalalala.user.js @@ -0,0 +1,446 @@ +// ==UserScript== +// @name Whyfalalala download WordPress gallery images +// @namespace https://github.com/ToostInc/userscripts +// @description Adds download links to posts containing a gallery +// @include /^https:\/\/whyfalalala\.wordpress\.com\/(\d{4}\/\d{2}\/\d{2}\/\S*\/(\#more-\d{4})?|page\/\d{1,}\/)?$/ +// @author Joost Bremmer < contact@madeofmagicandwires.online > +// @copyright 2018, Joost Bremmer +// @license MIT +// @version 1.3 +// @date 2018-07-17 +// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js +// ==/UserScript== + +/** + * @file A userscript to download (manga) chapters from WordPress based galleries. + * It adds a download link at the end of every post where you can download a zip file containing all the images. + * + * @name whyfalalala + */ + + +/** + * String representing the scanlation group. + * @const + * @type {String} + */ +const GROUPNAME = "whyfalalala"; + +/** + * Enum for the scanlation source + * @const + * @enum {String} + */ +const SOURCE = { + /** Use if the source of the scanlation is from magazine scans */ + MAGAZINE: "mag", + /** Use if the source of the scanlation is from mixed scans. */ + MIXED: "mix", + /** Use if the source of the scanlation is from tonkabon scans. */ + VOLUME: "v", + /** Use if the source of the scanlation is from online sources. */ + WEB: "web", + /** Use if the source of the scanlation scans are unknown. */ + UNKNOWN: "unk" +}; + +/** + * Regex to check if a string matches the Madokami Naming Scheme + * @const + * @see {@link https://github.com/Daiz/manga-naming-scheme} + */ +const RE = new RegExp([ + /^(.*?)\s(\[\w{3}\])?\s?/, // Name of Manga [lang] + /(-)\s/, // - + /(((c|d)?\d{3,})((x|y|-|z)?((c|d)?\d{1,})?((-|x|y|z)?\d{1,})?((-|x|y)\d{1,})?))\s/, // c000-000x0-0 + /(\((mag|mix|(v\d{2,}(-\d{2,})?)|web)\))\s/, // (mag/web/mix/v00-00) + /(\[.*?\])?\s?/, // [Extra Information] + /(\[.*?\])/, // [Group] + /(\{(v|r)\d*?\})?$/, // {r00} + // /\.(\w{3,4})$/ // File extension +].map(r => r.source).join("")); + + + +/** + * @class Chapter + * @classdec Class representing a chapter. + * @global + * @property {HTMLElement} post - The WordPress post element asociated with the chapter + * @property {String} title - The title of the chapter, as taken from the post + * @property {String} fileName - The filename of the chapter; generated from the post title. + * @property {?Array.} images - Array of image objects of each image found in the post. + * @porperty {String} images[].url - The url of where the image resides. + * @porperty {String} images[].fileName - The file name to where the image needs to be saved to. + */ +class Chapter{ + /** + * Creates a Chapter instance. + * @constructor + * @param {HTMLElement} post - the root element of a WordPress post; usually
+ */ + constructor(post) { + /** @private @readonly */ + this._post = post; + /** @private */ + this._title = post.getElementsByTagName("h1")[0].innerText; + /** @private */ + this._fileName = Chapter.madokamiFileName(this, GROUPNAME, SOURCE.UNKNOWN); + /** @private */ + this._images = this.getImages(this._post); + } + + get post() { + return this._post; + } + + set post(value) { + // read only + } + + get title() { + return this._title; + } + + set title(value) { + this._title = value; + } + + get fileName() { + return this._fileName; + } + + set fileName(fName) { + this._fileName = fName; + } + + get images() { + return this._images; + } + + set images(post) { + this._images = this.getImages(post); + } + + /** + * Estimates a filename compatible with the Madokami Naming Scheme based on the + * chapter title. + * @see {@link https://github.com/Daiz/manga-naming-scheme} for more info. + * + * @method Chapter.madokamiFileName + * @static + * @param {Chapter} chapter - a Chapter object to base the filename on + * @param {String} chapter.title - the title of the chapter + * + * @param {String} groupName - the name of the scanlation group to use in the filename + * @param {SOURCE.Enum} source - the source of the scanlation can be either (mag|mix|v|web) + * + * @returns {String} the estimated madokami filename; Still needs to be adjusted slightly + */ + static madokamiFileName(chapter, groupName=GROUPNAME, source=SOURCE.UNKNOWN){ + + let chapterInfo = chapter.title.split(/(^.*?)\s?((volume|v)\s?\d{1,2})?\s?((chapter|ch|c)?\s?\d{1,}-?\d{1,})/gi); + + chapterInfo = chapterInfo.splice(1, chapterInfo.length - 2); + + let seriesTitle = chapterInfo[0]; + let chapterNo = (chapterInfo[3]) ? chapterInfo[3].replace(/(chapter|ch|c)\s?/i, "").padStart(3, "0") : ""; + let sourceStr = null; + + // Deterimine source. + switch(source) { + case SOURCE.MAGAZINE: + sourceStr = "(mag) "; + break; + case SOURCE.MIX: + sourceStr = "(mix) "; + break; + case SOURCE.VOLUME: + // Check if volumeNo was found in ChapterInfo + sourceStr = chapterInfo[1] ? chapterInfo[1].replace(/(volume|vol|v)\s?/i, "").padStart(2,"0") : ""; + sourceStr = (sourceStr !== "") ? `(v${sourceStr}) ` : ""; // output: "(v??) " + break; + case SOURCE.WEB: + sourceStr = "(web) "; + break; + case SOURCE.UNKNOWN: + sourceStr = "(unk) "; + break; + default: + sourceStr = "(unk) "; + } + + + // Example output: "Giant Killing - c001 (v01) [GROUPNAME]" + let madokamiStr = `${seriesTitle} - c${chapterNo} ${sourceStr}[${groupName}]`; + + return madokamiStr; + } + + /** + * Extracts all images from a WordPress gallery. + * + * @method Chapter#getImages + * @param {HTMLElement} post - root element of a WordPress post containing a gallery + * + * @returns {?Array.} - returns an array of Objects or null. + */ + getImages(post) { + if ( post.classList.contains("post_format-post-format-gallery") || post.getElementsByClassName("tiled-gallery").length > 0) { + let images = []; + let imgElements = Array.from(post.getElementsByTagName("img")); + + for (let img of imgElements) { + let chapImg = {}; + + chapImg.url = img.dataset.origFile.split('?')[0]; // remove any width parameters + chapImg.fileName = `${img.dataset.imageTitle}.${chapImg.url.match(/\.(\w{3,4})(?:($|\?))/)[1]}`; + images.push(chapImg); + } + return images; + } else { + return null; + } + } + + /** + * Fetch an image by url and turn it into a Blob object. + * Uses http://cors-anywhere.herokuapp.com as a porxy because of Same-Origin + * issues + * + * @method Chapter.getImageBlob + * @static + * @param {String} url - the url the image resides at. + * @return {Blob} a Blob object of the fetched image data. + * + */ + static getImageBlob(url){ + const proxy = "https://cors-anywhere.herokuapp.com"; + + return fetch(`${proxy}/${url}`).then((response) => { + return response.blob(); + }); + } +} + + + + +/** + * Iterates over objects asynchronously. + * + * @function asyncForEach + * @async + * @param {Array} array - element to iterate over. Also works with things like HTMLCollections. + * @param {Function} callback - function to asynchronously call on each iteration. + * @return {void} + * + * @see {@link https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404} + * + */ +async function asyncForEach(array, callback) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } +} + +/** + * Creates and appends necessary textarea and anchors to an element. + * + * @function createElements + * @param {Chapter} chapter - root element of a WordPress post, usually
+ * @returns {HTMLDivElement} - container of appended elements. + * + */ +function createElements(chapter) { + let container = document.createElement("div"); + let fNameDiv = container.cloneNode(); + let fNameLabel = document.createElement("label"); + let fNameInput = document.createElement("input"); + let textArea = document.createElement("textarea"); + let downloadButton = document.createElement("a"); + + + container.classList.add("container"); + + fNameDiv.classList.add("input-container"); + fNameDiv.style.marginBottom = "10px"; + fNameLabel.htmlFor = `${chapter.title}_fName`; + fNameLabel.innerText = "File Name"; + fNameInput.id = `${chapter.title}_fName`; + fNameInput.type = "text"; + fNameInput.value = chapter.fileName; + + // TODO: make this nice. + // let reOld = /^(.*?)\s(\[\w{3}\])?\s?(-)\s(((c|d)?\d{3,})((x|y|-|z)?((c|d)?\d{1,})?((-|x|y|z)?\d{1,})?((-|x|y)\d{1,})?))\s(\((mag|mix|v\d{2,}|web)\))\s(\[.*?\])?\s?(\[.*?\])(\{(v|r)\d*?\})?$/; + + fNameInput.pattern = RE.source; + fNameInput.placeholder = fNameInput.getAttribute("title"); + fNameInput.setAttribute("title", "Name of Manga [lang] - c000-000x0-0 (mag/web/mix/v00-00) [Extra Information] [Group]{revision}"); + fNameInput.style = "min-width: 50%; margin-left: 10px; padding: 0.33rem 0.75rem;"; + + fNameInput.addEventListener("input", () => { + if(fNameInput.validity.valid) { + console.log("fileName is valid; updating"); + chapter.fileName = fNameInput.value; + } else { + console.log(fNameInput.validationMessage); + } + }); + + textArea.value = "Loading..."; + + downloadButton.innerText = "Loading..."; + downloadButton.style = ` + margin: 5px 0; + display: inline-block; + background: #f8f8f8; + padding: 5px 4px; + border-radius: 5px; + text-decoration: none; + border-color: #bbb; + border-width: 1px; + border-style: solid; + `; + + fNameDiv.appendChild(fNameLabel); + fNameDiv.appendChild(fNameInput); + + container.appendChild(fNameDiv); + container.appendChild(textArea); + container.appendChild(downloadButton); + + // append to .entry-summary for blogroll or .entry-content for specific post + if (chapter.post.getElementsByClassName("entry-summary").length > 0) { + if (chapter.post.getElementsByClassName("more-link").length > 0) { + container.style.paddingBottom = "100px"; + } + chapter.post.getElementsByClassName("entry-summary")[0].appendChild(container); + } else { + chapter.post.getElementsByClassName("entry-content")[0].appendChild(container); + } + + return container; +} + +/** + * Links an achor to a Blob"s ObjectURL + * + * @function createFile + * @param {HTMLAnchorElement} anchor - anchor element that will link to the Blob + * @param {Blob} data - data that will be linked to + * @param {String} fileName - filename that the data will be saved to. + * @returns {void} + * + */ +function createFile(anchor, data, fileName) { + let url = URL.createObjectURL(data); + + anchor.href = url; + anchor.download = fileName; + anchor.innerText = "Download"; +} + +/** + * creates a Zip file of a Chapter object with {@link Chapter.images} as the + * files. + * + * @function createZip + * @async + * @param {Chapter} chapter - the chapter object to zip. + * @param {String} chapter.fileName - the file name of the folder that will be zipped. + * @param {Array.} chapter.images - the images that will be zipped + * @param {String} chapter.images.url - the url where the image resides. + * @param {String} chapter.images.fileName - the file under which the image data will be zipped + * + * @returns {Blob} - data as Blob of the generated Zip file. + * + */ +async function createZip(chapter) { + let zip = new JSZip(); + + let zipCh = zip.folder(chapter.fileName); + + await asyncForEach(chapter.images, async (imgObj) => { + await zipCh.file(imgObj.fileName, Chapter.getImageBlob(imgObj.url)); + }); + + return zip.generateAsync({type: "blob", comment: `generated by ${GROUPNAME} downloader`}); +} + +let chapters = []; + + +/** + * Creates {Chapter} objects from all matching posts on a page and appends Zips to them + * + * @function getChapters + * @async + * @param {HTMLCollection} chapterElements - element to search for WordPress posts + * @returns {Number} - returns zero if all went well or -1 if something went wrong. + */ +async function getChapters(chapterElements) { + + await asyncForEach(chapterElements, async (chapterElement) => { + let chapter = new Chapter(chapterElement); + + if (chapter !== undefined && chapter.images !== null) { + console.log(`Found ${chapter.title}`); + chapters.push(chapter); + + let container = createElements(chapter); + let textArea = container.children[1]; + + container.id = `chapter_${chapter.title.replace(" ", "_")}`; + + let imageUrls = chapter.images.map((img) => img.url); + + textArea.value = imageUrls.join("\n"); + + let chapterZip = await createZip(chapter); + + await createFile(container.children[2], chapterZip, `${chapter.fileName}.cbz`); + + container.children[2].addEventListener("click", () => { + container.children[2].download = `${chapter.fileName}.cbz`; + }); + + return 0; + } else { return -1; } + }); +} + +/****************************************************************************** +* Blog specific stuff starts here. +* +*******************************************************************************/ + +async function filterPosts(elements) { + return new Promise((resolve, reject) => { + let chapterElements = Array.prototype.filter.call(elements, (element) => { + if (element.classList.contains("tag-translation")) { + return true; + } + return false; + }); + + if (chapterElements.length > 0) { + resolve(chapterElements); + } else { + reject(null); + } + }); +} + +/** + * Blog specific function that initiates the script + * @returns {void} + */ +async function start() { + console.log("Start!"); + let chapterElements = await filterPosts(document.getElementsByTagName("article")); + + await getChapters(chapterElements); +} + +start(); diff --git a/youtube-hide-watched/youtube-hide-watched.meta.js b/youtube-hide-watched/youtube-hide-watched.meta.js index 7c68d00..45db599 100644 --- a/youtube-hide-watched/youtube-hide-watched.meta.js +++ b/youtube-hide-watched/youtube-hide-watched.meta.js @@ -7,9 +7,11 @@ // @copyright 2014, Joost Bremmer // @license MIT // @version 1.1.3 -// @date 13-06-2015 -// @require http://code.jquery.com/jquery-latest.min.js -// @downloadURL https://rawgit.com/ToostInc/userscripts/master/youtube-hide-watched/youtube-hide-watched.user.js -// @updateURL https://rawgit.com/ToostInc/userscripts/master/youtube-hide-watched/youtube-hide-watched.meta.js +// @date 19-10-2017 +// @downloadURL +// https://rawgit.com/ToostInc/userscripts/master/youtube-hide-watched/youtube-hide-watched.user.js +// @updateURL +// https://rawgit.com/ToostInc/userscripts/master/youtube-hide-watched/youtube-hide-watched.meta.js // @grant none +// @runat document-end // ==/UserScript== diff --git a/youtube-hide-watched/youtube-hide-watched.user.js b/youtube-hide-watched/youtube-hide-watched.user.js index 13a1608..b6e25b0 100644 --- a/youtube-hide-watched/youtube-hide-watched.user.js +++ b/youtube-hide-watched/youtube-hide-watched.user.js @@ -7,96 +7,106 @@ // @copyright 2014, Joost Bremmer // @license MIT // @version 1.1.3 -// @date 13-06-2015 -// @require http://code.jquery.com/jquery-latest.min.js -// @downloadURL https://rawgit.com/ToostInc/userscripts/master/youtube-hide-watched/youtube-hide-watched.user.js -// @updateURL https://rawgit.com/ToostInc/userscripts/master/youtube-hide-watched/youtube-hide-watched.meta.js +// @date 19-10-2017 +// @downloadURL +// https://rawgit.com/ToostInc/userscripts/master/youtube-hide-watched/youtube-hide-watched.user.js +// @updateURL +// https://rawgit.com/ToostInc/userscripts/master/youtube-hide-watched/youtube-hide-watched.meta.js // @grant none +// @runat document-end // ==/UserScript== +/** + * Copyright (c) 2014 Joost Bremmer + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and + * to permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +console.log("Start!"); + +const viewedVideos=[]; + +function getWatchedVideos() { + try { + console.log("Start async func") + + return new Promise((resolve, reject) => { + function checkMutation(mutation) { + if (mutation.target.tagName === + "YTD-THUMBNAIL-OVERLAY-PLAYBACK-STATUS-RENDERER") { + viewedVideos.push(mutation.target); + if (viewedVideos.length == 1) { + console.log(viewedVideos); + resolve(viewedVideos); + } + } + } + const mutate = new MutationObserver((mutations) => { + mutations.forEach(mutation => { checkMutation(mutation); }); + }); + Array.prototype.forEach.call( + document.getElementsByTagName("ytd-thumbnail"), + (element) => { + mutate.observe(element, { childList : true, subtree : true }) + } + ); + }); + } catch (e) { + console.log(e); + reject(e); + } +} -// The MIT License -// -// Copyright (c) 2014 Joost Bremmer -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files -// (the "Software"), to deal in the Software without restriction, -// including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and -// to permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -$(document).ready (function () { - //Add mutation observer, checks for changes in DOM - if (MutationObserver) { - var myObserver = new MutationObserver(hideWatched); - } - else { - var myObserver = new WebKitMutationObserver(hideWatched); - } - myObserver.observe(document, { childList : true, subtree : true }); - hideWatched(); - - // Add checkbox - var checker = '
  • \n'+ - '\t\n'+ - '
  • '; - $("#appbar-nav .appbar-nav-menu").prepend(checker); - $("#checker-container").css({ - 'color': "#666", - "vertical-align" : "middle", - "text-align" : "center" - }); - //checkbox event - $("#hide-videos").change(function() { - if ( $(this).is(":not(:checked)") ) { - showWatched(); - } - - else { - hideWatched(); - }; - - }); - - //BONUS: always enable load more button. - $("button.load-more-button").removeProp("disabled"); - - hideWatched(); - - -}); +async function hideWatched() { + elements = getWatchedVideos().then(() => { + console.log(viewedVideos.length); + viewedVideos.forEach((element) => { + console.log("hiding this", element); + element.style.display="none"; + }); + }); + console.log(elements.constructor.name.toString()); + console.log(elements.length); + /* Array.prototype.filter.call(viewedVideos, video => { */ + /* console.log("hideWatched", video, video.style, video.style.width); */ + /* return video.getAttribute("width") === "100%"; */ + /* }); */ +} -function hideWatched () { +/* console.log("videolist",document.getElementsByTagName("ytd-section-list-renderer")); */ +/* console.log("thumbnail", document.getElementsByTagName("ytd-thumbnail")); */ - if ( $("#hide-videos").is(":checked") ) { - $("div.watched-badge").each(function() { - $(this).closest("ol.item-section").hide("200"); +/* + * Array.prototype.forEach.call( + * document.getElementsByTagName("ytd-thumbnail"), elementToObserve => { + * mutate.observe(elementToObserve, { childList : true, subtree : true }); + * } + * ); + */ - }); - } -}; +/* TODO: checkbox */ -function showWatched() { - $("div.watched-badge").each(function() { - $(this).closest("ol.item-section").show("300"); +console.log(""); +hideWatched(); +console.log("End"); - }); -} +/* vim: set ts=2 sts=2 sw=2 et: */