Greasy Fork is available in English.

FanFictionNavigator

Mark and hide fanfics or authors

// ==UserScript==
// @name        FanFictionNavigator
// @name:ru     FanFictionNavigator
// @license MIT
// @version     80
// @namespace   window
// @description  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     http://www.archiveofourown.org/*
// @include     https://www.archiveofourown.org/*
// @include     https://author.today/*
// @run-at      document-end

// @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
// @grant       GM_addStyle
// @description Mark and hide fanfics or authors
// ==/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_INLIBRARY = 3;

// Authors:
const AUTHOR_LIKED = 0;
const AUTHOR_DISLIKED = 1;

// Communities:
const COMMUNITY_LIKED = 0;
const COMMUNITY_DISLIKED = 1;

// colors. now used for like/dislike/etc links
const COLOR_LIKED = '#C4FFCA';
const COLOR_DISLIKED = '#FCB0B0';
const COLOR_MARKED = '#CCCCCC';
const COLOR_INLIBRARY = '#F1D173';
const COLOR_CLEARED = '#FFF';
const COLOR_FB_CLEAR = '#FFF';


// styles for author/story links; <a> 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_inlibrary {font-weight: bold;}");
GM_addStyle("a.ffn_dislike_community {text-decoration: line-through; font-weight: bold;}");
GM_addStyle("a.ffn_like_community {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_inlibrary:not(a) {background-color:#F1D173 !important;}");
GM_addStyle(".ffn_like_community:not(a) {background-color:#C4FFCA !important;}");
GM_addStyle(".ffn_dislike_community:not(a) {background-color:#FCB0B0 !important;}");



// styles for boxes, they differ between sites

/*
switch(window.location.hostname){
case "www.fanfiction.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_inlibrary {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_inlibrary {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_inlibrary {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_inlibrary {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 patharr = window.location.pathname.split("/");     // moved to main block to save on split functions elsewhere

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:{}, community:{} };

	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" || type == "community") {
			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 {
            if (typeof(db[a.namespace][type]) == 'undefined') {
                db[a.namespace][type] = {};
            }
			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.inlibrary = function(id) {
            a.save("fic",id,FIC_INLIBRARY);
            var story = _.find(a.collection,function(story){
				return story.ficId() == id;
			});
            story.fic = FIC_INLIBRARY;
            story.inlibrary_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.community = {};
		a.community.get = function(id) {
            if (typeof(db[a.namespace].community) == "undefined") {
                db[a.namespace].community = {}
            }
			return db[a.namespace].community[id];
		};

		a.community.like = function(id) {
			a.save("community",id,COMMUNITY_LIKED);

			var community = _.find(a.collection,function(community){
				return community.communityId() == id;
			});

			community.community = COMMUNITY_LIKED;
			community.like_community();
		};

		a.community.dislike = function(id) {
			a.save("community",id,COMMUNITY_DISLIKED);
			var community = _.find(a.collection,function(community){
				return community.communityId() == id;
			});
			community.community = COMMUNITY_DISLIKED;
			community.dislike_community();
		};


		a.community.clear = function(id) {
			a.save("community",id,"clear");
			var community = _.find(a.collection,function(community){
				return community.communityId() == id;
			});
			community.community = '';
			community.clear_community();
		};

	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 =	'<div class="new_like_actions" style="margin:0px 0px 0px 20px; font-size:11px;">'+

								'Story: <a href="" class="like_story"><font color="'+COLOR_LIKED+'">Like</font></a> | '+
								'<a href="" class="dislike_story"><font color="'+COLOR_DISLIKED+'">Dislike</font></a> | '+
    							'<a href="" class="mark_story"><font color="'+COLOR_MARKED+'">Mark</font></a> | '+
    							'<a href="" class="inlibrary_story"><font color="'+COLOR_INLIBRARY+'">InLibrary</font></a> | '+
								'<a href="" class="clear_story" style="color:blue;">Clear</a>'+

								'   Author: <a href="" class="like_author" style="color:blue;">Like</a> | '+
								'<a href="" class="dislike_author" style="color:blue;">Dislike</a> | '+
								'<a href="" class="clear_author" style="color:blue;">Clear</a>'+

							'</div>';
			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 .inlibrary_story').click(function(){ app.fic.inlibrary(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_fic");
				this.$fic.addClass("ffn_dislike_fic");
                break;
            case FIC_MARKED:
    			_this.addClass("ffn_mark");
				this.$fic.addClass("ffn_mark");
                break;
            case FIC_INLIBRARY:
    			_this.addClass("ffn_inlibrary");
				this.$fic.addClass("ffn_inlibrary");
                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");
        },
        inlibrary_story: function(){
            this.clear_story();
            _this.addClass("ffn_inlibrary");
            this.$fic.addClass("ffn_inlibrary");
        },
        clear_story: function(){
            _this.removeClass("ffn_like_fic ffn_dislike_fic ffn_mark ffn_inlibrary");
            this.$fic.removeClass("ffn_like_fic ffn_dislike_fic ffn_mark ffn_inlibrary");

        },
        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 patharr[2];
            } else {
        		return this.$author.attr('href').split('/')[2];
            }
        },
		ficId: function() {
            if (this.$fic.length === 0) {
                return patharr[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 (!patharr[1].match("^s$|^u$")) _this.hide(); // do not hide fic on author pages (to clearly see how many fics you like and dislike) and on reading pages
		}
	};

//

	a["archiveofourown.org"] = {
    	$author: _this.find('a[href^="/users/"]:first'),
		$fic: _this.find('a[href^="/works/"]:first'),
		authorId: function() {
			// old: return this.$author.attr('href').split('/')[2];
            // Contributed by Vannis:
            if (this.$author.length === 0) {
                return 0;
            } else {
                return this.$author.attr('href').split('/')[2];
            }
		},
        ficId: function() {
            if (this.$fic.length === 0) {
                return patharr[2];
            } else {
                return this.$fic.attr('href').split('/')[2];
            }
		},
        hide:function(){
            if (patharr[1] !== "users" &&    // do not hide fic on author pages (to clearly see how many fics you like and dislike)
                !/(collections\/[^\/]+\/)?works\/\d+/.test(window.location.pathname)) { // do not hide fic header on reading pages)
                _this.hide();
            }
        }
	};

//

    a["ficbook.net"] = {
        $author: _this.find('a[href^="/authors"]:first'),
        $fic: _this.find('a[href^="/readfic"]:first'),
		authorId: function() {
			return this.$author.attr('href').split('/')[2];
		},
        ficId: function() {
            if (this.$fic.length === 0) {
                return patharr[2].replace(/([0123456789abcdef-]+).*?$/,"$1");
            } else {
                return this.$fic.attr('href').split('/')[2].replace(/([0123456789abcdef-]+).*?$/,"$1");
            }
		},
        hide:function(){
            if (patharr[1]==="popular-fanfics"){
                _this.parent().hide();
                return;
            }
            if (patharr[1]!=="readfic" && patharr[2]!=="favourites") {
                _this.hide();
                return;
            }
        }
	};

//
    a["author.today"] = {
        $author: _this.find('a[href^="/u"]:first'),
        $fic: _this.find('a[href^="/work/"]:first'),
        authorId: function() {
            if (typeof this.$author.attr('href') === "undefined") {
                return patharr[2];
            } else {
        		return this.$author.attr('href').split('/')[2];
            }
        },
		ficId: function() {
            //console.log(this.$fic);
            if (typeof this.$fic.attr('href') === "undefined" ||
               patharr[2].match(/^\d+$/)) {
                return patharr[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 (!patharr[1].match("^work$|^u$")) {
                _this.parent().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  (removed to show new fics of liked authors)
       (app.options("hide_marked") === true && b.fic === FIC_MARKED) ||
       (app.options("hide_inlibrary") === true && b.fic === FIC_INLIBRARY)){
//		if(b.fic !== true && b.author) { // for liked fics of disliked authors
			b.hide();
//		}
	}
	return b;
};

// Community

var Community = function(optionsin) {

	var c = {};
	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);

	c["default"] = {
		template: function() {
			var template =	'<div class="new_like_actions" style="margin:0px 0px 0px 20px; font-size:11px;">'+
								'Community: <a href="" class="like_community"><font color="'+COLOR_LIKED+'">Like</font></a> | '+
								'<a href="" class="dislike_community"><font color="'+COLOR_DISLIKED+'">Dislike</font></a> | '+
								'<a href="" class="clear_community" style="color:blue;">Clear</a>'+
							'</div>';
			return template;
		},
		addActions: function() {
			var instance = this;
			_this.append(this.template());

			_this.find('.new_like_actions .like_community').click(function(){ app.community.like(instance.communityId()); return false; });
			_this.find('.new_like_actions .dislike_community').click(function(){ app.community.dislike(instance.communityId()); return false; });
			_this.find('.new_like_actions .clear_community').click(function(){ app.community.clear(instance.communityId()); return false; });
		},
		hide: function() {
			_this.hide();
		},
		set_community: function(){
            switch(this.community){
            case COMMUNITY_LIKED:
				_this.addClass("ffn_like_community");
				this.$community.addClass("ffn_like_community");
                break;
			case COMMUNITY_DISLIKED:
    			_this.addClass("ffn_dislike_community");
				this.$community.addClass("ffn_dislike_community");
                break;
			}
		},
     	like_community: function(){
            this.clear_community();
            _this.addClass("ffn_like_community");
            this.$community.addClass("ffn_like_community");
		},
        dislike_community: function(){
            this.clear_community();
            _this.addClass("ffn_dislike_community");
            this.$community.addClass("ffn_dislike_community");
        },
        clear_community: function(){
            _this.removeClass("ffn_like_community ffn_dislike_community");
            this.$community.removeClass("ffn_like_community ffn_dislike_community");

        }
    };

// Specific sites overrides

    c["www.fanfiction.net"] = {
        $community: _this.find('a[href^="/community"]:first'),
		communityId: function() {
            if (typeof this.$community.attr('href') === "undefined") {
                return patharr[3];
            } else {
                return this.$community.attr('href').split('/')[3];
            }
        },
    	hide: function() {
            // needrework for community
            // do not hide story header on reading page and story block on author page
            if (!patharr[1].match("^s$|^u$")) _this.hide(); // do not hide fic on author pages (to clearly see how many fics you like and dislike) and on reading pages
		}
	};

	var d = ffn$.extend({}, c["default"], c[options.namespace]);
	d.community = app.community.get(d.communityId());

    // do not show liker links if communityId is undefined
    if (d.communityId() !== 0) {
    	d.addActions();
    }
	d.set_community();

	//hide
	if((app.options("hide_dislikes") === true && d.community === COMMUNITY_DISLIKED) ||
       (app.options("hide_likes") === true && d.community === COMMUNITY_LIKED)){
			d.hide();
//		}
	}
	return d;
};


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":
        // small tweak to allow text selection
        GM_addStyle("* {user-select:text !important;}");
        // adding hotkeys
        // added toggle option, suggested by Vannius
        if (app.options("enable_list_hotkeys")) {
            document.addEventListener('keydown', function(e){
                if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
                    switch (e.keyCode){
                        case 37:
                            var Prev = ffn$("a:contains('« Prev')");
                            if (typeof(Prev[0])!=='undefined') {Prev[0].click();}
                            break;
                        case 39:
                            var Next = ffn$("a:contains('Next »')");
                            if (typeof(Next[0])!=='undefined') {Next[0].click();}
                            break;
                    }
                }
            }, false);
        }
        if (app.options("enable_read_hotkeys")) {
            document.addEventListener('keydown', function(e){
                if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
                    switch (e.keyCode){
                        case 37:
                            var Prev = ffn$("button:contains('< Prev')");
                            if (typeof(Prev[0])!=='undefined') {Prev.click();}
                            break;
                        case 39:
                            var Next = ffn$("button:contains('Next >')");
                            if (typeof(Next[0])!=='undefined') {Next.click();}
                            break;
                    }
                }
            }, false);
        }
        // links in list
        if (patharr[1] == "communities") {
            ffn$(".z-list").each(function(){
                var community = new Community({ namespace: app.namespace, instance: this });
                app.collection.push(community);
            });

        } else {
            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(
            '<div class="liker_script_options" style="padding:5px; border:1px solid #333399; margin-bottom:5px; background:#D8D8FF;">'+
            '<b>Liker Options:</b> '+
            '</div>'
        );
        break;
    case "archiveofourown.org":
        // adding hotkeys
        if (app.options("enable_list_hotkeys")) {
            document.addEventListener('keydown', function(e){
                if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
                    switch (e.keyCode){
                        case 37:
                            var Prev = ffn$("a:contains('← Previous')");
                            if (typeof(Prev[0])!=='undefined') {Prev[0].click();}
                            break;
                        case 39:
                            var Next = ffn$("a:contains('Next →')");
                            if (typeof(Next[0])!=='undefined') {Next[0].click();}
                            break;
                    }
                }
            }, false);
        }
        if (app.options("enable_read_hotkeys")) {
            document.addEventListener('keydown', function(e){
                if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
                    switch (e.keyCode){
                        case 37:
                            var Prev = ffn$("a:contains('←Previous Chapter')");
                            if (typeof(Prev[0])!=='undefined') {Prev[0].click();}
                            break;
                        case 39:
                            var Next = ffn$("a:contains('Next Chapter →')");
                            if (typeof(Next[0])!=='undefined') {Next[0].click();}
                            break;
                    }
                }
            }, false);
        }
        // in lists
        // old: ffn$("ol.work.index.group > li").each(function(){
        // contribution by Vannius from greasyfork site
        ffn$(".blurb").each(function(){
            var story = new Story({ namespace: app.namespace, instance: this });
            app.collection.push(story);
        });
        // on reading page
        ffn$("div.preface.group").each(function(){
            var story = new Story({ namespace: app.namespace, instance: this });
            app.collection.push(story);
        });
        // hide/show options
        ffn$('div.navigation.actions.module, div.primary.header.module').after(
            '<div class="liker_script_options" style="padding:5px; border:1px solid #333399; margin-bottom:5px; background:#D8D8FF;">'+
            '<b>Liker Options:</b> '+
            '</div>'
        );
        break;
    case "ficbook.net":
        // on reading page
        if (patharr[1]==="readfic"){
            //ffn$("div.row.hat-row > ul.list-unstyled").each(function() {
             //ffn$("section.fanfiction-hat.container-fluid").each(function() {
            ffn$("div.fanfic-hat-body").each(function() {
//                console.log(this);
                var story = new Story({
                    namespace: app.namespace,
                    instance: this
                });
                app.collection.push(story);
            });
            if (app.options("enable_read_hotkeys")) {
                document.addEventListener('keydown', function(e){
                    var textcontent;
                    if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
                        switch (e.keyCode){
                            case 37:
                                var Prev = ffn$("a.btn-back");
                                if (Prev.length>0) window.location.href = Prev[0];
                                break;
                            case 39:
                                var Next = ffn$("a.btn-next");
                                if (Next.length>0) window.location.href = Next[0];
                                break;
                        }
                    }
                    //   Ctrl+ up/dn hotkeys
                    if (e.ctrlKey && !e.altKey && !e.shiftKey) {
                        switch (e.keyCode){
                            case 38:
                                console.log("Ctrl+up")
                                textcontent = document.getElementById("content");
                                var txt = textcontent.outerHTML;
                                txt = txt.replace(/&nbsp;/g," ");
                                txt = txt.replace(/(<div[^>]*id=\"content\"[^>]*>)[ ]*/m,"$1    ");
                                txt = txt.replace(/[\r\n][\ \r\n\t]+/gm,"\r\n");
                                txt = txt.replace(/^((<[^>]+>)*)[ \t]+/gm,"$1");
                                txt = txt.replace(/\n[ \t]*/gm,"\n    ");
                                txt = txt.replace("- ","— ");
                                textcontent.outerHTML = txt;
                                break;
                            /* removed since we don't have a way to completely restore formatting after deleting so much junk
                            case 40:
                                textcontent = document.getElementById("content");
                                textcontent.outerHTML = textcontent.outerHTML.replace(/<br n=\"2\">/gi,"<br>\n<br>");
                                ffn$("#undo_typograf").click();
                                break;
                            */
                        }
                    }
                    if (!e.ctrlKey && !e.altKey && e.shiftKey) {
                        switch (e.keyCode){
                            case 38:
                                ffn$("#do_typograf").click();
                                break;
                            case 40:
                                ffn$("#undo_typograf").click();
                                break;
                        }
                    }
                }, false);
            }
        }
        // in lists
        if (/^find-fanfics[\-0-9]*/.test(patharr[1]) ||
            (/^popular-fanfics[\-0-9]*/.test(patharr[1])) ||
            (patharr[1]==="collections") ||
            (patharr[1]==="fanfiction") ||
            (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$("article.fanfic-inline").each(function() {
            //ffn$("div.js-toggle-description").each(function() {
                var story = new Story({
                    namespace: app.namespace,
                    instance: this
                });
                app.collection.push(story);
            });
        }
        // button for quickly reading/unreading story
        if (/^find-fanfics[\-0-9]*/.test(patharr[1]) ||
            (/^popular-fanfics[\-0-9]*/.test(patharr[1])) ||
            (patharr[2]==="favourites") ||
            (patharr[1]==="authors" && patharr[3]==="profile" && patharr[4]==="works")) {
            ffn$("article.fanfic-inline").each(function() { // need more wide block to detect if the fic is read/unread to construct correct button
                var _this = ffn$(this);
                //console.log(_this);
                var ficId = _this.find('a[href^="/readfic"]:first').attr('href').split('/')[2].replace(/([0123456789abcdef-]+).*?$/,"$1");
                var readButton;
                console.log(_this.find(".read-notification").length);
                if (_this.find(".read-notification").length!==0 && _this.find(".new-content").length===0) {   // button should be 'read' if there's a notification and there's no indication of new chapters (for consistency with readfic pages)
                    _this.find(".new_like_actions").append('<button type="button" class="btn btn-success jsVueComponent read"><svg class="ic_checkbox-checked2"><use href="/assets/icons/icons-sprite20.svg#ic_checkbox-checked2"></use></svg> Прочитано </button>');
                    readButton = _this.find("button.btn-success");
                } else {
                    _this.find(".new_like_actions").append('<button type="button" class="btn btn-default jsVueComponent notread"><svg class="ic_checkbox-unchecked2"><use href="/assets/icons/icons-sprite20.svg#ic_checkbox-unchecked2"></use></svg> Прочитано </button>');
                    readButton = _this.find("button.btn-default");
                };
                readButton.click(function(e){
                    e.preventDefault();
                    if (readButton.hasClass("notread")){
                        fanfiction_net_read(ficId);
                        readButton.removeClass("notread btn-default ");
                        readButton.addClass("read btn-success");
                        readButton.html('<svg class="ic_checkbox-checked2"><use href="/assets/icons/icons-sprite20.svg#ic_checkbox-checked2"></use></svg> Прочитано ');
                    } else {
                        fanfiction_net_unread(ficId);
                        readButton.removeClass("read btn-success");
                        readButton.addClass("notread btn-default ");
                        readButton.html('<svg class="ic_checkbox-unchecked2"><use href="/assets/icons/icons-sprite20.svg#ic_checkbox-unchecked2"></use></svg> Прочитано ');
                    }
                });
            });
        }
//        */
        // add hotkeys
        if (app.options("enable_list_hotkeys")) {
            document.addEventListener('keydown', function(e){
                if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
                    switch (e.keyCode){
                        case 37:
                            var Prev = ffn$("a[aria-label='Предыдущая']");
                            if (Prev.length>0) Prev[0].click();
                            break;
                        case 39:
                            var Next = ffn$("a[aria-label='Следующая']");
                            if (Next.length>0) Next[0].click();
                            break;
                    }
                }
            }, false);
        }

        // hide/show options
        ffn$('section.content-section').after(
            '<div class="liker_script_options" style="padding:5px; border:1px solid #333399; margin-bottom:5px; background:#D8D8FF;">'+
            '<b>Liker Options:</b> '+
            '</div>'
        );
        break;
    case "author.today":
        //if (window.location.pathname.match("^/work/[0123456789]+$")){
        //    console.log("TRUE");
        //};
        GM_addStyle("* {user-select:text !important;}");
        // adding hotkeys
        // added toggle option, suggested by Vannius
        if (app.options("enable_list_hotkeys")) {
            document.addEventListener('keydown', function(e){
                if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
                    switch (e.keyCode){
                        case 37:
                            var Prev = ffn$("a:contains('« Prev')");
                            if (typeof(Prev[0])!=='undefined') {Prev[0].click();}
                            break;
                        case 39:
                            var Next = ffn$("a:contains('Next »')");
                            if (typeof(Next[0])!=='undefined') {Next[0].click();}
                            break;
                    }
                }
            }, false);
        }
        if (app.options("enable_read_hotkeys")) {
            document.addEventListener('keydown', function(e){
                if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
                    switch (e.keyCode){
                        case 37:
                            var Prev = ffn$("button:contains('< Prev')");
                            if (typeof(Prev[0])!=='undefined') {Prev.click();}
                            break;
                        case 39:
                            var Next = ffn$("button:contains('Next >')");
                            if (typeof(Next[0])!=='undefined') {Next.click();}
                            break;
                    }
                }
            }, false);
        }
        // links on fic page
        if (patharr[1] === "work") {
            ffn$("div.book-row-content").each(function(){
                var story = new Story({ namespace: app.namespace, instance: this });
                app.collection.push(story);
            });
            ffn$("div.book-meta-panel").each(function(){
                var story = new Story({ namespace: app.namespace, instance: this });
                app.collection.push(story);
            });
        }
        // links on author page
        if (patharr[1] === "u" && patharr[3] === "works") {
            ffn$("div.book-row-content").each(function(){
                var story = new Story({ namespace: app.namespace, instance: this });
                app.collection.push(story);
            });
        }
        // Options panel
        ffn$('div#search-results').after(
            '<div class="liker_script_options" style="padding:5px; border:1px solid #333399; margin-bottom:5px; background:#D8D8FF;">'+
            '<b>Liker Options:</b> '+
            '</div>'
        );
        ffn$('div#profile-work-search-results').after(
            '<div class="liker_script_options" style="padding:5px; border:1px solid #333399; margin-bottom:5px; background:#D8D8FF;">'+
            '<b>Liker Options:</b> '+
            '</div>'
        );
        break;

}

//	OPTIONS
//	-  show/hide global options
//

if(app.options("hide_likes")){
	ffn$('.liker_script_options').append('<a href="" class="show_likes" style="color:blue">Show Liked (Fics)</a>');
	ffn$('.liker_script_options .show_likes').click(function(){ show_likes();  });
} else {
	ffn$('.liker_script_options').append('<a href="" class="hide_likes" style="color:blue">Hide Liked (Fics)</a>');
	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('<a href="" class="show_dislikes" style="color:blue">Show Disliked (all)</a>');
	ffn$('.liker_script_options .show_dislikes').click(function(){ show_dislikes();  });
} else {
	ffn$('.liker_script_options').append('<a href="" class="hide_dislikes" style="color:blue">Hide Disliked (all)</a>');
	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('<a href="" class="show_marked" style="color:blue">Show Marked</a>');
	ffn$('.liker_script_options .show_marked').click(function(){ show_marked();  });
} else {
	ffn$('.liker_script_options').append('<a href="" class="hide_marked" style="color:blue">Hide Marked</a>');
	ffn$('.liker_script_options .hide_marked').click(function(){ hide_marked(); });
}
ffn$('.liker_script_options').append('| ');

if(app.options("hide_inlibrary")){
	ffn$('.liker_script_options').append('<a href="" class="show_inlibrary" style="color:blue">Show InLibrary</a>');
	ffn$('.liker_script_options .show_inlibrary').click(function(){ show_inlibrary(); });
} else {
	ffn$('.liker_script_options').append('<a href="" class="hide_inlibrary" style="color:blue">Hide InLibrary</a>');
	ffn$('.liker_script_options .hide_inlibrary').click(function(){ hide_inlibrary(); });
}

// specific links for sites

// dislike all authors, currently on ffnet only (enter some slash community and blacklist everyone there, saves time when browsing HP fanfics later)
if (window.location.hostname === "www.fanfiction.net") {
    ffn$('.liker_script_options').append('| ');
    ffn$('.liker_script_options').append('<a href="" class="dislike_all" style="color:blue">Dislike All Authors</a>');
	ffn$('.liker_script_options .dislike_all').click(function(e){ e.preventDefault(); dislike_all();});
    if (patharr[1] === "s") {
        ffn$('.new_like_actions').append('| Actions: <a href="" id="ffn_open_all_chapters" style="color:blue">Open all chapters</a>');
        ffn$('#ffn_open_all_chapters').click(function(e){ e.preventDefault(); fanfiction_net_open_all();});
    }
}

if (window.location.hostname === "ficbook.net") {
switch(patharr[1]){
        case "find-fanfics":
            // revert read status for all visible fics (circumvent ficbook bug where alreadн read fics still appear in search)
            ffn$('.liker_script_options').append('| Actions: ');
            ffn$('.liker_script_options').append('<a href="" class="ffn_ficbook_invert_read" style="color:blue">Invert Read status</a>');
            ffn$('.liker_script_options .ffn_ficbook_invert_read').click(function(e){ e.preventDefault(); ficbook_invertread(); return false; });
            break
        case "readfic":
            // specific request - download fb2 and like fic in one click
            ffn$('.new_like_actions').append('| Actions: <a href="" class="ffn_ficbook_fb2_and_like" style="color:blue">FB2+Like+Read</a>');
            ffn$('.new_like_actions .ffn_ficbook_fb2_and_like').click(function(e){ e.preventDefault(); ficbook_fb2andlike(); return false; });
            ffn$('.new_like_actions').append(' <a href="" id="ficbook_open_all_chapters" style="color:blue">Open all chapters</a>');
            ffn$('#ficbook_open_all_chapters').click(function(e){ e.preventDefault(); ficbooknet_open_all();});
            break
    }
    //
}

function ficbook_fb2andlike(){
    var a= ffn$('a[href^="/fanfic_download/fb2/"]');
    ffn$('.like_story').click();
    document.location = a.attr("href");
    ffn$('.js-mark-readed').not('.btn-success').click();
    return false;
}


ffn$('.liker_script_options').append('| <a href="" id="ffn_OptionsToggle" style="color:blue">FFN Options</a>');
ffn$('.liker_script_options').after(
    "<div id='ffn_options_block' style='display:none;'>" +
    "<input type='checkbox' id='ffn_checkbox_hide_likes'> Hide Likes (fics only, not authors)</br>" +
    "<input type='checkbox' id='ffn_checkbox_hide_dislikes'> Hide Dislikes (both fics and authors)</br>" +
    "<input type='checkbox' id='ffn_checkbox_hide_marked'> Hide Marked</br>" +
    "<input type='checkbox' id='ffn_checkbox_hide_inlibrary'> Hide InLibrary</br>" +
    "</br>" +
    "<input type='checkbox' id='ffn_checkbox_enable_list_hotkeys'> Enable hotkeys on lists pages (Left/Right for next/prev page)</br>" +
    "<input type='checkbox' id='ffn_checkbox_enable_read_hotkeys'> Enable hotkeys on reading pages (Left/Right for next/prev chapter)</br>" +
    "</br>" +
    "<button id='ffn_options_button_save'>Save options and reload page</button></br>" +
    "</br>" +
    "</br>" +
    "Export data: <button id='ffn_export_to_file'>Download text file</button></br>" +
    "Import data: <input id='ffn_import_file_box' type='file' accept='text/plain'>" + "<button id='ffn_import_file_button'>Import</button>" +

// Old import/export (slow when db is big)
/*    "<a href='' class='backupToggle' style='color:blue'>Manage Site Data</a>'" +
    "    <div class='backup_text' style='display:none'>" +
    "    <table><tr>" +
    "    <td class='backup_data' valign='top'>" +
    "    	<b>Backup data</b><br />" +
    "    	Copy this text some where safe<br />" +
    "    	<textarea class=''></textarea>" +
    "    </td>" +
    "    <td>&nbsp;&nbsp;</td>" +
    "    <td class='save_new_data' valign='top'>" +
    "    	<b>Upload new Data</b><br>" +
    "    	Upload data you had previously saved<br />" +
    "    	<textarea></textarea><br >" +
    "    	<button class='new_data_save'>save</button>" +
    "    </td></tr></table>" +
    "    </div>" +*/



    "</div>"

);

ffn$('#ffn_import_file_button').click(function(){
    var selectedFile = document.getElementById('ffn_import_file_box').files.item(0);
    if (selectedFile === null) return;
    fileToText(selectedFile, (text) => {
        var new_db;
        try {
            new_db = JSON.parse(text);
        } catch(err) {
            alert("JSON data in file is invalid");
            return;
        }
        localStorage.setItem("FFLikerAA", JSON.stringify(new_db));
        document.location = document.location;
    });
});

function fileToText(file, callback) {
  const reader = new FileReader();
  reader.readAsText(file);
  reader.onload = () => {
    callback(reader.result);
  };
}

ffn$('#ffn_OptionsToggle').click(function(){
    ffn$("#ffn_options_block").toggle();
    ffn$("#ffn_checkbox_hide_likes").prop("checked",app.options("hide_likes"));
    ffn$("#ffn_checkbox_hide_dislikes").prop("checked",app.options("hide_dislikes"));
    ffn$("#ffn_checkbox_hide_marked").prop("checked",app.options("hide_marked"));
    ffn$("#ffn_checkbox_hide_inlibrary").prop("checked",app.options("hide_inlibrary"));
    ffn$("#ffn_checkbox_enable_list_hotkeys").prop("checked",app.options("enable_list_hotkeys"));
    ffn$("#ffn_checkbox_enable_read_hotkeys").prop("checked",app.options("enable_read_hotkeys"));
    return false;
});
ffn$('#ffn_options_button_save').click(function(){
    app.options("hide_likes", ffn$("#ffn_checkbox_hide_likes").prop("checked"));
    app.options("hide_dislikes", ffn$("#ffn_checkbox_hide_dislikes").prop("checked"));
    app.options("hide_marked", ffn$("#ffn_checkbox_hide_marked").prop("checked"));
    app.options("hide_inlibrary", ffn$("#ffn_checkbox_hide_inlibrary").prop("checked"));
    app.options("enable_list_hotkeys", ffn$("#ffn_checkbox_enable_list_hotkeys").prop("checked"));
    app.options("enable_read_hotkeys", ffn$("#ffn_checkbox_enable_read_hotkeys").prop("checked"));
    location.reload();
    return false;
});

// import/export db data

/* old import/export, with textboxes
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();
    var new_db;
    try {
        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$('.backupToggle').click(function(){
    ffn$(".backup_text").toggle();
    ffn$(".backup_text .backup_data textarea").html(JSON.stringify(db));
    return false;
});
*/

//ffn$('.liker_script_options').append('| <a href="" id="testlink" style="color:blue">test' + '</a>');

ffn$('#ffn_export_to_file').click(function(){
    var curdate = new Date();
    var year = curdate.getFullYear();
    var month = curdate.getMonth() + 1;
    month = month<10? "0"+month : month;
    var day = curdate.getDate();
    day = day<10? "0"+day : day;
    export_file(JSON.stringify(db,null," "),"FFN_" + window.location.host + "_" + year + "-" + month + "-" + day + ".txt", "text/plain");
    return false;
});

function export_file(content, fileName, mime) {
    const blob = new Blob([content], {
      type: mime
    });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.style = "display: none";
    a.href = url;
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
    setTimeout(function(){
        document.body.removeChild(a);
        window.URL.revokeObjectURL(url);
    }, 100);
}

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_inlibrary(){
	app.options("hide_inlibrary",false);
	return false;
}

function hide_inlibrary(){
	app.options("hide_inlibrary",true);
	return false;
}

function dislike_all(){
    ffn$("div.z-list:visible").each(function() {
        var story = new Story({
            namespace: app.namespace,
            instance: this
        });
        app.collection.push(story);
        app.author.dislike(story.authorId());
    });
}

function ficbook_invertread(){
    ffn$("button.btn.btn-primary.btn-success.js-mark-readed:visible").each(function() {
        this.click();
    })
    ffn$("button.btn.btn-primary.btn-default.js-mark-readed:visible").each(function() {
        this.click();
    })
}

function ficbooknet_open_all(){
    // get fic id
    var ficid = patharr[2];
    // get links to chapters
    var chapters = ffn$.find("ul.list-of-fanfic-parts > li > a");
    var chapnum = chapters.length - 1;
    if (confirm("Are you sure? This will open " + chapnum + " tabs\n(Delay of 2 seconds for each tab)") === true) {
        for (let i=0; i<chapnum;i++) {
            setTimeout(function(){
                window.open(chapters[i].href,"_blank");
            },i*2000);
        }
    }

}



function fanfictionnet_dislike_all(){
    ffn$("div.z-list:visible").each(function() {
        var story = new Story({
            namespace: app.namespace,
            instance: this
        });
        app.collection.push(story);
        app.author.dislike(story.authorId());
    });
}


function fanfiction_net_open_all(){
    // get fic id
    var ficid = patharr[2];
    // get number of chapters from header
    var chapnum = ffn$.find("div#profile_top")[0].textContent.match(/.*Chapters: (\d+) - .*/)[1];
    if (chapnum === null) {
        console.log("FFN: Could not find chapters count")
        return false;
    }
    chapnum = parseInt(chapnum);
    if (confirm("Are you sure? This will open " + chapnum + " tabs") === true) {
        var url;
        for (let i=1; i < (chapnum+1); i++) {
            setTimeout(function(){
                url = "https://www.fanfiction.net/s/" + ficid + "/" + i + "/";
                window.open(url,"_blank");
            }, i * 500);
        }
    }
}

function fanfiction_net_read(ff_id){
    console.log(ff_id);
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "https://ficbook.net/fanfic_read/read", true);
    xhr.setRequestHeader("Content-Type"," application/x-www-form-urlencoded; charset=UTF-8");
    xhr.send("fanfic_id=" + ff_id);
}
function fanfiction_net_unread(ff_id){
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "https://ficbook.net/fanfic_read/unread", true);
    xhr.setRequestHeader("Content-Type"," application/x-www-form-urlencoded; charset=UTF-8");
    xhr.send("fanfic_id=" + ff_id);
}