FB - Clean my feeds

Hide Sponsored and Suggested posts in FB's News Feed, Groups Feed, Watch Videos Feed and Marketplace Feed

当前为 2024-07-04 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name FB - Clean my feeds
  3. // @description Hide Sponsored and Suggested posts in FB's News Feed, Groups Feed, Watch Videos Feed and Marketplace Feed
  4. // @namespace https://greasyfork.org/users/812551
  5. // @supportURL https://github.com/zbluebugz/facebook-clean-my-feeds/issues
  6. // @version 4.31
  7. // @author zbluebugz (https://github.com/zbluebugz/)
  8. // @match https://www.facebook.com/*
  9. // @match https://web.facebook.com/*
  10. // @match https://facebook.com/*
  11. // @noframes
  12. // @grant GM.registerMenuCommand
  13. // @grant GM.info
  14. // @grant unsafeWindow
  15. // @license MIT; https://opensource.org/licenses/MIT
  16. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB2AAAAdgB+lymcgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAanSURBVHic5ZtpjBVFEMd/u8vthsCi3OcGxQWBKJdEDEbuDypqNoGgxGgkWY8YDaIYFQ9MRCOJJ0Y80JhgVESiRpCVIAYIIAQPCEoQZCOIyuIB667IPj/8qzPzhveANzPvvX3LP5nMVHdPT1V3VVd1zQwUNroDK4A/gB3A1Pyyk1sUA18DCeBPoBH4FxiRT6ZyiXFI+C+AFsAdRj+TSSfF8fOVM0y281LgP+BwHnnJC75FM15u9BtGV+aNoxyiG7L5H3xlNUgTyjLpqFBNYDJQBKw0ejDQE9gC1GbSUaEOwCQ7r0pDN2sUA78CDUCpla1G9j86X0zlEqOQsJ8b3RaoQ6pfkmlnhWgCQXW/Ag1CNXAi086awwCcVfbfATgOHEReAGAXMone+WIql7geCbvE6F5G7wjbYaGZQFDdpwToZo99aKHrbPQypAGT0t3QnFCBhN1idAlyff8A7cJ2WkgmEFT/S4GOwDoUB4RCIQ3ARDt/ZudL7LwyRdtmhzbAMZT5aWll5wGzgfb5YiqXmIDsf3ncHReKCQTtfwDwGtA1P+zkHi7708/o142ekvaOZoQeKPvzva8sVPYnFQrBBCahuN+pf+jsTyoUygDAWbr7KwF+J4vZnxZxdJICFYQLTxvQzi5h9HCgE7AGOGp9jgGOAJujs5kd3IAECHvc7+vrYSubY/QUo9+Ni9lsrAG97LwN+NiuDwPvAVuN/s7oA0avBjbatdvpQYHa/1w0S1Xo7W0CT7hZRj9o9IdGX4yX7FhodTnJ/jRlLzAerVGrkNC9UAS4A9gf10Oa8gDkJPvTlAdgIooAXf4/K/YflxssBc5HA9rdynoDQ+z6HGAYnu12N7qD0RUo5AXoAlxtbb9Cb4FaAFeidFid3XschccNMckQGpXIR0dxfemO+faMy9LUH0RvikIjqga0QdvS1sBHQH3E/hzGo3RX0P1twlsAy9BXIq8AQ2N6bsa4CM1EnHaZKvuzyZ4zMNB2P1onWhISURdBp0Fx2uHlKORdg+y8E7L5GmBnoG09ihFCyxHVBH6280jgRsKbwGbgJ7sOrvbj0aaohuTPXzoCfYHfyPNC+CrRF7tdvv7SZX/SHfdEYb7o9E3OqI9K9JHidBTnf5JhH+vsXAE8DexGUV8Rmvmu6PO3BNK07sCzaK9RHY39+DAUMZip8AB3kzyrK5Dwg43e4Gu7By18baIwmw2UIWa/yfC+3iiOqAfeRsGO20AttutHrG2RtTsUnd3s4ChnlqfrjFT5MWA9EvIJqxuFIkC/Royzui5Gb6WJwm1XS9PUlyFbriNZwEa8V18gt1aF8ggJ5G3KUYYogbbRTRLViMGKFHW98AboLxTBzQJewhuEJWiWHc7Fyxk8ihbaBPBCVriPAe5z1QmB8g7Aj1a3DAnm0Bt98Oy04QhwJ/L9RXgmMsPKE8B9WZMgIh5HDN4cKH8e792e+5StL/IY6fz7duApu95m9y3AG4xYEHdWuMbOPX1lA4Bb0QJ5O1rlxyIfXorc2jIkJGjnNw251aHoDdBtdp/LN7rnNDm4rO1io8fiqb6L2AaiPzwSSGNap+hnttXvRKGww5dWXp7inryhGH28vAjvD46jaKFz6vwmnrY5tZ+Xoq9StBdwqv9+oH4vWixTDVpeMA5vZQ8etSjEnYYXco+wut0km18nZN/HAn34w9xi9EvML9kRJXPMRbORANYCM4E+QKs07VuhAUkAN/nKL0T7eqc5y/G8yQe+dt2sbAtNAC52/5tT/6FRhgSsxEtqbMWb/XKU80ugMNjFADM42d+PxPMkecUwpIp1nJyPKwKuRTPnojj/sZHkNz/uReeLJO9MX7bymb6y66zsuZjkCI2VxkhVoHwQml0nbB3a269GMzmVZCHddz970dfeDm1RMOQixlo73Ppwb6zSZIh+xsQekhexCXgMrgeu4tQr9RiU0U1YWz/m4Q1gre9osPLpUYWIgipj4klf2RDEbCPwAMn5ueHI17+FNKEaeQCnJQtJxnD05Wc9cEGg7lO7Z0wMcoSGy8q4ha8EL4U1x9duLMoLpHKPjSheCIazg9A+P4EW2SC2W13f6GKEh0tQuK2rW63X4tn3Ajz3uAFtYEYD/dGq77d3kMbcgvdyZVGaZx9C4XDoFHgcuAsxudQYcWrpdn/zjT4MXHOavvqj312dBp1AW95UecqWVn8gRV0kZJoUbY9Uuw+KAdohM9hHcia3FsX7qdAK+fvgTNaTXsBipPqbifgqLA70AN5B+fhTpavjPhqBh+IW5n827DI9G+eQwwAAAABJRU5ErkJggg==
  17. // @icon64 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB2AAAAdgB+lymcgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAanSURBVHic5ZtpjBVFEMd/u8vthsCi3OcGxQWBKJdEDEbuDypqNoGgxGgkWY8YDaIYFQ9MRCOJJ0Y80JhgVESiRpCVIAYIIAQPCEoQZCOIyuIB667IPj/8qzPzhveANzPvvX3LP5nMVHdPT1V3VVd1zQwUNroDK4A/gB3A1Pyyk1sUA18DCeBPoBH4FxiRT6ZyiXFI+C+AFsAdRj+TSSfF8fOVM0y281LgP+BwHnnJC75FM15u9BtGV+aNoxyiG7L5H3xlNUgTyjLpqFBNYDJQBKw0ejDQE9gC1GbSUaEOwCQ7r0pDN2sUA78CDUCpla1G9j86X0zlEqOQsJ8b3RaoQ6pfkmlnhWgCQXW/Ag1CNXAi086awwCcVfbfATgOHEReAGAXMone+WIql7geCbvE6F5G7wjbYaGZQFDdpwToZo99aKHrbPQypAGT0t3QnFCBhN1idAlyff8A7cJ2WkgmEFT/S4GOwDoUB4RCIQ3ARDt/ZudL7LwyRdtmhzbAMZT5aWll5wGzgfb5YiqXmIDsf3ncHReKCQTtfwDwGtA1P+zkHi7708/o142ekvaOZoQeKPvzva8sVPYnFQrBBCahuN+pf+jsTyoUygDAWbr7KwF+J4vZnxZxdJICFYQLTxvQzi5h9HCgE7AGOGp9jgGOAJujs5kd3IAECHvc7+vrYSubY/QUo9+Ni9lsrAG97LwN+NiuDwPvAVuN/s7oA0avBjbatdvpQYHa/1w0S1Xo7W0CT7hZRj9o9IdGX4yX7FhodTnJ/jRlLzAerVGrkNC9UAS4A9gf10Oa8gDkJPvTlAdgIooAXf4/K/YflxssBc5HA9rdynoDQ+z6HGAYnu12N7qD0RUo5AXoAlxtbb9Cb4FaAFeidFid3XschccNMckQGpXIR0dxfemO+faMy9LUH0RvikIjqga0QdvS1sBHQH3E/hzGo3RX0P1twlsAy9BXIq8AQ2N6bsa4CM1EnHaZKvuzyZ4zMNB2P1onWhISURdBp0Fx2uHlKORdg+y8E7L5GmBnoG09ihFCyxHVBH6280jgRsKbwGbgJ7sOrvbj0aaohuTPXzoCfYHfyPNC+CrRF7tdvv7SZX/SHfdEYb7o9E3OqI9K9JHidBTnf5JhH+vsXAE8DexGUV8Rmvmu6PO3BNK07sCzaK9RHY39+DAUMZip8AB3kzyrK5Dwg43e4Gu7By18baIwmw2UIWa/yfC+3iiOqAfeRsGO20AttutHrG2RtTsUnd3s4ChnlqfrjFT5MWA9EvIJqxuFIkC/Royzui5Gb6WJwm1XS9PUlyFbriNZwEa8V18gt1aF8ggJ5G3KUYYogbbRTRLViMGKFHW98AboLxTBzQJewhuEJWiWHc7Fyxk8ihbaBPBCVriPAe5z1QmB8g7Aj1a3DAnm0Bt98Oy04QhwJ/L9RXgmMsPKE8B9WZMgIh5HDN4cKH8e792e+5StL/IY6fz7duApu95m9y3AG4xYEHdWuMbOPX1lA4Bb0QJ5O1rlxyIfXorc2jIkJGjnNw251aHoDdBtdp/LN7rnNDm4rO1io8fiqb6L2AaiPzwSSGNap+hnttXvRKGww5dWXp7inryhGH28vAjvD46jaKFz6vwmnrY5tZ+Xoq9StBdwqv9+oH4vWixTDVpeMA5vZQ8etSjEnYYXco+wut0km18nZN/HAn34w9xi9EvML9kRJXPMRbORANYCM4E+QKs07VuhAUkAN/nKL0T7eqc5y/G8yQe+dt2sbAtNAC52/5tT/6FRhgSsxEtqbMWb/XKU80ugMNjFADM42d+PxPMkecUwpIp1nJyPKwKuRTPnojj/sZHkNz/uReeLJO9MX7bymb6y66zsuZjkCI2VxkhVoHwQml0nbB3a269GMzmVZCHddz970dfeDm1RMOQixlo73Ppwb6zSZIh+xsQekhexCXgMrgeu4tQr9RiU0U1YWz/m4Q1gre9osPLpUYWIgipj4klf2RDEbCPwAMn5ueHI17+FNKEaeQCnJQtJxnD05Wc9cEGg7lO7Z0wMcoSGy8q4ha8EL4U1x9duLMoLpHKPjSheCIazg9A+P4EW2SC2W13f6GKEh0tQuK2rW63X4tn3Ajz3uAFtYEYD/dGq77d3kMbcgvdyZVGaZx9C4XDoFHgcuAsxudQYcWrpdn/zjT4MXHOavvqj312dBp1AW95UecqWVn8gRV0kZJoUbY9Uuw+KAdohM9hHcia3FsX7qdAK+fvgTNaTXsBipPqbifgqLA70AN5B+fhTpavjPhqBh+IW5n827DI9G+eQwwAAAABJRU5ErkJggg==
  18. // @run-at document-start
  19. // ==/UserScript==
  20. /*
  21.  
  22. :: Tip ::
  23. This userscript does not block video ads (begin-roll, mid-roll, end-roll), however there's a work-around:
  24. 1) Install uBlock Origin (uBO) in your browser(s)
  25. 2) In uBO, goto "My filters" tab and paste in the following rule: facebook.com##+js(set, Object.prototype.scrubber, undefined)
  26. Note: I have not tested this in other content/ad-blockers.
  27.  
  28. v4.31 :: July 2024
  29. News Feed - Reels and Videos - added extra detection rule (dictionary based)
  30. News Feed - Suggestions - added extra detection rule (x2)
  31. Survey - updated detection rule
  32. Reels - option to stop looping
  33. Fix bug with showing/hiding the FB-CMF button
  34.  
  35. v4.30 :: March 2024
  36. Hot fix
  37. Updated Marketplace feed detection component
  38.  
  39. v4.29 :: February 2024
  40. !!! Hot fix !!!
  41. Issues with FB, Adblockers and FB-CMF - all clashing
  42. Adjusted News Feed's query rules
  43. Temporarily disabled News Feed's message/notification tab (will be restored in next version)
  44.  
  45. v4.28 :: January 2024
  46. Enabled option to toggle Sponsored post detection rule (for uBO compatibility)
  47. Added Video's "Live" detection rule
  48. Enabled Reels' video controls
  49. Added Ukrainian (Україна)
  50. Added Bulgarian (български)
  51. Dialog box - reworded "Miscellaneous items" to "Supplementary / information section"
  52. Dialog box - added "Reset" button to reset the options
  53. Fixed bug with Survey detection component
  54. Fixed bug with Importing settings from a file
  55. Revised message/notification tab for News feed
  56. Revised Create Stories detection rule
  57. Add option to filter posts by number of Likes
  58. Fixed bug with function scanTreeForText() - failing to detect "Anonymous participant"
  59. Updated Groups Feed filter rules - new HTML structure via (Feeds > Groups)
  60. Added display of script's version number to dialog box
  61.  
  62. v4.27 :: December 2023
  63. Added Russian (Русский) - supplied by github user Kenya-West
  64. v4.26 :: November 2023
  65. Added web.facebook.com to @match conditions
  66. Added Survey detection component (Home / News feed)
  67. Added Follow detection component (Home / News feed)
  68. Added Participate detection component (Home / News feed)
  69. Updated Marketplace detection rules
  70. v4.25 :: November 2023
  71. Added extra filter rule for nf_isSuggested() (for "Suggested for you" posts) - fix supplied by opello (via github)
  72. Added News Feed's Stories post detection rule.
  73. Revised function scanTreeForText() to include other elements for scanning
  74. Fixed bug with Marketplace prices' filter
  75. Reduce possible conflicts with uBlock Origin / other adblockers
  76. Code tweaks
  77. v4.24 :: September 2023
  78. Fixed issues with v4.23 (selection/detection rules)
  79. Code tweaks
  80. v4.23 :: August 2023
  81. Fixed bug with showing Marketplace's hidden items
  82. Updated Marketplace detection rules
  83. Split Marketplace's text filter into two - prices and description
  84. Merged "Stories" with "Stories | Reels | Rooms" detection rules.
  85. Fixed bug with CMF's hidden dialog box's text being included in CTRL+F search (now excluded)
  86. Dropped "Create room" detection component (no longer listed in FB)
  87. v4.22 :: July 2023
  88. Updated News Feed posts selection rule (FB changed structure)
  89. Updated Events you may like detection rule
  90. v4.21 :: June 2023
  91. Updated news feed detection rules - for older HTML structures
  92. Updated watch videos feed detection rules
  93. Added Greek (Ελληνικά)
  94. Updated various functions
  95. v4.20 :: May 2023
  96. Added "Feeds (most recent)" to the clean up rules (FB recently introduced the "Feeds (most recent)" feature)
  97. Updated Search Feed sponsored posts rule
  98. v4.19 :: May 2023
  99. Updated News Feed posts selection rule (FB changed structure)
  100. v4.18 :: May 2023
  101. Updated News Feed sponsored posts rule
  102. Added News Feed sponsored video posts rule
  103. Updated News Feed suggested posts rule
  104. v4.17 :: March 2023
  105. Fixed issue with GreaseMonkey & FireMonkey not able to run userscript
  106. Updated News Feed sponsored posts rule
  107. Updated Videos Feed sponsored posts rule
  108. Added option to hide "# shares" on posts (news + groups)
  109. v4.16 :: February 2023
  110. Fixed issue with <no message> setting breaking FB
  111. Code tweaks
  112. v4.15 :: February 2023
  113. Updated News Feed sponsored posts rule (FB changed structure)
  114. Updated Marketplace Feed > Item page posts rules
  115. Code tweaks
  116. v4.14 :: January 2023
  117. Updated News Feed Suggestion/Recommendation posts rule (FB changed structure)
  118. Updated News Feed verbosity behaviour. FB limits 40 posts in News Feed. Show either no notification or 1 post hidden. 2+ posts hidden notification disabled.
  119. Groups Feed posts - added icon to open post in new window (fix annoying FB bug with not showing comments properly)
  120.  
  121. Attribution: Mop & Bucket icon:
  122. - made by Freepik (https://www.freepik.com) @ flaticon (https://www.flaticon.com/)
  123. - page: https://www.flaticon.com/premium-icon/mop_2383747
  124.  
  125.  
  126. Instructions on how to use:
  127. - In FB, top right corner or bottom left corner, click on the "Clean my feeds" icon (mop + bucket)
  128. - Alternatively, click on the script manager icon in the menu bar and select "Settings" under FB - Clean my feeds
  129. - Toggle the various options
  130. - Click Save then Close.
  131. - It is recommended that you Export your settings every now and then.
  132. (When your browser flushes the cache, your settings are deleted).
  133.  
  134. Known issue(s):
  135. - Settings are not saved in Private/Incognito mode when using Firefox.
  136. - For Chrome/Edge, in Private/Incognito mode, settings are retained until browser is closed.
  137.  
  138. \\\ --- No need to amend any of the code below --- ///
  139. */
  140.  
  141. // -- need version 8 for async/await
  142. esversion: 8;
  143.  
  144. (async function () {
  145.  
  146. 'use strict';
  147.  
  148. // -- TM doesn't like spacesin version number, so convert to human-readable-format.
  149. const SCRIPT_VERSION = 'v' + GM.info.script.version.replaceAll('-', ' ');
  150.  
  151. // Due to a GreaseMonkey bug with @require, we've copied an external script into here.
  152. // @require https://unpkg.com/idb-keyval@6.0.3/dist/umd.js
  153. function _typeof(n) { return (_typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (n) { return typeof n } : function (n) { return n && "function" == typeof Symbol && n.constructor === Symbol && n !== Symbol.prototype ? "symbol" : typeof n })(n) } !function (n, t) { "object" === ("undefined" == typeof exports ? "undefined" : _typeof(exports)) && "undefined" != typeof module ? t(exports) : "function" == typeof define && define.amd ? define(["exports"], t) : t((n = "undefined" != typeof globalThis ? globalThis : n || self).idbKeyval = {}) }(this, (function (n) { "use strict"; function t(n) { return new Promise((function (t, e) { n.oncomplete = n.onsuccess = function () { return t(n.result) }, n.onabort = n.onerror = function () { return e(n.error) } })) } function e(n, e) { var r, o = (!navigator.userAgentData && /Safari\//.test(navigator.userAgent) && !/Chrom(e|ium)\//.test(navigator.userAgent) && indexedDB.databases ? new Promise((function (n) { var t = function () { return indexedDB.databases().finally(n) }; r = setInterval(t, 100), t() })).finally((function () { return clearInterval(r) })) : Promise.resolve()).then((function () { var r = indexedDB.open(n); return r.onupgradeneeded = function () { return r.result.createObjectStore(e) }, t(r) })); return function (n, t) { return o.then((function (r) { return t(r.transaction(e, n).objectStore(e)) })) } } var r; function o() { return r || (r = e("keyval-store", "keyval")), r } function u(n, e) { return n("readonly", (function (n) { return n.openCursor().onsuccess = function () { this.result && (e(this.result), this.result.continue()) }, t(n.transaction) })) } n.clear = function () { var n = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : o(); return n("readwrite", (function (n) { return n.clear(), t(n.transaction) })) }, n.createStore = e, n.del = function (n) { var e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : o(); return e("readwrite", (function (e) { return e.delete(n), t(e.transaction) })) }, n.delMany = function (n) { var e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : o(); return e("readwrite", (function (e) { return n.forEach((function (n) { return e.delete(n) })), t(e.transaction) })) }, n.entries = function () { var n = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : o(), t = []; return u(n, (function (n) { return t.push([n.key, n.value]) })).then((function () { return t })) }, n.get = function (n) { var e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : o(); return e("readonly", (function (e) { return t(e.get(n)) })) }, n.getMany = function (n) { var e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : o(); return e("readonly", (function (e) { return Promise.all(n.map((function (n) { return t(e.get(n)) }))) })) }, n.keys = function () { var n = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : o(), t = []; return u(n, (function (n) { return t.push(n.key) })).then((function () { return t })) }, n.promisifyRequest = t, n.set = function (n, e) { var r = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : o(); return r("readwrite", (function (r) { return r.put(e, n), t(r.transaction) })) }, n.setMany = function (n) { var e = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : o(); return e("readwrite", (function (e) { return n.forEach((function (n) { return e.put(n[1], n[0]) })), t(e.transaction) })) }, n.update = function (n, e) { var r = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : o(); return r("readwrite", (function (r) { return new Promise((function (o, u) { r.get(n).onsuccess = function () { try { r.put(e(this.result), n), o(t(r.transaction)) } catch (n) { u(n) } } })) })) }, n.values = function () { var n = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : o(), t = []; return u(n, (function (n) { return t.push(n.value) })).then((function () { return t })) }, Object.defineProperty(n, "__esModule", { value: !0 }) }));
  154.  
  155. // *** *** Language components *** ***
  156. const KeyWords = {
  157. // *** Which languages have been setup (for dialog box)
  158. // -- not used as keywords for detection of anything
  159.  
  160. // - 'en' is default.
  161. LANGUAGES: [
  162. 'en', // English
  163. 'pt', // Português (Portugal and Brazil)
  164. 'de', // Deutsch (Germany)
  165. 'fr', // Français (France)
  166. 'es', // Espanol (Spain)
  167. 'cs', // Čeština (Czechia)
  168. 'vi', // Tiếng Việt (Vietnam)
  169. 'it', // Italino (Italy)
  170. 'lv', // Latviešu (Latvia)
  171. 'pl', // Polski (Poland)
  172. 'nl', // Nederlands (Netherlands)
  173. 'he', // עִברִית (Hebrew)
  174. 'ar', // العربية (Arabic)
  175. 'id', // Bahasa Indonesia (Indonesia)
  176. 'zh-Hans', // Chinese (Simplified)
  177. 'zh-Hant', // Chinese (Traditional)
  178. 'ja', // Japanese (Japan)
  179. 'fi', // Suomi - Finnish (Finland)
  180. 'tr', // Türkçe (Turkey)
  181. 'el', // Ελληνικά (Greece)
  182. 'ru', // Русский (Russia)
  183. 'uk', // Україна (Ukraine)
  184. 'bg', // България (Bulgaria)
  185. ],
  186.  
  187. // *** sample Object setup:
  188. // _VARIABLE_NAME_: {
  189. // 'en': '',
  190. // 'pt': '',
  191. // 'de': '',
  192. // 'fr': '',
  193. // 'es': '',
  194. // 'cs': '',
  195. // 'vi': '',
  196. // 'it': '',
  197. // 'lv': '',
  198. // 'pl': '',
  199. // 'nl': '',
  200. // 'he': '',
  201. // 'ar': '',
  202. // 'id': '',
  203. // 'zh-Hans': '',
  204. // 'zh-Hant': '',
  205. // 'ja': '',
  206. // 'fi': '',
  207. // 'tr': '',
  208. // 'el': '',
  209. // 'ru': '',
  210. // 'uk': '',
  211. // 'bg': '',
  212. // },
  213.  
  214.  
  215. // - Sponsored
  216. SPONSORED: {
  217. 'en': 'Sponsored',
  218. 'pt': 'Patrocinado',
  219. 'de': 'Gesponsert',
  220. 'fr': 'Sponsorisé',
  221. 'es': 'Publicidad',
  222. 'cs': 'Sponzorováno',
  223. 'vi': 'Được tài trợ',
  224. 'it': 'Sponsorizzato',
  225. 'lv': 'Apmaksāta reklāma',
  226. 'pl': 'Sponsorowane',
  227. 'nl': 'Gesponsord',
  228. 'he': 'ממומן',
  229. 'ar': 'مُموَّل',
  230. 'id': 'Bersponsor',
  231. 'zh-Hans': '赞助内容',
  232. 'zh-Hant': '贊助',
  233. 'ja': '広告',
  234. 'fi': 'Sponsoroitu',
  235. 'tr': 'Sponsorlu',
  236. 'el': 'Χορηγούμενη',
  237. 'ru': 'Реклама',
  238. 'uk': 'Спонсорована',
  239. 'bg': 'Спонсорирано',
  240. 'defaultEnabled': true
  241. },
  242.  
  243. // *** News Feed ::
  244.  
  245. // - "Stories | Reels | Rooms" tablist box
  246. // -- includes the standalone Stories component
  247. NF_TABLIST_STORIES_REELS_ROOMS: {
  248. 'en': '"Stories | Reels | Rooms" tabs list box',
  249. 'pt': 'Caixa de listagem da guia "Stories | Vídeos do Reels | Salas"',
  250. 'de': 'Listenfeld der Registerkarte "Stories | Reels | Rooms"',
  251. 'fr': 'Zone de liste de l\'onglet "Stories | Reels | Salons"',
  252. 'es': 'Cuadro de lista de la pestaña "Historias | Reels | Salas"',
  253. 'cs': 'Seznam karet "Stories | Reels | Místnosti"',
  254. 'vi': 'Hộp danh sách tab "Tin | Reels | Phòng họp mặt"',
  255. 'it': 'Casella di riepilogo della scheda "Storie | Reels | Stanze"',
  256. 'lv': 'Cilnes "Stāsti | Video rullīši | Rooms" sarakstlodziņš',
  257. 'pl': 'Pole listy zakładki "Relacje | Reels | Pokoje"',
  258. 'nl': 'Keuzelijst tabblad "Verhalen | Reels | Ruimtes"',
  259. 'he': 'תיבת רשימה של כרטיסיות "סטוריז | Reels | חדרים"',
  260. 'ar': '"القصص | ريلز | الغرف" مربع قائمة علامات تبويب',
  261. 'id': 'Kotak daftar tab "Cerita | Reels | Forum"',
  262. 'zh-Hans': '“快拍|Reels|畅聊室”选项卡列表框',
  263. 'zh-Hant': '"限時動態 | Reels | 包廂" 分頁列表框',
  264. 'ja': '「Stories | Reels | Rooms」タブのリストボックス',
  265. 'fi': '"Tarinat | Reels | Rooms" -välilehtien luetteloruutu',
  266. 'tr': '"Hikayeler | Makaralar | Odalar" sekmeleri liste kutusu',
  267. 'el': 'Λίστα καρτελών "Ιστορίες | Reels | Δωμάτια"',
  268. 'ru': 'Список вкладок "Истории | Reels | Комнаты"',
  269. 'uk': 'Поле списку вкладок «Історії | Reels | Кімнати»',
  270. 'bg': 'Списъчно поле на раздела „Истории | Макари | Стаи“',
  271. 'defaultEnabled': false
  272. },
  273. // - "Stories" posts (separate from tablist, in News Feed stream)
  274. NF_STORIES: {
  275. 'en': 'Stories',
  276. 'pt': 'Stories',
  277. 'de': 'Stories',
  278. 'fr': 'Stories',
  279. 'es': 'Historias',
  280. 'cs': 'Stories',
  281. 'vi': 'Tin',
  282. 'it': 'Storie',
  283. 'lv': 'Stāsti',
  284. 'pl': 'Relacje',
  285. 'nl': 'Verhalen',
  286. 'he': 'סטוריז ',
  287. 'ar': 'القصص',
  288. 'id': 'Cerita',
  289. 'zh-Hans': '故事',
  290. 'zh-Hant': '故事',
  291. 'ja': 'Stories',
  292. 'fi': 'Tarinat',
  293. 'tr': 'Hikayeler',
  294. 'el': 'Ιστορίες',
  295. 'ru': 'Истории',
  296. 'uk': 'Історії',
  297. 'bg': 'Истории',
  298. 'defaultEnabled': false
  299. },
  300. // - fb's survey
  301. // - <name>, we're asking a small group of people for their opinion
  302. // - could you take a few minutes to answer a short survey? <button>start survey</button>
  303. NF_SURVEY: {
  304. 'en': 'Survey',
  305. 'pt': 'Enquete',
  306. 'de': 'Umfrage',
  307. 'fr': 'Enquête',
  308. 'es': 'Encuesta',
  309. 'cs': 'Průzkum',
  310. 'vi': 'Khảo sát',
  311. 'it': 'Sondaggio',
  312. 'lv': 'Aptauja',
  313. 'pl': 'Badanie',
  314. 'nl': 'Vragenlijst',
  315. 'he': 'סקר',
  316. 'ar': 'استبيان',
  317. 'id': 'Survei',
  318. 'zh-Hans': '调查',
  319. 'zh-Hant': '調查',
  320. 'ja': 'アンケート',
  321. 'fi': 'Kysely',
  322. 'tr': 'Anket',
  323. 'el': 'Τοπογράφηση',
  324. 'ru': 'Опрос',
  325. 'uk': 'Опитування',
  326. 'bg': 'Анкета',
  327. 'defaultEnabled': false
  328. },
  329.  
  330. // - People you may know:
  331. NF_PEOPLE_YOU_MAY_KNOW: {
  332. 'en': 'People you may know',
  333. 'pt': 'Pessoas que talvez conheças',
  334. 'de': 'Personen, die du kennen könntest',
  335. 'fr': 'Connaissez-vous...',
  336. 'es': 'Personas que quizá conozcas',
  337. 'cs': 'Koho možná znáte',
  338. 'vi': 'Những người bạn có thể biết',
  339. 'it': 'Persone che potresti conoscere',
  340. 'lv': 'Cilvēki, kurus tu varētu pazīt',
  341. 'pl': 'Osoby, które możesz znać',
  342. 'nl': 'Mensen die je misschien kent',
  343. 'he': 'אנשים שאולי אתה מכיר',
  344. 'ar': 'أشخاص قد تعرفهم',
  345. 'id': 'Orang yang Mungkin Anda Kenal',
  346. 'zh-Hans': '你可能认识的人',
  347. 'zh-Hant': '你可能認識的人',
  348. 'ja': 'あなたが知っているかもしれない人々',
  349. 'fi': 'Ihmiset, jotka saatat tuntea',
  350. 'tr': 'Tanıyor olabileceğin kişiler',
  351. 'el': 'Άτομα που ίσως γνωρίζετε',
  352. 'ru': 'Люди, которых вы можете знать',
  353. 'uk': 'Люди, яких Ви можете знати',
  354. 'bg': 'Хора, които може би познавате',
  355. 'defaultEnabled': false
  356. },
  357.  
  358. // - Paid partnership:
  359. // - page you follow is "sponsoring" another page's post (e.g. job)
  360. NF_PAID_PARTNERSHIP: {
  361. 'en': 'Paid partnership',
  362. 'pt': 'Parceria paga',
  363. 'de': 'Bezahlte Werbepartnerschaft',
  364. 'fr': 'Partenariat rémunéré',
  365. 'es': 'Colaboración pagada',
  366. 'cs': 'Placené partnerství',
  367. 'vi': 'Mối quan hệ tài trợ',
  368. 'it': 'Partnership pubblicizzata',
  369. 'lv': 'Apmaksāta sadarbība',
  370. 'pl': 'Post sponsorowany',
  371. 'nl': 'Betaald partnerschap',
  372. 'he': 'שותפות בתשלום',
  373. 'ar': 'شراكة مدفوعة',
  374. 'id': 'Kemitraan berbayar',
  375. 'zh-Hans': '付费合伙',
  376. 'zh-Hant': '付費合作',
  377. 'ja': '有償パートナーシップ',
  378. 'fi': 'Maksettu kumppanuus',
  379. 'tr': 'ücretli ortaklık',
  380. 'el': 'Πληρωμένη συνεργασία',
  381. 'ru': 'Платное партнерство',
  382. 'uk': 'Оплачуване партнерство',
  383. 'bg': 'Платено партньорство',
  384. 'defaultEnabled': true
  385. },
  386.  
  387. // - Sponsored · Paid for by ______ :
  388. // - (paid advertising)
  389. NF_SPONSORED_PAID: {
  390. 'en': 'Sponsored · Paid for by ______',
  391. 'pt': 'Patrocinado · Financiado por ______',
  392. 'de': 'Gesponsert · Finanziert von ______',
  393. 'fr': 'Sponsorisé · Financé par ______',
  394. 'es': 'Publicidad · Pagado por ______',
  395. 'cs': 'Sponzorováno · Platí za to ______',
  396. 'vi': 'Được tài trợ · Tài trợ bởi ______',
  397. 'it': 'Sponsorizzato · Finanziato da ______',
  398. 'lv': 'Apmaksāta reklāma · Apmaksā ______',
  399. 'pl': 'Sponsorowane · Opłacona przez ______',
  400. 'nl': 'Gesponsord · Betaald door ______',
  401. 'he': 'ממומן · שולם על ידי ______',
  402. 'ar': 'برعاية · مدفوعة بواسطة ______',
  403. 'id': 'Disponsori · Dibayar oleh ______',
  404. 'zh-Hans': '赞助 · 由 ______ 付费',
  405. 'zh-Hant': '贊助 · 出資者:______',
  406. 'ja': '後援 · ______ による支払い',
  407. 'fi': 'Sponsoroitu · Maksaja ______',
  408. 'tr': 'Sponsorlu · ______ tarafından ödendi',
  409. 'el': 'Χορηγούμενο · Πληρωμένο από ______',
  410. 'ru': 'Реклама · Оплачено ______',
  411. 'uk': 'Спонсоровано · Оплачено ______',
  412. 'bg': 'Спонсорирано · Платено от ______',
  413. 'defaultEnabled': true
  414. },
  415.  
  416. // - Various Suggested/recommendations type posts
  417. NF_SUGGESTIONS: {
  418. 'en': 'Suggestions / Recommendations',
  419. 'pt': 'Sugestões / Recomendações',
  420. 'de': 'Vorschläge / Empfehlungen',
  421. 'fr': 'Suggestions / Recommandations',
  422. 'es': 'Sugerencias / Recomendaciones',
  423. 'cs': 'Návrhy / Doporučení',
  424. 'vi': 'Đề xuất / Khuyến nghị',
  425. 'it': 'Suggerimenti / Raccomandazioni',
  426. 'lv': 'Ieteikumi',
  427. 'pl': 'Sugestie / Zalecenia',
  428. 'nl': 'Suggesties / Aanbevelingen',
  429. 'he': 'הצעות / המלצות',
  430. 'ar': 'الاقتراحات / التوصيات',
  431. 'id': 'Saran / Rekomendasi',
  432. 'zh-Hans': '建议',
  433. 'zh-Hant': '建議/推薦',
  434. 'ja': '提案/推奨事項',
  435. 'fi': 'Ehdotuksia / Suosituksia',
  436. 'tr': 'Öneriler',
  437. 'el': 'Προτάσεις / Συστάσεις',
  438. 'ru': 'Предложения / Рекомендации',
  439. 'uk': 'Пропозиції / Рекомендації',
  440. 'bg': 'Предложения / Препоръки',
  441. 'defaultEnabled': false
  442. },
  443.  
  444. // - Follow
  445. NF_FOLLOW: {
  446. 'en': 'Follow',
  447. 'pt': 'Seguir',
  448. 'de': 'Folgen',
  449. 'fr': 'Suivre',
  450. 'es': 'Seguir',
  451. 'cs': 'Sledovat',
  452. 'vi': 'Theo dõi',
  453. 'it': 'Segui',
  454. 'lv': 'Sekot',
  455. 'pl': 'Obserwuj',
  456. 'nl': 'Volgen',
  457. 'he': 'עקוב',
  458. 'ar': 'تابع',
  459. 'id': 'Ikuti',
  460. 'zh-Hans': '关注',
  461. 'zh-Hant': '追蹤',
  462. 'ja': 'フォロー',
  463. 'fi': 'Seuraa',
  464. 'tr': 'Takip Et',
  465. 'el': 'Ακολούθησε',
  466. 'ru': 'Подписаться',
  467. 'uk': 'Слідуйте',
  468. 'bg': 'Следвай',
  469. 'defaultEnabled': false
  470. },
  471.  
  472. // - Participate
  473. NF_PARTICIPATE: {
  474. 'en': 'Participate',
  475. 'pt': 'Participar',
  476. 'de': 'Teilnehmen',
  477. 'fr': 'Participer',
  478. 'es': 'Participar',
  479. 'cs': 'Participovat',
  480. 'vi': 'Tham gia',
  481. 'it': 'Partecipare',
  482. 'lv': 'Piedalīties',
  483. 'pl': 'Uczestniczyć',
  484. 'nl': 'Deelnemen',
  485. 'he': 'השתתף',
  486. 'ar': 'المشاركة',
  487. 'id': 'Berpartisipasi',
  488. 'zh-Hans': '参与',
  489. 'zh-Hant': '參與',
  490. 'ja': '参加する',
  491. 'fi': 'Osallistua',
  492. 'tr': 'Katılmak',
  493. 'el': 'Συμμετέχω',
  494. 'ru': 'Участвовать',
  495. 'uk': 'Беріть участь',
  496. 'bg': 'Участвай',
  497. 'defaultEnabled': false
  498. },
  499.  
  500. // - Reels and short videos:
  501. NF_REELS_SHORT_VIDEOS: {
  502. 'en': 'Reels and short videos',
  503. 'pt': 'Vídeos do Reels e vídeos de curta duração',
  504. 'de': 'Reels und Kurzvideos',
  505. 'fr': 'Reels et vidéos courtes',
  506. 'es': 'Reels y vídeos cortos',
  507. 'cs': 'Sekvence a krátká videa',
  508. 'vi': 'Reels và video ngắn',
  509. 'it': 'Reel e video brevi',
  510. 'lv': 'Reels un īsi videoklipi',
  511. 'pl': 'Rolki i krótkie filmy',
  512. 'nl': 'Reels en korte video\'s',
  513. 'he': 'סרטוני Reels וקטעי וידאו קצרים',
  514. 'ar': 'ريلز ومقاطع الفيديو القصيرة',
  515. 'id': 'Reels dan Video Pendek',
  516. 'zh-Hans': '卷轴和短视频',
  517. 'zh-Hant': 'Reels 和短影片',
  518. 'ja': 'リールとショート動画',
  519. 'fi': 'Keloja ja lyhyitä videoita',
  520. 'tr': 'Makaralar ve kısa videolar',
  521. 'el': 'Reel και σύντομα βίντεο',
  522. 'ru': 'Reels и короткие видео',
  523. 'uk': 'Відео Reels і короткі відео',
  524. 'bg': 'Ленти и кратки видеоклипове',
  525. 'defaultEnabled': false
  526. },
  527.  
  528. // - Reel/short video posts
  529. NF_SHORT_REEL_VIDEO: {
  530. 'en': 'Reel/short video',
  531. 'pt': 'Rolo/vídeo curto',
  532. 'de': 'Reel/kurzes Video',
  533. 'fr': 'Bobine/courte vidéo',
  534. 'es': 'Reel/video corto',
  535. 'cs': 'Naviják/krátké video',
  536. 'vi': 'Reel / video ngắn',
  537. 'it': 'Bobina/breve video',
  538. 'lv': 'Ruļļa/īss video',
  539. 'pl': 'Reel/krótki film',
  540. 'nl': 'Spoel/korte video',
  541. 'he': 'סליל/סרטון קצר',
  542. 'ar': 'بكرة / فيديو قصير',
  543. 'id': 'Reel/video pendek',
  544. 'zh-Hans': '卷轴/短视频',
  545. 'zh-Hant': 'Reel/短影片',
  546. 'ja': 'リール/ショートビデオ',
  547. 'fi': 'Kela/lyhyt video',
  548. 'tr': 'makara/kısa video',
  549. 'el': 'Ριλς/μικρό βίντεο',
  550. 'ru': 'Reels/короткое видео',
  551. 'uk': 'Reel/коротке відео',
  552. 'bg': 'Рил/късо видео',
  553. 'defaultEnabled': false
  554. },
  555.  
  556. // - Events you may like:
  557. NF_EVENTS_YOU_MAY_LIKE: {
  558. 'en': 'Events you may like',
  559. 'pt': 'Eventos que você pode gostar',
  560. 'de': 'Veranstaltungen, die Ihnen gefallen könnten',
  561. 'fr': 'Évènements qui pourraient vous intéresser',
  562. 'es': 'Eventos que te pueden gustar',
  563. 'cs': 'Události, které se vám mohou líbit',
  564. 'vi': 'Sự kiện bạn có thể thích',
  565. 'it': 'Eventi che potrebbero piacerti',
  566. 'lv': 'Notikumi, kas jums varētu patikt',
  567. 'pl': 'Wydarzenia, które mogą Ci się spodobać',
  568. 'nl': 'Evenementen die je misschien leuk vindt',
  569. 'he': 'אירועים שאולי תאהבו',
  570. 'ar': 'أحداث قد تعجبك',
  571. 'id': 'Acara yang mungkin Anda sukai',
  572. 'zh-Hans': '您可能喜欢的活动',
  573. 'zh-Hant': '你可能感興趣的活動',
  574. 'ja': 'リール/ショートビデオ',
  575. 'fi': 'Kela/lyhyt video',
  576. 'tr': 'makara/kısa video',
  577. 'el': 'Εκδηλώσεις που μπορεί να σας αρέσουν',
  578. 'ru': 'Мероприятия, которые вам могут понравиться',
  579. 'uk': 'Події, які можуть вам сподобатися',
  580. 'bg': 'Събития, които може да ви харесат',
  581. 'defaultEnabled': false,
  582. },
  583.  
  584. // - pause animated GIFs:
  585. NF_ANIMATED_GIFS: {
  586. 'en': 'Pause animated GIFs',
  587. 'pt': 'Pausar GIFs animados',
  588. 'de': 'Animierte GIFs pausieren',
  589. 'fr': 'Mettre en pause les GIF animés',
  590. 'es': 'Pausar GIF animados',
  591. 'cs': 'Pozastavit animované GIFy',
  592. 'vi': 'Tạm dừng các ảnh GIF động',
  593. 'it': 'Metti in pausa le GIF animate',
  594. 'lv': 'Apturiet animētos GIF',
  595. 'pl': 'Wstrzymaj animowane GIF-y',
  596. 'nl': 'Geanimeerde GIF\'s pauzeren',
  597. 'he': 'השהה קובצי GIF מונפשים',
  598. 'ar': 'وقفة GIF المتحركة',
  599. 'id': 'Jeda GIF animasi',
  600. 'zh-Hans': '暂停动画 GIF',
  601. 'zh-Hant': '暫停 GIF 動畫',
  602. 'ja': 'アニメーション GIF を一時停止する',
  603. 'fi': 'Keskeytä animoidut GIF-kuvat',
  604. 'tr': 'Hareketli GIF\'leri duraklat',
  605. 'el': 'Παύση κινούμενων GIF',
  606. 'ru': 'Приостановить анимированные GIF',
  607. 'uk': 'Призупинити анімовані GIF-файли',
  608. 'bg': 'Пауза на анимирани GIF файлове',
  609. 'defaultEnabled': false
  610. },
  611.  
  612. // - # shares
  613. NF_SHARES: {
  614. 'en': '# shares',
  615. 'pt': '# partilhas',
  616. 'de': '# Mal geteilt',
  617. 'fr': '# partages',
  618. 'es': '# veces compartida',
  619. 'cs': '# sdílení',
  620. 'vi': '# lượt chia sẻ',
  621. 'it': 'Condivisioni: #',
  622. 'lv': '# dalījās',
  623. 'pl': '# udostępnienia',
  624. 'nl': '# keer gedeeld',
  625. 'he': '# שיתופים',
  626. 'ar': '# مشاركات',
  627. 'id': '# Kali dibagikan',
  628. 'zh-Hans': '#次分享',
  629. 'zh-Hant': '#次分享',
  630. 'ja': 'シェア#件',
  631. 'fi': '# jakoa',
  632. 'tr': '# Paylaşım',
  633. 'el': '# μερίδια',
  634. 'ru': '# поделились',
  635. 'uk': '# Поширити',
  636. 'bg': '# споделяния',
  637. 'defaultEnabled': false
  638. },
  639.  
  640. // -- Maximum Likes
  641. // -- hide posts having excessive Likes
  642. // -- user's input count value is stored in NF_LIKES_MAXIMUM_COUNT
  643. NF_LIKES_MAXIMUM: {
  644. 'en': 'Maximum number of Likes',
  645. 'pt': 'Número máximo de curtidas',
  646. 'de': 'Maximale Anzahl an Likes',
  647. 'fr': 'Nombre maximum de J\'aime',
  648. 'es': 'Número máximo de Me gusta',
  649. 'cs': 'Maximální počet hodnocení Líbí se mi',
  650. 'vi': 'Số lượt thích tối đa',
  651. 'it': 'Numero massimo di Mi piace',
  652. 'lv': 'Maksimālais atzīmju Patīk skaits',
  653. 'pl': 'Maksymalna ilość "Lubię to!"',
  654. 'nl': 'Maximaal aantal likes',
  655. 'he': 'מספר לייקים מקסימלי',
  656. 'ar': 'الحد الأقصى لعدد الإعجابات',
  657. 'id': 'Jumlah maksimum Suka',
  658. 'zh-Hans': '最大点赞数',
  659. 'zh-Hant': '最大按讚數',
  660. 'ja': '「いいね!」の最大数',
  661. 'fi': 'Maksimimäärä tykkäyksiä',
  662. 'tr': 'Maksimum Beğeni sayısı',
  663. 'el': 'Μέγιστα "Μου αρέσει"',
  664. 'ru': 'Максимальное количество «Нравится»',
  665. 'uk': 'Максимальна кількість «Подобається».',
  666. 'bg': 'Максимален брой Харесвания',
  667. 'defaultEnabled': false
  668. },
  669.  
  670. // *** Groups Feed ::
  671.  
  672. // - Paid partnership:
  673. // - a page you follow is "sponsoring" another page's post (e.g. job)
  674. GF_PAID_PARTNERSHIP: {
  675. 'en': 'Paid partnership',
  676. 'pt': 'Parceria paga',
  677. 'de': 'Bezahlte Werbepartnerschaft',
  678. 'fr': 'Partenariat rémunéré',
  679. 'es': 'Colaboración pagada',
  680. 'cs': 'Placené partnerství',
  681. 'vi': 'Mối quan hệ tài trợ',
  682. 'it': 'Partnership pubblicizzata',
  683. 'lv': 'Apmaksāta sadarbība',
  684. 'pl': 'Post sponsorowany',
  685. 'nl': 'Betaald partnerschap',
  686. 'he': 'שותפות בתשלום',
  687. 'ar': 'شراكة مدفوعة',
  688. 'id': 'Kemitraan berbayar',
  689. 'zh-Hans': '有偿合作',
  690. 'zh-Hant': '付費合作',
  691. 'ja': '有償パートナーシップ',
  692. 'fi': 'Maksettu kumppanuus',
  693. 'tr': 'ücretli ortaklık',
  694. 'el': 'Πληρωμένη συνεργασία',
  695. 'ru': 'Платное партнерство',
  696. 'uk': 'Оплачуване партнерство',
  697. 'bg': 'Платено партньорство',
  698. 'defaultEnabled': true
  699. },
  700.  
  701. // - Various suggested/recommendations:
  702. GF_SUGGESTIONS: {
  703. 'en': 'Suggestions / Recommendations',
  704. 'pt': 'Sugestões / Recomendações',
  705. 'de': 'Vorschläge / Empfehlungen',
  706. 'fr': 'Suggestions / Recommandations',
  707. 'es': 'Sugerencias / Recomendaciones',
  708. 'cs': 'Návrhy / Doporučení',
  709. 'vi': 'Đề xuất / Khuyến nghị',
  710. 'it': 'Suggerimenti / Raccomandazioni',
  711. 'lv': 'Ieteikumi',
  712. 'pl': 'Sugestie / Zalecenia',
  713. 'nl': 'Suggesties / Aanbevelingen',
  714. 'he': 'הצעות / המלצות',
  715. 'ar': 'الاقتراحات / التوصيات',
  716. 'id': 'Saran / Rekomendasi',
  717. 'zh-Hans': '建议/建议',
  718. 'zh-Hant': '建議/推薦',
  719. 'ja': '提案/推奨事項',
  720. 'fi': 'Ehdotuksia / Suosituksia',
  721. 'tr': 'Öneriler',
  722. 'el': 'Προτάσεις / Συστάσεις',
  723. 'ru': 'Предложения / Рекомендации',
  724. 'uk': 'Пропозиції / Рекомендації',
  725. 'bg': 'Предложения / Препоръки',
  726. 'defaultEnabled': false
  727. },
  728.  
  729. // - Reel/short video posts
  730. GF_SHORT_REEL_VIDEO: {
  731. 'en': 'Reel/short video',
  732. 'pt': 'Rolo/vídeo curto',
  733. 'de': 'Reel/kurzes Video',
  734. 'fr': 'Bobine/courte vidéo',
  735. 'es': 'Reel/video corto',
  736. 'cs': 'Naviják/krátké video',
  737. 'vi': 'Reel / video ngắn',
  738. 'it': 'Bobina/breve video',
  739. 'lv': 'Ruļļa/īss video',
  740. 'pl': 'Reel/krótki film',
  741. 'nl': 'Spoel/korte video',
  742. 'he': 'סליל/סרטון קצר',
  743. 'ar': 'بكرة / فيديو قصير',
  744. 'id': 'Reel/video pendek',
  745. 'zh-Hans': '卷轴和短视频',
  746. 'zh-Hant': 'Reel/短影片',
  747. 'ja': 'リールとショートビデオ',
  748. 'fi': 'Keloja ja lyhyitä videoita',
  749. 'tr': 'makara/kısa video',
  750. 'el': 'Ριλς/μικρό βίντεο',
  751. 'ru': 'Reel/короткое видео',
  752. 'uk': 'Reel/коротке відео',
  753. 'bg': 'Рил/късо видео',
  754. 'defaultEnabled': false
  755. },
  756.  
  757. // - pause animated GIFs:
  758. GF_ANIMATED_GIFS: {
  759. 'en': 'Pause animated GIFs',
  760. 'pt': 'Pausar GIFs animados',
  761. 'de': 'Animierte GIFs pausieren',
  762. 'fr': 'Mettre en pause les GIF animés',
  763. 'es': 'Pausar GIF animados',
  764. 'cs': 'Pozastavit animované GIFy',
  765. 'vi': 'Tạm dừng các ảnh GIF động',
  766. 'it': 'Metti in pausa le GIF animate',
  767. 'lv': 'Apturiet animētos GIF',
  768. 'pl': 'Wstrzymaj animowane GIF-y',
  769. 'nl': 'Geanimeerde GIF\'s pauzeren',
  770. 'he': 'השהה קובצי GIF מונפשים',
  771. 'ar': 'وقفة GIF المتحركة',
  772. 'id': 'Jeda GIF animasi',
  773. 'zh-Hans': '暂停动画 GIF',
  774. 'zh-Hant': '暫停 GIF 動畫',
  775. 'ja': 'リール/ショートビデオ',
  776. 'fi': 'Kela/lyhyt video',
  777. 'tr': 'Hareketli GIF\'leri duraklat',
  778. 'el': 'Παύση κινούμενων GIF',
  779. 'ru': 'Приостановить анимированные GIF',
  780. 'uk': 'Призупинити анімовані GIF-файли',
  781. 'bg': 'Пауза на анимирани GIF файлове',
  782. 'defaultEnabled': false,
  783. },
  784. // - # shares
  785. GF_SHARES: {
  786. 'en': '# shares',
  787. 'pt': '# partilhas',
  788. 'de': '# Mal geteilt',
  789. 'fr': '# partages',
  790. 'es': '# veces compartida',
  791. 'cs': '# sdílení',
  792. 'vi': '# lượt chia sẻ',
  793. 'it': 'Condivisioni: #',
  794. 'lv': '# dalījās',
  795. 'pl': '# udostępnienia',
  796. 'nl': '# keer gedeeld',
  797. 'he': '# שיתופים',
  798. 'ar': '# مشاركات',
  799. 'id': '# Kali dibagikan',
  800. 'zh-Hans': '#次分享',
  801. 'zh-Hant': '#次分享',
  802. 'ja': 'シェア#件',
  803. 'fi': '# jakoa',
  804. 'tr': '# Paylaşım',
  805. 'el': '# μερίδια',
  806. 'ru': '# поделились',
  807. 'uk': '# Поширити',
  808. 'bg': '# споделяния',
  809. 'defaultEnabled': false
  810. },
  811.  
  812. // *** Watch Videos Feed ::
  813.  
  814. // - LIVE videos:
  815. VF_LIVE: {
  816. 'en': 'LIVE',
  817. 'pt': 'DIRETO',
  818. 'de': 'LIVE',
  819. 'fr': 'EN DIRECT',
  820. 'es': 'ESTRENO',
  821. 'cs': 'ŽIVĚ',
  822. 'vi': 'TRỰC TIẾP',
  823. 'it': 'IN DIRETTA',
  824. 'lv': 'TIEŠRAIDE',
  825. 'pl': 'NA ŻYWO',
  826. 'nl': 'LIVE',
  827. 'he': 'שידור חי',
  828. 'ar': 'مباشر',
  829. 'id': 'LANGSUNG',
  830. 'zh-Hans': '现场直播',
  831. 'zh-Hant': '現場直播',
  832. 'ja': 'ライブ',
  833. 'fi': 'LIVE',
  834. 'tr': 'CANLI',
  835. 'el': 'ΖΩΝΤΑΝΑ',
  836. 'ru': 'В ЭФИРЕ',
  837. 'uk': 'ЕФІР',
  838. 'bg': 'НА ЖИВО',
  839. 'defaultEnabled': false,
  840. },
  841.  
  842. // - pause animated GIFs:
  843. VF_ANIMATED_GIFS: {
  844. 'en': 'Pause animated GIFs',
  845. 'pt': 'Pausar GIFs animados',
  846. 'de': 'Animierte GIFs pausieren',
  847. 'fr': 'Mettre en pause les GIF animés',
  848. 'es': 'Pausar GIF animados',
  849. 'cs': 'Pozastavit animované GIFy',
  850. 'vi': 'Tạm dừng các ảnh GIF động',
  851. 'it': 'Metti in pausa le GIF animate',
  852. 'lv': 'Apturiet animētos GIF',
  853. 'pl': 'Wstrzymaj animowane GIF-y',
  854. 'nl': 'Geanimeerde GIF\'s pauzeren',
  855. 'he': 'השהה קובצי GIF מונפשים',
  856. 'ar': 'وقفة GIF المتحركة',
  857. 'id': 'Jeda GIF animasi',
  858. 'zh-Hans': '暂停动画 GIF',
  859. 'zh-Hant': '暫停 GIF 動畫',
  860. 'ja': 'アニメーション GIF を一時停止する',
  861. 'fi': 'Keskeytä animoidut GIF-kuvat',
  862. 'tr': 'Hareketli GIF\'leri duraklat',
  863. 'el': 'Παύση κινούμενων GIF',
  864. 'ru': 'Приостановить анимированные GIF',
  865. 'uk': 'Призупинити анімовані GIF-файли',
  866. 'bg': 'Пауза на анимирани GIF файлове',
  867. 'defaultEnabled': false,
  868. },
  869. // (videos do not have # shares count)
  870.  
  871.  
  872. // *** block text - feeds
  873.  
  874. // - text filter for News Feed:
  875. NF_BLOCKED_FEED: {
  876. 'en': ['News Feed', 'Groups Feed', 'Videos Feed'],
  877. 'pt': ['Feed de notícias', 'Feed de grupos', 'Feed de vídeos'],
  878. 'de': ['Newsfeed', 'Gruppen-Feed', 'Video-Feed'],
  879. 'fr': ['Fil de nouvelles', 'Flux de groupes', 'Flux de vidéos'],
  880. 'es': ['Feed de noticias', 'Feed de grupos', 'Feed de videos'],
  881. 'cs': ['Informační kanál', 'Skupinový kanál', 'Video kanál'],
  882. 'vi': ['Nguồn cấp tin tức', 'Nguồn cấp dữ liệu Nhóm', 'Nguồn cấp dữ liệu video'],
  883. 'it': ['Feed di notizie', 'Feed di gruppo', 'Feed di video'],
  884. 'lv': ['Ziņu plūsma', 'Grupu plūsma', 'Video plūsma'],
  885. 'pl': ['Kanał aktualności', 'Kanał grup', 'Kanał wideo'],
  886. 'nl': ['Nieuwsfeed', 'Groepsfeed', 'Videofeed'],
  887. 'he': ['ניוז פיד', 'פיד קבוצות', 'צפה בפיד סרטונים'],
  888. 'ar': ['موجز الأخبار', 'تغذية المجموعات', 'تغذية الفيديو'],
  889. 'id': ['Umpan Berita', 'Umpan Grup', 'Umpan Video'],
  890. 'zh-Hans': ['新闻提要', '组提要', '视频提要'],
  891. 'zh-Hant': ['新聞動態消息', '群組動態消息', '影片動態消息'],
  892. 'ja': ['ニュースフィード', 'グループ フィード', '動画フィード'],
  893. 'fi': ['Uutissyöte', 'Ryhmäsyöte', 'Videosyöte'],
  894. 'tr': ['Haber akışı', 'Gruplar Feed\'i', 'Video Beslemelerini İzle'],
  895. 'el': ['Ροή ειδήσεων', 'Ροή ομάδων', 'Ροή βίντεο'],
  896. 'ru': ['Лента новостей', 'Лента групп', 'Лента видео'],
  897. 'uk': ['Стрічка новин', 'Стрічка Групи', 'Стрічка відео'],
  898. 'bg': ['Новинарски поток', 'Поток с групи', 'Поток с видеа'],
  899. 'defaultEnabled': ['1', '0', '0'],
  900. },
  901. // - text filter for Groups Feed:
  902. GF_BLOCKED_FEED: {
  903. 'en': ['News Feed', 'Groups Feed', 'Videos Feed'],
  904. 'pt': ['Feed de notícias', 'Feed de grupos', 'Feed de vídeos'],
  905. 'de': ['Newsfeed', 'Gruppen-Feed', 'Video-Feed'],
  906. 'fr': ['Fil de nouvelles', 'Flux de groupes', 'Flux de vidéos'],
  907. 'es': ['Feed de noticias', 'Feed de grupos', 'Feed de videos'],
  908. 'cs': ['Informační kanál', 'Skupinový kanál', 'Video kanál'],
  909. 'vi': ['Nguồn cấp tin tức', 'Nguồn cấp dữ liệu Nhóm', 'Nguồn cấp dữ liệu video'],
  910. 'it': ['Feed di notizie', 'Feed di gruppo', 'Feed di video'],
  911. 'lv': ['Ziņu plūsma', 'Grupu plūsma', 'Video plūsma'],
  912. 'pl': ['Kanał aktualności', 'Kanał grup', 'Kanał wideo'],
  913. 'nl': ['Nieuwsfeed', 'Groepsfeed', 'Videofeed'],
  914. 'he': ['ניוז פיד', 'פיד קבוצות', 'צפה בפיד סרטונים'],
  915. 'ar': ['موجز الأخبار', 'تغذية المجموعات', 'تغذية الفيديو'],
  916. 'id': ['Umpan Berita', 'Umpan Grup', 'Umpan Video'],
  917. 'zh-Hans': ['新闻提要', '组提要', '视频提要'],
  918. 'zh-Hant': ['新聞動態消息', '群組動態消息', '影片動態消息'],
  919. 'ja': ['ニュースフィード', 'グループ フィード', '動画フィード'],
  920. 'fi': ['Uutissyöte', 'Ryhmäsyöte', 'Videosyöte'],
  921. 'tr': ['Haber akışı', 'Gruplar Feed\'i', 'Video Beslemelerini İzle'],
  922. 'el': ['Ροή ειδήσεων', 'Ροή ομάδων', 'Ροή βίντεο'],
  923. 'ru': ['Лента новостей', 'Лента групп', 'Лента видео'],
  924. 'uk': ['Стрічка новин', 'Стрічка Групи', 'Стрічка відео'],
  925. 'bg': ['Новинарски поток', 'Поток с групи', 'Поток с видеа'],
  926. 'defaultEnabled': ['0', '1', '0'],
  927. },
  928. // - text filter for Vidoes Feed:
  929. VF_BLOCKED_FEED: {
  930. 'en': ['News Feed', 'Groups Feed', 'Videos Feed'],
  931. 'pt': ['Feed de notícias', 'Feed de grupos', 'Feed de vídeos'],
  932. 'de': ['Newsfeed', 'Gruppen-Feed', 'Video-Feed'],
  933. 'fr': ['Fil de nouvelles', 'Flux de groupes', 'Flux de vidéos'],
  934. 'es': ['Feed de noticias', 'Feed de grupos', 'Feed de videos'],
  935. 'cs': ['Informační kanál', 'Skupinový kanál', 'Video kanál'],
  936. 'vi': ['Nguồn cấp tin tức', 'Nguồn cấp dữ liệu Nhóm', 'Nguồn cấp dữ liệu video'],
  937. 'it': ['Feed di notizie', 'Feed di gruppo', 'Feed di video'],
  938. 'lv': ['Ziņu plūsma', 'Grupu plūsma', 'Video plūsma'],
  939. 'pl': ['Kanał aktualności', 'Kanał grup', 'Kanał wideo'],
  940. 'nl': ['Nieuwsfeed', 'Groepsfeed', 'Videofeed'],
  941. 'he': ['ניוז פיד', 'פיד קבוצות', 'צפה בפיד סרטונים'],
  942. 'ar': ['موجز الأخبار', 'تغذية المجموعات', 'تغذية الفيديو'],
  943. 'id': ['Umpan Berita', 'Umpan Grup', 'Umpan Video'],
  944. 'zh-Hans': ['新闻提要', '组提要', '视频提要'],
  945. 'zh-Hant': ['新聞動態消息', '群組動態消息', '影片動態消息'],
  946. 'ja': ['ニュースフィード', 'グループ フィード', '動画フィード'],
  947. 'fi': ['Uutissyöte', 'Ryhmäsyöte', 'Videosyöte'],
  948. 'tr': ['Haber akışı', 'Gruplar Feed\'i', 'Video Beslemelerini İzle'],
  949. 'el': ['Ροή ειδήσεων', 'Ροή ομάδων', 'Ροή βίντεο'],
  950. 'ru': ['Лента новостей', 'Лента групп', 'Лента видео'],
  951. 'uk': ['Стрічка новин', 'Стрічка Групи', 'Стрічка відео'],
  952. 'bg': ['Новинарски поток', 'Поток с групи', 'Поток с видеа'],
  953. 'defaultEnabled': ['0', '0', '1'],
  954. },
  955. // - text filter for Marketplace feed:
  956. MP_BLOCKED_FEED: {
  957. 'en': ['Marketplace Feed'],
  958. 'pt': ['Feed de mercado'],
  959. 'de': ['Marktplatz-Feed'],
  960. 'fr': ['Flux de la place de marché'],
  961. 'es': ['Feed de Marketplace'],
  962. 'cs': ['Marketplace kanál'],
  963. 'vi': ['Nguồn cấp dữ liệu Marketplace'],
  964. 'it': ['Feed id Marketplace'],
  965. 'lv': ['Marketplace'],
  966. 'pl': ['Kanał Marketplace'],
  967. 'nl': ['Marktplaatsfeed'],
  968. 'he': ['זירת מסחר'],
  969. 'ar': ['السوق تغذية'],
  970. 'id': ['Umpan Marketplace'],
  971. 'zh-Hans': ['市场提要'],
  972. 'zh-Hant': ['Marketplace 動態消息'],
  973. 'ja': ['マーケットプレイス フィード'],
  974. 'fi': ['Marketplace-syöte'],
  975. 'tr': ['Pazar Yeri Feed\'i'],
  976. 'el': ['Ροή Marketplace'],
  977. 'ru': ['Лента Marketplace'],
  978. 'uk': ['Стрічка Marketplace'],
  979. 'bg': ['Поток с Marketplace'],
  980. 'defaultEnabled': ['1', '0', '0'],
  981. },
  982.  
  983.  
  984. // *** Miscellaneous/Supplementary/Other items ::
  985.  
  986. // - Info box - coronavirus:
  987. OTHER_INFO_BOX_CORONAVIRUS: {
  988. 'en': 'Coronavirus (information box)',
  989. 'pt': 'Coronavírus (caixa de informações)',
  990. 'de': 'Coronavirus (Infobox)',
  991. 'fr': 'Coronavirus (encadré d\'information)',
  992. 'es': 'Coronavirus (cuadro de información)',
  993. 'cs': 'Coronavirus (informační box)',
  994. 'vi': 'Virus corona (hộp thông tin)',
  995. 'it': 'Coronavirus (casella informativa)',
  996. 'lv': 'Koronavīruss (informācijas lodziņš)',
  997. 'pl': 'Koronawirus (skrzynka informacyjna)',
  998. 'nl': 'Coronavirus (informatiebox)',
  999. 'he': 'וירוס קורונה (תיבת מידע)',
  1000. 'ar': 'فيروس كورونا (صندوق المعلومات)',
  1001. 'id': 'Virus Corona (kotak informasi)',
  1002. 'zh-Hans': '冠状病毒(信息框)',
  1003. 'zh-Hant': '武漢肺炎病毒(資訊框)',
  1004. 'ja': 'コロナウイルス(インフォメーションボックス)',
  1005. 'fi': 'Koronavirus (tietolaatikko)',
  1006. 'tr': 'Koronavirüs (bilgi kutusu)',
  1007. 'el': 'Κορονοϊός (πλαίσιο πληροφοριών)',
  1008. 'ru': 'Коронавирус (информационное окно)',
  1009. 'uk': 'Коронавірус (інформаційне вікно)',
  1010. 'bg': 'Коронавирус (информационна кутия)',
  1011. 'defaultEnabled': false,
  1012. 'pathMatch': '/coronavirus_info/',
  1013. },
  1014.  
  1015. // - Info box - climate science
  1016. OTHER_INFO_BOX_CLIMATE_SCIENCE: {
  1017. 'en': 'Climate Science (information box)',
  1018. 'pt': 'Ciência do Clima (caixa de informações)',
  1019. 'de': 'Klimawissenschaft (Infobox)',
  1020. 'fr': 'Science du climat (encadré d\'information)',
  1021. 'es': 'Ciencia del clima (cuadro de información)',
  1022. 'cs': 'Klimatická věda (informační box)',
  1023. 'vi': 'Khoa học khí hậu (hộp thông tin)',
  1024. 'it': 'Scienza del clima (casella informativa)',
  1025. 'lv': 'Klimata zinātne (informācijas lodziņš)',
  1026. 'pl': 'Nauka o klimacie (skrzynka informacyjna)',
  1027. 'nl': 'Klimaatwetenschap (informatiebox)',
  1028. 'he': 'מדע האקלים (תיבת מידע)',
  1029. 'ar': 'علوم المناخ (صندوق المعلومات)',
  1030. 'id': 'Ilmu iklim (kotak informasi)',
  1031. 'zh-Hans': '气候科学(信息框)',
  1032. 'zh-Hant': '氣候科學(資訊框)',
  1033. 'ja': '気候科学(情報ボックス)',
  1034. 'fi': 'Ilmastotiede (tietolaatikko)',
  1035. 'tr': 'İklim Bilimi (bilgi kutusu)',
  1036. 'el': 'Επιστήμη του κλίματος (πλαίσιο πληροφοριών)',
  1037. 'ru': 'Наука о климате (информационное окно)',
  1038. 'uk': 'Наука про клімат (інформаційне вікно)',
  1039. 'bg': 'Наука за климата (информационна кутия)',
  1040. 'defaultEnabled': false,
  1041. 'pathMatch': '/climatescienceinfo/',
  1042. },
  1043.  
  1044. // - Info box - subscribe
  1045. OTHER_INFO_BOX_SUBSCRIBE: {
  1046. 'en': 'Subscribe (information box)',
  1047. 'pt': 'Assine (caixa de informações)',
  1048. 'de': 'Abonnieren (Infobox)',
  1049. 'fr': 'S’abonner (encadré d\'information)',
  1050. 'es': 'Suscribir (cuadro de información)',
  1051. 'cs': 'Odebírat (informační box)',
  1052. 'vi': 'Đăng kí (hộp thông tin)',
  1053. 'it': 'Iscriviti (casella informativa)',
  1054. 'lv': 'Abonēt (informācijas lodziņš)',
  1055. 'pl': 'Subskrybuj (pole informacyjne)',
  1056. 'nl': 'Abonneren (informatievak)',
  1057. 'he': 'הירשם (תיבת מידע)',
  1058. 'ar': '(صندوق المعلومات) الاشتراك',
  1059. 'id': 'Berlangganan (kotak informasi)',
  1060. 'zh-Hans': '订阅(信息框)',
  1061. 'zh-Hant': '訂閱(資訊框)',
  1062. 'ja': '購読する(情報ボックス)',
  1063. 'fi': 'Rekisteröidy (tietolaatikko)',
  1064. 'tr': 'Abone ol (bilgi kutusu)',
  1065. 'el': 'Εγγραφή (πλαίσιο πληροφοριών)',
  1066. 'ru': 'Подписаться (информационное окно)',
  1067. 'uk': 'Підписатися (інформаційне вікно)',
  1068. 'bg': 'Абонирай се (информационна кутия)',
  1069. 'defaultEnabled': false,
  1070. 'pathMatch': '/support/',
  1071. },
  1072.  
  1073. REELS_TITLE: {
  1074. 'en': 'Reels',
  1075. 'pt': 'Reels',
  1076. 'de': 'Reels',
  1077. 'fr': 'Reels',
  1078. 'es': 'Reels',
  1079. 'cs': 'Reels',
  1080. 'vi': 'Reels',
  1081. 'it': 'Reels',
  1082. 'lv': 'Reels',
  1083. 'pl': 'Reels',
  1084. 'nl': 'Reels',
  1085. 'he': 'Reels', // -- FB's label
  1086. 'ar': 'ريلز', // -- FB's label
  1087. 'id': 'Reels',
  1088. 'zh-Hans': 'Reels',
  1089. 'zh-Hant': 'Reels',
  1090. 'ja': 'リール動画',
  1091. 'fi': 'Reels',
  1092. 'tr': 'Reels',
  1093. 'el': 'Reel',
  1094. 'ru': 'Видео Reels',
  1095. 'uk': 'Reels',
  1096. 'bg': 'Ленти', // -- FB's label
  1097. },
  1098.  
  1099. REELS_CONTROLS: {
  1100. 'en': 'Show video controls',
  1101. 'pt': 'Mostrar controles do vídeo',
  1102. 'de': 'Video-Steuerung anzeigen',
  1103. 'fr': 'Afficher les contrôles vidéo',
  1104. 'es': 'Mostrar controles de video',
  1105. 'cs': 'Zobrazit ovládání videa',
  1106. 'vi': 'Hiển thị điều khiển video',
  1107. 'it': 'Mostra controlli video',
  1108. 'lv': 'Rādīt video vadīklus',
  1109. 'pl': 'Pokaż sterowanie wideo',
  1110. 'nl': 'Toon video bedieningselementen',
  1111. 'he': 'הצג אפשרויות בקרת וידאו',
  1112. 'ar': 'عرض أدوات التحكم في الفيديو',
  1113. 'id': 'Tampilkan kontrol video',
  1114. 'zh-Hans': '显示视频控制',
  1115. 'zh-Hant': '顯示影片控制',
  1116. 'ja': 'ビデオコントロールを表示',
  1117. 'fi': 'Näytä videon hallintaelementit',
  1118. 'tr': 'Video kontrollerini göster',
  1119. 'el': 'Εμφάνιση χειριστηρίων βίντεο',
  1120. 'ru': 'Показать элементы управления видео',
  1121. 'uk': 'Відображення елементів керування відео',
  1122. 'bg': 'Покажи контроли на видеото',
  1123. 'defaultEnabled': true
  1124. },
  1125. REELS_DISABLE_LOOPING: {
  1126. 'en': 'Disable looping',
  1127. 'pt': 'Desativar repetição',
  1128. 'de': 'Wiederholung deaktivieren',
  1129. 'fr': 'Désactiver la boucle',
  1130. 'es': 'Desactivar bucle',
  1131. 'cs': 'Vypnout smyčení',
  1132. 'vi': 'Tắt lặp lại',
  1133. 'it': 'Disattiva ripetizione',
  1134. 'lv': 'Atspējot cilpotošanu',
  1135. 'pl': 'Wyłącz pętlę',
  1136. 'nl': 'Herhalen uitschakelen',
  1137. 'he': 'השבת לולאה',
  1138. 'ar': 'تعطيل التكرار',
  1139. 'id': 'Nonaktifkan pengulangan',
  1140. 'zh-Hans': '禁用循环',
  1141. 'zh-Hant': '停用循環',
  1142. 'ja': 'ループの無効化',
  1143. 'fi': 'Poista toisto',
  1144. 'tr': 'Döngüyü devre dışı bırak',
  1145. 'el': 'Απενεργοποίηση επανάληψης',
  1146. 'ru': 'Отключить повторение',
  1147. 'uk': 'Вимкнути повторення',
  1148. 'bg': 'Изключване на повторението',
  1149. 'defaultEnable': true
  1150. },
  1151.  
  1152. // *** Dialog box ::
  1153.  
  1154. // - CMF's dialog title:
  1155. DLG_TITLE: {
  1156. 'en': 'Clean my feeds',
  1157. 'pt': 'Limpe meus feeds',
  1158. 'de': 'Bereinige meine Feeds',
  1159. 'fr': 'Nettoyer mes flux',
  1160. 'es': 'Limpia mis feeds',
  1161. 'cs': 'Vyčistěte mé kanály',
  1162. 'vi': 'Làm sạch nguồn cấp dữ liệu của tôi',
  1163. 'it': 'Pulisci i miei feed',
  1164. 'lv': 'Tīrīt manas plūsmas',
  1165. 'pl': 'Wyczyść moje kanały',
  1166. 'nl': 'Schoon mijn feeds',
  1167. 'he': 'תנקה את הזנות שלי',
  1168. 'ar': 'تنظيف خلاصاتي',
  1169. 'id': 'Bersihkan feed saya',
  1170. 'zh-Hans': '清理我的提要',
  1171. 'zh-Hant': '清理我的動態消息',
  1172. 'ja': 'フィードをクリーンアップ',
  1173. 'fi': 'Puhdista syötteeni',
  1174. 'tr': 'Feed\'lerimi temizle',
  1175. 'el': 'Καθαρισμός των ροών μου',
  1176. 'ru': 'Очистить мои новостные ленты',
  1177. 'uk': 'Очистити мої стрічки',
  1178. 'bg': 'Почисти моите емисии',
  1179. },
  1180.  
  1181. // - label for News Feed:
  1182. DLG_NF: {
  1183. 'en': 'News Feed',
  1184. 'pt': 'Feed de notícias',
  1185. 'de': 'Newsfeed',
  1186. 'fr': 'Fil de nouvelles',
  1187. 'es': 'Feed de noticias',
  1188. 'cs': 'Informační kanál',
  1189. 'vi': 'Nguồn cấp tin tức',
  1190. 'it': 'Feed di notizie',
  1191. 'lv': 'Ziņu plūsma',
  1192. 'pl': 'Kanał aktualności',
  1193. 'nl': 'Nieuwsfeed',
  1194. 'he': 'ניוז פיד',
  1195. 'ar': 'الأخبار تغذية',
  1196. 'id': 'Umpan Berita',
  1197. 'zh-Hans': '新闻提要',
  1198. 'zh-Hant': '新聞動態消息',
  1199. 'ja': 'ニュースフィード',
  1200. 'fi': 'Uutisvirta',
  1201. 'tr': 'Haber akışı',
  1202. 'el': 'Ροή ειδήσεων',
  1203. 'ru': 'Лента новостей',
  1204. 'uk': 'Стрічка новин',
  1205. 'bg': 'Новинарски поток',
  1206. },
  1207.  
  1208. // - label for Groups Feed:
  1209. DLG_GF: {
  1210. 'en': 'Groups Feed',
  1211. 'pt': 'Feed de grupos',
  1212. 'de': 'Gruppen-Feed',
  1213. 'fr': 'Flux de groupes',
  1214. 'es': 'Feed de grupos',
  1215. 'cs': 'Skupinový kanál',
  1216. 'vi': 'Nguồn cấp dữ liệu Nhóm',
  1217. 'it': 'Feed di gruppo',
  1218. 'lv': 'Grupu plūsma',
  1219. 'pl': 'Kanał grup',
  1220. 'nl': 'Groepsfeed',
  1221. 'he': 'פיד קבוצות',
  1222. 'ar': 'مجموعات تغذية',
  1223. 'id': 'Umpan Grup',
  1224. 'zh-Hans': '群组提要',
  1225. 'zh-Hant': '群組動態消息',
  1226. 'ja': 'グループ フィード',
  1227. 'fi': 'Ryhmäsyöte',
  1228. 'tr': 'Gruplar Feed\'i',
  1229. 'el': 'Ροή ομάδων',
  1230. 'ru': 'Лента групп',
  1231. 'uk': 'Стрічка Групи',
  1232. 'bg': 'Поток с групи',
  1233. },
  1234.  
  1235. // - label for Videos Feed:
  1236. DLG_VF: {
  1237. 'en': 'Videos Feed',
  1238. 'pt': 'Feed de vídeos',
  1239. 'de': 'Video-Feed',
  1240. 'fr': 'Flux de vidéos',
  1241. 'es': 'Feed de vídeos',
  1242. 'cs': 'Video kanál',
  1243. 'vi': 'Nguồn cấp dữ liệu video',
  1244. 'it': 'Feed di video',
  1245. 'lv': 'Video plūsma',
  1246. 'pl': 'Kanał wideo',
  1247. 'nl': 'Videofeed',
  1248. 'he': 'צפה בפיד הסרטונים',
  1249. 'ar': 'الفيديو تغذية',
  1250. 'id': 'Umpan Video',
  1251. 'zh-Hans': '视频提要',
  1252. 'zh-Hant': '影片動態消息',
  1253. 'ja': '動画フィード',
  1254. 'fi': 'Videosyöte',
  1255. 'tr': 'Video Beslemelerini İzle',
  1256. 'el': 'Ροή βίντεο',
  1257. 'ru': 'Лента видео',
  1258. 'uk': 'Стрічка відео',
  1259. 'bg': 'Поток с видеа',
  1260. },
  1261.  
  1262. // - label for Marketplace Feed:
  1263. DLG_MP: {
  1264. 'en': 'Marketplace Feed',
  1265. 'pt': 'Feed de mercado',
  1266. 'de': 'Marktplatz-Feed',
  1267. 'fr': 'Flux de la place de marché',
  1268. 'es': 'Feed de Marketplace',
  1269. 'cs': 'Marketplace kanál',
  1270. 'vi': 'Nguồn cấp dữ liệu Marketplace',
  1271. 'it': 'Feed id Marketplace',
  1272. 'lv': 'Marketplace',
  1273. 'pl': 'Kanał Marketplace',
  1274. 'nl': 'Marktplaatsfeed',
  1275. 'he': 'זירת מסחר',
  1276. 'ar': 'السوق تغذية',
  1277. 'id': 'Umpan Marketplace',
  1278. 'zh-Hans': '市场提要',
  1279. 'zh-Hant': 'Marketplace 動態消息',
  1280. 'ja': 'マーケットプレイス フィード',
  1281. 'fi': 'Marketplace-syöte',
  1282. 'tr': 'Pazar Yeri Feed\'i',
  1283. 'el': 'Ροή Marketplace',
  1284. 'ru': 'Лента Marketplace',
  1285. 'uk': 'Стрічка Marketplace',
  1286. 'bg': 'Поток с Marketplace',
  1287. },
  1288.  
  1289. // - label for Miscellaneous/Other items:
  1290. // DLG_OTHER: {
  1291. // 'en': 'Miscellaneous items',
  1292. // 'pt': 'Itens miscelâneos',
  1293. // 'de': 'Sonstige Gegenstände',
  1294. // 'fr': 'Articles divers',
  1295. // 'es': 'Artículos diversos',
  1296. // 'cs': 'Různé položky',
  1297. // 'vi': 'Những thứ linh tinh',
  1298. // 'it': 'Articoli vari',
  1299. // 'lv': 'Dažādas vienumus',
  1300. // 'pl': 'Różne pozycje',
  1301. // 'nl': 'Diversen',
  1302. // 'he': 'פריטים שונים',
  1303. // 'ar': 'عناصر متنوعة',
  1304. // 'id': 'Barang lain-lain',
  1305. // 'zh-Hans': '杂件',
  1306. // 'zh-Hant': '雜項',
  1307. // 'ja': 'その他のアイテム',
  1308. // 'fi': 'Sekalaiset tavarat',
  1309. // 'tr': 'Diğer öğeler',
  1310. // 'el': 'Διάφορα αντικείμενα',
  1311. // 'ru': 'Разное',
  1312. // },
  1313. DLG_OTHER: {
  1314. 'en': 'Supplementary / information section',
  1315. 'pt': 'Seção suplementar / informativa',
  1316. 'de': 'Ergänzungs-/Informationsbereich',
  1317. 'fr': 'Section supplémentaire / information',
  1318. 'es': 'Sección suplementaria / información',
  1319. 'cs': 'Doplňková/informační část',
  1320. 'vi': 'Phần bổ sung/thông tin',
  1321. 'it': 'Sezione supplementare / informativa',
  1322. 'lv': 'Papildu / informācija sadaļa',
  1323. 'pl': 'Sekcja uzupełniająca/informacyjna',
  1324. 'nl': 'Aanvullende / informatie sectie',
  1325. 'he': 'מדור משלים / מידע',
  1326. 'ar': 'قسم التكميلي / المعلومات',
  1327. 'id': 'Bagian tambahan / informasi',
  1328. 'zh-Hans': '补充/资料部分',
  1329. 'zh-Hant': '補充/資訊部分',
  1330. 'ja': '補足・情報セクション',
  1331. 'fi': 'Täydentävä / tieto-osio',
  1332. 'tr': 'Tamamlayıcı / bilgi bölümü',
  1333. 'el': 'Συμπληρωματικό τμήμα / πληροφοριακό τμήμα',
  1334. 'ru': 'Дополнительный/информационный раздел',
  1335. 'uk': 'Додатковий/інформаційний розділ',
  1336. 'bg': 'Допълнителен / информационен раздел',
  1337. },
  1338.  
  1339. // - text filter label (title)
  1340. DLG_BLOCK_TEXT_FILTER_TITLE: {
  1341. 'en': 'Text filter',
  1342. 'pt': 'Filtro de texto',
  1343. 'de': 'Textfilter',
  1344. 'fr': 'Filtre de texte',
  1345. 'es': 'Filtro de texto',
  1346. 'cs': 'Textový filtr',
  1347. 'vi': 'Bộ lọc văn bản',
  1348. 'it': 'Filtro di testo',
  1349. 'lv': 'Teksta filtrs',
  1350. 'pl': 'Filtr tekstu',
  1351. 'nl': 'Tekstfilter',
  1352. 'he': 'מסנן טקסט',
  1353. 'ar': 'مرشح النص',
  1354. 'id': 'Filter teks',
  1355. 'zh-Hans': '文本过滤器',
  1356. 'zh-Hant': '文字過濾器',
  1357. 'ja': 'テキストフィルター',
  1358. 'fi': 'Tekstisuodatin',
  1359. 'tr': 'Metin filtresi',
  1360. 'el': 'Φίλτρο κειμένου',
  1361. 'ru': 'Текстовый фильтр',
  1362. 'uk': 'Текстовий фільтр',
  1363. 'bg': 'Текстов филтър',
  1364. },
  1365.  
  1366. // - text filter - separate keywords with new line:
  1367. DLG_BLOCK_NEW_LINE: {
  1368. 'en': '(separate words or phrases with a line break)',
  1369. 'pt': '(separe palavras ou frases com quebras de linha)',
  1370. 'de': '(trennen Sie Wörter oder Sätze mit Zeilenumbrüchen)',
  1371. 'fr': '(mots ou phrases séparés avec des sauts de ligne)',
  1372. 'es': '(palabras o frases separadas con saltos de línea)',
  1373. 'cs': '(oddělte slova nebo fráze na nový řádek)',
  1374. 'vi': '(tách các từ hoặc cụm từ bằng dấu ngắt dòng)',
  1375. 'it': '(separare parole o frasi con un\'interruzione di riga)',
  1376. 'lv': '(atdaliet vārdus vai frāzes ar rindas pārtraukumu)',
  1377. 'pl': '(oddziel słowa lub wyrażenia z podziałem wiersza)',
  1378. 'nl': '(scheid woorden of woordgroepen met een regeleinde)',
  1379. 'he': '(הפרד מילים או ביטויים עם מעבר שורה)',
  1380. 'ar': '(افصل الكلمات أو العبارات بفاصل أسطر)',
  1381. 'id': '(pisahkan kata atau frasa dengan jeda baris)',
  1382. 'zh-Hans': '(用换行符分隔单词或短语)',
  1383. 'zh-Hant': '(用換行符號分隔單字或短語)',
  1384. 'ja': '(改行で単語または語句を区切ります)',
  1385. 'fi': '(erottele sanat tai lauseet rivinvaihdolla)',
  1386. 'tr': '(sözcükleri veya tümcecikleri satır sonu ile ayırın)',
  1387. 'el': '(διαχωρίστε λέξεις ή φράσεις με αλλαγή γραμμής)',
  1388. 'ru': '(разделяйте слова или фразы с помощью переноса строки)',
  1389. 'uk': '(розділяти слова або фрази розривом рядка)',
  1390. 'bg': '(разделяйте думи или фрази с нов ред)',
  1391. },
  1392.  
  1393. NF_BLOCKED_ENABLED: {
  1394. 'en': 'Enabled',
  1395. 'pt': 'Habilidoso',
  1396. 'de': 'Ermöglichte',
  1397. 'fr': 'Activé',
  1398. 'es': 'Habilitadas',
  1399. 'cs': 'Zapnuto',
  1400. 'vi': 'Đã kích hoạt',
  1401. 'it': 'Abilita opzione',
  1402. 'lv': 'Iespējots',
  1403. 'pl': 'Włączone',
  1404. 'nl': 'Ingeschakeld',
  1405. 'he': 'מופעל',
  1406. 'ar': 'تمكين',
  1407. 'id': 'Diaktifkan',
  1408. 'zh-Hans': '启用',
  1409. 'zh-Hant': '啟用',
  1410. 'ja': '有効化',
  1411. 'fi': 'Ota vaihtoehto käyttöön',
  1412. 'tr': 'Etkinleştirildi',
  1413. 'el': 'Ενεργοποιημένο',
  1414. 'ru': 'Включено',
  1415. 'uk': 'Увімкнено',
  1416. 'bg': 'Активирано',
  1417. 'defaultEnabled': false,
  1418. },
  1419.  
  1420. GF_BLOCKED_ENABLED: {
  1421. 'en': 'Enabled',
  1422. 'pt': 'Habilidoso',
  1423. 'de': 'Ermöglichte',
  1424. 'fr': 'Activé',
  1425. 'es': 'Habilitadas',
  1426. 'cs': 'Zapnuto',
  1427. 'vi': 'Đã kích hoạt',
  1428. 'it': 'Abilita opzione',
  1429. 'lv': 'Iespējots',
  1430. 'pl': 'Włączone',
  1431. 'nl': 'Ingeschakeld',
  1432. 'he': 'מופעל',
  1433. 'ar': 'تمكين',
  1434. 'id': 'Diaktifkan',
  1435. 'zh-Hans': '启用',
  1436. 'zh-Hant': '啟用',
  1437. 'ja': '有効化',
  1438. 'fi': 'Ota vaihtoehto käyttöön',
  1439. 'tr': 'Etkinleştirildi',
  1440. 'el': 'Ενεργοποιημένο',
  1441. 'ru': 'Включено',
  1442. 'uk': 'Увімкнено',
  1443. 'bg': 'Активирано',
  1444. 'defaultEnabled': false,
  1445. },
  1446.  
  1447. VF_BLOCKED_ENABLED: {
  1448. 'en': 'Enabled',
  1449. 'pt': 'Habilidoso',
  1450. 'de': 'Ermöglichte',
  1451. 'fr': 'Activé',
  1452. 'es': 'Habilitadas',
  1453. 'cs': 'Zapnuto',
  1454. 'vi': 'Đã kích hoạt',
  1455. 'it': 'Abilita opzione',
  1456. 'lv': 'Iespējots',
  1457. 'pl': 'Włączone',
  1458. 'nl': 'Ingeschakeld',
  1459. 'he': 'מופעל',
  1460. 'ar': 'تمكين',
  1461. 'id': 'Diaktifkan',
  1462. 'zh-Hans': '启用',
  1463. 'zh-Hant': '啟用',
  1464. 'ja': '有効化',
  1465. 'fi': 'Ota vaihtoehto käyttöön',
  1466. 'tr': 'Etkinleştirildi',
  1467. 'el': 'Ενεργοποιημένο',
  1468. 'ru': 'Включено',
  1469. 'uk': 'Увімкнено',
  1470. 'bg': 'Активирано',
  1471. 'defaultEnabled': false,
  1472. },
  1473.  
  1474. MP_BLOCKED_ENABLED: {
  1475. 'en': 'Enabled',
  1476. 'pt': 'Habilidoso',
  1477. 'de': 'Ermöglichte',
  1478. 'fr': 'Activé',
  1479. 'es': 'Habilitadas',
  1480. 'cs': 'Zapnuto',
  1481. 'vi': 'Đã kích hoạt',
  1482. 'it': 'Abilita opzione',
  1483. 'lv': 'Iespējots',
  1484. 'pl': 'Włączone',
  1485. 'nl': 'Ingeschakeld',
  1486. 'he': 'מופעל',
  1487. 'ar': 'تمكين',
  1488. 'id': 'Diaktifkan',
  1489. 'zh-Hans': '启用',
  1490. 'zh-Hant': '啟用',
  1491. 'ja': '有効化',
  1492. 'fi': 'Ota vaihtoehto käyttöön',
  1493. 'tr': 'Etkinleştirildi',
  1494. 'el': 'Ενεργοποιημένο',
  1495. 'ru': 'Включено',
  1496. 'uk': 'Увімкнено',
  1497. 'bg': 'Активирано',
  1498. 'defaultEnabled': false,
  1499. },
  1500.  
  1501. // - label for Verbosity
  1502. DLG_VERBOSITY: {
  1503. 'en': 'Verbosity',
  1504. 'pt': 'Verbosidade',
  1505. 'de': 'Ausführlichkeit',
  1506. 'fr': 'Verbosité',
  1507. 'es': 'Verbosidad',
  1508. 'cs': 'Výřečnost',
  1509. 'vi': 'Tính dài dòng',
  1510. 'it': 'Verbosità',
  1511. 'lv': 'Runīgums',
  1512. 'pl': 'Włączone',
  1513. 'nl': 'Ingeschakeld',
  1514. 'he': 'ורבוסיטי',
  1515. 'ar': 'الإسهاب',
  1516. 'id': 'Verbositas',
  1517. 'zh-Hans': '详细程度',
  1518. 'zh-Hant': '詳細程度',
  1519. 'ja': '詳細度',
  1520. 'fi': 'Monisanaisuus',
  1521. 'tr': 'Ayrıntı',
  1522. 'el': 'Πολυλογία',
  1523. 'ru': 'Многословие',
  1524. 'uk': 'Багатослівність',
  1525. 'bg': 'Многословие',
  1526. 'defaultValue': '1',
  1527. },
  1528.  
  1529. // - label for display a message if a post is hidden:
  1530. DLG_VERBOSITY_MESSAGE: {
  1531. 'en': 'Display a message if a post is hidden',
  1532. 'pt': 'Exibir uma mensagem se uma postagem estiver oculta',
  1533. 'de': 'Nachricht anzeigen, wenn ein Beitrag ausgeblendet ist',
  1534. 'fr': 'Afficher un message si une publication est masquée',
  1535. 'es': 'Mostrar un mensaje si una publicación está oculta',
  1536. 'cs': 'Zobrazit zprávu, pokud je příspěvek skrytý',
  1537. 'vi': 'Hiển thị một tin nhắn nếu một bài đăng bị ẩn',
  1538. 'it': 'Visualizza un messaggio se un post è nascosto',
  1539. 'lv': 'Parādīt ziņojumu, ja raksts ir paslēpts',
  1540. 'pl': 'Wyświetlaj wiadomość, jeśli wpis jest ukryty',
  1541. 'nl': 'Een bericht weergeven als een artikel verborgen is',
  1542. 'he': 'הצג הודעה אם פוסט מוסתר',
  1543. 'ar': 'اعرض رسالة إذا كانت المشاركة مخفية',
  1544. 'id': 'Tampilkan pesan jika kiriman disembunyikan',
  1545. 'zh-Hans': '如果帖子被隐藏,则显示消息',
  1546. 'zh-Hant': '如果貼文被隱藏,則顯示訊息',
  1547. 'ja': '投稿が非表示の場合にメッセージを表示する',
  1548. 'fi': 'Näytä viesti, jos postaus on piilotettu',
  1549. 'tr': 'Bir gönderi gizlenmişse bir mesaj göster',
  1550. 'el': 'Εμφάνιση μηνύματος αν ένας δημοσίευση είναι κρυμμένη',
  1551. 'ru': 'Показывать сообщение, если пост скрыт',
  1552. 'uk': 'Відображати повідомлення, якщо публікація прихована',
  1553. 'bg': 'Показване на съобщение, ако публикацията е скрита',
  1554. },
  1555.  
  1556. // - Verbosity - say nothing:
  1557. VERBOSITY_NO_MESSAGE: {
  1558. 'en': 'no message',
  1559. 'pt': 'nenhuma mensagem',
  1560. 'de': 'keine Nachricht',
  1561. 'fr': 'pas de message',
  1562. 'es': 'Sin mensaje',
  1563. 'cs': 'žádná zpráva',
  1564. 'vi': 'không có tin nhắn',
  1565. 'it': 'Nessun messaggio',
  1566. 'lv': 'Nekādu ziņojumu',
  1567. 'pl': 'nie ma wiadomości',
  1568. 'nl': 'geen bericht',
  1569. 'he': 'אין הודעה',
  1570. 'ar': 'لا توجد رسالة',
  1571. 'id': 'tidak ada pesan',
  1572. 'zh-Hans': '没有消息',
  1573. 'zh-Hant': '不要顯示訊息',
  1574. 'ja': 'メッセージなし',
  1575. 'fi': 'ei viestiä',
  1576. 'tr': 'esaj yok',
  1577. 'el': 'Κανένα μήνυμα',
  1578. 'ru': 'сообщение отсутствует',
  1579. 'uk': 'немає повідомлення',
  1580. 'bg': 'Без съобщение',
  1581. },
  1582.  
  1583. // - notification
  1584. VERBOSITY_MESSAGE: {
  1585. 'en': ['1 post hidden. Rule: ', ' posts hidden'],
  1586. 'pt': ['1 postagem oculta. Regra: ', ' postagens ocultas'],
  1587. 'de': ['1 Beitrag ausgeblendet. Regel: ', ' Beiträge versteckt'],
  1588. 'fr': ['1 poste caché. Règle: ', ' posts cachés'],
  1589. 'es': ['1 publicación oculta. Regla: ', ' publicaciones ocultas'],
  1590. 'cs': ['1 příspěvek byl skryt. Pravidlo: ', ' příspěvků skrytých'],
  1591. 'vi': ['1 bài bị ẩn. Quy tắc: ', ' bài viết ẩn'],
  1592. 'it': ['1 post nascosto Regola: ', ' post nascosti'],
  1593. 'lv': ['1 ziņa ir paslēpta. Noteikums: ', ' ziņas ir paslēptas'],
  1594. 'pl': ['Ukryto 1 post. Reguła: ', ' posty ukryte'],
  1595. 'nl': ['1 post verborgen. Regel: ', ' posts verborgen'],
  1596. 'he': ['פוסט אחד מוסתר. כלל: ', ' פוסטים מוסתרים'],
  1597. 'ar': ['مشاركة واحدة مخفية. حكم: ', ' المشاركات المخفية'],
  1598. 'id': ['1 pos disembunyikan. Aturan: ', ' postingan disembunyikan'],
  1599. 'zh-Hans': ['1 个帖子已隐藏。 规则: ', ' 个帖子已隐藏'],
  1600. 'zh-Hant': ['1 個貼文已隱藏。 規則: ', ' 個貼文已隱藏'],
  1601. 'ja': ['1 件の投稿が非表示になっています。 ルール: ', ' 件の投稿が非表示'],
  1602. 'fi': ['1 viesti piilotettu. Sääntö: ', ' viestiä piilotettu'],
  1603. 'tr': ['1 gönderi gizlendi. Kural: ', ' gönderi gizlendi'],
  1604. 'el': ['1 δημοσίευση κρυμμένη. Κανόνας: ', ' δημοσιεύσεις κρυμμένες'],
  1605. 'ru': ['1 пост скрыт. Правило: ', ' постов скрыто'],
  1606. 'uk': ['1 допис прихований. Правило: ', ' дописи приховано'],
  1607. 'bg': ['1 скрита публикация. Правило: ', ' скрити публикации'],
  1608. },
  1609.  
  1610. // - colour of the verbosity message:
  1611. VERBOSITY_MESSAGE_COLOUR: {
  1612. 'en': 'Text colour',
  1613. 'pt': 'Cor do texto',
  1614. 'de': 'Textfarbe',
  1615. 'fr': 'Couleur du texte',
  1616. 'es': 'Color del texto',
  1617. 'cs': 'Barva textu',
  1618. 'vi': 'Màu văn bản',
  1619. 'it': 'Colore del testo',
  1620. 'lv': 'Teksta krāsa',
  1621. 'pl': 'Kolor tekstu',
  1622. 'nl': 'Tekstkleur',
  1623. 'he': 'צבע טקסט',
  1624. 'ar': 'لون النص',
  1625. 'id': 'Warna teks',
  1626. 'zh-Hans': '文字颜色',
  1627. 'zh-Hant': '文字顏色',
  1628. 'ja': 'テキストの色',
  1629. 'fi': 'Tekstin väri',
  1630. 'tr': 'Metin rengi',
  1631. 'el': 'Χρώμα κειμένου',
  1632. 'ru': 'Цвет текста',
  1633. 'uk': 'Колір тексту',
  1634. 'bg': 'Цвят на текста',
  1635. },
  1636.  
  1637. // - background colour of the verbosity message:
  1638. VERBOSITY_MESSAGE_BG_COLOUR: {
  1639. 'en': 'Background colour',
  1640. 'pt': 'Cor de fundo',
  1641. 'de': 'Hintergrundfarbe',
  1642. 'fr': 'Couleur de fond',
  1643. 'es': 'Color de fondo',
  1644. 'cs': 'Barva pozadí',
  1645. 'vi': 'Màu nền',
  1646. 'it': 'Colore di sfondo',
  1647. 'lv': 'Fona krāsa',
  1648. 'pl': 'Kolor tła',
  1649. 'nl': 'Achtergrondkleur',
  1650. 'he': 'צבע הרקע',
  1651. 'ar': 'لون الخلفية',
  1652. 'id': 'Warna latar belakang',
  1653. 'zh-Hans': '背景颜色',
  1654. 'zh-Hant': '背景顏色',
  1655. 'ja': '背景色',
  1656. 'fi': 'Taustaväri',
  1657. 'tr': 'Arka plan rengi',
  1658. 'el': 'Χρώμα φόντου',
  1659. 'ru': 'Цвет фона',
  1660. 'uk': 'Колір фону',
  1661. 'bg': 'Цвят на фона',
  1662. 'defaultValue': 'LightGrey',
  1663. },
  1664.  
  1665. // - debugging - show "hidden" posts
  1666. VERBOSITY_DEBUG: {
  1667. 'en': 'Highlight "hidden" posts',
  1668. 'pt': 'Destacar postagens "ocultas"',
  1669. 'de': 'Markieren Sie "versteckte" Beiträge',
  1670. 'fr': 'Mettez en surbrillance les messages « cachés »',
  1671. 'es': 'Destacar publicaciones "ocultas"',
  1672. 'cs': 'Zvýrazněte „skryté“ příspěvky',
  1673. 'vi': 'Đánh dấu các bài đăng "ẩn"',
  1674. 'it': 'Evidenzia i post "nascosti"',
  1675. 'lv': 'Izceliet "slēptos" rakstus',
  1676. 'pl': 'Wyróżnij „ukryte” posty',
  1677. 'nl': 'Highlight "verborgen" artikelen',
  1678. 'he': 'הדגש פוסטים "מוסתרים"',
  1679. 'ar': 'تسليط الضوء على المشاركات "المخفية"',
  1680. 'id': 'Sorot postingan "tersembunyi"',
  1681. 'zh-Hans': '突出显示“隐藏”的帖子',
  1682. 'zh-Hant': '強調顯示「隱藏」的貼文',
  1683. 'ja': '「非表示」の投稿を強調表示する',
  1684. 'fi': 'Korosta "piilotetut" postaus',
  1685. 'tr': '"Gizli" gönderileri vurgulayın',
  1686. 'el': 'Επισήμανση "κρυφών αναρτήσεων"',
  1687. 'ru': 'Выделить «скрытые» посты',
  1688. 'uk': '«Виділяти «приховані» дописи»',
  1689. 'bg': 'Открояване на скритите публикации',
  1690. 'defaultValue': false,
  1691. },
  1692.  
  1693. // - customisation of cmf's dialog box:
  1694. CMF_CUSTOMISATIONS: {
  1695. 'en': 'Customisations',
  1696. 'pt': 'Personalizações',
  1697. 'de': 'Anpassungen',
  1698. 'fr': 'Personnalisations',
  1699. 'es': 'Personalizaciones',
  1700. 'cs': 'Přizpůsobení',
  1701. 'vi': 'Các tùy chỉnh',
  1702. 'it': 'Personalizzazioni',
  1703. 'lv': 'Personalizēšana',
  1704. 'pl': 'Personalizacja',
  1705. 'nl': 'Personalisaties',
  1706. 'he': 'התאמות אישיות',
  1707. 'ar': 'التخصيصات',
  1708. 'id': 'Kustomisasi',
  1709. 'zh-Hans': '定制化',
  1710. 'zh-Hant': '客製化',
  1711. 'ja': 'カスタマイズ',
  1712. 'fi': 'Räätälöinnit',
  1713. 'tr': 'özelleştirmeler',
  1714. 'el': 'Προσαρμογές',
  1715. 'ru': 'Настройки',
  1716. 'uk': 'Налаштування',
  1717. 'bg': 'Настройки',
  1718. },
  1719.  
  1720. // - label for location of button:
  1721. CMF_BTN_LOCATION: {
  1722. 'en': 'Location of Clean my feeds\' button',
  1723. 'pt': 'Localização do botão Limpe meus feeds',
  1724. 'de': 'Position der Schaltfläche "Bereinige meine Feeds"',
  1725. 'fr': 'Emplacement du bouton Nettoyer mes flux',
  1726. 'es': 'Ubicación del botón Limpia mis feeds',
  1727. 'cs': 'Umístění tlačítka Vyčistěte mé kanály',
  1728. 'vi': 'Vị trí của nút Làm sạch nguồn cấp dữ liệu của tôi',
  1729. 'it': 'Posizione del pulsante Pulisci i miei feed',
  1730. 'lv': 'Pogas Tīrīt manas plūsmas atrašanās vieta',
  1731. 'pl': 'Lokalizacja przycisku Wyczyść moje kanały',
  1732. 'nl': 'Locatie van de knop Mijn feeds opschonen',
  1733. 'he': 'תנקה את הזנות שלי מיקום הכפתור',
  1734. 'ar': 'موقع الزر "تنظيف خلاصاتي"',
  1735. 'id': 'Lokasi tombol Bersihkan umpan saya',
  1736. 'zh-Hans': '“清理我的提要”按钮位置',
  1737. 'zh-Hant': '「清理我的動態消息」按鈕的位置',
  1738. 'ja': '「フィードをクリーンアップ」ボタンの配置',
  1739. 'fi': 'Puhdista syötteeni -painikkeen sijainti',
  1740. 'tr': '"Feed\'lerimi temizle" için düğmenin konumu',
  1741. 'el': 'Τοποθεσία του κουμπιού "Καθαρισμός των ροών μου"',
  1742. 'ru': 'Расположение кнопки «Очистить мои новостные ленты»',
  1743. 'uk': 'Розташування кнопки «Очистити мої стрічки»',
  1744. 'bg': 'Местоположение на бутона "Почисти моите емисии"',
  1745. },
  1746.  
  1747. // - location of button:
  1748. CMF_BTN_OPTION: {
  1749. 'en': ['bottom left', 'top right', 'disabled (use "Settings" in User Script Commands menu")'],
  1750. 'pt': ['inferior esquerdo', 'superior direito', 'desativado (use "Configurações" no menu Comandos de script do usuário)'],
  1751. 'de': ['unten links', 'oben rechts', 'deaktiviert (verwenden Sie "Einstellungen" im Menü "Benutzerskriptbefehle")'],
  1752. 'fr': ['en bas à gauche', 'en haut à droite', 'désactivé (utilisez "Paramètres" dans le menu Commandes de script utilisateur)'],
  1753. 'es': ['abajo a la izquierda', 'arriba a la derecha', 'deshabilitado (use "Configuración" en el menú Comandos de script de usuario)'],
  1754. 'cs': ['vlevo dole', 'vpravo nahoře', 'zakázáno (použijte "Nastavení" v nabídce Příkazy uživatelského skriptu)'],
  1755. 'vi': ['dưới cùng bên trái', 'trên cùng bên phải', 'bị vô hiệu hóa (sử dụng "Cài đặt" trong menu Lệnh của Tập lệnh Người dùng)'],
  1756. 'it': ['in basso a sinistra', 'in alto a destra', 'disabilitato (usa "Impostazioni" nel menu Comandi script utente)'],
  1757. 'lv': ['apakšējā kreisajā stūrī', 'augšējā labajā stūrī', 'atspējota (lietotāja skripta komandu izvēlnē izmantojiet sadaļu Iestatījumi)'],
  1758. 'pl': ['lewy dolny róg', 'prawy górny róg', 'wyłączone (użyj "Ustawienia" w menu Polecenia skryptu użytkownika)'],
  1759. 'nl': ['linksonder', 'rechtsboven', 'uitgeschakeld (gebruik "Instellingen" in het menu Gebruikersscriptopdrachten)'],
  1760. 'he': ['שמאל למטה', 'ימינה למעלה', 'מושבת (השתמש ב"הגדרות" בתפריט פקודות סקריפט משתמש)'],
  1761. 'ar': ['أسفل اليسار', 'أعلى اليمين', 'معطل (استخدم "الإعدادات" في قائمة أوامر البرنامج النصي للمستخدم)'],
  1762. 'id': ['kiri bawah', 'kanan atas', 'dinonaktifkan (gunakan "Pengaturan" di menu Perintah Skrip Pengguna)'],
  1763. 'zh-Hans': ['左下方', '右上', '禁用(使用用户脚本命令菜单中的“设置”)'],
  1764. 'zh-Hant': ['左下方', '右上方', '禁用(在用户脚本命令菜单中使用“设置”)'],
  1765. 'ja': ['下左', '上右', '無効 ([ユーザー スクリプト コマンド] メニューの [設定] を使用)'],
  1766. 'fi': ['alhaalla vasemmalla', 'ylhäällä oikealle', 'pois käytöstä (käytä "Asetukset" User Script Commands -valikossa)'],
  1767. 'tr': ['sol alt', 'sağ üst', 'devre dışı (Kullanıcı Komut Dosyası Komutları menüsünde "Ayarlar"ı kullanın)'],
  1768. 'el': ['κάτω αριστερά', 'πάνω δεξιά', 'απενεργοποιημένο (χρησιμοποιήστε "Ρυθμίσεις" στο μενού "Εντολές σεναρίου χρήστη")'],
  1769. 'ru': ['внизу слева', 'вверху справа', 'отключено (используйте «Настройки» в меню «Пользовательские команды скрипта»)'],
  1770. 'uk': ['внизу ліворуч', 'вгорі праворуч', 'вимкнено (використовуйте «Параметри» в меню команд сценарію користувача»)'],
  1771. 'bg': ['долу вляво', 'горе вдясно', 'деактивирано (използвайте "Настройки" в менюто с команди за потребителски сценарии)'],
  1772. 'defaultValue': '0',
  1773. },
  1774. // - script manager's menu item "Settings"
  1775. GM_MENU_SETTINGS: {
  1776. 'en': 'Settings',
  1777. 'pt': 'Configurações',
  1778. 'de': 'Einstellungen',
  1779. 'fr': 'Paramètres',
  1780. 'es': 'Configuración',
  1781. 'cs': 'Nastavení',
  1782. 'vi': 'Cài đặt',
  1783. 'it': 'Impostazioni',
  1784. 'lv': 'Iestatījumi',
  1785. 'pl': 'Ustawienia',
  1786. 'nl': 'Instellingen',
  1787. 'he': 'ההגדרות',
  1788. 'ar': 'الإعدادات',
  1789. 'id': 'Pengaturan',
  1790. 'zh-Hans': '设置',
  1791. 'zh-Hant': '設置',
  1792. 'ja': '設定',
  1793. 'fi': 'Asetukset',
  1794. 'tr': 'Ayarlar',
  1795. 'el': 'Ρυθμίσεις',
  1796. 'ru': 'Настройки',
  1797. 'uk': 'Параметри',
  1798. 'bg': 'Настройки',
  1799. },
  1800.  
  1801. // - label for location of dialog:
  1802. CMF_DIALOG_LOCATION: {
  1803. 'en': 'Location of Clean my feeds\' dialog box',
  1804. 'pt': 'Localização da caixa de diálogo Limpe meus feeds',
  1805. 'de': 'Position des Dialogfelds "Bereinige meine Feeds"',
  1806. 'fr': 'Emplacement de la boîte de dialogue Nettoyer mes flux',
  1807. 'es': 'Ubicación del cuadro de diálogo Limpia mis feeds',
  1808. 'cs': 'Umístění dialogového okna Vyčistěte mé kanály',
  1809. 'vi': 'Vị trí của hộp thoại Làm sạch nguồn cấp dữ liệu của tôi',
  1810. 'it': 'Posizione della finestra di dialogo Pulisci i miei feed',
  1811. 'lv': 'Dialoglodziņa Tīrīt manas plūsmas atrašanās vieta',
  1812. 'pl': 'Lokalizacja okna dialogowego Wyczyść moje kanały',
  1813. 'nl': 'Locatie van het dialoogvenster Mijn feeds opschonen',
  1814. 'he': 'מיקום תיבת הדו-שיח "נקה את ההזנות שלי"',
  1815. 'ar': 'موقع مربع الحوار "تنظيف موجز ويباتي"',
  1816. 'id': 'Lokasi kotak dialog Bersihkan umpan saya',
  1817. 'zh-Hans': '“清理我的提要”对话框位置',
  1818. 'zh-Hant': '「清理我的動態消息」對話框的位置',
  1819. 'ja': '[フィードの消去] ダイアログ ボックスの配置',
  1820. 'fi': 'Puhdista syötteeni -valintaikkunan sijainti',
  1821. 'tr': '"Feed\'lerimi temizle" iletişim kutusunun konumu',
  1822. 'el': 'Τοποθεσία της διαλόγου "Καθαρισμός των ροών μου"',
  1823. 'ru': 'Расположение диалогового окна «Очистить мои ленты»',
  1824. 'uk': 'Розташування діалогового вікна «Очистити мої стрічки»',
  1825. 'bg': 'Местоположение на диалоговия прозорец "Почисти моите емисии"',
  1826. },
  1827.  
  1828. // - location of dialog:
  1829. CMF_DIALOG_OPTION: {
  1830. 'en': ['left side', 'right side'],
  1831. 'pt': ['lado esquerdo', 'lado direito'],
  1832. 'de': ['linke Seite', 'rechte Seite'],
  1833. 'fr': ['côté gauche', 'côté droit'],
  1834. 'es': ['lado izquierdo', 'lado derecho'],
  1835. 'cs': ['levá strana', 'pravá strana'],
  1836. 'vi': ['bên trái', 'bên phải'],
  1837. 'it': ['lato sinistro', 'lato destro'],
  1838. 'lv': ['kreisā puse', 'labā puse'],
  1839. 'pl': ['lewa strona', 'prawa strona'],
  1840. 'nl': ['linkerkant', 'rechterkant'],
  1841. 'he': ['צד שמאל', 'צד ימין'],
  1842. 'ar': ['الجهه اليسرى', 'الجانب الصحيح'],
  1843. 'id': ['sisi kiri', 'sisi kanan'],
  1844. 'zh-Hans': ['左边', '右边'],
  1845. 'zh-Hant': ['左邊', '右邊'],
  1846. 'ja': ['左側', '右側'],
  1847. 'fi': ['vasen puoli', 'oikea puoli'],
  1848. 'tr': ['sol yan', 'sağ yan'],
  1849. 'el': ['αριστερή πλευρά', 'δεξιά πλευρά'],
  1850. 'ru': ['левая сторона', 'правая сторона'],
  1851. 'uk': ['ліва сторона', 'права сторона'],
  1852. 'bg': ['лява страна', 'дясна страна'],
  1853. 'defaultValue': '0',
  1854. },
  1855.  
  1856. // - colour of the dialog's text:
  1857. CMF_DIALOG_COLOUR: {
  1858. 'en': 'Text colour',
  1859. 'pt': 'Cor do texto',
  1860. 'de': 'Textfarbe',
  1861. 'fr': 'Couleur du texte',
  1862. 'es': 'Color del texto',
  1863. 'cs': 'Barva textu',
  1864. 'vi': 'Màu văn bản',
  1865. 'it': 'Colore del testo',
  1866. 'lv': 'Teksta krāsa',
  1867. 'pl': 'Kolor tekstu',
  1868. 'nl': 'Tekstkleur',
  1869. 'he': 'צבע טקסט',
  1870. 'ar': 'لون النص',
  1871. 'id': 'Warna teks',
  1872. 'zh-Hans': '文字颜色',
  1873. 'zh-Hant': '文字顏色',
  1874. 'ja': 'テキストの色',
  1875. 'fi': 'Tekstin väri',
  1876. 'tr': 'Metin rengi',
  1877. 'el': 'Χρώμα κειμένου',
  1878. 'ru': 'Цвет текста',
  1879. 'uk': 'Колір тексту',
  1880. 'bg': 'Цвят на текста',
  1881. 'defaultValue': '',
  1882. },
  1883.  
  1884. // - background colour of the dialog:
  1885. CMF_DIALOG_BG_COLOUR: {
  1886. 'en': 'Background colour',
  1887. 'pt': 'Cor de fundo',
  1888. 'de': 'Hintergrundfarbe',
  1889. 'fr': 'Couleur de fond',
  1890. 'es': 'Color de fondo',
  1891. 'cs': 'Barva pozadí',
  1892. 'vi': 'Màu nền',
  1893. 'it': 'Colore di sfondo',
  1894. 'lv': 'Fona krāsa',
  1895. 'pl': 'Kolor tła',
  1896. 'nl': 'Achtergrondkleur',
  1897. 'he': 'צבע הרקע',
  1898. 'ar': 'لون الخلفية',
  1899. 'id': 'Warna latar belakang',
  1900. 'zh-Hans': '背景颜色',
  1901. 'zh-Hant': '背景顏色',
  1902. 'ja': '背景色',
  1903. 'fi': 'Taustaväri',
  1904. 'tr': 'Arka plan rengi',
  1905. 'el': 'Χρώμα φόντου',
  1906. 'ru': 'Цвет фона',
  1907. 'uk': 'Колір фону',
  1908. 'bg': 'Цвят на фона',
  1909. 'defaultValue': '',
  1910. },
  1911.  
  1912. // - dialog's border colour:
  1913. CMF_BORDER_COLOUR: {
  1914. 'en': 'Border colour',
  1915. 'pt': 'Cor da borda',
  1916. 'de': 'Farbe der Umrandung',
  1917. 'fr': 'Couleur de bordure',
  1918. 'es': 'Color de borde',
  1919. 'cs': 'Barva ohraničení',
  1920. 'vi': 'Màu viền',
  1921. 'it': 'Colore del bordo',
  1922. 'lv': 'Apmales krāsa',
  1923. 'pl': 'Kolor obramowania',
  1924. 'nl': 'Randkleur',
  1925. 'he': 'צבע גבול',
  1926. 'ar': 'لون الحدود',
  1927. 'id': 'Warna perbatasan',
  1928. 'zh-Hans': '边框颜色',
  1929. 'zh-Hant': '邊框顏色',
  1930. 'ja': 'ボーダーカラー',
  1931. 'fi': 'Reunuksen väri',
  1932. 'tr': 'Kenarlık rengi',
  1933. 'el': 'Χρώμα περιγράμματος',
  1934. 'ru': 'Цвет границы',
  1935. 'uk': 'Колір кордону',
  1936. 'bg': 'Цвят на рамката',
  1937. 'defaultValue': 'OrangeRed',
  1938. },
  1939.  
  1940. // - label for tips:
  1941. DLG_TIPS: {
  1942. 'en': 'Tips',
  1943. 'pt': 'Pontas',
  1944. 'de': 'Tipps',
  1945. 'fr': 'Des astuces',
  1946. 'es': 'Consejos',
  1947. 'cs': 'Tipy',
  1948. 'vi': 'Thủ thuật',
  1949. 'it': 'Suggerimenti',
  1950. 'lv': 'Padomi',
  1951. 'pl': 'Sugestia',
  1952. 'nl': 'Tips',
  1953. 'he': 'טיפים',
  1954. 'ar': 'تلميحات',
  1955. 'id': 'Tips',
  1956. 'zh-Hans': '提示',
  1957. 'zh-Hant': '提示',
  1958. 'ja': 'ヒント',
  1959. 'fi': 'Vinkkejä',
  1960. 'tr': 'Ipuçları',
  1961. 'el': 'Συμβουλές',
  1962. 'ru': 'Советы',
  1963. 'uk': 'Підказки',
  1964. 'bg': 'Съвети',
  1965. },
  1966.  
  1967. // - tip's content:
  1968. DLG_TIPS_CONTENT: {
  1969. 'en': 'Clearing your browser\'s cache will reset your settings to their default values.\n\nUse the "Export" and "Import" buttons to backup and restore your customised settings.',
  1970. 'pt': 'Limpar o cache do navegador redefinirá suas configurações para os valores padrão.\n\nUse os botões "Exportar" e "Importar" para fazer backup e restaurar suas configurações personalizadas.',
  1971. 'de': 'Wenn Sie den Cache Ihres Browsers leeren, werden Ihre Einstellungen auf die Standardwerte zurückgesetzt.\n\nVerwenden Sie die Schaltflächen "Exportieren" und "Importieren", um Ihre benutzerdefinierten Einstellungen zu sichern und wiederherzustellen.',
  1972. 'fr': 'Vider le cache de votre navigateur réinitialisera vos paramètres à leurs valeurs par défaut.\n\nUtilisez les boutons "Exporter" et "Importer" pour sauvegarder et restaurer vos paramètres personnalisés.',
  1973. 'es': 'Limpiar la memoria caché de su navegador restablecerá la configuración a sus valores predeterminados.\n\nUtilice los botones "Exportar" e "Importar" para hacer una copia de seguridad y restaurar su configuración personalizada.',
  1974. 'cs': 'Vymazáním mezipaměti prohlížeče obnovíte výchozí hodnoty nastavení.\n\nPomocí tlačítek "Export" a "Import" zálohujte a obnovte svá přizpůsobená nastavení.',
  1975. 'vi': 'Xóa bộ nhớ cache của trình duyệt sẽ đặt lại cài đặt của bạn về các giá trị mặc định của chúng.\n\nSử dụng các nút "Xuất" và "Nhập" để sao lưu và khôi phục cài đặt tùy chỉnh của bạn.',
  1976. 'it': 'La cancellazione della cache del browser ripristinerà le impostazioni ai valori predefiniti.\n\nUtilizza i pulsanti "Esporta" e "Importa" per eseguire il backup e ripristinare le impostazioni personalizzate.',
  1977. 'lv': 'Iztīrot pārlūkprogrammas kešatmiņu, iestatījumi tiks atiestatīti uz noklusējuma vērtībām.\n\nIzmantojiet pogas "Eksportēt" un "Importēt", lai dublētu un atjaunotu pielāgotos iestatījumus.',
  1978. 'pl': 'Wyczyszczenie pamięci podręcznej przeglądarki spowoduje zresetowanie ustawień do wartości domyślnych.\n\nUżyj przycisków „Eksportuj” i „Importuj”, aby wykonać kopię zapasową i przywrócić niestandardowe ustawienia.',
  1979. 'nl': 'Als u de cache van uw browser wist, worden uw instellingen teruggezet naar hun standaardwaarden.\n\nGebruik de knoppen "Exporteren" en "Importeren" om een back-up te maken van uw aangepaste instellingen en deze te herstellen.',
  1980. 'he': 'מחיקת ההיסטורה בדפדפן תנקה את ההגדרות ותחזיר אותם לברירת המחדל.\n\nהשתמש ב"ייצא" ו"ייבא" כדי לגבות ולהחזיר את ההגדרות שלך',
  1981. 'ar': 'سيؤدي مسح ذاكرة التخزين المؤقت للمتصفح إلى إعادة تعيين الإعدادات إلى قيمها الافتراضية.\n\nاستخدم الزرين "تصدير" و "استيراد" للنسخ الاحتياطي واستعادة الإعدادات المخصصة.',
  1982. 'id': 'Menghapus cache browser Anda akan mengatur ulang pengaturan Anda ke nilai defaultnya.\n\nGunakan tombol "Ekspor" dan "Impor" untuk mencadangkan dan memulihkan pengaturan khusus Anda.',
  1983. 'zh-Hans': '清除浏览器缓存会将您的设置重置为默认值。\n\n使用“导出”和“导入”按钮来备份和恢复您的自定义设置。',
  1984. 'zh-Hant': '清除瀏覽器快取會將您的設定重置為預設值。\n\n使用「匯出」和「匯入」按鈕來備份和回復您的自定義設定。',
  1985. 'ja': 'ブラウザのキャッシュをクリアすると、設定がデフォルト値にリセットされます。\n\n[エクスポート] および [インポート] ボタンを使用して、カスタマイズした設定をバックアップおよび復元します。',
  1986. 'fi': 'Selaimen välimuistin tyhjentäminen palauttaa asetuksesi oletusarvoihinsa.\n\nKäytä "Vie"- ja "Tuo"-painikkeita varmuuskopioidaksesi ja palauttaaksesi mukautetut asetukset.',
  1987. 'tr': 'Tarayıcınızın önbelleğini temizlemek, ayarlarınızı varsayılan değerlerine sıfırlayacaktır. \n\nÖzelleştirilmiş ayarlarınızı yedeklemek ve geri yüklemek için "Dışa Aktar" ve "İçe Aktar" düğmelerini kullanın.',
  1988. 'el': 'Η εκκαθάριση της μνήμης cache του προγράμματος περιήγησης θα επαναφέρει τις ρυθμίσεις σας στις προεπιλεγμένες τιμές τους.\n\nΧρησιμοποιήστε τα κουμπιά "Εξαγωγή" και "Εισαγωγή" για να δημιουργήσετε αντίγραφο ασφαλείας και να επαναφέρετε τις εξατομικευμένες ρυθμίσεις σας.',
  1989. 'ru': 'Очистка кэша браузера сбросит ваши настройки на значения по умолчанию.\n\nИспользуйте кнопки «Экспорт» и «Импорт», чтобы создать резервную копию и восстановить ваши настройки.',
  1990. 'uk': 'Очищення кешу браузера призведе до скидання налаштувань до значень за замовчуванням.\n\nВикористовуйте кнопки «Експорт» та «Імпорт», щоб створити резервну копію та відновити налаштовані налаштування.',
  1991. 'bg': 'Изчистването на кеша на браузъра ще нулира настройките ви до техните стандартни стойности.\n\nИзползвайте бутоните "Експорт" и "Импорт", за да запазите и възстановите персонализираните си настройки.'
  1992. },
  1993.  
  1994. DLG_BUTTONS: {
  1995. 'en': ['Save', 'Close', 'Export', 'Import', 'Reset'],
  1996. 'pt': ['Salvar', 'Fechar', 'Exportar', 'Importar', 'Redefinir'],
  1997. 'de': ['Speichern', 'Schließen', 'Exportieren', 'Importieren', 'Zurücksetzen'],
  1998. 'fr': ['Sauvegarder', 'Fermer', 'Exporter', 'Importer', 'Réinitialiser'],
  1999. 'es': ['Guardar', 'Cerrar', 'Exportar', 'Importar', 'Reajustar'],
  2000. 'cs': ['Uložit', 'Zavřít', 'Export', 'Import', 'Resetovat'],
  2001. 'vi': ['Lưu', 'Đóng', 'Xuất', 'Nhập', 'Đặt lại'],
  2002. 'it': ['Salva', 'Chiudi', 'Esportare', 'Importare', 'Ripristina'],
  2003. 'lv': ['Saglabājiet', 'Aizveriet', 'Eksportēt', 'Importēt', 'Atiestatīt'],
  2004. 'pl': ['Zapisz', 'Zamknij', 'Eksport', 'Import', 'Przeskładać'],
  2005. 'nl': ['Opslaan', 'Sluiten', 'Exporteren', 'Importeren', 'Reset'],
  2006. 'he': ['שמור', 'סגור', 'ייצא', 'ייבא', 'איפוס'],
  2007. 'ar': ['حفظ', 'قريب', 'يصدّر', 'يستورد', 'إعادة تعيين'],
  2008. 'id': ['Simpan', 'Tutup', 'Ekspor', 'Impor', 'Reset'],
  2009. 'zh-Hans': ['节省', '关', '出口', '进口', '重置'],
  2010. 'zh-Hant': ['儲存', '關閉', '匯出', '匯入', '重設'],
  2011. 'ja': ['セーブ', 'クローズ', '輸出する', '輸入', 'リセット'],
  2012. 'fi': ['Tallentaa', 'Sulkea', 'Vienti', 'Tuonti', 'Nollaa'],
  2013. 'tr': ['Kaydetmek', 'Kapat', 'İhracat', 'İçe aktarmak', 'Sıfırla'],
  2014. 'el': ['Αποθήκευση', 'Κλείσιμο', 'Εξαγωγή', 'Εισαγωγή', 'Επαναφορά'],
  2015. 'ru': ['Сохранить', 'Закрыть', 'Экспорт', 'Импорт', 'Сброс'],
  2016. 'uk': ['Зберегти', 'Закрити', 'Експорт', 'Імпорт', 'Скинути'],
  2017. 'bg': ['Запази', 'Затвори', 'Експорт', 'Импорт', 'Нулиране'],
  2018. },
  2019.  
  2020. DLG_FB_COLOUR_HINT: {
  2021. 'en': 'Leave blank to use FB\'s colour scheme',
  2022. 'pt': 'Deixe em branco para usar o esquema de cores do FB',
  2023. 'de': 'Leer lassen, um das Farbschema von FB zu verwenden',
  2024. 'fr': 'Laissez vide pour utiliser le jeu de couleurs de FB',
  2025. 'es': 'Dejar en blanco para usar el esquema de color de FB',
  2026. 'cs': 'Chcete-li použít barevné schéma FB, nechte prázdné',
  2027. 'vi': 'Để trống để sử dụng bảng màu của FB',
  2028. 'it': 'Lascia vuoto per usare la combinazione di colori di FB',
  2029. 'lv': 'Atstājiet tukšu, lai izmantotu FB krāsu shēmu',
  2030. 'pl': 'Pozostaw puste, aby użyć schematu kolorów FB',
  2031. 'nl': 'Laat leeg om het kleurenschema van FB te gebruiken',
  2032. 'he': 'השאר ריק כדי להשתמש בערכת הצבעים של FB',
  2033. 'ar': 'اتركه فارغًا لاستخدام نظام ألوان FB',
  2034. 'id': 'Biarkan kosong untuk menggunakan skema warna FB',
  2035. 'zh-Hans': '留空以使用 FB 的配色方案',
  2036. 'zh-Hant': '留空以使用 FB 的配色方案',
  2037. 'ja': '空白のままにすると、FB の配色が使用されます',
  2038. 'fi': 'Jätä tyhjäksi käyttääksesi FB:n värimaailmaa',
  2039. 'tr': 'FB\'un renk düzenini kullanmak için boş bırakın',
  2040. 'el': 'Αφήστε κενό για να χρησιμοποιήσετε το χρωματικό σχήμα του FB',
  2041. 'ru': 'Оставьте пустым, чтобы использовать цветовую схему FB',
  2042. 'uk': 'Залиште порожнім, щоб використовувати колірну схему FB',
  2043. 'bg': 'Оставете празно, за да използвате цветовата схема на FB',
  2044. }
  2045. };
  2046.  
  2047. // *** *** end of language components *** ***
  2048.  
  2049. // - console log "label" - used for filtering console logs.
  2050. const log = '-- fbcmf :: ';
  2051.  
  2052. // - idb-keyval - indexedDB wrapper
  2053. // -- needs the "@require https://unpkg.com/idb-keyval@6.0.3/dist/umd.js" entry.
  2054. // -- which functions do we want to use from the idb-keyval?
  2055. const { get, set, del, createStore } = idbKeyval;
  2056. // - override idb-keyval's default db and store names.
  2057. let DBVARS = {
  2058. DBName: 'dbCMF',
  2059. DBStore: 'Mopping',
  2060. DBKey: 'Options',
  2061. ostore: null
  2062. }
  2063. // - make sure the db's store exists ...
  2064. DBVARS.ostore = createStore(DBVARS.DBName, DBVARS.DBStore);
  2065.  
  2066. // - post attribute - hidden and reason
  2067. const postAtt = 'cmfrx';
  2068. // - post attribute - consecutive posts id
  2069. const postAttCPID = 'cmfcpid';
  2070. // - post property - # of light dusting duties done
  2071. const postPropDS = 'cmfDusted';
  2072. // - post's child element attribute - used for queries that original don't include the parent element.
  2073. const postAttChildFlag = 'cmfcf';
  2074. // - post's toggle state bar + post tab.
  2075. const postAttTab = 'cmftsb'
  2076. // - marketplace post - indicate already scanned
  2077. const postAttMPSkip = 'cmfsmp';
  2078. // - reel video attribute
  2079. const rvAtt = 'cmfrv';
  2080.  
  2081. // - Feed Details variables
  2082. // -- nb: setFeedSettings() adjusts some of these settings.
  2083. const VARS = {
  2084. // - mutations counter
  2085. mutationsCount: 0,
  2086. // - how many mutations to ignore @ start (usually non-feed elements)
  2087. mutationsInitSkip: 75,
  2088. // - how many times to scan a post
  2089. scanCountStart: 0,
  2090. scanCountMaxLoop: 15, // Nov 2023; changed from 12 to 15, need to make code a tad bit more aggressive.
  2091.  
  2092. // - langauge (default to EN)
  2093. language: '',
  2094. // - user options
  2095. Options: {},
  2096. optionsReady: false,
  2097. // - blocked text
  2098. Filters: {},
  2099. // - blocked text separator
  2100. SEP: '¦¦',
  2101.  
  2102. dictionaryReelsAndShortVideos: {},
  2103.  
  2104. // - Feed toggles
  2105. isNF: false, // news
  2106. isGF: false, // groups
  2107. isVF: false, // videos
  2108. isMF: false, // marketplace
  2109. isAF: false, // all feeds
  2110. isSF: false, // search feed
  2111. isRF: false, // reel feed
  2112.  
  2113. isRF_InTimeoutMode: false, // -- processing Reel videos in timeout calls instead of mutations
  2114.  
  2115. // groups feed type : 'group' = single group; 'groups' = multiple groups;
  2116. gfType: '',
  2117.  
  2118. // watch/videos feed type : 'vidoes' = normal feed; 'search' = search videos;
  2119. vfType: '',
  2120.  
  2121. // marketplace feed type: 'marketplace' = default view; 'category' = category view; 'item' = viewing an item; 'search' = search results;
  2122. mpType: '',
  2123.  
  2124. // remember current URL - used for page change detection
  2125. prevURL: '',
  2126. prevPathname: '',
  2127.  
  2128. // element containing echo message about post(s) being hidden
  2129. echoEl: null,
  2130. echoElFirstNote: null, // for restoring "missing" echo message
  2131. echoElCreatedCount: 0,
  2132. echoELFirstPost: null,
  2133. // how many consecutive posts have been hidden
  2134. echoCount: 0,
  2135. // current consecutive posts id
  2136. echoCPID: '',
  2137.  
  2138. // StyleSheet Id
  2139. cssID: '',
  2140. // CSS class names
  2141. cssHide: '',
  2142. cssHideEl: '',
  2143. cssEcho: '',
  2144. cssHideNumberOfShares: '',
  2145. cssShow: '',
  2146. // toggle dialog button (visible if is a Feed page)
  2147. btnToggleEl: null,
  2148. // - script's logo
  2149. logoHTML: '<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="32" height="32"><g id="Layer" fill="currentColor"><path id="Layer" fill-rule="evenodd" class="s0" d="m51 3.2c0.7 1.1 0.7 1-1.6 9.2-1.4 5-2.1 7.4-2.3 7.6-0.1 0.1-0.3 0.2-0.6 0.2-0.4 0-0.9-0.4-0.9-0.7 0-0.1 1-3.5 2-7.4 1.2-4 2-7.3 2-7.5 0-0.4-0.6-1-0.9-1-0.2 0-0.5 0.2-0.7 0.3-0.3 0.3-0.7 1.8-5.5 19.2l-5.3 18.9 0.9 0.5c0.5 0.3 0.9 0.5 0.9 0.5 0 0 1.3-4.4 2.8-9.8 1.5-5.3 2.8-10 2.8-10.3 0.2-0.5 0.3-0.7 0.6-0.9 0.3-0.1 0.4-0.1 0.8 0 0.2 0.2 0.4 0.3 0.4 0.5 0.1 0.2-0.4 2.2-1.5 6.1-0.9 3.2-1.6 5.8-1.6 5.9 0 0 0.5 0.1 1.3 0.1 1.9 0 2.7 0.4 3.2 1.5 0.3 0.6 0.3 2.7 0 3.4-0.3 0.9-1.2 1.4-2 1.4-0.3 0-0.5 0.1-0.5 0.1 0 0.2-2.3 20.2-2.3 20.4-0.2 0.8 0.7 0.7-14.1 0.7-15.3 0-14.3 0.1-15.3-1-0.8-0.8-1.1-1.5-1-2.9 0.2-3.6 2.7-6.7 6.3-7.8 0.4-0.2 0.9-0.3 1-0.3 0.6 0 0.6 0.1 0.1-4.5-0.3-2.4-0.5-4.4-0.5-4.5-0.1-0.1-0.3-0.1-0.7-0.2-0.6 0-1.1-0.3-1.6-1-0.3-0.4-0.3-0.5-0.4-1.8 0-1.7 0.1-2.1 0.6-2.7 0.7-0.6 1-0.7 2.5-0.8h1.3v-2.9c0-3.1 0-3.4 0.6-3.6 0.2-0.1 2.4-0.1 7.1-0.1 6.5 0.1 6.9 0.1 7.1 0.3 0.2 0.2 0.2 0.3 0.2 3.3v3h0.6l0.6-0.1 4.3-15.3c2.4-8.5 4.4-15.6 4.5-15.9 0.4-0.6 0.9-1 1.5-1.3 1.2-0.4 2.6 0.1 3.3 1.2zm-26.6 26.6h-0.7c-0.3 0-0.6 0-0.7 0 0 0.1-0.1 1.2-0.1 2.5v2.3h1.5zm3.4 0h-0.7c-0.5 0-0.9 0-0.9 0.1 0 0-0.1 1.1-0.1 2.4v2.3h1.8v-2.4zm3.4 0h-1.6v4.8h1.6zm3.2 0h-1.3v4.8h1.3zm-6.4 6.6c-7.9 0-9 0-9.2 0.2-0.3 0.2-0.3 0.3-0.3 1.3 0 0.7 0.1 1.1 0.2 1.2 0.1 0.1 2.3 0.1 7.3 0.1 6.9 0.1 7.2 0.1 7.5 0.3 0.3 0.3 0.3 1 0 1.3-0.2 0.2-0.8 0.2-6.3 0.2h-6l0.1 0.5c0 0.3 0.2 2.3 0.5 4.5l0.4 4h0.4c0.6 0 1.5-0.3 2-0.7 0.3-0.3 0.7-0.8 0.9-1.3 0.6-1.1 1.3-2 2.1-2.7 1.1-0.9 2.8-1.5 4-1.5h0.6l0.7-1.1c0.6-1 0.8-1.2 1.3-1.5 0.4-0.2 0.6-0.2 0.9-0.2 0.4 0.1 0.5 0.1 0.5-0.1 0.1-0.1 0.3-1.1 0.6-2.1 0.3-1.1 0.6-2.1 0.6-2.2 0.1-0.2-0.4-0.2-8.8-0.2zm16.2 0h-1.5l-0.4 1.3c-0.2 0.8-0.4 1.4-0.4 1.5 0 0 0.9 0 2 0 2.3 0 2.3 0.1 2.3-1.4 0-0.9-0.1-1-0.3-1.2-0.2-0.2-0.6-0.2-1.7-0.2zm-2.8 4.7c0 0.1-0.2 0.8-0.5 1.6-0.2 1-0.3 1.4-0.2 1.5 0 0 0.3 0.2 0.6 0.4 0.4 0.4 0.4 0.5 0.5 1.2 0 0.6 0 0.7-0.8 2-0.7 1.1-0.8 1.3-1.3 1.6l-0.5 0.2v1.8c0 1.3-0.1 2-0.2 2.5-0.1 0.4-0.2 0.8-0.2 0.8 0 0 0.7 0.1 1.5 0.1 1.2 0 1.6-0.1 1.6-0.2 0-0.1 0.4-3.1 0.8-6.8 0.4-3.6 0.7-6.7 0.7-6.7-0.1-0.2-1.9-0.1-2 0zm-6.3 1.8c-0.2-0.1-0.3 0-0.9 1-0.2 0.4-0.4 0.8-0.3 0.8 0 0.1 1.1 0.7 2.3 1.5 1.3 0.7 2.4 1.4 2.5 1.5 0.3 0.1 0.3 0.1 0.8-0.8 0.3-0.6 0.6-1 0.5-1 0 0-1.1-0.7-2.4-1.5-1.3-0.8-2.4-1.4-2.5-1.5zm-4.5 2.8c-1.6 0.5-2.7 1.5-3.5 3.1-0.6 1.2-1.3 2-2.4 2.5-0.9 0.4-0.9 0.4-2.9 0.5-2.8 0.1-3.9 0.6-5.4 2.1-0.8 0.8-1 1.1-1.4 1.9-1 2.2-0.9 4 0.2 4.4 0.7 0.3 0.8 0.3 1-0.5 0.8-2.4 2.7-4.5 5.1-5.5 1.1-0.4 1.6-0.5 3.2-0.6 2-0.2 2.8-0.7 3.4-2.2 0.3-0.5 0.6-1.2 0.8-1.6 0.8-1.3 2.4-2.5 3.8-2.9 0.4-0.1 0.8-0.2 0.8-0.2q0.2-0.1-0.3-0.4c-0.3-0.2-0.6-0.4-0.6-0.5-0.1-0.3-1.1-0.3-1.8-0.1zm3.2 2.7c-0.9 0.2-2 0.8-2.8 1.5-0.7 0.6-0.8 0.9-1.6 2.6-0.7 1.5-2.2 2.5-3.9 2.7-3.4 0.4-4.3 0.8-5.8 2.2-0.7 0.8-1 1.2-1.4 1.9l-0.5 1 0.9 0.1c0.9 0 0.9 0 1.2-0.4q2.7-3.2 7.3-3.2c2.2 0 2.9-0.5 3.9-2.3 0.3-0.5 0.7-1.2 0.9-1.5 1-1.2 3-2.3 4.6-2.4l0.8-0.1-0.1-0.5c-0.1-0.8-0.3-1.2-0.9-1.4-0.7-0.2-1.9-0.3-2.6-0.2zm3.6 3.9h-0.4c-0.5 0-1.6 0.3-2.3 0.7-0.7 0.5-1.6 1.5-2.2 2.6-1.1 2.1-2.5 2.9-5.2 2.9-0.6 0-1.6 0.1-2 0.2-1 0.2-2.3 0.8-2.9 1.3l-0.4 0.4h4.1c4.6-0.1 4.7-0.1 6.5-1 0.9-0.5 1.3-0.7 2.2-1.6 1.4-1.4 2.2-3 2.5-4.9zm4.3 4.2h-1.9-1.8l-0.5 0.8c-0.6 0.9-1.5 1.9-2.4 2.6l-0.6 0.5h3.4c2.6 0 3.4 0 3.4-0.1 0-0.1 0.1-1 0.2-2z"/></g></svg>',
  2150. // - new window icon
  2151. iconNewWindow: '<svg width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-external-link"><title>Open post in a new window</title><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>',
  2152. // - for reels - chromium browsers needs more space for video controls...
  2153. isChromium: false,
  2154. };
  2155.  
  2156. // -- which language is the FB page in?
  2157. function setLanguageAndOptions() {
  2158. // - run this function when HEAD is available.
  2159. // - language (default to EN)
  2160. // - also run getUserOptions().
  2161. if (document.head) {
  2162. let lang = document.head.parentNode.lang || 'en';
  2163. VARS.language = (KeyWords.LANGUAGES.indexOf(lang) >= 0) ? lang : 'en';
  2164.  
  2165. // ...
  2166. let result = getUserOptions().then(() => {
  2167. return true;
  2168. });
  2169. }
  2170. else {
  2171. setTimeout(setLanguageAndOptions, 5);
  2172. }
  2173. }
  2174.  
  2175. function buildDictonaryForReelsAndShortVideos() {
  2176. VARS.dictionaryReelsAndShortVideos = Object.values(KeyWords.NF_REELS_SHORT_VIDEOS)
  2177. .filter(value => typeof value === 'string')
  2178. .map(value => value.toLocaleLowerCase());
  2179. }
  2180.  
  2181. function generateRandomString() {
  2182. // - generate random text (first letter must be an alphabet)
  2183. // -- used for css classes
  2184. // -- used for postAttCPID
  2185. // -- used for tagging items
  2186. const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  2187. const strArray = [chars.charAt(Math.floor(Math.random() * 52))]; // First letter must be an alphabet
  2188.  
  2189. for (let i = 0; i < 12; i++) {
  2190. strArray.push(chars.charAt(Math.floor(Math.random() * 62)));
  2191. }
  2192.  
  2193. return strArray.join('');
  2194. }
  2195.  
  2196. // -- stylesheet builder
  2197. VARS.tempStyleSheetCode = ''; // holds the SS code while it is being built.
  2198. function addToSS(classes, styles) {
  2199. // -- formats and builds the StyleSheet code
  2200. // -- parameters: classes (separated by comma), styles (separated by semicolon)
  2201. // -- array actions: .filter - remove empties, .map - trim (or pad + trim)
  2202. const listOfClasses = classes.split(',').filter(function (e) { return e.trim() }).map(e => e.trim());
  2203. let styleLines = styles.split(';').filter(function (e) { return e.trim() });
  2204. styleLines = styleLines.map(function (e) { let temp = e.split(':'); return ' ' + temp[0].trim() + ':' + temp[1].trim() });
  2205.  
  2206. let temp = listOfClasses.join(',\n') + ' {\n';
  2207. temp += styleLines.join(';\n') + ';\n';
  2208. temp += '}\n';
  2209. VARS.tempStyleSheetCode += temp;
  2210. }
  2211.  
  2212. // -- various CSS
  2213. function addCSS() {
  2214. // - CSS styles for hiding or highlighting the selected posts / element
  2215. // - CSS styles for dialog panel
  2216. let head, styleEl, classes, styles;
  2217. let isNewCSS = true;
  2218.  
  2219. if (VARS.cssID !== '') {
  2220. // - Reset the existing Stylesheet
  2221. styleEl = document.getElementById(VARS.cssID);
  2222. if (styleEl) {
  2223. styleEl.replaceChildren();
  2224. isNewCSS = false;
  2225. }
  2226. }
  2227. if (isNewCSS) {
  2228. // - Create the new Stylesheet head + classnames
  2229. VARS.cssID = generateRandomString().toUpperCase();
  2230. styleEl = document.createElement('style');
  2231. styleEl.setAttribute('type', 'text/css');
  2232. styleEl.setAttribute('id', VARS.cssID);
  2233. head = document.getElementsByTagName('head')[0];
  2234. head.appendChild(styleEl);
  2235.  
  2236. // - remember <element> attribute names (for other functions to use)
  2237. VARS.cssHide = generateRandomString(); // - the parent element - hides the nth level down element
  2238. VARS.cssHideEl = generateRandomString(); // - the element to hide - where there's no child element
  2239. VARS.cssHideNumberOfShares = generateRandomString(); // - hide "# shares" on posts.
  2240. VARS.cssShow = generateRandomString(); // - for revealing hidden elements.
  2241. }
  2242. VARS.tempStyleSheetCode = ''; // reset temp CSS code.
  2243.  
  2244. // -- override random class names - for testing purposes only.
  2245. // VARS.cssHide = 'cmfr-hide';
  2246. // VARS.cssHideEl = 'cmfr-hide-element';
  2247. // VARS.cssHideNumberOfShares = 'cmfr-hide-shares';
  2248.  
  2249. // -- **** fix fb's bug in not "hiding" certain elements properly when scrolling
  2250. addToSS('body > div[style*="position: absolute"], ' +
  2251. 'body > div[style*="position:absolute"]',
  2252. 'top: -1000000px !important;'
  2253. );
  2254.  
  2255. // - insert Styles (as classes)
  2256. // - NF/GF/VF
  2257. // -- remove margins
  2258. addToSS(
  2259. `[${VARS.cssHide}],` +
  2260. `[${VARS.cssHideEl}]`,
  2261. 'margin:0 !important;'
  2262. );
  2263.  
  2264. // -- post wrapper (mainly for news, groups and video feeds posts)
  2265. classes = `[${VARS.cssHide}] > div:not([${postAttTab}]) > div,`;
  2266. classes += `[${VARS.cssHide}] > span,`;
  2267. // -- non n/f & g/f wrappers (mainly for marketplace posts + some aside boxes)
  2268. classes += `[${VARS.cssHideEl}]`;
  2269. // -- which styles to apply?
  2270. // --- (display:block is for those span tags being a nth-of-child element.)
  2271. styles = 'display:none !important;';
  2272. addToSS(classes, styles);
  2273.  
  2274. // -- # shares
  2275. addToSS(
  2276. `[${VARS.cssHideNumberOfShares}]`,
  2277. 'display:none !important;'
  2278. );
  2279.  
  2280. // - toggle post state :: <toggle button> x posts hidden
  2281. addToSS(
  2282. `div[${postAttTab}]`,
  2283. 'display:flex; margin:1.5rem auto; padding:0.5rem 1rem 0.5rem 0; border-radius:0.55rem; width:85%; font-style:italic; cursor:pointer;' +
  2284. ((VARS.Options.VERBOSITY_MESSAGE_COLOUR === '') ? '' : ` color: ${VARS.Options.VERBOSITY_MESSAGE_COLOUR}; `) +
  2285. `background-color:${(VARS.Options.VERBOSITY_MESSSAGE_BG_COLOUR === '') ? KeyWords.VERBOSITY_MESSAGE_BG_COLOUR.defaultValue : VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR};`
  2286. );
  2287. // - echo msg flex boxes
  2288. addToSS(
  2289. `div[${postAttTab}] > div`,
  2290. 'display:flex; align-items: center;'
  2291. );
  2292. // - echo msg's button
  2293. addToSS(
  2294. `div[${postAttTab}] > div.wbtn`,
  2295. 'flex:0 0 3rem; justify-content: center;'
  2296. );
  2297. // - echo msg's text
  2298. addToSS(
  2299. `div[${postAttTab}] > div.wtxt`,
  2300. 'flex:1;'
  2301. );
  2302. // - echo msg hover
  2303. addToSS(
  2304. `div[${postAttTab}]:hover`,
  2305. 'text-decoration: underline; background-color:white; color:black;'
  2306. );
  2307. addToSS(
  2308. `div[${postAttTab}] button:hover`,
  2309. 'cursor:pointer;'
  2310. );
  2311. // - echo msg's button's default state
  2312. addToSS(
  2313. `div[${postAttTab}] > div.wbtn > button`,
  2314. 'transform: rotate(180deg);transition: transform 0.15s linear;'
  2315. );
  2316. // - flagged post's mini-tab
  2317. addToSS(
  2318. `h6[${postAttTab}]`,
  2319. 'border-radius: 0.55rem 0.55rem 0 0; width:75%; margin:0 auto; padding: 0.45rem 0.25rem; font-style:italic; text-align:center; font-weight:normal;' +
  2320. ((VARS.Options.VERBOSITY_MESSAGE_COLOUR === '') ? '' : ` color: ${VARS.Options.VERBOSITY_MESSAGE_COLOUR}; `) +
  2321. `background-color:${(VARS.Options.VERBOSITY_MESSSAGE_BG_COLOUR === '') ? KeyWords.VERBOSITY_MESSAGE_BG_COLOUR.defaultValue : VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR}; `
  2322. );
  2323.  
  2324. // - 'show' - reveal a hidden post (except for mp)
  2325. addToSS(
  2326. `[${VARS.cssShow}]:not([${VARS.cssHideEl}]) > div:not([${postAttTab}]) > div, ` +
  2327. `[${VARS.cssHideNumberOfShares}][${VARS.cssShow}]`,
  2328. 'display:block !important; ' +
  2329. `border:3px dotted ${VARS.Options.CMF_BORDER_COLOUR} !important; border-radius:8px; padding:0.2rem 0.1rem 0.1rem 0.1rem;` // 4px
  2330. );
  2331. // - echo msg show state
  2332. addToSS(
  2333. `[${VARS.cssShow}] > div[${postAttTab}]`,
  2334. 'margin-top:0.5rem; margin-bottom:0.5rem;'
  2335. );
  2336. // - echo msg's button show state
  2337. addToSS(
  2338. `[${VARS.cssShow}] > div[${postAttTab}] > div.wbtn > button`,
  2339. 'transform: rotate(360deg);transition: transform 0.15s linear;'
  2340. );
  2341. // - non-standard post's show state (mp)
  2342. addToSS(
  2343. `[${VARS.cssHideEl}][${VARS.cssShow}] `,
  2344. `display: block !important; border:3px dotted ${VARS.Options.CMF_BORDER_COLOUR} !important; border-radius:8px; padding:0.1rem;` // 4px
  2345. );
  2346. // - dailog box CSS
  2347. // --- dialog box; position + flex
  2348. let bColour = (VARS.Options.CMF_BORDER_COLOUR === '') ? KeyWords.CMF_BORDER_COLOUR.defaultValue : VARS.Options.CMF_BORDER_COLOUR;
  2349. let tColour = ((VARS.Options.CMF_DIALOG_COLOUR === '') || (VARS.Options.CMF_DIALOG_COLOUR === undefined)) ? 'var(--primary-text)' : VARS.Options.CMF_DIALOG_COLOUR;
  2350. // - left / right done in fn addExtraCSS()
  2351. addToSS(
  2352. '.fb-cmf ',
  2353. 'position:fixed; top:0.15rem; bottom:0.15rem; display:flex; flex-direction:column; max-width:30rem; padding:0 1rem; z-index:5;' +
  2354. `border:2px solid ${bColour}; border-radius:1rem; opacity:0; color:${tColour}; visibility:hidden;`
  2355. );
  2356. if ((VARS.Options.CMF_DIALOG_BG_COLOUR === '') || (VARS.Options.CMF_DIALOG_BG_COLOUR === undefined)) {
  2357. addToSS('.__fb-light-mode .fb-cmf', 'background-color:#fefefa;');
  2358. addToSS('.__fb-dark-mode .fb-cmf', 'background-color:var(--web-wash);');
  2359. addToSS('.fb-cmf', 'background-color:floralwhite;'); // -- fall back colour.
  2360. }
  2361. else {
  2362. addToSS('.fb-cmf', `background-color:${VARS.Options.CMF_DIALOG_BG_COLOUR};`);
  2363. }
  2364. // -- header
  2365. addToSS(
  2366. '.fb-cmf header',
  2367. 'display:flex; justify-content:space-between; direction:ltr;'
  2368. );
  2369. addToSS(
  2370. '.fb-cmf header .fb-cmf-icon',
  2371. 'flex-grow:0; align-self:auto; width:75px; text-align:left; order:1;'
  2372. );
  2373. addToSS(
  2374. '.fb-cmf header .fb-cmf-icon svg',
  2375. 'width:64px; height:64px; margin:2px 0;'
  2376. );
  2377. addToSS(
  2378. '.fb-cmf header .fb-cmf-title',
  2379. 'flex-grow:2; align-self:auto; order:2;'
  2380. );
  2381. addToSS(
  2382. '.fb-cmf header .fb-cmf-title .script-version',
  2383. 'font-size: 0.75rem; font-weight: normal;'
  2384. );
  2385. addToSS(
  2386. '.fb-cmf header .fb-cmf-lang-1',
  2387. 'padding-top:1.25rem;'
  2388. );
  2389. addToSS(
  2390. '.fb-cmf header .fb-cmf-lang-2',
  2391. 'padding-top:0.75rem;'
  2392. );
  2393. addToSS(
  2394. '.fb-cmf header .fb-cmf-title > div',
  2395. 'font-size:1.35rem; font-weight: 700; text-align:center;'
  2396. );
  2397. addToSS(
  2398. '.fb-cmf header .fb-cmf-title > small',
  2399. 'display:block; font-size:0.8rem; text-align:center;'
  2400. );
  2401. addToSS(
  2402. '.fb-cmf header .fb-cmf-close',
  2403. 'flex-grow:0; align-self:auto; width:75px; text-align:right; padding: 1.5rem 0 0 0; order:3;'
  2404. );
  2405. addToSS(
  2406. '.fb-cmf header .fb-cmf-close button',
  2407. 'width:1.75rem; height:1.5rem; font-family: monospace;'
  2408. );
  2409.  
  2410. // -- content
  2411. addToSS(
  2412. '.fb-cmf div.content',
  2413. ` flex:1; overflow:auto; border:2px double ${bColour}; border-radius:0.5rem; color: var(--primary-text);`
  2414. );
  2415. addToSS(
  2416. '.fb-cmf fieldset',
  2417. 'margin:0.5rem; padding:0.5rem; border-color:LightGrey;'
  2418. );
  2419. addToSS(
  2420. '.fb-cmf fieldset legend',
  2421. 'font-weight:700;'
  2422. );
  2423. addToSS(
  2424. '.fb-cmf fieldset label',
  2425. 'display:inline-block; padding:0.125rem 0; color: var(--primary-text); font-weight: normal;'
  2426. );
  2427. addToSS(
  2428. '.fb-cmf fieldset label input',
  2429. 'margin: 0 0.5rem 0 0.5rem; vertical-align:baseline;' // left & right margins for RTL & LTR text
  2430. );
  2431. addToSS(
  2432. '.fb-cmf fieldset label[disabled]',
  2433. 'color:darkgrey;'
  2434. )
  2435. addToSS(
  2436. '.fb-cmf fieldset textarea',
  2437. 'width:100%; height:12rem;'
  2438. );
  2439. addToSS(
  2440. '.__fb-dark-mode .fb-cmf fieldset textarea,' +
  2441. '.__fb-dark-mode .fb-cmf fieldset input[type="input"]',
  2442. 'background-color:var(--comment-background); color:var(--primary-text);'
  2443. );
  2444. // -- footer - buttons + results
  2445. addToSS(
  2446. '.fb-cmf footer',
  2447. 'display: grid; justify-content: space-evenly; padding:1rem 0.25rem; text-align:center;'
  2448. );
  2449. addToSS(
  2450. '.fb-cmf .buttons button',
  2451. // 'margin-left: 1rem; margin-right:1rem;'
  2452. 'margin-left: 0.25rem; margin-right: 0.25rem;'
  2453. );
  2454. // -- footer - file input
  2455. addToSS(
  2456. '.fb-cmf .fileInput',
  2457. 'display:none;'
  2458. );
  2459. // -- footer - import results
  2460. addToSS(
  2461. '.fb-cmf .fileResults',
  2462. 'grid-column-start: 1; grid-column-end: 6; font-style:italic; margin-top: 1rem;'
  2463. );
  2464. // -- show dialog box (default is not to show)
  2465. addToSS(
  2466. `.fb-cmf[${VARS.cssShow}]`,
  2467. 'opacity:1; transform:scale(1); visibility:visible;'
  2468. );
  2469. // -- new window icon
  2470. addToSS(
  2471. '.link-new',
  2472. 'width: 1rem; height: 1rem;'
  2473. );
  2474. // 'width: 1rem; height: 1rem; margin-left: 0.2rem; margin-right: 0.2rem;'
  2475. addToSS(
  2476. '.link-new a',
  2477. 'width: 1rem; position: relative; display: inline-block;'
  2478. );
  2479. addToSS(
  2480. '.link-new svg',
  2481. 'position: absolute; top: -13.5px; stroke: rgb(101, 103, 107);'
  2482. );
  2483.  
  2484. // - save & apply the CSS
  2485. styleEl.appendChild(document.createTextNode(VARS.tempStyleSheetCode));
  2486. VARS.tempStyleSheetCode = '';
  2487. }
  2488.  
  2489. function addExtraCSS() {
  2490. // - extra CSS styles
  2491. // - fb can sometimes be a bit slow in loading certain parts of the site ...
  2492. // - ... this function is called several ms later ...
  2493. // - ... and when saving the options (via save button)
  2494. let cmfBtnLocation = KeyWords.CMF_BTN_OPTION.defaultValue;
  2495. let cmfDlgLocation = KeyWords.CMF_DIALOG_OPTION.defaultValue
  2496. if (VARS.Options.hasOwnProperty('CMF_BTN_OPTION')) {
  2497. if (VARS.Options.CMF_BTN_OPTION.toString() !== '') {
  2498. cmfBtnLocation = VARS.Options.CMF_BTN_OPTION;
  2499. }
  2500. }
  2501. if (VARS.Options.hasOwnProperty('CMF_DIALOG_OPTION')) {
  2502. if (VARS.Options.CMF_DIALOG_OPTION.toString() !== '') {
  2503. cmfDlgLocation = VARS.Options.CMF_DIALOG_OPTION;
  2504. }
  2505. }
  2506. cmfBtnLocation = cmfBtnLocation.toString();
  2507. cmfDlgLocation = cmfDlgLocation.toString();
  2508.  
  2509. // Grab the existing Stylesheet and amend it
  2510. let styleEl = document.getElementById(VARS.cssID);
  2511. let styles;
  2512.  
  2513. VARS.tempStyleSheetCode = ''; // reset
  2514.  
  2515. // - button's location.
  2516. if (cmfBtnLocation === '1') {
  2517. // - top right - has the buttons running across the top of the page (pre May 2022).
  2518. if (document.querySelector('[role="banner"]')) {
  2519. // - oldish FB structure has menu buttons across the top (changed for some users in Apr/May 2022)
  2520. addToSS(
  2521. 'div[role="banner"] > div:last-of-type div[role="navigation"]',
  2522. 'margin-right: 42px;'
  2523. );
  2524. }
  2525. styles = 'position:fixed; top:0.5rem; right:0.5rem; display:none;';
  2526. }
  2527. else if (cmfBtnLocation === '2') {
  2528. // - disabled, use "Settings" in the user script menu.
  2529. // - no css
  2530. styles = 'display: none;';
  2531. }
  2532. else {
  2533. // - bottom left - has the buttons running down the side of the page (May 2022 ->).
  2534. // - cmfBtnLocation === "0"
  2535. // styles = 'position:fixed; bottom:4.25rem; left:1.1rem; display:none;';
  2536. styles = 'position:fixed; bottom:4.25rem; left:1.1rem; display:none; z-index:999;';
  2537. }
  2538. if (styles.length > 0) {
  2539. addToSS(
  2540. '.fb-cmf-toggle',
  2541. styles
  2542. );
  2543. // - btn - basic styling.
  2544. addToSS('.fb-cmf-toggle', 'border-radius:0.3rem;');
  2545. addToSS('.fb-cmf-toggle svg', 'height:32px; width:32px;');
  2546. addToSS('.fb-cmf-toggle:hover', 'cursor:pointer;');
  2547. // - dialog box's display
  2548. addToSS(`.fb-cmf-toggle[${VARS.cssShow}]`, 'display:block;');
  2549. }
  2550. // - dialog box's left/right + animated open/close behaviour
  2551. if (cmfDlgLocation === '1') {
  2552. // - right
  2553. styles = 'right:0.35rem; margin-left:1rem; transform:scale(0);transform-origin:top right;';
  2554. }
  2555. else {
  2556. // - left (cmfDlgLocation === '0')
  2557. styles = 'left:4.25rem; margin-right:1rem; transform:scale(0);transform-origin:center center;';
  2558. }
  2559. addToSS(
  2560. '.fb-cmf',
  2561. styles +
  2562. 'transition:transform .45s ease, opacity .25s ease, visibility 1s ease;'
  2563. );
  2564. if (VARS.tempStyleSheetCode.length > 0) {
  2565. styleEl.appendChild(document.createTextNode(VARS.tempStyleSheetCode));
  2566. VARS.tempStyleSheetCode = '';
  2567. }
  2568. }
  2569.  
  2570. // -- get the user's settings ...
  2571. async function getUserOptions() {
  2572. // -- read in the saved data, else set defaults.
  2573. let changed = false;
  2574. // - reset Options
  2575. VARS.Options = new Object();
  2576. VARS.optionsReady = false;
  2577.  
  2578. // - has the user previously saved options?
  2579. // -- if yes, the update Options
  2580. let result = await get(DBVARS.DBKey, DBVARS.ostore).then((values) => {
  2581. if (values) {
  2582. // -- has data
  2583. VARS.Options = JSON.parse(values);
  2584. return 1;
  2585. }
  2586. else {
  2587. // -- no data (first time)
  2588. return 0;
  2589. }
  2590. }).catch((err) => {
  2591. console.info(`${log}getuserOptions() > get() - Error:`, err);
  2592. });
  2593.  
  2594. // -- check that all variables exists ... if not, assign them default values..
  2595. // -- Sponsored (always enabled)
  2596. if (!VARS.Options.hasOwnProperty('NF_SPONSORED')) {
  2597. VARS.Options.NF_SPONSORED = KeyWords['SPONSORED'].defaultEnabled;
  2598. changed = true;
  2599. }
  2600. if (!VARS.Options.hasOwnProperty('GF_SPONSORED')) {
  2601. VARS.Options.GF_SPONSORED = KeyWords['SPONSORED'].defaultEnabled;
  2602. changed = true;
  2603. }
  2604. if (!VARS.Options.hasOwnProperty('VF_SPONSORED')) {
  2605. VARS.Options.VF_SPONSORED = KeyWords['SPONSORED'].defaultEnabled;
  2606. changed = true;
  2607. }
  2608. if (!VARS.Options.hasOwnProperty('MP_SPONSORED')) {
  2609. VARS.Options.MP_SPONSORED = KeyWords['SPONSORED'].defaultEnabled;
  2610. changed = true;
  2611. }
  2612.  
  2613. // -- which option has been enabled / disabled?
  2614. VARS.hideAnInfoBox = false;
  2615. for (const key in KeyWords) {
  2616. if (key.slice(0, 3) === 'NF_' && key.slice(0, 10) !== 'NF_BLOCKED') {
  2617. if (!VARS.Options.hasOwnProperty(key)) {
  2618. VARS.Options[key] = KeyWords[key].defaultEnabled;
  2619. changed = true;
  2620. }
  2621. }
  2622. else if (key.slice(0, 3) === 'GF_' && key.slice(0, 10) !== 'GF_BLOCKED') {
  2623. if (!VARS.Options.hasOwnProperty(key)) {
  2624. VARS.Options[key] = KeyWords[key].defaultEnabled;
  2625. changed = true;
  2626. }
  2627. }
  2628. else if (key.slice(0, 3) === 'VF_' && key.slice(0, 10) !== 'VF_BLOCKED') {
  2629. if (!VARS.Options.hasOwnProperty(key)) {
  2630. VARS.Options[key] = KeyWords[key].defaultEnabled;
  2631. changed = true;
  2632. }
  2633. }
  2634. else if (key.slice(0, 3) === 'MP_' && key.slice(0, 10) !== 'MP_BLOCKED') {
  2635. if (!VARS.Options.hasOwnProperty(key)) {
  2636. VARS.Options[key] = KeyWords[key].defaultEnabled;
  2637. changed = true;
  2638. }
  2639. }
  2640. else if (key.slice(0, 10) === 'OTHER_INFO') {
  2641. if (!VARS.Options.hasOwnProperty(key)) {
  2642. VARS.Options[key] = KeyWords[key].defaultEnabled;
  2643. changed = true;
  2644. }
  2645. if (VARS.Options[key]) {
  2646. VARS.hideAnInfoBox = true;
  2647. }
  2648. }
  2649. }
  2650.  
  2651. // -- all other options.
  2652. if (!VARS.Options.hasOwnProperty('NF_BLOCKED_ENABLED')) {
  2653. VARS.Options.NF_BLOCKED_ENABLED = KeyWords.NF_BLOCKED_ENABLED.defaultEnabled;
  2654. changed = true;
  2655. }
  2656. if (!VARS.Options.hasOwnProperty('NF_BLOCKED_FEED')) {
  2657. VARS.Options.NF_BLOCKED_FEED = KeyWords.NF_BLOCKED_FEED.defaultEnabled;
  2658. changed = true;
  2659. }
  2660. if (!VARS.Options.hasOwnProperty('NF_BLOCKED_TEXT')) {
  2661. VARS.Options.NF_BLOCKED_TEXT = '';
  2662. changed = true;
  2663. }
  2664.  
  2665. if (!VARS.Options.hasOwnProperty('GF_BLOCKED_ENABLED')) {
  2666. VARS.Options.GF_BLOCKED_ENABLED = KeyWords.GF_BLOCKED_ENABLED.defaultEnabled;
  2667. changed = true;
  2668. }
  2669. if (!VARS.Options.hasOwnProperty('GF_BLOCKED_FEED')) {
  2670. VARS.Options.GF_BLOCKED_FEED = KeyWords.GF_BLOCKED_FEED.defaultEnabled;
  2671. changed = true;
  2672. }
  2673. if (!VARS.Options.hasOwnProperty('GF_BLOCKED_TEXT')) {
  2674. VARS.Options.GF_BLOCKED_TEXT = '';
  2675. changed = true;
  2676. }
  2677.  
  2678. if (!VARS.Options.hasOwnProperty('VF_BLOCKED_ENABLED')) {
  2679. VARS.Options.VF_BLOCKED_ENABLED = KeyWords.VF_BLOCKED_ENABLED.defaultEnabled;
  2680. changed = true;
  2681. }
  2682. if (!VARS.Options.hasOwnProperty('VF_BLOCKED_FEED')) {
  2683. VARS.Options.VF_BLOCKED_FEED = KeyWords.VF_BLOCKED_FEED.defaultEnabled;
  2684. changed = true;
  2685. }
  2686. if (!VARS.Options.hasOwnProperty('VF_BLOCKED_TEXT')) {
  2687. VARS.Options.VF_BLOCKED_TEXT = '';
  2688. changed = true;
  2689. }
  2690.  
  2691. if (!VARS.Options.hasOwnProperty('MP_BLOCKED_ENABLED')) {
  2692. VARS.Options.MP_BLOCKED_ENABLED = KeyWords.MP_BLOCKED_ENABLED.defaultEnabled;
  2693. changed = true;
  2694. }
  2695. if (!VARS.Options.hasOwnProperty('MP_BLOCKED_FEED')) {
  2696. VARS.Options.MP_BLOCKED_FEED = KeyWords.MP_BLOCKED_FEED.defaultEnabled;
  2697. changed = true;
  2698. }
  2699. if (!VARS.Options.hasOwnProperty('MP_BLOCKED_TEXT')) {
  2700. VARS.Options.MP_BLOCKED_TEXT = '';
  2701. changed = true;
  2702. }
  2703. if (!VARS.Options.hasOwnProperty('MP_BLOCKED_TEXT_DESCRIPTION')) {
  2704. VARS.Options.MP_BLOCKED_TEXT_DESCRIPTION = '';
  2705. changed = true;
  2706. }
  2707. if (!VARS.Options.hasOwnProperty('VERBOSITY_LEVEL')) {
  2708. VARS.Options.VERBOSITY_LEVEL = KeyWords.DLG_VERBOSITY.defaultValue;
  2709. changed = true;
  2710. }
  2711. if (!VARS.Options.hasOwnProperty('VERBOSITY_MESSAGE_COLOUR')) {
  2712. VARS.Options.VERBOSITY_MESSAGE_COLOUR = '';
  2713. changed = true;
  2714. }
  2715. // - nb: test conditions, undefined needs to be tested before using .toString(), otherwise JS complains...
  2716. if (
  2717. (!VARS.Options.hasOwnProperty('VERBOSITY_MESSAGE_BG_COLOUR')) ||
  2718. (VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR === undefined) ||
  2719. (VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR.toString() === '')
  2720. ) {
  2721. VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR = KeyWords.VERBOSITY_MESSAGE_BG_COLOUR.defaultValue;
  2722. changed = true;
  2723. }
  2724. if (
  2725. (!VARS.Options.hasOwnProperty('VERBOSITY_DEBUG')) ||
  2726. (VARS.Options.VERBOSITY_DEBUG === undefined) ||
  2727. (VARS.Options.VERBOSITY_DEBUG.toString() === '')
  2728. ) {
  2729. VARS.Options.VERBOSITY_DEBUG = KeyWords.VERBOSITY_DEBUG.defaultValue;
  2730. changed = true;
  2731. }
  2732.  
  2733. if (!VARS.Options.hasOwnProperty('CMF_BTN_OPTION')) {
  2734. VARS.Options.CMF_BTN_OPTION = KeyWords.CMF_BTN_OPTION.defaultValue;
  2735. changed = true;
  2736. }
  2737. if (!VARS.Options.hasOwnProperty('CMF_DIALOG_OPTION')) {
  2738. VARS.Options.CMF_DIALOG_OPTION = KeyWords.CMF_DIALOG_OPTION.defaultValue;
  2739. changed = true;
  2740. }
  2741. if (
  2742. (!VARS.Options.hasOwnProperty('CMF_BORDER_COLOUR')) ||
  2743. (VARS.Options.CMF_BORDER_COLOUR.toString() === undefined) ||
  2744. (VARS.Options.CMF_BORDER_COLOUR.toString() === '')
  2745. ) {
  2746. VARS.Options.CMF_BORDER_COLOUR = KeyWords.CMF_BORDER_COLOUR.defaultValue;
  2747. changed = true;
  2748. }
  2749. if (
  2750. (!VARS.Options.hasOwnProperty('CMF_DIALOG_COLOUR')) ||
  2751. (VARS.Options.CMF_DIALOG_COLOUR.toString() === undefined)
  2752. ) {
  2753. VARS.Options.CMF_DIALOG_COLOUR = KeyWords.CMF_DIALOG_COLOUR.defaultValue;
  2754. changed = true;
  2755. }
  2756. if (
  2757. (!VARS.Options.hasOwnProperty('CMF_DIALOG_BG_COLOUR')) ||
  2758. (VARS.Options.CMF_DIALOG_BG_COLOUR.toString() === undefined)
  2759. ) {
  2760. VARS.Options.CMF_DIALOG_BG_COLOUR = KeyWords.CMF_DIALOG_BG_COLOUR.defaultValue;
  2761. changed = true;
  2762. }
  2763. if (!VARS.Options.hasOwnProperty('NF_LIKES_MAXIMUM_COUNT')) {
  2764. VARS.Options.NF_LIKES_MAXIMUM_COUNT = '';
  2765. changed = true;
  2766. }
  2767.  
  2768. if (changed) {
  2769. // - save the changes ...
  2770. // -- usually happen if first time setup or change in Options' variables.
  2771. let result = await set(DBVARS.DBKey, JSON.stringify(VARS.Options), DBVARS.ostore).then(() => {
  2772. return true;
  2773. }).catch((err) => {
  2774. console.info(`${log}getUserOptions() > changed > saving - failed, Error: ${err}`);
  2775. return false;
  2776. });
  2777. if (VARS.Options.VERBOSITY_DEBUG) {
  2778. if (result) {
  2779. console.info(`${log}Changed - success`);
  2780. }
  2781. else {
  2782. console.info(`${log}Changed - failed`);
  2783. }
  2784. }
  2785. }
  2786.  
  2787. // split the blocks of texts entries into arrays and translate to lowercase.
  2788. VARS.Filters = new Object();
  2789.  
  2790. // -- original list of text for each feed
  2791. let nfBlockedText = '';
  2792. let gfBlockedText = '';
  2793. let vfBlockedText = '';
  2794. let mpBlockedText = '';
  2795. let mpBlockedTextDesc = '';
  2796. if (VARS.Options.NF_BLOCKED_ENABLED === true) {
  2797. nfBlockedText = VARS.Options.NF_BLOCKED_TEXT;
  2798. }
  2799. if (VARS.Options.GF_BLOCKED_ENABLED === true) {
  2800. gfBlockedText = VARS.Options.GF_BLOCKED_TEXT;
  2801. }
  2802. if (VARS.Options.VF_BLOCKED_ENABLED === true) {
  2803. vfBlockedText = VARS.Options.VF_BLOCKED_TEXT;
  2804. }
  2805. if (VARS.Options.MP_BLOCKED_ENABLED === true) {
  2806. mpBlockedText = VARS.Options.MP_BLOCKED_TEXT;
  2807. mpBlockedTextDesc = VARS.Options.MP_BLOCKED_TEXT_DESCRIPTION;
  2808. }
  2809.  
  2810. // -- final list of text for each feed
  2811. let nfBlockedTextList = '';
  2812. let gfBlockedTextList = '';
  2813. let vfBlockedTextList = '';
  2814. let mpBlockedTextList = '';
  2815. let mpBlockedTextDescList = '';
  2816.  
  2817. // -- ##_BLOCKED_FEED[X] : 0 = NF, 1 = GF, 2 = VF.
  2818. // -- rule: both feeds must be enabled before appending text list from one feed to another text list
  2819. // -- news feed:
  2820. if (VARS.Options.NF_BLOCKED_ENABLED) {
  2821. nfBlockedTextList = nfBlockedText; // final list
  2822. // -- add gf to nf
  2823. if (VARS.Options.GF_BLOCKED_ENABLED && VARS.Options.GF_BLOCKED_FEED[0] === '1') {
  2824. if (gfBlockedText.length > 0) {
  2825. nfBlockedTextList += ((nfBlockedTextList.length > 0) ? VARS.SEP : '') + gfBlockedText;
  2826. }
  2827. }
  2828. // -- add vf to nf
  2829. if (VARS.Options.VF_BLOCKED_ENABLED && VARS.Options.VF_BLOCKED_FEED[0] === '1') {
  2830. if (vfBlockedText.length > 0) {
  2831. nfBlockedTextList += ((nfBlockedTextList.length > 0) ? VARS.SEP : '') + vfBlockedText;
  2832. }
  2833. }
  2834. }
  2835. // -- groups feed:
  2836. if (VARS.Options.GF_BLOCKED_ENABLED) {
  2837. gfBlockedTextList = gfBlockedText; // final list
  2838. // -- add nf to gf
  2839. if (VARS.Options.NF_BLOCKED_ENABLED && VARS.Options.NF_BLOCKED_FEED[1] === '1') {
  2840. if (nfBlockedText.length > 0) {
  2841. gfBlockedTextList += ((gfBlockedTextList.length > 0) ? VARS.SEP : '') + nfBlockedText;
  2842. }
  2843. }
  2844. // -- add vf to gf
  2845. if (VARS.Options.VF_BLOCKED_ENABLED && VARS.Options.VF_BLOCKED_FEED[1] === '1') {
  2846. if (vfBlockedText.length > 0) {
  2847. gfBlockedTextList += ((gfBlockedTextList.length > 0) ? VARS.SEP : '') + vfBlockedText;
  2848. }
  2849. }
  2850. }
  2851. // -- videos feed:
  2852. if (VARS.Options.VF_BLOCKED_ENABLED) {
  2853. vfBlockedTextList = vfBlockedText; // final list
  2854. // -- add nf to vf
  2855. if (VARS.Options.NF_BLOCKED_ENABLED && VARS.Options.NF_BLOCKED_FEED[2] === '1') {
  2856. if (nfBlockedText.length > 0) {
  2857. vfBlockedTextList += ((vfBlockedTextList.length > 0) ? VARS.SEP : '') + nfBlockedText;
  2858. }
  2859. }
  2860. // -- add gf to vf
  2861. if (VARS.Options.GF_BLOCKED_ENABLED && VARS.Options.GF_BLOCKED_FEED[2] === '1') {
  2862. if (gfBlockedText.length > 0) {
  2863. vfBlockedTextList += ((vfBlockedTextList.length > 0) ? VARS.SEP : '') + gfBlockedText;
  2864. }
  2865. }
  2866. }
  2867.  
  2868. // -- market place (stand-alone)
  2869. if (VARS.Options.MP_BLOCKED_ENABLED) {
  2870. mpBlockedTextList = mpBlockedText;
  2871. mpBlockedTextDescList = mpBlockedTextDesc;
  2872. }
  2873.  
  2874. // -- populate the VARS.Filters.###...
  2875. // -- news feed:
  2876. VARS.Filters.NF_BLOCKED_TEXT = [];
  2877. VARS.Filters.NF_BLOCKED_TEXT_LC = [];
  2878. VARS.Filters.NF_BLOCKED_ENABLED = false;
  2879. if (VARS.Options.NF_BLOCKED_ENABLED && nfBlockedTextList.length > 0) {
  2880. VARS.Filters.NF_BLOCKED_ENABLED = true;
  2881. VARS.Filters.NF_BLOCKED_TEXT = nfBlockedTextList.split(VARS.SEP);
  2882. VARS.Filters.NF_BLOCKED_TEXT_LC = VARS.Filters.NF_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  2883. }
  2884. // -- groups feed:
  2885. VARS.Filters.GF_BLOCKED_TEXT = [];
  2886. VARS.Filters.GF_BLOCKED_TEXT_LC = [];
  2887. VARS.Filters.GF_BLOCKED_ENABLED = false;
  2888. if (VARS.Options.GF_BLOCKED_ENABLED && gfBlockedTextList.length > 0) {
  2889. VARS.Filters.GF_BLOCKED_ENABLED = true;
  2890. VARS.Filters.GF_BLOCKED_TEXT = gfBlockedTextList.split(VARS.SEP);
  2891. VARS.Filters.GF_BLOCKED_TEXT_LC = VARS.Filters.GF_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  2892. }
  2893. // -- watch videos feed
  2894. VARS.Filters.VF_BLOCKED_TEXT = [];
  2895. VARS.Filters.VF_BLOCKED_TEXT_LC = [];
  2896. VARS.Filters.VF_BLOCKED_ENABLED = false;
  2897. if (VARS.Options.VF_BLOCKED_ENABLED && vfBlockedTextList.length > 0) {
  2898. VARS.Filters.VF_BLOCKED_ENABLED = true;
  2899. VARS.Filters.VF_BLOCKED_TEXT = vfBlockedTextList.split(VARS.SEP);
  2900. VARS.Filters.VF_BLOCKED_TEXT_LC = VARS.Filters.VF_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  2901. }
  2902. // -- marketplace feed
  2903. VARS.Filters.MP_BLOCKED_TEXT = [];
  2904. VARS.Filters.MP_BLOCKED_TEXT_LC = [];
  2905. VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION = [];
  2906. VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION_LC = [];
  2907. VARS.Filters.MP_BLOCKED_ENABLED = false;
  2908. if (VARS.Options.MP_BLOCKED_ENABLED && ((mpBlockedTextList.length > 0) || (mpBlockedTextDescList.length > 0))) {
  2909. VARS.Filters.MP_BLOCKED_ENABLED = true;
  2910. // -- prices ::
  2911. VARS.Filters.MP_BLOCKED_TEXT = mpBlockedTextList.split(VARS.SEP);
  2912. VARS.Filters.MP_BLOCKED_TEXT_LC = VARS.Filters.MP_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  2913. // -- description ::
  2914. VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION = mpBlockedTextDescList.split(VARS.SEP);
  2915. VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION_LC = VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION.map(btext => btext.toLowerCase());
  2916. }
  2917.  
  2918. // console.info(log + 'getUserOptions() - Options:', VARS.Options);
  2919. // console.info(log + 'getUserOptions() - Filters:', VARS.Filters);
  2920.  
  2921. VARS.optionsReady = true;
  2922. }
  2923.  
  2924. // -- run some functions now - not dependent on HEAD being available.
  2925. // (includes getUserOptions())
  2926. setLanguageAndOptions();
  2927.  
  2928. // -- dailog box for displaying options (called in runMO)
  2929. function buildMoppingDialog() {
  2930. // build the dialog box component ...
  2931. // -- BODY must be available for use.
  2932. // -- used for displaying/getting/setting the various options
  2933.  
  2934. function createSingleCB(cbName, cbReadOnly = false) {
  2935. // -- create toggle style checkboxes
  2936. const CBTYPE = 'T'; // checkbox, single value, Toggle style
  2937. let cb = document.createElement('input');
  2938. cb.type = 'checkbox';
  2939. cb.setAttribute('cbType', CBTYPE);
  2940. cb.name = cbName;
  2941. cb.value = cbName;
  2942. cb.checked = VARS.Options[cbName];
  2943. let label = document.createElement('label');
  2944. if (cbReadOnly) {
  2945. cb.checked = true;
  2946. cb.disabled = true;
  2947. label.setAttribute('disabled', 'disabled');
  2948. }
  2949. label.appendChild(cb);
  2950. if (KeyWords[cbName]) {
  2951. if (Array.isArray(KeyWords[cbName][VARS.language]) === false) {
  2952. label.appendChild(document.createTextNode(KeyWords[cbName][VARS.language]));
  2953. }
  2954. else {
  2955. label.appendChild(document.createTextNode(Array.from(KeyWords[cbName][VARS.language]).join(', ')));
  2956. }
  2957. }
  2958. else if (['NF_SPONSORED', 'GF_SPONSORED', 'VF_SPONSORED', 'MP_SPONSORED'].indexOf(cbName) >= 0) {
  2959. label.appendChild(document.createTextNode(KeyWords.SPONSORED[VARS.language]));
  2960. }
  2961. else {
  2962. label.appendChild(document.createTextNode(cbName));
  2963. }
  2964. let div = document.createElement('div');
  2965. div.appendChild(label);
  2966. return div;
  2967. }
  2968.  
  2969. function createMultipeCBs(cbName, cbReadOnlyIdx = -1) {
  2970. // -- create multiple values checkboxes
  2971. const CBTYPE = 'M'; // checkbox, Multiple values
  2972. let arrElements = [];
  2973. for (let i = 0; i < KeyWords[cbName][VARS.language].length; i++) {
  2974. let div = document.createElement('div');
  2975. let cbKeyWord = KeyWords[cbName][VARS.language][i];
  2976. let cb = document.createElement('input');
  2977. cb.type = 'checkbox';
  2978. cb.setAttribute('cbType', CBTYPE);
  2979. cb.name = cbName;
  2980. cb.value = i;
  2981. cb.checked = VARS.Options[cbName][i] === '1';
  2982. let label = document.createElement('label');
  2983. if (i === cbReadOnlyIdx) {
  2984. cb.checked = true;
  2985. cb.disabled = true;
  2986. label.setAttribute('disabled', 'disabled');
  2987. }
  2988. label.appendChild(cb);
  2989. label.appendChild(document.createTextNode(cbKeyWord));
  2990. div.appendChild(label);
  2991. arrElements.push(div);
  2992. };
  2993. let br = document.createElement('br');
  2994. arrElements.push(br);
  2995. return arrElements;
  2996. }
  2997.  
  2998. function createRB(rbName, rbValue, rbLabelText) {
  2999. let div = document.createElement('div');
  3000. let rb = document.createElement('input');
  3001. rb.type = 'radio';
  3002. rb.name = rbName;
  3003. rb.value = rbValue;
  3004. rb.checked = (VARS.Options[rbName] === rbValue);
  3005. let label = document.createElement('label');
  3006. label.appendChild(rb);
  3007. label.appendChild(document.createTextNode(rbLabelText));
  3008. div.appendChild(label);
  3009. return div;
  3010. }
  3011.  
  3012. function createInput(iName, iLabel) {
  3013. let div = document.createElement('div');
  3014. let input = document.createElement('input');
  3015. input.type = 'text';
  3016. input.name = iName;
  3017. input.value = VARS.Options[iName];
  3018. let label = document.createElement('label');
  3019. label.appendChild(document.createTextNode(iLabel));
  3020. label.appendChild(document.createElement('br'));
  3021. label.appendChild(input);
  3022. div.appendChild(label);
  3023. return div;
  3024. }
  3025.  
  3026. function createCheckboxAndInput(cbName, iName) {
  3027. // -- checkbox with input box.
  3028. // -- no read-only attributes
  3029. // -- no multiple keyword values.
  3030.  
  3031. // -- create checkbox first.
  3032. const CBTYPE = 'T';
  3033. let cb = document.createElement('input');
  3034. cb.type = 'checkbox';
  3035. cb.setAttribute('cbType', CBTYPE);
  3036. cb.name = cbName;
  3037. cb.value = cbName;
  3038. cb.checked = VARS.Options[cbName];
  3039.  
  3040. // -- create input box
  3041. let input = document.createElement('input');
  3042. input.type = 'text';
  3043. input.name = iName;
  3044. input.value = VARS.Options[iName];
  3045. input.placeholder = '1000';
  3046. input.size = 6;
  3047. input.addEventListener('input', checkInputNumber, false);
  3048.  
  3049. // -- wrap checkbox and input inside a label
  3050. let label = document.createElement('label');
  3051. label.appendChild(cb);
  3052. label.appendChild(document.createTextNode(KeyWords[cbName][VARS.language] + ': '));
  3053. label.appendChild(input);
  3054.  
  3055. // -- wrap inside a div container ..
  3056. let div = document.createElement('div');
  3057. div.appendChild(label);
  3058. return div;
  3059. }
  3060.  
  3061. function checkInputNumber(event) {
  3062. // -- accept numbers/digits only.
  3063. const el = event.target;
  3064. if (el.value === '') {
  3065. return true;
  3066. }
  3067. const digitsValues = el.value.replace(/\D/g, '');
  3068. el.value = digitsValues.length > 0 ? parseInt(digitsValues) : '';
  3069. }
  3070.  
  3071. function createDialog() {
  3072. let dlg, hdr, hdr1, hdr2, hdr3, htxt, stxt, btn, cnt, fs, l, s, ta, div, footer;
  3073.  
  3074. // -- wrapper
  3075. dlg = document.createElement('div');
  3076. dlg.id = 'fbcmf';
  3077. dlg.className = 'fb-cmf';
  3078. // class "show" reveals the dialog.
  3079. // -- header (logo + title + close button)
  3080. hdr = document.createElement('header');
  3081. hdr1 = document.createElement('div');
  3082. hdr1.className = 'fb-cmf-icon';
  3083. hdr1.innerHTML = VARS.logoHTML;
  3084.  
  3085. hdr2 = document.createElement('div');
  3086. hdr2.className = 'fb-cmf-title';
  3087. htxt = document.createElement('div');
  3088. htxt.textContent = KeyWords.DLG_TITLE['en'];
  3089. s = document.createElement('small');
  3090. s.className = 'script-version';
  3091. s.appendChild(document.createTextNode(` (${SCRIPT_VERSION})`));
  3092. htxt.appendChild(s);
  3093. hdr2.appendChild(htxt);
  3094. if (VARS.language !== 'en') {
  3095. stxt = document.createElement('small');
  3096. stxt.textContent = `(${KeyWords.DLG_TITLE[VARS.language]})`;
  3097. hdr2.appendChild(stxt);
  3098. hdr2.classList.add('fb-cmf-lang-2');
  3099. }
  3100. else {
  3101. hdr2.classList.add('fb-cmf-lang-1')
  3102. }
  3103. hdr3 = document.createElement('div');
  3104. hdr3.className = 'fb-cmf-close';
  3105. btn = document.createElement('button');
  3106. btn.textContent = 'X';
  3107. btn.addEventListener('click', toggleDialog, false);
  3108. hdr3.appendChild(btn);
  3109.  
  3110. hdr.appendChild(hdr1);
  3111. hdr.appendChild(hdr2);
  3112. hdr.appendChild(hdr3);
  3113. dlg.appendChild(hdr);
  3114.  
  3115. // content container
  3116. cnt = document.createElement('div');
  3117. cnt.classList.add('content');
  3118.  
  3119. // -- News Feed options
  3120. fs = document.createElement('fieldset');
  3121. l = document.createElement('legend');
  3122. l.textContent = KeyWords.DLG_NF[VARS.language];
  3123. fs.appendChild(l);
  3124. fs.appendChild(createSingleCB('NF_SPONSORED', false)); // -- changed to false (Dec 2023)
  3125. for (const key in KeyWords) {
  3126. if (key.slice(0, 3) === 'NF_') {
  3127. if (key.slice(0, 8) === 'NF_BLOCK') {
  3128. continue;
  3129. }
  3130. if (key.slice(0, 8) === 'NF_LIKES') {
  3131. if (key === 'NF_LIKES_MAXIMUM') {
  3132. fs.appendChild(createCheckboxAndInput(key, 'NF_LIKES_MAXIMUM_COUNT'));
  3133. }
  3134. }
  3135. else {
  3136. fs.appendChild(createSingleCB(key));
  3137. }
  3138. }
  3139. }
  3140.  
  3141. // -- Keywords to block - News Feed
  3142. fs.appendChild(document.createElement('br'));
  3143. l = document.createElement('strong');
  3144. l.textContent = KeyWords.DLG_BLOCK_TEXT_FILTER_TITLE[VARS.language] + ":";
  3145. fs.appendChild(l);
  3146.  
  3147. createMultipeCBs('NF_BLOCKED_FEED', 0).forEach(el => {
  3148. fs.appendChild(el);
  3149. });
  3150.  
  3151. fs.appendChild(createSingleCB('NF_BLOCKED_ENABLED'));
  3152. s = document.createElement('small');
  3153. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE[VARS.language]));
  3154. fs.appendChild(s);
  3155. ta = document.createElement('textarea');
  3156. ta.name = 'NF_BLOCKED_TEXT';
  3157. ta.textContent = VARS.Options.NF_BLOCKED_TEXT.split(VARS.SEP).join('\n');
  3158. fs.appendChild(ta);
  3159. cnt.appendChild(fs);
  3160.  
  3161. // -- Groups Feed options
  3162. fs = document.createElement('fieldset');
  3163. l = document.createElement('legend');
  3164. l.textContent = KeyWords.DLG_GF[VARS.language];
  3165. fs.appendChild(l);
  3166. fs.appendChild(createSingleCB('GF_SPONSORED', false)); // -- changed to false (Dec 2023)
  3167. for (const key in KeyWords) {
  3168. if (key.slice(0, 3) === 'GF_' && key.slice(0, 8) !== 'GF_BLOCK') {
  3169. fs.appendChild(createSingleCB(key));
  3170. }
  3171. }
  3172.  
  3173. // -- Keywords to block - Groups Feed
  3174. fs.appendChild(document.createElement('br'));
  3175. l = document.createElement('strong');
  3176. l.textContent = KeyWords.DLG_BLOCK_TEXT_FILTER_TITLE[VARS.language] + ':';
  3177. fs.appendChild(l);
  3178.  
  3179. createMultipeCBs('GF_BLOCKED_FEED', 1).forEach(el => {
  3180. fs.appendChild(el);
  3181. });
  3182.  
  3183. fs.appendChild(createSingleCB('GF_BLOCKED_ENABLED'));
  3184. s = document.createElement('small');
  3185. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE[VARS.language]));
  3186. fs.appendChild(s);
  3187. ta = document.createElement('textarea');
  3188. ta.name = 'GF_BLOCKED_TEXT';
  3189. ta.textContent = VARS.Options.GF_BLOCKED_TEXT.split(VARS.SEP).join('\n');
  3190. fs.appendChild(ta);
  3191. cnt.appendChild(fs);
  3192.  
  3193. // -- Watch Videos Feed options
  3194. fs = document.createElement('fieldset');
  3195. l = document.createElement('legend');
  3196. l.textContent = KeyWords.DLG_VF[VARS.language];
  3197. fs.appendChild(l);
  3198. fs.appendChild(createSingleCB('VF_SPONSORED', false)); // -- changed to false (Dec 2023)
  3199. for (const key in KeyWords) {
  3200. if (key.slice(0, 3) === 'VF_' && key.slice(0, 8) !== 'VF_BLOCK') {
  3201. fs.appendChild(createSingleCB(key));
  3202. }
  3203. }
  3204.  
  3205. // -- Keywords to block - Watch Videos Feed
  3206. fs.appendChild(document.createElement('br'));
  3207. l = document.createElement('strong');
  3208. l.textContent = KeyWords.DLG_BLOCK_TEXT_FILTER_TITLE[VARS.language] + ':';
  3209. fs.appendChild(l);
  3210.  
  3211. createMultipeCBs('VF_BLOCKED_FEED', 2).forEach(el => {
  3212. fs.appendChild(el);
  3213. });
  3214.  
  3215. fs.appendChild(createSingleCB('VF_BLOCKED_ENABLED'));
  3216. s = document.createElement('small');
  3217. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE[VARS.language]));
  3218. fs.appendChild(s);
  3219. ta = document.createElement('textarea');
  3220. ta.name = 'VF_BLOCKED_TEXT';
  3221. ta.textContent = VARS.Options.VF_BLOCKED_TEXT.split(VARS.SEP).join('\n');
  3222. fs.appendChild(ta);
  3223. cnt.appendChild(fs);
  3224.  
  3225. // -- MarketPlace option(s)
  3226. fs = document.createElement('fieldset');
  3227. l = document.createElement('legend');
  3228. l.textContent = KeyWords.DLG_MP[VARS.language];
  3229. fs.appendChild(l);
  3230. fs.appendChild(createSingleCB('MP_SPONSORED', false)); // -- changed to false (Dec 2023)
  3231.  
  3232. // -- Keywords to block - Marketplace Feed
  3233. fs.appendChild(document.createElement('br'));
  3234. l = document.createElement('strong');
  3235. l.textContent = KeyWords.DLG_BLOCK_TEXT_FILTER_TITLE[VARS.language] + ':';
  3236. fs.appendChild(l);
  3237.  
  3238. createMultipeCBs('MP_BLOCKED_FEED', 0).forEach(el => {
  3239. fs.appendChild(el);
  3240. });
  3241. // -- 2 x textarea boxes; one for prices and one for description
  3242. fs.appendChild(createSingleCB('MP_BLOCKED_ENABLED'));
  3243. l = document.createElement('strong');
  3244. l.textContent = 'Prices: ';
  3245. fs.appendChild(l);
  3246. fs.appendChild(document.createElement('br'));
  3247. s = document.createElement('small');
  3248. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE[VARS.language]));
  3249. fs.appendChild(s);
  3250. ta = document.createElement('textarea');
  3251. ta.name = 'MP_BLOCKED_TEXT';
  3252. ta.textContent = VARS.Options.MP_BLOCKED_TEXT.split(VARS.SEP).join('\n');
  3253. fs.appendChild(ta);
  3254. fs.appendChild(document.createElement('br'));
  3255. fs.appendChild(document.createElement('br'));
  3256.  
  3257. l = document.createElement('strong');
  3258. l.textContent = 'Description: ';
  3259. fs.appendChild(l);
  3260. fs.appendChild(document.createElement('br'));
  3261. s = document.createElement('small');
  3262. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE[VARS.language]));
  3263. fs.appendChild(s);
  3264. ta = document.createElement('textarea');
  3265. ta.name = 'MP_BLOCKED_TEXT_DESCRIPTION';
  3266. ta.textContent = VARS.Options.MP_BLOCKED_TEXT_DESCRIPTION.split(VARS.SEP).join('\n');
  3267. fs.appendChild(ta);
  3268.  
  3269. cnt.appendChild(fs);
  3270.  
  3271. // -- Other items options
  3272. fs = document.createElement('fieldset');
  3273. l = document.createElement('legend');
  3274. l.textContent = KeyWords.DLG_OTHER[VARS.language];
  3275. fs.appendChild(l);
  3276. for (const key in KeyWords) {
  3277. if (key.slice(0, 10) === 'OTHER_INFO') {
  3278. fs.appendChild(createSingleCB(key));
  3279. }
  3280. }
  3281. cnt.appendChild(fs);
  3282.  
  3283. // -- Reels
  3284. fs = document.createElement('fieldset');
  3285. l = document.createElement('legend');
  3286. l.textContent = KeyWords.REELS_TITLE[VARS.language];
  3287. fs.appendChild(l);
  3288. fs.appendChild(createSingleCB('REELS_CONTROLS'), false);
  3289. fs.appendChild(l);
  3290. fs.appendChild(createSingleCB('REELS_DISABLE_LOOPING'), false);
  3291. cnt.appendChild(fs);
  3292.  
  3293. // -- Verbosity
  3294. fs = document.createElement('fieldset');
  3295. l = document.createElement('legend');
  3296. l.textContent = KeyWords.DLG_VERBOSITY[VARS.language];
  3297. fs.appendChild(l);
  3298. s = document.createElement('span');
  3299. s.appendChild(document.createTextNode(`${KeyWords.DLG_VERBOSITY_MESSAGE[VARS.language]}:`));
  3300. fs.appendChild(s);
  3301. fs.appendChild(createRB('VERBOSITY_LEVEL', '0', `<${KeyWords.VERBOSITY_NO_MESSAGE[VARS.language]}>`));
  3302. fs.appendChild(createRB('VERBOSITY_LEVEL', '1', `${KeyWords.VERBOSITY_MESSAGE[VARS.language][0]}______`));
  3303. fs.appendChild(createRB('VERBOSITY_LEVEL', '2', `7${KeyWords.VERBOSITY_MESSAGE[VARS.language][1]}`));
  3304. fs.appendChild(document.createElement('br'));
  3305. fs.appendChild(createInput('VERBOSITY_MESSAGE_COLOUR', `${KeyWords.VERBOSITY_MESSAGE_COLOUR[VARS.language]}:`));
  3306. fs.appendChild(createInput('VERBOSITY_MESSAGE_BG_COLOUR', `${KeyWords.VERBOSITY_MESSAGE_BG_COLOUR[VARS.language]}:`));
  3307. fs.appendChild(document.createElement('br'));
  3308. fs.appendChild(createSingleCB('VERBOSITY_DEBUG'));
  3309. cnt.appendChild(fs);
  3310.  
  3311. // -- cmf customisations
  3312. fs = document.createElement('fieldset');
  3313. l = document.createElement('legend');
  3314. l.textContent = KeyWords.CMF_CUSTOMISATIONS[VARS.language];
  3315. fs.appendChild(l);
  3316. fs.appendChild(document.createTextNode(`${KeyWords.CMF_BTN_LOCATION[VARS.language]}:`));
  3317. let len = KeyWords.CMF_BTN_OPTION[VARS.language].length;
  3318. for (let i = 0; i < len; i++) {
  3319. fs.appendChild(createRB('CMF_BTN_OPTION', i.toString(), KeyWords.CMF_BTN_OPTION[VARS.language][i]));
  3320. };
  3321. fs.appendChild(document.createElement('br'));
  3322. fs.appendChild(document.createTextNode(`${KeyWords.CMF_DIALOG_LOCATION[VARS.language]}:`));
  3323. fs.appendChild(createRB('CMF_DIALOG_OPTION', '0', KeyWords.CMF_DIALOG_OPTION[VARS.language][0]));
  3324. fs.appendChild(createRB('CMF_DIALOG_OPTION', '1', KeyWords.CMF_DIALOG_OPTION[VARS.language][1]));
  3325. fs.appendChild(document.createElement('br'));
  3326. fs.appendChild(createInput('CMF_BORDER_COLOUR', `${KeyWords.CMF_BORDER_COLOUR[VARS.language]}:`));
  3327. cnt.appendChild(fs);
  3328.  
  3329. // -- tips
  3330. fs = document.createElement('fieldset');
  3331. l = document.createElement('legend');
  3332. l.textContent = KeyWords.DLG_TIPS[VARS.language];
  3333. fs.appendChild(l);
  3334. s = document.createElement('span');
  3335. s.appendChild(document.createTextNode(KeyWords.DLG_TIPS_CONTENT[VARS.language]));
  3336. fs.appendChild(s);
  3337. cnt.appendChild(fs);
  3338.  
  3339. dlg.appendChild(cnt);
  3340.  
  3341. // -- Actions (buttons) + status
  3342. footer = document.createElement('footer');
  3343. // footer.classList.add('buttons');
  3344. btn = document.createElement('button');
  3345. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][0]; // save
  3346. btn.addEventListener('click', saveUserOptions, false);
  3347. footer.appendChild(btn);
  3348. btn = document.createElement('button');
  3349. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][1]; // close
  3350. btn.addEventListener('click', toggleDialog, false);
  3351. footer.appendChild(btn);
  3352. btn = document.createElement('button');
  3353. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][2]; // export
  3354. btn.addEventListener('click', exportUserOptions, false);
  3355. footer.appendChild(btn);
  3356. btn = document.createElement('button');
  3357. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][3]; // import
  3358. btn.setAttribute('id', 'BTNImport');
  3359. footer.appendChild(btn);
  3360. btn = document.createElement('button');
  3361. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][4]; // reset
  3362. btn.addEventListener('click', resetUserOptions, false);
  3363. footer.appendChild(btn);
  3364. // -- file input field is hidden, but triggered by the Import button.
  3365. let fileImport = document.createElement('input');
  3366. fileImport.setAttribute('type', 'file');
  3367. fileImport.setAttribute('id', `FI${postAtt}`);
  3368. fileImport.classList.add('fileInput');
  3369. footer.appendChild(fileImport);
  3370. // -- save/export/import/reset status/results
  3371. div = document.createElement('div');
  3372. div.classList.add('fileResults');
  3373. footer.appendChild(div);
  3374.  
  3375. dlg.appendChild(footer);
  3376.  
  3377. document.body.appendChild(dlg);
  3378.  
  3379. // -- add event listeners to the import button and file input field
  3380. let fileInput = document.getElementById(`FI${postAtt}`);
  3381. fileInput.addEventListener('change', importUserOptions, false);
  3382. // -- make the btn Import trigger file input ...
  3383. let btnImport = document.getElementById('BTNImport');
  3384. btnImport.addEventListener('click', function () {
  3385. fileInput.click()
  3386. }, false);
  3387. }
  3388.  
  3389. function updateDialog() {
  3390. let content = document.getElementById('fbcmf').querySelector('.content');
  3391. if (content) {
  3392. // -- toggle checkboxes
  3393. let cbs = Array.from(content.querySelectorAll('input[type="checkbox"][cbtype="T"]'));
  3394. cbs.forEach(cb => {
  3395. if (VARS.Options.hasOwnProperty(cb.name)) {
  3396. cb.checked = VARS.Options[cb.name];
  3397. }
  3398. });
  3399. // -- multiple values checkboxes
  3400. cbs = Array.from(content.querySelectorAll('input[type="checkbox"][cbtype="M"]'));
  3401. cbs.forEach(cb => {
  3402. if (VARS.Options.hasOwnProperty(cb.name)) {
  3403. cb.checked = VARS.Options[cb.name][parseInt(cb.value)] === '1';
  3404. }
  3405. });
  3406. // -- radios
  3407. let rbs = content.querySelectorAll('input[type="radio"]');
  3408. rbs.forEach(rb => {
  3409. if (VARS.Options.hasOwnProperty(rb.name) && (rb.value === VARS.Options[rb.name])) {
  3410. rb.checked = VARS.Options[rb.name];
  3411. }
  3412. });
  3413. // -- textareas
  3414. let tas = Array.from(content.querySelectorAll('textarea'));
  3415. tas.forEach(ta => {
  3416. if (VARS.Options.hasOwnProperty(ta.name)) {
  3417. ta.value = VARS.Options[ta.name].replaceAll(VARS.SEP, '\n');
  3418. }
  3419. });
  3420. // -- plain inputs
  3421. let inputs = Array.from(content.querySelectorAll('input[type="text"]'));
  3422. inputs.forEach(inp => {
  3423. if (VARS.Options.hasOwnProperty(inp.name)) {
  3424. inp.value = VARS.Options[inp.name];
  3425. }
  3426. });
  3427. }
  3428. }
  3429.  
  3430. async function saveUserOptions(event, source = 'dialog') {
  3431. // -- save Options in indexeddb as JSON.
  3432. if (source === 'dialog') {
  3433. let md, cbs, rbs, tas, inputs;
  3434.  
  3435. // -- grab the dialog box and get the various options.
  3436. md = document.getElementById('fbcmf');
  3437.  
  3438. // -- input validation for NF_LIKES_MAXIMUM_COUNT
  3439. const elLikesMaximum = md.querySelector('input[name="NF_LIKES_MAXIMUM"]');
  3440. if (elLikesMaximum.checked) {
  3441. const elLikesMaximumCount = md.querySelector('input[name="NF_LIKES_MAXIMUM_COUNT"]');
  3442. if (elLikesMaximumCount.value.length === 0) {
  3443. alert(KeyWords.NF_LIKES_MAXIMUM[VARS.language] + '?');
  3444. elLikesMaximumCount.focus();
  3445. return;
  3446. }
  3447. }
  3448.  
  3449. // -- checkboxes (toggle variations)
  3450. cbs = Array.from(md.querySelectorAll('input[type="checkbox"][cbtype="T"]'));
  3451. cbs.forEach(cb => {
  3452. VARS.Options[cb.name] = cb.checked;
  3453. });
  3454. // -- checkboxes (multipe values variations)
  3455. let cbName = 'NF_BLOCKED_FEED';
  3456. cbs = Array.from(md.querySelectorAll(`input[type="checkbox"][name="${cbName}"]`));
  3457. cbs.forEach(cb => {
  3458. VARS.Options[cbName][parseInt(cb.value)] = (cb.checked) ? '1' : '0';
  3459. });
  3460. cbName = 'GF_BLOCKED_FEED';
  3461. cbs = Array.from(md.querySelectorAll(`input[type="checkbox"][name="${cbName}"]`));
  3462. cbs.forEach(cb => {
  3463. VARS.Options[cbName][parseInt(cb.value)] = (cb.checked) ? '1' : '0';
  3464. });
  3465. cbName = 'VF_BLOCKED_FEED';
  3466. cbs = Array.from(md.querySelectorAll(`input[type="checkbox"][name="${cbName}"]`));
  3467. cbs.forEach(cb => {
  3468. VARS.Options[cbName][parseInt(cb.value)] = (cb.checked) ? '1' : '0';
  3469. });
  3470. cbName = 'MP_BLOCKED_FEED';
  3471. cbs = Array.from(md.querySelectorAll(`input[type="checkbox"][name="${cbName}"]`));
  3472. cbs.forEach(cb => {
  3473. VARS.Options[cbName][parseInt(cb.value)] = (cb.checked) ? '1' : '0';
  3474. });
  3475.  
  3476. // -- radios
  3477. rbs = md.querySelectorAll('input[type="radio"]:checked');
  3478. rbs.forEach(rb => {
  3479. VARS.Options[rb.name] = rb.value;
  3480. });
  3481. // -- text input
  3482. inputs = Array.from(md.querySelectorAll('input[type="text"]'));
  3483. inputs.forEach(inp => {
  3484. VARS.Options[inp.name] = inp.value;
  3485. });
  3486. // -- Blocked text (textareas)
  3487. tas = md.querySelectorAll('textarea');
  3488. tas.forEach(ta => {
  3489. let txtn = ta.value.split('\n');
  3490. let txts = [];
  3491. txtn.forEach(txt => {
  3492. if (txt.trim().length > 0) {
  3493. txts.push(txt); // -- do not trim - retain entry as is.
  3494. }
  3495. });
  3496. VARS.Options[ta.name] = txts.join(VARS.SEP);
  3497. });
  3498. }
  3499.  
  3500. // -- clear out items that are not valid.
  3501. let md = document.getElementById('fbcmf');
  3502. let inputs = Array.from(md.querySelectorAll('input:not([type="file"]), textarea'));
  3503. let validNames = [];
  3504. inputs.forEach(inp => {
  3505. if (validNames.indexOf(inp.name) < 0) {
  3506. validNames.push(inp.name);
  3507. }
  3508. });
  3509. for (let key in VARS.Options) {
  3510. if (validNames.indexOf(key) < 0) {
  3511. if (VARS.Options.VERBOSITY_DEBUG) {
  3512. console.info(log + 'saveUserOptions(); Deleting key:', key);
  3513. }
  3514. delete VARS.Options[key];
  3515. }
  3516. }
  3517.  
  3518. // -- save options
  3519. let result = await set(DBVARS.DBKey, JSON.stringify(VARS.Options), DBVARS.ostore).then(() => {
  3520. // -- refresh options and split blocks of texts
  3521. let result2 = getUserOptions().then(() => {
  3522. return true;
  3523. });
  3524. return result2;
  3525. }).catch((err) => {
  3526. console.info(`${log}saveUserOptions() > set() -> Error:`, err);
  3527. return false;
  3528. });
  3529. if (VARS.Options.VERBOSITY_DEBUG) {
  3530. console.info(`${log}saveUserOptions() > set() -> Saved:`, result);
  3531. }
  3532. // - update some variables.
  3533. if (result) {
  3534. setFeedSettings(true);
  3535. // - rebuild css - need user's preferences to take effect
  3536. addCSS();
  3537. addExtraCSS();
  3538. // - check if toggling debugging mode.
  3539. toggleHiddenElements();
  3540. }
  3541. document.querySelector('#fbcmf .fileResults').textContent = `Last Saved @ ${(new Date).toTimeString().slice(0, 8)}`;
  3542.  
  3543. // -- reset the posts and do the cleaning/mopping up again ...
  3544. if (VARS.isAF) {
  3545. // -- "reset" scan counts
  3546. VARS.scanCountStart += 100;
  3547. VARS.scanCountMaxLoop += 100;
  3548. // -- "purge" toggle post state bar + post-tab
  3549. let elements = document.querySelectorAll(`[${postAttTab}]`);
  3550. for (const element of elements) {
  3551. const elParent = element.parentElement;
  3552. elParent.removeChild(element);
  3553. }
  3554. // -- remove attribute
  3555. elements = document.querySelectorAll(`[${postAtt}]`);
  3556. for (const element of elements) {
  3557. element.removeAttribute(postAtt);
  3558. element.removeAttribute(VARS.cssHide);
  3559. element.removeAttribute(VARS.cssHideEl);
  3560. element.removeAttribute(VARS.cssHideNumberOfShares);
  3561. element.removeAttribute(VARS.cssShow);
  3562.  
  3563. // -- February 2024 - hot fix !!! - remove the collapse post completely hack
  3564. const elWithMargin = climbUpTheTree(element, 4);
  3565. if (elWithMargin && elWithMargin.hasAttribute('style')) {
  3566. elWithMargin.removeAttribute('style');
  3567. }
  3568. }
  3569. // -- remove other attributes
  3570. elements = document.querySelectorAll(`[${postAttCPID}], [${postAttChildFlag}]`);
  3571. for (const element of elements) {
  3572. if (element.hasAttribute(postAttCPID)) {
  3573. element.removeAttribute(postAttCPID);
  3574. }
  3575. if (element.hasAttribute(postAttChildFlag)) {
  3576. element.removeAttribute(postAttChildFlag);
  3577. }
  3578. }
  3579. // -- remove some more attributes
  3580. // -- (don't add cssShow to query, the button needs it ...)
  3581. elements = document.querySelectorAll(`[${VARS.cssHide}], [${VARS.cssHideEl}], [${VARS.cssHideNumberOfShares}]`);
  3582. for (const element of elements) {
  3583. element.removeAttribute(VARS.cssHide);
  3584. element.removeAttribute(VARS.cssHideEl);
  3585. element.removeAttribute(VARS.cssHideNumberOfShares);
  3586. element.removeAttribute(VARS.cssShow);
  3587. }
  3588.  
  3589. if (VARS.isNF) {
  3590. mopUpTheNewsFeed();
  3591. }
  3592. else if (VARS.isGF) {
  3593. mopUpTheGroupsFeed();
  3594. }
  3595. else if (VARS.isVF) {
  3596. mopUpTheWatchVideosFeed();
  3597. }
  3598. else if (VARS.isMF) {
  3599. mopUpTheMarketplaceFeed();
  3600. }
  3601. else if (VARS.isSF) {
  3602. mopUpTheSearchFeed();
  3603. }
  3604. else if (VARS.isRF) {
  3605. mopUpTheReelFeed('saveUserOptions');
  3606. }
  3607. }
  3608. // console.info(log + 'saveUserOptions(); OPTIONS:', VARS.Options);
  3609. }
  3610.  
  3611. function exportUserOptions() {
  3612. // -- export user's options into a text file.
  3613. let exportOptions = document.createElement("a");
  3614. exportOptions.href = window.URL.createObjectURL(new Blob([JSON.stringify(VARS.Options)], { type: "text/plain" }));
  3615. exportOptions.download = 'fb - clean my feeds - settings.json';
  3616. exportOptions.click();
  3617. exportOptions.remove();
  3618. document.querySelector('#fbcmf .fileResults').textContent = 'Exported: fb - clean my feeds - settings.json';
  3619. }
  3620.  
  3621. function importUserOptions(event) {
  3622. // -- import user's options from a text file.
  3623. let fileResults = document.querySelector('#fbcmf .fileResults');
  3624. let file = event.target.files[0];
  3625. let fileN = event.target.files[0].name;
  3626. // -- setup reader for reading in the file
  3627. let reader = new FileReader();
  3628. // -- what to do when reader is called.
  3629. reader.onload = (file) => {
  3630. try {
  3631. let fileContent = JSON.parse(file.target.result);
  3632. if (
  3633. fileContent.hasOwnProperty('NF_SPONSORED') &&
  3634. fileContent.hasOwnProperty('GF_SPONSORED') &&
  3635. fileContent.hasOwnProperty('VF_SPONSORED') &&
  3636. fileContent.hasOwnProperty('MP_SPONSORED')
  3637. ) {
  3638. VARS.Options = fileContent;
  3639. // console.info(log + 'importUserOptions() > reader.onload: Options:', VARS.Options);
  3640. // -- save the file to the db
  3641. // -- save will run getUserOptions();
  3642. let result = saveUserOptions(null, 'file').then(() => {
  3643. updateDialog();
  3644. fileResults.textContent = `File imported: ${fileN}`;
  3645. return true;
  3646. });
  3647. }
  3648. else {
  3649. fileResults.textContent = `File NOT imported: ${fileN}`;
  3650. }
  3651. }
  3652. catch (e) {
  3653. fileResults.textContent = `File NOT imported: ${fileN}`;
  3654. }
  3655. }
  3656. // -- call reader to read in the file ...
  3657. reader.readAsText(file);
  3658. }
  3659.  
  3660. function resetUserOptions() {
  3661. // -- reset the options to original state (before customisations)
  3662. del(DBVARS.DBKey, DBVARS.ostore)
  3663. .then(() => {
  3664. // console.info(log + 'resetUserOptions();', 'Data deleted successfully');
  3665. setLanguageAndOptions();
  3666. let result = saveUserOptions(null, 'reset').then(() => {
  3667. updateDialog();
  3668. return true;
  3669. });
  3670. })
  3671. .catch((error) => {
  3672. console.info(log + 'resetUserOptions(); Error - unable to delete Data.', error);
  3673. });
  3674. }
  3675.  
  3676. function createToggleButton() {
  3677. let btn = document.createElement('button');
  3678. btn.innerHTML = VARS.logoHTML;
  3679. btn.id = 'fbcmfToggle';
  3680. btn.title = KeyWords.DLG_TITLE[VARS.language];
  3681. btn.className = 'fb-cmf-toggle fb-cmf-icon';
  3682. document.body.appendChild(btn);
  3683. btn.addEventListener('click', toggleDialog, false);
  3684. VARS.btnToggleEl = btn;
  3685. }
  3686.  
  3687. createToggleButton();
  3688. createDialog();
  3689. }
  3690. // --- end of dailog code.
  3691.  
  3692. // -- toggleDialog() function placed here to allow a GM.registerMenuCommand(...) to call it.
  3693. function toggleDialog() {
  3694. const elDialog = document.getElementById('fbcmf');
  3695. if (elDialog.hasAttribute(VARS.cssShow)) {
  3696. elDialog.removeAttribute(VARS.cssShow);
  3697. }
  3698. else {
  3699. elDialog.setAttribute(VARS.cssShow, '');
  3700. }
  3701. }
  3702.  
  3703. // adjust some settings - if URL has changed.
  3704. function setFeedSettings(forceUpdate = false) {
  3705. if ((VARS.prevURL !== window.location.href) || forceUpdate) {
  3706. // - remember current page's URL
  3707. VARS.prevURL = window.location.href;
  3708. // -- pathname pattern: /marketplace...
  3709. VARS.prevPathname = window.location.pathname;
  3710. // -- search pattern: ?query= ...
  3711. VARS.prevQuery = window.location.search;
  3712. // - reset feeds flags
  3713. VARS.isNF = false;
  3714. VARS.isGF = false;
  3715. VARS.isVF = false;
  3716. VARS.isMF = false;
  3717. VARS.isSF = false;
  3718. VARS.isRF = false;
  3719. if ((VARS.prevPathname === '/') || (VARS.prevPathname === '/home.php')) {
  3720. // -- news feed
  3721. // -- nb: "Feeds (most recent)" combines a few feeds into one ... apply NF rules to all, except Groups.
  3722. if (VARS.prevQuery.indexOf('?filter=groups') < 0) {
  3723. VARS.isNF = true;
  3724. }
  3725. else {
  3726. VARS.isGF = true;
  3727. VARS.gfType = 'groups-recent';
  3728. }
  3729. }
  3730. else if (VARS.prevPathname.indexOf('/groups/') >= 0) {
  3731. // -- groups feed
  3732. VARS.isGF = true;
  3733. if (VARS.prevPathname.indexOf('/groups/feed') >= 0) {
  3734. VARS.gfType = 'groups'
  3735. }
  3736. else if (VARS.prevPathname.indexOf('/groups/search') >= 0) {
  3737. VARS.gfType = 'search';
  3738. }
  3739. else if (VARS.prevPathname.indexOf('?filter=groups&sk=h_chr') >= 0) {
  3740. VARS.gfType = 'groups-recent';
  3741. }
  3742. else {
  3743. VARS.gfType = 'group';
  3744. }
  3745. }
  3746. else if (VARS.prevPathname.indexOf('/watch') >= 0) {
  3747. // -- watch videos feed
  3748. VARS.isVF = true;
  3749. if (VARS.prevPathname.indexOf('/watch/search') >= 0) {
  3750. VARS.vfType = 'search';
  3751. }
  3752. else if (VARS.prevQuery.indexOf('?ref=seach') >= 0) {
  3753. VARS.vfType = 'item';
  3754. }
  3755. else if (VARS.prevQuery.indexOf('?v=') >= 0) {
  3756. VARS.vfType = 'item';
  3757. }
  3758. else {
  3759. VARS.vfType = 'videos';
  3760. }
  3761. }
  3762. else if (VARS.prevPathname.indexOf('/marketplace') >= 0) {
  3763. // -- marketplace
  3764. VARS.isMF = true;
  3765. mp_stopTrackingDirtIntoMyHouse();
  3766. if (VARS.isMF && VARS.prevPathname.indexOf('/item/') >= 0) {
  3767. // - viewing an item
  3768. VARS.mpType = 'item';
  3769. }
  3770. else if (VARS.prevPathname.indexOf('/search') >= 0) {
  3771. // - searching within marketplace ... (has similar layout to category feed)
  3772. VARS.mpType = 'search';
  3773. }
  3774. else if (VARS.prevPathname.indexOf('/category/') >= 0) {
  3775. // - category feed
  3776. VARS.mpType = 'category';
  3777. }
  3778. else {
  3779. // -- check again for category - may have a location in the pathName ...
  3780. // -- pattern: :: https://www.facebook.com/marketplace/<location>/sports
  3781. const urlBits = VARS.prevPathname.split('/');
  3782. if (urlBits.length > 3) {
  3783. VARS.mpType = 'category';
  3784. }
  3785. else {
  3786. VARS.mpType = 'marketplace';
  3787. }
  3788. }
  3789. }
  3790. else if (VARS.prevPathname.indexOf('/commerce/listing/') >= 0) {
  3791. // - a group's for sale post - redirected to marketplace ...
  3792. // - same layout as a marketplace item.
  3793. VARS.isMF = true;
  3794. VARS.mpType = 'item';
  3795. }
  3796. else if (['/search/top/', '/search/top', '/search/posts/', '/search/posts', '/search/pages/'].indexOf(VARS.prevPathname) >= 0) {
  3797. // -- search results page : "All" and "Posts"
  3798. VARS.isSF = true;
  3799. }
  3800. else if (VARS.prevPathname.indexOf('/reel/') >= 0) {
  3801. // -- reel(s) page
  3802. // VARS.isRF = true;
  3803. VARS.isRF = (VARS.Options.REELS_CONTROLS === true) || (VARS.Options.REELS_DISABLE_LOOPING === true);
  3804. }
  3805.  
  3806. VARS.isAF = (VARS.isNF || VARS.isGF || VARS.isVF || VARS.isMF || VARS.isSF || VARS.isRF);
  3807.  
  3808. // when to display the cmf button
  3809. if (VARS.isAF) {
  3810. if (VARS.btnToggleEl) {
  3811. VARS.btnToggleEl.setAttribute(VARS.cssShow, '');
  3812. }
  3813. }
  3814. else {
  3815. if (VARS.btnToggleEl) {
  3816. VARS.btnToggleEl.removeAttribute(VARS.cssShow);
  3817. }
  3818. }
  3819.  
  3820. // - reset consecutive count of hidden posts
  3821. VARS.echoCount = 0;
  3822.  
  3823. // console.info(`${log}setFeedSettings() :: isAF: ${VARS.isAF}; isNF: ${VARS.isNF}; isGF: ${VARS.isGF}; isVF: ${VARS.isVF}; isMF: ${VARS.isMF}; isSF: ${VARS.isSF}; isRF: ${VARS.isRF};`);
  3824.  
  3825. return true;
  3826. }
  3827. else {
  3828. return false;
  3829. }
  3830. }
  3831.  
  3832. function climbUpTheTree(element, numberOfBranches = 1) {
  3833. while (element && numberOfBranches > 0) {
  3834. element = element.parentNode;
  3835. numberOfBranches--;
  3836. }
  3837. return element || null;
  3838. }
  3839.  
  3840. function doLightDusting(post) {
  3841. // - remove 'dusty' elements that interfere with querySelectorAll, nth-of-type, :not() queries.
  3842. // -- needs to run a few times to be effective.
  3843. let scanCount = VARS.scanCountStart;
  3844. if (post[postPropDS] !== undefined) {
  3845. scanCount = parseInt(post[postPropDS]);
  3846. scanCount = (scanCount < VARS.scanCountStart) ? VARS.scanCountStart : scanCount;
  3847. }
  3848. if (scanCount < VARS.scanCountMaxLoop) {
  3849. const dustySpots = post.querySelectorAll('[data-0="0"]');
  3850. if (dustySpots) {
  3851. dustySpots.forEach((element) => {
  3852. element.remove();
  3853. });
  3854. }
  3855. scanCount++;
  3856. post[postPropDS] = scanCount;
  3857. }
  3858. }
  3859.  
  3860. function scanTreeForText(theNode) {
  3861. const arrayTextValues = [];
  3862. const elements = theNode.querySelectorAll(':scope > div, :scope > blockquote, :scope > span');
  3863.  
  3864. for (const element of elements) {
  3865. if (element.hasAttribute('aria-hidden') && element.getAttribute('aria-hidden') === "false") {
  3866. // -- skip this branch (hidden)
  3867. continue;
  3868. }
  3869.  
  3870. // -- scan this branch
  3871. const walk = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null);
  3872. let currentNode;
  3873. while ((currentNode = walk.nextNode())) {
  3874. const elParent = currentNode.parentElement;
  3875. const elParentTN = elParent.tagName.toLowerCase();
  3876. const val = cleanText(currentNode.textContent).trim();
  3877.  
  3878. // console.info(log + '---> scanTreeForText(); currentNode:', currentNode, elParent, elParentTN, val);
  3879.  
  3880. if (val === '' || val.toLowerCase() === 'facebook') {
  3881. // -- skip this node
  3882. continue;
  3883. }
  3884.  
  3885. if (elParent.hasAttribute('aria-hidden') && elParent.getAttribute('aria-hidden') === 'true') {
  3886. // -- skip this node
  3887. continue;
  3888. }
  3889.  
  3890. // if (elParentTN === 'div' && elParent.hasAttribute('role') && elParent.getAttribute('role') === 'button') {
  3891. // // -- skip this node
  3892. // continue;
  3893. // }
  3894. if (elParentTN === 'div' && elParent.hasAttribute('role') && elParent.getAttribute('role') === 'button') {
  3895. // -- February 2024 - issue with "Anonymous participant" not being detected properly
  3896. // -- when viewing a post in a group (not groups feed), "Anonymous participant" is inside a div:button wrapped inside an object.
  3897. if (elParent.parentElement && elParent.parentElement.tagName.toLowerCase() !== 'object') {
  3898. // -- skip this node
  3899. continue;
  3900. }
  3901. }
  3902.  
  3903. if (elParentTN === 'title') {
  3904. // -- skip this node
  3905. continue;
  3906. }
  3907.  
  3908. // const elGeneric = elParent.closest('div[role="button"]');
  3909. // if (elGeneric === null && val.length > 1) {
  3910. // // - keep 2+ char strings.
  3911. // arrayTextValues.push(...val.split('\n'));
  3912. // }
  3913. // else {
  3914. // // -- skip this node (hidden / meta info)
  3915. // }
  3916.  
  3917. // -- February 2024 - issue with "Anonymous participant" not being detected properly
  3918. // --- usually has a div with role of button, and that div only has 1 descendant.
  3919. // --- previously, we skipped when a div has a button role.
  3920. const elGeneric = elParent.closest('div[role="button"]');
  3921. const elGenericDescendantsCount = elGeneric ? countDescendants(elGeneric) : 0;
  3922. // console.info(log + 'scanTreeForText(); final test:', elGeneric, elParent, currentNode, elGenericDescendantsCount, val);
  3923. if (elGenericDescendantsCount < 2 && val.length > 1) {
  3924. // - keep 2+ char strings.
  3925. arrayTextValues.push(...val.split('\n'));
  3926. }
  3927. else {
  3928. // -- skip this node (hidden / meta info)
  3929. }
  3930. }
  3931. }
  3932.  
  3933. // -- remove duplicates and return results.
  3934. // console.info(log + 'scanTreeForText(); returning::', theNode, arrayTextValues);
  3935. return [...new Set(arrayTextValues)];
  3936. }
  3937.  
  3938. function mp_scanTreeForText(theNode) {
  3939. const arrayTextValues = [];
  3940. let n;
  3941. const walk = document.createTreeWalker(theNode, NodeFilter.SHOW_TEXT, null);
  3942. while ((n = walk.nextNode())) {
  3943. let val = cleanText(n.textContent).trim();
  3944. if ((val !== '') && (val.length > 1) && (val.toLowerCase() !== 'facebook')) {
  3945. // - keep 2+ char strings.
  3946. // arrayTextValues.push(val);
  3947. arrayTextValues.push(val.toLowerCase());
  3948. }
  3949. }
  3950. return arrayTextValues;
  3951. }
  3952.  
  3953. function scanImagesForAltText(theNode) {
  3954. const arrayAltTextValues = [];
  3955. const images = theNode.querySelectorAll('img[alt]');
  3956. for (let i = 0; i < images.length; i++) {
  3957. const img = images[i];
  3958. if (img.alt.length > 0 && img.naturalWidth > 32) {
  3959. // -- (emojis usually have widths < 33 ... skip them)
  3960. const sAlt = cleanText(img.alt);
  3961. if (!arrayAltTextValues.includes(sAlt)) {
  3962. arrayAltTextValues.push(sAlt);
  3963. }
  3964. }
  3965. }
  3966. return arrayAltTextValues;
  3967. }
  3968.  
  3969. function countDescendants(element) {
  3970. return element.querySelectorAll('div, span').length;
  3971. }
  3972.  
  3973. function extractTextContent(post, selector, maxBlocks) {
  3974. // - get the text node values of the regular feed posts
  3975. // - get the alt-text values of any images in the feed posts
  3976. // -- scan the top portion of the posts (first maxBlocks blocks)
  3977. // -- parameters:
  3978. // post: post to scan
  3979. // selector: querySelector's query
  3980. // maxBlocks: max number of blocks to scan
  3981. const blocks = post.querySelectorAll(selector);
  3982. const arrayTextValues = [];
  3983.  
  3984. // - process first maxBlocks blocks
  3985. // - block 0 = Suggested headings, block 1 = title/heading, block 2 = content, block 3 = info box / comments, block 4 = comments
  3986. // - nb: some suggested posts only have one block ...
  3987. for (let b = 0; b < Math.min(maxBlocks, blocks.length); b++) {
  3988. const block = blocks[b];
  3989. if (countDescendants(block) > 0) {
  3990. arrayTextValues.push(...scanTreeForText(block));
  3991. arrayTextValues.push(...scanImagesForAltText(block));
  3992. }
  3993. }
  3994.  
  3995. return arrayTextValues.filter(item => item !== '');
  3996. }
  3997.  
  3998. function extractTextContentVF(post, selector, whichBlock) {
  3999. // - get the text node values of the regular feed posts
  4000. // -- scan a certain block in the posts
  4001. // -- parameters:
  4002. // post: post to scan
  4003. // selector: querySelector's query
  4004. // whichBlock: the block to scan for text (0 = first block ...)
  4005. const blocks = post.querySelectorAll(selector);
  4006. const arrayTextValues = [];
  4007. if ((blocks.length - 1) >= whichBlock) {
  4008. // - block 0 = Suggested headings, block 1 = title/heading, block 2 = content, block 3 = info box / comments, block 4 = comments
  4009. // - nb: some suggested posts only have one block ...
  4010. let blockToScan = blocks[whichBlock];
  4011. if (blockToScan.innerHTML.length > 0) {
  4012. arrayTextValues = arrayTextValues.concat(scanTreeForText(blockToScan));
  4013. }
  4014. }
  4015. return arrayTextValues;
  4016. }
  4017.  
  4018. function createTogglePostStateBar(reason) {
  4019. // :: <toggle button> <reason for hiding post(s)>
  4020. const elToggleBar = document.createElement('div');
  4021. elToggleBar.setAttribute(postAttTab, '0');
  4022.  
  4023. const elButtonBox = document.createElement('div');
  4024. elButtonBox.className = 'wbtn';
  4025.  
  4026. const elButton = document.createElement('button');
  4027. elButton.textContent = '___';
  4028. elButton.addEventListener('click', togglePost, false);
  4029. elButtonBox.appendChild(elButton);
  4030.  
  4031. const elTextReason = document.createElement('div');
  4032. elTextReason.className = 'wtxt';
  4033. elTextReason.textContent = KeyWords.VERBOSITY_MESSAGE[VARS.language][0] + reason;
  4034.  
  4035. elToggleBar.appendChild(elButtonBox);
  4036. elToggleBar.appendChild(elTextReason);
  4037.  
  4038. elToggleBar.addEventListener('click', togglePost, false);
  4039. return elToggleBar;
  4040. }
  4041.  
  4042. function createPostTab(reason) {
  4043. const elTab = document.createElement('h6');
  4044. elTab.setAttribute(postAttTab, '0');
  4045. elTab.textContent = reason;
  4046. return elTab;
  4047. }
  4048.  
  4049. function sanitizeReason(reason) {
  4050. // -- setting an attribute, so remove double quotes from reason's text.
  4051. return reason.replaceAll('"', '');
  4052. }
  4053.  
  4054. function hideFeature(post, reason, addPostTab = false) {
  4055. // -- hide something - keep it out of the regular feed stuff.
  4056. // -- no counter
  4057.  
  4058. if ((parseInt(VARS.Options.VERBOSITY_LEVEL, 10) > 0) && (reason !== '')) {
  4059. // -- add info tab
  4060. const elPostToggleStateBar = createTogglePostStateBar(reason);
  4061. let postFirstChild = post.querySelector(':scope > :first-child');
  4062. if (postFirstChild) {
  4063. postFirstChild.before(elPostToggleStateBar);
  4064. }
  4065. }
  4066.  
  4067. post.setAttribute(VARS.cssHide, '');
  4068. post.setAttribute(postAtt, sanitizeReason(reason));
  4069.  
  4070. if (addPostTab === true) {
  4071. // - add mini-tab to indicate why post is hidden
  4072. const elTabSpot = post.querySelector(`:scope > div:not([${postAttTab}]) > div > div > div:first-child`);
  4073. if (elTabSpot) {
  4074. elTabSpot.before(createPostTab(reason));
  4075. }
  4076. }
  4077.  
  4078. // - in debugging mode?
  4079. if (VARS.Options.VERBOSITY_DEBUG) {
  4080. post.setAttribute(VARS.cssShow, '');
  4081. }
  4082. }
  4083.  
  4084. function toggleHiddenElements() {
  4085. const containers = Array.from(document.querySelectorAll('[' + VARS.cssHide + ']'));
  4086. const blocks = Array.from(document.querySelectorAll('[' + VARS.cssHideEl + ']'));
  4087. const shares = Array.from(document.querySelectorAll('[' + VARS.cssHideNumberOfShares + ']'));
  4088.  
  4089. const elements = [...containers, ...blocks, ...shares];
  4090.  
  4091. if (VARS.Options.VERBOSITY_DEBUG) {
  4092. for (const element of elements) {
  4093. element.setAttribute(VARS.cssShow, '');
  4094. }
  4095. }
  4096. else {
  4097. for (const element of elements) {
  4098. element.removeAttribute(VARS.cssShow);
  4099. }
  4100. }
  4101. }
  4102.  
  4103. function togglePost(ev) {
  4104. ev.stopPropagation();
  4105. // -- grab the toggle post state bar container
  4106. const elToggleBarContainer = ev.target.closest(`div[${postAttTab}]`);
  4107. // -- grab the post
  4108. const elPost = elToggleBarContainer.parentElement;
  4109. // -- then toggle the VARS.cssShow attribute on the post.
  4110. if (!elToggleBarContainer.hasAttribute(postAttCPID)) {
  4111. // -- single post being hidden
  4112. elPost.hasAttribute(VARS.cssShow) ? elPost.removeAttribute(VARS.cssShow) : elPost.setAttribute(VARS.cssShow, '');
  4113. }
  4114. else {
  4115. // -- multiple consecutive posts being hidden
  4116. const cpid = elToggleBarContainer.getAttribute(postAttCPID);
  4117. const flaggedPosts = document.querySelectorAll(`[${postAtt}][${postAttCPID}=${cpid}]`);
  4118. if (flaggedPosts.length > 0) {
  4119. // -- cannot use classList.toggle() while posts are still being loaded ...
  4120. if (elPost.hasAttribute(VARS.cssShow)) {
  4121. for (const flaggedPost of flaggedPosts) {
  4122. flaggedPost.removeAttribute(VARS.cssShow);
  4123. }
  4124. }
  4125. else {
  4126. for (const flaggedPost of flaggedPosts) {
  4127. flaggedPost.setAttribute(VARS.cssShow, '');
  4128. }
  4129. }
  4130. }
  4131. }
  4132. }
  4133.  
  4134. function gvf_hidePost(post, reason) {
  4135. // -- hide something ..
  4136. // -- group feed
  4137. // -- video feed
  4138. // -- Verbosity_Level: 0 = hide; 1 = single info note; 2 = consecutive info notes
  4139.  
  4140. // console.info(log + 'gvf_hidePost(); v_L:', VARS.Options.VERBOSITY_LEVEL, VARS.echoEl, VARS.echoCount, reason, post);
  4141.  
  4142. if ((VARS.Options.VERBOSITY_LEVEL !== '0') && (reason !== '')) {
  4143. // -- in info tab mode
  4144. // -- nb: calling function will either zap or increment VARS.echoCount
  4145. // -- they don't look at VARS.Options.VERBOSITY_LEVEL's value
  4146.  
  4147. if ((VARS.Options.VERBOSITY_LEVEL === '1') || (VARS.isNF === true)) {
  4148. // - single post level
  4149. VARS.echoCount = 1;
  4150. }
  4151.  
  4152. if (VARS.echoCount < 2) {
  4153. // - 1 post to be hidden (includes first of consecutive group)
  4154.  
  4155. const postFirstChildEl = post.querySelector(':scope > :first-child');
  4156. if (postFirstChildEl) {
  4157. VARS.echoEl = createTogglePostStateBar(reason);
  4158. postFirstChildEl.before(VARS.echoEl);
  4159. }
  4160. else {
  4161. // post has been changed while being processed (very rare)
  4162. }
  4163. }
  4164.  
  4165. if ((VARS.Options.VERBOSITY_LEVEL === '2') && (VARS.isNF === false)) {
  4166.  
  4167. if (VARS.echoCount < 2) {
  4168. // - first post in a possible consecutive collection.
  4169. // - CPID = consecutive post id
  4170. VARS.echoCPID = generateRandomString();
  4171. VARS.echoEl.setAttribute(postAttCPID, VARS.echoCPID);
  4172. }
  4173. else {
  4174. // - 2+ consecutive posts hidden
  4175. VARS.echoEl.querySelector('.wtxt').textContent = VARS.echoCount + KeyWords.VERBOSITY_MESSAGE[VARS.language][1];
  4176. }
  4177.  
  4178. // - consecutive posts level - flag it as part of a consecutive group
  4179. post.setAttribute(postAttCPID, VARS.echoCPID);
  4180.  
  4181. // - add a tab (indicates why post is hidden)
  4182. const elTabSpot = post.querySelector(`:scope > div:not([${postAttTab}]) > div > div > div:first-child`);
  4183. if (elTabSpot) {
  4184. elTabSpot.before(createPostTab(reason));
  4185. }
  4186. }
  4187. }
  4188.  
  4189. // - flag & hide the post
  4190. post.setAttribute(VARS.cssHide, '');
  4191. post.setAttribute(postAtt, sanitizeReason(reason));
  4192.  
  4193. // - in debugging mode?
  4194. if (VARS.Options.VERBOSITY_DEBUG) {
  4195. post.setAttribute(VARS.cssShow, '');
  4196. }
  4197.  
  4198. //console.info(log+'gvf_hidePost():', VARS.echoElFirst);
  4199. }
  4200.  
  4201. function nf_hidePost(post, reason) {
  4202. // -- hide something ..
  4203. // -- news feed + search feed
  4204. // -- no consecutive counts.
  4205. // -- Verbosity_Level: 0 = hide; 1|2 = single info note; (no consecutive mode)
  4206.  
  4207. // console.info(log + 'nf_hidePost(); v_L:', VARS.Options.VERBOSITY_LEVEL, VARS.echoEl, VARS.echoCount, reason, post);
  4208.  
  4209. // -- February 2024 - hit fix !!! - disabled.
  4210. /*
  4211. if ((VARS.Options.VERBOSITY_LEVEL !== '0') && (reason !== '')) {
  4212. // -- in message/info tab mode
  4213. // -- code 2 is switched to code 1.
  4214. // -- VARS.echoCount is ignored.
  4215.  
  4216. const postFirstChildEl = post.querySelector(':scope > :first-child');
  4217. if (postFirstChildEl) {
  4218. const elMessageTab = createTogglePostStateBar(reason);
  4219. postFirstChildEl.before(elMessageTab);
  4220. }
  4221. else {
  4222. // post has been changed while being processed (very rare)
  4223. }
  4224. }
  4225. */
  4226.  
  4227. // - flag & hide the post
  4228. post.setAttribute(VARS.cssHide, '');
  4229. post.setAttribute(postAtt, sanitizeReason(reason));
  4230.  
  4231. // -- February 2024 - hot fix !!! - collapse post completely
  4232. const elWithMargin = climbUpTheTree(post, 4);
  4233. elWithMargin.setAttribute('style', 'margin:0 !important;');
  4234.  
  4235. // -- February 2024 - hot fix !!! - disabled
  4236. /*
  4237. // - in debugging mode?
  4238. if (VARS.Options.VERBOSITY_DEBUG) {
  4239. post.setAttribute(VARS.cssShow, '');
  4240. }
  4241. */
  4242.  
  4243. //console.info(log+'nf_hidePost():', VARS.echoElFirst);
  4244. }
  4245.  
  4246.  
  4247. function nf_dropTags(post) {
  4248. // -- remove cmf's attributes/classes from empty posts.
  4249. // -- including tabs (if any)
  4250. post.removeAttribute(postAtt);
  4251. post.removeAttribute(VARS.cssHide);
  4252. post.removeAttribute(VARS.cssHideEl);
  4253. post.removeAttribute(VARS.cssShow);
  4254. // -- other attribute(s)
  4255. post.removeAttribute(postAttCPID);
  4256. // -- remove the notification tab.
  4257. if (post.querySelectorAll('div, h6').length > 0) {
  4258. post.removeChild(post.firstElementChild);
  4259. }
  4260.  
  4261. // -- February 2024 - hot fix !!! - remove the collapse post completely hack
  4262. const elWithMargin = climbUpTheTree(post, 4);
  4263. if (elWithMargin && elWithMargin.hasAttribute('style')) {
  4264. elWithMargin.removeAttribute('style');
  4265. }
  4266.  
  4267. post[postPropDS] = 1; // -- reset scanning count
  4268. }
  4269.  
  4270. function hideBlock(block, link, reason) {
  4271. block.setAttribute(VARS.cssHideEl, '');
  4272. link.setAttribute(postAtt, sanitizeReason(reason));
  4273. // - in debugging mode?
  4274. if (VARS.Options.VERBOSITY_DEBUG) {
  4275. block.setAttribute(VARS.cssShow, '');
  4276. }
  4277. }
  4278.  
  4279. function cleanText(text) {
  4280. // - fb is using ASCII code 160 for whitespace ...
  4281. // -- also "normalise" the text (i.e. convert unicode magic to normal ascii code)
  4282. // -- (unicode magic used to bold/italic/etc characters without html/css/style)
  4283. // return text.replaceAll(String.fromCharCode(160), String.fromCharCode(32)).normalize('NFKC');
  4284. // -- normalise(NKFC) will convert 160(00A0) to 32(0020)
  4285. // -- https://www.unicode.org/charts/normalization/index.html
  4286. return text.normalize('NFKC');
  4287. }
  4288.  
  4289. function isSponsored(post) {
  4290. // Is it a sponsored post?
  4291. // -- applies to News feed, Groups feed, Videos Feed
  4292. // -- mopUpTheMarketplaceFeed() looks after marketplace feed sponsored posts/items.
  4293.  
  4294. let isSponsoredPost = false;
  4295. const PARAM_FIND = '__cft__[0]=';
  4296. const PARAM_MIN_SIZE = (VARS.isSF) ? 254 : 299;
  4297.  
  4298. let elLinks = [];
  4299. if (VARS.isNF || VARS.isGF) {
  4300. // -- news feed
  4301. // -- and possibly groups feed (haven't seen a sponsored post in groups for ages!)
  4302. // -- May 2023 update (#3) - works with the introduced "Feeds (most recent)" feature
  4303. elLinks = Array.from(post.querySelectorAll(`div[aria-posinset] span > a[href*="${PARAM_FIND}"]:not([href^="/groups/"]):not([href*="section_header_type"])`));
  4304. if (elLinks.length === 0) {
  4305. // -- try again, some users don't have the aria-posinet attribute.
  4306. elLinks = Array.from(post.querySelectorAll(`div[aria-describedby] span > a[href*="${PARAM_FIND}"]:not([href^="/groups/"]):not([href*="section_header_type"])`));
  4307. }
  4308.  
  4309. // -- May 2023 update (disabled - May 2023 #3 takes care of this fix)
  4310. // elLinks = Array.from(post.querySelectorAll(`div[aria-posinset] h4 span > a[href*="${PARAM_FIND}"]:not([href^="/groups/"]):not([href*="section_header_type"])`));
  4311. // if (elLinks.length === 0) {
  4312. // // -- try again, some users don't have the aria-posinet attribute.
  4313. // elLinks = Array.from(post.querySelectorAll(`div[aria-describedby] h4 span > a[href*="${PARAM_FIND}"]:not([href^="/groups/"]):not([href*="section_header_type"])`));
  4314. // }
  4315.  
  4316. // -- March 2023 udpate (disabled - May 2023 takes care of March's fix.)
  4317. // elLinks = Array.from(post.querySelectorAll(`div[aria-posinset] h4 > span > a[href*="${PARAM_FIND}"]:not([href^="/groups/"]):not([href*="section_header_type"])`));
  4318. // if (elLinks.length === 0) {
  4319. // // -- try again, some users don't have the aria-posinet attribute.
  4320. // elLinks = post.querySelectorAll(`div[aria-describedby] h4 > span > a[href*="${PARAM_FIND}"]:not([href^="/groups/"]):not([href*="section_header_type"])`);
  4321. // }
  4322.  
  4323. // -- April 2023, possible sponsored video post in News Feed ("LIVE")
  4324. // if (elLinks.length === 0) {
  4325. // elLinks = Array.from(post.querySelectorAll(`div[aria-posinset] h4 > span > strong > span > a[href*="${PARAM_FIND}"]:not([href^="/groups/"]):not([href*="section_header_type"])`));
  4326. // if (elLinks.length === 0) {
  4327. // // -- try again, some users don't have the aria-posinet attribute.
  4328. // elLinks = post.querySelectorAll(`div[aria-describedby] h4 > span > strong > span > a[href*="${PARAM_FIND}"]:not([href^="/groups/"]):not([href*="section_header_type"])`);
  4329. // }
  4330. // }
  4331. }
  4332. else if (VARS.isVF) {
  4333. // -- watch videos feed has a slightly different html structure for sponsored posts.
  4334. elLinks = Array.from(post.querySelectorAll(`div > div > div > div > span > span > div > a[href*="${PARAM_FIND}"]`));
  4335. }
  4336. else if (VARS.isSF) {
  4337. // -- search feed - has a slightly different html structure for sponsored posts.
  4338. elLinks = Array.from(post.querySelectorAll(`div[role="article"] span > a[href*="${PARAM_FIND}"]`));
  4339. }
  4340. if (elLinks.length > 0) {
  4341. for (let i = 0; i < elLinks.length; i++) {
  4342. let el = elLinks[i];
  4343. let pos = el.href.indexOf(PARAM_FIND);
  4344. if (pos >= 0) {
  4345. if (el.href.slice(pos).length > PARAM_MIN_SIZE) {
  4346. isSponsoredPost = true;
  4347. break;
  4348. };
  4349. }
  4350. }
  4351. }
  4352. // if (isSponsoredPost) console.info(log + 'isSponsored(); isSponsoredPost:', isSponsoredPost, post);
  4353. return isSponsoredPost;
  4354. }
  4355.  
  4356. function querySelectorAllNoChildren(container = document, queries = [], minText = 0, executeAllQueries = false) {
  4357. // -- nb: .querySelectorAll(..) can have multiple queries and will execute them all (regardless of results)
  4358. if (!Array.isArray(queries)) {
  4359. queries = [queries];
  4360. }
  4361.  
  4362. if (queries.length === 0) {
  4363. return [];
  4364. };
  4365.  
  4366. if (executeAllQueries) {
  4367. return Array.from(container.querySelectorAll(queries)).filter((el) => {
  4368. return el.children.length === 0 && el.textContent.length >= minText;
  4369. });
  4370. }
  4371.  
  4372. for (const query of queries) {
  4373. const elements = container.querySelectorAll(query);
  4374. for (const element of elements) {
  4375. if (element.children.length === 0 && element.textContent.length >= minText) {
  4376. return [element];
  4377. }
  4378. }
  4379. }
  4380.  
  4381. return [];
  4382. }
  4383.  
  4384. function nf_isSuggested(post) {
  4385. // - check if any of the suggestions / recommendations type post
  4386. // -- nb: "<name> commented / <name> replied to a commment" posts have similar structure - but have extra elements ...
  4387. // -- nb: "x people recently commented" posts have similar structure - suggested/recommended posts don't start with a number ...
  4388.  
  4389. const queries = [
  4390. // -- June 2024
  4391. 'div[aria-posinset] span> div[id] > span:nth-of-type(1):not([style]):not([class])',
  4392. 'div[aria-describedby] span> div[id] > span:nth-of-type(1):not([style]):not([class])',
  4393.  
  4394. // -- github 30/06/2024 mr-pokemon
  4395. 'div[aria-posinset] > div > div > div > div > div > div:nth-of-type(2) > div > div > div:nth-of-type(2) > div > div:nth-of-type(2) > div > div:nth-of-type(2) > span > div > span:nth-of-type(1)',
  4396. 'div[aria-describedby] > div > div > div > div > div > div:nth-of-type(2) > div > div > div:nth-of-type(2) > div > div:nth-of-type(2) > div > div:nth-of-type(2) > span > div > span:nth-of-type(1)',
  4397.  
  4398. // -- May 2023
  4399. 'div[aria-posinset] > div > div > div > div > div > div:nth-of-type(2) > div > div > div:nth-of-type(1) > div > div > span',
  4400. 'div[aria-describedby] > div > div > div > div > div > div:nth-of-type(2) > div > div > div:nth-of-type(1) > div > div > span',
  4401. // -- February 2023 (no need for light dusting ...)
  4402. ':scope div[aria-posinset] > div > div > div > div > div > div:not([data-0]) > div > div > div:nth-of-type(1) > div > div > div > div > span',
  4403. ':scope div[aria-describedby] > div > div > div > div > div > div:not([data-0]) > div > div > div:nth-of-type(1) > div > div > div > div > span',
  4404.  
  4405. // -- Oct/Nov 2023 (provisional - not tested by zbluebugz)
  4406. 'div[aria-posinset] > div > div > div > div > div > div:nth-of-type(2) > div > div > div:nth-of-type(2) > div > div:nth-of-type(2) > div > div:nth-of-type(2) > span > span > span:nth-of-type(2)',
  4407. 'div[aria-describedby] > div > div > div > div > div > div:nth-of-type(2) > div > div > div:nth-of-type(2) > div > div:nth-of-type(2) > div > div:nth-of-type(2) > span > span > span:nth-of-type(2)',
  4408.  
  4409. // -- December 2022 - #2
  4410. ':scope div[aria-posinset] > div > div > div > div > div > div:nth-of-type(2) > div > div > div:nth-of-type(1) > div > div > div > div > span',
  4411. ':scope div[aria-describedby] > div > div > div > div > div > div:nth-of-type(2) > div > div > div:nth-of-type(1) > div > div > div > div > span',
  4412. // -- December 2022 - #1
  4413. ':scope > div > div > div > div > div > div > div > div > div > div > div > div:nth-of-type(2) > div > div > div:nth-of-type(1) > div > div > div > div > span',
  4414. // -- < December 2022
  4415. ':scope > div > div > div > div > div > div > div > div > div > div > div > div:nth-of-type(2) > div > div:nth-of-type(1) > div > div > div > div > span',
  4416. ':scope > div > div > div > div > div > div > div > div > div > div > div:nth-of-type(2) > div > div:nth-of-type(1) > div > div > div > div > span'
  4417. ];
  4418.  
  4419. const elSuggestion = querySelectorAllNoChildren(post, queries, 1);
  4420. if (elSuggestion.length > 0) {
  4421. if (nf_isReelsAndShortVideos(post).length > 0) {
  4422. // -- false-positive hit.
  4423. return '';
  4424. }
  4425. // -- pattern: a digit from 0 to 9 or any number in the unicode space
  4426. // --- Basic Latin: \u0030-\u0039 (Range: 0-9)
  4427. // --- Arabic-Indic Digits: \u0660-\u0669 (Range: ٠-٩)
  4428. // --- Eastern Arabic-Indic Digits: \u06F0-\u06F9 (Range: ۰-۹)
  4429. // --- Devanagari Digits: \u0966-\u096F (Range: ०-९)
  4430. // --- Bengali Digits: \u09E6-\u09EF (Range: ০-৯)
  4431. // --- Myanmar Digits: \u1040-\u1049 (Range: ၀-၉)
  4432. // --- Thai Digits: \u0E50-\u0E59 (Range: ๐-๙)
  4433. // --- Tibetan Digits: \u0F20-\u0F29 (Range: ༠-༩)
  4434. // const pattern = /([0-9]|[\u0660-\u0669])/;
  4435. const pattern = /([0-9]|[\u0660-\u0669]|[\u06F0-\u06F9]|[\u0966-\u096F]|[\u09E6-\u09EF]|[\u1040-\u1049]|[\u0E50-\u0E59]|[\u0F20-\u0F29])/;
  4436. // -- if text starts with a number, return nothing, else the trigger word.
  4437. const firstCharacter = cleanText(elSuggestion[0].textContent).trim().slice(0, 1);
  4438. // console.info(log+'isSuggested - match test:', firstCharacter, pattern.test(firstCharacter), pattern.test(firstCharacter) ? 'No': 'Yes' );
  4439. return (pattern.test(firstCharacter)) ? '' : KeyWords.NF_SUGGESTIONS[VARS.language];
  4440. }
  4441. return '';
  4442. }
  4443.  
  4444. function gf_isSuggested(post) {
  4445. // - check if any of the suggestions / recommendations type post
  4446. // -- get the blocks/sections, then have a look for <i> in 1st block (providing there's more than 1 block)
  4447. // -- (query bypasses the dusty elements)
  4448. // -- some users don't have [aria-posinset], hence [aria-describedby]
  4449. let results = '';
  4450. let blocksQuery = 'div[aria-posinset] > div > div > div > div > div > div > div > div, div[aria-describedby] > div > div > div > div > div > div > div > div';
  4451. let blocks = post.querySelectorAll(blocksQuery);
  4452. if (blocks.length <= 1) {
  4453. // try again .. (December 2022 change)
  4454. blocksQuery = 'div[aria-posinset] > div > div > div > div > div > div > div > div > div, div[aria-describedby] > div > div > div > div > div > div > div > div > div';
  4455. blocks = post.querySelectorAll(blocksQuery);
  4456. }
  4457. if (blocks.length > 1) {
  4458. let suggIcon = blocks[0].querySelector('i[data-visualcompletion="css-img"][style]');
  4459. if (suggIcon) {
  4460. results = KeyWords.GF_SUGGESTIONS[VARS.language];
  4461. }
  4462. else {
  4463. // -- a sneaky group post without the standard suggestion/recommendation header
  4464. let query = 'h3 > div > span ~ span > span > div > div';
  4465. let sneakyGroupPost = blocks[1].querySelector(query);
  4466. if (sneakyGroupPost) {
  4467. results = KeyWords.GF_SUGGESTIONS[VARS.language];
  4468. }
  4469. }
  4470. }
  4471. return results;
  4472. }
  4473.  
  4474. function nf_isPeopleYouMayKnow(post) {
  4475. const queryPYMK = 'a[href*="/friends/suggestions/"][role="link"]';
  4476. const linksPYMK = post.querySelectorAll(queryPYMK);
  4477. return (linksPYMK.length === 0) ? '' : KeyWords.NF_PEOPLE_YOU_MAY_KNOW[VARS.language];
  4478. }
  4479.  
  4480. function nf_isPaidPartnership(post) {
  4481. const queryPP = 'span[dir] > span[id] a[href^="/business/help/"]';
  4482. const elPaidPartnership = post.querySelector(queryPP);
  4483. return (elPaidPartnership === null) ? '' : KeyWords.NF_PAID_PARTNERSHIP[VARS.language];
  4484. }
  4485.  
  4486. function nf_isSponsoredPaidBy(post) {
  4487. const querySPB = 'div:nth-child(2) > div > div:nth-child(2) > span[class] > span[id] > div:nth-child(2)';
  4488. const sponsoredPaidBy = querySelectorAllNoChildren(post, querySPB, 1);
  4489. return (sponsoredPaidBy.length === 0) ? '' : KeyWords.NF_SPONSORED_PAID[VARS.language];
  4490. }
  4491.  
  4492. function nf_isReelsAndShortVideos(post) {
  4493. // -- reels and short videos (multiple)
  4494. const queryReelsAndShortVideos = 'a[href="/reel/?s=ifu_see_more"]';
  4495. const elReelsAndShortVideos = post.querySelector(queryReelsAndShortVideos);
  4496.  
  4497. if (elReelsAndShortVideos !== null) {
  4498. return KeyWords.NF_REELS_SHORT_VIDEOS[VARS.language];
  4499. }
  4500. // -- try again, but use dictionary based rule (for some reason the above rule has failed for certain users)
  4501. const buttonDiv = post.querySelector('div[role="button"] > i ~ div');
  4502. if (buttonDiv && buttonDiv.textContent) {
  4503. const buttonText = buttonDiv.textContent.trim().toLowerCase();
  4504. if (VARS.dictionaryReelsAndShortVideos.find(item => item === buttonText)) {
  4505. return KeyWords.NF_REELS_SHORT_VIDEOS[VARS.language];
  4506. }
  4507. }
  4508.  
  4509. return '';
  4510. }
  4511.  
  4512.  
  4513. function nf_isShortReelVideo(post) {
  4514. // -- reel/short video post (single)
  4515. // -- post must have only one reel link
  4516. const querySRV = 'a[href*="/reel/"]';
  4517. const elementsSRV = Array.from(post.querySelectorAll(querySRV));
  4518. return (elementsSRV.length !== 1) ? '' : KeyWords.NF_SHORT_REEL_VIDEO[VARS.language];
  4519. }
  4520.  
  4521. function gf_isShortReelVideo(post) {
  4522. // -- reel/short video post (single)
  4523. // -- post must have only one reel link
  4524. const querySRV = 'a[href*="/reel/"]';
  4525. const elementsSRV = Array.from(post.querySelectorAll(querySRV));
  4526. return (elementsSRV.length !== 1) ? '' : KeyWords.GF_SHORT_REEL_VIDEO[VARS.language];
  4527. }
  4528.  
  4529. function nf_isEventsYouMayLike(post) {
  4530. // -- events you may like posts
  4531. const query = (':scope div > div:nth-of-type(2) > div > div > h3 > span');
  4532. const events = querySelectorAllNoChildren(post, query, 0);
  4533. return (events.length === 0) ? '' : KeyWords.NF_EVENTS_YOU_MAY_LIKE[VARS.language];
  4534. }
  4535.  
  4536. function nf_isFollow(post) {
  4537. // -- follow someone/something post
  4538. const queryFollow = ':scope h4[id] > span > div > span';
  4539. const elementsFollow = querySelectorAllNoChildren(post, queryFollow, 0);
  4540. return (elementsFollow.length !== 1) ? '' : KeyWords.NF_FOLLOW[VARS.language];
  4541. }
  4542.  
  4543. function nf_isParticipate(post) {
  4544. // -- participate in a post ...
  4545. const queryParticipate = ':scope h4[id] > div[class] > span[dir] > span[class] > div[class] > span[class]';
  4546. const elementsParticipate = querySelectorAllNoChildren(post, queryParticipate, 0);
  4547. return (elementsParticipate.length !== 1) ? '' : KeyWords.NF_PARTICIPATE[VARS.language];
  4548. }
  4549.  
  4550. function findFirstMatch(longText, valuesToFind) {
  4551.  
  4552. return findFirstMatchRE(longText, valuesToFind);
  4553.  
  4554. // const foundWord = valuesToFind.find(word => longText.includes(word));
  4555. // return foundWord !== undefined ? foundWord : '';
  4556. }
  4557.  
  4558. function findFirstMatchRE(longText, patterns) {
  4559. // -- using Regular Expressions
  4560. // -- user supplied the RE patterns
  4561. for (const pattern of patterns) {
  4562. // -- do not use 'g' - want to reset lastindex to 0 for each test.
  4563. // --'i' flag for case-insensitive matching;
  4564. const regex = new RegExp(pattern, 'i');
  4565. if (regex.test(longText)) {
  4566. return pattern;
  4567. }
  4568. }
  4569. return '';
  4570. }
  4571.  
  4572. function nf_isBlockedText(post) {
  4573. // - check for blocked text - partial text match
  4574. // -- news feed post's blocks (have 1-4 blocks)
  4575. // -- scan 1st & 3rd blocks
  4576. // -- used by the fn extractTextContent() and fn doMoppingInfoBox()
  4577. // -- some users don't have [aria-posinset], hence [aria-describedby]
  4578. let blocksQuery = 'div[aria-posinset] > div > div > div > div > div > div > div > div, div[aria-describedby] > div > div > div > div > div > div > div > div';
  4579. let blocks = post.querySelectorAll(blocksQuery);
  4580. if (blocks.length <= 1) {
  4581. // -- try again .. (December 2022 change)
  4582. // -- no need to do another querySelectorAll(..) - extractTextContent will do this.
  4583. blocksQuery = 'div[aria-posinset] > div > div > div > div > div > div > div > div > div, div[aria-describedby] > div > div > div > div > div > div > div > div > div';
  4584. }
  4585. const postTexts = (extractTextContent(post, blocksQuery, 3)).join(' ').toLowerCase();
  4586. const blockedText = findFirstMatch(postTexts, VARS.Filters.NF_BLOCKED_TEXT_LC);
  4587. return blockedText;
  4588. }
  4589.  
  4590. function gf_isBlockedText(post) {
  4591. // - check for blocked text - partial text match
  4592. // -- groups feed post's blocks (have 1-4 blocks)
  4593. // -- scan first 3 blocks
  4594. // -- some users don't have [aria-posinset], hence [aria-describedby]
  4595. let blocksQuery = 'div[aria-posinset] > div > div > div > div > div > div > div > div, div[aria-describedby] > div > div > div > div > div > div > div > div';
  4596. let blocks = post.querySelectorAll(blocksQuery);
  4597. if (blocks.length <= 1) {
  4598. // try again .. (Dec 2022 change)
  4599. // -- no need to do another querySelectorAll(..) - extractTextContent will do this.
  4600. blocksQuery = 'div[aria-posinset] > div > div > div > div > div > div > div > div > div, div[aria-describedby] > div > div > div > div > div > div > div > div > div';
  4601. }
  4602. const postTexts = (extractTextContent(post, blocksQuery, 3)).join(' ').toLowerCase();
  4603. const blockedText = findFirstMatch(postTexts, VARS.Filters.GF_BLOCKED_TEXT_LC);
  4604. return blockedText;
  4605. }
  4606.  
  4607. function vf_isBlockedText(post, queryBlocks) {
  4608. // - check for blocked text - partial text match
  4609. // -- regular videos feed post's blocks (have 1-3 blocks)
  4610. // -- scan 1st block only
  4611. const postTexts = (extractTextContent(post, queryBlocks, 1)).join(' ').toLowerCase();
  4612. const blockedText = findFirstMatch(postTexts, VARS.Filters.VF_BLOCKED_TEXT_LC);
  4613. return blockedText;
  4614. }
  4615.  
  4616. function vf_isVideoLive(post) {
  4617. // - check for "LIVE" indicator on videos
  4618. const liveRule = 'div[role="presentation"] ~ div > div:nth-of-type(1) > span';
  4619. const elLive = post.querySelectorAll(liveRule);
  4620. return (elLive.length > 0) ? KeyWords.VF_LIVE[VARS.language] : '';
  4621. }
  4622.  
  4623. function mp_getBlockedPrices(elBlockOfText) {
  4624. // -- scan the first price listed in itemPrices for a match.
  4625. // -- (second price is the old one (with strike-through))
  4626. // -- needs to be an extact match.
  4627. // :: return : blocked text or ''.
  4628. if (VARS.Filters.MP_BLOCKED_TEXT.length > 0) {
  4629. const itemPrices = mp_scanTreeForText(elBlockOfText);
  4630. const blockedPrices = findFirstMatch(itemPrices, VARS.Filters.MP_BLOCKED_TEXT_LC);
  4631. return blockedPrices;
  4632. }
  4633. return '';
  4634. }
  4635.  
  4636. function mp_getBlockedTextDescription(collectionBlocksOfText, skipFirstBlock = true) {
  4637. // -- scan the various blocks of text for blocked text.
  4638. // -- partial match.
  4639. // :: return : blocked text or ''.
  4640. if (VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION.length > 0) {
  4641. const startIndex = skipFirstBlock ? 1 : 0;
  4642. for (let i = startIndex; i < collectionBlocksOfText.length; i++) {
  4643. const descriptionTextList = mp_scanTreeForText(collectionBlocksOfText[i]);
  4644. const descriptionText = descriptionTextList.join(' ').toLowerCase();
  4645. const blockedText = findFirstMatch(descriptionText, VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION_LC);
  4646. if (blockedText.length > 0) {
  4647. return blockedText
  4648. }
  4649. }
  4650. }
  4651. return '';
  4652. }
  4653.  
  4654. function mp_doBlockingByBlockedText() {
  4655. // -- scan the marketplace for items
  4656. // -- hide an item if the price is listed in the list of blocked text
  4657. // -- hide an item if the descriptinis listed in the list of blocked description text
  4658. // :: return <nothing>
  4659.  
  4660. // -- March 2024 (fb changed code - personalised and non-personalised)
  4661. const queries = [
  4662. // -- landing page listing
  4663. `div[style]:not([${postAtt}]) > div > div > span > div > div > a[href*="/marketplace/item/"]`,
  4664. `div[style]:not([${postAtt}]) > div > div > span > div > div > a[href*="/marketplace/np/item/"]`,
  4665. // -- category page listing
  4666. `div[style]:not([${postAtt}]) > div > span > div > div > a[href*="/marketplace/item/"]`,
  4667. `div[style]:not([${postAtt}]) > div > span > div > div > a[href*="/marketplace/np/item/"]`
  4668. ];
  4669. let items = [];
  4670. for (const query of queries) {
  4671. items = document.querySelectorAll(query);
  4672. if (items.length > 0) {
  4673. break;
  4674. }
  4675. }
  4676.  
  4677. for (const item of items) {
  4678. // - item's container
  4679. const box = item.closest('div[style]');
  4680. if (box.hasAttribute(postAttMPSkip)) {
  4681. if (box.innerHTML.length == box.getAttribute(postAttMPSkip)) {
  4682. continue;
  4683. }
  4684. }
  4685. // - blocks of text to scan
  4686. const queryTextBlock = ':scope > div > div:nth-of-type(2) > div'
  4687. const blocksOfText = item.querySelectorAll(queryTextBlock);
  4688. if (blocksOfText.length > 0) {
  4689. // -- price(s) is in first block
  4690. const blockedTextPrices = mp_getBlockedPrices(blocksOfText[0]);
  4691. // -- description is in other blocks
  4692. const blockedTextDescription = mp_getBlockedTextDescription(blocksOfText, true);
  4693.  
  4694. if (blockedTextPrices.length > 0) {
  4695. // -- hide the item
  4696. mp_hideBox(box, blockedTextPrices);
  4697. }
  4698. else if (blockedTextDescription.length > 0) {
  4699. // -- hide the item
  4700. mp_hideBox(box, blockedTextDescription);
  4701. }
  4702. else {
  4703. // -- flag the item not to be scanned again
  4704. box.setAttribute(postAtt, '');
  4705. // box.setAttribute(postAttMPSkip, box.innerHTML.length);
  4706. }
  4707. }
  4708. };
  4709. }
  4710.  
  4711. function vf_scrubSponsoredBlock(post) {
  4712. // - some videos have a sponsored block beneath the video block/section
  4713. // - includes "watch more ___ videos by ___"
  4714. // :: return <nothing>
  4715. const queryForContainer = ':scope > div > div > div > div > div > div:nth-of-type(2)';
  4716. const blocksContainer = post.querySelector(queryForContainer);
  4717. if (blocksContainer && blocksContainer.childElementCount > 0) {
  4718. const adBlock = blocksContainer.querySelector(':scope > a');
  4719. if (adBlock && !adBlock.hasAttribute(postAtt)) {
  4720. hideBlock(adBlock, adBlock, KeyWords.SPONSORED[VARS.language]);
  4721. }
  4722. }
  4723. // const query = `:scope > div > div > div > div > div:nth-of-type(2) > div`;
  4724. // const blocks = post.querySelectorAll(query);
  4725. // if (blocks.length > 3) {
  4726. // const block = blocks[2]; // -- 3rd block
  4727. // const isJunk = block.querySelector(`:scope > div > div > div > div > div > div > span > a:not([${postAtt}])`);
  4728. // if (isJunk !== null) {
  4729. // hideBlock(block, isJunk, KeyWords.SPONSORED[VARS.language]);
  4730. // }
  4731. // }
  4732. }
  4733.  
  4734. function swatTheMosquitos(post) {
  4735. // - scan the post for any gifs that is animating (pausing them once)
  4736. // :: return <nothing>
  4737. const query = `div[role="button"][aria-label*="GIF"]:not([${postAtt}]) > i:not([data-visualcompletion])`;
  4738. const animatedGIFs = post.querySelectorAll(query);
  4739. // console.info('pausing, animatedGIFs::', animatedGIFs);
  4740. for (const gif of animatedGIFs) {
  4741. // mimic user clicking on animating gif
  4742. // - which will trigger fb's click event.
  4743. // - grab the A tag that is displayed when paused (uses Opacity)
  4744. const parent = climbUpTheTree(gif, 3);
  4745. const sibling = parent.querySelector(':scope > a');
  4746. if (sibling) {
  4747. const sibingCS = window.getComputedStyle(sibling);
  4748. if (sibingCS.opacity === '0') {
  4749. // 0 = animating; 1 = paused;
  4750. gif.parentElement.click();
  4751. // console.info(log + 'swatTheMosquitos() - paused', gif);
  4752. }
  4753. gif.parentElement.setAttribute(postAtt, '1');
  4754. }
  4755. }
  4756. }
  4757.  
  4758. function nf_scrubTheTabbies() {
  4759. // -- Tablist : stories | reels | rooms
  4760. // -- Stories
  4761. // -- both appear at top of NF
  4762. const queryTabList = 'div[role="main"] > div > div > div > div > div > div > div > div[role="tablist"]';
  4763. const elTabList = document.querySelector(queryTabList);
  4764. if (elTabList) {
  4765. if (elTabList.hasAttribute(postAttChildFlag)) {
  4766. return;
  4767. }
  4768. // -- parent is 4 levels up.
  4769. const elParent = climbUpTheTree(elTabList, 4);
  4770. if (elParent) {
  4771. hideFeature(elParent, (KeyWords.NF_TABLIST_STORIES_REELS_ROOMS[VARS.language]).replaceAll('"', ''), false);
  4772. elTabList.setAttribute(postAttChildFlag, 'tablist');
  4773. return;
  4774. }
  4775. }
  4776. else {
  4777. // - Stories only
  4778. // -- two patterns
  4779. // -- - one with listing of stories
  4780. // -- - one with no listing of stories
  4781.  
  4782. function getStoriesParent(element) {
  4783. const elAFewBranchesUp = climbUpTheTree(element, 4);
  4784. const moreStories = elAFewBranchesUp.querySelectorAll('a[href*="/stories/"]');
  4785. let elParent = null;
  4786. if (moreStories.length > 1) {
  4787. // -- query results has create and at least 1 story link
  4788. elParent = climbUpTheTree(elCreateStory.closest('div[aria-label][role="region"]'), 4);
  4789. }
  4790. else {
  4791. // -- query results has one link - create story
  4792. elParent = climbUpTheTree(elCreateStory, 7);
  4793. }
  4794. return elParent;
  4795. }
  4796.  
  4797. // const queryForCreateStory = 'a[href*="/stories/create"]'; - too loose, grabs the FB menu's entries as well ...
  4798. const queryForCreateStory = 'div[role="main"] > div > div > div > div > div > div > div > div a[href*="/stories/create"]';
  4799. const elCreateStory = document.querySelector(queryForCreateStory);
  4800. if (elCreateStory && !elCreateStory.hasAttribute(postAttChildFlag)) {
  4801. const elParent = getStoriesParent(elCreateStory);
  4802. if (elParent !== null) {
  4803. hideFeature(elParent, KeyWords.NF_TABLIST_STORIES_REELS_ROOMS[VARS.language], false);
  4804. elCreateStory.setAttribute(postAttChildFlag, '1');
  4805. }
  4806. }
  4807. }
  4808. }
  4809.  
  4810. function nf_isStoriesPost(post) {
  4811. // - Stories posts
  4812. // -- appears in News-Feed stream
  4813. const queryForStory = '[href^="/stories/"][href*="source=from_feed"]';
  4814. const elStory = post.querySelector(queryForStory);
  4815. return (elStory) ? KeyWords.NF_STORIES[VARS.language] : '';
  4816. }
  4817.  
  4818. function nf_cleanTheConsoleTable(findItem = 'Sponsored') {
  4819. // -- mopping up the news feed aside panel. findItem values: Sponosored | Suggestions
  4820. // :: return <nothing>
  4821. const query = 'div[role="complementary"] > div > div > div > div > div:not([data-visualcompletion])';
  4822. const asideBoxes = document.querySelectorAll(query);
  4823. if (asideBoxes.length === 0) {
  4824. return;
  4825. }
  4826. // -- only interested in the first container.
  4827. const asideContainer = asideBoxes[0];
  4828. if (asideContainer.childElementCount === 0) {
  4829. return;
  4830. }
  4831.  
  4832. let elItem = null;
  4833. let reason = '';
  4834. if (findItem === 'Sponsored') {
  4835. elItem = asideContainer.querySelector(`:scope > span:not([${postAtt}])`);
  4836. if (elItem && elItem.innerHTML.length > 0) {
  4837. reason = KeyWords.SPONSORED[VARS.language];
  4838. }
  4839. }
  4840. else if (findItem === 'Suggestions') {
  4841. elItem = asideContainer.querySelector(`:scope > div:not([${postAtt}])`);
  4842. if (elItem && elItem.innerHTML.length > 0) {
  4843. // -- check for birthdays
  4844. const birthdays = elItem.querySelectorAll('a[href="/events/birthdays/"]').length > 0;
  4845. // -- check for "your pages and profiles"
  4846. // -- suggested groups only have 1 i[..] attribute
  4847. const pagesAndProfiles = elItem.querySelectorAll('div > i[data-visualcompletion="css-img"]').length > 1;
  4848.  
  4849. if (birthdays === false && pagesAndProfiles === false) {
  4850. reason = KeyWords.NF_SUGGESTIONS[VARS.language];
  4851. }
  4852. }
  4853. }
  4854. if (reason.length > 0) {
  4855. hideFeature(elItem, reason, true);
  4856. }
  4857. }
  4858.  
  4859. function gf_cleanTheConsoleTable(findItem = 'Suggestions') {
  4860. // -- mopping up the groups feed aside panel - suggested
  4861. // :: return <nothing>
  4862. if (findItem !== 'Suggestions') {
  4863. return;
  4864. }
  4865.  
  4866. const query = `a[href*="/groups/discover"]:not([${postAtt}]) > span > span`;
  4867. const asideBoxes = querySelectorAllNoChildren(document, query, 1);
  4868. if (asideBoxes.length === 0) {
  4869. return;
  4870. }
  4871.  
  4872. for (const asideBox of asideBoxes) {
  4873. // parent is 21 levels up ...
  4874. const elParent = climbUpTheTree(asideBox, 21);
  4875. asideBox.closest('a').setAttribute(postAtt, KeyWords.GF_SUGGESTIONS[VARS.language]);
  4876. hideFeature(elParent, KeyWords.GF_SUGGESTIONS[VARS.language], true);
  4877. }
  4878. }
  4879.  
  4880. function gf_setPostLinkToOpenInNewTab(post) {
  4881. // -- from the groups feed, open a group post in a new window
  4882. // -- add an icon next to the other icons at the top of the group post.
  4883. // -- (there's no equivalent function for news feed posts - no quick way of getting the post's URL)
  4884. // :: return <nothing>
  4885.  
  4886. try {
  4887. if (post.hasAttribute('class')) {
  4888. // -- not a group post.
  4889. return;
  4890. }
  4891. // - parts of the post's link can be found in the first link
  4892. const postLinks = post.querySelectorAll('div > div > a[href*="/groups/"][role="link"]');
  4893. if (postLinks.length > 0) {
  4894. const postLink = postLinks[0];
  4895. // -- get the container for the lower part of the header block.
  4896. const elHeader = climbUpTheTree(postLink, 4);
  4897. const blockOfIcons = elHeader.querySelector(':scope > div:nth-of-type(2) > div > div:nth-of-type(2) > span > span');
  4898. let newLink = '';
  4899.  
  4900. if (blockOfIcons && blockOfIcons.querySelector('.cmf-link-new') === null) {
  4901. // -- need to create the group post's link
  4902. // -- nb: last post may not be a post - it could be the "You've completely caught up. Check again later for more updates" post.
  4903. // -- it doesn't have a set of icons ...
  4904. const postId = new URLSearchParams(postLink.href).get('multi_permalinks');
  4905. if (postId !== null) {
  4906. // -- sample link: https://www.facebook.com/groups/424532172574012/?hoisted_section_header_type=recently_seen&multi_permalinks=720886619605231&__cft__[0]=AZV1vpwA0h21cVRZoS_GM3Q7H_Ul77iObEbYu2EA4oL7XyM-C78sQp5KIEpPooBCQZ2dmAMTvpCi1qYt5VxSTiCQCBkTmv8_Ra77OyacW2l685TVbttwb4qwKUm6AVr0zIapBxKODmLHgnNcYaSkXeCEOMEMdxQajQX8NTcniWYUA7OuVNY5C4F-ETSuab37Azw&__tn__=%3C%3C%2CP-R
  4907. // -- .. converted to: https://www.facebook.com/groups/424532172574012/posts/720886619605231/
  4908. // -- post link structure: https://www.facebook.com/groups/<group id>/posts/<post id>/
  4909. newLink = postLink.href.split('?')[0] + 'posts/' + postId + '/';
  4910. }
  4911. else {
  4912. return;
  4913. }
  4914. }
  4915. else {
  4916. // -- hmm, we don't have the info to reconstruct a group post link.
  4917. return;
  4918. }
  4919.  
  4920. // -- put in fb's spacer between icons.
  4921. const spanSpacer = document.createElement('span');
  4922. spanSpacer.innerHTML = '<span><span style="position:absolute;width:1px;height:1px;">&nbsp;</span><span aria-hidden="true"> · </span></span>'
  4923. blockOfIcons.appendChild(spanSpacer);
  4924.  
  4925. const container = document.createElement('span');
  4926. container.className = 'link-new';
  4927. const span2 = document.createElement('span');
  4928. const linkNew = document.createElement('a');
  4929. linkNew.setAttribute('href', newLink);
  4930. linkNew.innerHTML = VARS.iconNewWindow;
  4931. linkNew.setAttribute('target', '_blank');
  4932. span2.appendChild(linkNew);
  4933. container.appendChild(span2);
  4934.  
  4935. blockOfIcons.appendChild(container);
  4936. }
  4937. }
  4938. catch (error) {
  4939. console.error(log + 'gf_setPostLinkToOpenInNewTab(); Error:', post, error);
  4940. }
  4941. }
  4942.  
  4943. function scrubInfoBoxes(post) {
  4944. // -- hide the "truth" info boxes that appear in posts having a certain topic.
  4945. // :: return <nothing>
  4946. let hiding = false;
  4947.  
  4948. if (VARS.Options.OTHER_INFO_BOX_CLIMATE_SCIENCE) {
  4949. const elLink = post.querySelector(`a[href*="${KeyWords.OTHER_INFO_BOX_CLIMATE_SCIENCE.pathMatch}"]:not([${postAtt}])`);
  4950. if (elLink !== null) {
  4951. // - block @ 5 levels up.
  4952. const block = climbUpTheTree(elLink, 5);
  4953. hideBlock(block, elLink, KeyWords.OTHER_INFO_BOX_CLIMATE_SCIENCE[VARS.language]);
  4954. hiding = true;
  4955. }
  4956. }
  4957. //console.info(log+'scrubInfoBoxes():', hiding, VARS.Options.OTHER_INFO_BOX_CORONAVIRUS, KeyWords.OTHER_INFO_BOX_CORONAVIRUS.pathMatch, post);
  4958. if (!hiding && VARS.Options.OTHER_INFO_BOX_CORONAVIRUS) {
  4959. const elLink = post.querySelector(`a[href*="${KeyWords.OTHER_INFO_BOX_CORONAVIRUS.pathMatch}"]:not([${postAtt}])`);
  4960. if (elLink !== null) {
  4961. // - block @ 5 levels up.
  4962. const block = climbUpTheTree(elLink, 5);
  4963. hideBlock(block, elLink, KeyWords.OTHER_INFO_BOX_CORONAVIRUS[VARS.language]);
  4964. hiding = true;
  4965. }
  4966. }
  4967. if (!hiding && VARS.Options.OTHER_INFO_BOX_SUBSCRIBE) {
  4968. const elLink = post.querySelector(`a[href*="${KeyWords.OTHER_INFO_BOX_SUBSCRIBE.pathMatch}"]:not([${postAtt}])`);
  4969. if (elLink !== null) {
  4970. // - block @ 5 levels up.
  4971. const block = climbUpTheTree(elLink, 5);
  4972. hideBlock(block, elLink, KeyWords.OTHER_INFO_BOX_SUBSCRIBE[VARS.language]);
  4973. hiding = true;
  4974. }
  4975. }
  4976. }
  4977.  
  4978. function nf_hideNumberOfShares(post) {
  4979. // -- hide the number of shares component
  4980. // :: return <nothing>
  4981. const query = `div[data-visualcompletion="ignore-dynamic"] > div:not([class]) > div:not([class]) > div:not([class]) > div[class] > div:nth-of-type(1) > div > div > span > div:not([id]) > span[dir]:not(${postAtt})`;
  4982. const shares = post.querySelectorAll(query);
  4983. for (const share of shares) {
  4984. share.setAttribute(VARS.cssHideNumberOfShares, '');
  4985. if (VARS.Options.VERBOSITY_DEBUG) {
  4986. share.setAttribute(VARS.cssShow, '');
  4987. }
  4988. share.setAttribute(postAtt, 'Shares');
  4989. }
  4990. }
  4991.  
  4992. function gf_hideNumberOfShares(post) {
  4993. // -- groups feed posts have same '# shares' html structure as news feed posts.
  4994. // -- .. hence calling nf_hideNumberOfShares(...)
  4995. // :: return <nothing>
  4996. nf_hideNumberOfShares(post);
  4997. }
  4998.  
  4999. function nf_postExceedsLikeCount(post) {
  5000. // -- hide posts having Like counts over a certain number
  5001. // const query = 'div[data-visualcompletion="ignore-dynamic"] > div:not([class]) > div:not([class]) > div:not([class]) > div[class] > div:nth-of-type(1) > div > div[class] > span > div[role="button"] > span[class][aria-hidden] > span:not([class]) > span[class]';
  5002. const queryLikes = 'span[role="toolbar"] ~ div div[role="button"] > span[class][aria-hidden] > span:not([class]) > span[class]';
  5003. const elLikes = post.querySelectorAll(queryLikes);
  5004. if (elLikes.length > 0) {
  5005. const maxLikes = parseInt(VARS.Options.NF_LIKES_MAXIMUM_COUNT);
  5006. const postLikesCount = getFullNumber(elLikes[0].textContent.trim());
  5007. // const postLikesCount = parseInt(elLikes[0].textContent);
  5008. const results = postLikesCount >= maxLikes ? KeyWords.NF_LIKES_MAXIMUM[VARS.language] : '';
  5009. // console.info(log + 'nf_postExceedsLikeCount(); results:', results, maxLikes, postLikesCount, post);
  5010. return results;
  5011. }
  5012. return false;
  5013. }
  5014.  
  5015. function getFullNumber(value) {
  5016. // -- convert shortened numbers into full numbers
  5017. // -- e.g 323 to 323; 1.2K to 1200; 1.4M to 1400000;
  5018. // :: returns a whole number.
  5019. let nvalue = 0;
  5020. if (value !== '') {
  5021. value = value.toUpperCase();
  5022. if (value.endsWith('K') || value.endsWith('M')) {
  5023. let multiplier = 1;
  5024. let pow_Y = 0;
  5025. if (value.endsWith('K')) {
  5026. // -- thousands
  5027. multiplier = 1000;
  5028. pow_Y = 3;
  5029. }
  5030. else if (value.endsWith('M')) {
  5031. // -- millions
  5032. multiplier = 1000000;
  5033. pow_Y = 6;
  5034. }
  5035.  
  5036. let bits = value.replace(/[KM]/g, '').replace(',', '.').split('.');
  5037.  
  5038. nvalue = parseInt(bits[0], 10) * multiplier;
  5039.  
  5040. if (bits.length > 1) {
  5041. nvalue += (parseInt(bits[1], 10) * Math.pow(10, (pow_Y - bits[1].length)));
  5042. }
  5043. }
  5044. else {
  5045. // -- less than 1000.
  5046. nvalue = parseInt(value, 10);
  5047. }
  5048. }
  5049. // console.info('results:', value, nvalue);
  5050. return nvalue;
  5051. }
  5052.  
  5053. function nf_scrubTheSurvey() {
  5054. // -- fb survey
  5055. // -- appears on the home page ..
  5056. // document.querySelectorAll('a[href*="/survey/?session="] > div[role="none"]')[0].closest('[style*="border-radius"]').parentElement.parentElement.parentElement
  5057. let btnSurvey = document.querySelector(`a[href*="/survey/?session="] > div[role="none"]:not([${postAtt}])`);
  5058. if (btnSurvey) {
  5059. let elContainer = climbUpTheTree(btnSurvey.closest('[style*="border-radius"]'), 3);
  5060. if (elContainer) {
  5061. hideFeature(elContainer, 'Survey', false);
  5062. btnSurvey.setAttribute(postAttChildFlag, 'Survey');
  5063. }
  5064. }
  5065. }
  5066.  
  5067. function nf_getCollectionOfPosts() {
  5068. // -- get a collection of posts
  5069. // -- fb serves a mixture of html structures
  5070. // -- so, we have a set of queries to try until we have found something...
  5071. // :: return : collection of posts.
  5072.  
  5073. let posts = [];
  5074. // -- various news feed queries
  5075. const queries = [
  5076. // -- February 2024 - hot fix !!! (no "extra" spaces in the feeds)
  5077. // 'h3[dir="auto"] ~ div:not([class]) > div[class] > div > div > div > div',
  5078. // 'h2[dir="auto"] ~ div:not([class]) > div[class] > div > div > div > div',
  5079.  
  5080. // -- February 2024 - hot fix !!! (test)
  5081. 'h3[dir="auto"] ~ div:not([class]) > div[class] :is([aria-posinset],[aria-describedby]:not([aria-posinset]))',
  5082. 'h2[dir="auto"] ~ div:not([class]) > div[class] :is([aria-posinset],[aria-describedby]:not([aria-posinset]))',
  5083.  
  5084.  
  5085. // -- February 2023 (promoted above Sept/August due to no hard-coded class names.
  5086. 'h3[dir="auto"] ~ div:not([class]) > div[class]',
  5087. 'h2[dir="auto"] ~ div:not([class]) > div[class]',
  5088. // -- September 2023
  5089. // -- - home news feed
  5090. 'h3[dir="auto"] + div > div.x1lliihq',
  5091. // -- - recent news feed
  5092. 'h2[dir="auto"] + div > div.x1lliihq',
  5093. // -- July 2023 - fb tweaked the structure - random div levels.
  5094. // -- - home news feed
  5095. 'h3[dir="auto"] ~ div > div > div div.x1yztbdb:not([role])',
  5096. // -- - recent news feed
  5097. 'h2[dir="auto"] ~ div > div > div div.x1yztbdb:not([role])',
  5098. ]
  5099.  
  5100. for (const query of queries) {
  5101. const nodeList = document.querySelectorAll(query);
  5102. if (nodeList.length > 0) {
  5103. posts = Array.from(nodeList);
  5104. break;
  5105. }
  5106. }
  5107.  
  5108. return posts;
  5109. }
  5110.  
  5111. function mopUpTheNewsFeed() {
  5112. // -- mopping up the news feed page
  5113.  
  5114. // -- Tablist - not part of the general news feed stream
  5115. // -- Includes Stories (standalone tab)
  5116. if (VARS.Options.NF_TABLIST_STORIES_REELS_ROOMS) {
  5117. nf_scrubTheTabbies();
  5118. }
  5119. if (VARS.Options.NF_SURVEY) {
  5120. nf_scrubTheSurvey();
  5121. }
  5122.  
  5123.  
  5124. // -- aside's sponsored
  5125. nf_cleanTheConsoleTable('Sponsored');
  5126.  
  5127. // -- aside's suggestions
  5128. if (VARS.Options.NF_SUGGESTIONS) {
  5129. nf_cleanTheConsoleTable('Suggestions');
  5130. }
  5131.  
  5132. // -- news feed stream ...
  5133. const posts = nf_getCollectionOfPosts();
  5134.  
  5135. // console.info(log + 'mopUpTheNewsFeed(); posts:', posts);
  5136.  
  5137. if (posts.length > 0) {
  5138. // console.info(log+'---> mopUpTheNewsFeed()');
  5139. // -- fb clears out "older" posts as the user scrolls ... so, only process the last X posts.
  5140. const count = posts.length;
  5141. const start = (count < 50) ? 0 : (count - 50);
  5142.  
  5143. // for (let post of posts) {
  5144. for (let i = start; i < count; i++) {
  5145.  
  5146. const post = posts[i];
  5147.  
  5148. if (post.innerHTML.length === 0) {
  5149. if (post.hasAttribute(postAtt)) {
  5150. // -- fb is clearing out the posts as the user scrolls ...
  5151. nf_dropTags(post);
  5152. }
  5153. }
  5154. else {
  5155.  
  5156. let hideReason = '';
  5157.  
  5158. if (post.hasAttribute(postAtt)) {
  5159. // -- already flagged ...
  5160. hideReason = 'hidden';
  5161. // -- however, fb is clearing out the posts as the user scrolls ...
  5162. if (post.querySelector('a') === null) {
  5163. nf_dropTags(post);
  5164. }
  5165. }
  5166. else if ((post[postPropDS] !== undefined) && (parseInt(post[postPropDS]) >= VARS.scanCountMaxLoop)) {
  5167. // -- skip these - already been scanned a few times
  5168. }
  5169. else {
  5170. doLightDusting(post);
  5171.  
  5172. if (hideReason === '' && VARS.Options.NF_REELS_SHORT_VIDEOS) {
  5173. hideReason = nf_isReelsAndShortVideos(post);
  5174. }
  5175. if (hideReason === '' && VARS.Options.NF_SHORT_REEL_VIDEO) {
  5176. hideReason = nf_isShortReelVideo(post);
  5177. }
  5178. if (hideReason === '' && VARS.Options.NF_PAID_PARTNERSHIP) {
  5179. hideReason = nf_isPaidPartnership(post);
  5180. }
  5181. if (hideReason === '' && VARS.Options.NF_PEOPLE_YOU_MAY_KNOW) {
  5182. hideReason = nf_isPeopleYouMayKnow(post);
  5183. }
  5184. if (hideReason === '' && VARS.Options.NF_SUGGESTIONS) {
  5185. hideReason = nf_isSuggested(post);
  5186. }
  5187. if (hideReason === '' && VARS.Options.NF_FOLLOW) {
  5188. hideReason = nf_isFollow(post);
  5189. }
  5190. if (hideReason === '' && VARS.Options.NF_PARTICIPATE) {
  5191. hideReason = nf_isParticipate(post);
  5192. }
  5193. if (hideReason === '' && VARS.Options.NF_SPONSORED_PAID) {
  5194. hideReason = nf_isSponsoredPaidBy(post);
  5195. }
  5196. if (hideReason === '' && VARS.Options.NF_EVENTS_YOU_MAY_LIKE) {
  5197. hideReason = nf_isEventsYouMayLike(post);
  5198. }
  5199. if (hideReason === '' && VARS.Options.NF_BLOCKED_ENABLED) {
  5200. hideReason = nf_isBlockedText(post);
  5201. }
  5202. if (hideReason === '' && VARS.Options.NF_STORIES) {
  5203. hideReason = nf_isStoriesPost(post);
  5204. }
  5205. // -- placed here due to "overlaps" between this rule and at least 1 of the above rule.
  5206. if (hideReason === '' && VARS.Options.NF_SPONSORED && isSponsored(post)) {
  5207. hideReason = KeyWords.SPONSORED[VARS.language];
  5208. }
  5209. if (hideReason === '' && VARS.Options.NF_LIKES_MAXIMUM && VARS.Options.NF_LIKES_MAXIMUM !== '') {
  5210. hideReason = nf_postExceedsLikeCount(post);
  5211. }
  5212. }
  5213.  
  5214. if (hideReason.length > 0) {
  5215. // -- increment hidden count
  5216. VARS.echoCount++;
  5217. if (hideReason !== 'hidden') {
  5218. // -- post not yet hidden, hide it.
  5219. nf_hidePost(post, hideReason);
  5220. }
  5221. }
  5222. else {
  5223. // -- not a hidden post
  5224. // -- reset hidden count
  5225. VARS.echoCount = 0;
  5226. // -- run pause animation (useful to hide those animated comments)
  5227. if (VARS.Options.NF_ANIMATED_GIFS) {
  5228. swatTheMosquitos(post);
  5229. }
  5230. // -- hide info boxes
  5231. if (VARS.hideAnInfoBox) {
  5232. scrubInfoBoxes(post);
  5233. }
  5234. // -- hide number of shares box
  5235. if (VARS.Options.NF_SHARES) {
  5236. nf_hideNumberOfShares(post);
  5237. }
  5238. }
  5239. }
  5240. }
  5241. // console.info(log+'<--- mopUpTheNewsFeed()');
  5242. }
  5243. }
  5244.  
  5245. function mopUpTheGroupsFeed() {
  5246. // -- mopping up the groups feed page
  5247.  
  5248. // console.info(log+'mopUpTheGroupsFeed(), gfType:', VARS.gfType, '; hide an info box:', VARS.hideAnInfoBox);
  5249.  
  5250. if (VARS.gfType === 'groups' || VARS.gfType === 'groups-recent' || VARS.gfType === 'search') {
  5251. // - main groups feed.
  5252. // -- 'groups' - accessible via the Groups link
  5253. // -- 'groups-recent' - accessible via the Feeds > group link (has similar HTML structure to News Feed)
  5254. // -- 'search' - groups (same layout as groups feed)
  5255.  
  5256. // -- aside's suggestions (also appears above feed on narrow pages)
  5257. if (VARS.Options.GF_SUGGESTIONS) {
  5258. gf_cleanTheConsoleTable('Suggestions');
  5259. }
  5260.  
  5261. // -- groups feed stream ...
  5262. const query = VARS.gfType === 'groups-recent' ? 'h2[dir="auto"] + div > div' : 'div[role="feed"] > div';
  5263. const posts = Array.from(document.querySelectorAll(query));
  5264. if (posts.length) {
  5265. // console.info(log+'---> mopUpTheGroupsFeed() - multiple groups');
  5266. for (const post of posts) {
  5267. if (post.innerHTML.length === 0) {
  5268. continue;
  5269. }
  5270.  
  5271. let hideReason = '';
  5272.  
  5273. // -- add the open post in new tab link+icon.
  5274. if ((VARS.gfType === 'groups') && (post[postPropDS] === undefined)) {
  5275. gf_setPostLinkToOpenInNewTab(post);
  5276. }
  5277.  
  5278. if (post.hasAttribute(postAtt)) {
  5279. // -- already flagged
  5280. hideReason = 'hidden';
  5281. }
  5282. else if ((post[postPropDS] !== undefined) && (parseInt(post[postPropDS]) >= VARS.scanCountMaxLoop)) {
  5283. // -- skip these - already been scanned a few times
  5284. }
  5285. else {
  5286.  
  5287. doLightDusting(post);
  5288.  
  5289. if (hideReason === '' && VARS.Options.GF_SPONSORED && isSponsored(post)) {
  5290. hideReason = KeyWords.SPONSORED[VARS.language];
  5291. }
  5292. if (hideReason === '' && VARS.Options.GF_SUGGESTIONS) {
  5293. hideReason = gf_isSuggested(post);
  5294. }
  5295. // if (hideReason === '' && VARS.Options.GF_PAID_PARTNERSHIP) {
  5296. // //console.info(log + 'mopUpTheGroupsFeed(), ---- Paid partnership - needs code ----')
  5297. // }
  5298. if (hideReason === '' && VARS.Options.GF_SHORT_REEL_VIDEO) {
  5299. hideReason = gf_isShortReelVideo(post);
  5300. }
  5301. if (hideReason === '' && VARS.Options.GF_BLOCKED_ENABLED) {
  5302. hideReason = gf_isBlockedText(post);
  5303. }
  5304. }
  5305.  
  5306. if (hideReason.length > 0) {
  5307. // -- increment hidden count
  5308. VARS.echoCount++;
  5309. if (hideReason !== 'hidden') {
  5310. // -- post not yet hidden, hide it.
  5311. gvf_hidePost(post, hideReason)
  5312. }
  5313. }
  5314. else {
  5315. // -- not a hidden post
  5316. // -- reset hidden count
  5317. VARS.echoCount = 0;
  5318. // -- run pause animation (useful to hide those animated comments)
  5319. if (VARS.Options.GF_ANIMATED_GIFS) {
  5320. // console.info(log + 'pausing animations ...');
  5321. swatTheMosquitos(post);
  5322. }
  5323. // -- hide info boxes
  5324. if (VARS.hideAnInfoBox) {
  5325. scrubInfoBoxes(post);
  5326. }
  5327. // -- hide number of shares box
  5328. if (VARS.Options.GF_SHARES) {
  5329. gf_hideNumberOfShares(post);
  5330. }
  5331. }
  5332. // console.info(log+'mopUpTheGroupsFeed:', hideReason, VARS.echoCount, post);
  5333. }
  5334. // console.info(log+'<--- mopUpTheGroupsFeed() - multiple groups');
  5335. }
  5336. }
  5337. else {
  5338. // - single group ...
  5339. const query = 'div[role="feed"] > div';
  5340. const posts = Array.from(document.querySelectorAll(query));
  5341. if (posts.length) {
  5342. // console.info(log+'---> mopUpTheGroupsFeed() - single group');
  5343. for (const post of posts) {
  5344.  
  5345. if (post.innerHTML.length === 0) {
  5346. continue;
  5347. }
  5348.  
  5349. let hideReason = '';
  5350.  
  5351. if (post.hasAttribute(postAtt)) {
  5352. // -- already flagged
  5353. hideReason = 'hidden';
  5354. }
  5355. else if ((post[postPropDS] !== undefined) && (parseInt(post[postPropDS]) >= VARS.scanCountMaxLoop)) {
  5356. // -- skip these - already scanned a few times
  5357. }
  5358. else {
  5359. doLightDusting(post);
  5360.  
  5361. if (hideReason === '' && VARS.Options.GF_SHORT_REEL_VIDEO) {
  5362. hideReason = gf_isShortReelVideo(post);
  5363. }
  5364. if (hideReason === '' && VARS.Options.GF_BLOCKED_ENABLED) {
  5365. hideReason = gf_isBlockedText(post);
  5366. }
  5367. }
  5368.  
  5369. if (hideReason.length > 0) {
  5370. // -- increment hidden counter
  5371. VARS.echoCount++;
  5372. if (hideReason !== 'hidden') {
  5373. // -- post not yet hidden, hide it.
  5374. gvf_hidePost(post, hideReason)
  5375. }
  5376. }
  5377. else {
  5378. // -- not a hidden post
  5379. // -- reset hidden count
  5380. VARS.echoCount = 0;
  5381. // -- run pause animation (useful to hide those animated comments)
  5382. if (VARS.Options.GF_ANIMATED_GIFS) {
  5383. // console.info(log + 'pausing animations ...');
  5384. swatTheMosquitos(post);
  5385. }
  5386. // -- hide info boxes
  5387. if (VARS.hideAnInfoBox) {
  5388. scrubInfoBoxes(post);
  5389. }
  5390. // -- hide number of shares box
  5391. if (VARS.Options.GF_SHARES) {
  5392. gf_hideNumberOfShares(post);
  5393. }
  5394. }
  5395. }
  5396. // console.info(log+'<--- mopUpTheGroupsFeed() - single group');
  5397. }
  5398. }
  5399. }
  5400.  
  5401. function mopUpTheWatchVideosFeed() {
  5402. // -- mopping up the watch videos feed page
  5403.  
  5404. let query;
  5405. let queryBlocks;
  5406. if (VARS.vfType === 'videos') {
  5407. // -- normal feed
  5408. query = 'div[id="watch_feed"] > div > div > div > div > div > div';
  5409. //queryBlocks = ':scope > div > div > div > div > div:nth-of-type(2) > div';
  5410. queryBlocks = ':scope > div > div > div > div > div > div:nth-of-type(2) > div';
  5411. }
  5412. else if (VARS.vfType === 'search') {
  5413. // -- videos --> search
  5414. query = 'div[role="feed"] > div[role="article"]';
  5415. queryBlocks = ':scope > div > div > div > div > div > div > div:nth-of-type(2)';
  5416. }
  5417. else if (VARS.vfType === 'item') {
  5418. // -- videos --> search --> item (videos being listed below the video of interest)
  5419. // -- video - via link
  5420. query = 'div[id="watch_feed"] > div > div:nth-of-type(2) > div > div > div > div:nth-of-type(2) > div > div > div';
  5421. queryBlocks = ':scope > div > div > div > div > div:nth-of-type(2) > div';
  5422. }
  5423. else {
  5424. return;
  5425. }
  5426.  
  5427. if (VARS.vfType !== 'search') {
  5428. const posts = document.querySelectorAll(query);
  5429. for (const post of posts) {
  5430. if (post.innerHTML.length === 0) {
  5431. continue;
  5432. }
  5433.  
  5434. let hideReason = '';
  5435.  
  5436. if (post.hasAttribute(postAtt)) {
  5437. // -- already hidden
  5438. hideReason = 'hidden';
  5439. }
  5440. else if ((post[postPropDS] !== undefined) && (parseInt(post[postPropDS]) >= VARS.scanCountMaxLoop)) {
  5441. // -- skip these - already been scanned a few times
  5442. // console.info(log + 'video; skipping;', post[postPropDS], VARS.scanCountMaxLoop, post);
  5443. }
  5444. else {
  5445. doLightDusting(post);
  5446.  
  5447. if (hideReason === '' && VARS.Options.VF_SPONSORED && isSponsored(post)) {
  5448. hideReason = KeyWords.SPONSORED[VARS.language];
  5449. }
  5450. if (hideReason === '' && VARS.Options.VF_LIVE) {
  5451. hideReason = vf_isVideoLive(post);
  5452. }
  5453. if (hideReason === '' && VARS.Options.VF_BLOCKED_ENABLED) {
  5454. hideReason = vf_isBlockedText(post, queryBlocks);
  5455. }
  5456. // console.info(log + 'mopUpTheWatchVideosFeed(); ::: hideReason:', hideReason, post, queryBlocks);
  5457. }
  5458.  
  5459. if (hideReason.length > 0) {
  5460. // -- increment hidden count
  5461. VARS.echoCount++;
  5462. if (hideReason !== 'hidden') {
  5463. // -- post not yet hidden, hide it.
  5464. gvf_hidePost(post, hideReason);
  5465. }
  5466. }
  5467. else {
  5468. // -- not a hidden post
  5469. // -- reset hidden count
  5470. VARS.echoCount = 0;
  5471. // -- run pause animation (useful to hide those animated comments)
  5472. if (VARS.Options.VF_ANIMATED_GIFS) {
  5473. // console.info(log + 'pausing animations ...');
  5474. swatTheMosquitos(post);
  5475. }
  5476. // -- hide info boxes
  5477. if (VARS.hideAnInfoBox) {
  5478. scrubInfoBoxes(post);
  5479. }
  5480. // -- nb: videos do not have a number of shares box
  5481.  
  5482. // -- hide sponsored blocks (appears between video & comments)
  5483. vf_scrubSponsoredBlock(post);
  5484. }
  5485. }
  5486. }
  5487. else {
  5488. // -- search videos
  5489. // -- structure is different from regular video feed
  5490. // -- thumbnail on left, text on right
  5491. const posts = document.querySelectorAll(query);
  5492. for (const post of posts) {
  5493.  
  5494. let hideReason = '';
  5495.  
  5496. if (post.hasAttribute(postAtt)) {
  5497. // -- already hidden
  5498. hideReason = 'hidden';
  5499. }
  5500. else {
  5501. if (VARS.Options.VF_BLOCKED_ENABLED) {
  5502. hideReason = vf_isBlockedText(post, queryBlocks);
  5503. }
  5504. }
  5505.  
  5506. if (hideReason.length > 0) {
  5507. // -- increment hidden count
  5508. VARS.echoCount++;
  5509. if (hideReason !== 'hidden') {
  5510. // -- post not yet hidden, hide it.
  5511. gvf_hidePost(post, hideReason);
  5512. }
  5513. }
  5514. else {
  5515. // -- not a hidden post
  5516. // -- reset hidden count
  5517. VARS.echoCount = 0;
  5518. }
  5519. }
  5520. }
  5521. }
  5522.  
  5523. function mp_hideBox(box, reason) {
  5524. box.setAttribute(VARS.cssHideEl, '');
  5525. box.setAttribute(postAtt, sanitizeReason(reason));
  5526. if (VARS.Options.VERBOSITY_DEBUG) {
  5527. box.setAttribute(VARS.cssShow, '');
  5528. }
  5529. }
  5530.  
  5531. function mp_stopTrackingDirtIntoMyHouse() {
  5532. // -- remove tracking bits
  5533. const collectionOfLinks = document.querySelectorAll('a[href*="/?ref="]');
  5534. for (const trackingLink of collectionOfLinks) {
  5535. trackingLink.href = trackingLink.href.split('/?ref')[0];
  5536. }
  5537. }
  5538.  
  5539. function mopUpTheMarketplaceFeed() {
  5540. // -- mopping up parts of the Marketplace ...
  5541.  
  5542. mp_stopTrackingDirtIntoMyHouse();
  5543.  
  5544. // console.info(log + 'mopUpTheMarketplaceFeed(); mpType:', VARS.mpType);
  5545.  
  5546. if (VARS.mpType === 'marketplace' || VARS.mpType === 'item') {
  5547. // - standard marketplace page
  5548. // - on the item page, there's listing of items to sell ... (similar structure to standard marketplace page)
  5549. // -- "sponsored" is _not_ obfuscated;
  5550. // -- nb: adguard base filter hides the label, but not the item/product ...
  5551. const queryHeadings = `div:not([${postAtt}]) > a[href="/ads/about/?entry_product=ad_preferences"]`;
  5552. const headings = document.querySelectorAll(queryHeadings);
  5553.  
  5554. let queryItems = `div[style]:not([${postAtt}]) > span > div:first-of-type > a:not([href*="marketplace"])`;
  5555. let items = document.querySelectorAll(queryItems);
  5556. if (items.length === 0) {
  5557. // -- structure changed in Nov 2023.
  5558. queryItems = `div[style]:not([${postAtt}]) > span > div:first-of-type > div > a:not([href*="marketplace"])`;
  5559. items = document.querySelectorAll(queryItems);
  5560. }
  5561.  
  5562. // console.info(log+'marketplace(); headings:', headings);
  5563. // console.info(log+'marketplace(); items:', items);
  5564.  
  5565. if (VARS.Options.MP_SPONSORED && (headings.length > 0) && (items.length > 0)) {
  5566. for (const heading of headings) {
  5567. // heading = heading.parentElement;
  5568. mp_hideBox(heading.parentElement, KeyWords.SPONSORED[VARS.language]);
  5569. }
  5570. for (const item of items) {
  5571. // const parentItem = climbUpTheTree(item, 3);
  5572. const parentItem = climbUpTheTree(item, 4);
  5573. mp_hideBox(parentItem, KeyWords.SPONSORED[VARS.language]);
  5574. }
  5575. }
  5576. if (VARS.Options.MP_BLOCKED_ENABLED) {
  5577. mp_doBlockingByBlockedText();
  5578. }
  5579. }
  5580. if (VARS.mpType === 'item') {
  5581. // -- viewing a marketplace item - a small sponsored box often shows up on the right.
  5582. if (VARS.Options.MP_SPONSORED) {
  5583. const elDialog = document.querySelector('div[role="dialog"]');
  5584. if (elDialog) {
  5585. // -- viewing a mp item via mp feed (no new tab or reloaded page)
  5586. let queryPattern = 'a[href*="/ads/"]';
  5587. let element = elDialog.querySelector(queryPattern);
  5588. if (element) {
  5589. if (element.hasAttribute(postAtt)) {
  5590. return;
  5591. }
  5592. // console.info(`${log}MPItem() - element:`, queryPattern1, element);
  5593. if (element.closest('div[data-pagelet^="BrowseFeedUpsell"]') === null) {
  5594. // -- found the sponsored box inside the mp item box.
  5595. // -- mp item do not have a parent element having data-pagelet attribute.
  5596. let elParent = element.parentElement.closest('h2');
  5597. if (elParent) {
  5598. elParent = elParent.closest('span');
  5599. mp_hideBox(elParent, KeyWords.SPONSORED[VARS.language]);
  5600. element.setAttribute(postAtt, KeyWords.SPONSORED[VARS.language]);
  5601. }
  5602. }
  5603. }
  5604. else {
  5605. // -- structure change, Nov 2023
  5606. // -- multiple sponsored items (slider style)
  5607. queryPattern = 'a[href*="//l.facebook.com/l.php?u="]';
  5608. const elements = elDialog.querySelectorAll(queryPattern);
  5609. if (elements.length > 0) {
  5610. element = elements[0];
  5611. if (element.hasAttribute(postAttChildFlag)) {
  5612. return;
  5613. }
  5614. if (element.href.length > 500) {
  5615. const elParent = climbUpTheTree(element, 9);
  5616. mp_hideBox(elParent, KeyWords.SPONSORED[VARS.language]);
  5617. element.setAttribute(postAttChildFlag, KeyWords.SPONSORED[VARS.language]);
  5618. }
  5619. }
  5620. }
  5621. }
  5622. else {
  5623. // -- viewing a mp item in a new tab / reloaded page.
  5624. let queryPattern = 'a[href*="/ads/"]';
  5625. let element = document.querySelector(queryPattern);
  5626. if (element) {
  5627. if (!element.hasAttribute(postAtt)) {
  5628. // -- found the sponsored box inside the mp item box.
  5629. let elParent = element.parentElement.closest('h2');
  5630. if (elParent) {
  5631. elParent = elParent.closest('span');
  5632. mp_hideBox(elParent, KeyWords.SPONSORED[VARS.language]);
  5633. element.setAttribute(postAtt, KeyWords.SPONSORED[VARS.language]);
  5634. }
  5635. }
  5636. }
  5637. else {
  5638. // -- structure change, Nov 2023
  5639. // -- multiple sponsored items (slider style)
  5640. queryPattern = 'a[href*="//l.facebook.com/l.php?u="]';
  5641. const elements = document.querySelectorAll(queryPattern);
  5642. if (elements.length > 0) {
  5643. element = elements[0];
  5644. if (element.hasAttribute(postAttChildFlag)) {
  5645. return;
  5646. }
  5647. if (element.href.length > 500) {
  5648. const elParent = climbUpTheTree(element, 9);
  5649. mp_hideBox(elParent, KeyWords.SPONSORED[VARS.language]);
  5650. element.setAttribute(postAttChildFlag, KeyWords.SPONSORED[VARS.language]);
  5651. }
  5652. }
  5653. }
  5654. }
  5655. }
  5656. }
  5657. else if ((VARS.mpType === 'category') || (VARS.mpType === 'search')) {
  5658. // - viewing a markplace category or marketplace search results
  5659. // - (both have similar layout)
  5660. if (VARS.Options.MP_SPONSORED) {
  5661. const query = `a[href*="/ads/"]:not([${postAtt}])`;
  5662. const elements = document.querySelectorAll(query);
  5663. for (const element of elements) {
  5664. // console.info(log + 'mp-clean:', element);
  5665. element.setAttribute(postAtt, element.innerHTML.length);
  5666. const itemBox = climbUpTheTree(element.parentElement.closest('a'), 3);
  5667. mp_hideBox(itemBox, KeyWords.SPONSORED[VARS.language]);
  5668. }
  5669. }
  5670. if (VARS.Options.MP_BLOCKED_ENABLED) {
  5671. mp_doBlockingByBlockedText();
  5672. }
  5673. }
  5674. }
  5675.  
  5676. function mopUpTheSearchFeed() {
  5677. // mopping up the search feed / results
  5678. // -- (nb: has similar layout to news feed stream)
  5679. // -- "borrow" news feed's text filter.
  5680. if (VARS.Options.NF_BLOCKED_ENABLED) {
  5681. const query = 'div[role="feed"] > div';
  5682. const posts = Array.from(document.querySelectorAll(query));
  5683.  
  5684. for (const post of posts) {
  5685.  
  5686. if (post.innerHTML.length === 0) {
  5687. continue;
  5688. }
  5689.  
  5690. let hideReason = '';
  5691.  
  5692. if (post.hasAttribute(postAtt)) {
  5693. hideReason = 'hidden';
  5694. }
  5695. else {
  5696. if (VARS.Options.NF_SPONSORED && isSponsored(post)) {
  5697. hideReason = KeyWords.SPONSORED[VARS.language];
  5698. }
  5699. if (hideReason === '' && VARS.NF_BLOCKED_ENABLED) {
  5700. hideReason = nf_isBlockedText(post);
  5701. }
  5702. }
  5703.  
  5704. if (hideReason.length > 0) {
  5705. // -- increment hidden count
  5706. VARS.echoCount++;
  5707. if (hideReason !== 'hidden') {
  5708. // -- post not yet hidden, hide it.
  5709. nf_hidePost(post, hideReason);
  5710. }
  5711. }
  5712. else {
  5713. // -- not a hidden post
  5714. // -- reset hidden count
  5715. VARS.echoCount = 0;
  5716. // -- run pause animation (useful to hide those animated comments)
  5717. if (VARS.Options.NF_ANIMATED_GIFS) {
  5718. // console.info(log + 'pausing animations ...');
  5719. swatTheMosquitos(post);
  5720. }
  5721. // -- hide info boxes
  5722. if (VARS.hideAnInfoBox) {
  5723. scrubInfoBoxes(post);
  5724. }
  5725. }
  5726. }
  5727. }
  5728. }
  5729.  
  5730. function mopUpTheReelFeed(caller) {
  5731.  
  5732. // -- Reels ...
  5733.  
  5734. // -- saveUserOptions will also call this function ...
  5735.  
  5736. // -- nb: videos from the watch videos feed are also collected in the query ..
  5737. // -- .. but they do not have a container with [data-video-id] attribute
  5738. // -- .. and therefore skipped ...
  5739.  
  5740. // -- nb: setting VARS.isRF determines if this function is called or not.
  5741.  
  5742. // console.info(log + 'mopUpTheReelFeed(); ', VARS.isRF, VARS.isRF_InTimeoutMode);
  5743.  
  5744. if (!VARS.isRF) {
  5745. // -- no longer in Reels Feed
  5746. VARS.isRF_InTimeoutMode = false;
  5747. return;
  5748. }
  5749. if (caller !== 'self' && VARS.isRF_InTimeoutMode === true) {
  5750. // -- either bodyMutation or saveUserOptions is calling and TimeoutMode is currently active.
  5751. return;
  5752. }
  5753.  
  5754. const videoRules = `[data-video-id] video:not([${rvAtt}])`;
  5755. const videos = document.querySelectorAll(videoRules);
  5756.  
  5757. // console.info(log+'mopUpTheReelFeed(); videos:', caller, videos);
  5758.  
  5759. for (const video of videos) {
  5760. // -- get the video's container's child element
  5761. const elVideoId = video.closest('[data-video-id]');
  5762. if (elVideoId) {
  5763. // -- get the video's container
  5764. const videoContainer = elVideoId.parentElement;
  5765. if (videoContainer) {
  5766. if (VARS.Options.REELS_CONTROLS === true) {
  5767. // -- get the video's description + audio track info container.
  5768. const descriptionOverlay = videoContainer.nextElementSibling;
  5769. if (descriptionOverlay) {
  5770. // -- make room to display the controls by moving the description element up a bit ...
  5771. const elDescriptionContainer = descriptionOverlay.children[0];
  5772. elDescriptionContainer.setAttribute('style', `margin-bottom:${VARS.isChromium ? '4.5' : '2.25'}rem;`);
  5773. // -- enable controls on the video
  5774. video.setAttribute('controls', 'true');
  5775. // -- hide the video's sibling (makes it easier to click on the video's controls)
  5776. const sibling = video.nextElementSibling;
  5777. if (sibling) {
  5778. sibling.setAttribute('style', 'display:none;');
  5779. }
  5780. }
  5781. }
  5782. if (VARS.Options.REELS_DISABLE_LOOPING === true) {
  5783. // -- stop the video
  5784. video.addEventListener('ended', function (ev) {
  5785. ev.target.pause();
  5786. });
  5787. }
  5788. video.setAttribute(rvAtt, '1');
  5789. }
  5790. else {
  5791. // -- a video from the watch videos feed
  5792. // -- hidden underneath the reels feed overlay)
  5793. }
  5794. }
  5795. }
  5796. // -- call me again in a few ms ...
  5797. VARS.isRF_InTimeoutMode = true;
  5798. setTimeout(function () {
  5799. mopUpTheReelFeed('self')
  5800. }, 1000);
  5801. }
  5802.  
  5803.  
  5804. // ** Mutations processor
  5805. function bodyMutating(mutations) {
  5806. for (const mutation of mutations) {
  5807. if ((mutation.type === 'childList') && (mutation.addedNodes.length > 0)) {
  5808. if (VARS.prevURL !== window.location.href) {
  5809. // - page url has changed ... refresh the bodyObserver.
  5810. runMO();
  5811. }
  5812. else if (VARS.isAF) {
  5813. // -- isAF := any feed
  5814. // -- nb: don't bother with looping through mutation.addedNodes.length - 99.5% of the time there's only one ...
  5815. const mnode = mutation.addedNodes[0]; // placed here for error trapping block
  5816. // try {
  5817. VARS.mutationsCount++;
  5818. // if (VARS.mutationsCount % 100 === 0) {
  5819. // console.info(log+'bodyMutating(); mutationsCount:', VARS.mutationsCount);
  5820. // }
  5821. if (VARS.mutationsCount > VARS.mutationsInitSkip) {
  5822. if (['A', 'DIV', 'IMG', 'SPAN', '#text', 'svg', 'VIDEO'].indexOf(mnode.nodeName) > 0) {
  5823. if ((mnode.nodeType === Node.ELEMENT_NODE) && ((mnode.innerHTML.length < 129) || (mnode.textContent.length === 0))) {
  5824. // - skip these ...
  5825. }
  5826. else {
  5827. // console.info(log + 'bodyMutating();', VARS.isNF, VARS.isGF, VARS.isVF, VARS.isMF, VARS.isSF);
  5828. if (VARS.isNF) {
  5829. mopUpTheNewsFeed();
  5830. }
  5831. else if (VARS.isGF) {
  5832. mopUpTheGroupsFeed();
  5833. }
  5834. else if (VARS.isVF) {
  5835. mopUpTheWatchVideosFeed();
  5836. }
  5837. else if (VARS.isMF) {
  5838. mopUpTheMarketplaceFeed();
  5839. }
  5840. else if (VARS.isSF) {
  5841. mopUpTheSearchFeed();
  5842. }
  5843. else if (VARS.isRF) {
  5844. mopUpTheReelFeed('mutations');
  5845. }
  5846. }
  5847. }
  5848. }
  5849. }
  5850. }
  5851. }
  5852. };
  5853.  
  5854. // ** Mutation Observer
  5855. let bodyObserver = new MutationObserver(bodyMutating);
  5856. // ** MO starter / restarter
  5857. let firstRun = true;
  5858.  
  5859. function runMO() {
  5860. // -- run code soon as the elements HEAD, BDDY and variable Options are ready/available.
  5861. // -- or when page url has changed ...
  5862. if (document.head && document.body && VARS.optionsReady) {
  5863. // console.info(log + 'runMO(); - ready ...');
  5864. if (firstRun) {
  5865. GM.registerMenuCommand(KeyWords.GM_MENU_SETTINGS[VARS.language], toggleDialog);
  5866. addCSS();
  5867. window.setTimeout(addExtraCSS, 150); // fb is sometimes laggy ...
  5868. buildMoppingDialog();
  5869. // -- for reels's controls - chromium browsers needs more spacing ...
  5870. // -- requires: @grant unsafeWindow
  5871. VARS.isChromium = !!unsafeWindow.chrome && /Chrome|CriOS/.test(navigator.userAgent);
  5872. // -- for nf_isReelsAndShortVideos() - needs dictionary rule
  5873. buildDictonaryForReelsAndShortVideos();
  5874. firstRun = false;
  5875. }
  5876. if (setFeedSettings()) {
  5877. // - clear out mutations not yet processed ...
  5878. let mutations = bodyObserver.takeRecords();
  5879. bodyObserver.disconnect();
  5880. // - and start up the osbserver again.
  5881. bodyObserver.observe(document.body, {
  5882. childList: true,
  5883. subtree: true,
  5884. attributes: false
  5885. });
  5886.  
  5887. if (VARS.isRF) {
  5888. // -- mutations might not call mopUpTheReelFeed() when the URL changes, so call it now.
  5889. mopUpTheReelFeed('setFeedSettings');
  5890. }
  5891. }
  5892. }
  5893. else {
  5894. // HEAD / BODY / Options not yet ready ...
  5895. // console.info(log + 'runMO(); - not yet ready ...');
  5896. setTimeout(runMO, 10);
  5897. }
  5898. }
  5899. runMO();
  5900.  
  5901. })();