// ==UserScript== // @name FanFictionNavigator // @name:ru FanFictionNavigator // @namespace window // @description:en Mark and hide fanfics or authors // @description:ru Выделяет цветом/скрывает фанфики или авторов // @include https://ficbook.net/* // @include https://www.fanfiction.net/* // @include https://archiveofourown.org/* // @include http://archiveofourown.org/* // @include https://tbooklist.org/* // @require https://code.jquery.com/jquery-1.7.2.min.js // @require https://greasyfork.org/scripts/17419-underscore-js-1-8-3/code/Underscorejs%20183.js?version=109803 // @version 25 // @grant GM_addStyle // @description Mark and hide fanfics or authors // @downloadURL none // ==/UserScript== // Based on https://chrome.google.com/webstore/detail/fanfiction-organizer/adlnghnicfngjnofbljedmclmdoepcbe // by Stefan Hayden // Fics: const FIC_LIKED = 0; const FIC_DISLIKED = 1; const FIC_MARKED = 2; const FIC_CALIBRE = 3; // Authors: const AUTHOR_LIKED = 0; const AUTHOR_DISLIKED = 1; // colors. now used for like/dislike/etc links const COLOR_LIKED = '#C4FFCA'; const COLOR_DISLIKED = '#FCB0B0'; const COLOR_MARKED = '#CCCCCC'; const COLOR_CALIBRE = '#F1D173'; const COLOR_CLEARED = '#FFF'; const COLOR_FB_CLEAR = '#FFF'; // styles for author/story links; links should have only one of these so order doesn't matter GM_addStyle("a.ffn_dislike_fic {text-decoration: line-through; font-weight: bold;}"); GM_addStyle("a.ffn_like_fic {font-weight: bold;} "); GM_addStyle("a.ffn_dislike_author {text-decoration: line-through; font-weight: bold;}"); GM_addStyle("a.ffn_like_author {font-weight: bold;} "); GM_addStyle("a.ffn_mark {font-weight: bold;}"); GM_addStyle("a.ffn_calibre {font-weight: bold;}"); // styles for box background; fic style should overwrite author style GM_addStyle(".ffn_like_author:not(a) {background-color:#C4FFCA !important;}"); GM_addStyle(".ffn_dislike_author:not(a) {background-color:#FCB0B0 !important;}"); GM_addStyle(".ffn_like_fic:not(a) {background-color:#C4FFCA !important;}"); GM_addStyle(".ffn_dislike_fic:not(a) {background-color:#FCB0B0 !important;}"); GM_addStyle(".ffn_mark:not(a) {background-color:#CCCCCC !important;}"); GM_addStyle(".ffn_calibre:not(a) {background-color:#F1D173 !important;}"); // styles for boxes, they differ between sites /* switch(window.location.hostname){ case "www.fanfiction.net": case "tbooklist.org": GM_addStyle("div.ffn_dislike {background-color:#FCB0B0 !important;}"); GM_addStyle("div.ffn_like {background-color:#C4FFCA !important;}"); GM_addStyle("div.ffn_mark {background-color:#CCCCCC !important;}"); GM_addStyle("div.ffn_calibre {background-color:#F1D173 !important;}"); break case "archiveofourown.org": GM_addStyle(".ffn_dislike {background-color:#FCB0B0 !important;}"); GM_addStyle(".ffn_like {background-color:#C4FFCA !important;}"); GM_addStyle(".ffn_mark {background-color:#CCCCCC !important;}"); GM_addStyle(".ffn_calibre {background-color:#F1D173 !important;}"); break case "ficbook.net": GM_addStyle("div.ffn_dislike {background-color:#FCB0B0 !important;}"); GM_addStyle("div.ffn_like {background-color:#C4FFCA !important;}"); GM_addStyle("div.ffn_mark {background-color:#CCCCCC !important;}"); GM_addStyle("div.ffn_calibre {background-color:#F1D173 !important;}"); GM_addStyle("ul.ffn_dislike {background-color:#FCB0B0 !important;}"); GM_addStyle("ul.ffn_like {background-color:#C4FFCA !important;}"); GM_addStyle("ul.ffn_mark {background-color:#CCCCCC !important;}"); GM_addStyle("ul.ffn_calibre {background-color:#F1D173 !important;}"); break } */ // prevent conflicts with websites' jQuery version this.ffn$ = this.jQuery = jQuery.noConflict(true); var db = JSON.parse(localStorage.getItem("FFLikerAA") || '{}'); db.options = db.options || {}; db.version = db.version || '0.2'; // // APP // // Main var Application = function Application(optionsin) { var a = {}; var options = optionsin || {}; if(!options.namespace) { throw new Error("namespace is required"); } if(!options.db) { throw new Error("database object is required"); } a.namespace = options.namespace; var db = options.db; db[a.namespace] = db[a.namespace] || { fic: {}, author:{} }; a.collection = []; a.color = { link_default: ffn$("ol.work.index.group > li:first a:first").css("color"), like_link:'', like_background:'', dislike_link:'', dislike_background:'', }; a.save = function(type,id,value){ if(type == "fic" || type == "author") { a.saveNameSpaced(type,id,value); } else { if(value === "clear") { delete db[type][id]; } else { db[type][id] = value; } localStorage.setItem("FFLikerAA", JSON.stringify(db)); } }; a.saveNameSpaced = function(type,id,value){ if(value === "clear") { delete db[a.namespace][type][id]; } else { db[a.namespace][type][id] = value; } localStorage.setItem("FFLikerAA", JSON.stringify(db)); }; a.author = {}; a.author.get = function(id){ return db[a.namespace].author[id]; }; a.author.like = function(id) { a.save("author",id,AUTHOR_LIKED); _.each(a.author.getFics(id), function(story){ story.author = AUTHOR_LIKED; story.like_author(); }); }; a.author.dislike = function(id) { a.save("author",id,AUTHOR_DISLIKED); //ga('set', 'metric3', 1); _.each(a.author.getFics(id), function(story){ story.author = AUTHOR_DISLIKED; story.dislike_author(); }); }; a.author.clear = function(id) { a.save("author",id,"clear"); _.each(a.author.getFics(id), function(story){ story.author = ''; story.clear_author(); }); }; a.author.getFics = function(id) { return _.filter(a.collection,function(story){ return story.authorId() == id; }); }; a.fic = {}; a.fic.get = function(id) { return db[a.namespace].fic[id]; }; a.fic.like = function(id) { a.save("fic",id,FIC_LIKED); var story = _.find(a.collection,function(story){ return story.ficId() == id; }); story.fic = FIC_LIKED; story.like_story(); }; a.fic.dislike = function(id) { a.save("fic",id,FIC_DISLIKED); var story = _.find(a.collection,function(story){ return story.ficId() == id; }); story.fic = FIC_DISLIKED; story.dislike_story(); }; a.fic.mark = function(id) { a.save("fic",id,FIC_MARKED); var story = _.find(a.collection,function(story){ return story.ficId() == id; }); story.fic = FIC_MARKED; story.mark_story(); }; a.fic.calibre = function(id) { a.save("fic",id,FIC_CALIBRE); var story = _.find(a.collection,function(story){ return story.ficId() == id; }); story.fic = FIC_CALIBRE; story.calibre_story(); }; a.fic.clear = function(id) { a.save("fic",id,"clear"); var story = _.find(a.collection,function(story){ return story.ficId() == id; }); story.fic = ''; story.clear_story(); }; a.options = function(name, value) { if(!name) { throw new Error("name is required. what option are you looking for?"); } if(typeof value !== "undefined") { a.save("options",name,value); return false; } else { return db.options[name]; } }; return a; }; var Story = function(optionsin) { var a = {}; var options = optionsin || {}; if(!options.instance) { throw new Error("instance of this is required"); } if(!options.namespace) { throw new Error("namespace is required"); } var _this = ffn$(options.instance); a["default"] = { template: function() { var template = '
'+ 'Story: Like | '+ 'Dislike | '+ 'Mark | '+ 'Calibre | '+ 'Clear'+ ' Author: Like | '+ 'Dislike | '+ 'Clear'+ '
'; return template; }, addActions: function() { var instance = this; _this.append(this.template()); _this.find('.new_like_actions .like_author').click(function(){ app.author.like(instance.authorId()); return false; }); _this.find('.new_like_actions .dislike_author').click(function(){ app.author.dislike(instance.authorId()); return false; }); _this.find('.new_like_actions .clear_author').click(function(){ app.author.clear(instance.authorId()); return false; }); _this.find('.new_like_actions .like_story').click(function(){ app.fic.like(instance.ficId()); return false; }); _this.find('.new_like_actions .dislike_story').click(function(){ app.fic.dislike(instance.ficId()); return false; }); _this.find('.new_like_actions .mark_story').click(function(){ app.fic.mark(instance.ficId()); return false; }); _this.find('.new_like_actions .calibre_story').click(function(){ app.fic.calibre(instance.ficId()); return false; }); _this.find('.new_like_actions .clear_story').click(function(){ app.fic.clear(instance.ficId()); return false; }); }, hide: function() { _this.hide(); }, set_story: function(){ switch(this.fic){ case FIC_LIKED: // _this.css('background-color',COLOR_LIKED); // this.$fic.css('font-weight','900') _this.addClass("ffn_like_fic"); this.$fic.addClass("ffn_like_fic"); break; case FIC_DISLIKED: _this.addClass("ffn_dislike"); this.$fic.addClass("ffn_dislike"); break; case FIC_MARKED: _this.addClass("ffn_mark"); this.$fic.addClass("ffn_mark"); break; case FIC_CALIBRE: _this.addClass("ffn_calibre"); this.$fic.addClass("ffn_calibre"); break; } }, set_author: function() { if(this.author === AUTHOR_LIKED) { this.$author.addClass("ffn_like_author"); _this.addClass("ffn_like_author"); } if(this.author === AUTHOR_DISLIKED) { this.$author.addClass("ffn_dislike_author"); _this.addClass("ffn_dislike_author"); } }, like_story: function(){ this.clear_story(); _this.addClass("ffn_like_fic"); this.$fic.addClass("ffn_like_fic"); }, dislike_story: function(){ this.clear_story(); _this.addClass("ffn_dislike_fic"); this.$fic.addClass("ffn_dislike_fic"); }, mark_story: function(){ this.clear_story(); _this.addClass("ffn_mark"); this.$fic.addClass("ffn_mark"); }, calibre_story: function(){ this.clear_story(); _this.addClass("ffn_calibre"); this.$fic.addClass("ffn_calibre"); }, clear_story: function(){ _this.removeClass("ffn_like_fic ffn_dislike_fic ffn_mark ffn_calibre"); this.$fic.removeClass("ffn_like_fic ffn_dislike_fic ffn_mark ffn_calibre"); }, like_author: function(){ this.clear_author(); this.$author.addClass("ffn_like_author"); _this.addClass("ffn_like_author"); }, dislike_author: function(){ this.clear_author(); this.$author.addClass("ffn_dislike_author"); _this.addClass("ffn_dislike_author"); }, clear_author: function(){ _this.removeClass("ffn_like_author ffn_dislike_author"); this.$author.removeClass("ffn_like_author ffn_dislike_author"); } }; // Specific sites overrides a["www.fanfiction.net"] = { $author: _this.find('a[href^="/u"]:first'), $fic: _this.find('a[href^="/s"]:first'), authorId: function() { if (typeof this.$author.attr('href') === "undefined") { return window.location.pathname.split("/")[2]; } else { return this.$author.attr('href').split('/')[2]; } }, ficId: function() { if (this.$fic.length === 0) { return window.location.pathname.split("/")[2]; } else { return this.$fic.attr('href').split('/')[2]; } }, hide: function() { // do not hide story header on reading page and story block on author page if (! window.location.pathname.split("/")[1].match("^s$|^u$")) _this.hide(); } }; // a["archiveofourown.org"] = { $author: _this.find('a[href^="/users/"]:first'), $fic: _this.find('a[href^="/works/"]:first'), authorId: function() { return this.$author.attr('href').split('/')[2]; }, ficId: function() { if (this.$fic.length === 0) { return window.location.pathname.split("/")[2]; } else { return this.$fic.attr('href').split('/')[2]; } }, hide:function(){ if (window.location.pathname.split("/")[3]!=="chapters") { _this.hide(); } } }; // a["ficbook.net"] = { $author: _this.find('a[href^="/authors"]:first'), $fic: _this.find('a[href^="/readfic"]:first'), authorId: function() { console.log(this.$author); return this.$author.attr('href').split('/')[2]; }, ficId: function() { if (this.$fic.length === 0) { return window.location.pathname.split("/")[2]; } else { return this.$fic.attr('href').split('/')[2]; } }, hide:function(){ if (window.location.pathname.split("/")[1]!=="readfic" && window.location.pathname.split("/")[2]!=="favourites") { _this.parent().hide(); } } }; a["tbooklist.org"] = { $author: _this.find('a[href^="https://tbooklist.org/authors"]:first'), $fic: _this.find('a[href*="https://tbooklist.org/books"]:first'), authorId: function() { if (this.$author.length === 0) { return 0; } else { return this.$author.attr('href').trim().split('/')[4]; } }, ficId: function() { if (window.location.pathname.split("/")[1] === "books") { return window.location.pathname.trim().split("/")[2] } if (this.$fic.length === 0) { return 0; } else { return this.$fic.attr('href').trim().split('/')[4]; } }, hide:function(){ // do not hide when viewing fanfic details if(window.location.pathname.split("/")[1] !== "books") { _this.hide(); } } }; var b = ffn$.extend({}, a["default"], a[options.namespace]); b.fic = app.fic.get(b.ficId()); b.author = app.author.get(b.authorId()); // do not show liker links if ficid or authorid are undefined (tweak for tbooklist.org) if (b.ficId() !== 0 && b.authorId() !== 0) { b.addActions(); } b.set_story(); b.set_author(); //hide if((app.options("hide_dislikes") === true && (b.fic === FIC_DISLIKED || b.author === AUTHOR_DISLIKED)) || (app.options("hide_likes") === true && (b.fic === FIC_LIKED || b.author === AUTHOR_LIKED)) || (app.options("hide_marked") === true && b.fic === FIC_MARKED) || (app.options("hide_calibre") === true && b.fic === FIC_CALIBRE)){ // if(b.fic !== true && b.author) { // for liked fics of disliked authors b.hide(); // } } return b; }; var app = new Application({namespace:document.location.host, db: db}); // Adding action links and navigation shortcuts to pages switch(window.location.hostname){ case "www.fanfiction.net": // adding hotkeys document.addEventListener('keydown', function(e){ switch (e.keyCode) { case 37: var Prev = ffn$("a:contains('« Prev')"); if (typeof(Prev[0])!=='undefined') {window.location.href = Prev[0];} break; case 39: var Next = ffn$("a:contains('Next »')"); if (typeof(Next[0])!=='undefined') {window.location.href = Next[0];} break; } }, false); // links in list ffn$(".z-list").each(function(){ var story = new Story({ namespace: app.namespace, instance: this }); app.collection.push(story); }); // links on reading page ffn$("div#profile_top").each(function(){ var story = new Story({ namespace: app.namespace, instance: this }); app.collection.push(story); }); // hide/show options ffn$('div#content_wrapper_inner').after( '
'+ 'Liker Options: '+ '
' ); break; case "archiveofourown.org": // adding hotkeys document.addEventListener('keydown', function(e){ switch (e.keyCode) { case 37: var Prev = ffn$("a:contains('← Previous')"); if (typeof(Prev[0])!=='undefined') {window.location.href = Prev[0];} break; case 39: var Next = ffn$("a:contains('Next →')"); if (typeof(Next[0])!=='undefined') {window.location.href = Next[0];} break; } }, false); // in list ffn$("ol.work.index.group > li").each(function(){ var story = new Story({ namespace: app.namespace, instance: this }); app.collection.push(story); }); // on reading page ffn$("div.preface.group").each(function(){ // console.log(this); var story = new Story({ namespace: app.namespace, instance: this }); app.collection.push(story); }); // hide/show options ffn$('div.navigation.actions.module').after( '
'+ 'Liker Options: '+ '
' ); break; case "ficbook.net": // on reading page patharr = window.location.pathname.split("/"); if (patharr[1]==="readfic"){ ffn$("div.row.hat-row > ul.list-unstyled").each(function() { var story = new Story({ namespace: app.namespace, instance: this }); app.collection.push(story); }); } // in lists if (patharr[1]==="find" || (patharr[1]==="collections") || (patharr[3]==="profile" && patharr[4]==="works") || // in author profile / works (patharr[1] === "home" && ["favourites","collections"].indexOf(patharr[2])!=-1) ){ // indexOf => checks if patharr[2] is in [] array //ffn$(".description").each(function() { ffn$("article.block > div.description").each(function() { // console.log(this) var story = new Story({ namespace: app.namespace, instance: this }); app.collection.push(story); if (patharr[1]==="find") { ffn$(this).append(''); } }); // add hotkeys document.addEventListener('keydown', function(e){ switch (e.keyCode) { case 37: var Prev = ffn$("a[aria-label='Предыдущая']"); window.location.href = Prev[0]; break; case 39: var Next = ffn$("a[aria-label='Следующая']"); window.location.href = Next[0]; break; } }, false); } // hide/show options ffn$('section.content-section').after( '
'+ 'Liker Options: '+ '
' ); // quickly mark as read in search /*console.log(patharr) if (patharr[1]==="find") { ffn$(".js-mark-readed").click(function() { var t = ffn$(this); return ffn$(this).hasClass("btn-success") ? confirm(trans.t("Убрать из прочитанного?")) && RequestSender.send("/ajax/read", { fanfic_id: ffn$(this).data("fanficId"), action: "unread" }).then(function() { t.addClass("btn-primary").removeClass("btn-success"), t.html(' ' + trans.t(" Пометить как прочитанное")) }, function(t) { toastr.error(t.error) }) : (t.addClass("btn-success").removeClass("btn-default"), t.html(' ' + trans.t(" Прочитано!")), RequestSender.send("/ajax/read", { fanfic_id: ffn$(this).data("fanficId"), action: "read" }))}) }*/ break; case "tbooklist.org": patharr = window.location.pathname.split("/"); // in feed if (patharr[1]==="feed"){ ffn$("div.content-block > div.regular").each(function() { var story = new Story({ namespace: app.namespace, instance: this }); app.collection.push(story); }); } // book page if (patharr[1]==="books"){ ffn$("div.cmedia-divided__child__top").each(function() { var story = new Story({ namespace: app.namespace, instance: this }); app.collection.push(story); }); } // hide/show options ffn$('div.content-block').after( '
'+ 'Liker Options: '+ '
' ); break; } // OPTIONS // - show/hide global options // if(app.options("hide_likes")){ ffn$('.liker_script_options').append('Show Likes'); ffn$('.liker_script_options .show_likes').click(function(){ show_likes(); }); } else { ffn$('.liker_script_options').append('Hide Likes'); ffn$('.liker_script_options .hide_likes').click(function(){ hide_likes(); }); } ffn$('.liker_script_options').append('| '); if(app.options("hide_dislikes")){ ffn$('.liker_script_options').append('Show Dislikes'); ffn$('.liker_script_options .show_dislikes').click(function(){ show_dislikes(); }); } else { ffn$('.liker_script_options').append('Hide Dislikes'); ffn$('.liker_script_options .hide_dislikes').click(function(){ hide_dislikes(); }); } ffn$('.liker_script_options').append('| '); if(app.options("hide_marked")){ ffn$('.liker_script_options').append('Show Marked'); ffn$('.liker_script_options .show_marked').click(function(){ show_marked(); }); } else { ffn$('.liker_script_options').append('Hide Marked'); ffn$('.liker_script_options .hide_marked').click(function(){ hide_marked(); }); } ffn$('.liker_script_options').append('| '); if(app.options("hide_calibre")){ ffn$('.liker_script_options').append('Show Calibre'); ffn$('.liker_script_options .show_calibre').click(function(){ show_calibre(); }); } else { ffn$('.liker_script_options').append('Hide Calibre'); ffn$('.liker_script_options .hide_calibre').click(function(){ hide_calibre(); }); } // test dislike all if (window.location.hostname === "www.fanfiction.net") { ffn$('.liker_script_options').append('| '); ffn$('.liker_script_options').append('Dislike All Authors'); ffn$('.liker_script_options .dislike_all').click(function(){ dislike_all();return(false);}); } // ffn$('.liker_script_options').append('| '); ffn$('.liker_script_options').append('Manage Site Data'); ffn$('.liker_script_options').after(""); ffn$('.backup_text .backup_data textarea').on("click",function(){ ffn$(this).select(); }); ffn$('.backup_text .save_new_data button').on("click",function(){ var v = ffn$('.backup_text .save_new_data textarea').val(); try { var new_db = JSON.parse(v); } catch(err) { alert("that data is not valid"); return; } localStorage.setItem("FFLikerAA", JSON.stringify(new_db)); document.location = document.location; }); ffn$('.liker_script_options .backupToggle').click(function(){ ffn$(".backup_text").toggle(); ffn$(".backup_text .backup_data textarea").html(JSON.stringify(db)); return false; }); function show_dislikes(){ app.options("hide_dislikes",false); return false; } function hide_dislikes(){ app.options("hide_dislikes",true); return false; } function show_likes(){ app.options("hide_likes",false); return false; } function hide_likes(){ app.options("hide_likes",true); return false; } function show_marked(){ app.options("hide_marked",false); return false; } function hide_marked(){ app.options("hide_marked",true); return false; } function show_calibre(){ app.options("hide_calibre",false); return false; } function hide_calibre(){ app.options("hide_calibre",true); return false; } function dislike_all(){ ffn$("div.z-list:visible").each(function() { // console.log(this); var story = new Story({ namespace: app.namespace, instance: this }); app.collection.push(story); app.author.dislike(story.authorId()); }); }