FanFictionNavigator

Mark and hide fanfics or authors

当前为 2021-03-12 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name FanFictionNavigator
  3. // @name:ru FanFictionNavigator
  4. // @namespace window
  5. // @description Mark and hide fanfics or authors
  6. // @description:ru Выделяет цветом/скрывает фанфики или авторов
  7. // @include https://ficbook.net/*
  8. // @include https://www.fanfiction.net/*
  9. // @include https://archiveofourown.org/*
  10. // @include http://archiveofourown.org/*
  11. // @include https://tbooklist.org/*
  12. // @run-at document-end
  13.  
  14. // @require https://code.jquery.com/jquery-1.7.2.min.js
  15. // @require https://greasyfork.org/scripts/17419-underscore-js-1-8-3/code/Underscorejs%20183.js?version=109803
  16. // @version 62
  17. // @grant GM_addStyle
  18. // @description Mark and hide fanfics or authors
  19. // ==/UserScript==
  20.  
  21. // Based on https://chrome.google.com/webstore/detail/fanfiction-organizer/adlnghnicfngjnofbljedmclmdoepcbe
  22. // by Stefan Hayden
  23.  
  24. // Fics:
  25. const FIC_LIKED = 0;
  26. const FIC_DISLIKED = 1;
  27. const FIC_MARKED = 2;
  28. const FIC_INLIBRARY = 3;
  29.  
  30. // Authors:
  31. const AUTHOR_LIKED = 0;
  32. const AUTHOR_DISLIKED = 1;
  33.  
  34. // Communities:
  35. const COMMUNITY_LIKED = 0;
  36. const COMMUNITY_DISLIKED = 1;
  37.  
  38. // colors. now used for like/dislike/etc links
  39. const COLOR_LIKED = '#C4FFCA';
  40. const COLOR_DISLIKED = '#FCB0B0';
  41. const COLOR_MARKED = '#CCCCCC';
  42. const COLOR_INLIBRARY = '#F1D173';
  43. const COLOR_CLEARED = '#FFF';
  44. const COLOR_FB_CLEAR = '#FFF';
  45.  
  46.  
  47. // styles for author/story links; <a> links should have only one of these so order doesn't matter
  48. GM_addStyle("a.ffn_dislike_fic {text-decoration: line-through; font-weight: bold;}");
  49. GM_addStyle("a.ffn_like_fic {font-weight: bold;} ");
  50. GM_addStyle("a.ffn_dislike_author {text-decoration: line-through; font-weight: bold;}");
  51. GM_addStyle("a.ffn_like_author {font-weight: bold;} ");
  52. GM_addStyle("a.ffn_mark {font-weight: bold;}");
  53. GM_addStyle("a.ffn_inlibrary {font-weight: bold;}");
  54. GM_addStyle("a.ffn_dislike_community {text-decoration: line-through; font-weight: bold;}");
  55. GM_addStyle("a.ffn_like_community {font-weight: bold;} ");
  56.  
  57. // styles for box background; fic style should overwrite author style
  58. GM_addStyle(".ffn_like_author:not(a) {background-color:#C4FFCA !important;}");
  59. GM_addStyle(".ffn_dislike_author:not(a) {background-color:#FCB0B0 !important;}");
  60. GM_addStyle(".ffn_like_fic:not(a) {background-color:#C4FFCA !important;}");
  61. GM_addStyle(".ffn_dislike_fic:not(a) {background-color:#FCB0B0 !important;}");
  62. GM_addStyle(".ffn_mark:not(a) {background-color:#CCCCCC !important;}");
  63. GM_addStyle(".ffn_inlibrary:not(a) {background-color:#F1D173 !important;}");
  64. GM_addStyle(".ffn_like_community:not(a) {background-color:#C4FFCA !important;}");
  65. GM_addStyle(".ffn_dislike_community:not(a) {background-color:#FCB0B0 !important;}");
  66.  
  67.  
  68.  
  69. // styles for boxes, they differ between sites
  70.  
  71. /*
  72. switch(window.location.hostname){
  73. case "www.fanfiction.net":
  74. case "tbooklist.org":
  75. GM_addStyle("div.ffn_dislike {background-color:#FCB0B0 !important;}");
  76. GM_addStyle("div.ffn_like {background-color:#C4FFCA !important;}");
  77. GM_addStyle("div.ffn_mark {background-color:#CCCCCC !important;}");
  78. GM_addStyle("div.ffn_inlibrary {background-color:#F1D173 !important;}");
  79. break
  80. case "archiveofourown.org":
  81. GM_addStyle(".ffn_dislike {background-color:#FCB0B0 !important;}");
  82. GM_addStyle(".ffn_like {background-color:#C4FFCA !important;}");
  83. GM_addStyle(".ffn_mark {background-color:#CCCCCC !important;}");
  84. GM_addStyle(".ffn_inlibrary {background-color:#F1D173 !important;}");
  85. break
  86. case "ficbook.net":
  87. GM_addStyle("div.ffn_dislike {background-color:#FCB0B0 !important;}");
  88. GM_addStyle("div.ffn_like {background-color:#C4FFCA !important;}");
  89. GM_addStyle("div.ffn_mark {background-color:#CCCCCC !important;}");
  90. GM_addStyle("div.ffn_inlibrary {background-color:#F1D173 !important;}");
  91. GM_addStyle("ul.ffn_dislike {background-color:#FCB0B0 !important;}");
  92. GM_addStyle("ul.ffn_like {background-color:#C4FFCA !important;}");
  93. GM_addStyle("ul.ffn_mark {background-color:#CCCCCC !important;}");
  94. GM_addStyle("ul.ffn_inlibrary {background-color:#F1D173 !important;}");
  95. break
  96. }
  97. */
  98.  
  99. // prevent conflicts with websites' jQuery version
  100. this.ffn$ = this.jQuery = jQuery.noConflict(true);
  101.  
  102.  
  103. var db = JSON.parse(localStorage.getItem("FFLikerAA") || '{}');
  104. db.options = db.options || {};
  105. db.version = db.version || '0.2';
  106.  
  107.  
  108. //
  109. // APP
  110. //
  111.  
  112. // Main
  113. var patharr = window.location.pathname.split("/"); // moved to main block to save on split functions elsewhere
  114.  
  115. var Application = function Application(optionsin) {
  116. var a = {};
  117. var options = optionsin || {};
  118.  
  119. if(!options.namespace) { throw new Error("namespace is required"); }
  120. if(!options.db) { throw new Error("database object is required"); }
  121.  
  122. a.namespace = options.namespace;
  123. var db = options.db;
  124. db[a.namespace] = db[a.namespace] || { fic: {}, author:{}, community:{} };
  125.  
  126. a.collection = [];
  127.  
  128. a.color = {
  129. link_default: ffn$("ol.work.index.group > li:first a:first").css("color"),
  130. like_link:'',
  131. like_background:'',
  132. dislike_link:'',
  133. dislike_background:'',
  134. };
  135.  
  136. a.save = function(type,id,value){
  137. if(type == "fic" || type == "author" || type == "community") {
  138. a.saveNameSpaced(type,id,value);
  139. } else {
  140. if(value === "clear") {
  141. delete db[type][id];
  142. } else {
  143. db[type][id] = value;
  144. }
  145. localStorage.setItem("FFLikerAA", JSON.stringify(db));
  146. }
  147. };
  148.  
  149. a.saveNameSpaced = function(type,id,value){
  150. if(value === "clear") {
  151. delete db[a.namespace][type][id];
  152. } else {
  153. if (typeof(db[a.namespace][type]) == 'undefined') {
  154. db[a.namespace][type] = {};
  155. }
  156. db[a.namespace][type][id] = value;
  157. }
  158. localStorage.setItem("FFLikerAA", JSON.stringify(db));
  159. };
  160.  
  161.  
  162. a.author = {};
  163.  
  164. a.author.get = function(id){
  165. return db[a.namespace].author[id];
  166. };
  167.  
  168. a.author.like = function(id) {
  169. a.save("author",id,AUTHOR_LIKED);
  170.  
  171. _.each(a.author.getFics(id), function(story){
  172. story.author = AUTHOR_LIKED;
  173. story.like_author();
  174. });
  175. };
  176.  
  177. a.author.dislike = function(id) {
  178. a.save("author",id,AUTHOR_DISLIKED);
  179. //ga('set', 'metric3', 1);
  180.  
  181. _.each(a.author.getFics(id), function(story){
  182. story.author = AUTHOR_DISLIKED;
  183. story.dislike_author();
  184. });
  185. };
  186.  
  187. a.author.clear = function(id) {
  188. a.save("author",id,"clear");
  189.  
  190. _.each(a.author.getFics(id), function(story){
  191. story.author = '';
  192. story.clear_author();
  193. });
  194. };
  195.  
  196. a.author.getFics = function(id) {
  197. return _.filter(a.collection,function(story){
  198. return story.authorId() == id;
  199. });
  200. };
  201.  
  202. a.fic = {};
  203.  
  204. a.fic.get = function(id) {
  205. return db[a.namespace].fic[id];
  206. };
  207.  
  208. a.fic.like = function(id) {
  209. a.save("fic",id,FIC_LIKED);
  210.  
  211. var story = _.find(a.collection,function(story){
  212. return story.ficId() == id;
  213. });
  214.  
  215. story.fic = FIC_LIKED;
  216. story.like_story();
  217. };
  218.  
  219. a.fic.dislike = function(id) {
  220. a.save("fic",id,FIC_DISLIKED);
  221. var story = _.find(a.collection,function(story){
  222. return story.ficId() == id;
  223. });
  224. story.fic = FIC_DISLIKED;
  225. story.dislike_story();
  226. };
  227.  
  228. a.fic.mark = function(id) {
  229. a.save("fic",id,FIC_MARKED);
  230. var story = _.find(a.collection,function(story){
  231. return story.ficId() == id;
  232. });
  233. story.fic = FIC_MARKED;
  234. story.mark_story();
  235. };
  236.  
  237. a.fic.inlibrary = function(id) {
  238. a.save("fic",id,FIC_INLIBRARY);
  239. var story = _.find(a.collection,function(story){
  240. return story.ficId() == id;
  241. });
  242. story.fic = FIC_INLIBRARY;
  243. story.inlibrary_story();
  244. };
  245.  
  246.  
  247. a.fic.clear = function(id) {
  248. a.save("fic",id,"clear");
  249. var story = _.find(a.collection,function(story){
  250. return story.ficId() == id;
  251. });
  252. story.fic = '';
  253. story.clear_story();
  254. };
  255.  
  256. a.community = {};
  257. a.community.get = function(id) {
  258. if (typeof(db[a.namespace].community) == "undefined") {
  259. db[a.namespace].community = {}
  260. }
  261. return db[a.namespace].community[id];
  262. };
  263.  
  264. a.community.like = function(id) {
  265. a.save("community",id,COMMUNITY_LIKED);
  266.  
  267. var community = _.find(a.collection,function(community){
  268. return community.communityId() == id;
  269. });
  270.  
  271. community.community = COMMUNITY_LIKED;
  272. community.like_community();
  273. };
  274.  
  275. a.community.dislike = function(id) {
  276. a.save("community",id,COMMUNITY_DISLIKED);
  277. var community = _.find(a.collection,function(community){
  278. return community.communityId() == id;
  279. });
  280. community.community = COMMUNITY_DISLIKED;
  281. community.dislike_community();
  282. };
  283.  
  284.  
  285. a.community.clear = function(id) {
  286. a.save("community",id,"clear");
  287. var community = _.find(a.collection,function(community){
  288. return community.communityId() == id;
  289. });
  290. community.community = '';
  291. community.clear_community();
  292. };
  293.  
  294. a.options = function(name, value) {
  295.  
  296. if(!name) { throw new Error("name is required. what option are you looking for?"); }
  297.  
  298. if(typeof value !== "undefined") {
  299. a.save("options",name,value);
  300. return false;
  301. } else {
  302. return db.options[name];
  303. }
  304. };
  305.  
  306. return a;
  307. };
  308.  
  309. var Story = function(optionsin) {
  310.  
  311. var a = {};
  312. var options = optionsin || {};
  313.  
  314. if(!options.instance) { throw new Error("instance of this is required"); }
  315. if(!options.namespace) { throw new Error("namespace is required"); }
  316.  
  317. var _this = ffn$(options.instance);
  318.  
  319. a["default"] = {
  320. template: function() {
  321. var template = '<div class="new_like_actions" style="margin:0px 0px 0px 20px; font-size:11px;">'+
  322.  
  323. 'Story: <a href="" class="like_story"><font color="'+COLOR_LIKED+'">Like</font></a> | '+
  324. '<a href="" class="dislike_story"><font color="'+COLOR_DISLIKED+'">Dislike</font></a> | '+
  325. '<a href="" class="mark_story"><font color="'+COLOR_MARKED+'">Mark</font></a> | '+
  326. '<a href="" class="inlibrary_story"><font color="'+COLOR_INLIBRARY+'">InLibrary</font></a> | '+
  327. '<a href="" class="clear_story" style="color:blue;">Clear</a>'+
  328.  
  329. ' Author: <a href="" class="like_author" style="color:blue;">Like</a> | '+
  330. '<a href="" class="dislike_author" style="color:blue;">Dislike</a> | '+
  331. '<a href="" class="clear_author" style="color:blue;">Clear</a>'+
  332.  
  333. '</div>';
  334. return template;
  335. },
  336. addActions: function() {
  337. var instance = this;
  338. _this.append(this.template());
  339.  
  340. _this.find('.new_like_actions .like_author').click(function(){ app.author.like(instance.authorId()); return false; });
  341. _this.find('.new_like_actions .dislike_author').click(function(){ app.author.dislike(instance.authorId()); return false; });
  342. _this.find('.new_like_actions .clear_author').click(function(){ app.author.clear(instance.authorId()); return false; });
  343.  
  344. _this.find('.new_like_actions .like_story').click(function(){ app.fic.like(instance.ficId()); return false; });
  345. _this.find('.new_like_actions .dislike_story').click(function(){ app.fic.dislike(instance.ficId()); return false; });
  346. _this.find('.new_like_actions .mark_story').click(function(){ app.fic.mark(instance.ficId()); return false; });
  347. _this.find('.new_like_actions .inlibrary_story').click(function(){ app.fic.inlibrary(instance.ficId()); return false; });
  348. _this.find('.new_like_actions .clear_story').click(function(){ app.fic.clear(instance.ficId()); return false; });
  349. },
  350. hide: function() {
  351. _this.hide();
  352. },
  353. set_story: function(){
  354. switch(this.fic){
  355. case FIC_LIKED:
  356. // _this.css('background-color',COLOR_LIKED);
  357. // this.$fic.css('font-weight','900')
  358. _this.addClass("ffn_like_fic");
  359. this.$fic.addClass("ffn_like_fic");
  360. break;
  361. case FIC_DISLIKED:
  362. _this.addClass("ffn_dislike_fic");
  363. this.$fic.addClass("ffn_dislike_fic");
  364. break;
  365. case FIC_MARKED:
  366. _this.addClass("ffn_mark");
  367. this.$fic.addClass("ffn_mark");
  368. break;
  369. case FIC_INLIBRARY:
  370. _this.addClass("ffn_inlibrary");
  371. this.$fic.addClass("ffn_inlibrary");
  372. break;
  373. }
  374. },
  375. set_author: function() {
  376. if(this.author === AUTHOR_LIKED) {
  377. this.$author.addClass("ffn_like_author");
  378. _this.addClass("ffn_like_author");
  379. }
  380. if(this.author === AUTHOR_DISLIKED) {
  381. this.$author.addClass("ffn_dislike_author");
  382. _this.addClass("ffn_dislike_author");
  383. }
  384. },
  385. like_story: function(){
  386. this.clear_story();
  387. _this.addClass("ffn_like_fic");
  388. this.$fic.addClass("ffn_like_fic");
  389. },
  390. dislike_story: function(){
  391. this.clear_story();
  392. _this.addClass("ffn_dislike_fic");
  393. this.$fic.addClass("ffn_dislike_fic");
  394. },
  395. mark_story: function(){
  396. this.clear_story();
  397. _this.addClass("ffn_mark");
  398. this.$fic.addClass("ffn_mark");
  399. },
  400. inlibrary_story: function(){
  401. this.clear_story();
  402. _this.addClass("ffn_inlibrary");
  403. this.$fic.addClass("ffn_inlibrary");
  404. },
  405. clear_story: function(){
  406. _this.removeClass("ffn_like_fic ffn_dislike_fic ffn_mark ffn_inlibrary");
  407. this.$fic.removeClass("ffn_like_fic ffn_dislike_fic ffn_mark ffn_inlibrary");
  408.  
  409. },
  410. like_author: function(){
  411. this.clear_author();
  412. this.$author.addClass("ffn_like_author");
  413. _this.addClass("ffn_like_author");
  414. },
  415. dislike_author: function(){
  416. this.clear_author();
  417. this.$author.addClass("ffn_dislike_author");
  418. _this.addClass("ffn_dislike_author");
  419. },
  420. clear_author: function(){
  421. _this.removeClass("ffn_like_author ffn_dislike_author");
  422. this.$author.removeClass("ffn_like_author ffn_dislike_author");
  423. }
  424. };
  425.  
  426. // Specific sites overrides
  427.  
  428. a["www.fanfiction.net"] = {
  429. $author: _this.find('a[href^="/u"]:first'),
  430. $fic: _this.find('a[href^="/s"]:first'),
  431. authorId: function() {
  432. if (typeof this.$author.attr('href') === "undefined") {
  433. return patharr[2];
  434. } else {
  435. return this.$author.attr('href').split('/')[2];
  436. }
  437. },
  438. ficId: function() {
  439. if (this.$fic.length === 0) {
  440. return patharr[2];
  441. } else {
  442. return this.$fic.attr('href').split('/')[2];
  443. }
  444. },
  445. hide: function() {
  446. // do not hide story header on reading page and story block on author page
  447. 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
  448. }
  449. };
  450.  
  451. //
  452.  
  453. a["archiveofourown.org"] = {
  454. $author: _this.find('a[href^="/users/"]:first'),
  455. $fic: _this.find('a[href^="/works/"]:first'),
  456. authorId: function() {
  457. // old: return this.$author.attr('href').split('/')[2];
  458. // Contributed by Vannis:
  459. if (this.$author.length === 0) {
  460. return 0;
  461. } else {
  462. return this.$author.attr('href').split('/')[2];
  463. }
  464. },
  465. ficId: function() {
  466. if (this.$fic.length === 0) {
  467. return patharr[2];
  468. } else {
  469. return this.$fic.attr('href').split('/')[2];
  470. }
  471. },
  472. hide:function(){
  473. if (patharr[1] !== "users" && // do not hide fic on author pages (to clearly see how many fics you like and dislike)
  474. !/(collections\/[^\/]+\/)?works\/\d+/.test(window.location.pathname)) { // do not hide fic header on reading pages)
  475. _this.hide();
  476. }
  477. }
  478. };
  479.  
  480. //
  481.  
  482. a["ficbook.net"] = {
  483. $author: _this.find('a[href^="/authors"]:first'),
  484. $fic: _this.find('a[href^="/readfic"]:first'),
  485. authorId: function() {
  486. return this.$author.attr('href').split('/')[2];
  487. },
  488. ficId: function() {
  489. if (this.$fic.length === 0) {
  490. return patharr[2].replace(/(\d+).*?$/,"$1");
  491. } else {
  492. return this.$fic.attr('href').split('/')[2].replace(/(\d+).*?$/,"$1");
  493. }
  494. },
  495. hide:function(){
  496. if (patharr[1]==="popular"){
  497. _this.parent().parent().parent().parent().hide();
  498. return;
  499. }
  500. if (patharr[1]!=="readfic" && patharr[2]!=="favourites") {
  501. _this.parent().hide();
  502. return;
  503. }
  504. }
  505. };
  506.  
  507. a["tbooklist.org"] = {
  508. $author: _this.find('a[href^="https://tbooklist.org/authors"]:first'),
  509. $fic: _this.find('a[href*="https://tbooklist.org/books"]:first'),
  510. authorId: function() {
  511. if (this.$author.length === 0) {
  512. return 0;
  513. } else {
  514. return this.$author.attr('href').trim().split('/')[4];
  515. }
  516. },
  517. ficId: function() {
  518. if (patharr[1] === "books") {
  519. return window.location.pathname.trim().split("/")[2];
  520. }
  521. if (this.$fic.length === 0) {
  522. return 0;
  523. } else {
  524. return this.$fic.attr('href').trim().split('/')[4];
  525. }
  526. },
  527. hide:function(){
  528. // do not hide when viewing fanfic details
  529. if(patharr[1] !== "books") {
  530. _this.hide();
  531. }
  532. }
  533. };
  534.  
  535. var b = ffn$.extend({}, a["default"], a[options.namespace]);
  536. b.fic = app.fic.get(b.ficId());
  537.  
  538. b.author = app.author.get(b.authorId());
  539. // do not show liker links if ficid or authorid are undefined (tweak for tbooklist.org)
  540. if (b.ficId() !== 0 && b.authorId() !== 0) {
  541. b.addActions();
  542. }
  543. b.set_story();
  544. b.set_author();
  545.  
  546. //hide
  547. if((app.options("hide_dislikes") === true && (b.fic === FIC_DISLIKED || b.author === AUTHOR_DISLIKED)) ||
  548. (app.options("hide_likes") === true && (b.fic === FIC_LIKED || b.author === AUTHOR_LIKED)) ||
  549. (app.options("hide_marked") === true && b.fic === FIC_MARKED) ||
  550. (app.options("hide_inlibrary") === true && b.fic === FIC_INLIBRARY)){
  551. // if(b.fic !== true && b.author) { // for liked fics of disliked authors
  552. b.hide();
  553. // }
  554. }
  555. return b;
  556. };
  557.  
  558. // Community
  559.  
  560. var Community = function(optionsin) {
  561.  
  562. var c = {};
  563. var options = optionsin || {};
  564.  
  565. if(!options.instance) { throw new Error("instance of this is required"); }
  566. if(!options.namespace) { throw new Error("namespace is required"); }
  567.  
  568. var _this = ffn$(options.instance);
  569.  
  570. c["default"] = {
  571. template: function() {
  572. var template = '<div class="new_like_actions" style="margin:0px 0px 0px 20px; font-size:11px;">'+
  573. 'Community: <a href="" class="like_community"><font color="'+COLOR_LIKED+'">Like</font></a> | '+
  574. '<a href="" class="dislike_community"><font color="'+COLOR_DISLIKED+'">Dislike</font></a> | '+
  575. '<a href="" class="clear_community" style="color:blue;">Clear</a>'+
  576. '</div>';
  577. return template;
  578. },
  579. addActions: function() {
  580. var instance = this;
  581. _this.append(this.template());
  582.  
  583. _this.find('.new_like_actions .like_community').click(function(){ app.community.like(instance.communityId()); return false; });
  584. _this.find('.new_like_actions .dislike_community').click(function(){ app.community.dislike(instance.communityId()); return false; });
  585. _this.find('.new_like_actions .clear_community').click(function(){ app.community.clear(instance.communityId()); return false; });
  586. },
  587. hide: function() {
  588. _this.hide();
  589. },
  590. set_community: function(){
  591. switch(this.community){
  592. case COMMUNITY_LIKED:
  593. _this.addClass("ffn_like_community");
  594. this.$community.addClass("ffn_like_community");
  595. break;
  596. case COMMUNITY_DISLIKED:
  597. _this.addClass("ffn_dislike_community");
  598. this.$community.addClass("ffn_dislike_community");
  599. break;
  600. }
  601. },
  602. like_community: function(){
  603. this.clear_community();
  604. _this.addClass("ffn_like_community");
  605. this.$community.addClass("ffn_like_community");
  606. },
  607. dislike_community: function(){
  608. this.clear_community();
  609. _this.addClass("ffn_dislike_community");
  610. this.$community.addClass("ffn_dislike_community");
  611. },
  612. clear_community: function(){
  613. _this.removeClass("ffn_like_community ffn_dislike_community");
  614. this.$community.removeClass("ffn_like_community ffn_dislike_community");
  615.  
  616. }
  617. };
  618.  
  619. // Specific sites overrides
  620.  
  621. c["www.fanfiction.net"] = {
  622. $community: _this.find('a[href^="/community"]:first'),
  623. communityId: function() {
  624. if (typeof this.$community.attr('href') === "undefined") {
  625. return patharr[3];
  626. } else {
  627. return this.$community.attr('href').split('/')[3];
  628. }
  629. },
  630. hide: function() {
  631. // needrework for community
  632. // do not hide story header on reading page and story block on author page
  633. 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
  634. }
  635. };
  636.  
  637. var d = ffn$.extend({}, c["default"], c[options.namespace]);
  638. d.community = app.community.get(d.communityId());
  639.  
  640. // do not show liker links if communityId is undefined
  641. if (d.communityId() !== 0) {
  642. d.addActions();
  643. }
  644. d.set_community();
  645.  
  646. //hide
  647. if((app.options("hide_dislikes") === true && d.community === COMMUNITY_DISLIKED) ||
  648. (app.options("hide_likes") === true && d.community === COMMUNITY_LIKED)){
  649. d.hide();
  650. // }
  651. }
  652. return d;
  653. };
  654.  
  655.  
  656. var app = new Application({namespace:document.location.host, db: db});
  657.  
  658. // Adding action links and navigation shortcuts to pages
  659. switch(window.location.hostname){
  660. case "www.fanfiction.net":
  661. GM_addStyle("* {user-select:text !important;}");
  662. // adding hotkeys
  663. // added toggle option, suggested by Vannius
  664. if (app.options("enable_list_hotkeys")) {
  665. document.addEventListener('keydown', function(e){
  666. if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
  667. switch (e.keyCode){
  668. case 37:
  669. var Prev = ffn$("a:contains('« Prev')");
  670. if (typeof(Prev[0])!=='undefined') {Prev[0].click();}
  671. break;
  672. case 39:
  673. var Next = ffn$("a:contains('Next »')");
  674. if (typeof(Next[0])!=='undefined') {Next[0].click();}
  675. break;
  676. }
  677. }
  678. }, false);
  679. }
  680. if (app.options("enable_read_hotkeys")) {
  681. document.addEventListener('keydown', function(e){
  682. if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
  683. switch (e.keyCode){
  684. case 37:
  685. var Prev = ffn$("button:contains('< Prev')");
  686. if (typeof(Prev[0])!=='undefined') {Prev.click();}
  687. break;
  688. case 39:
  689. var Next = ffn$("button:contains('Next >')");
  690. if (typeof(Next[0])!=='undefined') {Next.click();}
  691. break;
  692. }
  693. }
  694. }, false);
  695. }
  696. // links in list
  697. if (patharr[1] == "communities") {
  698. ffn$(".z-list").each(function(){
  699. var community = new Community({ namespace: app.namespace, instance: this });
  700. app.collection.push(community);
  701. });
  702.  
  703. } else {
  704. ffn$(".z-list").each(function(){
  705. var story = new Story({ namespace: app.namespace, instance: this });
  706. app.collection.push(story);
  707. });
  708. }
  709.  
  710. // links on reading page
  711. ffn$("div#profile_top").each(function(){
  712. var story = new Story({ namespace: app.namespace, instance: this });
  713. app.collection.push(story);
  714. });
  715.  
  716. // hide/show options
  717. ffn$('div#content_wrapper_inner').after(
  718. '<div class="liker_script_options" style="padding:5px; border:1px solid #333399; margin-bottom:5px; background:#D8D8FF;">'+
  719. '<b>Liker Options:</b> '+
  720. '</div>'
  721. );
  722. break;
  723. case "archiveofourown.org":
  724. // adding hotkeys
  725. if (app.options("enable_list_hotkeys")) {
  726. document.addEventListener('keydown', function(e){
  727. if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
  728. switch (e.keyCode){
  729. case 37:
  730. var Prev = ffn$("a:contains('← Previous')");
  731. if (typeof(Prev[0])!=='undefined') {Prev[0].click();}
  732. break;
  733. case 39:
  734. var Next = ffn$("a:contains('Next →')");
  735. if (typeof(Next[0])!=='undefined') {Next[0].click();}
  736. break;
  737. }
  738. }
  739. }, false);
  740. }
  741. if (app.options("enable_read_hotkeys")) {
  742. document.addEventListener('keydown', function(e){
  743. if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
  744. switch (e.keyCode){
  745. case 37:
  746. var Prev = ffn$("a:contains('←Previous Chapter')");
  747. if (typeof(Prev[0])!=='undefined') {Prev[0].click();}
  748. break;
  749. case 39:
  750. var Next = ffn$("a:contains('Next Chapter →')");
  751. if (typeof(Next[0])!=='undefined') {Next[0].click();}
  752. break;
  753. }
  754. }
  755. }, false);
  756. }
  757. // in lists
  758. // old: ffn$("ol.work.index.group > li").each(function(){
  759. // contribution by Vannius from greasyfork site
  760. ffn$(".blurb").each(function(){
  761. var story = new Story({ namespace: app.namespace, instance: this });
  762. app.collection.push(story);
  763. });
  764. // on reading page
  765. ffn$("div.preface.group").each(function(){
  766. var story = new Story({ namespace: app.namespace, instance: this });
  767. app.collection.push(story);
  768. });
  769. // hide/show options
  770. ffn$('div.navigation.actions.module, div.primary.header.module').after(
  771. '<div class="liker_script_options" style="padding:5px; border:1px solid #333399; margin-bottom:5px; background:#D8D8FF;">'+
  772. '<b>Liker Options:</b> '+
  773. '</div>'
  774. );
  775. break;
  776. case "ficbook.net":
  777. // on reading page
  778. if (patharr[1]==="readfic"){
  779. //ffn$("div.row.hat-row > ul.list-unstyled").each(function() {
  780. //ffn$("section.fanfiction-hat.container-fluid").each(function() {
  781. ffn$("div.fanfic-hat-body").each(function() {
  782. // console.log(this);
  783. var story = new Story({
  784. namespace: app.namespace,
  785. instance: this
  786. });
  787. app.collection.push(story);
  788. });
  789. if (app.options("enable_read_hotkeys")) {
  790. document.addEventListener('keydown', function(e){
  791. var textcontent;
  792. if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
  793. switch (e.keyCode){
  794. case 37:
  795. var Prev = ffn$("a.btn-back");
  796. if (Prev.length>0) window.location.href = Prev[0];
  797. break;
  798. case 39:
  799. var Next = ffn$("a.btn-next");
  800. if (Next.length>0) window.location.href = Next[0];
  801. break;
  802. }
  803. }
  804. // Ctrl+ up/dn hotkeys
  805. if (e.ctrlKey && !e.altKey && !e.shiftKey) {
  806. switch (e.keyCode){
  807. case 38:
  808. console.log("Ctrl+up")
  809. textcontent = document.getElementById("content");
  810. var txt = textcontent.outerHTML;
  811. txt = txt.replace(/&nbsp;/g," ");
  812. txt = txt.replace(/(<div[^>]*id=\"content\"[^>]*>)[ ]*/m,"$1 ");
  813. txt = txt.replace(/[\r\n][\ \r\n\t]+/gm,"\r\n");
  814. txt = txt.replace(/^((<[^>]+>)*)[ \t]+/gm,"$1");
  815. txt = txt.replace(/\n[ \t]*/gm,"\n ");
  816. txt = txt.replace("- ","— ");
  817. textcontent.outerHTML = txt;
  818. break;
  819. /* removed since we don't have a way to completely restore formatting after deleting so much junk
  820. case 40:
  821. textcontent = document.getElementById("content");
  822. textcontent.outerHTML = textcontent.outerHTML.replace(/<br n=\"2\">/gi,"<br>\n<br>");
  823. ffn$("#undo_typograf").click();
  824. break;
  825. */
  826. }
  827. }
  828. if (!e.ctrlKey && !e.altKey && e.shiftKey) {
  829. switch (e.keyCode){
  830. case 38:
  831. ffn$("#do_typograf").click();
  832. break;
  833. case 40:
  834. ffn$("#undo_typograf").click();
  835. break;
  836. }
  837. }
  838. }, false);
  839. }
  840. }
  841. // in lists
  842. if (patharr[1]==="find" ||
  843. (patharr[1]==="popular") ||
  844. (patharr[1]==="collections") ||
  845. (patharr[1]==="fanfiction") ||
  846. (patharr[3]==="profile" && patharr[4]==="works") || // in author profile / works
  847. (patharr[1] === "home" && ["favourites","collections"].indexOf(patharr[2])!=-1) ){ // indexOf => checks if patharr[2] is in [] array
  848. //ffn$(".description").each(function() {
  849. ffn$("div.js-toggle-description").each(function() {
  850. var story = new Story({
  851. namespace: app.namespace,
  852. instance: this
  853. });
  854. app.collection.push(story);
  855. });
  856. }
  857. // button for quickly reading/unreading story
  858. // done by constructing same button element as on readfic pages
  859. if (patharr[1]==="find" ||
  860. (patharr[1]==="popular") ||
  861. (patharr[2]==="favourites") ||
  862. (patharr[1]==="authors" && patharr[3]==="profile" && patharr[4]==="works")) {
  863. ffn$("section.fanfic-thumb-block").each(function() { // need more wide block to detect if the fic is read/unread to construct correct button
  864. var _this = ffn$(this);
  865. console.log(_this);
  866.  
  867. var ficId = _this.find('a[href^="/readfic"]:first').attr('href').split('/')[2].replace(/(\d+).*?$/,"$1");
  868. 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)
  869. _this.find("div.js-toggle-description").append('<button type="button" data-fanfic-id="' + ficId + '" data-is-readed="1" class="btn btn-success js-toggle-fanfic-as-read" onclick="ym(199955, "reachGoal", "toggled-fic-as-readed-from-fic-chapter"); return true;"> \
  870. <svg class="ic_checkbox-checked2 mb-0"><use href="/icons/icons-sprite5.svg#ic_checkbox-checked2"></use></svg> \
  871. Прочитано \
  872. </button>');
  873. } else {
  874. _this.find("div.js-toggle-description").append('<button type="button" data-fanfic-id="' + ficId + '" data-is-readed="0" class="btn btn-default js-toggle-fanfic-as-read" onclick="ym(199955, "reachGoal", "toggled-fic-as-readed-from-fic-chapter"); return true;"> \
  875. <svg class="ic_checkbox-unchecked2 mb-0"><use href="/icons/icons-sprite5.svg#ic_checkbox-unchecked2"></use></svg> \
  876. Прочитано \
  877. </button>')
  878. }
  879. });
  880. }
  881. // */
  882. // add hotkeys
  883. if (app.options("enable_list_hotkeys")) {
  884. document.addEventListener('keydown', function(e){
  885. if (!e.ctrlKey && !e.altKey && !e.shiftKey) {
  886. switch (e.keyCode){
  887. case 37:
  888. var Prev = ffn$("a[aria-label='Предыдущая']");
  889. if (Prev.length>0) Prev[0].click();
  890. break;
  891. case 39:
  892. var Next = ffn$("a[aria-label='Следующая']");
  893. if (Next.length>0) Next[0].click();
  894. break;
  895. }
  896. }
  897. }, false);
  898. }
  899.  
  900. // hide/show options
  901. ffn$('section.content-section').after(
  902. '<div class="liker_script_options" style="padding:5px; border:1px solid #333399; margin-bottom:5px; background:#D8D8FF;">'+
  903. '<b>Liker Options:</b> '+
  904. '</div>'
  905. );
  906. break;
  907. case "tbooklist.org":
  908. // in feed
  909. if (patharr[1]==="feed"){
  910. ffn$("div.content-block > div.regular").each(function() {
  911. var story = new Story({
  912. namespace: app.namespace,
  913. instance: this
  914. });
  915. app.collection.push(story);
  916. });
  917. }
  918. // book page
  919. else if (patharr[1]==="books"){
  920. ffn$("div.cmedia-divided__child__top").each(function() {
  921. var story = new Story({
  922. namespace: app.namespace,
  923. instance: this
  924. });
  925. app.collection.push(story);
  926. });
  927. }
  928. // hide/show options
  929. ffn$('div.content-block').after(
  930. '<div class="liker_script_options" style="padding:5px; border:1px solid #333399; margin-bottom:5px; background:#D8D8FF;">'+
  931. '<b>Liker Options:</b> '+
  932. '</div>'
  933. );
  934. break;
  935. }
  936.  
  937. // OPTIONS
  938. // - show/hide global options
  939. //
  940.  
  941. if(app.options("hide_likes")){
  942. ffn$('.liker_script_options').append('<a href="" class="show_likes" style="color:blue">Show Likes</a>');
  943. ffn$('.liker_script_options .show_likes').click(function(){ show_likes(); });
  944. } else {
  945. ffn$('.liker_script_options').append('<a href="" class="hide_likes" style="color:blue">Hide Likes</a>');
  946. ffn$('.liker_script_options .hide_likes').click(function(){ hide_likes(); });
  947. }
  948. ffn$('.liker_script_options').append('| ');
  949.  
  950. if(app.options("hide_dislikes")){
  951. ffn$('.liker_script_options').append('<a href="" class="show_dislikes" style="color:blue">Show Dislikes</a>');
  952. ffn$('.liker_script_options .show_dislikes').click(function(){ show_dislikes(); });
  953. } else {
  954. ffn$('.liker_script_options').append('<a href="" class="hide_dislikes" style="color:blue">Hide Dislikes</a>');
  955. ffn$('.liker_script_options .hide_dislikes').click(function(){ hide_dislikes(); });
  956. }
  957. ffn$('.liker_script_options').append('| ');
  958.  
  959. if(app.options("hide_marked")){
  960. ffn$('.liker_script_options').append('<a href="" class="show_marked" style="color:blue">Show Marked</a>');
  961. ffn$('.liker_script_options .show_marked').click(function(){ show_marked(); });
  962. } else {
  963. ffn$('.liker_script_options').append('<a href="" class="hide_marked" style="color:blue">Hide Marked</a>');
  964. ffn$('.liker_script_options .hide_marked').click(function(){ hide_marked(); });
  965. }
  966. ffn$('.liker_script_options').append('| ');
  967.  
  968. if(app.options("hide_inlibrary")){
  969. ffn$('.liker_script_options').append('<a href="" class="show_inlibrary" style="color:blue">Show InLibrary</a>');
  970. ffn$('.liker_script_options .show_inlibrary').click(function(){ show_inlibrary(); });
  971. } else {
  972. ffn$('.liker_script_options').append('<a href="" class="hide_inlibrary" style="color:blue">Hide InLibrary</a>');
  973. ffn$('.liker_script_options .hide_inlibrary').click(function(){ hide_inlibrary(); });
  974. }
  975.  
  976. // specific links for sites
  977.  
  978. // dislike all authors, currently on ffnet only (enter some slash community and blacklist everyone there, saves time when browsing HP fanfics later)
  979. if (window.location.hostname === "www.fanfiction.net") {
  980. ffn$('.liker_script_options').append('| ');
  981. ffn$('.liker_script_options').append('<a href="" class="dislike_all" style="color:blue">Dislike All Authors</a>');
  982. ffn$('.liker_script_options .dislike_all').click(function(e){ e.preventDefault(); dislike_all();});
  983. }
  984.  
  985. if (window.location.hostname === "ficbook.net") {
  986. switch(patharr[1]){
  987. case "find":
  988. // revert read status for all visible fics (circumvent ficbook bug where alreadн read fics still appear in search)
  989. ffn$('.liker_script_options').append('| Actions: ');
  990. ffn$('.liker_script_options').append('<a href="" class="ffn_ficbook_invert_read" style="color:blue">Invert Read status</a>');
  991. ffn$('.liker_script_options .ffn_ficbook_invert_read').click(function(e){ e.preventDefault(); ficbook_invertread(); return false; });
  992. break
  993. case "readfic":
  994. // specific request - download fb2 and like fic in one click
  995. ffn$('.new_like_actions').append('| Actions: <a href="" class="ffn_ficbook_fb2_and_like" style="color:blue">FB2+Like+Read</a>');
  996. ffn$('.new_like_actions .ffn_ficbook_fb2_and_like').click(function(e){ e.preventDefault(); ficbook_fb2andlike(); return false; });
  997. break
  998. }
  999. //
  1000. }
  1001.  
  1002. function ficbook_fb2andlike(){
  1003. var a= ffn$('a[href^="/fanfic_download/fb2/"]');
  1004. ffn$('.like_story').click();
  1005. document.location = a.attr("href");
  1006. ffn$('.js-mark-readed').not('.btn-success').click();
  1007. return false;
  1008. }
  1009.  
  1010.  
  1011. ffn$('.liker_script_options').append('| <a href="" id="ffn_OptionsToggle" style="color:blue">FFN Options</a>');
  1012. ffn$('.liker_script_options').after(
  1013. "<div id='ffn_options_block' style='display:none;'>" +
  1014. "<input type='checkbox' id='ffn_checkbox_hide_likes'> Hide Likes</br>" +
  1015. "<input type='checkbox' id='ffn_checkbox_hide_dislikes'> Hide Dislikes</br>" +
  1016. "<input type='checkbox' id='ffn_checkbox_hide_marked'> Hide Marked</br>" +
  1017. "<input type='checkbox' id='ffn_checkbox_hide_inlibrary'> Hide InLibrary</br>" +
  1018. "</br>" +
  1019. "<input type='checkbox' id='ffn_checkbox_enable_list_hotkeys'> Enable hotkeys on lists pages (Left/Right for next/prev page)</br>" +
  1020. "<input type='checkbox' id='ffn_checkbox_enable_read_hotkeys'> Enable hotkeys on reading pages (Left/Right for next/prev chapter)</br>" +
  1021. "</br>" +
  1022. "<button id='ffn_options_button_save'>Save options and reload page</button></br>" +
  1023. "</br>" +
  1024. "</br>" +
  1025. "Export data: <button id='ffn_export_to_file'>Download text file</button></br>" +
  1026. "Import data: <input id='ffn_import_file_box' type='file' accept='text/plain'>" + "<button id='ffn_import_file_button'>Import</button>" +
  1027.  
  1028. // Old import/export (slow when db is big)
  1029. /* "<a href='' class='backupToggle' style='color:blue'>Manage Site Data</a>'" +
  1030. " <div class='backup_text' style='display:none'>" +
  1031. " <table><tr>" +
  1032. " <td class='backup_data' valign='top'>" +
  1033. " <b>Backup data</b><br />" +
  1034. " Copy this text some where safe<br />" +
  1035. " <textarea class=''></textarea>" +
  1036. " </td>" +
  1037. " <td>&nbsp;&nbsp;</td>" +
  1038. " <td class='save_new_data' valign='top'>" +
  1039. " <b>Upload new Data</b><br>" +
  1040. " Upload data you had previously saved<br />" +
  1041. " <textarea></textarea><br >" +
  1042. " <button class='new_data_save'>save</button>" +
  1043. " </td></tr></table>" +
  1044. " </div>" +*/
  1045.  
  1046.  
  1047.  
  1048. "</div>"
  1049.  
  1050. );
  1051.  
  1052. ffn$('#ffn_import_file_button').click(function(){
  1053. var selectedFile = document.getElementById('ffn_import_file_box').files.item(0);
  1054. if (selectedFile === null) return;
  1055. fileToText(selectedFile, (text) => {
  1056. var new_db;
  1057. try {
  1058. new_db = JSON.parse(text);
  1059. } catch(err) {
  1060. alert("JSON data in file is invalid");
  1061. return;
  1062. }
  1063. localStorage.setItem("FFLikerAA", JSON.stringify(new_db));
  1064. document.location = document.location;
  1065. });
  1066. });
  1067.  
  1068. function fileToText(file, callback) {
  1069. const reader = new FileReader();
  1070. reader.readAsText(file);
  1071. reader.onload = () => {
  1072. callback(reader.result);
  1073. };
  1074. }
  1075.  
  1076. ffn$('#ffn_OptionsToggle').click(function(){
  1077. ffn$("#ffn_options_block").toggle();
  1078. ffn$("#ffn_checkbox_hide_likes").prop("checked",app.options("hide_likes"));
  1079. ffn$("#ffn_checkbox_hide_dislikes").prop("checked",app.options("hide_dislikes"));
  1080. ffn$("#ffn_checkbox_hide_marked").prop("checked",app.options("hide_marked"));
  1081. ffn$("#ffn_checkbox_hide_inlibrary").prop("checked",app.options("hide_inlibrary"));
  1082. ffn$("#ffn_checkbox_enable_list_hotkeys").prop("checked",app.options("enable_list_hotkeys"));
  1083. ffn$("#ffn_checkbox_enable_read_hotkeys").prop("checked",app.options("enable_read_hotkeys"));
  1084. return false;
  1085. });
  1086. ffn$('#ffn_options_button_save').click(function(){
  1087. app.options("hide_likes", ffn$("#ffn_checkbox_hide_likes").prop("checked"));
  1088. app.options("hide_dislikes", ffn$("#ffn_checkbox_hide_dislikes").prop("checked"));
  1089. app.options("hide_marked", ffn$("#ffn_checkbox_hide_marked").prop("checked"));
  1090. app.options("hide_inlibrary", ffn$("#ffn_checkbox_hide_inlibrary").prop("checked"));
  1091. app.options("enable_list_hotkeys", ffn$("#ffn_checkbox_enable_list_hotkeys").prop("checked"));
  1092. app.options("enable_read_hotkeys", ffn$("#ffn_checkbox_enable_read_hotkeys").prop("checked"));
  1093. location.reload();
  1094. return false;
  1095. });
  1096.  
  1097. // import/export db data
  1098.  
  1099. /* old import/export, with textboxes
  1100. ffn$('.backup_text .backup_data textarea').on("click",function(){
  1101. ffn$(this).select();
  1102. });
  1103.  
  1104. ffn$('.backup_text .save_new_data button').on("click",function(){
  1105. var v = ffn$('.backup_text .save_new_data textarea').val();
  1106. var new_db;
  1107. try {
  1108. new_db = JSON.parse(v);
  1109. } catch(err) {
  1110. alert("that data is not valid");
  1111. return;
  1112. }
  1113. localStorage.setItem("FFLikerAA", JSON.stringify(new_db));
  1114. document.location = document.location;
  1115. });
  1116.  
  1117. ffn$('.backupToggle').click(function(){
  1118. ffn$(".backup_text").toggle();
  1119. ffn$(".backup_text .backup_data textarea").html(JSON.stringify(db));
  1120. return false;
  1121. });
  1122. */
  1123.  
  1124. //ffn$('.liker_script_options').append('| <a href="" id="testlink" style="color:blue">test' + '</a>');
  1125.  
  1126. ffn$('#ffn_export_to_file').click(function(){
  1127. var curdate = new Date();
  1128. var year = curdate.getFullYear();
  1129. var month = curdate.getMonth() + 1;
  1130. month = month<10? "0"+month : month;
  1131. var day = curdate.getDate();
  1132. day = day<10? "0"+day : day;
  1133. export_file(JSON.stringify(db,null," "),"FFN_" + window.location.host + "_" + year + "-" + month + "-" + day + ".txt", "text/plain");
  1134. return false;
  1135. });
  1136.  
  1137. function export_file(content, fileName, mime) {
  1138. const blob = new Blob([content], {
  1139. type: mime
  1140. });
  1141. const url = URL.createObjectURL(blob);
  1142. const a = document.createElement("a");
  1143. a.style = "display: none";
  1144. a.href = url;
  1145. a.download = fileName;
  1146. document.body.appendChild(a);
  1147. a.click();
  1148. setTimeout(function(){
  1149. document.body.removeChild(a);
  1150. window.URL.revokeObjectURL(url);
  1151. }, 100);
  1152. }
  1153.  
  1154. function show_dislikes(){
  1155. app.options("hide_dislikes",false);
  1156. return false;
  1157. }
  1158.  
  1159. function hide_dislikes(){
  1160. app.options("hide_dislikes",true);
  1161. return false;
  1162. }
  1163.  
  1164. function show_likes(){
  1165. app.options("hide_likes",false);
  1166. return false;
  1167. }
  1168.  
  1169. function hide_likes(){
  1170. app.options("hide_likes",true);
  1171. return false;
  1172. }
  1173.  
  1174. function show_marked(){
  1175. app.options("hide_marked",false);
  1176. return false;
  1177. }
  1178.  
  1179. function hide_marked(){
  1180. app.options("hide_marked",true);
  1181. return false;
  1182. }
  1183.  
  1184. function show_inlibrary(){
  1185. app.options("hide_inlibrary",false);
  1186. return false;
  1187. }
  1188.  
  1189. function hide_inlibrary(){
  1190. app.options("hide_inlibrary",true);
  1191. return false;
  1192. }
  1193.  
  1194. function dislike_all(){
  1195. ffn$("div.z-list:visible").each(function() {
  1196. var story = new Story({
  1197. namespace: app.namespace,
  1198. instance: this
  1199. });
  1200. app.collection.push(story);
  1201. app.author.dislike(story.authorId());
  1202. });
  1203. }
  1204.  
  1205. function ficbook_invertread(){
  1206. ffn$("button.btn.btn-primary.btn-success.js-mark-readed:visible").each(function() {
  1207. this.click();
  1208. })
  1209. ffn$("button.btn.btn-primary.btn-default.js-mark-readed:visible").each(function() {
  1210. this.click();
  1211. })
  1212. }