FB - Clean my feeds (5.02)

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

  1. // ==UserScript==
  2. // @name FB - Clean my feeds (5.02)
  3. // @description Hide Sponsored and Suggested posts in FB's News Feed, Groups Feed, Watch Videos Feed and Marketplace Feed
  4. // @namespace https://greatest.deepsurf.us/users/812551
  5. // @supportURL https://github.com/zbluebugz/facebook-clean-my-feeds/issues
  6. // @version 5.02
  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.  
  29. v5.02 :: November 2024
  30. Updated News Feed Sponsored detection rules
  31.  
  32. v5.01 :: October 2024
  33. Changed the detect-changes-engine component
  34. Updated dialog-box
  35. Users can change dialog-box's ui language
  36. Updated label for post has been hidden component
  37. Added Animated GIFs post detection component (News Feed + Groups Feed) (gif/mp4)
  38. Removed consecutive hidden posts facility from Watch Videos (FB keeps a few posts as you scroll)
  39. Added Duplicate Video detection component (Watch Videos feed)
  40. Added Instagram Video detection component (Watch Videos feed)
  41. Added code to remove "incomplete" Watch Video posts (posts with no content)
  42. Added icon to open a Watch Videos Feed post in a new window
  43. Updated News Feed's detection rule
  44. Updated Group Feed's dection rule
  45. Updated Marketplace detection rules
  46. Updated Search posts detection rule
  47. Updated News Feed's Sponsored posts detection rule
  48. Added option to Disable looping videos in Reels
  49. Fix bug with showing/hiding the FB-CMF button
  50. Updated nf_isSuggested filter rules
  51. Updated nf_isPeopleYouMayKnow filter rule
  52. Added RegExp option to text filters
  53. Code tweaks
  54.  
  55. v4.31 :: June 2024
  56. Reels and Videos - added extra detection rule (dictionary base)
  57. Survey - updated detection rule
  58. Reels - option to stop looping
  59.  
  60. v4.30 :: March 2024
  61. Hot fix
  62. Updated Marketplace feed detection component
  63.  
  64. v4.29 :: February 2024
  65. !!! Hot fix !!!
  66. Issues with FB, Adblockers and FB-CMF - all clashing
  67. Adjusted News Feed's query rules
  68. Temporarily disabled News Feed's message/notification tab (will be restored in next version)
  69.  
  70. v4.28 :: January 2024
  71. Enabled option to toggle Sponsored post detection rule (for uBO compatibility)
  72. Added Video's "Live" detection rule
  73. Enabled Reels' video controls
  74. Added Ukrainian (Україна)
  75. Added Bulgarian (български)
  76. Dialog box - reworded "Miscellaneous items" to "Supplementary / information section"
  77. Dialog box - added "Reset" button to reset the options
  78. Fixed bug with Survey detection component
  79. Fixed bug with Importing settings from a file
  80. Revised message/notification tab for News feed
  81. Revised Create Stories detection rule
  82. Add option to filter posts by number of Likes
  83. Fixed bug with function scanTreeForText() - failing to detect "Anonymous participant"
  84. Updated Groups Feed filter rules - new HTML structure via (Feeds > Groups)
  85. Added display of script's version number to dialog box
  86.  
  87. v4.27 :: December 2023
  88. Added Russian (Русский) - supplied by github user Kenya-West
  89. v4.26 :: November 2023
  90. Added web.facebook.com to @match conditions
  91. Added Survey detection component (Home / News feed)
  92. Added Follow detection component (Home / News feed)
  93. Added Participate detection component (Home / News feed)
  94. Updated Marketplace detection rules
  95. v4.25 :: November 2023
  96. Added extra filter rule for nf_isSuggested() (for "Suggested for you" posts) - fix supplied by opello (via github)
  97. Added News Feed's Stories post detection rule.
  98. Revised function scanTreeForText() to include other elements for scanning
  99. Fixed bug with Marketplace prices' filter
  100. Reduce possible conflicts with uBlock Origin / other adblockers
  101. Code tweaks
  102. v4.24 :: September 2023
  103. Fixed issues with v4.23 (selection/detection rules)
  104. Code tweaks
  105. v4.23 :: August 2023
  106. Fixed bug with showing Marketplace's hidden items
  107. Updated Marketplace detection rules
  108. Split Marketplace's text filter into two - prices and description
  109. Merged "Stories" with "Stories | Reels | Rooms" detection rules.
  110. Fixed bug with CMF's hidden dialog box's text being included in CTRL+F search (now excluded)
  111. Dropped "Create room" detection component (no longer listed in FB)
  112. v4.22 :: July 2023
  113. Updated News Feed posts selection rule (FB changed structure)
  114. Updated Events you may like detection rule
  115. v4.21 :: June 2023
  116. Updated news feed detection rules - for older HTML structures
  117. Updated watch videos feed detection rules
  118. Added Greek (Ελληνικά)
  119. Updated various functions
  120. v4.20 :: May 2023
  121. Added "Feeds (most recent)" to the clean up rules (FB recently introduced the "Feeds (most recent)" feature)
  122. Updated Search Feed sponsored posts rule
  123. v4.19 :: May 2023
  124. Updated News Feed posts selection rule (FB changed structure)
  125. v4.18 :: May 2023
  126. Updated News Feed sponsored posts rule
  127. Added News Feed sponsored video posts rule
  128. Updated News Feed suggested posts rule
  129. v4.17 :: March 2023
  130. Fixed issue with GreaseMonkey & FireMonkey not able to run userscript
  131. Updated News Feed sponsored posts rule
  132. Updated Videos Feed sponsored posts rule
  133. Added option to hide "# shares" on posts (news + groups)
  134. v4.16 :: February 2023
  135. Fixed issue with <no message> setting breaking FB
  136. Code tweaks
  137. v4.15 :: February 2023
  138. Updated News Feed sponsored posts rule (FB changed structure)
  139. Updated Marketplace Feed > Item page posts rules
  140. Code tweaks
  141. v4.14 :: January 2023
  142. Updated News Feed Suggestion/Recommendation posts rule (FB changed structure)
  143. 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.
  144. Groups Feed posts - added icon to open post in new window (fix annoying FB bug with not showing comments properly)
  145.  
  146. Attribution: Mop & Bucket icon:
  147. - made by Freepik (https://www.freepik.com) @ flaticon (https://www.flaticon.com/)
  148. - page: https://www.flaticon.com/premium-icon/mop_2383747
  149.  
  150.  
  151. Instructions on how to use:
  152. - In FB, top right corner or bottom left corner, click on the "Clean my feeds" icon (mop + bucket)
  153. - Alternatively, click on the script manager icon in the menu bar and select "Settings" under FB - Clean my feeds
  154. - Toggle the various options
  155. - Click Save then Close.
  156. - It is recommended that you Export your settings every now and then.
  157. (When your browser flushes the cache, your settings are deleted).
  158.  
  159. Known issue(s):
  160. - Settings are not saved in Private/Incognito mode when using Firefox.
  161. - For Chrome/Edge, in Private/Incognito mode, settings are retained until browser is closed.
  162.  
  163. \\\ --- No need to amend any of the code below --- ///
  164. */
  165.  
  166. // -- need version 8 for async/await
  167. esversion: 8;
  168.  
  169. (async function () {
  170.  
  171. 'use strict';
  172.  
  173. // -- TM doesn't like spacesin version number, so convert to human-readable-format.
  174. const SCRIPT_VERSION = 'v' + GM.info.script.version.replaceAll('-', ' ');
  175.  
  176. // Due to a GreaseMonkey bug with @require, we've copied an external script into here.
  177. // @require https://unpkg.com/idb-keyval@6.0.3/dist/umd.js
  178. 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 }) }));
  179.  
  180. // *** *** Language components *** ***
  181. const masterKeyWords = {
  182.  
  183. translations: {
  184. // - NF_ = News Feed
  185. // - GF_ = Groups Feed
  186. // - VF_ = Videos Feed
  187. // - MP_ = Marketplace Feed
  188. // - PP_ = Person/Page Profile
  189. // - DLG_ = CMF's Dialog box
  190. // - CMF_ = CMF's Dialog box
  191. // - GM_ = Userscript manager
  192.  
  193. // -- English
  194. en: {
  195. LANGUAGE_DIRECTION: 'ltr',
  196. SPONSORED: 'Sponsored',
  197. NF_TABLIST_STORIES_REELS_ROOMS: '"Stories | Reels | Rooms" tabs list box',
  198. NF_STORIES: 'Stories',
  199. NF_SURVEY: 'Survey',
  200. NF_PEOPLE_YOU_MAY_KNOW: 'People you may know',
  201. NF_PAID_PARTNERSHIP: 'Paid partnership',
  202. NF_SPONSORED_PAID: 'Sponsored · Paid for by ______',
  203. NF_SUGGESTIONS: 'Suggestions / Recommendations',
  204. NF_FOLLOW: 'Follow',
  205. NF_PARTICIPATE: 'Participate',
  206. NF_REELS_SHORT_VIDEOS: 'Reels and short videos',
  207. NF_SHORT_REEL_VIDEO: 'Reel/short video',
  208. NF_EVENTS_YOU_MAY_LIKE: 'Events you may like',
  209. NF_ANIMATED_GIFS_POSTS: 'Animated GIFs',
  210. NF_ANIMATED_GIFS_PAUSE: 'Pause animated GIFs',
  211. NF_SHARES: '# shares',
  212. NF_LIKES_MAXIMUM: 'Maximum number of Likes',
  213. GF_PAID_PARTNERSHIP: 'Paid partnership',
  214. GF_SUGGESTIONS: 'Suggestions / Recommendations',
  215. GF_SHORT_REEL_VIDEO: 'Reel/short video',
  216. GF_ANIMATED_GIFS_POSTS: 'Animated GIFs',
  217. GF_ANIMATED_GIFS_PAUSE: 'Pause animated GIFs',
  218. GF_SHARES: '# shares',
  219. VF_LIVE: 'LIVE',
  220. VF_INSTAGRAM: 'Instagram',
  221. VF_DUPLICATE_VIDEOS: 'Duplicate video',
  222. VF_ANIMATED_GIFS_PAUSE: 'Pause animated GIFs',
  223. PP_ANIMATED_GIFS_POSTS: 'Animated GIFs',
  224. PP_ANIMATED_GIFS_PAUSE: 'Pause animated GIFs',
  225. NF_BLOCKED_FEED: ['News Feed', 'Groups Feed', 'Videos Feed'],
  226. GF_BLOCKED_FEED: ['News Feed', 'Groups Feed', 'Videos Feed'],
  227. VF_BLOCKED_FEED: ['News Feed', 'Groups Feed', 'Videos Feed'],
  228. MP_BLOCKED_FEED: ['Marketplace Feed'],
  229. PP_BLOCKED_FEED: ['Profile page'],
  230. OTHER_INFO_BOX_CORONAVIRUS: 'Coronavirus (information box)',
  231. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Climate Science (information box)',
  232. OTHER_INFO_BOX_SUBSCRIBE: 'Subscribe (information box)',
  233. REELS_TITLE: 'Reels',
  234. REELS_CONTROLS: 'Show video controls',
  235. REELS_DISABLE_LOOPING: 'Disable looping',
  236. DLG_TITLE: 'Clean my feeds',
  237. DLG_NF: 'News Feed',
  238. DLG_GF: 'Groups Feed',
  239. DLG_VF: 'Videos Feed',
  240. DLG_MP: 'Marketplace Feed',
  241. DLG_PP: 'Profile / Page',
  242. DLG_OTHER: 'Supplementary / information section',
  243. DLG_BLOCK_TEXT_FILTER_TITLE: 'Text filter',
  244. DLG_BLOCK_NEW_LINE: '(Separate words or phrases with a line break, Regular Expressions are supported)',
  245. NF_BLOCKED_ENABLED: 'Enabled',
  246. GF_BLOCKED_ENABLED: 'Enabled',
  247. VF_BLOCKED_ENABLED: 'Enabled',
  248. MP_BLOCKED_ENABLED: 'Enabled',
  249. PP_BLOCKED_ENABLED: 'Enabled',
  250. NF_BLOCKED_RE: 'Regular Expressions (RegExp)',
  251. GF_BLOCKED_RE: 'Regular Expressions (RegExp)',
  252. VF_BLOCKED_RE: 'Regular Expressions (RegExp)',
  253. MP_BLOCKED_RE: 'Regular Expressions (RegExp)',
  254. PP_BLOCKED_RE: 'Regular Expressions (RegExp)',
  255. DLG_VERBOSITY: 'Options for Hidden Posts',
  256. DLG_VERBOSITY_CAPTION: 'Show a label if a post is hidden',
  257. VERBOSITY_MESSAGE: ['no label', 'Post hidden. Rule: ', ' posts hidden', '7 posts hidden ~ (Groups Feed only)'],
  258. VERBOSITY_MESSAGE_COLOUR: 'Text colour',
  259. VERBOSITY_MESSAGE_BG_COLOUR: 'Background colour',
  260. VERBOSITY_DEBUG: 'Highlight "hidden" posts',
  261. CMF_CUSTOMISATIONS: 'Customisations',
  262. CMF_BTN_LOCATION: 'Location of Clean my feeds\' button',
  263. CMF_BTN_OPTION: ['bottom left', 'top right', 'disabled (use "Settings" in User Script Commands menu")'],
  264. CMF_DIALOG_LANGUAGE_LABEL: 'Clean my feeds\' dialog-box language',
  265. CMF_DIALOG_LANGUAGE: 'English',
  266. CMF_DIALOG_LANGUAGE_DEFAULT: 'Use site language',
  267. GM_MENU_SETTINGS: 'Settings',
  268. CMF_DIALOG_LOCATION: 'Location of Clean my feeds\' dialog box',
  269. CMF_DIALOG_OPTION: ['left side', 'right side'],
  270. CMF_BORDER_COLOUR: 'Border colour',
  271. DLG_TIPS: 'Tips',
  272. DLG_TIPS_CONTENT: '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.',
  273. DLG_BUTTONS: ['Save', 'Close', 'Export', 'Import', 'Reset'],
  274. DLG_FB_COLOUR_HINT: 'Leave blank to use FB\'s colour scheme',
  275. },
  276. // -- العربية (Arabic)
  277. ar: {
  278. LANGUAGE_DIRECTION: 'rtl',
  279. SPONSORED: 'مُموَّل',
  280. NF_TABLIST_STORIES_REELS_ROOMS: '"القصص | ريلز | الغرف" مربع قائمة علامات تبويب',
  281. NF_STORIES: 'القصص',
  282. NF_SURVEY: 'استبيان',
  283. NF_PEOPLE_YOU_MAY_KNOW: 'أشخاص قد تعرفهم',
  284. NF_PAID_PARTNERSHIP: 'شراكة مدفوعة',
  285. NF_SPONSORED_PAID: 'برعاية · مدفوعة بواسطة ______',
  286. NF_SUGGESTIONS: 'الاقتراحات / التوصيات',
  287. NF_FOLLOW: 'تابع',
  288. NF_PARTICIPATE: 'المشاركة',
  289. NF_REELS_SHORT_VIDEOS: 'ريلز ومقاطع الفيديو القصيرة',
  290. NF_SHORT_REEL_VIDEO: 'بكرة / فيديو قصير',
  291. NF_EVENTS_YOU_MAY_LIKE: 'أحداث قد تعجبك',
  292. NF_ANIMATED_GIFS_POSTS: 'صور GIF المتحركة',
  293. NF_ANIMATED_GIFS_PAUSE: 'وقفة ملفات GIF المتحركة',
  294. NF_SHARES: '# مشاركات',
  295. NF_LIKES_MAXIMUM: 'الحد الأقصى لعدد الإعجابات',
  296. GF_PAID_PARTNERSHIP: 'شراكة مدفوعة',
  297. GF_SUGGESTIONS: 'الاقتراحات / التوصيات',
  298. GF_SHORT_REEL_VIDEO: 'بكرة / فيديو قصير',
  299. GF_ANIMATED_GIFS_POSTS: 'صور GIF المتحركة',
  300. GF_ANIMATED_GIFS_PAUSE: 'وقفة ملفات GIF المتحركة',
  301. GF_SHARES: '# مشاركات',
  302. VF_LIVE: 'مباشر',
  303. VF_INSTAGRAM: 'Instagram',
  304. VF_DUPLICATE_VIDEOS: 'فيديو مكرر',
  305. VF_ANIMATED_GIFS_PAUSE: 'وقفة ملفات GIF المتحركة',
  306. PP_ANIMATED_GIFS_POSTS: 'صور GIF المتحركة',
  307. PP_ANIMATED_GIFS_PAUSE: 'وقفة ملفات GIF المتحركة',
  308. NF_BLOCKED_FEED: ['موجز الأخبار', 'تغذية المجموعات', 'تغذية الفيديو'],
  309. GF_BLOCKED_FEED: ['موجز الأخبار', 'تغذية المجموعات', 'تغذية الفيديو'],
  310. VF_BLOCKED_FEED: ['موجز الأخبار', 'تغذية المجموعات', 'تغذية الفيديو'],
  311. MP_BLOCKED_FEED: ['السوق تغذية'],
  312. PP_BLOCKED_FEED: '',
  313. OTHER_INFO_BOX_CORONAVIRUS: 'فيروس كورونا (صندوق المعلومات)',
  314. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'علوم المناخ (صندوق المعلومات)',
  315. OTHER_INFO_BOX_SUBSCRIBE: '(صندوق المعلومات) الاشتراك',
  316. REELS_TITLE: 'ريلز', // -- FB's label
  317. REELS_CONTROLS: 'عرض أدوات التحكم في الفيديو',
  318. REELS_DISABLE_LOOPING: 'تعطيل التكرار',
  319. DLG_TITLE: 'تنظيف خلاصاتي',
  320. DLG_NF: 'الأخبار تغذية',
  321. DLG_GF: 'مجموعات تغذية',
  322. DLG_VF: 'الفيديو تغذية',
  323. DLG_MP: 'السوق تغذية',
  324. DLG_PP: 'الملف الشخصي / الصفحة',
  325. DLG_OTHER: 'قسم التكميلي / المعلومات',
  326. DLG_BLOCK_TEXT_FILTER_TITLE: 'مرشح النص',
  327. DLG_BLOCK_NEW_LINE: '(افصل الكلمات أو العبارات بفاصل سطر، يتم دعم التعبيرات العادية)',
  328. NF_BLOCKED_ENABLED: 'تمكين',
  329. GF_BLOCKED_ENABLED: 'تمكين',
  330. VF_BLOCKED_ENABLED: 'تمكين',
  331. MP_BLOCKED_ENABLED: 'تمكين',
  332. PP_BLOCKED_ENABLED: 'تمكين',
  333. NF_BLOCKED_RE: 'التعبيرات العادية (RegExp)',
  334. GF_BLOCKED_RE: 'التعبيرات العادية (RegExp)',
  335. VF_BLOCKED_RE: 'التعبيرات العادية (RegExp)',
  336. MP_BLOCKED_RE: 'التعبيرات العادية (RegExp)',
  337. PP_BLOCKED_RE: 'التعبيرات العادية (RegExp)',
  338. DLG_VERBOSITY: 'خيارات المشاركات المخفية',
  339. DLG_VERBOSITY_CAPTION: 'إظهار إشعار بعرض المقالات المخفية',
  340. VERBOSITY_MESSAGE: ['لا تسمية', 'مشاركة واحدة مخفية. حكم: ', ' المشاركات المخفية', '7 مشاركات مخفية ~ (فقط في تغذية المجموعات)'],
  341. VERBOSITY_MESSAGE_COLOUR: 'لون النص',
  342. VERBOSITY_MESSAGE_BG_COLOUR: 'لون الخلفية',
  343. VERBOSITY_DEBUG: 'تسليط الضوء على المشاركات "المخفية"',
  344. CMF_CUSTOMISATIONS: 'التخصيصات',
  345. CMF_BTN_LOCATION: 'موقع الزر "تنظيف خلاصاتي"',
  346. CMF_BTN_OPTION: ['أسفل اليسار', 'أعلى اليمين', 'معطل (استخدم "الإعدادات" في قائمة أوامر البرنامج النصي للمستخدم)'],
  347. CMF_DIALOG_LANGUAGE_LABEL: 'نظف لغة صندوق حوار تغذيتي',
  348. CMF_DIALOG_LANGUAGE: 'العربية',
  349. CMF_DIALOG_LANGUAGE_DEFAULT: 'استخدم لغة الموقع',
  350. GM_MENU_SETTINGS: 'الإعدادات',
  351. CMF_DIALOG_LOCATION: 'موقع مربع الحوار "تنظيف موجز ويباتي"',
  352. CMF_DIALOG_OPTION: ['الجهه اليسرى', 'الجانب الصحيح'],
  353. CMF_BORDER_COLOUR: 'لون الحدود',
  354. DLG_TIPS: 'تلميحات',
  355. DLG_TIPS_CONTENT: 'سيؤدي مسح ذاكرة التخزين المؤقت للمتصفح إلى إعادة تعيين الإعدادات إلى قيمها الافتراضية.\n\nاستخدم الزرين "تصدير" و "استيراد" للنسخ الاحتياطي واستعادة الإعدادات المخصصة.',
  356. DLG_BUTTONS: ['حفظ', 'قريب', 'يصدّر', 'يستورد', 'إعادة تعيين'],
  357. DLG_FB_COLOUR_HINT: 'اتركه فارغًا لاستخدام نظام ألوان FB',
  358. },
  359. // -- България (Bulgaria)
  360. bg: {
  361. LANGUAGE_DIRECTION: 'ltr',
  362. SPONSORED: 'Спонсорирано',
  363. NF_TABLIST_STORIES_REELS_ROOMS: 'Списъчно поле на раздела „Истории | Макари | Стаи“',
  364. NF_STORIES: 'Истории',
  365. NF_SURVEY: 'Анкета',
  366. NF_PEOPLE_YOU_MAY_KNOW: 'Хора, които може би познавате',
  367. NF_PAID_PARTNERSHIP: 'Платено партньорство',
  368. NF_SPONSORED_PAID: 'Спонсорирано · Платено от ______',
  369. NF_SUGGESTIONS: 'Предложения / Препоръки',
  370. NF_FOLLOW: 'Следвай',
  371. NF_PARTICIPATE: 'Участвай',
  372. NF_REELS_SHORT_VIDEOS: 'Ленти и кратки видеоклипове',
  373. NF_SHORT_REEL_VIDEO: 'Рил/късо видео',
  374. NF_EVENTS_YOU_MAY_LIKE: 'Събития, които може да ви харесат',
  375. NF_ANIMATED_GIFS_POSTS: 'Анимирани GIF файлове',
  376. NF_ANIMATED_GIFS_PAUSE: 'Пауза на анимирани GIF файлове',
  377. NF_SHARES: '# споделяния',
  378. NF_LIKES_MAXIMUM: 'Максимален брой Харесвания',
  379. GF_PAID_PARTNERSHIP: 'Платено партньорство',
  380. GF_SUGGESTIONS: 'Предложения / Препоръки',
  381. GF_SHORT_REEL_VIDEO: 'Рил/късо видео',
  382. GF_ANIMATED_GIFS_POSTS: 'Анимирани GIF файлове',
  383. GF_ANIMATED_GIFS_PAUSE: 'Пауза на анимирани GIF файлове',
  384. GF_SHARES: '# споделяния',
  385. VF_LIVE: 'НА ЖИВО',
  386. VF_INSTAGRAM: 'Instagram',
  387. VF_DUPLICATE_VIDEOS: 'Дублирано видео',
  388. VF_ANIMATED_GIFS_PAUSE: 'Пауза на анимирани GIF файлове',
  389. PP_ANIMATED_GIFS_POSTS: 'Анимирани GIF файлове',
  390. PP_ANIMATED_GIFS_PAUSE: 'Пауза на анимирани GIF файлове',
  391. NF_BLOCKED_FEED: ['Новинарски поток', 'Поток с групи', 'Поток с видеа'],
  392. GF_BLOCKED_FEED: ['Новинарски поток', 'Поток с групи', 'Поток с видеа'],
  393. VF_BLOCKED_FEED: ['Новинарски поток', 'Поток с групи', 'Поток с видеа'],
  394. MP_BLOCKED_FEED: ['Поток с Marketplace'],
  395. PP_BLOCKED_FEED: '',
  396. OTHER_INFO_BOX_CORONAVIRUS: 'Коронавирус (информационна кутия)',
  397. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Наука за климата (информационна кутия)',
  398. OTHER_INFO_BOX_SUBSCRIBE: 'Абонирай се (информационна кутия)',
  399. REELS_TITLE: 'Ленти', // -- FB's label
  400. REELS_CONTROLS: 'Покажи контроли на видеото',
  401. REELS_DISABLE_LOOPING: 'Изключване на повторението',
  402. DLG_TITLE: 'Почисти моите емисии',
  403. DLG_NF: 'Новинарски поток',
  404. DLG_GF: 'Поток с групи',
  405. DLG_VF: 'Поток с видеа',
  406. DLG_MP: 'Поток с Marketplace',
  407. DLG_PP: 'Профил / Страница',
  408. DLG_OTHER: 'Допълнителен / информационен раздел',
  409. DLG_BLOCK_TEXT_FILTER_TITLE: 'Текстов филтър',
  410. DLG_BLOCK_NEW_LINE: '(Разделете думите или фразите с нов ред, регулярните изрази са поддържани)',
  411. NF_BLOCKED_ENABLED: 'Активирано',
  412. GF_BLOCKED_ENABLED: 'Активирано',
  413. VF_BLOCKED_ENABLED: 'Активирано',
  414. MP_BLOCKED_ENABLED: 'Активирано',
  415. PP_BLOCKED_ENABLED: 'Активирано',
  416. NF_BLOCKED_RE: 'Регулярни изрази (RegExp)',
  417. GF_BLOCKED_RE: 'Регулярни изрази (RegExp)',
  418. VF_BLOCKED_RE: 'Регулярни изрази (RegExp)',
  419. MP_BLOCKED_RE: 'Регулярни изрази (RegExp)',
  420. PP_BLOCKED_RE: 'Регулярни изрази (RegExp)',
  421. DLG_VERBOSITY: 'Опции за скрити публикации',
  422. DLG_VERBOSITY_CAPTION: 'Показване на етикет, ако публикацията е скрита',
  423. VERBOSITY_MESSAGE: ['няма етикет', 'Скрита публикация. Правило: ', ' скрити публикации', '7 скрити публикации ~ (само за Груповия поток)'],
  424. VERBOSITY_MESSAGE_COLOUR: 'Цвят на текста',
  425. VERBOSITY_MESSAGE_BG_COLOUR: 'Цвят на фона',
  426. VERBOSITY_DEBUG: 'Открояване на скритите публикации',
  427. CMF_CUSTOMISATIONS: 'Настройки',
  428. CMF_BTN_LOCATION: 'Местоположение на бутона "Почисти моите емисии"',
  429. CMF_BTN_OPTION: ['долу вляво', 'горе вдясно', 'деактивирано (използвайте "Настройки" в менюто с команди за потребителски сценарии)'],
  430. CMF_DIALOG_LANGUAGE_LABEL: 'Почистете езика на моето диалогово поле за новини',
  431. CMF_DIALOG_LANGUAGE: 'Български',
  432. CMF_DIALOG_LANGUAGE_DEFAULT: 'Използване на езика на сайта',
  433. GM_MENU_SETTINGS: 'Настройки',
  434. CMF_DIALOG_LOCATION: 'Местоположение на диалоговия прозорец "Почисти моите емисии"',
  435. CMF_DIALOG_OPTION: ['лява страна', 'дясна страна'],
  436. CMF_BORDER_COLOUR: 'Цвят на рамката',
  437. DLG_TIPS: 'Съвети',
  438. DLG_TIPS_CONTENT: 'Изчистването на кеша на браузъра ще нулира настройките ви до техните стандартни стойности.\n\nИзползвайте бутоните "Експорт" и "Импорт", за да запазите и възстановите персонализираните си настройки.',
  439. DLG_BUTTONS: ['Запази', 'Затвори', 'Експорт', 'Импорт', 'Нулиране'],
  440. DLG_FB_COLOUR_HINT: 'Оставете празно, за да използвате цветовата схема на FB',
  441. },
  442. // -- Čeština (Czechia)
  443. cs: {
  444. LANGUAGE_DIRECTION: 'ltr',
  445. SPONSORED: 'Sponzorováno',
  446. NF_TABLIST_STORIES_REELS_ROOMS: 'Seznam karet "Stories | Reels | Místnosti"',
  447. NF_STORIES: 'Stories',
  448. NF_SURVEY: 'Průzkum',
  449. NF_PEOPLE_YOU_MAY_KNOW: 'Koho možná znáte',
  450. NF_PAID_PARTNERSHIP: 'Placené partnerství',
  451. NF_SPONSORED_PAID: 'Sponzorováno · Platí za to ______',
  452. NF_SUGGESTIONS: 'Návrhy / Doporučení',
  453. NF_FOLLOW: 'Sledovat',
  454. NF_PARTICIPATE: 'Participovat',
  455. NF_REELS_SHORT_VIDEOS: 'Sekvence a krátká videa',
  456. NF_SHORT_REEL_VIDEO: 'Naviják/krátké video',
  457. NF_EVENTS_YOU_MAY_LIKE: 'Události, které se vám mohou líbit',
  458. NF_ANIMATED_GIFS_POSTS: 'Animované GIFy',
  459. NF_ANIMATED_GIFS_PAUSE: 'Pozastavit animované GIFy',
  460. NF_SHARES: '# sdílení',
  461. NF_LIKES_MAXIMUM: 'Maximální počet hodnocení Líbí se mi',
  462. GF_PAID_PARTNERSHIP: 'Placené partnerství',
  463. GF_SUGGESTIONS: 'Návrhy / Doporučení',
  464. GF_SHORT_REEL_VIDEO: 'Naviják/krátké video',
  465. GF_ANIMATED_GIFS_POSTS: 'Animované GIFy',
  466. GF_ANIMATED_GIFS_PAUSE: 'Pozastavit animované GIFy',
  467. GF_SHARES: '# sdílení',
  468. VF_LIVE: 'ŽIVĚ',
  469. VF_INSTAGRAM: 'Instagram',
  470. VF_DUPLICATE_VIDEOS: 'Duplikované video',
  471. VF_ANIMATED_GIFS_PAUSE: 'Pozastavit animované GIFy',
  472. PP_ANIMATED_GIFS_POSTS: 'Animované GIFy',
  473. PP_ANIMATED_GIFS_PAUSE: 'Pozastavit animované GIFy',
  474. NF_BLOCKED_FEED: ['Informační kanál', 'Skupinový kanál', 'Video kanál'],
  475. GF_BLOCKED_FEED: ['Informační kanál', 'Skupinový kanál', 'Video kanál'],
  476. VF_BLOCKED_FEED: ['Informační kanál', 'Skupinový kanál', 'Video kanál'],
  477. MP_BLOCKED_FEED: ['Marketplace kanál'],
  478. PP_BLOCKED_FEED: '',
  479. OTHER_INFO_BOX_CORONAVIRUS: 'Coronavirus (informační box)',
  480. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Klimatická věda (informační box)',
  481. OTHER_INFO_BOX_SUBSCRIBE: 'Odebírat (informační box)',
  482. REELS_TITLE: 'Reels',
  483. REELS_CONTROLS: 'Zobrazit ovládání videa',
  484. REELS_DISABLE_LOOPING: 'Vypnout smyčení',
  485. DLG_TITLE: 'Vyčistěte mé kanály',
  486. DLG_NF: 'Informační kanál',
  487. DLG_GF: 'Skupinový kanál',
  488. DLG_VF: 'Video kanál',
  489. DLG_MP: 'Marketplace kanál',
  490. DLG_PP: 'Profil / Stránka',
  491. DLG_OTHER: 'Doplňková/informační část',
  492. DLG_BLOCK_TEXT_FILTER_TITLE: 'Textový filtr',
  493. DLG_BLOCK_NEW_LINE: '(Oddělte slova nebo fráze pomocí nového řádku, regulární výrazy jsou podporovány)',
  494. NF_BLOCKED_ENABLED: 'Zapnuto',
  495. GF_BLOCKED_ENABLED: 'Zapnuto',
  496. VF_BLOCKED_ENABLED: 'Zapnuto',
  497. MP_BLOCKED_ENABLED: 'Zapnuto',
  498. PP_BLOCKED_ENABLED: 'Zapnuto',
  499. NF_BLOCKED_RE: 'Reguläre Ausdrücke (RegExp)',
  500. GF_BLOCKED_RE: 'Reguläre Ausdrücke (RegExp)',
  501. VF_BLOCKED_RE: 'Reguläre Ausdrücke (RegExp)',
  502. MP_BLOCKED_RE: 'Reguläre Ausdrücke (RegExp)',
  503. PP_BLOCKED_RE: 'Reguläre Ausdrücke (RegExp)',
  504. DLG_VERBOSITY: 'Možnosti skrytých příspěvků',
  505. DLG_VERBOSITY_CAPTION: 'Zobrazit popisek, pokud je příspěvek skrytý',
  506. VERBOSITY_MESSAGE: ['žádný popisek', 'Příspěvek byl skryt. Pravidlo: ', ' příspěvků skrytých', '7 příspěvků skrytých ~ (pouze ve skupinovém zpravodaji)'],
  507. VERBOSITY_MESSAGE_COLOUR: 'Barva textu',
  508. VERBOSITY_MESSAGE_BG_COLOUR: 'Barva pozadí',
  509. VERBOSITY_DEBUG: 'Zvýrazněte „skryté“ příspěvky',
  510. CMF_CUSTOMISATIONS: 'Přizpůsobení',
  511. CMF_BTN_LOCATION: 'Umístění tlačítka Vyčistěte mé kanály',
  512. CMF_BTN_OPTION: ['vlevo dole', 'vpravo nahoře', 'zakázáno (použijte "Nastavení" v nabídce Příkazy uživatelského skriptu)'],
  513. CMF_DIALOG_LANGUAGE_LABEL: 'Vyčistit jazyk mého dialogového okna s feedy',
  514. CMF_DIALOG_LANGUAGE: 'Čeština',
  515. CMF_DIALOG_LANGUAGE_DEFAULT: 'Použít jazyk webu',
  516. GM_MENU_SETTINGS: 'Nastavení',
  517. CMF_DIALOG_LOCATION: 'Umístění dialogového okna Vyčistěte mé kanály',
  518. CMF_DIALOG_OPTION: ['levá strana', 'pravá strana'],
  519. CMF_BORDER_COLOUR: 'Barva ohraničení',
  520. DLG_TIPS: 'Tipy',
  521. DLG_TIPS_CONTENT: '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í.',
  522. DLG_BUTTONS: ['Uložit', 'Zavřít', 'Export', 'Import', 'Resetovat'],
  523. DLG_FB_COLOUR_HINT: 'Chcete-li použít barevné schéma FB, nechte prázdné',
  524. },
  525. // -- Deutsch (Germany)
  526. de: {
  527. LANGUAGE_DIRECTION: 'ltr',
  528. SPONSORED: 'Gesponsert',
  529. SPONSORED_EXTRA: 'Anzeige',
  530. NF_TABLIST_STORIES_REELS_ROOMS: 'Listenfeld der Registerkarte "Stories | Reels | Rooms"',
  531. NF_STORIES: 'Stories',
  532. NF_SURVEY: 'Umfrage',
  533. NF_PEOPLE_YOU_MAY_KNOW: 'Personen, die du kennen könntest',
  534. NF_PAID_PARTNERSHIP: 'Bezahlte Werbepartnerschaft',
  535. NF_SPONSORED_PAID: 'Gesponsert · Finanziert von ______',
  536. NF_SUGGESTIONS: 'Vorschläge / Empfehlungen',
  537. NF_FOLLOW: 'Folgen',
  538. NF_PARTICIPATE: 'Teilnehmen',
  539. NF_REELS_SHORT_VIDEOS: 'Reels und Kurzvideos',
  540. NF_SHORT_REEL_VIDEO: 'Reel/kurzes Video',
  541. NF_EVENTS_YOU_MAY_LIKE: 'Veranstaltungen, die Ihnen gefallen könnten',
  542. NF_ANIMATED_GIFS_POSTS: 'Animierte GIFs',
  543. NF_ANIMATED_GIFS_PAUSE: 'Animierte GIFs pausieren',
  544. NF_SHARES: '# Mal geteilt',
  545. NF_LIKES_MAXIMUM: 'Maximale Anzahl an Likes',
  546. GF_PAID_PARTNERSHIP: 'Bezahlte Werbepartnerschaft',
  547. GF_SUGGESTIONS: 'Vorschläge / Empfehlungen',
  548. GF_SHORT_REEL_VIDEO: 'Reel/kurzes Video',
  549. GF_ANIMATED_GIFS_POSTS: 'Animierte GIFs',
  550. GF_ANIMATED_GIFS_PAUSE: 'Animierte GIFs pausieren',
  551. GF_SHARES: '# Mal geteilt',
  552. VF_LIVE: 'LIVE',
  553. VF_INSTAGRAM: 'Instagram',
  554. VF_DUPLICATE_VIDEOS: 'Dupliziertes Video',
  555. VF_ANIMATED_GIFS_PAUSE: 'Animierte GIFs pausieren',
  556. PP_ANIMATED_GIFS_POSTS: 'Animierte GIFs',
  557. PP_ANIMATED_GIFS_PAUSE: 'Animierte GIFs pausieren',
  558. NF_BLOCKED_FEED: ['Newsfeed', 'Gruppen-Feed', 'Video-Feed'],
  559. GF_BLOCKED_FEED: ['Newsfeed', 'Gruppen-Feed', 'Video-Feed'],
  560. VF_BLOCKED_FEED: ['Newsfeed', 'Gruppen-Feed', 'Video-Feed'],
  561. MP_BLOCKED_FEED: ['Marktplatz-Feed'],
  562. PP_BLOCKED_FEED: '',
  563. OTHER_INFO_BOX_CORONAVIRUS: 'Coronavirus (Infobox)',
  564. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Klimawissenschaft (Infobox)',
  565. OTHER_INFO_BOX_SUBSCRIBE: 'Abonnieren (Infobox)',
  566. REELS_TITLE: 'Reels',
  567. REELS_CONTROLS: 'Video-Steuerung anzeigen',
  568. REELS_DISABLE_LOOPING: 'Wiederholung deaktivieren',
  569. DLG_TITLE: 'Bereinige meine Feeds',
  570. DLG_NF: 'Newsfeed',
  571. DLG_GF: 'Gruppen-Feed',
  572. DLG_VF: 'Video-Feed',
  573. DLG_MP: 'Marktplatz-Feed',
  574. DLG_PP: 'Profil / Seite',
  575. DLG_OTHER: 'Ergänzungs-/Informationsbereich',
  576. DLG_BLOCK_TEXT_FILTER_TITLE: 'Textfilter',
  577. DLG_BLOCK_NEW_LINE: '(Trennen Sie Wörter oder Phrasen mit einem Zeilenumbruch, reguläre Ausdrücke werden unterstützt)',
  578. NF_BLOCKED_ENABLED: 'Ermöglichte',
  579. GF_BLOCKED_ENABLED: 'Ermöglichte',
  580. VF_BLOCKED_ENABLED: 'Ermöglichte',
  581. MP_BLOCKED_ENABLED: 'Ermöglichte',
  582. PP_BLOCKED_ENABLED: 'Ermöglichte',
  583. NF_BLOCKED_RE: 'Reguläre Ausdrücke (RegExp)',
  584. GF_BLOCKED_RE: 'Reguläre Ausdrücke (RegExp)',
  585. VF_BLOCKED_RE: 'Reguläre Ausdrücke (RegExp)',
  586. MP_BLOCKED_RE: 'Reguläre Ausdrücke (RegExp)',
  587. PP_BLOCKED_RE: 'Reguläre Ausdrücke (RegExp)',
  588. DLG_VERBOSITY: 'Optionen für ausgeblendete Beiträge',
  589. DLG_VERBOSITY_CAPTION: 'Ein Label anzeigen, wenn ein Beitrag ausgeblendet ist',
  590. VERBOSITY_MESSAGE: ['kein Label', 'Beitrag ausgeblendet. Regel: ', ' Beiträge versteckt', '7 Beiträge versteckt ~ (nur Gruppen-Feed)'],
  591. VERBOSITY_MESSAGE_COLOUR: 'Textfarbe',
  592. VERBOSITY_MESSAGE_BG_COLOUR: 'Hintergrundfarbe',
  593. VERBOSITY_DEBUG: 'Markieren Sie "versteckte" Beiträge',
  594. CMF_CUSTOMISATIONS: 'Anpassungen',
  595. CMF_BTN_LOCATION: 'Position der Schaltfläche "Bereinige meine Feeds"',
  596. CMF_BTN_OPTION: ['unten links', 'oben rechts', 'deaktiviert (verwenden Sie "Einstellungen" im Menü "Benutzerskriptbefehle")'],
  597. CMF_DIALOG_LANGUAGE_LABEL: 'Sprache meines Feeds-Dialogfelds säubern',
  598. CMF_DIALOG_LANGUAGE: 'Deutsch',
  599. CMF_DIALOG_LANGUAGE_DEFAULT: 'Website-Sprache verwenden',
  600. GM_MENU_SETTINGS: 'Einstellungen',
  601. CMF_DIALOG_LOCATION: 'Position des Dialogfelds "Bereinige meine Feeds"',
  602. CMF_DIALOG_OPTION: ['linke Seite', 'rechte Seite'],
  603. CMF_BORDER_COLOUR: 'Farbe der Umrandung',
  604. DLG_TIPS: 'Tipps',
  605. DLG_TIPS_CONTENT: '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.',
  606. DLG_BUTTONS: ['Speichern', 'Schließen', 'Exportieren', 'Importieren', 'Zurücksetzen'],
  607. DLG_FB_COLOUR_HINT: 'Leer lassen, um das Farbschema von FB zu verwenden',
  608. },
  609. // -- Ελληνικά (Greece)
  610. el: {
  611. LANGUAGE_DIRECTION: 'ltr',
  612. SPONSORED: 'Χορηγούμενη',
  613. NF_TABLIST_STORIES_REELS_ROOMS: 'Λίστα καρτελών "Ιστορίες | Reels | Δωμάτια"',
  614. NF_STORIES: 'Ιστορίες',
  615. NF_SURVEY: 'Τοπογράφηση',
  616. NF_PEOPLE_YOU_MAY_KNOW: 'Άτομα που ίσως γνωρίζετε',
  617. NF_PAID_PARTNERSHIP: 'Πληρωμένη συνεργασία',
  618. NF_SPONSORED_PAID: 'Χορηγούμενο · Πληρωμένο από ______',
  619. NF_SUGGESTIONS: 'Προτάσεις / Συστάσεις',
  620. NF_FOLLOW: 'Ακολούθησε',
  621. NF_PARTICIPATE: 'Συμμετέχω',
  622. NF_REELS_SHORT_VIDEOS: 'Reel και σύντομα βίντεο',
  623. NF_SHORT_REEL_VIDEO: 'Ριλς/μικρό βίντεο',
  624. NF_EVENTS_YOU_MAY_LIKE: 'Εκδηλώσεις που μπορεί να σας αρέσουν',
  625. NF_ANIMATED_GIFS_POSTS: 'Κινούμενες εικόνες GIF',
  626. NF_ANIMATED_GIFS_PAUSE: 'Παύση κινούμενων GIF',
  627. NF_SHARES: '# μερίδια',
  628. NF_LIKES_MAXIMUM: 'Μέγιστα "Μου αρέσει"',
  629. GF_PAID_PARTNERSHIP: 'Πληρωμένη συνεργασία',
  630. GF_SUGGESTIONS: 'Προτάσεις / Συστάσεις',
  631. GF_SHORT_REEL_VIDEO: 'Ριλς/μικρό βίντεο',
  632. GF_ANIMATED_GIFS_POSTS: 'Κινούμενες εικόνες GIF',
  633. GF_ANIMATED_GIFS_PAUSE: 'Παύση κινούμενων GIF',
  634. GF_SHARES: '# μερίδια',
  635. VF_LIVE: 'ΖΩΝΤΑΝΑ',
  636. VF_INSTAGRAM: 'Instagram',
  637. VF_DUPLICATE_VIDEOS: 'Διπλότυπο βίντεο',
  638. VF_ANIMATED_GIFS_PAUSE: 'Παύση κινούμενων GIF',
  639. PP_ANIMATED_GIFS_POSTS: 'Κινούμενες εικόνες GIF',
  640. PP_ANIMATED_GIFS_PAUSE: 'Παύση κινούμενων GIF',
  641. NF_BLOCKED_FEED: ['Ροή ειδήσεων', 'Ροή ομάδων', 'Ροή βίντεο'],
  642. GF_BLOCKED_FEED: ['Ροή ειδήσεων', 'Ροή ομάδων', 'Ροή βίντεο'],
  643. VF_BLOCKED_FEED: ['Ροή ειδήσεων', 'Ροή ομάδων', 'Ροή βίντεο'],
  644. MP_BLOCKED_FEED: ['Ροή Marketplace'],
  645. PP_BLOCKED_FEED: '',
  646. OTHER_INFO_BOX_CORONAVIRUS: 'Κορονοϊός (πλαίσιο πληροφοριών)',
  647. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Επιστήμη του κλίματος (πλαίσιο πληροφοριών)',
  648. OTHER_INFO_BOX_SUBSCRIBE: 'Εγγραφή (πλαίσιο πληροφοριών)',
  649. REELS_TITLE: 'Reel',
  650. REELS_CONTROLS: 'Εμφάνιση χειριστηρίων βίντεο',
  651. REELS_DISABLE_LOOPING: 'Απενεργοποίηση επανάληψης',
  652. DLG_TITLE: 'Καθαρισμός των ροών μου',
  653. DLG_NF: 'Ροή ειδήσεων',
  654. DLG_GF: 'Ροή ομάδων',
  655. DLG_VF: 'Ροή βίντεο',
  656. DLG_MP: 'Ροή Marketplace',
  657. DLG_PP: 'Προφίλ / Σελίδα',
  658. DLG_OTHER: 'Συμπληρωματικό τμήμα / πληροφοριακό τμήμα',
  659. DLG_BLOCK_TEXT_FILTER_TITLE: 'Φίλτρο κειμένου',
  660. DLG_BLOCK_NEW_LINE: '(Διαχωρίστε λέξεις ή φράσεις με αλλαγή γραμμής, υποστηρίζονται κανονικές εκφράσεις)',
  661. NF_BLOCKED_ENABLED: 'Ενεργοποιημένο',
  662. GF_BLOCKED_ENABLED: 'Ενεργοποιημένο',
  663. VF_BLOCKED_ENABLED: 'Ενεργοποιημένο',
  664. MP_BLOCKED_ENABLED: 'Ενεργοποιημένο',
  665. PP_BLOCKED_ENABLED: 'Ενεργοποιημένο',
  666. NF_BLOCKED_RE: 'Κανονικές Εκφράσεις (RegExp)',
  667. GF_BLOCKED_RE: 'Κανονικές Εκφράσεις (RegExp)',
  668. VF_BLOCKED_RE: 'Κανονικές Εκφράσεις (RegExp)',
  669. MP_BLOCKED_RE: 'Κανονικές Εκφράσεις (RegExp)',
  670. PP_BLOCKED_RE: 'Κανονικές Εκφράσεις (RegExp)',
  671. DLG_VERBOSITY: 'Επιλογές για κρυφές αναρτήσεις',
  672. DLG_VERBOSITY_CAPTION: 'Εμφάνιση ετικέτας αν μια δημοσίευση είναι κρυμμένη',
  673. VERBOSITY_MESSAGE: ['χωρίς ετικέτα', 'Δημοσίευση κρυμμένη. Κανόνας: ', ' δημοσιεύσεις κρυμμένες', '7 δημοσιεύσεις κρυμμένες ~ (μόνο στην τροφοδοσία των ομάδων)'],
  674. VERBOSITY_MESSAGE_COLOUR: 'Χρώμα κειμένου',
  675. VERBOSITY_MESSAGE_BG_COLOUR: 'Χρώμα φόντου',
  676. VERBOSITY_DEBUG: 'Επισήμανση "κρυφών αναρτήσεων"',
  677. CMF_CUSTOMISATIONS: 'Προσαρμογές',
  678. CMF_BTN_LOCATION: 'Τοποθεσία του κουμπιού "Καθαρισμός των ροών μου"',
  679. CMF_BTN_OPTION: ['κάτω αριστερά', 'πάνω δεξιά', 'απενεργοποιημένο (χρησιμοποιήστε "Ρυθμίσεις" στο μενού "Εντολές σεναρίου χρήστη")'],
  680. CMF_DIALOG_LANGUAGE_LABEL: 'Καθαρίστε τη γλώσσα του παραθύρου διαλόγου με τα τροφοδοτούμενα μου',
  681. CMF_DIALOG_LANGUAGE: 'Ελληνικά',
  682. CMF_DIALOG_LANGUAGE_DEFAULT: 'Χρήση γλώσσας ιστότοπου',
  683. GM_MENU_SETTINGS: 'Ρυθμίσεις',
  684. CMF_DIALOG_LOCATION: 'Τοποθεσία της διαλόγου "Καθαρισμός των ροών μου"',
  685. CMF_DIALOG_OPTION: ['αριστερή πλευρά', 'δεξιά πλευρά'],
  686. CMF_BORDER_COLOUR: 'Χρώμα περιγράμματος',
  687. DLG_TIPS: 'Συμβουλές',
  688. DLG_TIPS_CONTENT: 'Η εκκαθάριση της μνήμης cache του προγράμματος περιήγησης θα επαναφέρει τις ρυθμίσεις σας στις προεπιλεγμένες τιμές τους.\n\nΧρησιμοποιήστε τα κουμπιά "Εξαγωγή" και "Εισαγωγή" για να δημιουργήσετε αντίγραφο ασφαλείας και να επαναφέρετε τις εξατομικευμένες ρυθμίσεις σας.',
  689. DLG_BUTTONS: ['Αποθήκευση', 'Κλείσιμο', 'Εξαγωγή', 'Εισαγωγή', 'Επαναφορά'],
  690. DLG_FB_COLOUR_HINT: 'Αφήστε κενό για να χρησιμοποιήσετε το χρωματικό σχήμα του FB',
  691. },
  692. // -- Espanol (Spain)
  693. es: {
  694. LANGUAGE_DIRECTION: 'ltr',
  695. SPONSORED: 'Publicidad',
  696. NF_TABLIST_STORIES_REELS_ROOMS: 'Cuadro de lista de la pestaña "Historias | Reels | Salas"',
  697. NF_STORIES: 'Historias',
  698. NF_SURVEY: 'Encuesta',
  699. NF_PEOPLE_YOU_MAY_KNOW: 'Personas que quizá conozcas',
  700. NF_PAID_PARTNERSHIP: 'Colaboración pagada',
  701. NF_SPONSORED_PAID: 'Publicidad · Pagado por ______',
  702. NF_SUGGESTIONS: 'Sugerencias / Recomendaciones',
  703. NF_FOLLOW: 'Seguir',
  704. NF_PARTICIPATE: 'Participar',
  705. NF_REELS_SHORT_VIDEOS: 'Reels y vídeos cortos',
  706. NF_SHORT_REEL_VIDEO: 'Reel/video corto',
  707. NF_EVENTS_YOU_MAY_LIKE: 'Eventos que te pueden gustar',
  708. NF_ANIMATED_GIFS_POSTS: 'GIF animados',
  709. NF_ANIMATED_GIFS_PAUSE: 'Pausar GIF animados',
  710. NF_SHARES: '# veces compartida',
  711. NF_LIKES_MAXIMUM: 'Número máximo de Me gusta',
  712. GF_PAID_PARTNERSHIP: 'Colaboración pagada',
  713. GF_SUGGESTIONS: 'Sugerencias / Recomendaciones',
  714. GF_SHORT_REEL_VIDEO: 'Reel/video corto',
  715. GF_ANIMATED_GIFS_POSTS: 'GIF animados',
  716. GF_ANIMATED_GIFS_PAUSE: 'Pausar GIF animados',
  717. GF_SHARES: '# veces compartida',
  718. VF_LIVE: 'ESTRENO',
  719. VF_INSTAGRAM: 'Instagram',
  720. VF_DUPLICATE_VIDEOS: 'Video duplicado',
  721. VF_ANIMATED_GIFS_PAUSE: 'Pausar GIF animados',
  722. PP_ANIMATED_GIFS_POSTS: 'GIF animados',
  723. PP_ANIMATED_GIFS_PAUSE: 'Pausar GIF animados',
  724. NF_BLOCKED_FEED: ['Feed de noticias', 'Feed de grupos', 'Feed de videos'],
  725. GF_BLOCKED_FEED: ['Feed de noticias', 'Feed de grupos', 'Feed de videos'],
  726. VF_BLOCKED_FEED: ['Feed de noticias', 'Feed de grupos', 'Feed de videos'],
  727. MP_BLOCKED_FEED: ['Feed de Marketplace'],
  728. PP_BLOCKED_FEED: '',
  729. OTHER_INFO_BOX_CORONAVIRUS: 'Coronavirus (cuadro de información)',
  730. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Ciencia del clima (cuadro de información)',
  731. OTHER_INFO_BOX_SUBSCRIBE: 'Suscribir (cuadro de información)',
  732. REELS_TITLE: 'Reels',
  733. REELS_CONTROLS: 'Mostrar controles de video',
  734. REELS_DISABLE_LOOPING: 'Desactivar bucle',
  735. DLG_TITLE: 'Limpia mis feeds',
  736. DLG_NF: 'Feed de noticias',
  737. DLG_GF: 'Feed de grupos',
  738. DLG_VF: 'Feed de vídeos',
  739. DLG_MP: 'Feed de Marketplace',
  740. DLG_PP: 'Perfil / Página',
  741. DLG_OTHER: 'Sección suplementaria / información',
  742. DLG_BLOCK_TEXT_FILTER_TITLE: 'Filtro de texto',
  743. DLG_BLOCK_NEW_LINE: '(Separe palabras o frases con un salto de línea, se admiten expresiones regulares)',
  744. NF_BLOCKED_ENABLED: 'Habilitadas',
  745. GF_BLOCKED_ENABLED: 'Habilitadas',
  746. VF_BLOCKED_ENABLED: 'Habilitadas',
  747. MP_BLOCKED_ENABLED: 'Habilitadas',
  748. PP_BLOCKED_ENABLED: 'Habilitadas',
  749. NF_BLOCKED_RE: 'Expresiones regulares (RegExp)',
  750. GF_BLOCKED_RE: 'Expresiones regulares (RegExp)',
  751. VF_BLOCKED_RE: 'Expresiones regulares (RegExp)',
  752. MP_BLOCKED_RE: 'Expresiones regulares (RegExp)',
  753. PP_BLOCKED_RE: 'Expresiones regulares (RegExp)',
  754. DLG_VERBOSITY: 'Opciones para publicaciones ocultas',
  755. DLG_VERBOSITY_CAPTION: 'Mostrar una etiqueta si una publicación está oculta',
  756. VERBOSITY_MESSAGE: ['sin etiqueta', 'Publicación oculta. Regla: ', ' publicaciones ocultas', '7 publicaciones ocultas ~ (solo en el Feed de Grupos)'],
  757. VERBOSITY_MESSAGE_COLOUR: 'Color del texto',
  758. VERBOSITY_MESSAGE_BG_COLOUR: 'Color de fondo',
  759. VERBOSITY_DEBUG: 'Destacar publicaciones "ocultas"',
  760. CMF_CUSTOMISATIONS: 'Personalizaciones',
  761. CMF_BTN_LOCATION: 'Ubicación del botón Limpia mis feeds',
  762. CMF_BTN_OPTION: ['abajo a la izquierda', 'arriba a la derecha', 'deshabilitado (use "Configuración" en el menú Comandos de script de usuario)'],
  763. CMF_DIALOG_LANGUAGE_LABEL: 'Limpiar el idioma de mi cuadro de diálogo de feeds',
  764. CMF_DIALOG_LANGUAGE: 'Español',
  765. CMF_DIALOG_LANGUAGE_DEFAULT: 'Usar idioma del sitio',
  766. GM_MENU_SETTINGS: 'Configuración',
  767. CMF_DIALOG_LOCATION: 'Ubicación del cuadro de diálogo Limpia mis feeds',
  768. CMF_DIALOG_OPTION: ['lado izquierdo', 'lado derecho'],
  769. CMF_BORDER_COLOUR: 'Color de borde',
  770. DLG_TIPS: 'Consejos',
  771. DLG_TIPS_CONTENT: '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.',
  772. DLG_BUTTONS: ['Guardar', 'Cerrar', 'Exportar', 'Importar', 'Reajustar'],
  773. DLG_FB_COLOUR_HINT: 'Dejar en blanco para usar el esquema de color de FB',
  774. },
  775. // -- Suomi - Finnish (Finland)
  776. fi: {
  777. LANGUAGE_DIRECTION: 'ltr',
  778. SPONSORED: 'Sponsoroitu',
  779. NF_TABLIST_STORIES_REELS_ROOMS: '"Tarinat | Reels | Rooms" -välilehtien luetteloruutu',
  780. NF_STORIES: 'Tarinat',
  781. NF_SURVEY: 'Kysely',
  782. NF_PEOPLE_YOU_MAY_KNOW: 'Ihmiset, jotka saatat tuntea',
  783. NF_PAID_PARTNERSHIP: 'Maksettu kumppanuus',
  784. NF_SPONSORED_PAID: 'Sponsoroitu · Maksaja ______',
  785. NF_SUGGESTIONS: 'Ehdotuksia / Suosituksia',
  786. NF_FOLLOW: 'Seuraa',
  787. NF_PARTICIPATE: 'Osallistua',
  788. NF_REELS_SHORT_VIDEOS: 'Keloja ja lyhyitä videoita',
  789. NF_SHORT_REEL_VIDEO: 'Kela/lyhyt video',
  790. NF_EVENTS_YOU_MAY_LIKE: 'Kela/lyhyt video',
  791. NF_ANIMATED_GIFS_POSTS: 'Animoidut GIF-kuvat',
  792. NF_ANIMATED_GIFS_PAUSE: 'Pysäytä animoidut GIF-kuvat',
  793. NF_SHARES: '# jakoa',
  794. NF_LIKES_MAXIMUM: 'Maksimimäärä tykkäyksiä',
  795. GF_PAID_PARTNERSHIP: 'Maksettu kumppanuus',
  796. GF_SUGGESTIONS: 'Ehdotuksia / Suosituksia',
  797. GF_SHORT_REEL_VIDEO: 'Keloja ja lyhyitä videoita',
  798. GF_ANIMATED_GIFS_POSTS: 'Animoidut GIF-kuvat',
  799. GF_ANIMATED_GIFS_PAUSE: 'Pysäytä animoidut GIF-kuvat',
  800. GF_SHARES: '# jakoa',
  801. VF_LIVE: 'LIVE',
  802. VF_INSTAGRAM: 'Instagram',
  803. VF_DUPLICATE_VIDEOS: 'Kaksoisvideo',
  804. VF_ANIMATED_GIFS_PAUSE: 'Pysäytä animoidut GIF-kuvat',
  805. PP_ANIMATED_GIFS_POSTS: 'Animoidut GIF-kuvat',
  806. PP_ANIMATED_GIFS_PAUSE: 'Pysäytä animoidut GIF-kuvat',
  807. NF_BLOCKED_FEED: ['Uutissyöte', 'Ryhmäsyöte', 'Videosyöte'],
  808. GF_BLOCKED_FEED: ['Uutissyöte', 'Ryhmäsyöte', 'Videosyöte'],
  809. VF_BLOCKED_FEED: ['Uutissyöte', 'Ryhmäsyöte', 'Videosyöte'],
  810. MP_BLOCKED_FEED: ['Marketplace-syöte'],
  811. PP_BLOCKED_FEED: '',
  812. OTHER_INFO_BOX_CORONAVIRUS: 'Koronavirus (tietolaatikko)',
  813. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Ilmastotiede (tietolaatikko)',
  814. OTHER_INFO_BOX_SUBSCRIBE: 'Rekisteröidy (tietolaatikko)',
  815. REELS_TITLE: 'Reels',
  816. REELS_CONTROLS: 'Näytä videon hallintaelementit',
  817. REELS_DISABLE_LOOPING: 'Poista toisto',
  818. DLG_TITLE: 'Puhdista syötteeni',
  819. DLG_NF: 'Uutisvirta',
  820. DLG_GF: 'Ryhmäsyöte',
  821. DLG_VF: 'Videosyöte',
  822. DLG_MP: 'Marketplace-syöte',
  823. DLG_PP: 'Profiili / Sivu',
  824. DLG_OTHER: 'Täydentävä / tieto-osio',
  825. DLG_BLOCK_TEXT_FILTER_TITLE: 'Tekstisuodatin',
  826. DLG_BLOCK_NEW_LINE: '(Erota sanat tai lauseet rivinvaihdolla, säännölliset lausekkeet ovat tuettuja)',
  827. NF_BLOCKED_ENABLED: 'Ota vaihtoehto käyttöön',
  828. GF_BLOCKED_ENABLED: 'Ota vaihtoehto käyttöön',
  829. VF_BLOCKED_ENABLED: 'Ota vaihtoehto käyttöön',
  830. MP_BLOCKED_ENABLED: 'Ota vaihtoehto käyttöön',
  831. PP_BLOCKED_ENABLED: 'Ota vaihtoehto käyttöön',
  832. NF_BLOCKED_RE: 'Säännölliset lausekkeet (RegExp)',
  833. GF_BLOCKED_RE: 'Säännölliset lausekkeet (RegExp)',
  834. VF_BLOCKED_RE: 'Säännölliset lausekkeet (RegExp)',
  835. MP_BLOCKED_RE: 'Säännölliset lausekkeet (RegExp)',
  836. PP_BLOCKED_RE: 'Säännölliset lausekkeet (RegExp)',
  837. DLG_VERBOSITY: 'Vaihtoehdot piilotetuille viesteille',
  838. DLG_VERBOSITY_CAPTION: 'Näytä merkki, jos artikkeli on piilotettu',
  839. VERBOSITY_MESSAGE: ['ei tunnistetta', 'Viesti piilotettu. Sääntö: ', ' viestiä piilotettu', '7 viestiä piilotettu ~ (vain Ryhmien syötteessä)'],
  840. VERBOSITY_MESSAGE_COLOUR: 'Tekstin väri',
  841. VERBOSITY_MESSAGE_BG_COLOUR: 'Taustaväri',
  842. VERBOSITY_DEBUG: 'Korosta "piilotetut" postaus',
  843. CMF_CUSTOMISATIONS: 'Räätälöinnit',
  844. CMF_BTN_LOCATION: 'Puhdista syötteeni -painikkeen sijainti',
  845. CMF_BTN_OPTION: ['alhaalla vasemmalla', 'ylhäällä oikealle', 'pois käytöstä (käytä "Asetukset" User Script Commands -valikossa)'],
  846. CMF_DIALOG_LANGUAGE_LABEL: 'Puhdista syötteideni keskusteluruudun kieli',
  847. CMF_DIALOG_LANGUAGE: 'Suomi',
  848. CMF_DIALOG_LANGUAGE_DEFAULT: 'Käytä sivuston kieltä',
  849. GM_MENU_SETTINGS: 'Asetukset',
  850. CMF_DIALOG_LOCATION: 'Puhdista syötteeni -valintaikkunan sijainti',
  851. CMF_DIALOG_OPTION: ['vasen puoli', 'oikea puoli'],
  852. CMF_BORDER_COLOUR: 'Reunuksen väri',
  853. DLG_TIPS: 'Vinkkejä',
  854. DLG_TIPS_CONTENT: 'Selaimen välimuistin tyhjentäminen palauttaa asetuksesi oletusarvoihinsa.\n\nKäytä "Vie"- ja "Tuo"-painikkeita varmuuskopioidaksesi ja palauttaaksesi mukautetut asetukset.',
  855. DLG_BUTTONS: ['Tallentaa', 'Sulkea', 'Vienti', 'Tuonti', 'Nollaa'],
  856. DLG_FB_COLOUR_HINT: 'Jätä tyhjäksi käyttääksesi FB:n värimaailmaa',
  857. },
  858. // -- Français (France)
  859. fr: {
  860. LANGUAGE_DIRECTION: 'ltr',
  861. SPONSORED: 'Sponsorisé',
  862. NF_TABLIST_STORIES_REELS_ROOMS: 'Zone de liste de l\'onglet "Stories | Reels | Salons"',
  863. NF_STORIES: 'Stories',
  864. NF_SURVEY: 'Enquête',
  865. NF_PEOPLE_YOU_MAY_KNOW: 'Connaissez-vous...',
  866. NF_PAID_PARTNERSHIP: 'Partenariat rémunéré',
  867. NF_SPONSORED_PAID: 'Sponsorisé · Financé par ______',
  868. NF_SUGGESTIONS: 'Suggestions / Recommandations',
  869. NF_FOLLOW: 'Suivre',
  870. NF_PARTICIPATE: 'Participer',
  871. NF_REELS_SHORT_VIDEOS: 'Reels et vidéos courtes',
  872. NF_SHORT_REEL_VIDEO: 'Bobine/courte vidéo',
  873. NF_EVENTS_YOU_MAY_LIKE: 'Évènements qui pourraient vous intéresser',
  874. NF_ANIMATED_GIFS_POSTS: 'GIF animés',
  875. NF_ANIMATED_GIFS_PAUSE: 'Mettre en pause les GIF animés',
  876. NF_SHARES: '# partages',
  877. NF_LIKES_MAXIMUM: 'Nombre maximum de J\'aime',
  878. GF_PAID_PARTNERSHIP: 'Partenariat rémunéré',
  879. GF_SUGGESTIONS: 'Suggestions / Recommandations',
  880. GF_SHORT_REEL_VIDEO: 'Bobine/courte vidéo',
  881. GF_ANIMATED_GIFS_POSTS: 'GIF animés',
  882. GF_ANIMATED_GIFS_PAUSE: 'Mettre en pause les GIF animés',
  883. GF_SHARES: '# partages',
  884. VF_LIVE: 'EN DIRECT',
  885. VF_INSTAGRAM: 'Instagram',
  886. VF_DUPLICATE_VIDEOS: 'Vidéo en double',
  887. VF_ANIMATED_GIFS_PAUSE: 'Mettre en pause les GIF animés',
  888. PP_ANIMATED_GIFS_POSTS: 'GIF animés',
  889. PP_ANIMATED_GIFS_PAUSE: 'Mettre en pause les GIF animés',
  890. NF_BLOCKED_FEED: ['Fil de nouvelles', 'Flux de groupes', 'Flux de vidéos'],
  891. GF_BLOCKED_FEED: ['Fil de nouvelles', 'Flux de groupes', 'Flux de vidéos'],
  892. VF_BLOCKED_FEED: ['Fil de nouvelles', 'Flux de groupes', 'Flux de vidéos'],
  893. MP_BLOCKED_FEED: ['Flux de la place de marché'],
  894. PP_BLOCKED_FEED: '',
  895. OTHER_INFO_BOX_CORONAVIRUS: 'Coronavirus (encadré d\'information)',
  896. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Science du climat (encadré d\'information)',
  897. OTHER_INFO_BOX_SUBSCRIBE: 'S’abonner (encadré d\'information)',
  898. REELS_TITLE: 'Reels',
  899. REELS_CONTROLS: 'Afficher les contrôles vidéo',
  900. REELS_DISABLE_LOOPING: 'Désactiver la boucle',
  901. DLG_TITLE: 'Nettoyer mes flux',
  902. DLG_NF: 'Fil de nouvelles',
  903. DLG_GF: 'Flux de groupes',
  904. DLG_VF: 'Flux de vidéos',
  905. DLG_MP: 'Flux de la place de marché',
  906. DLG_PP: 'Profil / Page',
  907. DLG_OTHER: 'Section supplémentaire / information',
  908. DLG_BLOCK_TEXT_FILTER_TITLE: 'Filtre de texte',
  909. DLG_BLOCK_NEW_LINE: '(Séparez les mots ou les phrases par un saut de ligne, les expressions régulières sont prises en charge)',
  910. NF_BLOCKED_ENABLED: 'Activé',
  911. GF_BLOCKED_ENABLED: 'Activé',
  912. VF_BLOCKED_ENABLED: 'Activé',
  913. MP_BLOCKED_ENABLED: 'Activé',
  914. PP_BLOCKED_ENABLED: 'Activé',
  915. NF_BLOCKED_RE: 'Expressions régulières (RegExp)',
  916. GF_BLOCKED_RE: 'Expressions régulières (RegExp)',
  917. VF_BLOCKED_RE: 'Expressions régulières (RegExp)',
  918. MP_BLOCKED_RE: 'Expressions régulières (RegExp)',
  919. PP_BLOCKED_RE: 'Expressions régulières (RegExp)',
  920. DLG_VERBOSITY: 'Options pour les publications cachées',
  921. DLG_VERBOSITY_CAPTION: 'Afficher un libellé si une publication est masquée',
  922. VERBOSITY_MESSAGE: ['pas de libellé', 'Poste caché. Règle: ', ' posts cachés', '7 posts cachés ~ (uniquement dans le flux de groupes)'],
  923. VERBOSITY_MESSAGE_COLOUR: 'Couleur du texte',
  924. VERBOSITY_MESSAGE_BG_COLOUR: 'Couleur de fond',
  925. VERBOSITY_DEBUG: 'Mettez en surbrillance les messages « cachés »',
  926. CMF_CUSTOMISATIONS: 'Personnalisations',
  927. CMF_BTN_LOCATION: 'Emplacement du bouton Nettoyer mes flux',
  928. CMF_BTN_OPTION: ['en bas à gauche', 'en haut à droite', 'désactivé (utilisez "Paramètres" dans le menu Commandes de script utilisateur)'],
  929. CMF_DIALOG_LANGUAGE_LABEL: 'Nettoyer la langue de ma boîte de dialogue de flux',
  930. CMF_DIALOG_LANGUAGE: 'Français',
  931. CMF_DIALOG_LANGUAGE_DEFAULT: 'Utiliser la langue du site',
  932. GM_MENU_SETTINGS: 'Paramètres',
  933. CMF_DIALOG_LOCATION: 'Emplacement de la boîte de dialogue Nettoyer mes flux',
  934. CMF_DIALOG_OPTION: ['côté gauche', 'côté droit'],
  935. CMF_BORDER_COLOUR: 'Couleur de bordure',
  936. DLG_TIPS: 'Des astuces',
  937. DLG_TIPS_CONTENT: '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.',
  938. DLG_BUTTONS: ['Sauvegarder', 'Fermer', 'Exporter', 'Importer', 'Réinitialiser'],
  939. DLG_FB_COLOUR_HINT: 'Laissez vide pour utiliser le jeu de couleurs de FB',
  940. },
  941. // -- עִברִית (Hebrew)
  942. he: {
  943. LANGUAGE_DIRECTION: 'rtl',
  944. SPONSORED: 'ממומן',
  945. NF_TABLIST_STORIES_REELS_ROOMS: 'תיבת רשימה של כרטיסיות "סטוריז | Reels | חדרים"',
  946. NF_STORIES: 'סטוריז ',
  947. NF_SURVEY: 'סקר',
  948. NF_PEOPLE_YOU_MAY_KNOW: 'אנשים שאולי אתה מכיר',
  949. NF_PAID_PARTNERSHIP: 'שותפות בתשלום',
  950. NF_SPONSORED_PAID: 'ממומן · שולם על ידי ______',
  951. NF_SUGGESTIONS: 'הצעות / המלצות',
  952. NF_FOLLOW: 'עקוב',
  953. NF_PARTICIPATE: 'השתתף',
  954. NF_REELS_SHORT_VIDEOS: 'סרטוני Reels וקטעי וידאו קצרים',
  955. NF_SHORT_REEL_VIDEO: 'סליל/סרטון קצר',
  956. NF_EVENTS_YOU_MAY_LIKE: 'אירועים שאולי תאהבו',
  957. NF_ANIMATED_GIFS_POSTS: 'קובצי GIF מונפשים',
  958. NF_ANIMATED_GIFS_PAUSE: 'השהה קובצי GIF מונפ',
  959. NF_SHARES: '# שיתופים',
  960. NF_LIKES_MAXIMUM: 'מספר לייקים מקסימלי',
  961. GF_PAID_PARTNERSHIP: 'שותפות בתשלום',
  962. GF_SUGGESTIONS: 'הצעות / המלצות',
  963. GF_SHORT_REEL_VIDEO: 'סליל/סרטון קצר',
  964. GF_ANIMATED_GIFS_POSTS: 'קובצי GIF מונפשים',
  965. GF_ANIMATED_GIFS_PAUSE: 'השהה קובצי GIF מונפ',
  966. GF_SHARES: '# שיתופים',
  967. VF_LIVE: 'שידור חי',
  968. VF_INSTAGRAM: 'Instagram',
  969. VF_DUPLICATE_VIDEOS: 'וידאו כפול',
  970. VF_ANIMATED_GIFS_PAUSE: 'השהה קובצי GIF מונפ',
  971. PP_ANIMATED_GIFS_POSTS: 'קובצי GIF מונפשים',
  972. PP_ANIMATED_GIFS_PAUSE: 'השהה קובצי GIF מונפ',
  973. NF_BLOCKED_FEED: ['ניוז פיד', 'פיד קבוצות', 'צפה בפיד סרטונים'],
  974. GF_BLOCKED_FEED: ['ניוז פיד', 'פיד קבוצות', 'צפה בפיד סרטונים'],
  975. VF_BLOCKED_FEED: ['ניוז פיד', 'פיד קבוצות', 'צפה בפיד סרטונים'],
  976. MP_BLOCKED_FEED: ['זירת מסחר'],
  977. PP_BLOCKED_FEED: '',
  978. OTHER_INFO_BOX_CORONAVIRUS: 'וירוס קורונה (תיבת מידע)',
  979. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'מדע האקלים (תיבת מידע)',
  980. OTHER_INFO_BOX_SUBSCRIBE: 'הירשם (תיבת מידע)',
  981. REELS_TITLE: 'Reels', // -- FB's label
  982. REELS_CONTROLS: 'הצג אפשרויות בקרת וידאו',
  983. REELS_DISABLE_LOOPING: 'השבת לולאה',
  984. DLG_TITLE: 'תנקה את הזנות שלי',
  985. DLG_NF: 'ניוז פיד',
  986. DLG_GF: 'פיד קבוצות',
  987. DLG_VF: 'צפה בפיד הסרטונים',
  988. DLG_MP: 'זירת מסחר',
  989. DLG_PP: 'פרופיל / דף',
  990. DLG_OTHER: 'מדור משלים / מידע',
  991. DLG_BLOCK_TEXT_FILTER_TITLE: 'מסנן טקסט',
  992. DLG_BLOCK_NEW_LINE: '(הפרד מילים או ביטויים עם ירידת שורה, ביטויים רגולריים נתמכים)',
  993. NF_BLOCKED_ENABLED: 'מופעל',
  994. GF_BLOCKED_ENABLED: 'מופעל',
  995. VF_BLOCKED_ENABLED: 'מופעל',
  996. MP_BLOCKED_ENABLED: 'מופעל',
  997. PP_BLOCKED_ENABLED: 'מופעל',
  998. NF_BLOCKED_RE: 'ביטויים רגולריים (RegExp)',
  999. GF_BLOCKED_RE: 'ביטויים רגולריים (RegExp)',
  1000. VF_BLOCKED_RE: 'ביטויים רגולריים (RegExp)',
  1001. MP_BLOCKED_RE: 'ביטויים רגולריים (RegExp)',
  1002. PP_BLOCKED_RE: 'ביטויים רגולריים (RegExp)',
  1003. DLG_VERBOSITY: 'אפשרויות לפוסטים מוסתרים',
  1004. DLG_VERBOSITY_CAPTION: 'הצג תוית אם מאמר מוסתר',
  1005. VERBOSITY_MESSAGE: ['אין תווית', 'פוסט אחד מוסתר. כלל: ', ' פוסטים מוסתרים', '7 פוסטים מוסתרים ~ (רק בסדר חברים)'],
  1006. VERBOSITY_MESSAGE_COLOUR: 'צבע טקסט',
  1007. VERBOSITY_MESSAGE_BG_COLOUR: 'צבע הרקע',
  1008. VERBOSITY_DEBUG: 'הדגש פוסטים "מוסתרים"',
  1009. CMF_CUSTOMISATIONS: 'התאמות אישיות',
  1010. CMF_BTN_LOCATION: 'תנקה את הזנות שלי מיקום הכפתור',
  1011. CMF_BTN_OPTION: ['שמאל למטה', 'ימינה למעלה', 'מושבת (השתמש ב"הגדרות" בתפריט פקודות סקריפט משתמש)'],
  1012. CMF_DIALOG_LANGUAGE_LABEL: 'נקה את שפת תיקיית הדיאלוג שלי',
  1013. CMF_DIALOG_LANGUAGE: 'עִבְרִית',
  1014. CMF_DIALOG_LANGUAGE_DEFAULT: 'השתמש בשפת האתר',
  1015. GM_MENU_SETTINGS: 'ההגדרות',
  1016. CMF_DIALOG_LOCATION: 'מיקום תיבת הדו-שיח "נקה את ההזנות שלי"',
  1017. CMF_DIALOG_OPTION: ['צד שמאל', 'צד ימין'],
  1018. CMF_BORDER_COLOUR: 'צבע גבול',
  1019. DLG_TIPS: 'טיפים',
  1020. DLG_TIPS_CONTENT: 'מחיקת ההיסטורה בדפדפן תנקה את ההגדרות ותחזיר אותם לברירת המחדל.\n\nהשתמש ב"ייצא" ו"ייבא" כדי לגבות ולהחזיר את ההגדרות שלך',
  1021. DLG_BUTTONS: ['שמור', 'סגור', 'ייצא', 'ייבא', 'איפוס'],
  1022. DLG_FB_COLOUR_HINT: 'השאר ריק כדי להשתמש בערכת הצבעים של FB',
  1023. },
  1024. // -- Bahasa Indonesia (Indonesia)
  1025. id: {
  1026. LANGUAGE_DIRECTION: 'ltr',
  1027. SPONSORED: 'Bersponsor',
  1028. NF_TABLIST_STORIES_REELS_ROOMS: 'Kotak daftar tab "Cerita | Reels | Forum"',
  1029. NF_STORIES: 'Cerita',
  1030. NF_SURVEY: 'Survei',
  1031. NF_PEOPLE_YOU_MAY_KNOW: 'Orang yang Mungkin Anda Kenal',
  1032. NF_PAID_PARTNERSHIP: 'Kemitraan berbayar',
  1033. NF_SPONSORED_PAID: 'Disponsori · Dibayar oleh ______',
  1034. NF_SUGGESTIONS: 'Saran / Rekomendasi',
  1035. NF_FOLLOW: 'Ikuti',
  1036. NF_PARTICIPATE: 'Berpartisipasi',
  1037. NF_REELS_SHORT_VIDEOS: 'Reels dan Video Pendek',
  1038. NF_SHORT_REEL_VIDEO: 'Reel/video pendek',
  1039. NF_EVENTS_YOU_MAY_LIKE: 'Acara yang mungkin Anda sukai',
  1040. NF_ANIMATED_GIFS_POSTS: 'GIF animasi',
  1041. NF_ANIMATED_GIFS_PAUSE: 'Jeda GIF animasi',
  1042. NF_SHARES: '# Kali dibagikan',
  1043. NF_LIKES_MAXIMUM: 'Jumlah maksimum Suka',
  1044. GF_PAID_PARTNERSHIP: 'Kemitraan berbayar',
  1045. GF_SUGGESTIONS: 'Saran / Rekomendasi',
  1046. GF_SHORT_REEL_VIDEO: 'Reel/video pendek',
  1047. GF_ANIMATED_GIFS_POSTS: 'GIF animasi',
  1048. GF_ANIMATED_GIFS_PAUSE: 'Jeda GIF animasi',
  1049. GF_SHARES: '# Kali dibagikan',
  1050. VF_LIVE: 'LANGSUNG',
  1051. VF_INSTAGRAM: 'Instagram',
  1052. VF_DUPLICATE_VIDEOS: 'Video duplikat',
  1053. VF_ANIMATED_GIFS_PAUSE: 'Jeda GIF animasi',
  1054. PP_ANIMATED_GIFS_POSTS: 'GIF animasi',
  1055. PP_ANIMATED_GIFS_PAUSE: 'Jeda GIF animasi',
  1056. NF_BLOCKED_FEED: ['Umpan Berita', 'Umpan Grup', 'Umpan Video'],
  1057. GF_BLOCKED_FEED: ['Umpan Berita', 'Umpan Grup', 'Umpan Video'],
  1058. VF_BLOCKED_FEED: ['Umpan Berita', 'Umpan Grup', 'Umpan Video'],
  1059. MP_BLOCKED_FEED: ['Umpan Marketplace'],
  1060. PP_BLOCKED_FEED: '',
  1061. OTHER_INFO_BOX_CORONAVIRUS: 'Virus Corona (kotak informasi)',
  1062. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Ilmu iklim (kotak informasi)',
  1063. OTHER_INFO_BOX_SUBSCRIBE: 'Berlangganan (kotak informasi)',
  1064. REELS_TITLE: 'Reels',
  1065. REELS_CONTROLS: 'Tampilkan kontrol video',
  1066. REELS_DISABLE_LOOPING: 'Nonaktifkan pengulangan',
  1067. DLG_TITLE: 'Bersihkan feed saya',
  1068. DLG_NF: 'Umpan Berita',
  1069. DLG_GF: 'Umpan Grup',
  1070. DLG_VF: 'Umpan Video',
  1071. DLG_MP: 'Umpan Marketplace',
  1072. DLG_PP: 'Profil / Halaman',
  1073. DLG_OTHER: 'Bagian tambahan / informasi',
  1074. DLG_BLOCK_TEXT_FILTER_TITLE: 'Filter teks',
  1075. DLG_BLOCK_NEW_LINE: '(Pisahkan kata atau frasa dengan jeda baris, Ekspresi Reguler didukung)',
  1076. NF_BLOCKED_ENABLED: 'Diaktifkan',
  1077. GF_BLOCKED_ENABLED: 'Diaktifkan',
  1078. VF_BLOCKED_ENABLED: 'Diaktifkan',
  1079. MP_BLOCKED_ENABLED: 'Diaktifkan',
  1080. PP_BLOCKED_ENABLED: 'Diaktifkan',
  1081. NF_BLOCKED_RE: 'Ekspresi Reguler (RegExp)',
  1082. GF_BLOCKED_RE: 'Ekspresi Reguler (RegExp)',
  1083. VF_BLOCKED_RE: 'Ekspresi Reguler (RegExp)',
  1084. MP_BLOCKED_RE: 'Ekspresi Reguler (RegExp)',
  1085. PP_BLOCKED_RE: 'Ekspresi Reguler (RegExp)',
  1086. DLG_VERBOSITY: 'Opsi untuk Postingan Tersembunyi',
  1087. DLG_VERBOSITY_CAPTION: 'Tampilkan label jika kiriman disembunyikan',
  1088. VERBOSITY_MESSAGE: ['tanpa label', 'Pos disembunyikan. Aturan: ', ' postingan disembunyikan', '7 postingan disembunyikan ~ (hanya di Feed Grup)'],
  1089. VERBOSITY_MESSAGE_COLOUR: 'Warna teks',
  1090. VERBOSITY_MESSAGE_BG_COLOUR: 'Warna latar belakang',
  1091. VERBOSITY_DEBUG: 'Sorot postingan "tersembunyi"',
  1092. CMF_CUSTOMISATIONS: 'Kustomisasi',
  1093. CMF_BTN_LOCATION: 'Lokasi tombol Bersihkan umpan saya',
  1094. CMF_BTN_OPTION: ['kiri bawah', 'kanan atas', 'dinonaktifkan (gunakan "Pengaturan" di menu Perintah Skrip Pengguna)'],
  1095. CMF_DIALOG_LANGUAGE_LABEL: 'Bersihkan bahasa kotak dialog umpan saya',
  1096. CMF_DIALOG_LANGUAGE: 'Bahasa Indonesia',
  1097. CMF_DIALOG_LANGUAGE_DEFAULT: 'Gunakan bahasa situs',
  1098. GM_MENU_SETTINGS: 'Pengaturan',
  1099. CMF_DIALOG_LOCATION: 'Lokasi kotak dialog Bersihkan umpan saya',
  1100. CMF_DIALOG_OPTION: ['sisi kiri', 'sisi kanan'],
  1101. CMF_BORDER_COLOUR: 'Warna perbatasan',
  1102. DLG_TIPS: 'Tips',
  1103. DLG_TIPS_CONTENT: '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.',
  1104. DLG_BUTTONS: ['Simpan', 'Tutup', 'Ekspor', 'Impor', 'Reset'],
  1105. DLG_FB_COLOUR_HINT: 'Biarkan kosong untuk menggunakan skema warna FB',
  1106. },
  1107. // -- Italino (Italy)
  1108. it: {
  1109. LANGUAGE_DIRECTION: 'ltr',
  1110. SPONSORED: 'Sponsorizzato',
  1111. NF_TABLIST_STORIES_REELS_ROOMS: 'Casella di riepilogo della scheda "Storie | Reels | Stanze"',
  1112. NF_STORIES: 'Storie',
  1113. NF_SURVEY: 'Sondaggio',
  1114. NF_PEOPLE_YOU_MAY_KNOW: 'Persone che potresti conoscere',
  1115. NF_PAID_PARTNERSHIP: 'Partnership pubblicizzata',
  1116. NF_SPONSORED_PAID: 'Sponsorizzato · Finanziato da ______',
  1117. NF_SUGGESTIONS: 'Suggerimenti / Raccomandazioni',
  1118. NF_FOLLOW: 'Segui',
  1119. NF_PARTICIPATE: 'Partecipare',
  1120. NF_REELS_SHORT_VIDEOS: 'Reel e video brevi',
  1121. NF_SHORT_REEL_VIDEO: 'Bobina/breve video',
  1122. NF_EVENTS_YOU_MAY_LIKE: 'Eventi che potrebbero piacerti',
  1123. NF_ANIMATED_GIFS_POSTS: 'GIF animate',
  1124. NF_ANIMATED_GIFS_PAUSE: 'Metti in pausa le GIF animate',
  1125. NF_SHARES: 'Condivisioni: #',
  1126. NF_LIKES_MAXIMUM: 'Numero massimo di Mi piace',
  1127. GF_PAID_PARTNERSHIP: 'Partnership pubblicizzata',
  1128. GF_SUGGESTIONS: 'Suggerimenti / Raccomandazioni',
  1129. GF_SHORT_REEL_VIDEO: 'Bobina/breve video',
  1130. GF_ANIMATED_GIFS_POSTS: 'GIF animate',
  1131. GF_ANIMATED_GIFS_PAUSE: 'Metti in pausa le GIF animate',
  1132. GF_SHARES: 'Condivisioni: #',
  1133. VF_LIVE: 'IN DIRETTA',
  1134. VF_INSTAGRAM: 'Instagram',
  1135. VF_DUPLICATE_VIDEOS: 'Video duplicato',
  1136. VF_ANIMATED_GIFS_PAUSE: 'Metti in pausa le GIF animate',
  1137. PP_ANIMATED_GIFS_POSTS: 'GIF animate',
  1138. PP_ANIMATED_GIFS_PAUSE: 'Metti in pausa le GIF animate',
  1139. NF_BLOCKED_FEED: ['Feed di notizie', 'Feed di gruppo', 'Feed di video'],
  1140. GF_BLOCKED_FEED: ['Feed di notizie', 'Feed di gruppo', 'Feed di video'],
  1141. VF_BLOCKED_FEED: ['Feed di notizie', 'Feed di gruppo', 'Feed di video'],
  1142. MP_BLOCKED_FEED: ['Feed id Marketplace'],
  1143. PP_BLOCKED_FEED: '',
  1144. OTHER_INFO_BOX_CORONAVIRUS: 'Coronavirus (casella informativa)',
  1145. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Scienza del clima (casella informativa)',
  1146. OTHER_INFO_BOX_SUBSCRIBE: 'Iscriviti (casella informativa)',
  1147. REELS_TITLE: 'Reels',
  1148. REELS_CONTROLS: 'Mostra controlli video',
  1149. REELS_DISABLE_LOOPING: 'Disattiva ripetizione',
  1150. DLG_TITLE: 'Pulisci i miei feed',
  1151. DLG_NF: 'Feed di notizie',
  1152. DLG_GF: 'Feed di gruppo',
  1153. DLG_VF: 'Feed di video',
  1154. DLG_MP: 'Feed id Marketplace',
  1155. DLG_PP: 'Profilo / Pagina',
  1156. DLG_OTHER: 'Sezione supplementare / informativa',
  1157. DLG_BLOCK_TEXT_FILTER_TITLE: 'Filtro di testo',
  1158. DLG_BLOCK_NEW_LINE: '(Separa parole o frasi con un\'interruzione di riga, le espressioni regolari sono supportate)',
  1159. NF_BLOCKED_ENABLED: 'Abilita opzione',
  1160. GF_BLOCKED_ENABLED: 'Abilita opzione',
  1161. VF_BLOCKED_ENABLED: 'Abilita opzione',
  1162. MP_BLOCKED_ENABLED: 'Abilita opzione',
  1163. PP_BLOCKED_ENABLED: 'Abilita opzione',
  1164. NF_BLOCKED_RE: 'Espressioni regolari (RegExp)',
  1165. GF_BLOCKED_RE: 'Espressioni regolari (RegExp)',
  1166. VF_BLOCKED_RE: 'Espressioni regolari (RegExp)',
  1167. MP_BLOCKED_RE: 'Espressioni regolari (RegExp)',
  1168. PP_BLOCKED_RE: 'Espressioni regolari (RegExp)',
  1169. DLG_VERBOSITY: 'Opzioni per post nascosti',
  1170. DLG_VERBOSITY_CAPTION: 'Mostrare un\'etichetta se un post è nascosto',
  1171. VERBOSITY_MESSAGE: ['nessuna etichetta', 'Post nascosto. Regola: ', ' post nascosti', '7 post nascosti ~ (solo nel Feed di Gruppi)'],
  1172. VERBOSITY_MESSAGE_COLOUR: 'Colore del testo',
  1173. VERBOSITY_MESSAGE_BG_COLOUR: 'Colore di sfondo',
  1174. VERBOSITY_DEBUG: 'Evidenzia i post "nascosti"',
  1175. CMF_CUSTOMISATIONS: 'Personalizzazioni',
  1176. CMF_BTN_LOCATION: 'Posizione del pulsante Pulisci i miei feed',
  1177. CMF_BTN_OPTION: ['in basso a sinistra', 'in alto a destra', 'disabilitato (usa "Impostazioni" nel menu Comandi script utente)'],
  1178. CMF_DIALOG_LANGUAGE_LABEL: 'Pulisci la lingua della mia casella di dialogo feed',
  1179. CMF_DIALOG_LANGUAGE: 'Italiano',
  1180. CMF_DIALOG_LANGUAGE_DEFAULT: 'Usa la lingua del sito',
  1181. GM_MENU_SETTINGS: 'Impostazioni',
  1182. CMF_DIALOG_LOCATION: 'Posizione della finestra di dialogo Pulisci i miei feed',
  1183. CMF_DIALOG_OPTION: ['lato sinistro', 'lato destro'],
  1184. CMF_BORDER_COLOUR: 'Colore del bordo',
  1185. DLG_TIPS: 'Suggerimenti',
  1186. DLG_TIPS_CONTENT: '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.',
  1187. DLG_BUTTONS: ['Salva', 'Chiudi', 'Esportare', 'Importare', 'Ripristina'],
  1188. DLG_FB_COLOUR_HINT: 'Lascia vuoto per usare la combinazione di colori di FB',
  1189. },
  1190. // -- Japanese (Japan)
  1191. ja: {
  1192. LANGUAGE_DIRECTION: 'ltr',
  1193. SPONSORED: '広告',
  1194. NF_TABLIST_STORIES_REELS_ROOMS: '「Stories | Reels | Rooms」タブのリストボックス',
  1195. NF_STORIES: 'Stories',
  1196. NF_SURVEY: 'アンケート',
  1197. NF_PEOPLE_YOU_MAY_KNOW: 'あなたが知っているかもしれない人々',
  1198. NF_PAID_PARTNERSHIP: '有償パートナーシップ',
  1199. NF_SPONSORED_PAID: '後援 · ______ による支払い',
  1200. NF_SUGGESTIONS: '提案/推奨事項',
  1201. NF_FOLLOW: 'フォロー',
  1202. NF_PARTICIPATE: '参加する',
  1203. NF_REELS_SHORT_VIDEOS: 'リールとショート動画',
  1204. NF_SHORT_REEL_VIDEO: 'リール/ショートビデオ',
  1205. NF_EVENTS_YOU_MAY_LIKE: 'リール/ショートビデオ',
  1206. NF_ANIMATED_GIFS_POSTS: 'アニメーション GIF',
  1207. NF_ANIMATED_GIFS_PAUSE: 'アニメーション GIF を一時停止する',
  1208. NF_SHARES: 'シェア#件',
  1209. NF_LIKES_MAXIMUM: '「いいね!」の最大数',
  1210. GF_PAID_PARTNERSHIP: '有償パートナーシップ',
  1211. GF_SUGGESTIONS: '提案/推奨事項',
  1212. GF_SHORT_REEL_VIDEO: 'リールとショートビデオ',
  1213. GF_ANIMATED_GIFS_POSTS: 'アニメーション GIF',
  1214. GF_ANIMATED_GIFS_PAUSE: 'アニメーション GIF を一時停止する',
  1215. GF_SHARES: 'シェア#件',
  1216. VF_LIVE: 'ライブ',
  1217. VF_INSTAGRAM: 'Instagram',
  1218. VF_DUPLICATE_VIDEOS: '重複する動画',
  1219. VF_ANIMATED_GIFS_PAUSE: 'アニメーション GIF を一時停止する',
  1220. PP_ANIMATED_GIFS_POSTS: 'アニメーション GIF',
  1221. PP_ANIMATED_GIFS_PAUSE: 'アニメーション GIF を一時停止する',
  1222. NF_BLOCKED_FEED: ['ニュースフィード', 'グループ フィード', '動画フィード'],
  1223. GF_BLOCKED_FEED: ['ニュースフィード', 'グループ フィード', '動画フィード'],
  1224. VF_BLOCKED_FEED: ['ニュースフィード', 'グループ フィード', '動画フィード'],
  1225. MP_BLOCKED_FEED: ['マーケットプレイス フィード'],
  1226. PP_BLOCKED_FEED: '',
  1227. OTHER_INFO_BOX_CORONAVIRUS: 'コロナウイルス(インフォメーションボックス)',
  1228. OTHER_INFO_BOX_CLIMATE_SCIENCE: '気候科学(情報ボックス)',
  1229. OTHER_INFO_BOX_SUBSCRIBE: '購読する(情報ボックス)',
  1230. REELS_TITLE: 'リール動画',
  1231. REELS_CONTROLS: 'ビデオコントロールを表示',
  1232. REELS_DISABLE_LOOPING: 'ループの無効化',
  1233. DLG_TITLE: 'フィードをクリーンアップ',
  1234. DLG_NF: 'ニュースフィード',
  1235. DLG_GF: 'グループ フィード',
  1236. DLG_VF: '動画フィード',
  1237. DLG_MP: 'マーケットプレイス フィード',
  1238. DLG_PP: 'プロフィール / ページ',
  1239. DLG_OTHER: '補足・情報セクション',
  1240. DLG_BLOCK_TEXT_FILTER_TITLE: 'テキストフィルター',
  1241. DLG_BLOCK_NEW_LINE: '(単語やフレーズを改行で区切ってください。正規表現がサポートされています)',
  1242. NF_BLOCKED_ENABLED: '有効化',
  1243. GF_BLOCKED_ENABLED: '有効化',
  1244. VF_BLOCKED_ENABLED: '有効化',
  1245. MP_BLOCKED_ENABLED: '有効化',
  1246. PP_BLOCKED_ENABLED: '有効化',
  1247. NF_BLOCKED_RE: '正規表現 (RegExp)',
  1248. GF_BLOCKED_RE: '正規表現 (RegExp)',
  1249. VF_BLOCKED_RE: '正規表現 (RegExp)',
  1250. MP_BLOCKED_RE: '正規表現 (RegExp)',
  1251. PP_BLOCKED_RE: '正規表現 (RegExp)',
  1252. DLG_VERBOSITY: '非表示投稿のオプション',
  1253. DLG_VERBOSITY_CAPTION: '投稿が非表示の場合にラベルを表示する',
  1254. VERBOSITY_MESSAGE: ['ラベルなし', '投稿を非表示にしました。 ルール: ', ' 件の投稿が非表示', '7件の投稿が非表示 ~ (グループフィードのみ)'],
  1255. VERBOSITY_MESSAGE_COLOUR: 'テキストの色',
  1256. VERBOSITY_MESSAGE_BG_COLOUR: '背景色',
  1257. VERBOSITY_DEBUG: '「非表示」の投稿を強調表示する',
  1258. CMF_CUSTOMISATIONS: 'カスタマイズ',
  1259. CMF_BTN_LOCATION: '「フィードをクリーンアップ」ボタンの配置',
  1260. CMF_BTN_OPTION: ['下左', '上右', '無効 ([ユーザー スクリプト コマンド] メニューの [設定] を使用)'],
  1261. CMF_DIALOG_LANGUAGE_LABEL: 'フィードのダイアログボックス言語をクリーンアップ',
  1262. CMF_DIALOG_LANGUAGE: '日本語',
  1263. CMF_DIALOG_LANGUAGE_DEFAULT: 'サイトの言語を使用',
  1264. GM_MENU_SETTINGS: '設定',
  1265. CMF_DIALOG_LOCATION: '[フィードの消去] ダイアログ ボックスの配置',
  1266. CMF_DIALOG_OPTION: ['左側', '右側'],
  1267. CMF_BORDER_COLOUR: 'ボーダーカラー',
  1268. DLG_TIPS: 'ヒント',
  1269. DLG_TIPS_CONTENT: 'ブラウザのキャッシュをクリアすると、設定がデフォルト値にリセットされます。\n\n[エクスポート] および [インポート] ボタンを使用して、カスタマイズした設定をバックアップおよび復元します。',
  1270. DLG_BUTTONS: ['セーブ', 'クローズ', '輸出する', '輸入', 'リセット'],
  1271. DLG_FB_COLOUR_HINT: '空白のままにすると、FB の配色が使用されます',
  1272. },
  1273. // -- Latviešu (Latvia)
  1274. lv: {
  1275. LANGUAGE_DIRECTION: 'ltr',
  1276. SPONSORED: 'Apmaksāta reklāma',
  1277. NF_TABLIST_STORIES_REELS_ROOMS: 'Cilnes "Stāsti | Video rullīši | Rooms" sarakstlodziņš',
  1278. NF_STORIES: 'Stāsti',
  1279. NF_SURVEY: 'Aptauja',
  1280. NF_PEOPLE_YOU_MAY_KNOW: 'Cilvēki, kurus tu varētu pazīt',
  1281. NF_PAID_PARTNERSHIP: 'Apmaksāta sadarbība',
  1282. NF_SPONSORED_PAID: 'Apmaksāta reklāma · Apmaksā ______',
  1283. NF_SUGGESTIONS: 'Ieteikumi',
  1284. NF_FOLLOW: 'Sekot',
  1285. NF_PARTICIPATE: 'Piedalīties',
  1286. NF_REELS_SHORT_VIDEOS: 'Reels un īsi videoklipi',
  1287. NF_SHORT_REEL_VIDEO: 'Ruļļa/īss video',
  1288. NF_EVENTS_YOU_MAY_LIKE: 'Notikumi, kas jums varētu patikt',
  1289. NF_ANIMATED_GIFS_POSTS: 'Animētos GIF',
  1290. NF_ANIMATED_GIFS_PAUSE: 'Apturiet animētos GIF',
  1291. NF_SHARES: '# dalījās',
  1292. NF_LIKES_MAXIMUM: 'Maksimālais atzīmju Patīk skaits',
  1293. GF_PAID_PARTNERSHIP: 'Apmaksāta sadarbība',
  1294. GF_SUGGESTIONS: 'Ieteikumi',
  1295. GF_SHORT_REEL_VIDEO: 'Ruļļa/īss video',
  1296. GF_ANIMATED_GIFS_POSTS: 'Animētos GIF',
  1297. GF_ANIMATED_GIFS_PAUSE: 'Apturiet animētos GIF',
  1298. GF_SHARES: '# dalījās',
  1299. VF_LIVE: 'TIEŠRAIDE',
  1300. VF_INSTAGRAM: 'Instagram',
  1301. VF_DUPLICATE_VIDEOS: 'Dublētais video',
  1302. VF_ANIMATED_GIFS_PAUSE: 'Apturiet animētos GIF',
  1303. PP_ANIMATED_GIFS_POSTS: 'Animētos GIF',
  1304. PP_ANIMATED_GIFS_PAUSE: 'Apturiet animētos GIF',
  1305. NF_BLOCKED_FEED: ['Ziņu plūsma', 'Grupu plūsma', 'Video plūsma'],
  1306. GF_BLOCKED_FEED: ['Ziņu plūsma', 'Grupu plūsma', 'Video plūsma'],
  1307. VF_BLOCKED_FEED: ['Ziņu plūsma', 'Grupu plūsma', 'Video plūsma'],
  1308. MP_BLOCKED_FEED: ['Marketplace'],
  1309. PP_BLOCKED_FEED: '',
  1310. OTHER_INFO_BOX_CORONAVIRUS: 'Koronavīruss (informācijas lodziņš)',
  1311. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Klimata zinātne (informācijas lodziņš)',
  1312. OTHER_INFO_BOX_SUBSCRIBE: 'Abonēt (informācijas lodziņš)',
  1313. REELS_TITLE: 'Reels',
  1314. REELS_CONTROLS: 'Rādīt video vadīklus',
  1315. REELS_DISABLE_LOOPING: 'Atspējot cilpotošanu',
  1316. DLG_TITLE: 'Tīrīt manas plūsmas',
  1317. DLG_NF: 'Ziņu plūsma',
  1318. DLG_GF: 'Grupu plūsma',
  1319. DLG_VF: 'Video plūsma',
  1320. DLG_MP: 'Marketplace',
  1321. DLG_PP: 'Profils / Lapa',
  1322. DLG_OTHER: 'Papildu / informācija sadaļa',
  1323. DLG_BLOCK_TEXT_FILTER_TITLE: 'Teksta filtrs',
  1324. DLG_BLOCK_NEW_LINE: '(Atdaliet vārdus vai frāzes ar rindkopu pārtraukumu, regulārie izteicieni tiek atbalstīti)',
  1325. NF_BLOCKED_ENABLED: 'Iespējots',
  1326. GF_BLOCKED_ENABLED: 'Iespējots',
  1327. VF_BLOCKED_ENABLED: 'Iespējots',
  1328. MP_BLOCKED_ENABLED: 'Iespējots',
  1329. PP_BLOCKED_ENABLED: 'Iespējots',
  1330. NF_BLOCKED_RE: 'Regulārās izteiksmes (RegExp)',
  1331. GF_BLOCKED_RE: 'Regulārās izteiksmes (RegExp)',
  1332. VF_BLOCKED_RE: 'Regulārās izteiksmes (RegExp)',
  1333. MP_BLOCKED_RE: 'Regulārās izteiksmes (RegExp)',
  1334. PP_BLOCKED_RE: 'Regulārās izteiksmes (RegExp)',
  1335. DLG_VERBOSITY: 'Slēpto ierakstu iespējas',
  1336. DLG_VERBOSITY_CAPTION: 'Rādīt etiķeti, ja raksts ir paslēpts',
  1337. VERBOSITY_MESSAGE: ['nav nekāda ziņojuma', 'Ziņa ir paslēpta. Noteikums: ', ' ziņas ir paslēptas', '7 ziņas paslēptas ~ (tikai Grupu plūsmē)'],
  1338. VERBOSITY_MESSAGE_COLOUR: 'Teksta krāsa',
  1339. VERBOSITY_MESSAGE_BG_COLOUR: 'Fona krāsa',
  1340. VERBOSITY_DEBUG: 'Izceliet "slēptos" rakstus',
  1341. CMF_CUSTOMISATIONS: 'Personalizēšana',
  1342. CMF_BTN_LOCATION: 'Pogas Tīrīt manas plūsmas atrašanās vieta',
  1343. CMF_BTN_OPTION: ['apakšējā kreisajā stūrī', 'augšējā labajā stūrī', 'atspējota (lietotāja skripta komandu izvēlnē izmantojiet sadaļu Iestatījumi)'],
  1344. CMF_DIALOG_LANGUAGE_LABEL: 'Tīrīt manu barošanas kastē dialoga valodu',
  1345. CMF_DIALOG_LANGUAGE: 'Latviešu',
  1346. CMF_DIALOG_LANGUAGE_DEFAULT: 'Izmantot vietnes valodu',
  1347. GM_MENU_SETTINGS: 'Iestatījumi',
  1348. CMF_DIALOG_LOCATION: 'Dialoglodziņa Tīrīt manas plūsmas atrašanās vieta',
  1349. CMF_DIALOG_OPTION: ['kreisā puse', 'labā puse'],
  1350. CMF_BORDER_COLOUR: 'Apmales krāsa',
  1351. DLG_TIPS: 'Padomi',
  1352. DLG_TIPS_CONTENT: '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.',
  1353. DLG_BUTTONS: ['Saglabājiet', 'Aizveriet', 'Eksportēt', 'Importēt', 'Atiestatīt'],
  1354. DLG_FB_COLOUR_HINT: 'Atstājiet tukšu, lai izmantotu FB krāsu shēmu',
  1355. },
  1356. // -- Nederlands (Netherlands)
  1357. nl: {
  1358. LANGUAGE_DIRECTION: 'ltr',
  1359. SPONSORED: 'Gesponsord',
  1360. NF_TABLIST_STORIES_REELS_ROOMS: 'Keuzelijst tabblad "Verhalen | Reels | Ruimtes"',
  1361. NF_STORIES: 'Verhalen',
  1362. NF_SURVEY: 'Vragenlijst',
  1363. NF_PEOPLE_YOU_MAY_KNOW: 'Mensen die je misschien kent',
  1364. NF_PAID_PARTNERSHIP: 'Betaald partnerschap',
  1365. NF_SPONSORED_PAID: 'Gesponsord · Betaald door ______',
  1366. NF_SUGGESTIONS: 'Suggesties / Aanbevelingen',
  1367. NF_FOLLOW: 'Volgen',
  1368. NF_PARTICIPATE: 'Deelnemen',
  1369. NF_REELS_SHORT_VIDEOS: 'Reels en korte video\'s',
  1370. NF_SHORT_REEL_VIDEO: 'Spoel/korte video',
  1371. NF_EVENTS_YOU_MAY_LIKE: 'Evenementen die je misschien leuk vindt',
  1372. NF_ANIMATED_GIFS_POSTS: 'Geanimeerde GIF\'s',
  1373. NF_ANIMATED_GIFS_PAUSE: 'Geanimeerde GIF\'s pauzeren',
  1374. NF_SHARES: '# keer gedeeld',
  1375. NF_LIKES_MAXIMUM: 'Maximaal aantal likes',
  1376. GF_PAID_PARTNERSHIP: 'Betaald partnerschap',
  1377. GF_SUGGESTIONS: 'Suggesties / Aanbevelingen',
  1378. GF_SHORT_REEL_VIDEO: 'Spoel/korte video',
  1379. GF_ANIMATED_GIFS_POSTS: 'Geanimeerde GIF\'s',
  1380. GF_ANIMATED_GIFS_PAUSE: 'Geanimeerde GIF\'s pauzeren',
  1381. GF_SHARES: '# keer gedeeld',
  1382. VF_LIVE: 'LIVE',
  1383. VF_INSTAGRAM: 'Instagram',
  1384. VF_DUPLICATE_VIDEOS: 'Dubbel video',
  1385. VF_ANIMATED_GIFS_PAUSE: 'Geanimeerde GIF\'s pauzeren',
  1386. PP_ANIMATED_GIFS_POSTS: 'Geanimeerde GIF\'s',
  1387. PP_ANIMATED_GIFS_PAUSE: 'Geanimeerde GIF\'s pauzeren',
  1388. NF_BLOCKED_FEED: ['Nieuwsfeed', 'Groepsfeed', 'Videofeed'],
  1389. GF_BLOCKED_FEED: ['Nieuwsfeed', 'Groepsfeed', 'Videofeed'],
  1390. VF_BLOCKED_FEED: ['Nieuwsfeed', 'Groepsfeed', 'Videofeed'],
  1391. MP_BLOCKED_FEED: ['Marktplaatsfeed'],
  1392. PP_BLOCKED_FEED: '',
  1393. OTHER_INFO_BOX_CORONAVIRUS: 'Coronavirus (informatiebox)',
  1394. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Klimaatwetenschap (informatiebox)',
  1395. OTHER_INFO_BOX_SUBSCRIBE: 'Abonneren (informatievak)',
  1396. REELS_TITLE: 'Reels',
  1397. REELS_CONTROLS: 'Toon video bedieningselementen',
  1398. REELS_DISABLE_LOOPING: 'Herhalen uitschakelen',
  1399. DLG_TITLE: 'Schoon mijn feeds',
  1400. DLG_NF: 'Nieuwsfeed',
  1401. DLG_GF: 'Groepsfeed',
  1402. DLG_VF: 'Videofeed',
  1403. DLG_MP: 'Marktplaatsfeed',
  1404. DLG_PP: 'Profiel / Pagina',
  1405. DLG_OTHER: 'Aanvullende / informatie sectie',
  1406. DLG_BLOCK_TEXT_FILTER_TITLE: 'Tekstfilter',
  1407. DLG_BLOCK_NEW_LINE: '(Scheid woorden of zinnen met een regeleinde, reguliere expressies worden ondersteund)',
  1408. NF_BLOCKED_ENABLED: 'Ingeschakeld',
  1409. GF_BLOCKED_ENABLED: 'Ingeschakeld',
  1410. VF_BLOCKED_ENABLED: 'Ingeschakeld',
  1411. MP_BLOCKED_ENABLED: 'Ingeschakeld',
  1412. PP_BLOCKED_ENABLED: 'Ingeschakeld',
  1413. NF_BLOCKED_RE: 'Reguliere expressies (RegExp)',
  1414. GF_BLOCKED_RE: 'Reguliere expressies (RegExp)',
  1415. VF_BLOCKED_RE: 'Reguliere expressies (RegExp)',
  1416. MP_BLOCKED_RE: 'Reguliere expressies (RegExp)',
  1417. PP_BLOCKED_RE: 'Reguliere expressies (RegExp)',
  1418. DLG_VERBOSITY: 'Opties voor verborgen berichten',
  1419. DLG_VERBOSITY_CAPTION: 'Toon een label als een artikel verborgen is',
  1420. VERBOSITY_MESSAGE: ['geen label', 'Post verborgen. Regel: ', ' posts verborgen', '7 posts verborgen ~ (alleen in Groepen Feed)'],
  1421. VERBOSITY_MESSAGE_COLOUR: 'Tekstkleur',
  1422. VERBOSITY_MESSAGE_BG_COLOUR: 'Achtergrondkleur',
  1423. VERBOSITY_DEBUG: 'Highlight "verborgen" artikelen',
  1424. CMF_CUSTOMISATIONS: 'Personalisaties',
  1425. CMF_BTN_LOCATION: 'Locatie van de knop Mijn feeds opschonen',
  1426. CMF_BTN_OPTION: ['linksonder', 'rechtsboven', 'uitgeschakeld (gebruik "Instellingen" in het menu Gebruikersscriptopdrachten)'],
  1427. CMF_DIALOG_LANGUAGE_LABEL: 'Maak mijn feeds dialoogvensterstaal schoon',
  1428. CMF_DIALOG_LANGUAGE: 'Nederlands',
  1429. CMF_DIALOG_LANGUAGE_DEFAULT: 'Site-taal gebruiken',
  1430. GM_MENU_SETTINGS: 'Instellingen',
  1431. CMF_DIALOG_LOCATION: 'Locatie van het dialoogvenster Mijn feeds opschonen',
  1432. CMF_DIALOG_OPTION: ['linkerkant', 'rechterkant'],
  1433. CMF_BORDER_COLOUR: 'Randkleur',
  1434. DLG_TIPS: 'Tips',
  1435. DLG_TIPS_CONTENT: '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.',
  1436. DLG_BUTTONS: ['Opslaan', 'Sluiten', 'Exporteren', 'Importeren', 'Reset'],
  1437. DLG_FB_COLOUR_HINT: 'Laat leeg om het kleurenschema van FB te gebruiken',
  1438. },
  1439. // -- Polski (Poland)
  1440. pl: {
  1441. LANGUAGE_DIRECTION: 'ltr',
  1442. SPONSORED: 'Sponsorowane',
  1443. NF_TABLIST_STORIES_REELS_ROOMS: 'Pole listy zakładki "Relacje | Reels | Pokoje"',
  1444. NF_STORIES: 'Relacje',
  1445. NF_SURVEY: 'Badanie',
  1446. NF_PEOPLE_YOU_MAY_KNOW: 'Osoby, które możesz znać',
  1447. NF_PAID_PARTNERSHIP: 'Post sponsorowany',
  1448. NF_SPONSORED_PAID: 'Sponsorowane · Opłacona przez ______',
  1449. NF_SUGGESTIONS: 'Sugestie / Zalecenia',
  1450. NF_FOLLOW: 'Obserwuj',
  1451. NF_PARTICIPATE: 'Uczestniczyć',
  1452. NF_REELS_SHORT_VIDEOS: 'Rolki i krótkie filmy',
  1453. NF_SHORT_REEL_VIDEO: 'Reel/krótki film',
  1454. NF_EVENTS_YOU_MAY_LIKE: 'Wydarzenia, które mogą Ci się spodobać',
  1455. NF_ANIMATED_GIFS_POSTS: 'Animowane GIF-y',
  1456. NF_ANIMATED_GIFS_PAUSE: 'Wstrzymaj animowane GIF-y',
  1457. NF_SHARES: '# udostępnienia',
  1458. NF_LIKES_MAXIMUM: 'Maksymalna ilość "Lubię to!"',
  1459. GF_PAID_PARTNERSHIP: 'Post sponsorowany',
  1460. GF_SUGGESTIONS: 'Sugestie / Zalecenia',
  1461. GF_SHORT_REEL_VIDEO: 'Reel/krótki film',
  1462. GF_ANIMATED_GIFS_POSTS: 'Animowane GIF-y',
  1463. GF_ANIMATED_GIFS_PAUSE: 'Wstrzymaj animowane GIF-y',
  1464. GF_SHARES: '# udostępnienia',
  1465. VF_LIVE: 'NA ŻYWO',
  1466. VF_INSTAGRAM: 'Instagram',
  1467. VF_DUPLICATE_VIDEOS: 'Duplikat wideo',
  1468. VF_ANIMATED_GIFS_PAUSE: 'Wstrzymaj animowane GIF-y',
  1469. PP_ANIMATED_GIFS_POSTS: 'Animowane GIF-y',
  1470. PP_ANIMATED_GIFS_PAUSE: 'Wstrzymaj animowane GIF-y',
  1471. NF_BLOCKED_FEED: ['Kanał aktualności', 'Kanał grup', 'Kanał wideo'],
  1472. GF_BLOCKED_FEED: ['Kanał aktualności', 'Kanał grup', 'Kanał wideo'],
  1473. VF_BLOCKED_FEED: ['Kanał aktualności', 'Kanał grup', 'Kanał wideo'],
  1474. MP_BLOCKED_FEED: ['Kanał Marketplace'],
  1475. PP_BLOCKED_FEED: '',
  1476. OTHER_INFO_BOX_CORONAVIRUS: 'Koronawirus (skrzynka informacyjna)',
  1477. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Nauka o klimacie (skrzynka informacyjna)',
  1478. OTHER_INFO_BOX_SUBSCRIBE: 'Subskrybuj (pole informacyjne)',
  1479. REELS_TITLE: 'Reels',
  1480. REELS_CONTROLS: 'Pokaż sterowanie wideo',
  1481. REELS_DISABLE_LOOPING: 'Wyłącz pętlę',
  1482. DLG_TITLE: 'Wyczyść moje kanały',
  1483. DLG_NF: 'Kanał aktualności',
  1484. DLG_GF: 'Kanał grup',
  1485. DLG_VF: 'Kanał wideo',
  1486. DLG_MP: 'Kanał Marketplace',
  1487. DLG_PP: 'Profil / Strona',
  1488. DLG_OTHER: 'Sekcja uzupełniająca/informacyjna',
  1489. DLG_BLOCK_TEXT_FILTER_TITLE: 'Filtr tekstu',
  1490. DLG_BLOCK_NEW_LINE: '(Oddziel słowa lub frazy za pomocą znaku nowej linii, wyrażenia regularne są obsługiwane)',
  1491. NF_BLOCKED_ENABLED: 'Włączone',
  1492. GF_BLOCKED_ENABLED: 'Włączone',
  1493. VF_BLOCKED_ENABLED: 'Włączone',
  1494. MP_BLOCKED_ENABLED: 'Włączone',
  1495. PP_BLOCKED_ENABLED: 'Włączone',
  1496. NF_BLOCKED_RE: 'Wyrażenia regularne (RegExp)',
  1497. GF_BLOCKED_RE: 'Wyrażenia regularne (RegExp)',
  1498. VF_BLOCKED_RE: 'Wyrażenia regularne (RegExp)',
  1499. MP_BLOCKED_RE: 'Wyrażenia regularne (RegExp)',
  1500. PP_BLOCKED_RE: 'Wyrażenia regularne (RegExp)',
  1501. DLG_VERBOSITY: 'Opcje dla ukrytych postów',
  1502. DLG_VERBOSITY_CAPTION: 'Pokaż etykietę, jeśli artykuł jest ukryty',
  1503. VERBOSITY_MESSAGE: ['brak etykiety', 'Ukryto 1 post. Reguła: ', ' posty ukryte', '7 posty ukryte ~ (tylko w Kanałach Grup)'],
  1504. VERBOSITY_MESSAGE_COLOUR: 'Kolor tekstu',
  1505. VERBOSITY_MESSAGE_BG_COLOUR: 'Kolor tła',
  1506. VERBOSITY_DEBUG: 'Wyróżnij „ukryte” posty',
  1507. CMF_CUSTOMISATIONS: 'Personalizacja',
  1508. CMF_BTN_LOCATION: 'Lokalizacja przycisku Wyczyść moje kanały',
  1509. CMF_BTN_OPTION: ['lewy dolny róg', 'prawy górny róg', 'wyłączone (użyj "Ustawienia" w menu Polecenia skryptu użytkownika)'],
  1510. CMF_DIALOG_LANGUAGE_LABEL: 'Wyczyść język mojego okna dialogowego z wiadomościami',
  1511. CMF_DIALOG_LANGUAGE: 'Polski',
  1512. CMF_DIALOG_LANGUAGE_DEFAULT: 'Użyj języka witryny',
  1513. GM_MENU_SETTINGS: 'Ustawienia',
  1514. CMF_DIALOG_LOCATION: 'Lokalizacja okna dialogowego Wyczyść moje kanały',
  1515. CMF_DIALOG_OPTION: ['lewa strona', 'prawa strona'],
  1516. CMF_BORDER_COLOUR: 'Kolor obramowania',
  1517. DLG_TIPS: 'Sugestia',
  1518. DLG_TIPS_CONTENT: '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.',
  1519. DLG_BUTTONS: ['Zapisz', 'Zamknij', 'Eksport', 'Import', 'Przeskładać'],
  1520. DLG_FB_COLOUR_HINT: 'Pozostaw puste, aby użyć schematu kolorów FB',
  1521. },
  1522. // -- Português (Portugal and Brazil)
  1523. pt: {
  1524. LANGUAGE_DIRECTION: 'ltr',
  1525. SPONSORED: 'Patrocinado',
  1526. NF_TABLIST_STORIES_REELS_ROOMS: 'Caixa de listagem da guia "Stories | Vídeos do Reels | Salas"',
  1527. NF_STORIES: 'Stories',
  1528. NF_SURVEY: 'Enquete',
  1529. NF_PEOPLE_YOU_MAY_KNOW: 'Pessoas que talvez conheças',
  1530. NF_PAID_PARTNERSHIP: 'Parceria paga',
  1531. NF_SPONSORED_PAID: 'Patrocinado · Financiado por ______',
  1532. NF_SUGGESTIONS: 'Sugestões / Recomendações',
  1533. NF_FOLLOW: 'Seguir',
  1534. NF_PARTICIPATE: 'Participar',
  1535. NF_REELS_SHORT_VIDEOS: 'Vídeos do Reels e vídeos de curta duração',
  1536. NF_SHORT_REEL_VIDEO: 'Rolo/vídeo curto',
  1537. NF_EVENTS_YOU_MAY_LIKE: 'Eventos que você pode gostar',
  1538. NF_ANIMATED_GIFS_POSTS: 'GIFs animados',
  1539. NF_ANIMATED_GIFS_PAUSE: 'Pausar GIFs animados',
  1540. NF_SHARES: '# partilhas',
  1541. NF_LIKES_MAXIMUM: 'Número máximo de curtidas',
  1542. GF_PAID_PARTNERSHIP: 'Parceria paga',
  1543. GF_SUGGESTIONS: 'Sugestões / Recomendações',
  1544. GF_SHORT_REEL_VIDEO: 'Rolo/vídeo curto',
  1545. GF_ANIMATED_GIFS_POSTS: 'GIFs animados',
  1546. GF_ANIMATED_GIFS_PAUSE: 'Pausar GIFs animados',
  1547. GF_SHARES: '# partilhas',
  1548. VF_LIVE: 'DIRETO',
  1549. VF_INSTAGRAM: 'Instagram',
  1550. VF_DUPLICATE_VIDEOS: 'Vídeo duplicado',
  1551. VF_ANIMATED_GIFS_PAUSE: 'Pausar GIFs animados',
  1552. PP_ANIMATED_GIFS_POSTS: 'GIFs animados',
  1553. PP_ANIMATED_GIFS_PAUSE: 'Pausar GIFs animados',
  1554. NF_BLOCKED_FEED: ['Feed de notícias', 'Feed de grupos', 'Feed de vídeos'],
  1555. GF_BLOCKED_FEED: ['Feed de notícias', 'Feed de grupos', 'Feed de vídeos'],
  1556. VF_BLOCKED_FEED: ['Feed de notícias', 'Feed de grupos', 'Feed de vídeos'],
  1557. MP_BLOCKED_FEED: ['Feed de mercado'],
  1558. PP_BLOCKED_FEED: '',
  1559. OTHER_INFO_BOX_CORONAVIRUS: 'Coronavírus (caixa de informações)',
  1560. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Ciência do Clima (caixa de informações)',
  1561. OTHER_INFO_BOX_SUBSCRIBE: 'Assine (caixa de informações)',
  1562. REELS_TITLE: 'Reels',
  1563. REELS_CONTROLS: 'Mostrar controles do vídeo',
  1564. REELS_DISABLE_LOOPING: 'Desativar repetição',
  1565. DLG_TITLE: 'Limpe meus feeds',
  1566. DLG_NF: 'Feed de notícias',
  1567. DLG_GF: 'Feed de grupos',
  1568. DLG_VF: 'Feed de vídeos',
  1569. DLG_MP: 'Feed de mercado',
  1570. DLG_PP: 'Perfil / Página',
  1571. DLG_OTHER: 'Seção suplementar / informativa',
  1572. DLG_BLOCK_TEXT_FILTER_TITLE: 'Filtro de texto',
  1573. DLG_BLOCK_NEW_LINE: '(Separe palavras ou frases com uma quebra de linha, expressões regulares são suportadas)',
  1574. NF_BLOCKED_ENABLED: 'Habilidoso',
  1575. GF_BLOCKED_ENABLED: 'Habilidoso',
  1576. VF_BLOCKED_ENABLED: 'Habilidoso',
  1577. MP_BLOCKED_ENABLED: 'Habilidoso',
  1578. PP_BLOCKED_ENABLED: 'Habilidoso',
  1579. NF_BLOCKED_RE: 'Expressões regulares (RegExp)',
  1580. GF_BLOCKED_RE: 'Expressões regulares (RegExp)',
  1581. VF_BLOCKED_RE: 'Expressões regulares (RegExp)',
  1582. MP_BLOCKED_RE: 'Expressões regulares (RegExp)',
  1583. PP_BLOCKED_RE: 'Expressões regulares (RegExp)',
  1584. DLG_VERBOSITY: 'Opções para postagens ocultas',
  1585. DLG_VERBOSITY_CAPTION: 'Mostrar um rótulo se uma postagem estiver oculta',
  1586. VERBOSITY_MESSAGE: ['sem rótulo', 'Postagem oculta. Regra: ', ' postagens ocultas', '7 postagens ocultas ~ (apenas no Feed de Grupos)'],
  1587. VERBOSITY_MESSAGE_COLOUR: 'Cor do texto',
  1588. VERBOSITY_MESSAGE_BG_COLOUR: 'Cor de fundo',
  1589. VERBOSITY_DEBUG: 'Destacar postagens "ocultas"',
  1590. CMF_CUSTOMISATIONS: 'Personalizações',
  1591. CMF_BTN_LOCATION: 'Localização do botão Limpe meus feeds',
  1592. CMF_BTN_OPTION: ['inferior esquerdo', 'superior direito', 'desativado (use "Configurações" no menu Comandos de script do usuário)'],
  1593. CMF_DIALOG_LANGUAGE_LABEL: 'Limpar o idioma da caixa de diálogo dos meus feeds',
  1594. CMF_DIALOG_LANGUAGE: 'Português',
  1595. CMF_DIALOG_LANGUAGE_DEFAULT: 'Usar idioma do site',
  1596. GM_MENU_SETTINGS: 'Configurações',
  1597. CMF_DIALOG_LOCATION: 'Localização da caixa de diálogo Limpe meus feeds',
  1598. CMF_DIALOG_OPTION: ['lado esquerdo', 'lado direito'],
  1599. CMF_BORDER_COLOUR: 'Cor da borda',
  1600. DLG_TIPS: 'Pontas',
  1601. DLG_TIPS_CONTENT: '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.',
  1602. DLG_BUTTONS: ['Salvar', 'Fechar', 'Exportar', 'Importar', 'Redefinir'],
  1603. DLG_FB_COLOUR_HINT: 'Deixe em branco para usar o esquema de cores do FB',
  1604. },
  1605. // -- Русский (Russia)
  1606. ru: {
  1607. LANGUAGE_DIRECTION: 'ltr',
  1608. SPONSORED: 'Реклама',
  1609. NF_TABLIST_STORIES_REELS_ROOMS: 'Список вкладок "Истории | Reels | Комнаты"',
  1610. NF_STORIES: 'Истории',
  1611. NF_SURVEY: 'Опрос',
  1612. NF_PEOPLE_YOU_MAY_KNOW: 'Люди, которых вы можете знать',
  1613. NF_PAID_PARTNERSHIP: 'Платное партнерство',
  1614. NF_SPONSORED_PAID: 'Реклама · Оплачено ______',
  1615. NF_SUGGESTIONS: 'Предложения / Рекомендации',
  1616. NF_FOLLOW: 'Подписаться',
  1617. NF_PARTICIPATE: 'Участвовать',
  1618. NF_REELS_SHORT_VIDEOS: 'Reels и короткие видео',
  1619. NF_SHORT_REEL_VIDEO: 'Reels/короткое видео',
  1620. NF_EVENTS_YOU_MAY_LIKE: 'Мероприятия, которые вам могут понравиться',
  1621. NF_ANIMATED_GIFS_POSTS: 'Анимированные GIF-файлы',
  1622. NF_ANIMATED_GIFS_PAUSE: 'Приостановить анимированные GIF',
  1623. NF_SHARES: '# поделились',
  1624. NF_LIKES_MAXIMUM: 'Максимальное количество «Нравится»',
  1625. GF_PAID_PARTNERSHIP: 'Платное партнерство',
  1626. GF_SUGGESTIONS: 'Предложения / Рекомендации',
  1627. GF_SHORT_REEL_VIDEO: 'Reel/короткое видео',
  1628. GF_ANIMATED_GIFS_POSTS: 'Анимированные GIF-файлы',
  1629. GF_ANIMATED_GIFS_PAUSE: 'Приостановить анимированные GIF',
  1630. GF_SHARES: '# поделились',
  1631. VF_LIVE: 'В ЭФИРЕ',
  1632. VF_INSTAGRAM: 'Instagram',
  1633. VF_DUPLICATE_VIDEOS: 'Дубликат видео',
  1634. VF_ANIMATED_GIFS_PAUSE: 'Приостановить анимированные GIF',
  1635. PP_ANIMATED_GIFS_POSTS: 'Анимированные GIF-файлы',
  1636. PP_ANIMATED_GIFS_PAUSE: 'Приостановить анимированные GIF',
  1637. NF_BLOCKED_FEED: ['Лента новостей', 'Лента групп', 'Лента видео'],
  1638. GF_BLOCKED_FEED: ['Лента новостей', 'Лента групп', 'Лента видео'],
  1639. VF_BLOCKED_FEED: ['Лента новостей', 'Лента групп', 'Лента видео'],
  1640. MP_BLOCKED_FEED: ['Лента Marketplace'],
  1641. PP_BLOCKED_FEED: '',
  1642. OTHER_INFO_BOX_CORONAVIRUS: 'Коронавирус (информационное окно)',
  1643. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Наука о климате (информационное окно)',
  1644. OTHER_INFO_BOX_SUBSCRIBE: 'Подписаться (информационное окно)',
  1645. REELS_TITLE: 'Видео Reels',
  1646. REELS_CONTROLS: 'Показать элементы управления видео',
  1647. REELS_DISABLE_LOOPING: 'Отключить повторение',
  1648. DLG_TITLE: 'Очистить мои новостные ленты',
  1649. DLG_NF: 'Лента новостей',
  1650. DLG_GF: 'Лента групп',
  1651. DLG_VF: 'Лента видео',
  1652. DLG_MP: 'Лента Marketplace',
  1653. DLG_PP: 'Профиль / Страница',
  1654. DLG_OTHER: 'Дополнительный/информационный раздел',
  1655. DLG_BLOCK_TEXT_FILTER_TITLE: 'Текстовый фильтр',
  1656. DLG_BLOCK_NEW_LINE: '(Разделяйте слова или фразы с помощью разрыва строки, поддерживаются регулярные выражения)',
  1657. NF_BLOCKED_ENABLED: 'Включено',
  1658. GF_BLOCKED_ENABLED: 'Включено',
  1659. VF_BLOCKED_ENABLED: 'Включено',
  1660. MP_BLOCKED_ENABLED: 'Включено',
  1661. PP_BLOCKED_ENABLED: 'Включено',
  1662. NF_BLOCKED_RE: 'Регулярные выражения (RegExp)',
  1663. GF_BLOCKED_RE: 'Регулярные выражения (RegExp)',
  1664. VF_BLOCKED_RE: 'Регулярные выражения (RegExp)',
  1665. MP_BLOCKED_RE: 'Регулярные выражения (RegExp)',
  1666. PP_BLOCKED_RE: 'Регулярные выражения (RegExp)',
  1667. DLG_VERBOSITY: 'Настройки скрытых публикаций',
  1668. DLG_VERBOSITY_CAPTION: 'Показать ярлык, если запись скрыта',
  1669. VERBOSITY_MESSAGE: ['нет ярлыка', 'Пост скрыт. Правило: ', ' постов скрыто', '7 постов скрыто ~ (только в Ленте Групп)'],
  1670. VERBOSITY_MESSAGE_COLOUR: 'Цвет текста',
  1671. VERBOSITY_MESSAGE_BG_COLOUR: 'Цвет фона',
  1672. VERBOSITY_DEBUG: 'Выделить «скрытые» посты',
  1673. CMF_CUSTOMISATIONS: 'Настройки',
  1674. CMF_BTN_LOCATION: 'Расположение кнопки «Очистить мои новостные ленты»',
  1675. CMF_BTN_OPTION: ['внизу слева', 'вверху справа', 'отключено (используйте «Настройки» в меню «Пользовательские команды скрипта»)'],
  1676. CMF_DIALOG_LANGUAGE_LABEL: 'Очистить язык моего диалогового окна с лентами',
  1677. CMF_DIALOG_LANGUAGE: 'Русский',
  1678. CMF_DIALOG_LANGUAGE_DEFAULT: 'Использовать язык сайта',
  1679. GM_MENU_SETTINGS: 'Настройки',
  1680. CMF_DIALOG_LOCATION: 'Расположение диалогового окна «Очистить мои ленты»',
  1681. CMF_DIALOG_OPTION: ['левая сторона', 'правая сторона'],
  1682. CMF_BORDER_COLOUR: 'Цвет границы',
  1683. DLG_TIPS: 'Советы',
  1684. DLG_TIPS_CONTENT: 'Очистка кэша браузера сбросит ваши настройки на значения по умолчанию.\n\nИспользуйте кнопки «Экспорт» и «Импорт», чтобы создать резервную копию и восстановить ваши настройки.',
  1685. DLG_BUTTONS: ['Сохранить', 'Закрыть', 'Экспорт', 'Импорт', 'Сброс'],
  1686. DLG_FB_COLOUR_HINT: 'Оставьте пустым, чтобы использовать цветовую схему FB',
  1687. },
  1688. // -- Türkçe (Turkey)
  1689. tr: {
  1690. SPONSORED: 'Sponsorlu',
  1691. NF_TABLIST_STORIES_REELS_ROOMS: '"Hikayeler | Makaralar | Odalar" sekmeleri liste kutusu',
  1692. NF_STORIES: 'Hikayeler',
  1693. NF_SURVEY: 'Anket',
  1694. NF_PEOPLE_YOU_MAY_KNOW: 'Tanıyor olabileceğin kişiler',
  1695. NF_PAID_PARTNERSHIP: 'ücretli ortaklık',
  1696. NF_SPONSORED_PAID: 'Sponsorlu · ______ tarafından ödendi',
  1697. NF_SUGGESTIONS: 'Öneriler',
  1698. NF_FOLLOW: 'Takip Et',
  1699. NF_PARTICIPATE: 'Katılmak',
  1700. NF_REELS_SHORT_VIDEOS: 'Makaralar ve kısa videolar',
  1701. NF_SHORT_REEL_VIDEO: 'makara/kısa video',
  1702. NF_EVENTS_YOU_MAY_LIKE: 'makara/kısa video',
  1703. NF_ANIMATED_GIFS_POSTS: 'Animasyonlu GIF\'ler',
  1704. NF_ANIMATED_GIFS_PAUSE: 'Hareketli GIF\'leri duraklat',
  1705. NF_SHARES: '# Paylaşım',
  1706. NF_LIKES_MAXIMUM: 'Maksimum Beğeni sayısı',
  1707. GF_PAID_PARTNERSHIP: 'ücretli ortaklık',
  1708. GF_SUGGESTIONS: 'Öneriler',
  1709. GF_SHORT_REEL_VIDEO: 'makara/kısa video',
  1710. GF_ANIMATED_GIFS_POSTS: 'Animasyonlu GIF\'ler',
  1711. GF_ANIMATED_GIFS_PAUSE: 'Hareketli GIF\'leri duraklat',
  1712. GF_SHARES: '# Paylaşım',
  1713. VF_LIVE: 'CANLI',
  1714. VF_INSTAGRAM: 'Instagram',
  1715. VF_DUPLICATE_VIDEOS: 'Çift video',
  1716. VF_ANIMATED_GIFS_PAUSE: 'Hareketli GIF\'leri duraklat',
  1717. PP_ANIMATED_GIFS_POSTS: 'Animasyonlu GIF\'ler',
  1718. PP_ANIMATED_GIFS_PAUSE: 'Hareketli GIF\'leri duraklat',
  1719. NF_BLOCKED_FEED: ['Haber akışı', 'Gruplar Feed\'i', 'Video Beslemelerini İzle'],
  1720. GF_BLOCKED_FEED: ['Haber akışı', 'Gruplar Feed\'i', 'Video Beslemelerini İzle'],
  1721. VF_BLOCKED_FEED: ['Haber akışı', 'Gruplar Feed\'i', 'Video Beslemelerini İzle'],
  1722. MP_BLOCKED_FEED: ['Pazar Yeri Feed\'i'],
  1723. PP_BLOCKED_FEED: '',
  1724. OTHER_INFO_BOX_CORONAVIRUS: 'Koronavirüs (bilgi kutusu)',
  1725. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'İklim Bilimi (bilgi kutusu)',
  1726. OTHER_INFO_BOX_SUBSCRIBE: 'Abone ol (bilgi kutusu)',
  1727. REELS_TITLE: 'Reels',
  1728. REELS_CONTROLS: 'Video kontrollerini göster',
  1729. REELS_DISABLE_LOOPING: 'Döngüyü devre dışı bırak',
  1730. DLG_TITLE: 'Feed\'lerimi temizle',
  1731. DLG_NF: 'Haber akışı',
  1732. DLG_GF: 'Gruplar Feed\'i',
  1733. DLG_VF: 'Video Beslemelerini İzle',
  1734. DLG_MP: 'Pazar Yeri Feed\'i',
  1735. DLG_PP: 'Profil / Sayfa',
  1736. DLG_OTHER: 'Tamamlayıcı / bilgi bölümü',
  1737. DLG_BLOCK_TEXT_FILTER_TITLE: 'Metin filtresi',
  1738. DLG_BLOCK_NEW_LINE: '(Kelimeleri veya ifadeleri satır sonu ile ayırın, Düzenli İfadeler desteklenir)',
  1739. NF_BLOCKED_ENABLED: 'Etkinleştirildi',
  1740. GF_BLOCKED_ENABLED: 'Etkinleştirildi',
  1741. VF_BLOCKED_ENABLED: 'Etkinleştirildi',
  1742. MP_BLOCKED_ENABLED: 'Etkinleştirildi',
  1743. PP_BLOCKED_ENABLED: 'Etkinleştirildi',
  1744. NF_BLOCKED_RE: 'Düzenli İfadeler (RegExp)',
  1745. GF_BLOCKED_RE: 'Düzenli İfadeler (RegExp)',
  1746. VF_BLOCKED_RE: 'Düzenli İfadeler (RegExp)',
  1747. MP_BLOCKED_RE: 'Düzenli İfadeler (RegExp)',
  1748. PP_BLOCKED_RE: 'Düzenli İfadeler (RegExp)',
  1749. DLG_VERBOSITY: 'Gizli Gönderi Seçenekleri',
  1750. DLG_VERBOSITY_CAPTION: 'Bir gönderi gizlenmişse bir etiket göster',
  1751. VERBOSITY_MESSAGE: ['etiket yok', 'Gönderi gizlendi. Kural: ', ' gönderi gizlendi', '7 gönderi gizlendi ~ (yalnızca Grup Beslemesi)'],
  1752. VERBOSITY_MESSAGE_COLOUR: 'Metin rengi',
  1753. VERBOSITY_MESSAGE_BG_COLOUR: 'Arka plan rengi',
  1754. VERBOSITY_DEBUG: '"Gizli" gönderileri vurgulayın',
  1755. CMF_CUSTOMISATIONS: 'özelleştirmeler',
  1756. CMF_BTN_LOCATION: '"Feed\'lerimi temizle" için düğmenin konumu',
  1757. CMF_BTN_OPTION: ['sol alt', 'sağ üst', 'devre dışı (Kullanıcı Komut Dosyası Komutları menüsünde "Ayarlar"ı kullanın)'],
  1758. CMF_DIALOG_LANGUAGE_LABEL: 'Beslemelerimin iletişim kutusu dilini temizle',
  1759. CMF_DIALOG_LANGUAGE: 'Türkçe',
  1760. CMF_DIALOG_LANGUAGE_DEFAULT: 'Site dilini kullan',
  1761. GM_MENU_SETTINGS: 'Ayarlar',
  1762. CMF_DIALOG_LOCATION: '"Feed\'lerimi temizle" iletişim kutusunun konumu',
  1763. CMF_DIALOG_OPTION: ['sol yan', 'sağ yan'],
  1764. CMF_BORDER_COLOUR: 'Kenarlık rengi',
  1765. DLG_TIPS: 'Ipuçları',
  1766. DLG_TIPS_CONTENT: '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.',
  1767. DLG_BUTTONS: ['Kaydetmek', 'Kapat', 'İhracat', 'İçe aktarmak', 'Sıfırla'],
  1768. DLG_FB_COLOUR_HINT: 'FB\'un renk düzenini kullanmak için boş bırakın',
  1769. },
  1770. // -- Україна (Ukraine)
  1771. uk: {
  1772. LANGUAGE_DIRECTION: 'ltr',
  1773. SPONSORED: 'Спонсорована',
  1774. NF_TABLIST_STORIES_REELS_ROOMS: 'Поле списку вкладок «Історії | Reels | Кімнати»',
  1775. NF_STORIES: 'Історії',
  1776. NF_SURVEY: 'Опитування',
  1777. NF_PEOPLE_YOU_MAY_KNOW: 'Люди, яких Ви можете знати',
  1778. NF_PAID_PARTNERSHIP: 'Оплачуване партнерство',
  1779. NF_SPONSORED_PAID: 'Спонсоровано · Оплачено ______',
  1780. NF_SUGGESTIONS: 'Пропозиції / Рекомендації',
  1781. NF_FOLLOW: 'Слідуйте',
  1782. NF_PARTICIPATE: 'Беріть участь',
  1783. NF_REELS_SHORT_VIDEOS: 'Відео Reels і короткі відео',
  1784. NF_SHORT_REEL_VIDEO: 'Reel/коротке відео',
  1785. NF_EVENTS_YOU_MAY_LIKE: 'Події, які можуть вам сподобатися',
  1786. NF_ANIMATED_GIFS_POSTS: 'Анімовані GIF-файли',
  1787. NF_ANIMATED_GIFS_PAUSE: 'Призупинити анімовані GIF-файли',
  1788. NF_SHARES: '# Поширити',
  1789. NF_LIKES_MAXIMUM: 'Максимальна кількість «Подобається».',
  1790. GF_PAID_PARTNERSHIP: 'Оплачуване партнерство',
  1791. GF_SUGGESTIONS: 'Пропозиції / Рекомендації',
  1792. GF_SHORT_REEL_VIDEO: 'Reel/коротке відео',
  1793. GF_ANIMATED_GIFS_POSTS: 'Анімовані GIF-файли',
  1794. GF_ANIMATED_GIFS_PAUSE: 'Призупинити анімовані GIF-файли',
  1795. GF_SHARES: '# Поширити',
  1796. VF_LIVE: 'ЕФІР',
  1797. VF_INSTAGRAM: 'Instagram',
  1798. VF_DUPLICATE_VIDEOS: 'Дубльоване відео',
  1799. VF_ANIMATED_GIFS_PAUSE: 'Призупинити анімовані GIF-файли',
  1800. PP_ANIMATED_GIFS_POSTS: 'Анімовані GIF-файли',
  1801. PP_ANIMATED_GIFS_PAUSE: 'Призупинити анімовані GIF-файли',
  1802. NF_BLOCKED_FEED: ['Стрічка новин', 'Стрічка Групи', 'Стрічка відео'],
  1803. GF_BLOCKED_FEED: ['Стрічка новин', 'Стрічка Групи', 'Стрічка відео'],
  1804. VF_BLOCKED_FEED: ['Стрічка новин', 'Стрічка Групи', 'Стрічка відео'],
  1805. MP_BLOCKED_FEED: ['Стрічка Marketplace'],
  1806. PP_BLOCKED_FEED: '',
  1807. OTHER_INFO_BOX_CORONAVIRUS: 'Коронавірус (інформаційне вікно)',
  1808. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Наука про клімат (інформаційне вікно)',
  1809. OTHER_INFO_BOX_SUBSCRIBE: 'Підписатися (інформаційне вікно)',
  1810. REELS_TITLE: 'Reels',
  1811. REELS_CONTROLS: 'Відображення елементів керування відео',
  1812. REELS_DISABLE_LOOPING: 'Вимкнути повторення',
  1813. DLG_TITLE: 'Очистити мої стрічки',
  1814. DLG_NF: 'Стрічка новин',
  1815. DLG_GF: 'Стрічка Групи',
  1816. DLG_VF: 'Стрічка відео',
  1817. DLG_MP: 'Стрічка Marketplace',
  1818. DLG_PP: 'Профіль / Сторінка',
  1819. DLG_OTHER: 'Додатковий/інформаційний розділ',
  1820. DLG_BLOCK_TEXT_FILTER_TITLE: 'Текстовий фільтр',
  1821. DLG_BLOCK_NEW_LINE: '(Розділяйте слова або фрази розривом рядка, регулярні вирази підтримуються)',
  1822. NF_BLOCKED_ENABLED: 'Увімкнено',
  1823. GF_BLOCKED_ENABLED: 'Увімкнено',
  1824. VF_BLOCKED_ENABLED: 'Увімкнено',
  1825. MP_BLOCKED_ENABLED: 'Увімкнено',
  1826. PP_BLOCKED_ENABLED: 'Увімкнено',
  1827. NF_BLOCKED_RE: 'Регулярні вирази (RegExp)',
  1828. GF_BLOCKED_RE: 'Регулярні вирази (RegExp)',
  1829. VF_BLOCKED_RE: 'Регулярні вирази (RegExp)',
  1830. MP_BLOCKED_RE: 'Регулярні вирази (RegExp)',
  1831. PP_BLOCKED_RE: 'Регулярні вирази (RegExp)',
  1832. DLG_VERBOSITY: 'Параметри прихованих дописів',
  1833. DLG_VERBOSITY_CAPTION: 'Відображати мітку, якщо публікація прихована',
  1834. VERBOSITY_MESSAGE: ['жодної мітки', 'Допис прихований. Правило: ', ' дописи приховано', '7 дописи приховано ~ (лише в стрічці Груп)'],
  1835. VERBOSITY_MESSAGE_COLOUR: 'Колір тексту',
  1836. VERBOSITY_MESSAGE_BG_COLOUR: 'Колір фону',
  1837. VERBOSITY_DEBUG: '«Виділяти «приховані» дописи»',
  1838. CMF_CUSTOMISATIONS: 'Налаштування',
  1839. CMF_BTN_LOCATION: 'Розташування кнопки «Очистити мої стрічки»',
  1840. CMF_BTN_OPTION: ['внизу ліворуч', 'вгорі праворуч', 'вимкнено (використовуйте «Параметри» в меню команд сценарію користувача»)'],
  1841. CMF_DIALOG_LANGUAGE_LABEL: 'Очистити мову мого вікна діалогу',
  1842. CMF_DIALOG_LANGUAGE: 'Українська',
  1843. CMF_DIALOG_LANGUAGE_DEFAULT: 'Використовувати мову сайту',
  1844. GM_MENU_SETTINGS: 'Параметри',
  1845. CMF_DIALOG_LOCATION: 'Розташування діалогового вікна «Очистити мої стрічки»',
  1846. CMF_DIALOG_OPTION: ['ліва сторона', 'права сторона'],
  1847. CMF_BORDER_COLOUR: 'Колір кордону',
  1848. DLG_TIPS: 'Підказки',
  1849. DLG_TIPS_CONTENT: 'Очищення кешу браузера призведе до скидання налаштувань до значень за замовчуванням.\n\nВикористовуйте кнопки «Експорт» та «Імпорт», щоб створити резервну копію та відновити налаштовані налаштування.',
  1850. DLG_BUTTONS: ['Зберегти', 'Закрити', 'Експорт', 'Імпорт', 'Скинути'],
  1851. DLG_FB_COLOUR_HINT: 'Залиште порожнім, щоб використовувати колірну схему FB',
  1852. },
  1853. // -- Tiếng Việt (Vietnam)
  1854. vi: {
  1855. LANGUAGE_DIRECTION: 'ltr',
  1856. SPONSORED: 'Được tài trợ',
  1857. NF_TABLIST_STORIES_REELS_ROOMS: 'Hộp danh sách tab "Tin | Reels | Phòng họp mặt"',
  1858. NF_STORIES: 'Tin',
  1859. NF_SURVEY: 'Khảo sát',
  1860. NF_PEOPLE_YOU_MAY_KNOW: 'Những người bạn có thể biết',
  1861. NF_PAID_PARTNERSHIP: 'Mối quan hệ tài trợ',
  1862. NF_SPONSORED_PAID: 'Được tài trợ · Tài trợ bởi ______',
  1863. NF_SUGGESTIONS: 'Đề xuất / Khuyến nghị',
  1864. NF_FOLLOW: 'Theo dõi',
  1865. NF_PARTICIPATE: 'Tham gia',
  1866. NF_REELS_SHORT_VIDEOS: 'Reels và video ngắn',
  1867. NF_SHORT_REEL_VIDEO: 'Reel / video ngắn',
  1868. NF_EVENTS_YOU_MAY_LIKE: 'Sự kiện bạn có thể thích',
  1869. NF_ANIMATED_GIFS_POSTS: 'GIF động',
  1870. NF_ANIMATED_GIFS_PAUSE: 'Tạm dừng các ảnh GIF động',
  1871. NF_SHARES: '# lượt chia sẻ',
  1872. NF_LIKES_MAXIMUM: 'Số lượt thích tối đa',
  1873. GF_PAID_PARTNERSHIP: 'Mối quan hệ tài trợ',
  1874. GF_SUGGESTIONS: 'Đề xuất / Khuyến nghị',
  1875. GF_SHORT_REEL_VIDEO: 'Reel / video ngắn',
  1876. GF_ANIMATED_GIFS_POSTS: 'GIF động',
  1877. GF_ANIMATED_GIFS_PAUSE: 'Tạm dừng các ảnh GIF động',
  1878. GF_SHARES: '# lượt chia sẻ',
  1879. VF_LIVE: 'TRỰC TIẾP',
  1880. VF_INSTAGRAM: 'Instagram',
  1881. VF_DUPLICATE_VIDEOS: 'Video trùng lặp',
  1882. VF_ANIMATED_GIFS_PAUSE: 'Tạm dừng các ảnh GIF động',
  1883. PP_ANIMATED_GIFS_POSTS: 'GIF động',
  1884. PP_ANIMATED_GIFS_PAUSE: 'Tạm dừng các ảnh GIF động',
  1885. NF_BLOCKED_FEED: ['Nguồn cấp tin tức', 'Nguồn cấp dữ liệu Nhóm', 'Nguồn cấp dữ liệu video'],
  1886. GF_BLOCKED_FEED: ['Nguồn cấp tin tức', 'Nguồn cấp dữ liệu Nhóm', 'Nguồn cấp dữ liệu video'],
  1887. VF_BLOCKED_FEED: ['Nguồn cấp tin tức', 'Nguồn cấp dữ liệu Nhóm', 'Nguồn cấp dữ liệu video'],
  1888. MP_BLOCKED_FEED: ['Nguồn cấp dữ liệu Marketplace'],
  1889. PP_BLOCKED_FEED: '',
  1890. OTHER_INFO_BOX_CORONAVIRUS: 'Virus corona (hộp thông tin)',
  1891. OTHER_INFO_BOX_CLIMATE_SCIENCE: 'Khoa học khí hậu (hộp thông tin)',
  1892. OTHER_INFO_BOX_SUBSCRIBE: 'Đăng kí (hộp thông tin)',
  1893. REELS_TITLE: 'Reels',
  1894. REELS_CONTROLS: 'Hiển thị điều khiển video',
  1895. REELS_DISABLE_LOOPING: 'Tắt lặp lại',
  1896. DLG_TITLE: 'Làm sạch nguồn cấp dữ liệu của tôi',
  1897. DLG_NF: 'Nguồn cấp tin tức',
  1898. DLG_GF: 'Nguồn cấp dữ liệu Nhóm',
  1899. DLG_VF: 'Nguồn cấp dữ liệu video',
  1900. DLG_MP: 'Nguồn cấp dữ liệu Marketplace',
  1901. DLG_PP: 'Hồ sơ / Trang',
  1902. DLG_OTHER: 'Phần bổ sung/thông tin',
  1903. DLG_BLOCK_TEXT_FILTER_TITLE: 'Bộ lọc văn bản',
  1904. DLG_BLOCK_NEW_LINE: '(Ngăn cách từ hoặc cụm từ bằng dấu xuống dòng, Biểu thức chính quy được hỗ trợ)',
  1905. NF_BLOCKED_ENABLED: 'Đã kích hoạt',
  1906. GF_BLOCKED_ENABLED: 'Đã kích hoạt',
  1907. VF_BLOCKED_ENABLED: 'Đã kích hoạt',
  1908. MP_BLOCKED_ENABLED: 'Đã kích hoạt',
  1909. PP_BLOCKED_ENABLED: 'Đã kích hoạt',
  1910. NF_BLOCKED_RE: 'Biểu thức chính quy (RegExp)',
  1911. GF_BLOCKED_RE: 'Biểu thức chính quy (RegExp)',
  1912. VF_BLOCKED_RE: 'Biểu thức chính quy (RegExp)',
  1913. MP_BLOCKED_RE: 'Biểu thức chính quy (RegExp)',
  1914. PP_BLOCKED_RE: 'Biểu thức chính quy (RegExp)',
  1915. DLG_VERBOSITY: 'Tùy chọn cho bài đăng ẩn',
  1916. DLG_VERBOSITY_CAPTION: 'Hiển thị một nhãn nếu một bài đăng bị ẩn',
  1917. VERBOSITY_MESSAGE: ['không có nhãn', 'Bài bị ẩn. Quy tắc: ', ' bài viết ẩn', '7 bài viết ẩn ~ (chỉ áp dụng cho Bảng tin Nhóm)'],
  1918. VERBOSITY_MESSAGE_COLOUR: 'Màu văn bản',
  1919. VERBOSITY_MESSAGE_BG_COLOUR: 'Màu nền',
  1920. VERBOSITY_DEBUG: 'Đánh dấu các bài đăng "ẩn"',
  1921. CMF_CUSTOMISATIONS: 'Các tùy chỉnh',
  1922. CMF_BTN_LOCATION: 'Vị trí của nút Làm sạch nguồn cấp dữ liệu của tôi',
  1923. CMF_BTN_OPTION: ['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)'],
  1924. CMF_DIALOG_LANGUAGE_LABEL: 'Làm sạch ngôn ngữ của hộp thoại của tôi',
  1925. CMF_DIALOG_LANGUAGE: 'Tiếng Việt',
  1926. CMF_DIALOG_LANGUAGE_DEFAULT: 'Sử dụng ngôn ngữ trang web',
  1927. GM_MENU_SETTINGS: 'Cài đặt',
  1928. CMF_DIALOG_LOCATION: 'Vị trí của hộp thoại Làm sạch nguồn cấp dữ liệu của tôi',
  1929. CMF_DIALOG_OPTION: ['bên trái', 'bên phải'],
  1930. CMF_BORDER_COLOUR: 'Màu viền',
  1931. DLG_TIPS: 'Thủ thuật',
  1932. DLG_TIPS_CONTENT: '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.',
  1933. DLG_BUTTONS: ['Lưu', 'Đóng', 'Xuất', 'Nhập', 'Đặt lại'],
  1934. DLG_FB_COLOUR_HINT: 'Để trống để sử dụng bảng màu của FB',
  1935. },
  1936. // -- 简体中文 (Chinese (Simplified))
  1937. 'zh-Hans': {
  1938. LANGUAGE_DIRECTION: 'ltr',
  1939. SPONSORED: '赞助内容',
  1940. NF_TABLIST_STORIES_REELS_ROOMS: '“快拍|Reels|畅聊室”选项卡列表框',
  1941. NF_STORIES: '故事',
  1942. NF_SURVEY: '调查',
  1943. NF_PEOPLE_YOU_MAY_KNOW: '你可能认识的人',
  1944. NF_PAID_PARTNERSHIP: '付费合伙',
  1945. NF_SPONSORED_PAID: '赞助 · 由 ______ 付费',
  1946. NF_SUGGESTIONS: '建议',
  1947. NF_FOLLOW: '关注',
  1948. NF_PARTICIPATE: '参与',
  1949. NF_REELS_SHORT_VIDEOS: '卷轴和短视频',
  1950. NF_SHORT_REEL_VIDEO: '卷轴/短视频',
  1951. NF_EVENTS_YOU_MAY_LIKE: '您可能喜欢的活动',
  1952. NF_ANIMATED_GIFS_POSTS: '动图 GIF',
  1953. NF_ANIMATED_GIFS_PAUSE: '暂停动画 GIF',
  1954. NF_SHARES: '#次分享',
  1955. NF_LIKES_MAXIMUM: '最大点赞数',
  1956. GF_PAID_PARTNERSHIP: '有偿合作',
  1957. GF_SUGGESTIONS: '建议/建议',
  1958. GF_SHORT_REEL_VIDEO: '卷轴和短视频',
  1959. GF_ANIMATED_GIFS_POSTS: '动图 GIF',
  1960. GF_ANIMATED_GIFS_PAUSE: '暂停动画 GIF',
  1961. GF_SHARES: '#次分享',
  1962. VF_LIVE: '现场直播',
  1963. VF_INSTAGRAM: 'Instagram',
  1964. VF_DUPLICATE_VIDEOS: '重复视频',
  1965. VF_ANIMATED_GIFS_PAUSE: '暂停动画 GIF',
  1966. PP_ANIMATED_GIFS_POSTS: '动图 GIF',
  1967. PP_ANIMATED_GIFS_PAUSE: '暂停动画 GIF',
  1968. NF_BLOCKED_FEED: ['新闻提要', '组提要', '视频提要'],
  1969. GF_BLOCKED_FEED: ['新闻提要', '组提要', '视频提要'],
  1970. VF_BLOCKED_FEED: ['新闻提要', '组提要', '视频提要'],
  1971. MP_BLOCKED_FEED: ['市场提要'],
  1972. PP_BLOCKED_FEED: '',
  1973. OTHER_INFO_BOX_CORONAVIRUS: '冠状病毒(信息框)',
  1974. OTHER_INFO_BOX_CLIMATE_SCIENCE: '气候科学(信息框)',
  1975. OTHER_INFO_BOX_SUBSCRIBE: '订阅(信息框)',
  1976. REELS_TITLE: 'Reels',
  1977. REELS_CONTROLS: '显示视频控制',
  1978. REELS_DISABLE_LOOPING: '禁用循环',
  1979. DLG_TITLE: '清理我的提要',
  1980. DLG_NF: '新闻提要',
  1981. DLG_GF: '群组提要',
  1982. DLG_VF: '视频提要',
  1983. DLG_MP: '市场提要',
  1984. DLG_PP: '个人资料 / 页面',
  1985. DLG_OTHER: '补充/资料部分',
  1986. DLG_BLOCK_TEXT_FILTER_TITLE: '文本过滤器',
  1987. DLG_BLOCK_NEW_LINE: '(使用换行符分隔单词或短语,支持正则表达式)',
  1988. NF_BLOCKED_ENABLED: '启用',
  1989. GF_BLOCKED_ENABLED: '启用',
  1990. VF_BLOCKED_ENABLED: '启用',
  1991. MP_BLOCKED_ENABLED: '启用',
  1992. PP_BLOCKED_ENABLED: '启用',
  1993. NF_BLOCKED_RE: '正则表达式 (RegExp)',
  1994. GF_BLOCKED_RE: '正则表达式 (RegExp)',
  1995. VF_BLOCKED_RE: '正则表达式 (RegExp)',
  1996. MP_BLOCKED_RE: '正则表达式 (RegExp)',
  1997. PP_BLOCKED_RE: '正则表达式 (RegExp)',
  1998. DLG_VERBOSITY: '隐藏帖子选项',
  1999. DLG_VERBOSITY_CAPTION: '如果文章被隐藏,则显示标签',
  2000. VERBOSITY_MESSAGE: ['没有标签', '帖子已隐藏。规则:', ' 个帖子已隐藏', '7个帖子已隐藏 ~ (仅适用于群组动态)'],
  2001. VERBOSITY_MESSAGE_COLOUR: '文字颜色',
  2002. VERBOSITY_MESSAGE_BG_COLOUR: '背景颜色',
  2003. VERBOSITY_DEBUG: '突出显示“隐藏”的帖子',
  2004. CMF_CUSTOMISATIONS: '定制化',
  2005. CMF_BTN_LOCATION: '“清理我的提要”按钮位置',
  2006. CMF_BTN_OPTION: ['左下方', '右上', '禁用(使用用户脚本命令菜单中的“设置”)'],
  2007. CMF_DIALOG_LANGUAGE_LABEL: '清理我的动态框语言',
  2008. CMF_DIALOG_LANGUAGE: '中文(简体)',
  2009. CMF_DIALOG_LANGUAGE_DEFAULT: '使用网站语言',
  2010. GM_MENU_SETTINGS: '设置',
  2011. CMF_DIALOG_LOCATION: '“清理我的提要”对话框位置',
  2012. CMF_DIALOG_OPTION: ['左边', '右边'],
  2013. CMF_BORDER_COLOUR: '边框颜色',
  2014. DLG_TIPS: '提示',
  2015. DLG_TIPS_CONTENT: '清除浏览器缓存会将您的设置重置为默认值。\n\n使用“导出”和“导入”按钮来备份和恢复您的自定义设置。',
  2016. DLG_BUTTONS: ['节省', '关', '出口', '进口', '重置'],
  2017. DLG_FB_COLOUR_HINT: '留空以使用 FB 的配色方案',
  2018. },
  2019. // -- 中國傳統的 (Chinese (Traditional))
  2020. 'zh-Hant': {
  2021. LANGUAGE_DIRECTION: 'ltr',
  2022. SPONSORED: '贊助',
  2023. NF_TABLIST_STORIES_REELS_ROOMS: '"限時動態 | Reels | 包廂" 分頁列表框',
  2024. NF_STORIES: '故事',
  2025. NF_SURVEY: '調查',
  2026. NF_PEOPLE_YOU_MAY_KNOW: '你可能認識的人',
  2027. NF_PAID_PARTNERSHIP: '付費合作',
  2028. NF_SPONSORED_PAID: '贊助 · 出資者:______',
  2029. NF_SUGGESTIONS: '建議/推薦',
  2030. NF_FOLLOW: '追蹤',
  2031. NF_PARTICIPATE: '參與',
  2032. NF_REELS_SHORT_VIDEOS: 'Reels 和短影片',
  2033. NF_SHORT_REEL_VIDEO: 'Reel/短影片',
  2034. NF_EVENTS_YOU_MAY_LIKE: '你可能感興趣的活動',
  2035. NF_ANIMATED_GIFS_POSTS: '動態 GIF',
  2036. NF_ANIMATED_GIFS_PAUSE: '暫停 GIF 動畫',
  2037. NF_SHARES: '#次分享',
  2038. NF_LIKES_MAXIMUM: '最大按讚數',
  2039. GF_PAID_PARTNERSHIP: '付費合作',
  2040. GF_SUGGESTIONS: '建議/推薦',
  2041. GF_SHORT_REEL_VIDEO: 'Reel/短影片',
  2042. GF_ANIMATED_GIFS_POSTS: '動態 GIF',
  2043. GF_ANIMATED_GIFS_PAUSE: '暫停 GIF 動畫',
  2044. GF_SHARES: '#次分享',
  2045. VF_LIVE: '現場直播',
  2046. VF_INSTAGRAM: 'Instagram',
  2047. VF_DUPLICATE_VIDEOS: '重複視頻',
  2048. VF_ANIMATED_GIFS_PAUSE: '暫停 GIF 動畫',
  2049. PP_ANIMATED_GIFS_POSTS: '動態 GIF',
  2050. PP_ANIMATED_GIFS_PAUSE: '暫停 GIF 動畫',
  2051. NF_BLOCKED_FEED: ['新聞動態消息', '群組動態消息', '影片動態消息'],
  2052. GF_BLOCKED_FEED: ['新聞動態消息', '群組動態消息', '影片動態消息'],
  2053. VF_BLOCKED_FEED: ['新聞動態消息', '群組動態消息', '影片動態消息'],
  2054. MP_BLOCKED_FEED: ['Marketplace 動態消息'],
  2055. PP_BLOCKED_FEED: '',
  2056. OTHER_INFO_BOX_CORONAVIRUS: '武漢肺炎病毒(資訊框)',
  2057. OTHER_INFO_BOX_CLIMATE_SCIENCE: '氣候科學(資訊框)',
  2058. OTHER_INFO_BOX_SUBSCRIBE: '訂閱(資訊框)',
  2059. REELS_TITLE: 'Reels',
  2060. REELS_CONTROLS: '顯示影片控制',
  2061. REELS_DISABLE_LOOPING: '停用循環',
  2062. DLG_TITLE: '清理我的動態消息',
  2063. DLG_NF: '新聞動態消息',
  2064. DLG_GF: '群組動態消息',
  2065. DLG_VF: '影片動態消息',
  2066. DLG_MP: 'Marketplace 動態消息',
  2067. DLG_PP: '個人檔案 / 頁面',
  2068. DLG_OTHER: '補充/資訊部分',
  2069. DLG_BLOCK_TEXT_FILTER_TITLE: '文字過濾器',
  2070. DLG_BLOCK_NEW_LINE: '(使用換行符分隔單詞或短語,支持正則表達式)',
  2071. NF_BLOCKED_ENABLED: '啟用',
  2072. GF_BLOCKED_ENABLED: '啟用',
  2073. VF_BLOCKED_ENABLED: '啟用',
  2074. MP_BLOCKED_ENABLED: '啟用',
  2075. PP_BLOCKED_ENABLED: '啟用',
  2076. NF_BLOCKED_RE: '正則表達式 (RegExp)',
  2077. GF_BLOCKED_RE: '正則表達式 (RegExp)',
  2078. VF_BLOCKED_RE: '正則表達式 (RegExp)',
  2079. MP_BLOCKED_RE: '正則表達式 (RegExp)',
  2080. PP_BLOCKED_RE: '正則表達式 (RegExp)',
  2081. DLG_VERBOSITY: '隱藏帖子選項',
  2082. DLG_VERBOSITY_CAPTION: '如果文章被隱藏,則顯示標籤',
  2083. VERBOSITY_MESSAGE: ['沒有標籤', '帖子已隱藏。規則:', ' 個帖子已隱藏', '7個帖子已隱藏 ~ (僅適用於群組動態)'],
  2084. VERBOSITY_MESSAGE_COLOUR: '文字顏色',
  2085. VERBOSITY_MESSAGE_BG_COLOUR: '背景顏色',
  2086. VERBOSITY_DEBUG: '強調顯示「隱藏」的貼文',
  2087. CMF_CUSTOMISATIONS: '客製化',
  2088. CMF_BTN_LOCATION: '「清理我的動態消息」按鈕的位置',
  2089. CMF_BTN_OPTION: ['左下方', '右上方', '禁用(在用户脚本命令菜单中使用“设置”)'],
  2090. CMF_DIALOG_LANGUAGE_LABEL: '清理我的動態框語言',
  2091. CMF_DIALOG_LANGUAGE: '中文(繁體)',
  2092. CMF_DIALOG_LANGUAGE_DEFAULT: '使用網站語言',
  2093. GM_MENU_SETTINGS: '設置',
  2094. CMF_DIALOG_LOCATION: '「清理我的動態消息」對話框的位置',
  2095. CMF_DIALOG_OPTION: ['左邊', '右邊'],
  2096. CMF_BORDER_COLOUR: '邊框顏色',
  2097. DLG_TIPS: '提示',
  2098. DLG_TIPS_CONTENT: '清除瀏覽器快取會將您的設定重置為預設值。\n\n使用「匯出」和「匯入」按鈕來備份和回復您的自定義設定。',
  2099. DLG_BUTTONS: ['儲存', '關閉', '匯出', '匯入', '重設'],
  2100. DLG_FB_COLOUR_HINT: '留空以使用 FB 的配色方案',
  2101. },
  2102. },
  2103. defaults: {
  2104. SPONSORED: true,
  2105. NF_TABLIST_STORIES_REELS_ROOMS: false,
  2106. NF_STORIES: false,
  2107. NF_SURVEY: false,
  2108. NF_PEOPLE_YOU_MAY_KNOW: false,
  2109. NF_PAID_PARTNERSHIP: true,
  2110. NF_SPONSORED_PAID: true,
  2111. NF_SUGGESTIONS: false,
  2112. NF_FOLLOW: false,
  2113. NF_PARTICIPATE: false,
  2114. NF_REELS_SHORT_VIDEOS: false,
  2115. NF_SHORT_REEL_VIDEO: false,
  2116. NF_EVENTS_YOU_MAY_LIKE: false,
  2117. NF_ANIMATED_GIFS_POSTS: false,
  2118. NF_ANIMATED_GIFS_PAUSE: false,
  2119. NF_SHARES: false,
  2120. NF_LIKES_MAXIMUM: false,
  2121. GF_PAID_PARTNERSHIP: true,
  2122. GF_SUGGESTIONS: false,
  2123. GF_SHORT_REEL_VIDEO: false,
  2124. GF_ANIMATED_GIFS_POSTS: false,
  2125. GF_ANIMATED_GIFS_PAUSE: false,
  2126. GF_SHARES: false,
  2127. VF_LIVE: false,
  2128. VF_INSTAGRAM: false,
  2129. VF_DUPLICATE_VIDEOS: false,
  2130. VF_ANIMATED_GIFS_PAUSE: false,
  2131. PP_ANIMATED_GIFS_POSTS: false,
  2132. PP_ANIMATED_GIFS_PAUSE: false,
  2133. NF_BLOCKED_FEED: ['1', '0', '0'],
  2134. GF_BLOCKED_FEED: ['0', '1', '0'],
  2135. VF_BLOCKED_FEED: ['0', '0', '1'],
  2136. MP_BLOCKED_FEED: ['1', '0', '0'],
  2137. PP_BLOCKED_FEED: ['1', '0', '0'],
  2138. OTHER_INFO_BOX_CORONAVIRUS: false,
  2139. OTHER_INFO_BOX_CLIMATE_SCIENCE: false,
  2140. OTHER_INFO_BOX_SUBSCRIBE: false,
  2141. REELS_CONTROLS: true,
  2142. REELS_DISABLE_LOOPING: true,
  2143. NF_BLOCKED_ENABLED: false,
  2144. GF_BLOCKED_ENABLED: false,
  2145. VF_BLOCKED_ENABLED: false,
  2146. MP_BLOCKED_ENABLED: false,
  2147. PP_BLOCKED_ENABLED: false,
  2148. NF_BLOCKED_RE: false,
  2149. GF_BLOCKED_RE: false,
  2150. VF_BLOCKED_RE: false,
  2151. MP_BLOCKED_RE: false,
  2152. PP_BLOCKED_RE: false,
  2153. DLG_VERBOSITY: '1',
  2154. VERBOSITY_DEBUG: false,
  2155. VERBOSITY_MESSAGE_BG_COLOUR: 'LightGrey',
  2156. CMF_BTN_OPTION: '0',
  2157. CMF_DIALOG_OPTION: '0',
  2158. CMF_BORDER_COLOUR: 'OrangeRed',
  2159. },
  2160. pathInfo: {
  2161. OTHER_INFO_BOX_CORONAVIRUS: '/coronavirus_info/',
  2162. OTHER_INFO_BOX_CLIMATE_SCIENCE: '/climatescienceinfo/',
  2163. OTHER_INFO_BOX_SUBSCRIBE: '/support/',
  2164. },
  2165. };
  2166.  
  2167. // *** *** end of language components *** ***
  2168.  
  2169. // - console log "label" - used for filtering console logs.
  2170. const log = '-- fbcmf :: ';
  2171.  
  2172. // - idb-keyval - indexedDB wrapper
  2173. // -- needs the "@require https://unpkg.com/idb-keyval@6.0.3/dist/umd.js" entry.
  2174. // -- which functions do we want to use from the idb-keyval?
  2175. const { get, set, del, createStore } = idbKeyval;
  2176. // - override idb-keyval's default db and store names.
  2177. let DBVARS = {
  2178. DBName: 'dbCMF',
  2179. DBStore: 'Mopping',
  2180. DBKey: 'Options',
  2181. ostore: null
  2182. };
  2183. // - make sure the db's store exists ...
  2184. DBVARS.ostore = createStore(DBVARS.DBName, DBVARS.DBStore);
  2185.  
  2186. // - post attribute - hidden and reason
  2187. const postAtt = 'cmfr';
  2188. // - post attribute - consecutive posts id
  2189. const postAttCPID = 'cmfcpid';
  2190. // - post property - # of light dusting duties done
  2191. const postPropDS = 'cmfDusted';
  2192. // - post's child element attribute - used for queries that original don't include the parent element.
  2193. const postAttChildFlag = 'cmfcf';
  2194. // - post's toggle state bar + post tab.
  2195. const postAttTab = 'cmftsb';
  2196. // - marketplace post - indicate already scanned
  2197. const postAttMPSkip = 'cmfsmp';
  2198. // - reel video attribute
  2199. const rvAtt = 'cmfrv';
  2200. // - ...
  2201. const mainColumnAtt = 'cmfmc';
  2202.  
  2203. // - Feed Details variables
  2204. // -- nb: setFeedSettings() adjusts some of these settings.
  2205. const VARS = {
  2206. // - how many times to scan a post
  2207. scanCountStart: 0,
  2208. scanCountMaxLoop: 15, // Nov 2023; changed from 12 to 15, need to make code a tad bit more aggressive.
  2209.  
  2210. noChangeCounter: 0, // number of consecutive loops that reported no change in html structure.
  2211.  
  2212. // - langauge (default to EN)
  2213. language: '',
  2214. // - user options
  2215. Options: {},
  2216. optionsReady: false,
  2217. // - blocked text
  2218. Filters: {},
  2219. // - blocked text separator
  2220. SEP: '¦¦',
  2221.  
  2222. dictionarySponsored: {},
  2223. dictionaryReelsAndShortVideos: {},
  2224.  
  2225. // - Feed toggles
  2226. isNF: false, // news
  2227. isGF: false, // groups
  2228. isVF: false, // videos
  2229. isMF: false, // marketplace
  2230. isAF: false, // all feeds
  2231. isSF: false, // search feed
  2232. isRF: false, // reel feed
  2233. isPP: false, // profile page
  2234.  
  2235. isRF_InTimeoutMode: false, // -- processing Reel videos in timeout calls instead of mutations
  2236.  
  2237. // groups feed type : 'group' = single group; 'groups' = multiple groups;
  2238. gfType: '',
  2239.  
  2240. // watch/videos feed type : 'vidoes' = normal feed; 'search' = search videos;
  2241. vfType: '',
  2242.  
  2243. // marketplace feed type: 'marketplace' = default view; 'category' = category view; 'item' = viewing an item; 'search' = search results;
  2244. mpType: '',
  2245.  
  2246. // remember current URL - used for page change detection
  2247. prevURL: '',
  2248. prevPathname: '',
  2249.  
  2250. // element containing echo message about post(s) being hidden
  2251. echoEl: null,
  2252. echoElFirstNote: null, // for restoring "missing" echo message
  2253. echoElCreatedCount: 0,
  2254. echoELFirstPost: null,
  2255. // how many consecutive posts have been hidden
  2256. echoCount: 0,
  2257. // current consecutive posts id
  2258. echoCPID: '',
  2259.  
  2260. // dark-mode ..
  2261. isDarkMode: null,
  2262.  
  2263. // StyleSheet Id
  2264. cssID: '',
  2265. cssOID: '',
  2266.  
  2267. // Attribute names
  2268. hideAtt: '',
  2269. showAtt: '',
  2270.  
  2271. // special attribute
  2272. b1Att: '',
  2273. b2Att: '',
  2274.  
  2275. // CSS class names
  2276. cssHideEl: '',
  2277. cssEcho: '',
  2278. cssHideNumberOfShares: '',
  2279.  
  2280. // toggle dialog button (visible if is a Feed page)
  2281. btnToggleEl: null,
  2282. // - script's logo
  2283. 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>',
  2284. // - new window icon
  2285. 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>',
  2286. iconNewWindowClass: 'cmf-link-new',
  2287. // - for reels - chromium browsers needs more space for video controls...
  2288. isChromium: false,
  2289. };
  2290.  
  2291. // -- user's language portion of the masterKeywords
  2292. let KeyWords = {};
  2293. function cloneKeywords() {
  2294. if (VARS.language && VARS.language !== '') {
  2295. KeyWords = { ...masterKeyWords.translations[VARS.language] };
  2296. }
  2297. else {
  2298. KeyWords = { ...masterKeyWords.translations['en'] };
  2299. }
  2300. }
  2301.  
  2302. // -- which language is the FB page in?
  2303. function setLanguageAndOptions() {
  2304. // - run this function when HEAD is available.
  2305. // - also run getUserOptions().
  2306. if (document.head) {
  2307. // ...
  2308. let result = getUserOptions().then(() => {
  2309. // -- getUserOptions() will set the language.
  2310. return true;
  2311. });
  2312. }
  2313. else {
  2314. setTimeout(setLanguageAndOptions, 5);
  2315. }
  2316. }
  2317.  
  2318. function isDarkMode() {
  2319. // -- fb's dark-mode : off
  2320. if (document.documentElement.classList.contains('__fb-light-mode')) {
  2321. return false;
  2322. };
  2323. // -- fb's dark-mode : on
  2324. if (document.documentElement.classList.contains('__fb-dark-mode')) {
  2325. return true;
  2326. };
  2327. // -- fb's dark-mode: automatic;
  2328. if (document.body) {
  2329. // -- check the body's background colour
  2330. const bodyBackgroundColour = window.getComputedStyle(document.body).backgroundColor;
  2331. const rgb = bodyBackgroundColour.match(/\d+/g);
  2332. if (rgb) {
  2333. const red = parseInt(rgb[0], 10);
  2334. const green = parseInt(rgb[1], 10);
  2335. const blue = parseInt(rgb[2], 10);
  2336.  
  2337. const luminance = 0.299 * red + 0.587 * green + 0.114 * blue;
  2338. return luminance < 128;
  2339. }
  2340. }
  2341. // -- fallback ...
  2342. return false;
  2343. }
  2344.  
  2345. function buildDictionaries() {
  2346. // -- Sponsored
  2347. VARS.dictionarySponsored = Object.values(masterKeyWords.translations).map(translation => translation.SPONSORED);
  2348. VARS.dictionarySponsored = Object.values(masterKeyWords.translations).flatMap(translation => [
  2349. // -- the ?.toLowerCase() - optional chaining - used when there's a value.
  2350. translation.SPONSORED?.toLowerCase(),
  2351. translation.SPONSORED_EXTRA?.toLowerCase()
  2352. ]).filter(Boolean);
  2353.  
  2354. // -- Reels and Short Videos
  2355. VARS.dictionaryReelsAndShortVideos = Object.values(masterKeyWords.translations).map(translation => translation.NF_REELS_SHORT_VIDEOS);
  2356. }
  2357.  
  2358. function generateRandomString() {
  2359. // - generate random text (first letter must be an alphabet)
  2360. // -- used for css classes
  2361. // -- used for postAttCPID
  2362. // -- used for tagging items
  2363. const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  2364. const strArray = [chars.charAt(Math.floor(Math.random() * 52))]; // First letter must be an alphabet
  2365.  
  2366. for (let i = 0; i < 12; i++) {
  2367. strArray.push(chars.charAt(Math.floor(Math.random() * 62)));
  2368. }
  2369.  
  2370. return strArray.join('');
  2371. }
  2372.  
  2373. // -- stylesheet builder
  2374. VARS.tempStyleSheetCode = ''; // holds the SS code while it is being built.
  2375. function addToSS(classes, styles) {
  2376. // -- formats and builds the StyleSheet code
  2377. // -- parameters: classes (separated by comma), styles (separated by semicolon)
  2378. // -- array actions: .filter - remove empties, .map - trim (or pad + trim)
  2379. // -- function throwing a wobble? - check the properties and values pairs (one could be unmatched)
  2380. const listOfClasses = classes.split(',').filter(function (e) { return e.trim() }).map(e => e.trim());
  2381. let styleLines = styles.split(';').filter(function (e) { return e.trim() });
  2382. styleLines = styleLines.map(function (e) {
  2383. let temp = e.split(':');
  2384. return ' ' + temp[0].trim() + ':' + temp[1].trim();
  2385. });
  2386.  
  2387. let temp = listOfClasses.join(',\n') + ' {\n';
  2388. temp += styleLines.join(';\n') + ';\n';
  2389. temp += '}\n';
  2390. VARS.tempStyleSheetCode += temp;
  2391. }
  2392.  
  2393. // -- various CSS
  2394. function addCSS() {
  2395. // - CSS styles for hiding or highlighting the selected posts / element
  2396. // - CSS styles for dialog panel
  2397. let head, elStylesheet;
  2398. let isNewCSS = true;
  2399.  
  2400. if (VARS.cssID !== '') {
  2401. // - Reset the existing Stylesheet
  2402. elStylesheet = document.getElementById(VARS.cssID);
  2403. if (elStylesheet) {
  2404. elStylesheet.replaceChildren();
  2405. isNewCSS = false;
  2406. }
  2407. }
  2408. if (isNewCSS) {
  2409. // - Create the new Stylesheet head + classnames
  2410. VARS.cssID = generateRandomString().toUpperCase();
  2411. elStylesheet = document.createElement('style');
  2412. elStylesheet.setAttribute('type', 'text/css');
  2413. elStylesheet.setAttribute('id', VARS.cssID);
  2414. head = document.getElementsByTagName('head')[0];
  2415. head.appendChild(elStylesheet);
  2416.  
  2417. // - remember <element> attribute names (for other functions to use)
  2418. VARS.hideAtt = generateRandomString(); // - the parent element - hides the nth level down element
  2419. VARS.hideWithNoCaptionAtt = generateRandomString(); // - the element to hide - where there's no child element
  2420. VARS.cssHideEl = generateRandomString(); // - the element to hide - where there's no child element
  2421. VARS.cssHideNumberOfShares = generateRandomString(); // - hide "# shares" on posts.
  2422. VARS.showAtt = generateRandomString(); // - for revealing hidden elements.
  2423. }
  2424. VARS.tempStyleSheetCode = ''; // reset temp CSS code.
  2425.  
  2426. // -- override random class names - for testing purposes only.
  2427. // VARS.hideAtt = 'cmfr-hide';
  2428. // VARS.cssHideEl = 'cmfr-hide-element';
  2429. // VARS.cssHideNumberOfShares = 'cmfr-hide-shares';
  2430.  
  2431.  
  2432. // -- **** fix fb's bug in not "hiding" certain elements properly when scrolling
  2433. addToSS('body > div[style*="position: absolute"], ' +
  2434. 'body > div[style*="position:absolute"]',
  2435. 'top: -1000000px !important;'
  2436. );
  2437.  
  2438. // -- hide the post
  2439. // -- not using hidden post caption facility
  2440. addToSS(
  2441. `div[${VARS.hideAtt}]`,
  2442. 'max-height: 0; overflow: hidden; margin-bottom:0 !important;'
  2443. );
  2444.  
  2445. // -- reveal the post
  2446. // -- first one, inside a <details> element (showAtt's attribute not required)
  2447. // -- second one, not inside a <details> element
  2448. // -- not using hidden post caption facility
  2449. addToSS(
  2450. `details[${postAtt}][open] > div, ` +
  2451. `details[${postAtt}][open] > span > div, ` + // -- usually aside components
  2452. `div[${VARS.showAtt}]:not([id="fbcmf"])`,
  2453. 'max-height: 10000px; overflow: auto; margin-bottom:1rem !important; ' +
  2454. `border:3px dotted ${VARS.Options.CMF_BORDER_COLOUR} !important; border-radius:8px; padding:0.2rem 0.1rem 0.1rem 0.1rem;` // 4px
  2455. );
  2456.  
  2457. // -- summary element - list-style removes the twistie symbol
  2458. // -- using the +/- symbols to open/close
  2459. addToSS(
  2460. `details[${postAtt}] > summary`,
  2461. 'cursor: pointer; list-style: none; ' +
  2462. 'position: relative; ' +
  2463. 'margin:1.5rem auto; padding:0.5rem 1rem; border-radius:0.55rem; width:85%; font-style:italic;' +
  2464. ((VARS.Options.VERBOSITY_MESSAGE_COLOUR === '') ? '' : ` color: ${VARS.Options.VERBOSITY_MESSAGE_COLOUR}; `) +
  2465. `background-color:${(VARS.Options.VERBOSITY_MESSSAGE_BG_COLOUR === '') ? masterKeyWords.defaults.VERBOSITY_MESSAGE_BG_COLOUR : VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR};`
  2466. );
  2467. addToSS(
  2468. `details[${postAtt}] > summary:hover`,
  2469. 'text-decoration: underline; background-color:white; color:black;'
  2470. );
  2471. // -- formatting of +/-
  2472. addToSS(
  2473. `details[${postAtt}] > summary::after`,
  2474. 'background: darkgrey; color: white; border-radius: 50%;' +
  2475. 'width: 24px; height: 24px; line-height: 20px;' +
  2476. 'font-size: 1rem; font-weight: bold; transform: translateY(-50%); text-align: center;' +
  2477. 'position: absolute; top: 1rem; right: 0.25rem;'
  2478. );
  2479. addToSS(
  2480. `details[${postAtt}] > summary::after`,
  2481. 'content:"\\002B";' // "+"
  2482. );
  2483. addToSS(
  2484. `details[${postAtt}][open] > summary::after`,
  2485. 'content: "\\2212";' // "-"
  2486. );
  2487.  
  2488. // -- reveal a hidden post
  2489. addToSS(
  2490. `details[${postAtt}][open]`,
  2491. 'margin-bottom: 1rem;'
  2492. );
  2493. addToSS(
  2494. `details[${postAtt}][open] > summary`,
  2495. 'margin-bottom: 0.5rem;'
  2496. );
  2497.  
  2498. // -- hide a component (e.g. marketplace item)
  2499. addToSS(
  2500. `div[${VARS.hideWithNoCaptionAtt}],` +
  2501. `span[${VARS.hideWithNoCaptionAtt}]`,
  2502. 'display: none;'
  2503. );
  2504. // -- show a component (e.g. marketplace item)
  2505. addToSS(
  2506. `div[${VARS.hideWithNoCaptionAtt}][${VARS.showAtt}], ` +
  2507. `span[${VARS.hideWithNoCaptionAtt}][${VARS.showAtt}]`,
  2508. 'display: block;'
  2509. );
  2510.  
  2511. // - mini-caption (for gf + vf consecutive mode)
  2512. addToSS(
  2513. `h6[${postAttTab}]`,
  2514. '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;' +
  2515. ((VARS.Options.VERBOSITY_MESSAGE_COLOUR === '') ? '' : ` color: ${VARS.Options.VERBOSITY_MESSAGE_COLOUR}; `) +
  2516. `background-color:${(VARS.Options.VERBOSITY_MESSSAGE_BG_COLOUR === '') ? masterKeyWords.defaults.VERBOSITY_MESSAGE_BG_COLOUR : VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR}; `
  2517. );
  2518.  
  2519.  
  2520. // -- # shares
  2521. addToSS(
  2522. `[${VARS.cssHideNumberOfShares}]`,
  2523. 'display:none !important;'
  2524. );
  2525.  
  2526. // - dailog box CSS
  2527. // --- dialog box; position + flex
  2528. let bColour = (VARS.Options.CMF_BORDER_COLOUR === '') ? masterKeyWords.defaults.CMF_BORDER_COLOUR : VARS.Options.CMF_BORDER_COLOUR;
  2529. let tColour = 'var(--primary-text)';
  2530. // - left / right done in fn addExtraCSS()
  2531. addToSS(
  2532. '.fb-cmf ',
  2533. 'position:fixed; top:0.15rem; bottom:0.15rem; display:flex; flex-direction:column; width: 100%; max-width:30rem; padding:0 1rem; z-index:5;' +
  2534. `border:2px solid ${bColour}; border-radius:1rem; opacity:0; visibility:hidden; color:${tColour};`
  2535. );
  2536. // - dialog's background color
  2537. if (VARS.isDarkMode) {
  2538. addToSS('.fb-cmf', 'background-color:var(--web-wash);');
  2539. }
  2540. else {
  2541. addToSS('.fb-cmf', 'background-color:#fefefa;');
  2542. }
  2543.  
  2544. // addToSS('.__fb-light-mode .fb-cmf', 'background-color:#fefefa;');
  2545. // addToSS('.__fb-dark-mode .fb-cmf', 'background-color:var(--web-wash);');
  2546. // addToSS('.fb-cmf', 'background-color:floralwhite;'); // -- fall back colour.
  2547. // addToSS('.fb-cmf', 'background-color:var(--web-wash);'); // -- fall back colour.
  2548.  
  2549. addToSS(
  2550. '.fb-cmf header',
  2551. 'display:flex; justify-content:space-between; direction:ltr;'
  2552. );
  2553. addToSS(
  2554. '.fb-cmf header .fb-cmf-icon',
  2555. 'flex-grow:0; align-self:auto; width:75px; text-align:left; order:1;'
  2556. );
  2557. addToSS(
  2558. '.fb-cmf header .fb-cmf-icon svg',
  2559. 'width:64px; height:64px; margin:2px 0;'
  2560. );
  2561. addToSS(
  2562. '.fb-cmf header .fb-cmf-title',
  2563. 'flex-grow:2; align-self:auto; order:2;'
  2564. );
  2565. addToSS(
  2566. '.fb-cmf header .fb-cmf-title .script-version',
  2567. 'font-size: 0.75rem; font-weight: normal;'
  2568. );
  2569. addToSS(
  2570. '.fb-cmf header .fb-cmf-lang-1',
  2571. 'padding-top:1.25rem;'
  2572. );
  2573. addToSS(
  2574. '.fb-cmf header .fb-cmf-lang-2',
  2575. 'padding-top:0.75rem;'
  2576. );
  2577. addToSS(
  2578. '.fb-cmf header .fb-cmf-title > div',
  2579. 'font-size:1.35rem; font-weight: 700; text-align:center;'
  2580. );
  2581. addToSS(
  2582. '.fb-cmf header .fb-cmf-title > small',
  2583. 'display:block; font-size:0.8rem; text-align:center;'
  2584. );
  2585. addToSS(
  2586. '.fb-cmf header .fb-cmf-close',
  2587. 'flex-grow:0; align-self:auto; width:75px; text-align:right; padding: 1.5rem 0 0 0; order:3;'
  2588. );
  2589. addToSS(
  2590. '.fb-cmf header .fb-cmf-close button',
  2591. 'width:1.75rem; height:1.5rem; font-family: monospace;'
  2592. );
  2593.  
  2594. // -- content
  2595. addToSS(
  2596. '.fb-cmf div.content',
  2597. `flex:1; overflow: hidden scroll; border:2px double ${bColour}; border-radius:0.5rem; color: var(--primary-text);`
  2598. );
  2599. addToSS(
  2600. '.fb-cmf fieldset',
  2601. 'margin:0.5rem; padding:0.5rem; border-style: solid;'
  2602. );
  2603. addToSS(
  2604. '.fb-cmf fieldset *',
  2605. // 'font-size: calc(var(--body-font-size) * 0.9);'
  2606. //'font-size: var(--body-font-size);'
  2607. 'font-size: 0.8125rem;'
  2608. )
  2609. addToSS(
  2610. '.fb-cmf fieldset legend',
  2611. 'font-size: 0.95rem;' +
  2612. 'width: 95%; padding: 0 0.5rem 0.125rem 0.5rem;' +
  2613. 'line-height: 2.5; ' +
  2614. 'border-width: 2px; border-style: solid; border-radius: 0.5rem 0.5rem 0 0 ;'
  2615. );
  2616. addToSS(
  2617. '.fb-cmf fieldset legend:hover,' +
  2618. '.fb-cmf fieldset label:hover',
  2619. //'background-color: LightGrey; cursor: pointer;'
  2620. 'background-color: var(--hover-overlay); cursor: pointer;'
  2621. );
  2622. addToSS(
  2623. '.fb-cmf fieldset.visible,' +
  2624. '.fb-cmf fieldset.visible legend ',
  2625. `border-color: ${bColour};`
  2626. );
  2627. addToSS(
  2628. '.fb-cmf fieldset.hidden,' +
  2629. '.fb-cmf fieldset.hidden legend ',
  2630. 'border-color: LightGrey;'
  2631. );
  2632. addToSS(
  2633. '.fb-cmf fieldset.hidden *:not(legend) ',
  2634. 'display: none;'
  2635. );
  2636. addToSS(
  2637. '.fb-cmf fieldset.visible legend::after',
  2638. 'content: "\\2212"; float:right;' // "-"
  2639. )
  2640. addToSS(
  2641. '.fb-cmf fieldset.hidden legend::after',
  2642. 'content: "\\002B"; float:right;' // "+"
  2643. )
  2644. addToSS(
  2645. '.fb-cmf fieldset label',
  2646. 'display:inline-block; padding:0.125rem 0; color: var(--primary-text); font-weight: normal; width:100%;'
  2647. );
  2648. addToSS(
  2649. '.fb-cmf fieldset label input',
  2650. 'margin: 0 0.5rem 0 0.5rem; vertical-align:baseline;' // left & right margins for RTL & LTR text
  2651. );
  2652. addToSS(
  2653. '.fb-cmf fieldset label[disabled]',
  2654. 'color:darkgrey;'
  2655. );
  2656. addToSS(
  2657. '.fb-cmf fieldset textarea',
  2658. 'width:100%; height:12rem;'
  2659. );
  2660. addToSS(
  2661. '.fb-cmf fieldset select',
  2662. 'border: 2px inset lightgray;' +
  2663. 'margin: 0 0.5rem 0 0.5rem; vertical-align:baseline;' // left & right margins for RTL & LTR text
  2664. )
  2665. addToSS(
  2666. '.__fb-dark-mode .fb-cmf fieldset textarea,' +
  2667. '.__fb-dark-mode .fb-cmf fieldset input[type="input"]' +
  2668. '.__fb-dark-mode .fb-cmf fieldset select',
  2669. 'background-color:var(--comment-background); color:var(--primary-text);'
  2670. );
  2671. // -- footer - buttons + results
  2672. addToSS(
  2673. '.fb-cmf footer',
  2674. 'display: grid; justify-content: space-evenly; padding:1rem 0.25rem; text-align:center;'
  2675. );
  2676. addToSS(
  2677. '.fb-cmf .buttons button',
  2678. // 'margin-left: 1rem; margin-right:1rem;'
  2679. 'margin-left: 0.25rem; margin-right: 0.25rem;'
  2680. );
  2681. // -- footer - file input
  2682. addToSS(
  2683. '.fb-cmf .fileInput',
  2684. 'display:none;'
  2685. );
  2686. // -- footer - import results
  2687. addToSS(
  2688. '.fb-cmf .fileResults',
  2689. 'grid-column-start: 1; grid-column-end: 6; font-style:italic; margin-top: 1rem;'
  2690. );
  2691. // -- show dialog box (default is not to show)
  2692. addToSS(
  2693. `.fb-cmf[${VARS.showAtt}]`,
  2694. 'opacity:1; transform:scale(1); visibility:visible;'
  2695. );
  2696. // -- new window icon
  2697. addToSS(
  2698. `.${VARS.iconNewWindowClass}`,
  2699. 'width: 1rem; height: 1rem;'
  2700. );
  2701. // 'width: 1rem; height: 1rem; margin-left: 0.2rem; margin-right: 0.2rem;'
  2702. addToSS(
  2703. `.${VARS.iconNewWindowClass} a`,
  2704. 'width: 1rem; position: relative; display: inline-block;'
  2705. );
  2706. addToSS(
  2707. `.${VARS.iconNewWindowClass} svg`,
  2708. 'position: absolute; top: -13.5px; stroke: rgb(101, 103, 107);'
  2709. );
  2710.  
  2711. // - save & apply the CSS
  2712. elStylesheet.appendChild(document.createTextNode(VARS.tempStyleSheetCode));
  2713. VARS.tempStyleSheetCode = '';
  2714. }
  2715.  
  2716. function addExtraCSS() {
  2717. // - extra CSS styles
  2718. // - fb can sometimes be a bit slow in loading certain parts of the site ...
  2719. // - ... this function is called several ms later ...
  2720. // - ... and when saving the options (via save button)
  2721.  
  2722. // -- button location
  2723. let cmfBtnLocation = masterKeyWords.defaults.CMF_BTN_OPTION;
  2724. // -- dialog location
  2725. let cmfDlgLocation = masterKeyWords.defaults.CMF_DIALOG_OPTION;
  2726. // -- read in the settings
  2727. if (VARS.Options.hasOwnProperty('CMF_BTN_OPTION')) {
  2728. if (VARS.Options.CMF_BTN_OPTION.toString() !== '') {
  2729. cmfBtnLocation = VARS.Options.CMF_BTN_OPTION;
  2730. }
  2731. }
  2732. if (VARS.Options.hasOwnProperty('CMF_DIALOG_OPTION')) {
  2733. if (VARS.Options.CMF_DIALOG_OPTION.toString() !== '') {
  2734. cmfDlgLocation = VARS.Options.CMF_DIALOG_OPTION;
  2735. }
  2736. }
  2737. cmfBtnLocation = cmfBtnLocation.toString();
  2738. cmfDlgLocation = cmfDlgLocation.toString();
  2739.  
  2740. // Grab the existing Stylesheet and amend it
  2741. let elStylesheet = document.getElementById(VARS.cssID);
  2742. let styles;
  2743.  
  2744. VARS.tempStyleSheetCode = ''; // reset
  2745. styles = '';
  2746.  
  2747. // - button's location.
  2748. if (cmfBtnLocation === '1') {
  2749. // - top right
  2750. if (document.querySelector('[role="banner"]')) {
  2751. // - oldish FB structure has menu buttons across the top (changed for some users in Apr/May 2022)
  2752. addToSS(
  2753. 'div[role="banner"] > div:last-of-type div[role="navigation"]',
  2754. 'margin-right: 42px;'
  2755. );
  2756. }
  2757. styles = 'position:fixed; top:0.5rem; right:0.5rem; display:none;';
  2758. }
  2759. else if (cmfBtnLocation === '2') {
  2760. // - disabled, use "Settings" in the user script menu.
  2761. // - no css
  2762. styles = 'display: none !important;';
  2763. }
  2764. else {
  2765. // - cmfBtnLocation === "0" : bottom left
  2766. // - has the buttons running down the side of the page (May 2022 ->).
  2767. // styles = 'position:fixed; bottom:4.25rem; left:1.1rem; display:none;';
  2768. styles = 'position:fixed; bottom:4.25rem; left:1.1rem; display:none; z-index: 999;';
  2769. }
  2770. if (styles.length > 0) {
  2771. addToSS(
  2772. '.fb-cmf-toggle',
  2773. styles
  2774. );
  2775. // - btn - basic styling.
  2776. addToSS('.fb-cmf-toggle', 'border-radius:0.3rem;');
  2777. addToSS('.fb-cmf-toggle svg', 'height:32px; width:32px;');
  2778. addToSS('.fb-cmf-toggle:hover', 'cursor:pointer;');
  2779. // - dialog box's display
  2780. addToSS(`.fb-cmf-toggle[${VARS.showAtt}]`, 'display:block;');
  2781. }
  2782. // - dialog box's left/right + animated open/close behaviour
  2783. if (cmfDlgLocation === '1') {
  2784. // - right
  2785. styles = 'right:0.35rem; margin-left:1rem; transform:scale(0);transform-origin:top right;';
  2786. }
  2787. else {
  2788. // - left (cmfDlgLocation === '0')
  2789. styles = 'left:4.25rem; margin-right:1rem; transform:scale(0);transform-origin:center center;';
  2790. }
  2791. addToSS(
  2792. '.fb-cmf',
  2793. styles +
  2794. 'transition:transform .45s ease, opacity .25s ease, visibility 1s ease;'
  2795. );
  2796. if (VARS.tempStyleSheetCode.length > 0) {
  2797. elStylesheet.appendChild(document.createTextNode(VARS.tempStyleSheetCode));
  2798. VARS.tempStyleSheetCode = '';
  2799. }
  2800. }
  2801.  
  2802. // -- get the user's settings ...
  2803. async function getUserOptions() {
  2804. // -- read in the saved data, else set defaults.
  2805. let changed = false;
  2806. // - reset Options
  2807. VARS.Options = new Object();
  2808. VARS.optionsReady = false;
  2809.  
  2810. // - has the user previously saved options?
  2811. // -- if yes, the update Options
  2812. let result = await get(DBVARS.DBKey, DBVARS.ostore).then((values) => {
  2813. if (values) {
  2814. // -- has data
  2815. VARS.Options = JSON.parse(values);
  2816. return 1;
  2817. }
  2818. else {
  2819. // -- no data (first time)
  2820. return 0;
  2821. }
  2822. }).catch((err) => {
  2823. console.info(`${log}getuserOptions() > get() - Error:`, err);
  2824. });
  2825.  
  2826. if (!VARS.Options.hasOwnProperty('CMF_DIALOG_LANGUAGE')) {
  2827. const lang = document.head.parentNode.lang || 'en';
  2828. VARS.language = masterKeyWords.translations.hasOwnProperty(lang) ? lang : 'en';
  2829. //console.info(log + 'getUserOptions(); .. first time .. :', lang, VARS.language);
  2830. }
  2831. else {
  2832. const uiLang = VARS.Options.CMF_DIALOG_LANGUAGE || 'en';
  2833. const lang = document.head.parentNode.lang || 'en';
  2834. if (masterKeyWords.translations.hasOwnProperty(uiLang)) {
  2835. VARS.language = uiLang;
  2836. }
  2837. else if (masterKeyWords.translations.hasOwnProperty(lang)) {
  2838. VARS.language = lang;
  2839. }
  2840. else {
  2841. VARS.language = lang;
  2842. }
  2843. //console.info(log + 'getUserOptions(); language:', uiLang, lang, VARS.language);
  2844. //console.info(log + 'getUserOptions(); masterKeywords.transations:', masterKeyWords.translations);
  2845. }
  2846. VARS.Options.CMF_DIALOG_LANGUAGE = VARS.language;
  2847.  
  2848. cloneKeywords();
  2849.  
  2850. // -- check that all variables exists ... if not, assign them default values..
  2851. // -- Sponsored (always enabled)
  2852. if (!VARS.Options.hasOwnProperty('NF_SPONSORED')) {
  2853. VARS.Options.NF_SPONSORED = masterKeyWords.defaults['SPONSORED'];
  2854. changed = true;
  2855. }
  2856. if (!VARS.Options.hasOwnProperty('GF_SPONSORED')) {
  2857. VARS.Options.GF_SPONSORED = masterKeyWords.defaults['SPONSORED'];
  2858. changed = true;
  2859. }
  2860. if (!VARS.Options.hasOwnProperty('VF_SPONSORED')) {
  2861. VARS.Options.VF_SPONSORED = masterKeyWords.defaults['SPONSORED'];
  2862. changed = true;
  2863. }
  2864. if (!VARS.Options.hasOwnProperty('MP_SPONSORED')) {
  2865. VARS.Options.MP_SPONSORED = masterKeyWords.defaults['SPONSORED'];
  2866. changed = true;
  2867. }
  2868.  
  2869. // -- which option has been enabled / disabled?
  2870. VARS.hideAnInfoBox = false;
  2871. for (const key in KeyWords) {
  2872. if (key.slice(0, 3) === 'NF_' && key.slice(0, 10) !== 'NF_BLOCKED') {
  2873. if (!VARS.Options.hasOwnProperty(key)) {
  2874. VARS.Options[key] = masterKeyWords.defaults[key];
  2875. changed = true;
  2876. }
  2877. }
  2878. else if (key.slice(0, 3) === 'GF_' && key.slice(0, 10) !== 'GF_BLOCKED') {
  2879. if (!VARS.Options.hasOwnProperty(key)) {
  2880. VARS.Options[key] = masterKeyWords.defaults[key];
  2881. changed = true;
  2882. }
  2883. }
  2884. else if (key.slice(0, 3) === 'VF_' && key.slice(0, 10) !== 'VF_BLOCKED') {
  2885. if (!VARS.Options.hasOwnProperty(key)) {
  2886. VARS.Options[key] = masterKeyWords.defaults[key];
  2887. changed = true;
  2888. }
  2889. }
  2890. else if (key.slice(0, 3) === 'MP_' && key.slice(0, 10) !== 'MP_BLOCKED') {
  2891. if (!VARS.Options.hasOwnProperty(key)) {
  2892. VARS.Options[key] = masterKeyWords.defaults[key];
  2893. changed = true;
  2894. }
  2895. }
  2896. else if (key.slice(0, 3) === 'PP_' && key.slice(0, 10) !== 'PP_BLOCKED') {
  2897. if (!VARS.Options.hasOwnProperty(key)) {
  2898. VARS.Options[key] = masterKeyWords.defaults[key];
  2899. changed = true;
  2900. }
  2901. }
  2902. else if (key.slice(0, 10) === 'OTHER_INFO') {
  2903. if (!VARS.Options.hasOwnProperty(key)) {
  2904. VARS.Options[key] = masterKeyWords.defaults[key];
  2905. changed = true;
  2906. }
  2907. if (VARS.Options[key]) {
  2908. VARS.hideAnInfoBox = true;
  2909. }
  2910. }
  2911. }
  2912.  
  2913. // -- all other options.
  2914. if (!VARS.Options.hasOwnProperty('NF_BLOCKED_ENABLED')) {
  2915. VARS.Options.NF_BLOCKED_ENABLED = masterKeyWords.defaults.NF_BLOCKED_ENABLED;
  2916. changed = true;
  2917. }
  2918. if (!VARS.Options.hasOwnProperty('NF_BLOCKED_FEED')) {
  2919. VARS.Options.NF_BLOCKED_FEED = masterKeyWords.defaults.NF_BLOCKED_FEED;
  2920. changed = true;
  2921. }
  2922. if (!VARS.Options.hasOwnProperty('NF_BLOCKED_TEXT')) {
  2923. VARS.Options.NF_BLOCKED_TEXT = '';
  2924. changed = true;
  2925. }
  2926.  
  2927. if (!VARS.Options.hasOwnProperty('GF_BLOCKED_ENABLED')) {
  2928. VARS.Options.GF_BLOCKED_ENABLED = masterKeyWords.defaults.GF_BLOCKED_ENABLED;
  2929. changed = true;
  2930. }
  2931. if (!VARS.Options.hasOwnProperty('GF_BLOCKED_FEED')) {
  2932. VARS.Options.GF_BLOCKED_FEED = masterKeyWords.defaults.GF_BLOCKED_FEED;
  2933. changed = true;
  2934. }
  2935. if (!VARS.Options.hasOwnProperty('GF_BLOCKED_TEXT')) {
  2936. VARS.Options.GF_BLOCKED_TEXT = '';
  2937. changed = true;
  2938. }
  2939.  
  2940. if (!VARS.Options.hasOwnProperty('VF_BLOCKED_ENABLED')) {
  2941. VARS.Options.VF_BLOCKED_ENABLED = masterKeyWords.defaults.VF_BLOCKED_ENABLED;
  2942. changed = true;
  2943. }
  2944. if (!VARS.Options.hasOwnProperty('VF_BLOCKED_FEED')) {
  2945. VARS.Options.VF_BLOCKED_FEED = masterKeyWords.defaults.VF_BLOCKED_FEED;
  2946. changed = true;
  2947. }
  2948. if (!VARS.Options.hasOwnProperty('VF_BLOCKED_TEXT')) {
  2949. VARS.Options.VF_BLOCKED_TEXT = '';
  2950. changed = true;
  2951. }
  2952.  
  2953. if (!VARS.Options.hasOwnProperty('MP_BLOCKED_ENABLED')) {
  2954. VARS.Options.MP_BLOCKED_ENABLED = masterKeyWords.defaults.MP_BLOCKED_ENABLED;
  2955. changed = true;
  2956. }
  2957. if (!VARS.Options.hasOwnProperty('MP_BLOCKED_FEED')) {
  2958. VARS.Options.MP_BLOCKED_FEED = masterKeyWords.defaults.MP_BLOCKED_FEED;
  2959. changed = true;
  2960. }
  2961. if (!VARS.Options.hasOwnProperty('MP_BLOCKED_TEXT')) {
  2962. VARS.Options.MP_BLOCKED_TEXT = '';
  2963. changed = true;
  2964. }
  2965. if (!VARS.Options.hasOwnProperty('MP_BLOCKED_TEXT_DESCRIPTION')) {
  2966. VARS.Options.MP_BLOCKED_TEXT_DESCRIPTION = '';
  2967. changed = true;
  2968. }
  2969.  
  2970. if (!VARS.Options.hasOwnProperty('PP_BLOCKED_ENABLED')) {
  2971. VARS.Options.PP_BLOCKED_ENABLED = masterKeyWords.defaults.PP_BLOCKED_ENABLED;
  2972. changed = true;
  2973. }
  2974. if (!VARS.Options.hasOwnProperty('PP_BLOCKED_FEED')) {
  2975. VARS.Options.PP_BLOCKED_FEED = masterKeyWords.defaults.PP_BLOCKED_FEED;
  2976. changed = true;
  2977. }
  2978. if (!VARS.Options.hasOwnProperty('PP_BLOCKED_TEXT')) {
  2979. VARS.Options.PP_BLOCKED_TEXT = '';
  2980. changed = true;
  2981. }
  2982.  
  2983. if (!VARS.Options.hasOwnProperty('VERBOSITY_LEVEL')) {
  2984. VARS.Options.VERBOSITY_LEVEL = masterKeyWords.defaults.DLG_VERBOSITY;
  2985. changed = true;
  2986. }
  2987. if (!VARS.Options.hasOwnProperty('VERBOSITY_MESSAGE_COLOUR')) {
  2988. VARS.Options.VERBOSITY_MESSAGE_COLOUR = '';
  2989. changed = true;
  2990. }
  2991. // - nb: test conditions, undefined needs to be tested before using .toString(), otherwise JS complains...
  2992. if (
  2993. (!VARS.Options.hasOwnProperty('VERBOSITY_MESSAGE_BG_COLOUR')) ||
  2994. (VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR === undefined) ||
  2995. (VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR.toString() === '')
  2996. ) {
  2997. VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR = masterKeyWords.defaults.VERBOSITY_MESSAGE_BG_COLOUR;
  2998. changed = true;
  2999. }
  3000. if (
  3001. (!VARS.Options.hasOwnProperty('VERBOSITY_DEBUG')) ||
  3002. (VARS.Options.VERBOSITY_DEBUG === undefined) ||
  3003. (VARS.Options.VERBOSITY_DEBUG.toString() === '')
  3004. ) {
  3005. VARS.Options.VERBOSITY_DEBUG = masterKeyWords.defaults.VERBOSITY_DEBUG;
  3006. changed = true;
  3007. }
  3008.  
  3009. if (!VARS.Options.hasOwnProperty('CMF_BTN_OPTION')) {
  3010. VARS.Options.CMF_BTN_OPTION = masterKeyWords.defaults.CMF_BTN_OPTION;
  3011. changed = true;
  3012. }
  3013. if (!VARS.Options.hasOwnProperty('CMF_DIALOG_OPTION')) {
  3014. VARS.Options.CMF_DIALOG_OPTION = masterKeyWords.defaults.CMF_DIALOG_OPTION;
  3015. changed = true;
  3016. }
  3017. if (
  3018. (!VARS.Options.hasOwnProperty('CMF_BORDER_COLOUR')) ||
  3019. (VARS.Options.CMF_BORDER_COLOUR.toString() === undefined) ||
  3020. (VARS.Options.CMF_BORDER_COLOUR.toString() === '')
  3021. ) {
  3022. VARS.Options.CMF_BORDER_COLOUR = masterKeyWords.defaults.CMF_BORDER_COLOUR;
  3023. changed = true;
  3024. }
  3025. if (!VARS.Options.hasOwnProperty('NF_LIKES_MAXIMUM_COUNT')) {
  3026. VARS.Options.NF_LIKES_MAXIMUM_COUNT = '';
  3027. changed = true;
  3028. }
  3029.  
  3030.  
  3031.  
  3032. if (changed) {
  3033. // - save the changes ...
  3034. // -- usually happen if first time setup or change in Options' variables.
  3035. let result = await set(DBVARS.DBKey, JSON.stringify(VARS.Options), DBVARS.ostore).then(() => {
  3036. return true;
  3037. }).catch((err) => {
  3038. console.info(`${log}getUserOptions() > changed > saving - failed, Error: ${err}`);
  3039. return false;
  3040. });
  3041. if (VARS.Options.VERBOSITY_DEBUG) {
  3042. if (result) {
  3043. console.info(`${log}Changed - success`);
  3044. }
  3045. else {
  3046. console.info(`${log}Changed - failed`);
  3047. }
  3048. }
  3049. }
  3050.  
  3051. // split the blocks of texts entries into arrays and translate to lowercase.
  3052. VARS.Filters = new Object();
  3053.  
  3054. // -- original list of text for each feed
  3055. let nfBlockedText = '';
  3056. let gfBlockedText = '';
  3057. let vfBlockedText = '';
  3058. let ppBlockedText = '';
  3059. let mpBlockedText = '';
  3060. let mpBlockedTextDesc = '';
  3061. if (VARS.Options.NF_BLOCKED_ENABLED === true) {
  3062. nfBlockedText = VARS.Options.NF_BLOCKED_TEXT;
  3063. }
  3064. if (VARS.Options.GF_BLOCKED_ENABLED === true) {
  3065. gfBlockedText = VARS.Options.GF_BLOCKED_TEXT;
  3066. }
  3067. if (VARS.Options.VF_BLOCKED_ENABLED === true) {
  3068. vfBlockedText = VARS.Options.VF_BLOCKED_TEXT;
  3069. }
  3070. if (VARS.Options.MP_BLOCKED_ENABLED === true) {
  3071. mpBlockedText = VARS.Options.MP_BLOCKED_TEXT;
  3072. mpBlockedTextDesc = VARS.Options.MP_BLOCKED_TEXT_DESCRIPTION;
  3073. }
  3074. if (VARS.Options.PP_BLOCKED_ENABLED === true) {
  3075. ppBlockedText = VARS.Options.PP_BLOCKED_TEXT;
  3076. }
  3077.  
  3078. // -- final list of text for each feed
  3079. let nfBlockedTextList = '';
  3080. let gfBlockedTextList = '';
  3081. let vfBlockedTextList = '';
  3082. let ppBlockedTextList = '';
  3083. let mpBlockedTextList = '';
  3084. let mpBlockedTextDescList = '';
  3085.  
  3086. // -- ##_BLOCKED_FEED[X] : 0 = NF, 1 = GF, 2 = VF.
  3087. // -- rule: both feeds must be enabled before appending text list from one feed to another text list
  3088. // -- news feed:
  3089. if (VARS.Options.NF_BLOCKED_ENABLED) {
  3090. nfBlockedTextList = nfBlockedText; // final list
  3091. // -- add gf to nf
  3092. if (VARS.Options.GF_BLOCKED_ENABLED && VARS.Options.GF_BLOCKED_FEED[0] === '1') {
  3093. if (gfBlockedText.length > 0) {
  3094. nfBlockedTextList += ((nfBlockedTextList.length > 0) ? VARS.SEP : '') + gfBlockedText;
  3095. }
  3096. }
  3097. // -- add vf to nf
  3098. if (VARS.Options.VF_BLOCKED_ENABLED && VARS.Options.VF_BLOCKED_FEED[0] === '1') {
  3099. if (vfBlockedText.length > 0) {
  3100. nfBlockedTextList += ((nfBlockedTextList.length > 0) ? VARS.SEP : '') + vfBlockedText;
  3101. }
  3102. }
  3103. }
  3104. // -- groups feed:
  3105. if (VARS.Options.GF_BLOCKED_ENABLED) {
  3106. gfBlockedTextList = gfBlockedText; // final list
  3107. // -- add nf to gf
  3108. if (VARS.Options.NF_BLOCKED_ENABLED && VARS.Options.NF_BLOCKED_FEED[1] === '1') {
  3109. if (nfBlockedText.length > 0) {
  3110. gfBlockedTextList += ((gfBlockedTextList.length > 0) ? VARS.SEP : '') + nfBlockedText;
  3111. }
  3112. }
  3113. // -- add vf to gf
  3114. if (VARS.Options.VF_BLOCKED_ENABLED && VARS.Options.VF_BLOCKED_FEED[1] === '1') {
  3115. if (vfBlockedText.length > 0) {
  3116. gfBlockedTextList += ((gfBlockedTextList.length > 0) ? VARS.SEP : '') + vfBlockedText;
  3117. }
  3118. }
  3119. }
  3120. // -- videos feed:
  3121. if (VARS.Options.VF_BLOCKED_ENABLED) {
  3122. vfBlockedTextList = vfBlockedText; // final list
  3123. // -- add nf to vf
  3124. if (VARS.Options.NF_BLOCKED_ENABLED && VARS.Options.NF_BLOCKED_FEED[2] === '1') {
  3125. if (nfBlockedText.length > 0) {
  3126. vfBlockedTextList += ((vfBlockedTextList.length > 0) ? VARS.SEP : '') + nfBlockedText;
  3127. }
  3128. }
  3129. // -- add gf to vf
  3130. if (VARS.Options.GF_BLOCKED_ENABLED && VARS.Options.GF_BLOCKED_FEED[2] === '1') {
  3131. if (gfBlockedText.length > 0) {
  3132. vfBlockedTextList += ((vfBlockedTextList.length > 0) ? VARS.SEP : '') + gfBlockedText;
  3133. }
  3134. }
  3135. }
  3136.  
  3137. // -- market place (stand-alone):
  3138. if (VARS.Options.MP_BLOCKED_ENABLED) {
  3139. mpBlockedTextList = mpBlockedText;
  3140. mpBlockedTextDescList = mpBlockedTextDesc;
  3141. }
  3142.  
  3143. // -- profile page (stand-alone):
  3144. if (VARS.Options.PP_BLOCKED_ENABLED) {
  3145. ppBlockedTextList = ppBlockedText;
  3146. }
  3147.  
  3148. // -- populate the VARS.Filters.###...
  3149. // -- news feed:
  3150. VARS.Filters.NF_BLOCKED_TEXT = [];
  3151. VARS.Filters.NF_BLOCKED_TEXT_LC = [];
  3152. VARS.Filters.NF_BLOCKED_ENABLED = false;
  3153. if (VARS.Options.NF_BLOCKED_ENABLED && nfBlockedTextList.length > 0) {
  3154. VARS.Filters.NF_BLOCKED_ENABLED = true;
  3155. VARS.Filters.NF_BLOCKED_TEXT = nfBlockedTextList.split(VARS.SEP);
  3156. VARS.Filters.NF_BLOCKED_TEXT_LC = VARS.Filters.NF_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  3157. }
  3158. // -- groups feed:
  3159. VARS.Filters.GF_BLOCKED_TEXT = [];
  3160. VARS.Filters.GF_BLOCKED_TEXT_LC = [];
  3161. VARS.Filters.GF_BLOCKED_ENABLED = false;
  3162. if (VARS.Options.GF_BLOCKED_ENABLED && gfBlockedTextList.length > 0) {
  3163. VARS.Filters.GF_BLOCKED_ENABLED = true;
  3164. VARS.Filters.GF_BLOCKED_TEXT = gfBlockedTextList.split(VARS.SEP);
  3165. VARS.Filters.GF_BLOCKED_TEXT_LC = VARS.Filters.GF_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  3166. }
  3167. // -- watch videos feed
  3168. VARS.Filters.VF_BLOCKED_TEXT = [];
  3169. VARS.Filters.VF_BLOCKED_TEXT_LC = [];
  3170. VARS.Filters.VF_BLOCKED_ENABLED = false;
  3171. if (VARS.Options.VF_BLOCKED_ENABLED && vfBlockedTextList.length > 0) {
  3172. VARS.Filters.VF_BLOCKED_ENABLED = true;
  3173. VARS.Filters.VF_BLOCKED_TEXT = vfBlockedTextList.split(VARS.SEP);
  3174. VARS.Filters.VF_BLOCKED_TEXT_LC = VARS.Filters.VF_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  3175. }
  3176. // -- marketplace feed
  3177. VARS.Filters.MP_BLOCKED_TEXT = [];
  3178. VARS.Filters.MP_BLOCKED_TEXT_LC = [];
  3179. VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION = [];
  3180. VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION_LC = [];
  3181. VARS.Filters.MP_BLOCKED_ENABLED = false;
  3182. if (VARS.Options.MP_BLOCKED_ENABLED && ((mpBlockedTextList.length > 0) || (mpBlockedTextDescList.length > 0))) {
  3183. VARS.Filters.MP_BLOCKED_ENABLED = true;
  3184. // -- prices ::
  3185. VARS.Filters.MP_BLOCKED_TEXT = mpBlockedTextList.split(VARS.SEP);
  3186. VARS.Filters.MP_BLOCKED_TEXT_LC = VARS.Filters.MP_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  3187. // -- description ::
  3188. VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION = mpBlockedTextDescList.split(VARS.SEP);
  3189. VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION_LC = VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION.map(btext => btext.toLowerCase());
  3190. }
  3191.  
  3192. // -- profile page feed
  3193. VARS.Filters.PP_BLOCKED_TEXT = [];
  3194. VARS.Filters.PP_BLOCKED_TEXT_LC = [];
  3195. VARS.Filters.PP_BLOCKED_ENABLED = false;
  3196. if (VARS.Options.PP_BLOCKED_ENABLED && ppBlockedTextList.length > 0) {
  3197. VARS.Filters.PP_BLOCKED_ENABLED = true;
  3198. VARS.Filters.PP_BLOCKED_TEXT = ppBlockedTextList.split(VARS.SEP);
  3199. VARS.Filters.PP_BLOCKED_TEXT_LC = VARS.Filters.PP_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  3200. }
  3201.  
  3202. // console.info(log + 'getUserOptions() - Options:', VARS.Options);
  3203. // console.info(log + 'getUserOptions() - Filters:', VARS.Filters);
  3204.  
  3205. VARS.optionsReady = true;
  3206. }
  3207.  
  3208. // -- run some functions now - not dependent on HEAD being available.
  3209. // (includes getUserOptions())
  3210. setLanguageAndOptions();
  3211.  
  3212. // -- dailog box for displaying options (called in runMO)
  3213. function buildMoppingDialog() {
  3214. // build the dialog box component ...
  3215. // -- BODY must be available for use.
  3216. // -- used for displaying/getting/setting the various options
  3217.  
  3218. function createSingleCB(cbName, cbReadOnly = false) {
  3219. // -- create toggle style checkboxes
  3220. const CBTYPE = 'T'; // checkbox, single value, Toggle style
  3221. let cb = document.createElement('input');
  3222. cb.type = 'checkbox';
  3223. cb.setAttribute('cbType', CBTYPE);
  3224. cb.name = cbName;
  3225. cb.value = cbName;
  3226. cb.checked = VARS.Options[cbName];
  3227. let label = document.createElement('label');
  3228. if (cbReadOnly) {
  3229. cb.checked = true;
  3230. cb.disabled = true;
  3231. label.setAttribute('disabled', 'disabled');
  3232. }
  3233. label.appendChild(cb);
  3234. if (KeyWords[cbName]) {
  3235. if (Array.isArray(KeyWords[cbName]) === false) {
  3236. label.appendChild(document.createTextNode(KeyWords[cbName]));
  3237. }
  3238. else {
  3239. label.appendChild(document.createTextNode(Array.from(KeyWords[cbName]).join(', ')));
  3240. }
  3241. }
  3242. else if (['NF_SPONSORED', 'GF_SPONSORED', 'VF_SPONSORED', 'MP_SPONSORED'].includes(cbName)) {
  3243. label.appendChild(document.createTextNode(KeyWords.SPONSORED));
  3244. }
  3245. else {
  3246. label.appendChild(document.createTextNode(cbName));
  3247. }
  3248. let div = document.createElement('div');
  3249. div.appendChild(label);
  3250. return div;
  3251. }
  3252.  
  3253. function createMultipeCBs(cbName, cbReadOnlyIdx = -1) {
  3254. // -- create multiple values checkboxes
  3255. const CBTYPE = 'M'; // checkbox, Multiple values
  3256. let arrElements = [];
  3257. for (let i = 0; i < KeyWords[cbName].length; i++) {
  3258. let div = document.createElement('div');
  3259. let cbKeyWord = KeyWords[cbName][i];
  3260. let cb = document.createElement('input');
  3261. cb.type = 'checkbox';
  3262. cb.setAttribute('cbType', CBTYPE);
  3263. cb.name = cbName;
  3264. cb.value = i;
  3265. cb.checked = VARS.Options[cbName][i] === '1';
  3266. let label = document.createElement('label');
  3267. if (i === cbReadOnlyIdx) {
  3268. cb.checked = true;
  3269. cb.disabled = true;
  3270. label.setAttribute('disabled', 'disabled');
  3271. }
  3272. label.appendChild(cb);
  3273. label.appendChild(document.createTextNode(cbKeyWord));
  3274. div.appendChild(label);
  3275. arrElements.push(div);
  3276. }
  3277. let br = document.createElement('br');
  3278. arrElements.push(br);
  3279. return arrElements;
  3280. }
  3281.  
  3282. function createRB(rbName, rbValue, rbLabelText) {
  3283. let div = document.createElement('div');
  3284. let rb = document.createElement('input');
  3285. rb.type = 'radio';
  3286. rb.name = rbName;
  3287. rb.value = rbValue;
  3288. rb.checked = (VARS.Options[rbName] === rbValue);
  3289. let label = document.createElement('label');
  3290. label.appendChild(rb);
  3291. label.appendChild(document.createTextNode(rbLabelText));
  3292. div.appendChild(label);
  3293. return div;
  3294. }
  3295.  
  3296. function createInput(iName, iLabel) {
  3297. let div = document.createElement('div');
  3298. let input = document.createElement('input');
  3299. input.type = 'text';
  3300. input.name = iName;
  3301. input.value = VARS.Options[iName];
  3302. let label = document.createElement('label');
  3303. label.appendChild(document.createTextNode(iLabel));
  3304. label.appendChild(document.createElement('br'));
  3305. label.appendChild(input);
  3306. div.appendChild(label);
  3307. return div;
  3308. }
  3309. function createSelectLanguage() {
  3310. let div = document.createElement('div');
  3311. let select = getLanguagesComponent();
  3312. let label = document.createElement('label');
  3313. label.appendChild(document.createTextNode(`${KeyWords.CMF_DIALOG_LANGUAGE_LABEL}:`));
  3314. label.appendChild(document.createElement('br'));
  3315. label.appendChild(select);
  3316. div.appendChild(label);
  3317. return div;
  3318. }
  3319.  
  3320. function createCheckboxAndInput(cbName, iName) {
  3321. // -- checkbox with input box.
  3322. // -- no read-only attributes
  3323. // -- no multiple keyword values.
  3324.  
  3325. // -- create checkbox first.
  3326. const CBTYPE = 'T';
  3327. let cb = document.createElement('input');
  3328. cb.type = 'checkbox';
  3329. cb.setAttribute('cbType', CBTYPE);
  3330. cb.name = cbName;
  3331. cb.value = cbName;
  3332. cb.checked = VARS.Options[cbName];
  3333.  
  3334. // -- create input box
  3335. let input = document.createElement('input');
  3336. input.type = 'text';
  3337. input.name = iName;
  3338. input.value = VARS.Options[iName];
  3339. input.placeholder = '1000';
  3340. input.size = 6;
  3341. input.addEventListener('input', checkInputNumber, false);
  3342.  
  3343. // -- wrap checkbox and input inside a label
  3344. let label = document.createElement('label');
  3345. label.appendChild(cb);
  3346. label.appendChild(document.createTextNode(KeyWords[cbName] + ': '));
  3347. label.appendChild(input);
  3348.  
  3349. // -- wrap inside a div container ..
  3350. let div = document.createElement('div');
  3351. div.appendChild(label);
  3352. return div;
  3353. }
  3354.  
  3355. function checkInputNumber(event) {
  3356. // -- accept numbers/digits only.
  3357. const el = event.target;
  3358. if (el.value === '') {
  3359. return true;
  3360. }
  3361. const digitsValues = el.value.replace(/\D/g, '');
  3362. el.value = digitsValues.length > 0 ? parseInt(digitsValues) : '';
  3363. }
  3364.  
  3365. function getLanguagesComponent() {
  3366. const elSelect = document.createElement('select');
  3367. elSelect.name = 'CMF_DIALOG_LANGUAGE';
  3368. // let elOption = document.createElement('option');
  3369. // elOption.value = 'default';
  3370. // elOption.textContent = masterKeyWords.translations[VARS.language].CMF_DIALOG_LANGUAGE_DEFAULT;
  3371. // elSelect.appendChild(elOption);
  3372. Object.keys(masterKeyWords.translations).forEach(languageCode => {
  3373. const elOption = document.createElement('option');
  3374. elOption.value = languageCode;
  3375. elOption.textContent = masterKeyWords.translations[languageCode].CMF_DIALOG_LANGUAGE;
  3376. if (languageCode === VARS.language) {
  3377. elOption.setAttribute('selected', '');
  3378. }
  3379. elSelect.appendChild(elOption);
  3380. })
  3381.  
  3382. return elSelect;
  3383. }
  3384.  
  3385. function createDialog(languageChanged = false) {
  3386. let dlg, hdr, hdr1, hdr2, hdr3, htxt, stxt, btn, cnt, fs, l, s, ta, div, footer;
  3387.  
  3388. if (languageChanged) {
  3389. VARS.language = VARS.Options.CMF_DIALOG_LANGUAGE;
  3390. cloneKeywords();
  3391. }
  3392.  
  3393. if (languageChanged === false) {
  3394. // -- new dialog-box
  3395.  
  3396. // -- wrapper
  3397. dlg = document.createElement('div');
  3398. dlg.id = 'fbcmf';
  3399. dlg.className = 'fb-cmf';
  3400. // class "show" reveals the dialog.
  3401. // -- header (logo + title + close button)
  3402. hdr = document.createElement('header');
  3403. hdr1 = document.createElement('div');
  3404. hdr1.className = 'fb-cmf-icon';
  3405. hdr1.innerHTML = VARS.logoHTML;
  3406.  
  3407. hdr2 = document.createElement('div');
  3408. hdr2.className = 'fb-cmf-title';
  3409.  
  3410. hdr3 = document.createElement('div');
  3411. hdr3.className = 'fb-cmf-close';
  3412. btn = document.createElement('button');
  3413. btn.textContent = 'X';
  3414. btn.addEventListener('click', toggleDialog, false);
  3415. hdr3.appendChild(btn);
  3416.  
  3417. hdr.appendChild(hdr1);
  3418. hdr.appendChild(hdr2);
  3419. hdr.appendChild(hdr3);
  3420. dlg.appendChild(hdr);
  3421.  
  3422. // content container
  3423. cnt = document.createElement('div');
  3424. cnt.classList.add('content');
  3425.  
  3426. }
  3427. else {
  3428. // -- existing dialog-box
  3429. // -- UI's language has changed - reset some descriptive text
  3430. dlg = document.getElementById('fbcmf');
  3431. hdr = dlg.querySelector('header');
  3432. hdr2 = hdr.querySelector('.fb-cmf-title');
  3433. while (hdr2.firstChild) {
  3434. hdr2.removeChild(hdr2.firstChild);
  3435. }
  3436. hdr2.classList.remove('fb-cmf-lang-1');
  3437. hdr2.classList.remove('fb-cmf-lang-2');
  3438.  
  3439. cnt = dlg.querySelector('.content');
  3440. while (cnt.firstChild) {
  3441. cnt.removeChild(cnt.firstChild);
  3442. }
  3443. }
  3444. dlg.setAttribute('dir', masterKeyWords.translations[VARS.language].LANGUAGE_DIRECTION);
  3445.  
  3446. // -- header - title block
  3447. htxt = document.createElement('div');
  3448. htxt.textContent = masterKeyWords.translations.en.DLG_TITLE;
  3449. s = document.createElement('small');
  3450. s.className = 'script-version';
  3451. s.appendChild(document.createTextNode(` (${SCRIPT_VERSION})`));
  3452. htxt.appendChild(s);
  3453. hdr2.appendChild(htxt);
  3454. if (VARS.language !== 'en') {
  3455. stxt = document.createElement('small');
  3456. stxt.textContent = `(${KeyWords.DLG_TITLE})`;
  3457. hdr2.appendChild(stxt);
  3458. hdr2.classList.add('fb-cmf-lang-2');
  3459. }
  3460. else {
  3461. hdr2.classList.add('fb-cmf-lang-1');
  3462. }
  3463.  
  3464. // -- News Feed options
  3465. fs = document.createElement('fieldset');
  3466. l = document.createElement('legend');
  3467. l.textContent = KeyWords.DLG_NF;
  3468. fs.appendChild(l);
  3469. fs.appendChild(createSingleCB('NF_SPONSORED', false)); // -- changed to false (Dec 2023)
  3470. for (const key in KeyWords) {
  3471. if (key.slice(0, 3) === 'NF_') {
  3472. if (key.slice(0, 8) === 'NF_BLOCK') {
  3473. continue;
  3474. }
  3475. if (key.slice(0, 8) === 'NF_LIKES') {
  3476. if (key === 'NF_LIKES_MAXIMUM') {
  3477. fs.appendChild(createCheckboxAndInput(key, 'NF_LIKES_MAXIMUM_COUNT'));
  3478. }
  3479. }
  3480. else {
  3481. fs.appendChild(createSingleCB(key));
  3482. }
  3483. }
  3484. }
  3485.  
  3486. // -- Keywords to block - News Feed
  3487. fs.appendChild(document.createElement('br'));
  3488. l = document.createElement('strong');
  3489. l.textContent = KeyWords.DLG_BLOCK_TEXT_FILTER_TITLE + ":";
  3490. fs.appendChild(l);
  3491.  
  3492. createMultipeCBs('NF_BLOCKED_FEED', 0).forEach(el => {
  3493. fs.appendChild(el);
  3494. });
  3495.  
  3496. fs.appendChild(createSingleCB('NF_BLOCKED_ENABLED'));
  3497. fs.appendChild(createSingleCB('NF_BLOCKED_RE'));
  3498. s = document.createElement('small');
  3499. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE));
  3500. fs.appendChild(s);
  3501. ta = document.createElement('textarea');
  3502. ta.name = 'NF_BLOCKED_TEXT';
  3503. ta.textContent = VARS.Options.NF_BLOCKED_TEXT.split(VARS.SEP).join('\n');
  3504. fs.appendChild(ta);
  3505. cnt.appendChild(fs);
  3506.  
  3507. // -- Groups Feed options
  3508. fs = document.createElement('fieldset');
  3509. l = document.createElement('legend');
  3510. l.textContent = KeyWords.DLG_GF;
  3511. fs.appendChild(l);
  3512. fs.appendChild(createSingleCB('GF_SPONSORED', false)); // -- changed to false (Dec 2023)
  3513. for (const key in KeyWords) {
  3514. if (key.slice(0, 3) === 'GF_' && key.slice(0, 8) !== 'GF_BLOCK') {
  3515. fs.appendChild(createSingleCB(key));
  3516. }
  3517. }
  3518.  
  3519. // -- Keywords to block - Groups Feed
  3520. fs.appendChild(document.createElement('br'));
  3521. l = document.createElement('strong');
  3522. l.textContent = KeyWords.DLG_BLOCK_TEXT_FILTER_TITLE + ':';
  3523. fs.appendChild(l);
  3524.  
  3525. createMultipeCBs('GF_BLOCKED_FEED', 1).forEach(el => {
  3526. fs.appendChild(el);
  3527. });
  3528.  
  3529. fs.appendChild(createSingleCB('GF_BLOCKED_ENABLED'));
  3530. fs.appendChild(createSingleCB('GF_BLOCKED_RE'));
  3531. s = document.createElement('small');
  3532. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE));
  3533. fs.appendChild(s);
  3534. ta = document.createElement('textarea');
  3535. ta.name = 'GF_BLOCKED_TEXT';
  3536. ta.textContent = VARS.Options.GF_BLOCKED_TEXT.split(VARS.SEP).join('\n');
  3537. fs.appendChild(ta);
  3538. cnt.appendChild(fs);
  3539.  
  3540. // -- MarketPlace option(s)
  3541. fs = document.createElement('fieldset');
  3542. l = document.createElement('legend');
  3543. l.textContent = KeyWords.DLG_MP;
  3544. fs.appendChild(l);
  3545. fs.appendChild(createSingleCB('MP_SPONSORED', false)); // -- changed to false (Dec 2023)
  3546.  
  3547. // -- Keywords to block - Marketplace Feed
  3548. fs.appendChild(document.createElement('br'));
  3549. l = document.createElement('strong');
  3550. l.textContent = KeyWords.DLG_BLOCK_TEXT_FILTER_TITLE + ':';
  3551. fs.appendChild(l);
  3552.  
  3553. createMultipeCBs('MP_BLOCKED_FEED', 0).forEach(el => {
  3554. fs.appendChild(el);
  3555. });
  3556. // -- 2 x textarea boxes; one for prices and one for description
  3557. fs.appendChild(createSingleCB('MP_BLOCKED_ENABLED'));
  3558. fs.appendChild(createSingleCB('MP_BLOCKED_RE'));
  3559. l = document.createElement('strong');
  3560. l.textContent = 'Prices: ';
  3561. fs.appendChild(l);
  3562. fs.appendChild(document.createElement('br'));
  3563. s = document.createElement('small');
  3564. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE));
  3565. fs.appendChild(s);
  3566. ta = document.createElement('textarea');
  3567. ta.name = 'MP_BLOCKED_TEXT';
  3568. ta.textContent = VARS.Options.MP_BLOCKED_TEXT.split(VARS.SEP).join('\n');
  3569. fs.appendChild(ta);
  3570. fs.appendChild(document.createElement('br'));
  3571. fs.appendChild(document.createElement('br'));
  3572.  
  3573. l = document.createElement('strong');
  3574. l.textContent = 'Description: ';
  3575. fs.appendChild(l);
  3576. fs.appendChild(document.createElement('br'));
  3577. s = document.createElement('small');
  3578. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE));
  3579. fs.appendChild(s);
  3580. ta = document.createElement('textarea');
  3581. ta.name = 'MP_BLOCKED_TEXT_DESCRIPTION';
  3582. ta.textContent = VARS.Options.MP_BLOCKED_TEXT_DESCRIPTION.split(VARS.SEP).join('\n');
  3583. fs.appendChild(ta);
  3584.  
  3585. cnt.appendChild(fs);
  3586.  
  3587.  
  3588. // -- Watch Videos Feed options
  3589. fs = document.createElement('fieldset');
  3590. l = document.createElement('legend');
  3591. l.textContent = KeyWords.DLG_VF;
  3592. fs.appendChild(l);
  3593. fs.appendChild(createSingleCB('VF_SPONSORED', false)); // -- changed to false (Dec 2023)
  3594. for (const key in KeyWords) {
  3595. if (key.slice(0, 3) === 'VF_' && key.slice(0, 8) !== 'VF_BLOCK') {
  3596. fs.appendChild(createSingleCB(key));
  3597. }
  3598. }
  3599.  
  3600. // -- Keywords to block - Watch Videos Feed
  3601. fs.appendChild(document.createElement('br'));
  3602. l = document.createElement('strong');
  3603. l.textContent = KeyWords.DLG_BLOCK_TEXT_FILTER_TITLE + ':';
  3604. fs.appendChild(l);
  3605.  
  3606. createMultipeCBs('VF_BLOCKED_FEED', 2).forEach(el => {
  3607. fs.appendChild(el);
  3608. });
  3609.  
  3610. fs.appendChild(createSingleCB('VF_BLOCKED_ENABLED'));
  3611. fs.appendChild(createSingleCB('VF_BLOCKED_RE'));
  3612. s = document.createElement('small');
  3613. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE));
  3614. fs.appendChild(s);
  3615. ta = document.createElement('textarea');
  3616. ta.name = 'VF_BLOCKED_TEXT';
  3617. ta.textContent = VARS.Options.VF_BLOCKED_TEXT.split(VARS.SEP).join('\n');
  3618. fs.appendChild(ta);
  3619. cnt.appendChild(fs);
  3620.  
  3621.  
  3622. // -- Profile Page feed options
  3623. fs = document.createElement('fieldset');
  3624. l = document.createElement('legend');
  3625. l.textContent = KeyWords.DLG_PP;
  3626. fs.appendChild(l);
  3627. // fs.appendChild(createSingleCB('PP_SPONSORED', false));
  3628. for (const key in KeyWords) {
  3629. if (key.slice(0, 3) === 'PP_' && key.slice(0, 8) !== 'PP_BLOCK') {
  3630. fs.appendChild(createSingleCB(key));
  3631. }
  3632. }
  3633.  
  3634. // -- Keywords to block - Profile page
  3635. fs.appendChild(document.createElement('br'));
  3636. l = document.createElement('strong');
  3637. l.textContent = KeyWords.DLG_BLOCK_TEXT_FILTER_TITLE + ':';
  3638. fs.appendChild(l);
  3639.  
  3640. createMultipeCBs('PP_BLOCKED_FEED', 0).forEach(el => {
  3641. fs.appendChild(el);
  3642. });
  3643.  
  3644. fs.appendChild(createSingleCB('PP_BLOCKED_ENABLED'));
  3645. fs.appendChild(createSingleCB('PP_BLOCKED_RE'));
  3646. s = document.createElement('small');
  3647. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE));
  3648. fs.appendChild(s);
  3649. ta = document.createElement('textarea');
  3650. ta.name = 'PP_BLOCKED_TEXT';
  3651. ta.textContent = VARS.Options.PP_BLOCKED_TEXT.split(VARS.SEP).join('\n');
  3652. fs.appendChild(ta);
  3653. cnt.appendChild(fs);
  3654.  
  3655.  
  3656. // -- Other items options
  3657. fs = document.createElement('fieldset');
  3658. l = document.createElement('legend');
  3659. l.textContent = KeyWords.DLG_OTHER;
  3660. fs.appendChild(l);
  3661. for (const key in KeyWords) {
  3662. if (key.slice(0, 10) === 'OTHER_INFO') {
  3663. fs.appendChild(createSingleCB(key));
  3664. }
  3665. }
  3666. cnt.appendChild(fs);
  3667.  
  3668. // -- Reels
  3669. fs = document.createElement('fieldset');
  3670. l = document.createElement('legend');
  3671. l.textContent = KeyWords.REELS_TITLE;
  3672. fs.appendChild(l);
  3673. fs.appendChild(createSingleCB('REELS_CONTROLS'), false);
  3674. fs.appendChild(l);
  3675. fs.appendChild(createSingleCB('REELS_DISABLE_LOOPING'), false);
  3676. cnt.appendChild(fs);
  3677.  
  3678. // -- Verbosity
  3679. fs = document.createElement('fieldset');
  3680. l = document.createElement('legend');
  3681. l.textContent = KeyWords.DLG_VERBOSITY;
  3682. fs.appendChild(l);
  3683. s = document.createElement('span');
  3684. s.appendChild(document.createTextNode(`${KeyWords.DLG_VERBOSITY_CAPTION}:`));
  3685. fs.appendChild(s);
  3686. fs.appendChild(createRB('VERBOSITY_LEVEL', '0', `${KeyWords.VERBOSITY_MESSAGE[0]}`));
  3687. fs.appendChild(createRB('VERBOSITY_LEVEL', '1', `${KeyWords.VERBOSITY_MESSAGE[1]}______`));
  3688. fs.appendChild(createRB('VERBOSITY_LEVEL', '2', `${KeyWords.VERBOSITY_MESSAGE[3]}`));
  3689. fs.appendChild(document.createElement('br'));
  3690. fs.appendChild(createInput('VERBOSITY_MESSAGE_COLOUR', `${KeyWords.VERBOSITY_MESSAGE_COLOUR}:`));
  3691. fs.appendChild(createInput('VERBOSITY_MESSAGE_BG_COLOUR', `${KeyWords.VERBOSITY_MESSAGE_BG_COLOUR}:`));
  3692. fs.appendChild(document.createElement('br'));
  3693. fs.appendChild(createSingleCB('VERBOSITY_DEBUG'));
  3694. cnt.appendChild(fs);
  3695.  
  3696. // -- cmf customisations
  3697. fs = document.createElement('fieldset');
  3698. l = document.createElement('legend');
  3699. l.textContent = KeyWords.CMF_CUSTOMISATIONS;
  3700. fs.appendChild(l);
  3701. s = document.createElement('span');
  3702. s.appendChild(document.createTextNode(`${KeyWords.CMF_BTN_LOCATION}:`));
  3703. fs.appendChild(s);
  3704. let len = KeyWords.CMF_BTN_OPTION.length;
  3705. for (let i = 0; i < len; i++) {
  3706. fs.appendChild(createRB('CMF_BTN_OPTION', i.toString(), KeyWords.CMF_BTN_OPTION[i]));
  3707. }
  3708. fs.appendChild(document.createElement('br'));
  3709. s = document.createElement('span');
  3710. s.appendChild(document.createTextNode(`${KeyWords.CMF_DIALOG_LOCATION}:`));
  3711. fs.appendChild(s);
  3712. fs.appendChild(createRB('CMF_DIALOG_OPTION', '0', KeyWords.CMF_DIALOG_OPTION[0]));
  3713. fs.appendChild(createRB('CMF_DIALOG_OPTION', '1', KeyWords.CMF_DIALOG_OPTION[1]));
  3714. fs.appendChild(document.createElement('br'));
  3715. fs.appendChild(createInput('CMF_BORDER_COLOUR', `${KeyWords.CMF_BORDER_COLOUR}:`));
  3716.  
  3717. // - ui language
  3718. // fs.appendChild(document.createElement('br'));
  3719. // s = document.createElement('span');
  3720. // s.appendChild(document.createTextNode(`${KeyWords.CMF_DIALOG_LANGUAGE_LABEL}:`));
  3721. // fs.appendChild(s);
  3722. // fs.appendChild(document.createElement('br'));
  3723. // fs.appendChild(getLanguagesComponent());
  3724. // cnt.appendChild(fs);
  3725.  
  3726. fs.appendChild(document.createElement('br'));
  3727. fs.appendChild(createSelectLanguage());
  3728. cnt.appendChild(fs);
  3729.  
  3730.  
  3731. // -- tips
  3732. fs = document.createElement('fieldset');
  3733. l = document.createElement('legend');
  3734. l.textContent = KeyWords.DLG_TIPS;
  3735. fs.appendChild(l);
  3736. s = document.createElement('span');
  3737. s.appendChild(document.createTextNode(KeyWords.DLG_TIPS_CONTENT));
  3738. fs.appendChild(s);
  3739. cnt.appendChild(fs);
  3740.  
  3741. if (languageChanged === false) {
  3742.  
  3743. dlg.appendChild(cnt);
  3744.  
  3745. // -- Actions (buttons) + status
  3746. footer = document.createElement('footer');
  3747. // footer.classList.add('buttons');
  3748. btn = document.createElement('button');
  3749. btn.textContent = KeyWords.DLG_BUTTONS[0]; // save
  3750. btn.setAttribute('id', 'BTNSave');
  3751. btn.addEventListener('click', saveUserOptions, false);
  3752. footer.appendChild(btn);
  3753. btn = document.createElement('button');
  3754. btn.textContent = KeyWords.DLG_BUTTONS[1]; // close
  3755. btn.setAttribute('id', 'BTNClose');
  3756. btn.addEventListener('click', toggleDialog, false);
  3757. footer.appendChild(btn);
  3758. btn = document.createElement('button');
  3759. btn.textContent = KeyWords.DLG_BUTTONS[2]; // export
  3760. btn.setAttribute('id', 'BTNExport');
  3761. btn.addEventListener('click', exportUserOptions, false);
  3762. footer.appendChild(btn);
  3763. btn = document.createElement('button');
  3764. btn.textContent = KeyWords.DLG_BUTTONS[3]; // import
  3765. btn.setAttribute('id', 'BTNImport');
  3766. footer.appendChild(btn);
  3767. btn = document.createElement('button');
  3768. btn.textContent = KeyWords.DLG_BUTTONS[4]; // reset
  3769. btn.setAttribute('id', 'BTNReset');
  3770. btn.addEventListener('click', resetUserOptions, false);
  3771. footer.appendChild(btn);
  3772. // -- file input field is hidden, but triggered by the Import button.
  3773. let fileImport = document.createElement('input');
  3774. fileImport.setAttribute('type', 'file');
  3775. fileImport.setAttribute('id', `FI${postAtt}`);
  3776. fileImport.classList.add('fileInput');
  3777. footer.appendChild(fileImport);
  3778. // -- save/export/import/reset status/results
  3779. div = document.createElement('div');
  3780. div.classList.add('fileResults');
  3781. footer.appendChild(div);
  3782.  
  3783. dlg.appendChild(footer);
  3784.  
  3785. document.body.appendChild(dlg);
  3786.  
  3787. // -- add event listeners to the import button and file input field
  3788. let fileInput = document.getElementById(`FI${postAtt}`);
  3789. fileInput.addEventListener('change', importUserOptions, false);
  3790. // -- make the btn Import trigger file input ...
  3791. let btnImport = document.getElementById('BTNImport');
  3792. btnImport.addEventListener('click', function () {
  3793. fileInput.click();
  3794. }, false);
  3795. }
  3796. else {
  3797. // -- language changed
  3798. const footer = dlg.querySelector('footer');
  3799. let btn = footer.querySelector('#BTNSave');
  3800. btn.textContent = KeyWords.DLG_BUTTONS[0];
  3801. btn = footer.querySelector('#BTNClose');
  3802. btn.textContent = KeyWords.DLG_BUTTONS[1];
  3803. btn = footer.querySelector('#BTNExport');
  3804. btn.textContent = KeyWords.DLG_BUTTONS[2];
  3805. btn = footer.querySelector('#BTNImport');
  3806. btn.textContent = KeyWords.DLG_BUTTONS[3];
  3807. btn = footer.querySelector('#BTNReset');
  3808. btn.textContent = KeyWords.DLG_BUTTONS[4];
  3809. addLegendEvents();
  3810. }
  3811. }
  3812.  
  3813. function updateDialog() {
  3814. let content = document.getElementById('fbcmf').querySelector('.content');
  3815. if (content) {
  3816. // -- toggle checkboxes
  3817. let cbs = Array.from(content.querySelectorAll('input[type="checkbox"][cbtype="T"]'));
  3818. cbs.forEach(cb => {
  3819. if (VARS.Options.hasOwnProperty(cb.name)) {
  3820. cb.checked = VARS.Options[cb.name];
  3821. }
  3822. });
  3823. // -- multiple values checkboxes
  3824. cbs = Array.from(content.querySelectorAll('input[type="checkbox"][cbtype="M"]'));
  3825. cbs.forEach(cb => {
  3826. if (VARS.Options.hasOwnProperty(cb.name)) {
  3827. cb.checked = VARS.Options[cb.name][parseInt(cb.value)] === '1';
  3828. }
  3829. });
  3830. // -- radios
  3831. let rbs = content.querySelectorAll('input[type="radio"]');
  3832. rbs.forEach(rb => {
  3833. if (VARS.Options.hasOwnProperty(rb.name) && (rb.value === VARS.Options[rb.name])) {
  3834. rb.checked = VARS.Options[rb.name];
  3835. }
  3836. });
  3837. // -- textareas
  3838. let tas = Array.from(content.querySelectorAll('textarea'));
  3839. tas.forEach(ta => {
  3840. if (VARS.Options.hasOwnProperty(ta.name)) {
  3841. ta.value = VARS.Options[ta.name].replaceAll(VARS.SEP, '\n');
  3842. }
  3843. });
  3844. // -- plain inputs
  3845. let inputs = Array.from(content.querySelectorAll('input[type="text"]'));
  3846. inputs.forEach(inp => {
  3847. if (VARS.Options.hasOwnProperty(inp.name)) {
  3848. inp.value = VARS.Options[inp.name];
  3849. }
  3850. });
  3851. // -- selects
  3852. let selects = Array.from(content.querySelectorAll('select'));
  3853. selects.forEach(select => {
  3854. if (VARS.Options.hasOwnProperty(select.name)) {
  3855. for (let i = 0; i < select.options.length; i++) {
  3856. const option = select.options[i];
  3857. if (option.value === VARS.Options[select.name]) {
  3858. option.selected = true;
  3859. } else {
  3860. option.selected = false;
  3861. }
  3862. }
  3863. }
  3864. });
  3865. }
  3866. }
  3867.  
  3868. async function saveUserOptions(event, source = 'dialog') {
  3869. // -- save Options in indexeddb as JSON.
  3870. let languageChanged = false;
  3871. if (source === 'dialog') {
  3872. let md, cbs, rbs, tas, inputs, selects;
  3873.  
  3874. // -- grab the dialog box and get the various options.
  3875. md = document.getElementById('fbcmf');
  3876.  
  3877. // -- input validation for NF_LIKES_MAXIMUM_COUNT
  3878. const elLikesMaximum = md.querySelector('input[name="NF_LIKES_MAXIMUM"]');
  3879. if (elLikesMaximum.checked) {
  3880. const elLikesMaximumCount = md.querySelector('input[name="NF_LIKES_MAXIMUM_COUNT"]');
  3881. if (elLikesMaximumCount.value.length === 0) {
  3882. alert(KeyWords.NF_LIKES_MAXIMUM + '?');
  3883. elLikesMaximumCount.focus();
  3884. return;
  3885. }
  3886. }
  3887.  
  3888. // -- checkboxes (toggle variations)
  3889. cbs = Array.from(md.querySelectorAll('input[type="checkbox"][cbtype="T"]'));
  3890. cbs.forEach(cb => {
  3891. VARS.Options[cb.name] = cb.checked;
  3892. });
  3893. // -- checkboxes (multipe values variations)
  3894. let cbName = 'NF_BLOCKED_FEED';
  3895. cbs = Array.from(md.querySelectorAll(`input[type="checkbox"][name="${cbName}"]`));
  3896. cbs.forEach(cb => {
  3897. VARS.Options[cbName][parseInt(cb.value)] = (cb.checked) ? '1' : '0';
  3898. });
  3899. cbName = 'GF_BLOCKED_FEED';
  3900. cbs = Array.from(md.querySelectorAll(`input[type="checkbox"][name="${cbName}"]`));
  3901. cbs.forEach(cb => {
  3902. VARS.Options[cbName][parseInt(cb.value)] = (cb.checked) ? '1' : '0';
  3903. });
  3904. cbName = 'VF_BLOCKED_FEED';
  3905. cbs = Array.from(md.querySelectorAll(`input[type="checkbox"][name="${cbName}"]`));
  3906. cbs.forEach(cb => {
  3907. VARS.Options[cbName][parseInt(cb.value)] = (cb.checked) ? '1' : '0';
  3908. });
  3909. cbName = 'MP_BLOCKED_FEED';
  3910. cbs = Array.from(md.querySelectorAll(`input[type="checkbox"][name="${cbName}"]`));
  3911. cbs.forEach(cb => {
  3912. VARS.Options[cbName][parseInt(cb.value)] = (cb.checked) ? '1' : '0';
  3913. });
  3914. cbName = 'PP_BLOCKED_FEED';
  3915. cbs = Array.from(md.querySelectorAll(`input[type="checkbox"][name="${cbName}"]`));
  3916. cbs.forEach(cb => {
  3917. VARS.Options[cbName][parseInt(cb.value)] = (cb.checked) ? '1' : '0';
  3918. });
  3919.  
  3920. // -- radios
  3921. rbs = md.querySelectorAll('input[type="radio"]:checked');
  3922. rbs.forEach(rb => {
  3923. VARS.Options[rb.name] = rb.value;
  3924. });
  3925. // -- text input
  3926. inputs = Array.from(md.querySelectorAll('input[type="text"]'));
  3927. inputs.forEach(inp => {
  3928. VARS.Options[inp.name] = inp.value;
  3929. });
  3930. // -- Blocked text (textareas)
  3931. tas = md.querySelectorAll('textarea');
  3932. tas.forEach(ta => {
  3933. let txtn = ta.value.split('\n');
  3934. let txts = [];
  3935. txtn.forEach(txt => {
  3936. if (txt.trim().length > 0) {
  3937. txts.push(txt); // -- do not trim - retain entry as is.
  3938. }
  3939. });
  3940. VARS.Options[ta.name] = txts.join(VARS.SEP);
  3941. });
  3942. // -- selects
  3943. selects = Array.from(md.querySelectorAll('select'));
  3944. selects.forEach(select => {
  3945. VARS.Options[select.name] = select.value;
  3946. });
  3947.  
  3948. // -- did the ui language change?
  3949. languageChanged = (VARS.language !== VARS.Options.CMF_DIALOG_LANGUAGE);
  3950. }
  3951. else if (source === 'reset') {
  3952. languageChanged = true;
  3953. }
  3954.  
  3955.  
  3956. // -- clear out items that are not valid.
  3957. let md = document.getElementById('fbcmf');
  3958. let inputs = Array.from(md.querySelectorAll('input:not([type="file"]), textarea, select'));
  3959. let validNames = [];
  3960. inputs.forEach(inp => {
  3961. if (!validNames.includes(inp.name)) {
  3962. validNames.push(inp.name);
  3963. }
  3964. });
  3965. for (let key in VARS.Options) {
  3966. if (!validNames.includes(key)) {
  3967. if (VARS.Options.VERBOSITY_DEBUG) {
  3968. console.info(log + 'saveUserOptions(); Deleting key:', key);
  3969. }
  3970. delete VARS.Options[key];
  3971. }
  3972. }
  3973.  
  3974. // -- save options
  3975. let result = await set(DBVARS.DBKey, JSON.stringify(VARS.Options), DBVARS.ostore).then(() => {
  3976. // -- refresh options and split blocks of texts
  3977. let result2 = getUserOptions().then(() => {
  3978. return true;
  3979. });
  3980. return result2;
  3981. }).catch((err) => {
  3982. console.info(`${log}saveUserOptions() > set() -> Error:`, err);
  3983. return false;
  3984. });
  3985. if (VARS.Options.VERBOSITY_DEBUG) {
  3986. console.info(`${log}saveUserOptions() > set() -> Saved:`, result);
  3987. }
  3988.  
  3989. // - update some variables.
  3990. if (result) {
  3991.  
  3992. if (languageChanged) {
  3993. createDialog(true);
  3994. }
  3995.  
  3996. setFeedSettings(true);
  3997. // - rebuild css - need user's preferences to take effect
  3998. addCSS();
  3999. addExtraCSS();
  4000. // -- reset the main-column watcher ...
  4001. const elements = document.querySelectorAll(`[${mainColumnAtt}]`);
  4002. for (const element of elements) {
  4003. element.removeAttribute(mainColumnAtt);
  4004. }
  4005. // - check if toggling debugging mode.
  4006. toggleHiddenElements();
  4007. }
  4008. document.querySelector('#fbcmf .fileResults').textContent = `Last Saved @ ${(new Date()).toTimeString().slice(0, 8)}`;
  4009.  
  4010. // -- reset the posts and do the cleaning/mopping up again ...
  4011. if (VARS.isAF) {
  4012. // -- "reset" scan counts
  4013. VARS.scanCountStart += 100;
  4014. VARS.scanCountMaxLoop += 100;
  4015.  
  4016. // -- purge the hidden post captions
  4017. // -- however, need to move the div out of the <detais> ...
  4018. let details = document.querySelectorAll(`details[${postAtt}]`);
  4019. for (const element of details) {
  4020. const elParent = element.parentElement;
  4021. const elContent = element.lastElementChild;
  4022. if (elContent && elContent.tagName === 'DIV') {
  4023. elParent.appendChild(elContent);
  4024. }
  4025. // -- no need to copy the classes from <details> to parent - parent already have them.
  4026. elParent.removeChild(element);
  4027. }
  4028.  
  4029. // -- purge the mini-captions
  4030. let miniCaptions = document.querySelectorAll(`h6[${postAttTab}]`);
  4031. for (const miniCaption of miniCaptions) {
  4032. const elParent = miniCaption.parentElement;
  4033. elParent.removeChild(miniCaption);
  4034. }
  4035.  
  4036. // -- remove attribute
  4037. let elements = document.querySelectorAll(`[${postAtt}]`);
  4038. for (const element of elements) {
  4039. element.removeAttribute(postAtt);
  4040. element.removeAttribute(VARS.hideAtt);
  4041. element.removeAttribute(VARS.cssHideEl);
  4042. element.removeAttribute(VARS.cssHideNumberOfShares);
  4043. element.removeAttribute(VARS.showAtt);
  4044. }
  4045. // -- remove other attributes
  4046. elements = document.querySelectorAll(`[${postAttCPID}], [${postAttChildFlag}]`);
  4047. for (const element of elements) {
  4048. if (element.hasAttribute(postAttCPID)) {
  4049. element.removeAttribute(postAttCPID);
  4050. }
  4051. if (element.hasAttribute(postAttChildFlag)) {
  4052. element.removeAttribute(postAttChildFlag);
  4053. }
  4054. }
  4055. // -- remove some more attributes
  4056. // -- (don't add cssShow to query, the button needs it ...)
  4057. elements = document.querySelectorAll(`[${VARS.hideAtt}], [${VARS.cssHideEl}], [${VARS.cssHideNumberOfShares}]`);
  4058. for (const element of elements) {
  4059. element.removeAttribute(VARS.hideAtt);
  4060. element.removeAttribute(VARS.cssHideEl);
  4061. element.removeAttribute(VARS.cssHideNumberOfShares);
  4062. element.removeAttribute(VARS.showAtt);
  4063. }
  4064.  
  4065. if (VARS.isNF) {
  4066. mopUpTheNewsFeed();
  4067. }
  4068. else if (VARS.isGF) {
  4069. mopUpTheGroupsFeed();
  4070. }
  4071. else if (VARS.isVF) {
  4072. mopUpTheWatchVideosFeed();
  4073. }
  4074. else if (VARS.isMF) {
  4075. mopUpTheMarketplaceFeed();
  4076. }
  4077. else if (VARS.isSF) {
  4078. mopUpTheSearchFeed();
  4079. }
  4080. else if (VARS.isRF) {
  4081. mopUpTheReelFeed('saveUserOptions');
  4082. }
  4083. }
  4084. // console.info(log + 'saveUserOptions(); OPTIONS:', VARS.Options);
  4085. }
  4086.  
  4087. function exportUserOptions() {
  4088. // -- export user's options into a text file.
  4089. let exportOptions = document.createElement("a");
  4090. exportOptions.href = window.URL.createObjectURL(new Blob([JSON.stringify(VARS.Options)], { type: "text/plain" }));
  4091. exportOptions.download = 'fb - clean my feeds - settings.json';
  4092. exportOptions.click();
  4093. exportOptions.remove();
  4094. document.querySelector('#fbcmf .fileResults').textContent = 'Exported: fb - clean my feeds - settings.json';
  4095. }
  4096.  
  4097. function importUserOptions(event) {
  4098. // -- import user's options from a text file.
  4099. let fileResults = document.querySelector('#fbcmf .fileResults');
  4100. let file = event.target.files[0];
  4101. let fileN = event.target.files[0].name;
  4102. // -- setup reader for reading in the file
  4103. let reader = new FileReader();
  4104. // -- what to do when reader is called.
  4105. reader.onload = (file) => {
  4106. try {
  4107. let fileContent = JSON.parse(file.target.result);
  4108. if (
  4109. fileContent.hasOwnProperty('NF_SPONSORED') &&
  4110. fileContent.hasOwnProperty('GF_SPONSORED') &&
  4111. fileContent.hasOwnProperty('VF_SPONSORED') &&
  4112. fileContent.hasOwnProperty('MP_SPONSORED')
  4113. ) {
  4114. VARS.Options = fileContent;
  4115. // console.info(log + 'importUserOptions() > reader.onload: Options:', VARS.Options);
  4116. // -- save the file to the db
  4117. // -- save will run getUserOptions();
  4118. let result = saveUserOptions(null, 'file').then(() => {
  4119. updateDialog();
  4120. fileResults.textContent = `File imported: ${fileN}`;
  4121. return true;
  4122. });
  4123. }
  4124. else {
  4125. fileResults.textContent = `File NOT imported: ${fileN}`;
  4126. }
  4127. }
  4128. catch (e) {
  4129. fileResults.textContent = `File NOT imported: ${fileN}`;
  4130. }
  4131. };
  4132. // -- call reader to read in the file ...
  4133. reader.readAsText(file);
  4134. }
  4135.  
  4136. function resetUserOptions() {
  4137. // -- reset the options to original state (before customisations)
  4138. del(DBVARS.DBKey, DBVARS.ostore)
  4139. .then(() => {
  4140. // console.info(log + 'resetUserOptions();', 'Data deleted successfully');
  4141.  
  4142. // - reset language - setLanguageAndOptions() > getUserOptions() will correct this value.
  4143. VARS.Options.CMF_DIALOG_LANGUAGE = '';
  4144. setLanguageAndOptions();
  4145. let result = saveUserOptions(null, 'reset').then(() => {
  4146. updateDialog();
  4147. return true;
  4148. });
  4149. })
  4150. .catch((error) => {
  4151. console.info(log + 'resetUserOptions(); Error - unable to delete Data.', error);
  4152. });
  4153. }
  4154.  
  4155. function createToggleButton() {
  4156. let btn = document.createElement('button');
  4157. btn.innerHTML = VARS.logoHTML;
  4158. btn.id = 'fbcmfToggle';
  4159. btn.title = KeyWords.DLG_TITLE;
  4160. btn.className = 'fb-cmf-toggle fb-cmf-icon';
  4161. document.body.appendChild(btn);
  4162. btn.addEventListener('click', toggleDialog, false);
  4163. VARS.btnToggleEl = btn;
  4164. }
  4165.  
  4166. function addLegendEvents() {
  4167. const elFBCMF = document.getElementById('fbcmf');
  4168. if (elFBCMF) {
  4169. const legends = elFBCMF.querySelectorAll('legend');
  4170. legends.forEach(legend => {
  4171. legend.parentElement.classList.add('hidden');
  4172. legend.addEventListener('click', function () {
  4173. legend.parentElement.classList.toggle('hidden');
  4174. legend.parentElement.classList.toggle('visible');
  4175. });
  4176. });
  4177. }
  4178. }
  4179.  
  4180. createToggleButton();
  4181. createDialog();
  4182. addLegendEvents();
  4183. }
  4184. // --- end of dailog code.
  4185.  
  4186. // -- toggleDialog() function placed here to allow a GM.registerMenuCommand(...) to call it.
  4187. function toggleDialog() {
  4188. const elDialog = document.getElementById('fbcmf');
  4189. if (elDialog.hasAttribute(VARS.showAtt)) {
  4190. elDialog.removeAttribute(VARS.showAtt);
  4191. }
  4192. else {
  4193. elDialog.setAttribute(VARS.showAtt, '');
  4194. }
  4195. }
  4196.  
  4197. // adjust some settings - if URL has changed.
  4198. function setFeedSettings(forceUpdate = false) {
  4199. if ((VARS.prevURL !== window.location.href) || forceUpdate) {
  4200. // - remember current page's URL
  4201. VARS.prevURL = window.location.href;
  4202. // -- pathname pattern: /marketplace...
  4203. VARS.prevPathname = window.location.pathname;
  4204. // -- search pattern: ?query= ...
  4205. VARS.prevQuery = window.location.search;
  4206. // - reset feeds flags
  4207. VARS.isNF = false;
  4208. VARS.isGF = false;
  4209. VARS.isVF = false;
  4210. VARS.isMF = false;
  4211. VARS.isSF = false;
  4212. VARS.isRF = false;
  4213. VARS.isPP = false;
  4214. if ((VARS.prevPathname === '/') || (VARS.prevPathname === '/home.php')) {
  4215. // -- news feed
  4216. // -- nb: "Feeds (most recent)" combines a few feeds into one ... apply NF rules to all, except Groups.
  4217. if (VARS.prevQuery.indexOf('?filter=groups') < 0) {
  4218. VARS.isNF = true;
  4219. }
  4220. else {
  4221. VARS.isGF = true;
  4222. VARS.gfType = 'groups-recent';
  4223. }
  4224. }
  4225. else if (VARS.prevPathname.indexOf('/groups/') >= 0) {
  4226. // -- groups feed
  4227. VARS.isGF = true;
  4228. if (VARS.prevPathname.indexOf('/groups/feed') >= 0) {
  4229. VARS.gfType = 'groups';
  4230. }
  4231. else if (VARS.prevPathname.indexOf('/groups/search') >= 0) {
  4232. VARS.gfType = 'search';
  4233. }
  4234. else if (VARS.prevPathname.indexOf('?filter=groups&sk=h_chr') >= 0) {
  4235. VARS.gfType = 'groups-recent';
  4236. }
  4237. else {
  4238. VARS.gfType = 'group';
  4239. }
  4240. }
  4241. else if (VARS.prevPathname.indexOf('/watch') >= 0) {
  4242. // -- watch videos feed
  4243. VARS.isVF = true;
  4244. if (VARS.prevPathname.indexOf('/watch/search') >= 0) {
  4245. VARS.vfType = 'search';
  4246. }
  4247. else if (VARS.prevQuery.indexOf('?ref=seach') >= 0) {
  4248. VARS.vfType = 'item';
  4249. }
  4250. else if (VARS.prevQuery.indexOf('?v=') >= 0) {
  4251. VARS.vfType = 'item';
  4252. }
  4253. else {
  4254. VARS.vfType = 'videos';
  4255. }
  4256. }
  4257. else if (VARS.prevPathname.indexOf('/marketplace') >= 0) {
  4258. // -- marketplace
  4259. VARS.isMF = true;
  4260. mp_stopTrackingDirtIntoMyHouse();
  4261. if (VARS.isMF && VARS.prevPathname.indexOf('/item/') >= 0) {
  4262. // - viewing an item
  4263. VARS.mpType = 'item';
  4264. }
  4265. else if (VARS.prevPathname.indexOf('/search') >= 0) {
  4266. // - searching within marketplace ... (has similar layout to category feed)
  4267. VARS.mpType = 'search';
  4268. }
  4269. else if (VARS.prevPathname.indexOf('/category/') >= 0) {
  4270. // - category feed
  4271. VARS.mpType = 'category';
  4272. }
  4273. else {
  4274. // -- check again for category - may have a location in the pathName ...
  4275. // -- pattern: :: https://www.facebook.com/marketplace/<location>/sports
  4276. const urlBits = VARS.prevPathname.split('/');
  4277. if (urlBits.length > 3) {
  4278. VARS.mpType = 'category';
  4279. }
  4280. else {
  4281. VARS.mpType = 'marketplace';
  4282. }
  4283. }
  4284. }
  4285. else if (VARS.prevPathname.indexOf('/commerce/listing/') >= 0) {
  4286. // - a group's for sale post - redirected to marketplace ...
  4287. // - same layout as a marketplace item.
  4288. VARS.isMF = true;
  4289. VARS.mpType = 'item';
  4290. }
  4291. else if (['/search/top/', '/search/top', '/search/posts/', '/search/posts', '/search/pages/'].indexOf(VARS.prevPathname) >= 0) {
  4292. // -- search results page : "All" and "Posts"
  4293. VARS.isSF = true;
  4294. }
  4295. else if (VARS.prevPathname.indexOf('/reel/') >= 0) {
  4296. // -- reel(s) page
  4297. // VARS.isRF = true;
  4298. VARS.isRF = (VARS.Options.REELS_CONTROLS === true) || (VARS.Options.REELS_DISABLE_LOOPING === true);
  4299. }
  4300. else if (VARS.prevPathname.indexOf('/profile.php') >= 0) {
  4301. // -- profile page (usually a public page)
  4302. VARS.isPP = true;
  4303. }
  4304. else if (VARS.prevPathname.substring(1).length > 1 && VARS.prevPathname.substring(1).indexOf('/') < 0) {
  4305. // else if (document.querySelector('a[href*="/about"] > div > span')) { <-- to slow to detect - not available onload.
  4306. // -- profile page (usually person)
  4307. VARS.isPP = true;
  4308. }
  4309.  
  4310. VARS.isAF = (VARS.isNF || VARS.isGF || VARS.isVF || VARS.isMF || VARS.isSF || VARS.isRF || VARS.isPP);
  4311.  
  4312. // when to display the cmf button
  4313. if (VARS.isAF) {
  4314. if (VARS.btnToggleEl) {
  4315. VARS.btnToggleEl.setAttribute(VARS.showAtt, '');
  4316. }
  4317. }
  4318. else {
  4319. if (VARS.btnToggleEl) {
  4320. VARS.btnToggleEl.removeAttribute(VARS.showAtt);
  4321. }
  4322. }
  4323.  
  4324. // - reset consecutive count of hidden posts
  4325. VARS.echoCount = 0;
  4326.  
  4327. // -- reset the no-change-counter
  4328. VARS.noChangeCounter = 0;
  4329.  
  4330. // 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}; isPP: ${VARS.isPP}`);
  4331.  
  4332. return true;
  4333. }
  4334. else {
  4335. return false;
  4336. }
  4337. }
  4338.  
  4339. function climbUpTheTree(element, numberOfBranches = 1) {
  4340. while (element && numberOfBranches > 0) {
  4341. element = element.parentNode;
  4342. numberOfBranches--;
  4343. }
  4344. return element || null;
  4345. }
  4346.  
  4347. function doLightDusting(post) {
  4348. // - remove 'dusty' elements that interfere with querySelectorAll, nth-of-type, :not() queries.
  4349. // -- needs to run a few times to be effective.
  4350. let scanCount = VARS.scanCountStart;
  4351. if (post[postPropDS] !== undefined) {
  4352. scanCount = parseInt(post[postPropDS]);
  4353. scanCount = (scanCount < VARS.scanCountStart) ? VARS.scanCountStart : scanCount;
  4354. }
  4355. if (scanCount < VARS.scanCountMaxLoop) {
  4356. const dustySpots = post.querySelectorAll('[data-0="0"]');
  4357. if (dustySpots) {
  4358. dustySpots.forEach((element) => {
  4359. element.remove();
  4360. });
  4361. }
  4362. scanCount++;
  4363. post[postPropDS] = scanCount;
  4364. }
  4365. }
  4366.  
  4367. function scanTreeForText(theNode) {
  4368. const arrayTextValues = [];
  4369. const elements = theNode.querySelectorAll(':scope > div, :scope > blockquote, :scope > span');
  4370.  
  4371. for (const element of elements) {
  4372. if (element.hasAttribute('aria-hidden') && element.getAttribute('aria-hidden') === "false") {
  4373. // -- skip this branch (hidden)
  4374. continue;
  4375. }
  4376.  
  4377. // -- scan this branch
  4378. const walk = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null);
  4379. let currentNode;
  4380. while ((currentNode = walk.nextNode())) {
  4381. const elParent = currentNode.parentElement;
  4382. const elParentTN = elParent.tagName.toLowerCase();
  4383. const val = cleanText(currentNode.textContent).trim();
  4384.  
  4385. // console.info(log + '---> scanTreeForText(); currentNode:', currentNode, elParent, elParentTN, val);
  4386.  
  4387. if (val === '' || val.toLowerCase() === 'facebook') {
  4388. // -- skip this node
  4389. continue;
  4390. }
  4391.  
  4392. if (elParent.hasAttribute('aria-hidden') && elParent.getAttribute('aria-hidden') === 'true') {
  4393. // -- skip this node
  4394. continue;
  4395. }
  4396.  
  4397. if (elParentTN === 'div' && elParent.hasAttribute('role') && elParent.getAttribute('role') === 'button') {
  4398. // -- February 2024 - issue with "Anonymous participant" not being detected properly
  4399. // -- when viewing a post in a group (not groups feed), "Anonymous participant" is inside a div:button wrapped inside an object.
  4400. if (elParent.parentElement && elParent.parentElement.tagName.toLowerCase() !== 'object') {
  4401. // -- skip this node
  4402. continue;
  4403. }
  4404. }
  4405.  
  4406. if (elParentTN === 'title') {
  4407. // -- skip this node
  4408. continue;
  4409. }
  4410.  
  4411. // -- February 2024 - issue with "Anonymous participant" not being detected properly
  4412. // --- usually has a div with role of button, and that div only has 1 descendant.
  4413. // --- previously, we skipped when a div has a button role.
  4414. const elGeneric = elParent.closest('div[role="button"]');
  4415. const elGenericDescendantsCount = elGeneric ? countDescendants(elGeneric) : 0;
  4416. // console.info(log + 'scanTreeForText(); final test:', elGeneric, elParent, currentNode, elGenericDescendantsCount, val);
  4417. if (elGenericDescendantsCount < 2 && val.length > 1) {
  4418. // - keep 2+ char strings.
  4419. arrayTextValues.push(...val.split('\n'));
  4420. }
  4421. else {
  4422. // -- skip this node (hidden / meta info)
  4423. }
  4424. }
  4425. }
  4426.  
  4427. // -- remove duplicates and return results.
  4428. // console.info(log + 'scanTreeForText(); returning::', theNode, arrayTextValues);
  4429. return [...new Set(arrayTextValues)];
  4430. }
  4431.  
  4432. function mp_scanTreeForText(theNode) {
  4433. const arrayTextValues = [];
  4434. let n;
  4435. const walk = document.createTreeWalker(theNode, NodeFilter.SHOW_TEXT, null);
  4436. while ((n = walk.nextNode())) {
  4437. let val = cleanText(n.textContent).trim();
  4438. if ((val !== '') && (val.length > 1) && (val.toLowerCase() !== 'facebook')) {
  4439. // - keep 2+ char strings.
  4440. // arrayTextValues.push(val);
  4441. arrayTextValues.push(val.toLowerCase());
  4442. }
  4443. }
  4444. return arrayTextValues;
  4445. }
  4446.  
  4447. function scanImagesForAltText(theNode) {
  4448. const arrayAltTextValues = [];
  4449. const images = theNode.querySelectorAll('img[alt]');
  4450. for (let i = 0; i < images.length; i++) {
  4451. const img = images[i];
  4452. if (img.alt.length > 0 && img.naturalWidth > 32) {
  4453. // -- (emojis usually have widths < 33 ... skip them)
  4454. const sAlt = cleanText(img.alt);
  4455. if (!arrayAltTextValues.includes(sAlt)) {
  4456. arrayAltTextValues.push(sAlt);
  4457. }
  4458. }
  4459. }
  4460. return arrayAltTextValues;
  4461. }
  4462.  
  4463. function countDescendants(element) {
  4464. return element.querySelectorAll('div, span').length;
  4465. }
  4466.  
  4467. function extractTextContent(post, selector, maxBlocks) {
  4468. // - get the text node values of the regular feed posts
  4469. // - get the alt-text values of any images in the feed posts
  4470. // -- scan the top portion of the posts (first maxBlocks blocks)
  4471. // -- parameters:
  4472. // post: post to scan
  4473. // selector: querySelector's query
  4474. // maxBlocks: max number of blocks to scan
  4475. const blocks = post.querySelectorAll(selector);
  4476. const arrayTextValues = [];
  4477.  
  4478. // - process first maxBlocks blocks
  4479. // - block 0 = Suggested headings, block 1 = title/heading, block 2 = content, block 3 = info box / comments, block 4 = comments
  4480. // - nb: some suggested posts only have one block ...
  4481. for (let b = 0; b < Math.min(maxBlocks, blocks.length); b++) {
  4482. const block = blocks[b];
  4483. if (countDescendants(block) > 0) {
  4484. arrayTextValues.push(...scanTreeForText(block));
  4485. arrayTextValues.push(...scanImagesForAltText(block));
  4486. }
  4487. }
  4488.  
  4489. return arrayTextValues.filter(item => item !== '');
  4490. }
  4491.  
  4492. function addCaptionForHiddenPost(post, reason, marker = '') {
  4493. // :: caption for a post/feature that has been hidden ...
  4494. // -- using <details><summary>...</summary> ~~ post ~~ </details> structure.
  4495. // :: return : nothing
  4496.  
  4497. // -- create the details/summary component
  4498. const elDetails = document.createElement('details');
  4499. const elSummary = document.createElement('summary');
  4500. const elText = document.createTextNode(KeyWords.VERBOSITY_MESSAGE[1] + reason);
  4501.  
  4502. elSummary.appendChild(elText);
  4503. elDetails.appendChild(elSummary);
  4504. // -- nb: marker is sometimes "false" when not used by caller ...
  4505. elDetails.setAttribute(postAtt, (marker === false ? '' : marker));
  4506.  
  4507. // -- duplicate the post's class(es) - prevents fb from removing the post (Feb 2024).
  4508. if (post.classList.length > 0) {
  4509. elDetails.classList.add(...post.classList);
  4510. }
  4511. // -- add the caption component above the post
  4512. post.parentNode.appendChild(elDetails);
  4513. // -- move the post inside the caption's component.
  4514. elDetails.appendChild(post);
  4515.  
  4516. // - in debugging mode?
  4517. if (VARS.Options.VERBOSITY_DEBUG) {
  4518. post.setAttribute(VARS.showAtt, '');
  4519. elDetails.setAttribute('open', '');
  4520. }
  4521. }
  4522.  
  4523. function addMiniCaption(post, reason) {
  4524. // :: add a small caption to indicate why the post is hidden/flagged
  4525. // -- in debugging mode ***
  4526. // :: return : nothing
  4527.  
  4528. post.setAttribute(VARS.hideAtt, '');
  4529.  
  4530. const elTab = document.createElement('h6');
  4531. elTab.setAttribute(postAttTab, '0');
  4532. elTab.textContent = reason;
  4533.  
  4534. post.insertBefore(elTab, post.firstElementChild);
  4535. }
  4536.  
  4537. function sanitizeReason(reason) {
  4538. // -- setting an attribute, so remove double quotes from reason's text.
  4539. return reason.replaceAll('"', '');
  4540. }
  4541.  
  4542. function hideFeature(post, reason, marker = '') {
  4543. // -- hide something (not part of a regular feed)
  4544. // -- no consecutive counts (VARS.echoCount is ignored)
  4545. // -- Verbosity_Level: 0 = hide; 1|2 treated as 1 (no consecutive mode)
  4546.  
  4547. // -- mark the post with the reason to hide the post.
  4548. post.setAttribute(postAtt, sanitizeReason(reason));
  4549.  
  4550. // -- add a caption or not?
  4551. if ((VARS.Options.VERBOSITY_LEVEL !== '0') && (reason !== '')) {
  4552. // -- insert a caption for a hidden post
  4553. addCaptionForHiddenPost(post, reason, marker);
  4554. }
  4555. else {
  4556. // -- no caption required ...
  4557. // -- use an attribute to hide the post
  4558. post.setAttribute(VARS.hideAtt, '');
  4559. if (VARS.Options.VERBOSITY_DEBUG) {
  4560. // -- insert a small label to indicate why post was flagged
  4561. addMiniCaption(post, reason);
  4562. }
  4563. }
  4564. }
  4565.  
  4566. function toggleHiddenElements() {
  4567. const containers = Array.from(document.querySelectorAll('[' + VARS.hideAtt + ']'));
  4568. const blocks = Array.from(document.querySelectorAll('[' + VARS.cssHideEl + ']'));
  4569. const shares = Array.from(document.querySelectorAll('[' + VARS.cssHideNumberOfShares + ']'));
  4570.  
  4571. const elements = [...containers, ...blocks, ...shares];
  4572.  
  4573. if (VARS.Options.VERBOSITY_DEBUG) {
  4574. for (const element of elements) {
  4575. element.setAttribute(VARS.showAtt, '');
  4576. }
  4577. }
  4578. else {
  4579. for (const element of elements) {
  4580. element.removeAttribute(VARS.showAtt);
  4581. }
  4582. }
  4583. }
  4584.  
  4585. function toggleConsecutivesElements(ev) {
  4586. ev.stopPropagation();
  4587. const elSummary = ev.target;
  4588. const elDetails = elSummary.parentElement;
  4589. const elPostContent = elDetails.querySelector('div');
  4590. const cpidValue = elPostContent.getAttribute(postAttCPID);
  4591.  
  4592. const collection = document.querySelectorAll(`div[${postAttCPID}="${cpidValue}"]`);
  4593.  
  4594. if (elDetails.hasAttribute('open')) {
  4595. // -- moving into closed state
  4596. collection.forEach(post => {
  4597. post.removeAttribute(VARS.showAtt);
  4598. });
  4599. }
  4600. else {
  4601. // -- moving into open state
  4602. collection.forEach(post => {
  4603. post.setAttribute(VARS.showAtt, '');
  4604. });
  4605. }
  4606. }
  4607.  
  4608. function gf_hidePost(post, reason, marker) {
  4609. // :: hide a group feed post ...
  4610. // -- has consecutive counts
  4611. // -- Verbosity_Level: 0 = hide; 1 = single info note; 2 = consecutive info notes
  4612. // -- return : nothing
  4613.  
  4614. // console.info(log + 'gf_hidePost(); v_L:', VARS.Options.VERBOSITY_LEVEL, VARS.echoEl, VARS.echoCount, reason, post);
  4615.  
  4616. post.setAttribute(postAtt, sanitizeReason(reason));
  4617.  
  4618. if ((VARS.Options.VERBOSITY_LEVEL !== '0') && (reason !== '')) {
  4619. // -- insert either single caption for a hidden post or accumulate a caption for 2+ consecutive hidden posts
  4620. // -- nb: calling function will either zap or increment VARS.echoCount
  4621. // -- they don't look at VARS.Options.VERBOSITY_LEVEL's value
  4622.  
  4623. // -- the group post's element is the container ...
  4624. // -- so, need to go down a level to insert the caption ...
  4625. const elPostContent = post.querySelector('div');
  4626.  
  4627. if (VARS.Options.VERBOSITY_LEVEL === '1') {
  4628. // -- single caption only mode
  4629. // -- insert a caption for a hidden post
  4630. addCaptionForHiddenPost(elPostContent, reason, marker);
  4631. }
  4632. else {
  4633. // -- consecutive caption mode (VARS.Options.VERBOSITY_LEVEL === '2')
  4634.  
  4635. if (VARS.echoCount === 1) {
  4636. // - first post in a possible consecutive collection.
  4637. // - CPID = consecutive post id
  4638. addCaptionForHiddenPost(elPostContent, reason, marker);
  4639. VARS.echoCPID = generateRandomString();
  4640. VARS.echoEl = elPostContent;
  4641. VARS.echoEl.setAttribute(postAttCPID, VARS.echoCPID);
  4642. }
  4643. else {
  4644. // - 2+ consecutive posts being hidden
  4645.  
  4646. // console.info(log + 'gf_hidePost(); echoEL:', VARS.echoEl, VARS.echoCount, reason, post);
  4647.  
  4648. // -- get the primary details element
  4649. const elDetails = VARS.echoEl.closest('details');
  4650.  
  4651. if (VARS.echoCount === 2) {
  4652. // -- second post in same consecutive group, go back to first one and amend it ...
  4653. addMiniCaption(VARS.echoEl, reason);
  4654. // -- listen to open/close event - trigger a call to open/close the consecutive posts
  4655. elDetails.addEventListener('click', toggleConsecutivesElements);
  4656. }
  4657.  
  4658. // -- update the main caption hidden post element
  4659. elDetails.querySelector('summary').lastChild.textContent = VARS.echoCount + KeyWords.VERBOSITY_MESSAGE[1];
  4660.  
  4661. // second+ posts need a mini-caption
  4662. const elMiniCaptionSpot = elPostContent;
  4663. // -- second+ post in same consecutive group
  4664. addMiniCaption(elMiniCaptionSpot, reason);
  4665.  
  4666. // - consecutive posts level - flag it as part of a consecutive group
  4667. elPostContent.setAttribute(postAttCPID, VARS.echoCPID);
  4668.  
  4669. }
  4670. }
  4671.  
  4672. //console.info(log+'gf_hidePost():', VARS.echoElFirst);
  4673. }
  4674. else {
  4675. // -- verbosity_level = 0
  4676. // -- no caption required
  4677. // -- use an attribute to hide the post
  4678. post.setAttribute(VARS.hideAtt, '');
  4679. if (VARS.Options.VERBOSITY_DEBUG) {
  4680. // -- insert a small label to indicate why post was flagged
  4681. addMiniCaption(post, reason);
  4682. post.setAttribute(VARS.showAtt, '');
  4683. }
  4684. }
  4685. }
  4686.  
  4687. function vf_hidePost(post, reason, marker = '') {
  4688. // :: hide a video post ...
  4689. // -- applies to watch videos feed
  4690. // -- parameter 'marker' - for setting details' postTab value (optional)
  4691. // -- no consecutive counts
  4692. // -- Verbosity_Level: 0 = hide; 1|2 treated as 1 (no consecutive mode)
  4693. // -- return :: nothing
  4694.  
  4695. // -- mark the post with the reason for post being hidden/flagged
  4696. post.setAttribute(postAtt, sanitizeReason(reason));
  4697.  
  4698. // -- add a caption or not?
  4699. if ((VARS.Options.VERBOSITY_LEVEL !== '0') && (reason !== '')) {
  4700. // -- insert a caption for a hidden post
  4701. addCaptionForHiddenPost(post, reason, marker);
  4702. }
  4703. else {
  4704. // -- verbosity_level = 0
  4705. // -- no caption required ...
  4706. // -- use an attribute to hide the post
  4707. post.setAttribute(VARS.hideAtt, '');
  4708. if (VARS.Options.VERBOSITY_DEBUG) {
  4709. // -- insert a small label to indicate why post was flagged
  4710. addMiniCaption(post, reason);
  4711. post.setAttribute(VARS.showAtt, '');
  4712. }
  4713. }
  4714. //console.info(log+'nf_hidePost():', VARS.echoElFirst);
  4715. }
  4716.  
  4717. function nf_hidePost(post, reason, marker = '~') {
  4718. // :: hide a post ...
  4719. // -- applies to news feed + search feed
  4720. // -- parameter 'marker' - for setting details' postTab value (optional)
  4721. // -- no consecutive counts
  4722. // -- Verbosity_Level: 0 = hide; 1|2 treated as 1 (no consecutive mode)
  4723. // -- return :: nothing
  4724.  
  4725. // -- mark the post with the reason for post being hidden/flagged
  4726. post.setAttribute(postAtt, sanitizeReason(reason));
  4727.  
  4728. // -- add a caption or not?
  4729. if ((VARS.Options.VERBOSITY_LEVEL !== '0') && (reason !== '')) {
  4730. // -- insert a caption for a hidden post
  4731. addCaptionForHiddenPost(post, reason, marker);
  4732. }
  4733. else {
  4734. // -- verbosity_level = 0
  4735. // -- no caption required ...
  4736. // -- use an attribute to hide the post
  4737. post.setAttribute(VARS.hideAtt, '');
  4738. if (VARS.Options.VERBOSITY_DEBUG) {
  4739. // -- insert a small label to indicate why post was flagged
  4740. addMiniCaption(post, reason);
  4741. post.setAttribute(VARS.showAtt, '');
  4742. }
  4743. }
  4744. //console.info(log+'nf_hidePost():', VARS.echoElFirst);
  4745. }
  4746.  
  4747. function hideBlock(block, link, reason) {
  4748. block.setAttribute(VARS.cssHideEl, '');
  4749. link.setAttribute(postAtt, sanitizeReason(reason));
  4750. // - in debugging mode?
  4751. if (VARS.Options.VERBOSITY_DEBUG) {
  4752. block.setAttribute(VARS.showAtt, '');
  4753. }
  4754. }
  4755.  
  4756. function cleanText(text) {
  4757. // - fb is using ASCII code 160 for whitespace ...
  4758. // -- also "normalise" the text (i.e. convert unicode magic to normal ascii code)
  4759. // -- (unicode magic used to bold/italic/etc characters without html/css/style)
  4760. // return text.replaceAll(String.fromCharCode(160), String.fromCharCode(32)).normalize('NFKC');
  4761. // -- normalise(NKFC) will convert 160(00A0) to 32(0020)
  4762. // -- https://www.unicode.org/charts/normalization/index.html
  4763. return text.normalize('NFKC');
  4764. }
  4765.  
  4766.  
  4767. function nf_isSponsored_ShadowRoot1(post) {
  4768. // -- works for some languages - can be quite accurate.
  4769. // -- in the early start, this function may have a late hit rate - fb sometimes tad bit slow in loading <element> holding the sponsored text.
  4770.  
  4771. // -- Find the canvas element within the specified hierarchy
  4772. let hasSponsoredText = false;
  4773. const elCanvas = post.querySelector('a > span > span[aria-labelledby] > canvas');
  4774. if (elCanvas) {
  4775. // -- Get the 'aria-labelledby' attribute of the parent element
  4776. const elementId = elCanvas.parentElement.getAttribute('aria-labelledby');
  4777. // -- Check if the ID has the peculiar pattern we expect
  4778. if (elementId && elementId.slice(0, 1) === ':') {
  4779. // -- Escape the colon character in the ID for use in querySelector
  4780. const escapedId = elementId.replace(/(:)/g, '\\$1');
  4781. // -- Find the span element using the escaped ID
  4782. const elSpan = document.querySelector(`[id="${escapedId}"]`);
  4783. if (elSpan) {
  4784. // -- Check if the span's text content is 'Sponsored' (in user's language)
  4785. let lcText = elSpan.textContent.trim().toLowerCase();
  4786. hasSponsoredText = VARS.dictionarySponsored.includes(lcText);
  4787. // console.info(log + 'nf_isSponsored_ShadowRoot1(); results : ' + hasSponsoredText + "; context: " + elSpan.textContent);
  4788. }
  4789. }
  4790. }
  4791. return hasSponsoredText;
  4792. }
  4793. function nf_isSponsored_ShadowRoot2(post) {
  4794. // -- works for some languages - can be quite accurate.
  4795. // -- in the early start, this function may have a late hit rate - fb sometimes tad bit slow in loading <element> holding the sponsored text.
  4796.  
  4797. // -- Find the use element within the specified hierarchy
  4798. let hasSponsoredText = false;
  4799. const elUse = post.querySelector('a > span > span[aria-labelledby] svg > use[*|href]');
  4800. if (elUse) {
  4801. // -- Get the "id" for find the element having the text we want to scan
  4802. const elementId = elUse.href.baseVal;
  4803. if (elementId !== "" && elementId.slice(0,1) === "#") {
  4804. // -- Find the text element having the elementId
  4805. const elText = document.querySelector(`${elementId}`);
  4806. if (elText) {
  4807. // -- Check if the text's text content is 'Sponsored' (in user's language)
  4808. let lcText = elText.textContent.trim().toLowerCase();
  4809. hasSponsoredText = VARS.dictionarySponsored.includes(lcText);
  4810. // console.info(log + 'nf_isSponsored_ShadowRoot2(); results : ' + hasSponsoredText + "; context: " + elText.textContent + "; lcText: " + lcText);
  4811. // console.info(log + 'nf_isSponsored_ShadowRoot2(); dictionary : ' +VARS.dictionarySponsored );
  4812. }
  4813. }
  4814. }
  4815. return hasSponsoredText;
  4816. }
  4817.  
  4818. function nf_isSponsored_Plain(post) {
  4819. // -- works for some languages - more accurate than the other methods.
  4820. // -- simple structure.
  4821. let hasSponsoredText = false;
  4822. const queryElement = 'div[id] > span > a[role="link"] > span';
  4823. const elSpans = post.querySelectorAll(queryElement);
  4824. elSpans.forEach(elSpan => {
  4825. if (!elSpan.querySelector('svg')) {
  4826. const lcText = elSpan.textContent.trim().toLowerCase();
  4827. hasSponsoredText = VARS.dictionarySponsored.includes(lcText);
  4828. //console.info(log + 'nf_isSponsored_Plain(); results: ' + hasSponsoredText + "; context: " + elSpan.textContent);
  4829. }
  4830. });
  4831. return hasSponsoredText;
  4832. }
  4833.  
  4834. function isSponsored(post) {
  4835. // Is it a sponsored post?
  4836. // -- applies to News feed, Groups feed, Videos Feed
  4837. // -- mopUpTheMarketplaceFeed() looks after marketplace feed sponsored posts/items.
  4838.  
  4839. let isSponsoredPost = false;
  4840.  
  4841. if (VARS.isNF) {
  4842. // - try method #1 - content
  4843. isSponsoredPost = nf_isSponsored_Plain(post);
  4844. if (isSponsoredPost == false) {
  4845. // - try method #2 - shadow root
  4846. isSponsoredPost = nf_isSponsored_ShadowRoot1(post);
  4847. if (isSponsoredPost == false) {
  4848. // - try method #3 - shadow root
  4849. isSponsoredPost = nf_isSponsored_ShadowRoot2(post);
  4850. }
  4851. }
  4852. // console.info(log + 'isSponsoredNF(); isSponsoredPost: ', isSponsoredPost, post);
  4853. }
  4854. if (isSponsoredPost === false) {
  4855. // - try method #4 - structure ... tricky to get the size right .. prone to some false-hits.
  4856. const PARAM_FIND = '__cft__[0]=';
  4857. // was 308 ... trying 309
  4858. // const PARAM_MIN_SIZE = VARS.isSF ? 250 : VARS.isVF ? 299 : 309 ;
  4859. const PARAM_MIN_SIZE = VARS.isSF ? 250 : VARS.isVF ? 299 : 311 ; // 309? // 331?
  4860.  
  4861. let elLinks = [];
  4862. if (VARS.isNF || VARS.isGF) {
  4863. // -- news feed
  4864. // -- and possibly groups feed (haven't seen a sponsored post in groups for ages!)
  4865. elLinks = Array.from(post.querySelectorAll(`div[aria-posinset] span > a[href*="${PARAM_FIND}"]:not([href^="/groups/"]):not([href*="section_header_type"])`));
  4866. if (elLinks.length === 0) {
  4867. // -- try again, some users don't have the aria-posinet attribute.
  4868. elLinks = Array.from(post.querySelectorAll(`div[aria-describedby] span > a[href*="${PARAM_FIND}"]:not([href^="/groups/"]):not([href*="section_header_type"])`));
  4869. }
  4870. }
  4871. else if (VARS.isVF) {
  4872. // -- watch videos feed has a slightly different html structure for sponsored posts.
  4873. // console.info(log + "isSponsored(); video post:", post);
  4874. elLinks = Array.from(post.querySelectorAll(`div > div > div > div > span > span > div > a[href*="${PARAM_FIND}"]`));
  4875. }
  4876. else if (VARS.isSF) {
  4877. // -- search feed - has a slightly different html structure for sponsored posts.
  4878. elLinks = Array.from(post.querySelectorAll(`div[role="article"] span > a[href*="${PARAM_FIND}"]`));
  4879. }
  4880. if ((elLinks.length > 0) && (elLinks.length < 10)) {
  4881. // > 0 : found a built post
  4882. // < 10 : found a built post, but does not have an embedded post
  4883. // > 9 : embedded post - unlikely to be sponsored
  4884.  
  4885. // test the first 3 links.
  4886. // -- fb sometimes changes the 4th & 5th links after playing video, which can trigger a false isSponsoredPost flag.
  4887. const elMax = Math.min(2, elLinks.length);
  4888.  
  4889. for (let i = 0; i < elMax; i++) {
  4890. let el = elLinks[i];
  4891. let pos = el.href.indexOf(PARAM_FIND);
  4892. //if (VARS.isNF) console.info(log + "isSponsored(); isNF():: " + i + "; pos >= 0 : " + (pos >= 0) + "; " + el.href.slice(pos).length, (el.href.slice(pos).length >= PARAM_MIN_SIZE), el);
  4893. //if (VARS.isVF) console.info(log + "isSponsored(); isVF():: " + i + "; pos >= 0 : " + (pos >= 0) + "; " + el.href.slice(pos).length, (el.href.slice(pos).length >= PARAM_MIN_SIZE), el);
  4894. if (pos >= 0) {
  4895. // console.info(log + "isSponsored(); testing: " + el.href.slice(pos).length, (el.href.slice(pos).length > PARAM_MIN_SIZE), post);
  4896. if (el.href.slice(pos).length >= PARAM_MIN_SIZE) {
  4897. // console.info(log + "isSponsored(); sliced: " + i + "; " + el.href.slice(pos).length, el.href, post);
  4898. // console.info(log + "isSponsored(); # links: " + elLinks.length, post);
  4899. isSponsoredPost = true;
  4900. break;
  4901. }
  4902. }
  4903. }
  4904. }
  4905. }
  4906.  
  4907. return isSponsoredPost;
  4908. }
  4909.  
  4910. function querySelectorAllNoChildren(container = document, queries = [], minText = 0, executeAllQueries = false) {
  4911. // -- nb: .querySelectorAll(..) can have multiple queries and will execute them all (regardless of results)
  4912. if (!Array.isArray(queries)) {
  4913. queries = [queries];
  4914. }
  4915.  
  4916. if (queries.length === 0) {
  4917. return [];
  4918. }
  4919.  
  4920. if (executeAllQueries) {
  4921. return Array.from(container.querySelectorAll(queries)).filter((el) => {
  4922. return el.children.length === 0 && el.textContent.length >= minText;
  4923. });
  4924. }
  4925.  
  4926. for (const query of queries) {
  4927. const elements = container.querySelectorAll(query);
  4928. for (const element of elements) {
  4929. if (element.children.length === 0 && element.textContent.length >= minText) {
  4930. return [element];
  4931. }
  4932. }
  4933. }
  4934.  
  4935. return [];
  4936. }
  4937.  
  4938. function nf_isSuggested(post) {
  4939. // - check if any of the suggestions / recommendations type post
  4940. // -- nb: "<name> commented / <name> replied to a commment" posts have similar structure - but have extra elements ...
  4941. // -- nb: "x people recently commented" posts have similar structure - suggested/recommended posts don't start with a number ...
  4942.  
  4943. const queries = [
  4944. // // -- June 2024
  4945. // 'div[aria-posinset] span> div[id] > span:nth-of-type(1):not([style]):not([class])',
  4946. // 'div[aria-describedby] span> div[id] > span:nth-of-type(1):not([style]):not([class])',
  4947.  
  4948. // -- github 30/06/2024 mr-pokemon
  4949. '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)',
  4950. '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)',
  4951. ];
  4952.  
  4953. const elSuggestion = querySelectorAllNoChildren(post, queries, 1);
  4954. if (elSuggestion.length > 0) {
  4955. if (nf_isReelsAndShortVideos(post).length > 0) {
  4956. // -- false-positive hit.
  4957. return '';
  4958. }
  4959. // -- pattern: a digit from 0 to 9 or any number in the unicode space
  4960. // --- Basic Latin: \u0030-\u0039 (Range: 0-9)
  4961. // --- Arabic-Indic Digits: \u0660-\u0669 (Range: ٠-٩)
  4962. // --- Eastern Arabic-Indic Digits: \u06F0-\u06F9 (Range: ۰-۹)
  4963. // --- Devanagari Digits: \u0966-\u096F (Range: ०-९)
  4964. // --- Bengali Digits: \u09E6-\u09EF (Range: ০-৯)
  4965. // --- Myanmar Digits: \u1040-\u1049 (Range: ၀-၉)
  4966. // --- Thai Digits: \u0E50-\u0E59 (Range: ๐-๙)
  4967. // --- Tibetan Digits: \u0F20-\u0F29 (Range: ༠-༩)
  4968. // const pattern = /([0-9]|[\u0660-\u0669])/;
  4969. const pattern = /([0-9]|[\u0660-\u0669]|[\u06F0-\u06F9]|[\u0966-\u096F]|[\u09E6-\u09EF]|[\u1040-\u1049]|[\u0E50-\u0E59]|[\u0F20-\u0F29])/;
  4970. // -- if text starts with a number, return nothing, else the trigger word.
  4971. const firstCharacter = cleanText(elSuggestion[0].textContent).trim().slice(0, 1);
  4972. // console.info(log+'isSuggested - match test:', firstCharacter, pattern.test(firstCharacter), pattern.test(firstCharacter) ? 'No': 'Yes' );
  4973. return (pattern.test(firstCharacter)) ? '' : KeyWords.NF_SUGGESTIONS;
  4974. }
  4975. else if (nf_isGroupsYouMightLike(post)) {
  4976. return KeyWords.NF_SUGGESTIONS ;
  4977. }
  4978. return '';
  4979. }
  4980.  
  4981. function nf_isGroupsYouMightLike(post) {
  4982. // -- 21/09/2024 - <name>, here are groups you might like
  4983. const query = 'a[href*="/groups/discover"]' ;
  4984. const results = post.querySelectorAll(query);
  4985. return (results.length > 0);
  4986. }
  4987.  
  4988. function gf_isSuggested(post) {
  4989. // - check if any of the suggestions / recommendations type post
  4990. // -- get the blocks/sections, then have a look for <i> in 1st block (providing there's more than 1 block)
  4991. // -- (query bypasses the dusty elements)
  4992. // -- some users don't have [aria-posinset], hence [aria-describedby]
  4993. let results = '';
  4994. let blocksQuery = 'div[aria-posinset] > div > div > div > div > div > div > div > div, div[aria-describedby] > div > div > div > div > div > div > div > div';
  4995. let blocks = post.querySelectorAll(blocksQuery);
  4996. if (blocks.length <= 1) {
  4997. // try again .. (December 2022 change)
  4998. 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';
  4999. blocks = post.querySelectorAll(blocksQuery);
  5000. }
  5001. if (blocks.length > 1) {
  5002. let suggIcon = blocks[0].querySelector('i[data-visualcompletion="css-img"][style]');
  5003. if (suggIcon) {
  5004. results = KeyWords.GF_SUGGESTIONS;
  5005. }
  5006. else {
  5007. // -- a sneaky group post without the standard suggestion/recommendation header
  5008. let query = 'h3 > div > span ~ span > span > div > div';
  5009. let sneakyGroupPost = blocks[1].querySelector(query);
  5010. if (sneakyGroupPost) {
  5011. results = KeyWords.GF_SUGGESTIONS;
  5012. }
  5013. }
  5014. }
  5015. return results;
  5016. }
  5017.  
  5018. function nf_isPeopleYouMayKnow(post) {
  5019. //const queryPYMK = 'a[href*="/friends/suggestions/"][role="link"]';
  5020. // -- Sept 2024
  5021. const queryPYMK = 'a[href*="/friends/"][role="link"]';
  5022. const linksPYMK = post.querySelectorAll(queryPYMK);
  5023. return (linksPYMK.length === 0) ? '' : KeyWords.NF_PEOPLE_YOU_MAY_KNOW;
  5024. }
  5025.  
  5026. function nf_isPaidPartnership(post) {
  5027. const queryPP = 'span[dir] > span[id] a[href^="/business/help/"]';
  5028. const elPaidPartnership = post.querySelector(queryPP);
  5029. return (elPaidPartnership === null) ? '' : KeyWords.NF_PAID_PARTNERSHIP;
  5030. }
  5031.  
  5032. function nf_isSponsoredPaidBy(post) {
  5033. const querySPB = 'div:nth-child(2) > div > div:nth-child(2) > span[class] > span[id] > div:nth-child(2)';
  5034. const sponsoredPaidBy = querySelectorAllNoChildren(post, querySPB, 1);
  5035. return (sponsoredPaidBy.length === 0) ? '' : KeyWords.NF_SPONSORED_PAID;
  5036. }
  5037.  
  5038. function nf_isReelsAndShortVideos(post) {
  5039. // -- reels and short videos (multiple)
  5040. const queryReelsAndShortVideos = 'a[href="/reel/?s=ifu_see_more"]';
  5041. const elReelsAndShortVideos = post.querySelector(queryReelsAndShortVideos);
  5042.  
  5043. // -- method one
  5044. if (elReelsAndShortVideos !== null) {
  5045. return KeyWords.NF_REELS_SHORT_VIDEOS;
  5046. }
  5047. // -- method two
  5048. const queryManyReels = 'a[href*="/reel/"]';
  5049. const manyReels = post.querySelectorAll(queryManyReels);
  5050. if (manyReels.length > 4) {
  5051. return KeyWords.NF_REELS_SHORT_VIDEOS;
  5052. }
  5053.  
  5054. // -- method three
  5055. // -- (dictionary based rule)
  5056. const buttonDiv = post.querySelector('div[role="button"] > i ~ div');
  5057. if (buttonDiv && buttonDiv.textContent) {
  5058. const buttonText = buttonDiv.textContent.trim().toLowerCase();
  5059. // console.info(log + "nf_isReelsAndShortVideos(); buttonText: ", buttonText);
  5060. // console.info(log + "nf_isReelsAndShortVideos(); dictionary: ", VARS.dictionaryReelsAndShortVideos)
  5061. if (VARS.dictionaryReelsAndShortVideos.find(item => item === buttonText)) {
  5062. return KeyWords.NF_REELS_SHORT_VIDEOS;
  5063. }
  5064. }
  5065.  
  5066. return '';
  5067. }
  5068.  
  5069. function nf_isShortReelVideo(post) {
  5070. // -- reel/short video post (single)
  5071. // -- post must have only one reel link
  5072. const querySRV = 'a[href*="/reel/"]';
  5073. const elementsSRV = Array.from(post.querySelectorAll(querySRV));
  5074. return (elementsSRV.length !== 1) ? '' : KeyWords.NF_SHORT_REEL_VIDEO;
  5075. }
  5076.  
  5077. function gf_isShortReelVideo(post) {
  5078. // -- reel/short video post (single)
  5079. // -- post must have only one reel link
  5080. const querySRV = 'a[href*="/reel/"]';
  5081. const elementsSRV = Array.from(post.querySelectorAll(querySRV));
  5082. return (elementsSRV.length !== 1) ? '' : KeyWords.GF_SHORT_REEL_VIDEO;
  5083. }
  5084.  
  5085. function nf_isEventsYouMayLike(post) {
  5086. // -- events you may like posts
  5087. const query = (':scope div > div:nth-of-type(2) > div > div > h3 > span');
  5088. const events = querySelectorAllNoChildren(post, query, 0);
  5089. return (events.length === 0) ? '' : KeyWords.NF_EVENTS_YOU_MAY_LIKE;
  5090. }
  5091.  
  5092. function nf_isFollow(post) {
  5093. // -- follow someone/something post
  5094. // - Pre 09/2024
  5095. // const queryFollow = ':scope h4[id] > span > div > span';
  5096. // - 09/2024 - added the extra query
  5097. //const queryFollow = ':scope h4[id] > span > div > span, :scope h4[id] > span > span > div > span, :scope h4[id] > div > span > span[class] > div[class] > span[class]';
  5098. const queryFollow = [':scope h4[id] > span > div > span', ':scope h4[id] > span > span > div > span', ':scope h4[id] > div > span > span[class] > div[class] > span[class]'];
  5099. const elementsFollow = querySelectorAllNoChildren(post, queryFollow, 0, false);
  5100. // if (elementsFollow.length > 0) console.info(log + "nf_isFollow(post); elementsFollow:", elementsFollow, post);
  5101. return (elementsFollow.length !== 1) ? '' : KeyWords.NF_FOLLOW;
  5102. }
  5103.  
  5104. function nf_isParticipate(post) {
  5105. // -- participate in a post ...
  5106. const queryParticipate = ':scope h4[id] > div[class] > span[dir] > span[class] > div[class] > span[class]';
  5107. const elementsParticipate = querySelectorAllNoChildren(post, queryParticipate, 0);
  5108. return (elementsParticipate.length !== 1) ? '' : KeyWords.NF_PARTICIPATE;
  5109. }
  5110.  
  5111. function findFirstMatch(postFullText, textValuesToFind) {
  5112. const foundText = textValuesToFind.find(text => postFullText.includes(text));
  5113. return foundText !== undefined ? foundText : '';
  5114. }
  5115.  
  5116. function findFirstMatchRegExp(postFullText, regexpTextValuesToFind) {
  5117. // -- using Regular Expressions
  5118. // -- user supplied the RE patterns
  5119. for (const pattern of regexpTextValuesToFind) {
  5120. // -- do not use 'g' - want to reset lastindex to 0 for each test.
  5121. // --'i' flag for case-insensitive matching;
  5122. const regex = new RegExp(pattern, 'i');
  5123. if (regex.test(postFullText)) {
  5124. return pattern;
  5125. }
  5126. }
  5127. return '';
  5128. }
  5129.  
  5130. function nf_isBlockedText(post) {
  5131. // - check for blocked text - partial text match
  5132. // -- news feed post's blocks (have 1-4 blocks)
  5133. // -- scan 1st & 3rd blocks
  5134. // -- used by the fn extractTextContent() and fn doMoppingInfoBox()
  5135. const postTexts = (extractTextContent(post, nf_getBlocksQuery(post), 3)).join(' ').toLowerCase();
  5136. if (VARS.Options.NF_BLOCKED_RE) {
  5137. const blockedText = findFirstMatchRegExp(postTexts, VARS.Filters.NF_BLOCKED_TEXT_LC);
  5138. return blockedText;
  5139. }
  5140. else {
  5141. const blockedText = findFirstMatch(postTexts, VARS.Filters.NF_BLOCKED_TEXT_LC);
  5142. return blockedText;
  5143. }
  5144. }
  5145.  
  5146. function gf_isBlockedText(post) {
  5147. // - check for blocked text - partial text match
  5148. // -- groups feed post's blocks (have 1-4 blocks)
  5149. // -- scan first 3 blocks
  5150. // -- some users don't have [aria-posinset], hence [aria-describedby]
  5151.  
  5152. const postTexts = (extractTextContent(post, gf_getBlocksQuery(post), 3)).join(' ').toLowerCase();
  5153. if (VARS.Options.GF_BLOCKED_RE) {
  5154. const blockedText = findFirstMatchRegExp(postTexts, VARS.Filters.GF_BLOCKED_TEXT_LC);
  5155. return blockedText;
  5156. }
  5157. else {
  5158. const blockedText = findFirstMatch(postTexts, VARS.Filters.GF_BLOCKED_TEXT_LC);
  5159. return blockedText;
  5160. }
  5161.  
  5162. }
  5163.  
  5164. function vf_isBlockedText(post, queryBlocks) {
  5165. // - check for blocked text - partial text match
  5166. // -- regular videos feed post's blocks (have 1-3 blocks)
  5167. // -- scan 1st block only
  5168. const postTexts = (extractTextContent(post, queryBlocks, 1)).join(' ').toLowerCase();
  5169. if (VARS.Options.vF_BLOCKED_RE) {
  5170. const blockedText = findFirstMatchRegExp(postTexts, VARS.Filters.VF_BLOCKED_TEXT_LC);
  5171. return blockedText;
  5172. }
  5173. else {
  5174. const blockedText = findFirstMatch(postTexts, VARS.Filters.VF_BLOCKED_TEXT_LC);
  5175. return blockedText;
  5176. }
  5177. }
  5178.  
  5179. function pp_isBlockedText(post) {
  5180. // - check for blocked text - partial text match
  5181. // -- news feed post's blocks (have 1-4 blocks)
  5182. // -- scan 1st & 3rd blocks
  5183. // -- used by the fn extractTextContent() and fn doMoppingInfoBox()
  5184. const postTexts = (extractTextContent(post, nf_getBlocksQuery(post), 3)).join(' ').toLowerCase();
  5185. if (VARS.Options.PP_BLOCKED_RE) {
  5186. const blockedText = findFirstMatchRegExp(postTexts, VARS.Filters.PP_BLOCKED_TEXT_LC);
  5187. return blockedText;
  5188. }
  5189. else {
  5190. const blockedText = findFirstMatch(postTexts, VARS.Filters.PP_BLOCKED_TEXT_LC);
  5191. return blockedText;
  5192. }
  5193. }
  5194.  
  5195. function vf_isVideoLive(post) {
  5196. // - check for "LIVE" indicator on videos
  5197. const liveRule = 'div[role="presentation"] ~ div > div:nth-of-type(1) > span';
  5198. const elLive = post.querySelectorAll(liveRule);
  5199. return (elLive.length > 0) ? KeyWords.VF_LIVE : '';
  5200. }
  5201.  
  5202. function vf_isInstagram(post) {
  5203. // - check for Instagram video posts
  5204. // -- usually marked with authors having href="#"
  5205. // -- nb: some sponsored posts may have a similar structure - ignore for now.
  5206. const instagramRule = 'div > div > div > div > div > a[href="#"] > div > svg';
  5207. const elInstagram = post.querySelectorAll(instagramRule);
  5208. return (elInstagram.length > 0) ? KeyWords.VF_INSTAGRAM : '';
  5209. }
  5210.  
  5211. function findDuplicateVideos(urlQuery, postQuery, patternUsed) {
  5212. // - scan the document for all videos having the same video id.
  5213. const watchVideos = document.querySelectorAll(urlQuery);
  5214. // console.info(log + 'findDuplicateVideos(); video: ', urlQuery, ' count:', watchVideos.length);
  5215. if (watchVideos.length < 2) {
  5216. return;
  5217. }
  5218. // - flag those duplicates ...
  5219. // for (let i = watchVideos.length - 1; i >= 1; i--) {
  5220. // const videoPost = watchVideos[i].closest(postQuery);
  5221. // // console.info(log + 'findDuplicateVideos(); found duplicate?', videoPost);
  5222. // if (videoPost) {
  5223. // console.info(log + 'findDuplicateVideos(); duplicate: ', urlQuery, postQuery, patternUsed, videoPost);
  5224. // vf_hidePost(videoPost, KeyWords.VF_DUPLICATE_VIDEOS, '');
  5225. // }
  5226. // }
  5227. for (let i = 1; i < watchVideos.length; i++) {
  5228. const videoPost = watchVideos[i].closest(postQuery);
  5229. // console.info(log + 'findDuplicateVideos(); found duplicate?', videoPost);
  5230. if (videoPost) {
  5231. console.info(log + 'findDuplicateVideos(); duplicate: ', urlQuery, postQuery, patternUsed, videoPost);
  5232. vf_hidePost(videoPost, KeyWords.VF_DUPLICATE_VIDEOS, '');
  5233. }
  5234. }
  5235. }
  5236.  
  5237. function vf_hideDuplicateVideos(post, postQuery) {
  5238. // -- check for duplicate video posts
  5239. // -- hides duplicate videos
  5240. // :: return : <nothing>
  5241.  
  5242. // -- nb: there's two video url patterns to look for ...
  5243. // -- nb: each video post has two links to the video.
  5244.  
  5245. // -- try pattern #1: "/watch/?v=....&..."
  5246. const elWatchVideo = post.querySelector('div > span > a[href*="/watch/?v="]');
  5247. if (elWatchVideo) {
  5248. // - extract the video-id
  5249. const watchVideoVID = new URL(elWatchVideo.href).searchParams.get('v');
  5250. if (watchVideoVID) {
  5251. findDuplicateVideos(`div > span > a[href*="/watch/?v=${watchVideoVID}&"]`, postQuery, '1');
  5252. }
  5253. }
  5254. else {
  5255. // -- try pattern #2: "/<profile-name>/videos/.../?"
  5256. const elUserVideo = post.querySelector('div > span > a[href*="/videos/"]');
  5257. if (elUserVideo) {
  5258. // -- extract the video-id
  5259. const watchVideoVID = elUserVideo.href.split('/videos/')[1].split('/')[0];
  5260. if (watchVideoVID) {
  5261. findDuplicateVideos(`div > span > a[href*="/videos/${watchVideoVID}/"]`, postQuery, '2');
  5262. }
  5263. }
  5264. }
  5265. }
  5266.  
  5267. function vf_hideSponsoredBlock(post, query, queryBlocks) {
  5268. let videoBlocks = post.querySelectorAll(queryBlocks);
  5269. if (videoBlocks.length < 3) {
  5270. return;
  5271. }
  5272. const thirdBlock = videoBlocks[2];
  5273. if (thirdBlock.hasAttribute('class')) {
  5274. return;
  5275. }
  5276. if (thirdBlock.hasAttribute(VARS.hideAtt)) {
  5277. return;
  5278. }
  5279. thirdBlock.setAttribute(VARS.hideAtt, 'Sponsored Content');
  5280. console.info(log + 'vf_hideSponsoredBlock(); third block hidden:', thirdBlock);
  5281. }
  5282.  
  5283. function getVideoPublisherPathFromURL(videoURL) {
  5284.  
  5285. // -- sample incoming href: "https://www.facebook.com/watch/accesshollywood/?__cft__[0]=AZXwuwSI60vEG7hi6bj6YdygG6S8_Afw8RDQ3P-WX2316ihzte0s3aHyt_d-lNbJsqsfaayjaKMYsZNnrysjlKgUioONtwOdliRijNMzf5t81m1NRoqCkhsH2AuhRcpfi3AzBMIyMtbvnSgtU5ETIXEAT7NWllDpX-MdZN1eZElI9_yA8ebTVTyB6Ly58z5bv_E&__tn__=%3C"
  5286. // -- sample return href: "https://www.facebook.com/accesshollywood/"
  5287.  
  5288. const beginURL = videoURL.split('?')[0];
  5289. if (!beginURL) {
  5290. return '';
  5291. }
  5292. if (beginURL.indexOf('/watch/') >= 0) {
  5293. return beginURL.replace('/watch/', '/');
  5294. }
  5295. else {
  5296. return '';
  5297. }
  5298. }
  5299.  
  5300.  
  5301. function vf_setPostLinkToOpenInNewTab(post) {
  5302. // -- from the watch videos feed, open a video post in a new window
  5303. // -- add an icon next to the other icons at the top of the video post.
  5304. // -- (there's no equivalent function for news feed posts - no quick way of getting the post's URL)
  5305. // :: return <nothing>
  5306.  
  5307. try {
  5308. if (post.querySelector(`.${VARS.iconNewWindowClass}`)) {
  5309. // -- already has the open in new window component
  5310. return;
  5311. }
  5312.  
  5313. // - parts of the post's link can be found in the first link
  5314. const postLinks = post.querySelectorAll('div > span > a[href*="/watch/?v="][role="link"]');
  5315.  
  5316. if (postLinks.length > 0) {
  5317. const postLink = postLinks[0];
  5318. // -- get the container for the lower part of the header block.
  5319. const elHeader = climbUpTheTree(postLink, 3);
  5320. const blockOfIcons = elHeader.querySelector(':scope > div:nth-of-type(2) > span');
  5321. let newLink = '';
  5322.  
  5323. // if (blockOfIcons && blockOfIcons.querySelector(`.${VARS.iconNewWindowClass}`) === null) {
  5324. if (blockOfIcons) {
  5325. // -- need to create the video post's link
  5326. // -- nb: last post may not be a post - it could be the "You've completely caught up. Check again later for more updates" post.
  5327. // -- it doesn't have a set of icons ...
  5328. const videoId = new URL(postLink.href).searchParams.get('v');
  5329.  
  5330. if (videoId !== null) {
  5331. // -- output structure: https://www.facebook.com/<publisher>/videos/?v=<video id>
  5332. // --- try this: https://www.facebook.com/<publisher>/videos/1106976933678203/
  5333. const publisherLink = getVideoPublisherPathFromURL(post.querySelector('a[href*="/watch/"]').href);
  5334. if (publisherLink === '') {
  5335. return;
  5336. }
  5337. newLink = publisherLink + 'videos/' + videoId + '/';
  5338. }
  5339. else {
  5340. return;
  5341. }
  5342. }
  5343. else {
  5344. // -- hmm, we don't have the info to reconstruct a video post link.
  5345. return;
  5346. }
  5347.  
  5348. // -- put in fb's spacer between icons.
  5349. const spanSpacer = document.createElement('span');
  5350. spanSpacer.innerHTML = '<span><span style="position:absolute;width:1px;height:1px;">&nbsp;</span><span aria-hidden="true"> · </span></span>';
  5351. blockOfIcons.appendChild(spanSpacer);
  5352.  
  5353. const container = document.createElement('span');
  5354. container.className = VARS.iconNewWindowClass;
  5355. const span2 = document.createElement('span');
  5356. const linkNew = document.createElement('a');
  5357. linkNew.setAttribute('href', newLink);
  5358. linkNew.innerHTML = VARS.iconNewWindow;
  5359. linkNew.setAttribute('target', '_blank');
  5360. span2.appendChild(linkNew);
  5361. container.appendChild(span2);
  5362.  
  5363. blockOfIcons.appendChild(container);
  5364. }
  5365. }
  5366. catch (error) {
  5367. console.error('vf_setPostLinkToOpenInNewTab(); Error:', post, error);
  5368. }
  5369. }
  5370.  
  5371. function mp_getBlockedPrices(elBlockOfText) {
  5372. // -- scan the first price listed in itemPrices for a match.
  5373. // -- (second price is the old one (with strike-through))
  5374. // -- needs to be an extact match.
  5375. // :: return : blocked text or ''.
  5376. if (VARS.Filters.MP_BLOCKED_TEXT.length > 0) {
  5377. const itemPrices = mp_scanTreeForText(elBlockOfText);
  5378. const blockedPrices = findFirstMatch(itemPrices, VARS.Filters.MP_BLOCKED_TEXT_LC);
  5379. return blockedPrices;
  5380. }
  5381. return '';
  5382. }
  5383.  
  5384. function mp_getBlockedTextDescription(collectionBlocksOfText, skipFirstBlock = true) {
  5385. // -- scan the various blocks of text for blocked text.
  5386. // -- partial match.
  5387. // :: return : blocked text or ''.
  5388. if (VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION.length > 0) {
  5389. const startIndex = skipFirstBlock ? 1 : 0;
  5390. for (let i = startIndex; i < collectionBlocksOfText.length; i++) {
  5391. const descriptionTextList = mp_scanTreeForText(collectionBlocksOfText[i]);
  5392. const descriptionText = descriptionTextList.join(' ').toLowerCase();
  5393. const blockedText = findFirstMatch(descriptionText, VARS.Filters.MP_BLOCKED_TEXT_DESCRIPTION_LC);
  5394. if (blockedText.length > 0) {
  5395. return blockedText;
  5396. }
  5397. }
  5398. }
  5399. return '';
  5400. }
  5401.  
  5402. function mp_doBlockingByBlockedText() {
  5403. // -- scan the marketplace for items
  5404. // -- hide an item if the price is listed in the list of blocked text
  5405. // -- hide an item if the descriptinis listed in the list of blocked description text
  5406. // :: return <nothing>
  5407.  
  5408.  
  5409. const queries = [
  5410. // -- October 2024 (fb changed code - personalised and non-personalised)
  5411. // -- landing page listing
  5412. `div[style]:not([${postAtt}]) > div > div > span > div > div > div > div > a[href*="/marketplace/item/"]`,
  5413. `div[style]:not([${postAtt}]) > div > div > span > div > div > div > div > a[href*="/marketplace/np/item/"]`,
  5414. // -- category page listing
  5415. `div[style]:not([${postAtt}]) > div > span > div > div > a[href*="/marketplace/item/"]`,
  5416. `div[style]:not([${postAtt}]) > div > span > div > div > a[href*="/marketplace/np/item/"]`,
  5417.  
  5418. // -- March 2024 (fb changed code - personalised and non-personalised)
  5419. // -- landing page listing
  5420. `div[style]:not([${postAtt}]) > div > div > span > div > div > a[href*="/marketplace/item/"]`,
  5421. `div[style]:not([${postAtt}]) > div > div > span > div > div > a[href*="/marketplace/np/item/"]`,
  5422. // -- category page listing
  5423. `div[style]:not([${postAtt}]) > div > span > div > div > a[href*="/marketplace/item/"]`,
  5424. `div[style]:not([${postAtt}]) > div > span > div > div > a[href*="/marketplace/np/item/"]`
  5425. ];
  5426. let items = [];
  5427. for (const query of queries) {
  5428. items = document.querySelectorAll(query);
  5429. if (items.length > 0) {
  5430. break;
  5431. }
  5432. }
  5433. for (const item of items) {
  5434. // - item's container
  5435. const box = item.closest('div[style]');
  5436. if (box.hasAttribute(postAttMPSkip)) {
  5437. if (box.innerHTML.length == box.getAttribute(postAttMPSkip)) {
  5438. continue;
  5439. }
  5440. }
  5441. // - blocks of text to scan
  5442. const queryTextBlock = ':scope > div > div:nth-of-type(2) > div';
  5443. const blocksOfText = item.querySelectorAll(queryTextBlock);
  5444. if (blocksOfText.length > 0) {
  5445. // -- price(s) is in first block
  5446. const blockedTextPrices = mp_getBlockedPrices(blocksOfText[0]);
  5447. // -- description is in other blocks
  5448. const blockedTextDescription = mp_getBlockedTextDescription(blocksOfText, true);
  5449.  
  5450. if (blockedTextPrices.length > 0) {
  5451. // -- hide the item
  5452. mp_hideBox(box, blockedTextPrices);
  5453. }
  5454. else if (blockedTextDescription.length > 0) {
  5455. // -- hide the item
  5456. mp_hideBox(box, blockedTextDescription);
  5457. }
  5458. else {
  5459. // -- flag the item not to be scanned again
  5460. box.setAttribute(postAtt, '');
  5461. // box.setAttribute(postAttMPSkip, box.innerHTML.length);
  5462. }
  5463. }
  5464. }
  5465. }
  5466.  
  5467. function vf_scrubSponsoredBlock(post) {
  5468. // - some videos have a sponsored block beneath the video block/section
  5469. // - includes "watch more ___ videos by ___"
  5470. // :: return <nothing>
  5471. const queryForContainer = ':scope > div > div > div > div > div > div:nth-of-type(2)';
  5472. const blocksContainer = post.querySelector(queryForContainer);
  5473. if (blocksContainer && blocksContainer.childElementCount > 0) {
  5474. const adBlock = blocksContainer.querySelector(':scope > a');
  5475. if (adBlock && !adBlock.hasAttribute(postAtt)) {
  5476. hideBlock(adBlock, adBlock, KeyWords.SPONSORED);
  5477. }
  5478. }
  5479. }
  5480.  
  5481. function swatTheMosquitos(post) {
  5482. // - scan the post for any gifs that is animating (pausing them once)
  5483. // -- does not hide the post!
  5484. // :: return <nothing>
  5485. const query = getMosquitosQuery();
  5486. const animatedGIFs = post.querySelectorAll(query);
  5487. // console.info(log + 'swatTheMosquitos(); animatedGIFs::', animatedGIFs, post);
  5488. for (const gif of animatedGIFs) {
  5489. // mimic user clicking on animating gif
  5490. // - which will trigger fb's click event.
  5491. // - grab the A tag that is displayed when paused (uses Opacity)
  5492. let parent = climbUpTheTree(gif, 2);
  5493. let sibling = parent.querySelector(':scope > a');
  5494. if (!sibling) {
  5495. // -- pre 2023 rule
  5496. parent = climbUpTheTree(gif, 3);
  5497. sibling = parent.querySelector(':scope > a');
  5498. }
  5499. // console.info(log + 'swatTheMosquitos(); gif / parent / sibling:', gif, parent, sibling);
  5500. if (sibling) {
  5501. const sibingCS = window.getComputedStyle(sibling);
  5502. if (sibingCS.opacity === '0') {
  5503. // 0 = animating; 1 = paused;
  5504. gif.parentElement.click();
  5505. // console.info(log + 'swatTheMosquitos() - paused', gif);
  5506. }
  5507. gif.parentElement.setAttribute(postAtt, '1');
  5508. }
  5509. }
  5510. }
  5511.  
  5512. function nf_getBlocksQuery(post) {
  5513. // :: return the query needed to get a group post's blocks
  5514. // - block 0 = Suggested headings, block 1 = title/heading, block 2 = content, block 3 = info box / comments, block 4 = comments
  5515. // -- some users don't have [aria-posinset], hence [aria-describedby]
  5516. let blocksQuery = 'div[aria-posinset] > div > div > div > div > div > div > div > div, div[aria-describedby] > div > div > div > div > div > div > div > div';
  5517. let blocks = post.querySelectorAll(blocksQuery);
  5518. if (blocks.length <= 1) {
  5519. // -- try again .. (December 2022 change)
  5520. // -- no need to do another querySelectorAll(..) - extractTextContent will do this.
  5521. 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';
  5522. }
  5523. return blocksQuery;
  5524. }
  5525.  
  5526. function gf_getBlocksQuery(post) {
  5527. // :: return the query needed to get a group post's blocks
  5528. // - block 0 = Suggested headings, block 1 = title/heading, block 2 = content, block 3 = info box / comments, block 4 = comments
  5529. let blocksQuery = 'div[aria-posinset] > div > div > div > div > div > div > div > div, div[aria-describedby] > div > div > div > div > div > div > div > div';
  5530. let blocks = post.querySelectorAll(blocksQuery);
  5531. if (blocks.length <= 1) {
  5532. // try again .. (Dec 2022 change)
  5533. // -- no need to do another querySelectorAll(..) - extractTextContent will do this.
  5534. 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';
  5535. }
  5536. return blocksQuery;
  5537. }
  5538.  
  5539. function getMosquitosQuery() {
  5540. const queryForMosquitos = `div[role="button"][aria-label*="GIF"]:not([${postAtt}]) > i:not([data-visualcompletion])`;
  5541. return queryForMosquitos;
  5542. }
  5543.  
  5544. function nf_hasAnimatedGifContent(post) {
  5545. // - scan the post's content for any animated gifs
  5546. // -- scan the post's content block (2nd block) only (ignore comments block)
  5547. // -- the gif is usually either MP4 or GIPHY ... with an round dashed label "GIF" overlay
  5548. // :: return <reason>
  5549.  
  5550. const postBlocks = post.querySelectorAll(nf_getBlocksQuery(post));
  5551.  
  5552. if (postBlocks.length >= 2) {
  5553. const contentBlock = postBlocks[1];
  5554. const queryForAnimatedGIF = getMosquitosQuery();
  5555. const animatedGIFs = contentBlock.querySelectorAll(queryForAnimatedGIF);
  5556. const animatedGIFsText = (animatedGIFs.length > 0) ? KeyWords.GF_ANIMATED_GIFS_POSTS : '';
  5557. // console.info(log + 'nf_hasAnimatedGifContent(); results::', contentBlock, animatedGIFs.length, animatedGIFsText, post);
  5558. return animatedGIFsText;
  5559. }
  5560. return '';
  5561. }
  5562.  
  5563. function gf_hasAnimatedGifContent(post) {
  5564. // - scan the post's content for any animated gifs
  5565. // -- scan the post's content block only (ignore comments block)
  5566. // -- the gif is usually either MP4 or GIPHY ... with an round dashed label "GIF" overlay
  5567. // :: return <reason>
  5568.  
  5569. const postBlocks = post.querySelectorAll(gf_getBlocksQuery(post));
  5570.  
  5571. if (postBlocks.length >= 2) {
  5572. const contentBlock = postBlocks[1];
  5573. const queryForAnimatedGIF = getMosquitosQuery();
  5574. const animatedGIFs = contentBlock.querySelectorAll(queryForAnimatedGIF);
  5575. const animatedGIFsText = (animatedGIFs.length > 0) ? KeyWords.GF_ANIMATED_GIFS_POSTS : '';
  5576. // console.info(log + 'gf_hasAnimatedGifContent(); results::', contentBlock, animatedGIFs.length, animatedGIFsText, post);
  5577. return animatedGIFsText;
  5578. }
  5579. return '';
  5580. }
  5581.  
  5582. function nf_scrubTheTabbies() {
  5583. // -- Tablist : stories | reels | rooms
  5584. // -- Stories
  5585. // -- both appear at top of NF
  5586. const queryTabList = 'div[role="main"] > div > div > div > div > div > div > div > div[role="tablist"]';
  5587. const elTabList = document.querySelector(queryTabList);
  5588. if (elTabList) {
  5589. if (elTabList.hasAttribute(postAttChildFlag)) {
  5590. return;
  5591. }
  5592. // -- parent is 4 levels up.
  5593. const elParent = climbUpTheTree(elTabList, 4);
  5594. if (elParent) {
  5595. hideFeature(elParent, (KeyWords.NF_TABLIST_STORIES_REELS_ROOMS[VARS.language]).replaceAll('"', ''), false);
  5596. elTabList.setAttribute(postAttChildFlag, 'tablist');
  5597. return;
  5598. }
  5599. }
  5600. else {
  5601. // - Stories only
  5602. // -- two patterns
  5603. // -- - one with listing of stories
  5604. // -- - one with no listing of stories
  5605.  
  5606. // const queryForCreateStory = 'a[href*="/stories/create"]'; - too loose, grabs the FB menu's entries as well ...
  5607. const queryForCreateStory = 'div[role="main"] > div > div > div > div > div > div > div > div a[href*="/stories/create"]';
  5608. const elCreateStory = document.querySelector(queryForCreateStory);
  5609. if (elCreateStory && !elCreateStory.hasAttribute(postAttChildFlag)) {
  5610. const elParent = getStoriesParent(elCreateStory);
  5611. if (elParent !== null) {
  5612. hideFeature(elParent, KeyWords.NF_TABLIST_STORIES_REELS_ROOMS, false);
  5613. elCreateStory.setAttribute(postAttChildFlag, '1');
  5614. }
  5615. }
  5616. }
  5617.  
  5618. }
  5619.  
  5620.  
  5621. function getStoriesParent(element) {
  5622. const elAFewBranchesUp = climbUpTheTree(element, 4);
  5623. const moreStories = elAFewBranchesUp.querySelectorAll('a[href*="/stories/"]');
  5624. let elParent = null;
  5625. if (moreStories.length > 1) {
  5626. // -- query results has create and at least 1 story link
  5627. elParent = climbUpTheTree(element.closest('div[aria-label][role="region"]'), 4);
  5628. }
  5629. else {
  5630. // -- query results has one link - create story
  5631. elParent = climbUpTheTree(element, 7);
  5632. }
  5633. return elParent;
  5634. }
  5635.  
  5636. function nf_isStoriesPost(post) {
  5637. // - Stories posts
  5638. // -- appears in News-Feed stream
  5639. const queryForStory = '[href^="/stories/"][href*="source=from_feed"]';
  5640. const elStory = post.querySelector(queryForStory);
  5641. return (elStory) ? KeyWords.NF_STORIES : '';
  5642. }
  5643.  
  5644. function nf_cleanTheConsoleTable(findItem = 'Sponsored') {
  5645. // -- mopping up the news feed aside panel. findItem values: Sponosored | Suggestions
  5646. // :: return : <nothing>
  5647.  
  5648. const query = 'div[role="complementary"] > div > div > div > div > div:not([data-visualcompletion])';
  5649. const asideBoxes = document.querySelectorAll(query);
  5650. if (asideBoxes.length === 0) {
  5651. return;
  5652. }
  5653. // -- only interested in the first container.
  5654. const asideContainer = asideBoxes[0];
  5655. if (asideContainer.childElementCount === 0) {
  5656. return;
  5657. }
  5658.  
  5659. let elItem = null;
  5660. let reason = '';
  5661. if (findItem === 'Sponsored') {
  5662. elItem = asideContainer.querySelector(`:scope > span:not([${postAtt}])`);
  5663. if (elItem && elItem.innerHTML.length > 0) {
  5664. reason = KeyWords.SPONSORED;
  5665. }
  5666. }
  5667. else if (findItem === 'Suggestions') {
  5668. elItem = asideContainer.querySelector(`:scope > div:not([${postAtt}])`);
  5669. if (elItem && elItem.innerHTML.length > 0) {
  5670. // -- check for birthdays
  5671. const birthdays = elItem.querySelectorAll('a[href="/events/birthdays/"]').length > 0;
  5672. // -- check for "your pages and profiles"
  5673. // -- suggested groups only have 1 i[..] attribute
  5674. const pagesAndProfiles = elItem.querySelectorAll('div > i[data-visualcompletion="css-img"]').length > 1;
  5675.  
  5676. if (birthdays === false && pagesAndProfiles === false) {
  5677. reason = KeyWords.NF_SUGGESTIONS;
  5678. }
  5679. }
  5680. }
  5681. if (reason.length > 0) {
  5682. nf_hidePost(elItem, reason);
  5683. }
  5684. }
  5685.  
  5686. function gf_cleanTheConsoleTable(findItem = 'Suggestions') {
  5687. // -- mopping up the groups feed aside panel - suggested
  5688. // :: return : <nothing>
  5689.  
  5690. // console.info(log + 'gf_cleanTheConsoleTable() - fix me!');
  5691.  
  5692. // return;
  5693.  
  5694.  
  5695. if (findItem !== 'Suggestions') {
  5696. return;
  5697. }
  5698.  
  5699. const query = `a[href*="/groups/discover"]:not([${postAtt}]) > span > span`;
  5700. const asideBoxes = querySelectorAllNoChildren(document, query, 1);
  5701. if (asideBoxes.length === 0) {
  5702. return;
  5703. }
  5704.  
  5705. for (const asideBox of asideBoxes) {
  5706. // parent is 21 levels up ...
  5707. const elParent = climbUpTheTree(asideBox, 21);
  5708. asideBox.closest('a').setAttribute(postAtt, KeyWords.GF_SUGGESTIONS);
  5709. hideFeature(elParent, KeyWords.GF_SUGGESTIONS, true);
  5710. }
  5711. }
  5712.  
  5713. function gf_setPostLinkToOpenInNewTab(post) {
  5714. // -- from the groups feed, open a group post in a new window
  5715. // -- add an icon next to the other icons at the top of the group post.
  5716. // -- (there's no equivalent function for news feed posts - no quick way of getting the post's URL)
  5717. // :: return <nothing>
  5718.  
  5719. // console.info(log + 'gf_setPostLinkToOpenInNewTab(); post:', post);
  5720.  
  5721. try {
  5722. if (post.hasAttribute('class') && post.classList.length > 0) {
  5723. // -- not a group post.
  5724. return;
  5725. }
  5726. if (post.querySelector(`.${VARS.iconNewWindowClass}`)) {
  5727. // -- already has the open in new window component
  5728. return;
  5729. }
  5730. // - parts of the post's link can be found in the first link
  5731. const postLinks = post.querySelectorAll('div > div > a[href*="/groups/"][role="link"]');
  5732. if (postLinks.length > 0) {
  5733. const postLink = postLinks[0];
  5734. // -- get the container for the lower part of the header block.
  5735. const elHeader = climbUpTheTree(postLink, 4);
  5736. const blockOfIcons = elHeader.querySelector(':scope > div:nth-of-type(2) > div > div:nth-of-type(2) > span > span');
  5737. let newLink = '';
  5738.  
  5739. // if (blockOfIcons && blockOfIcons.querySelector(`.${VARS.iconNewWindowClass}`) === null) {
  5740. if (blockOfIcons) {
  5741. // -- need to create the group post's link
  5742. // -- nb: last post may not be a post - it could be the "You've completely caught up. Check again later for more updates" post.
  5743. // -- it doesn't have a set of icons ...
  5744. const postId = new URLSearchParams(postLink.href).get('multi_permalinks');
  5745. if (postId !== null) {
  5746. // -- 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
  5747. // -- .. converted to: https://www.facebook.com/groups/424532172574012/posts/720886619605231/
  5748. // -- post link structure: https://www.facebook.com/groups/<group id>/posts/<post id>/
  5749. newLink = postLink.href.split('?')[0] + 'posts/' + postId + '/';
  5750. }
  5751. else {
  5752. return;
  5753. }
  5754. }
  5755. else {
  5756. // -- hmm, we don't have the info to reconstruct a group post link.
  5757. return;
  5758. }
  5759.  
  5760. // -- put in fb's spacer between icons.
  5761. const spanSpacer = document.createElement('span');
  5762. spanSpacer.innerHTML = '<span><span style="position:absolute;width:1px;height:1px;">&nbsp;</span><span aria-hidden="true"> · </span></span>';
  5763. blockOfIcons.appendChild(spanSpacer);
  5764.  
  5765. const container = document.createElement('span');
  5766. container.className = VARS.iconNewWindowClass;
  5767. const span2 = document.createElement('span');
  5768. const linkNew = document.createElement('a');
  5769. linkNew.setAttribute('href', newLink);
  5770. linkNew.innerHTML = VARS.iconNewWindow;
  5771. linkNew.setAttribute('target', '_blank');
  5772. span2.appendChild(linkNew);
  5773. container.appendChild(span2);
  5774.  
  5775. blockOfIcons.appendChild(container);
  5776. }
  5777. }
  5778. catch (error) {
  5779. console.error(log + 'gf_setPostLinkToOpenInNewTab(); Error:', post, error);
  5780. }
  5781. }
  5782.  
  5783. function scrubInfoBoxes(post) {
  5784. // -- hide the "truth" info boxes that appear in posts having a certain topic.
  5785. // :: return <nothing>
  5786. let hiding = false;
  5787.  
  5788. if (VARS.Options.OTHER_INFO_BOX_CLIMATE_SCIENCE) {
  5789. const elLink = post.querySelector(`a[href*="${masterKeyWords.pathInfo.OTHER_INFO_BOX_CLIMATE_SCIENCE.pathMatch}"]:not([${postAtt}])`);
  5790. if (elLink !== null) {
  5791. // - block @ 5 levels up.
  5792. const block = climbUpTheTree(elLink, 5);
  5793. hideBlock(block, elLink, KeyWords.OTHER_INFO_BOX_CLIMATE_SCIENCE);
  5794. hiding = true;
  5795. }
  5796. }
  5797. //console.info(log+'scrubInfoBoxes():', hiding, VARS.Options.OTHER_INFO_BOX_CORONAVIRUS, masterKeyWords.pathInfo.OTHER_INFO_BOX_CORONAVIRUS.pathMatch, post);
  5798. if (!hiding && VARS.Options.OTHER_INFO_BOX_CORONAVIRUS) {
  5799. const elLink = post.querySelector(`a[href*="${masterKeyWords.pathInfo.OTHER_INFO_BOX_CORONAVIRUS.pathMatch}"]:not([${postAtt}])`);
  5800. if (elLink !== null) {
  5801. // - block @ 5 levels up.
  5802. const block = climbUpTheTree(elLink, 5);
  5803. hideBlock(block, elLink, KeyWords.OTHER_INFO_BOX_CORONAVIRUS);
  5804. hiding = true;
  5805. }
  5806. }
  5807. if (!hiding && VARS.Options.OTHER_INFO_BOX_SUBSCRIBE) {
  5808. const elLink = post.querySelector(`a[href*="${masterKeyWords.pathInfo.OTHER_INFO_BOX_SUBSCRIBE.pathMatch}"]:not([${postAtt}])`);
  5809. if (elLink !== null) {
  5810. // - block @ 5 levels up.
  5811. const block = climbUpTheTree(elLink, 5);
  5812. hideBlock(block, elLink, KeyWords.OTHER_INFO_BOX_SUBSCRIBE);
  5813. hiding = true;
  5814. }
  5815. }
  5816. }
  5817.  
  5818. function nf_hideNumberOfShares(post) {
  5819. // -- hide the number of shares component
  5820. // :: return <nothing>
  5821. 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})`;
  5822. const shares = post.querySelectorAll(query);
  5823. for (const share of shares) {
  5824. share.setAttribute(VARS.cssHideNumberOfShares, '');
  5825. if (VARS.Options.VERBOSITY_DEBUG) {
  5826. share.setAttribute(VARS.showAtt, '');
  5827. }
  5828. share.setAttribute(postAtt, 'Shares');
  5829. }
  5830. }
  5831.  
  5832. function gf_hideNumberOfShares(post) {
  5833. // -- groups feed posts have same '# shares' html structure as news feed posts.
  5834. // -- .. hence calling nf_hideNumberOfShares(...)
  5835. // :: return <nothing>
  5836. nf_hideNumberOfShares(post);
  5837. }
  5838.  
  5839. function nf_postExceedsLikeCount(post) {
  5840. // -- hide posts having Like counts over a certain number
  5841. // 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]';
  5842. const queryLikes = 'span[role="toolbar"] ~ div div[role="button"] > span[class][aria-hidden] > span:not([class]) > span[class]';
  5843. const elLikes = post.querySelectorAll(queryLikes);
  5844. if (elLikes.length > 0) {
  5845. const maxLikes = parseInt(VARS.Options.NF_LIKES_MAXIMUM_COUNT);
  5846. const postLikesCount = getFullNumber(elLikes[0].textContent.trim());
  5847. // const postLikesCount = parseInt(elLikes[0].textContent);
  5848. const results = postLikesCount >= maxLikes ? KeyWords.NF_LIKES_MAXIMUM : '';
  5849. // console.info(log + 'nf_postExceedsLikeCount(); results:', results, maxLikes, postLikesCount, post);
  5850. return results;
  5851. }
  5852. return false;
  5853. }
  5854.  
  5855. function getFullNumber(value) {
  5856. // -- convert shortened numbers into full numbers
  5857. // -- e.g 323 to 323; 1.2K to 1200; 1.4M to 1400000;
  5858. // :: returns a whole number.
  5859. let nvalue = 0;
  5860. if (value !== '') {
  5861. value = value.toUpperCase();
  5862. if (value.endsWith('K') || value.endsWith('M')) {
  5863. let multiplier = 1;
  5864. let pow_Y = 0;
  5865. if (value.endsWith('K')) {
  5866. // -- thousands
  5867. multiplier = 1000;
  5868. pow_Y = 3;
  5869. }
  5870. else if (value.endsWith('M')) {
  5871. // -- millions
  5872. multiplier = 1000000;
  5873. pow_Y = 6;
  5874. }
  5875.  
  5876. let bits = value.replace(/[KM]/g, '').replace(',', '.').split('.');
  5877.  
  5878. nvalue = parseInt(bits[0], 10) * multiplier;
  5879.  
  5880. if (bits.length > 1) {
  5881. nvalue += (parseInt(bits[1], 10) * Math.pow(10, (pow_Y - bits[1].length)));
  5882. }
  5883. }
  5884. else {
  5885. // -- less than 1000.
  5886. nvalue = parseInt(value, 10);
  5887. }
  5888. }
  5889. // console.info('results:', value, nvalue);
  5890. return nvalue;
  5891. }
  5892.  
  5893.  
  5894. function nf_scrubTheSurvey() {
  5895. // -- fb survey
  5896. // -- appears on the home page ..
  5897. // document.querySelectorAll('a[href*="/survey/?session="] > div[role="none"]')[0].closest('[style*="border-radius"]').parentElement.parentElement.parentElement
  5898. let btnSurvey = document.querySelector(`a[href*="/survey/?session="] > div[role="none"]:not([${postAtt}])`);
  5899. if (btnSurvey) {
  5900. let elContainer = climbUpTheTree(btnSurvey.closest('[style*="border-radius"]'), 3);
  5901. if (elContainer) {
  5902. hideFeature(elContainer, 'Survey', false);
  5903. btnSurvey.setAttribute(postAttChildFlag, KeyWords.NF_SURVEY);
  5904. }
  5905. }
  5906. }
  5907.  
  5908. function nf_getCollectionOfPosts() {
  5909. // -- get a collection of posts
  5910. // -- fb serves a mixture of html structures
  5911. // -- so, we have a set of queries to try until we have found something...
  5912. // :: return : collection of posts.
  5913.  
  5914. let posts = [];
  5915. // -- various news feed queries
  5916. const queries = [
  5917. // -- grab child div in each post having a class.
  5918. // -- nb: <details> is injected in between some <div>s - effectively kicking it out of the collection.
  5919.  
  5920. // -- mostly English users:
  5921. // -- FB's October 2024 update #2:
  5922. 'h3[dir="auto"] ~ div:not([class]) > div > div > div > div > div',
  5923. 'h2[dir="auto"] ~ div:not([class]) > div > div > div > div > div',
  5924.  
  5925. // 'h3[dir="auto"] ~ div:not([class]) > div.x1lliihq > div > div > div > div',
  5926. // 'h2[dir="auto"] ~ div:not([class]) > div.x1lliihq > div > div > div > div',
  5927.  
  5928. // -- mostly non-English users:
  5929. 'div[role="feed"] > h3[dir="auto"] ~ div:not([class]) > div[data-pagelet*="FeedUnit_"] > div > div > div > div',
  5930. 'div[role="feed"] > h2[dir="auto"] ~ div:not([class]) > div[data-pagelet*="FeedUnit_"] > div > div > div > div',
  5931.  
  5932. // -- FB's October 2024 update #1:
  5933. // 'h3[dir="auto"] ~ div:not([class]) > div[class] > div > div > div > div',
  5934. // 'h2[dir="auto"] ~ div:not([class]) > div[class] > div > div > div > div',
  5935.  
  5936. ];
  5937.  
  5938. for (const query of queries) {
  5939. const nodeList = document.querySelectorAll(query);
  5940. if (nodeList.length > 0) {
  5941. posts = Array.from(nodeList);
  5942. break;
  5943. }
  5944. }
  5945.  
  5946. return posts;
  5947. }
  5948.  
  5949. function hasSizeChanged(oldValue, newValue) {
  5950. // -- any changes in the size of the html structure?
  5951. // -- nb: fb is constantly changing something small ... hence the tolerance
  5952. const tolerance = 16;
  5953. // console.info(log + `hasSizeChanged(${oldValue}, ${newValue}); results: ${Math.abs(parseInt(newValue, 10) - parseInt(oldValue, 10))}`);
  5954. return Math.abs(parseInt(newValue, 10) - parseInt(oldValue, 10)) > tolerance;
  5955. }
  5956.  
  5957. function isTheHouseDirty() {
  5958. // -- check if the main column exists and has changed ...
  5959. // -- check if dialog exists and has changed ...
  5960. // -- applies to news feed,
  5961. // -- using a quick/crude method of testing if something changed - check size of innerHTML
  5962. // -- (a more advanced, but slower method would be hashing)
  5963. // :: return : array of two items
  5964.  
  5965. const arrReturn = [null, null]; // -- null means no change.
  5966.  
  5967. // -- main column / content (feed)
  5968. const mainColumnQuery = 'div[role="navigation"] ~ div[role="main"]';
  5969. const mainColumn = document.querySelector(mainColumnQuery);
  5970. if (mainColumn) {
  5971. if (mainColumn.hasAttribute(mainColumnAtt) === false) {
  5972. // -- first timer ...
  5973. arrReturn[0] = mainColumn;
  5974. }
  5975. else if (hasSizeChanged(mainColumn.getAttribute(mainColumnAtt), mainColumn.innerHTML.length)) {
  5976. // -- change detected ...
  5977. arrReturn[0] = mainColumn;
  5978. }
  5979. }
  5980.  
  5981. // -- dialog (article popup)
  5982. const elDialog = document.querySelector('div[role="dialog"]');
  5983. if (elDialog) {
  5984. if (elDialog.hasAttribute(mainColumnAtt) === false) {
  5985. arrReturn[1] = elDialog;
  5986. }
  5987. else if (hasSizeChanged(elDialog.getAttribute(mainColumnAtt), elDialog.innerHTML.length)) {
  5988. arrReturn[1] = elDialog;
  5989. }
  5990. }
  5991.  
  5992. VARS.noChangeCounter++;
  5993. return arrReturn;
  5994.  
  5995. }
  5996.  
  5997. function gf_isTheHouseDirty() {
  5998. // -- check if the main column exists and has changed ...
  5999. // -- check if dialog exists and has changed ...
  6000. // -- applies to groups feed, group feed ...
  6001. // -- using a quick/crude method of testing if something changed - check size of innerHTML
  6002. // -- (a more advanced, but slower method would be hashing)
  6003. // :: return : array of two items
  6004.  
  6005. const arrReturn = [null, null]; // -- null means no change.
  6006.  
  6007. // -- main column / content (feed)
  6008. const mainColumnQuery = 'div[role="navigation"] ~ div[role="main"]';
  6009. const mainColumn = document.querySelector(mainColumnQuery);
  6010. if (mainColumn) {
  6011. if (mainColumn.hasAttribute(mainColumnAtt) === false) {
  6012. // -- first timer ...
  6013. arrReturn[0] = mainColumn;
  6014. }
  6015. else if (hasSizeChanged(mainColumn.getAttribute(mainColumnAtt), mainColumn.innerHTML.length)) {
  6016. // -- change detected ...
  6017. arrReturn[0] = mainColumn;
  6018. }
  6019. }
  6020. else {
  6021. // -- inside a group profile ...
  6022. const mainColumnQueryGP = 'div[role="main"] div[role="feed"]' ;
  6023. const mainColumnGP = document.querySelector(mainColumnQueryGP);
  6024. if (mainColumnGP) {
  6025. if (mainColumnGP.hasAttribute(mainColumnAtt) === false) {
  6026. // -- first timer ...
  6027. arrReturn[0] = mainColumnGP;
  6028. }
  6029. else if (hasSizeChanged(mainColumnGP.getAttribute(mainColumnAtt), mainColumnGP.innerHTML.length)) {
  6030. // -- change detected ...
  6031. arrReturn[0] = mainColumnGP;
  6032. }
  6033. }
  6034. }
  6035.  
  6036.  
  6037. // -- dialog (article popup)
  6038. const elDialog = document.querySelector('div[role="dialog"]');
  6039. if (elDialog) {
  6040. if (elDialog.hasAttribute(mainColumnAtt) === false) {
  6041. arrReturn[1] = elDialog;
  6042. }
  6043. else if (hasSizeChanged(elDialog.getAttribute(mainColumnAtt), elDialog.innerHTML.length)) {
  6044. arrReturn[1] = elDialog;
  6045. }
  6046. }
  6047.  
  6048.  
  6049. VARS.noChangeCounter++;
  6050. return arrReturn;
  6051.  
  6052. }
  6053.  
  6054. function mp_isTheHouseDirty() {
  6055. // -- check if the main column size has changed ...
  6056. // -- if yes, do scanning.
  6057. // -- applies to marketplace ...
  6058.  
  6059. // -- two scenarios; 1) non-dialog mode, 2) dailog mode (viewing a single item).
  6060.  
  6061. // -- viewing an item in dialog mode or page mode ..
  6062. if (VARS.mpType === 'item') {
  6063. // -- do not test for mainColumnAtt first - in dialog mode that area is [hidden] and causes the function to return null ...
  6064. const mainColumnDM = document.querySelector('div[hidden] ~ div[class*="__"] div[role="dialog"]');
  6065. if (mainColumnDM) {
  6066. if (mainColumnDM.hasAttribute(mainColumnAtt)) {
  6067. if (hasSizeChanged(mainColumnDM.getAttribute(mainColumnAtt), mainColumnDM.innerHTML.length)) {
  6068. // -- change detected ...
  6069. return mainColumnDM;
  6070. }
  6071. }
  6072. else {
  6073. // -- main column not yet marked
  6074. return mainColumnDM;
  6075. }
  6076. }
  6077. const mainColumnPM = document.querySelector('div[role="navigation"] ~ div[role="main"]');
  6078. if (mainColumnPM) {
  6079. if (mainColumnPM.hasAttribute(mainColumnAtt)) {
  6080. if (hasSizeChanged(mainColumnPM.getAttribute(mainColumnAtt), mainColumnPM.innerHTML.length.toString())) {
  6081. // -- change detected ...
  6082. return mainColumnPM;
  6083. }
  6084. }
  6085. else {
  6086. // -- main column not yet marked
  6087. return mainColumnPM;
  6088. }
  6089. }
  6090. }
  6091. else {
  6092. // -- standard mp pages.
  6093. const mainColumn = document.querySelector(`[${mainColumnAtt}]`);
  6094. if (mainColumn) {
  6095. if (hasSizeChanged(mainColumn.getAttribute(mainColumnAtt), mainColumn.innerHTML.length)) {
  6096. // -- change detected ...
  6097. return mainColumn;
  6098. }
  6099. }
  6100. else {
  6101. // -- main column not yet marked
  6102. const query = 'div[role="navigation"] ~ div[role="main"]';
  6103. const mainColumn = document.querySelector(query);
  6104. if (mainColumn) {
  6105. return mainColumn;
  6106. }
  6107. }
  6108. }
  6109. // -- either main column not found or no change in size.
  6110. // console.info(log + 'nf_isTheHouseDirty(); - no mainColumn found / no change in size');
  6111. VARS.noChangeCounter++;
  6112. return null;
  6113. }
  6114.  
  6115. function sf_isTheHouseDirty() {
  6116. // -- check if the main column size has changed ...
  6117. // -- if yes, do scanning.
  6118. // -- applies to search results feed
  6119.  
  6120. const query = 'div[role="region"] ~ div[role="main"]';
  6121. const mainColumn = document.querySelector(query);
  6122. if (mainColumn) {
  6123. if (mainColumn.hasAttribute(mainColumnAtt) === false) {
  6124. // -- first timer
  6125. return mainColumn;
  6126. }
  6127. if (hasSizeChanged(mainColumn.getAttribute(mainColumnAtt), mainColumn.innerHTML.length)) {
  6128. return mainColumn;
  6129. }
  6130. }
  6131.  
  6132. // console.info(log + 'nf_isTheHouseDirty(); - no mainColumn found / no change in size');
  6133. VARS.noChangeCounter++;
  6134. return null;
  6135. }
  6136.  
  6137. function vf_isTheHouseDirty() {
  6138. // -- check if the main column size has changed ...
  6139. // -- if yes, do scanning.
  6140. // -- applies to videos feed ...
  6141.  
  6142. const arrReturn = [null, null]; // -- null means no change
  6143.  
  6144. // -- main column / content (feed)
  6145. // -- nb: sometimes we get more than one set of elements matching this ..
  6146. // -- the first 2 could be related to "new videos for you * 1" ..
  6147. // -- so grab the last one ...
  6148. const mainColumnQuery = 'div[role="navigation"] ~ div[role="main"] div[role="main"] > div > div > div > div > div';
  6149. const mainColumns = document.querySelectorAll(mainColumnQuery);
  6150. //const mainColumn = document.querySelector(mainColumnQuery);
  6151. let mainColumn = null;
  6152. if (mainColumns.length > 0) {
  6153. // - bypass the "new videos for you * 1" ...
  6154. mainColumn = mainColumns[mainColumns.length - 1];
  6155. }
  6156. // console.info(log + 'vf_isTheHouseDirty(); mainColumn(1):', mainColumn);
  6157. if (mainColumn) {
  6158. if (mainColumn.hasAttribute(mainColumnAtt) === false) {
  6159. // -- first timer
  6160. arrReturn[0] = mainColumn;
  6161. }
  6162. else if (hasSizeChanged(mainColumn.getAttribute(mainColumnAtt), mainColumn.innerHTML.length)) {
  6163. // -- change detected ...
  6164. arrReturn[0] = mainColumn;
  6165. }
  6166. }
  6167.  
  6168. // -- dialog box (clicked 'expand' from video feed)
  6169. // -- nb: vfType === 'item'
  6170. const elDialog = document.querySelector('div[role="dialog"] div[role="main"]');
  6171. // console.info(log + 'vf_isTheHouseDirty(); mainColumn(2):', elDialog);
  6172. if (elDialog) {
  6173. if (elDialog.hasAttribute(mainColumnAtt) === false) {
  6174. arrReturn[1] = elDialog;
  6175. }
  6176. else if (hasSizeChanged(elDialog.getAttribute(mainColumnAtt), elDialog.innerHTML.length)) {
  6177. arrReturn[1] = elDialog;
  6178. }
  6179. }
  6180.  
  6181. VARS.noChangeCounter++;
  6182. return arrReturn;
  6183. }
  6184.  
  6185. function pp_isTheHouseDirty() {
  6186. // -- check if the main column size has changed ...
  6187. // -- if yes, do scanning.
  6188. // -- applies to profile pages feed
  6189.  
  6190. const arrReturn = [null, null]; //-- null means no change
  6191.  
  6192. // -- main column / content (feed)
  6193. const mainColumnQuery = 'div[role="main"]';
  6194. const mainColumn = document.querySelector(mainColumnQuery);
  6195. if (mainColumn) {
  6196. if (mainColumn.hasAttribute(mainColumnAtt) === false) {
  6197. // -- first timer
  6198. arrReturn[0] = mainColumn;
  6199. }
  6200. else if (hasSizeChanged(mainColumn.getAttribute(mainColumnAtt), mainColumn.innerHTML.length)) {
  6201. // -- change detected ...
  6202. arrReturn[0] = mainColumn;
  6203. }
  6204. }
  6205.  
  6206. // -- dialog (article popup)
  6207. const elDialog = document.querySelector('div[role="dialog"]');
  6208. if (elDialog) {
  6209. if (elDialog.hasAttribute(mainColumnAtt) === false) {
  6210. arrReturn[1] = elDialog;
  6211. }
  6212. else if (hasSizeChanged(elDialog.getAttribute(mainColumnAtt), elDialog.innerHTML.length)) {
  6213. arrReturn[1] = elDialog;
  6214. }
  6215. }
  6216.  
  6217. VARS.noChangeCounter++;
  6218. return arrReturn;
  6219. }
  6220.  
  6221. function mopUpTheNewsFeed() {
  6222. // -- mopping up the news feed page
  6223.  
  6224. const [mainColumn, elDialog] = isTheHouseDirty();
  6225. if (mainColumn === null && elDialog === null) {
  6226. return;
  6227. }
  6228.  
  6229. if (mainColumn) {
  6230.  
  6231. // -- Tablist - not part of the general news feed stream
  6232. // -- Includes Stories (standalone tab)
  6233. if (VARS.Options.NF_TABLIST_STORIES_REELS_ROOMS) {
  6234. nf_scrubTheTabbies();
  6235. }
  6236. if (VARS.Options.NF_SURVEY) {
  6237. nf_scrubTheSurvey();
  6238. }
  6239.  
  6240. // -- aside's sponsored
  6241. if (VARS.Options.NF_SPONSORED) {
  6242. nf_cleanTheConsoleTable('Sponsored');
  6243. }
  6244.  
  6245. // -- aside's suggestions
  6246. if (VARS.Options.NF_SUGGESTIONS) {
  6247. nf_cleanTheConsoleTable('Suggestions');
  6248. }
  6249.  
  6250. // -- news feed stream ...
  6251. const posts = nf_getCollectionOfPosts();
  6252.  
  6253. // console.info(log + 'mopUpTheNewsFeed(); number of posts:', posts.length);
  6254.  
  6255. for (const post of posts) {
  6256.  
  6257. if (post.innerHTML.length === 0) {
  6258. // -- fb is clearing out the posts as the user scrolls ...
  6259. }
  6260. else {
  6261.  
  6262. let hideReason = '';
  6263. let isSponsoredPost = false;
  6264.  
  6265. if (post.hasAttribute(postAtt)) {
  6266. // -- already flagged ...
  6267. hideReason = 'hidden';
  6268. }
  6269. // else if ((post[postPropDS] !== undefined) && (parseInt(post[postPropDS]) >= VARS.scanCountMaxLoop)) {
  6270. // // -- skip these - already been scanned a few times
  6271. // console.info(log, 'mopping(); skipping:' + post[postPropDS] + '; ' + post);
  6272. // }
  6273. else {
  6274. doLightDusting(post);
  6275.  
  6276. if (hideReason === '' && VARS.Options.NF_REELS_SHORT_VIDEOS) {
  6277. hideReason = nf_isReelsAndShortVideos(post);
  6278. }
  6279. if (hideReason === '' && VARS.Options.NF_SHORT_REEL_VIDEO) {
  6280. hideReason = nf_isShortReelVideo(post);
  6281. }
  6282. if (hideReason === '' && VARS.Options.NF_PAID_PARTNERSHIP) {
  6283. hideReason = nf_isPaidPartnership(post);
  6284. }
  6285. if (hideReason === '' && VARS.Options.NF_PEOPLE_YOU_MAY_KNOW) {
  6286. hideReason = nf_isPeopleYouMayKnow(post);
  6287. }
  6288. if (hideReason === '' && VARS.Options.NF_SUGGESTIONS) {
  6289. hideReason = nf_isSuggested(post);
  6290. }
  6291. if (hideReason === '' && VARS.Options.NF_FOLLOW) {
  6292. hideReason = nf_isFollow(post);
  6293. }
  6294. if (hideReason === '' && VARS.Options.NF_PARTICIPATE) {
  6295. hideReason = nf_isParticipate(post);
  6296. }
  6297. if (hideReason === '' && VARS.Options.NF_SPONSORED_PAID) {
  6298. hideReason = nf_isSponsoredPaidBy(post);
  6299. }
  6300. if (hideReason === '' && VARS.Options.NF_EVENTS_YOU_MAY_LIKE) {
  6301. hideReason = nf_isEventsYouMayLike(post);
  6302. }
  6303. if (hideReason === '' && VARS.Options.NF_STORIES) {
  6304. hideReason = nf_isStoriesPost(post);
  6305. }
  6306. if (hideReason === '' && VARS.Options.NF_ANIMATED_GIFS_POSTS) {
  6307. hideReason = nf_hasAnimatedGifContent(post);
  6308. }
  6309. // -- placed here due to "overlaps" between this rule and at least 1 of the above rule.
  6310. if (hideReason === '' && VARS.Options.NF_SPONSORED && isSponsored(post)) {
  6311. isSponsoredPost = true;
  6312. hideReason = KeyWords.SPONSORED;
  6313. }
  6314. // -- sponsored + blocked text sometimes overlap. sponsored takes priority.
  6315. if (hideReason === '' && VARS.Options.NF_BLOCKED_ENABLED) {
  6316. hideReason = nf_isBlockedText(post);
  6317. }
  6318. if (hideReason === '' && VARS.Options.NF_LIKES_MAXIMUM && VARS.Options.NF_LIKES_MAXIMUM !== '') {
  6319. hideReason = nf_postExceedsLikeCount(post);
  6320. }
  6321. }
  6322.  
  6323. if (hideReason.length > 0) {
  6324. if (hideReason !== 'hidden') {
  6325. // -- post not yet hidden, hide it.
  6326. nf_hidePost(post, hideReason, isSponsoredPost);
  6327. }
  6328. }
  6329. else {
  6330. // -- not a hidden post
  6331.  
  6332. // -- run pause animation (useful to hide those animated posts/comments)
  6333. if (VARS.Options.NF_ANIMATED_GIFS_PAUSE) {
  6334. swatTheMosquitos(post);
  6335. }
  6336. // -- hide info boxes
  6337. if (VARS.hideAnInfoBox) {
  6338. scrubInfoBoxes(post);
  6339. }
  6340. // -- hide number of shares box
  6341. if (VARS.Options.NF_SHARES) {
  6342. nf_hideNumberOfShares(post);
  6343. }
  6344. }
  6345. }
  6346. }
  6347.  
  6348. mainColumn.setAttribute(mainColumnAtt, mainColumn.innerHTML.length.toString());
  6349. VARS.noChangeCounter = 0;
  6350. }
  6351.  
  6352. if (elDialog) {
  6353. // -- viewing a post in a "dialog-box" (triggered by viewing more comments)
  6354. if (VARS.Options.NF_ANIMATED_GIFS_PAUSE) {
  6355. swatTheMosquitos(elDialog);
  6356. }
  6357. elDialog.setAttribute(mainColumnAtt, elDialog.innerHTML.length.toString());
  6358. VARS.noChangeCounter = 0;
  6359. }
  6360.  
  6361. }
  6362.  
  6363. function mopUpTheGroupsFeed() {
  6364. // -- mopping up the groups feed page
  6365.  
  6366. // console.info(log+'mopUpTheGroupsFeed(), gfType:', VARS.gfType, '; hide an info box:', VARS.hideAnInfoBox);
  6367.  
  6368. const [mainColumn, elDialog] = gf_isTheHouseDirty();
  6369. if (mainColumn === null && elDialog === null) {
  6370. return;
  6371. }
  6372.  
  6373. if (mainColumn) {
  6374.  
  6375. if (VARS.gfType === 'groups' || VARS.gfType === 'groups-recent' || VARS.gfType === 'search') {
  6376. // - main groups feed.
  6377. // -- 'groups' - accessible via the Groups link
  6378. // -- 'groups-recent' - accessible via the Feeds > group link (has similar HTML structure to News Feed)
  6379. // -- 'search' - groups (same layout as groups feed)
  6380.  
  6381. // -- aside's suggestions (also appears above feed on narrow pages)
  6382. if (VARS.Options.GF_SUGGESTIONS) {
  6383. gf_cleanTheConsoleTable('Suggestions');
  6384. }
  6385.  
  6386. // -- groups feed stream ...
  6387. const query = VARS.gfType === 'groups-recent' ? 'h2[dir="auto"] + div > div' : 'div[role="feed"] > div';
  6388. const posts = Array.from(document.querySelectorAll(query));
  6389. if (posts.length > 0) {
  6390. // console.info(log+'---> mopUpTheGroupsFeed() - multiple groups');
  6391. const count = posts.length;
  6392. const start = (count < 25) ? 0 : (count - 25);
  6393.  
  6394. // for (const post of posts) {
  6395. for (let i = start; i < count; i++) {
  6396. const post = posts[i];
  6397.  
  6398. if (post.innerHTML.length === 0) {
  6399. continue;
  6400. }
  6401.  
  6402. let hideReason = '';
  6403.  
  6404. // -- add the "open post in new tab link+icon".
  6405. if ((VARS.gfType === 'groups') && (post[postPropDS] === undefined)) {
  6406. gf_setPostLinkToOpenInNewTab(post);
  6407. }
  6408.  
  6409. if (post.hasAttribute(postAtt)) {
  6410. // -- already flagged
  6411. hideReason = 'hidden';
  6412. }
  6413. // else if ((post[postPropDS] !== undefined) && (parseInt(post[postPropDS]) >= VARS.scanCountMaxLoop)) {
  6414. // // -- skip these - already been scanned a few times
  6415. // }
  6416. else {
  6417.  
  6418. doLightDusting(post);
  6419.  
  6420. if (hideReason === '' && VARS.Options.GF_SPONSORED && isSponsored(post)) {
  6421. hideReason = KeyWords.SPONSORED;
  6422. }
  6423. if (hideReason === '' && VARS.Options.GF_SUGGESTIONS) {
  6424. hideReason = gf_isSuggested(post);
  6425. }
  6426. // if (hideReason === '' && VARS.Options.GF_PAID_PARTNERSHIP) {
  6427. // //console.info(log + 'mopUpTheGroupsFeed(), ---- Paid partnership - needs code ----')
  6428. // }
  6429. if (hideReason === '' && VARS.Options.GF_SHORT_REEL_VIDEO) {
  6430. hideReason = gf_isShortReelVideo(post);
  6431. }
  6432. if (hideReason === '' && VARS.Options.GF_BLOCKED_ENABLED) {
  6433. hideReason = gf_isBlockedText(post);
  6434. }
  6435. if (hideReason === '' && VARS.Options.GF_ANIMATED_GIFS_POSTS) {
  6436. hideReason = gf_hasAnimatedGifContent(post);
  6437. }
  6438. }
  6439.  
  6440. if (hideReason.length > 0) {
  6441. // -- increment hidden count
  6442. VARS.echoCount++;
  6443. if (hideReason !== 'hidden') {
  6444. // -- post not yet hidden, hide it.
  6445. gf_hidePost(post, hideReason);
  6446. }
  6447. }
  6448. else {
  6449. // -- not a hidden post
  6450. // -- reset hidden count
  6451. VARS.echoCount = 0;
  6452. // -- run pause animation (useful to hide those animated comments)
  6453. if (VARS.Options.GF_ANIMATED_GIFS_PAUSE) {
  6454. // console.info(log + 'pausing animations ...');
  6455. swatTheMosquitos(post);
  6456. }
  6457. // -- hide info boxes
  6458. if (VARS.hideAnInfoBox) {
  6459. scrubInfoBoxes(post);
  6460. }
  6461. // -- hide number of shares box
  6462. if (VARS.Options.GF_SHARES) {
  6463. gf_hideNumberOfShares(post);
  6464. }
  6465. }
  6466. // console.info(log+'mopUpTheGroupsFeed:', hideReason, VARS.echoCount, post);
  6467. }
  6468. }
  6469. // console.info(log+'<--- mopUpTheGroupsFeed() - multiple groups');
  6470. }
  6471. else {
  6472. // - single group ...
  6473. const query = 'div[role="feed"] > div';
  6474. const posts = Array.from(document.querySelectorAll(query));
  6475. if (posts.length) {
  6476. // console.info(log+'---> mopUpTheGroupsFeed() - single group');
  6477. for (const post of posts) {
  6478.  
  6479. if (post.innerHTML.length === 0) {
  6480. continue;
  6481. }
  6482.  
  6483. let hideReason = '';
  6484.  
  6485. if (post.hasAttribute(postAtt)) {
  6486. // -- already flagged
  6487. hideReason = 'hidden';
  6488. }
  6489. // else if ((post[postPropDS] !== undefined) && (parseInt(post[postPropDS]) >= VARS.scanCountMaxLoop)) {
  6490. // // -- skip these - already scanned a few times
  6491. // // console.info(log + 'skipping:', post);
  6492. // }
  6493. else {
  6494. doLightDusting(post);
  6495.  
  6496. if (hideReason === '' && VARS.Options.GF_SHORT_REEL_VIDEO) {
  6497. hideReason = gf_isShortReelVideo(post);
  6498. }
  6499. if (hideReason === '' && VARS.Options.GF_BLOCKED_ENABLED) {
  6500. hideReason = gf_isBlockedText(post);
  6501. }
  6502. if (hideReason === '' && VARS.Options.GF_ANIMATED_GIFS_POSTS) {
  6503. hideReason = gf_hasAnimatedGifContent(post);
  6504. }
  6505. }
  6506.  
  6507. if (hideReason.length > 0) {
  6508. // -- increment hidden counter
  6509. VARS.echoCount++;
  6510. if (hideReason !== 'hidden') {
  6511. // -- post not yet hidden, hide it.
  6512. gf_hidePost(post, hideReason);
  6513. }
  6514. }
  6515. else {
  6516. // -- not a hidden post
  6517. // -- reset hidden count
  6518. VARS.echoCount = 0;
  6519. // -- run pause animation (useful to hide those animated posts/comments)
  6520. if (VARS.Options.GF_ANIMATED_GIFS_PAUSE) {
  6521. // console.info(log + 'pausing animations ...');
  6522. swatTheMosquitos(post);
  6523. }
  6524. // -- hide info boxes
  6525. if (VARS.hideAnInfoBox) {
  6526. scrubInfoBoxes(post);
  6527. }
  6528. // -- hide number of shares box
  6529. if (VARS.Options.GF_SHARES) {
  6530. gf_hideNumberOfShares(post);
  6531. }
  6532. }
  6533. }
  6534. // console.info(log+'<--- mopUpTheGroupsFeed() - single group');
  6535. }
  6536. }
  6537.  
  6538. mainColumn.setAttribute(mainColumnAtt, mainColumn.innerHTML.length.toString());
  6539. VARS.noChangeCounter = 0;
  6540. }
  6541.  
  6542. if (elDialog) {
  6543. // -- viewing a post in a "dialog-box" (triggered by viewing more comments)
  6544. if (VARS.Options.GF_ANIMATED_GIFS_PAUSE) {
  6545. swatTheMosquitos(elDialog);
  6546. }
  6547. elDialog.setAttribute(mainColumnAtt, elDialog.innerHTML.length.toString());
  6548. VARS.noChangeCounter = 0;
  6549. }
  6550.  
  6551. }
  6552.  
  6553. function mopUpTheWatchVideosFeed() {
  6554. // -- mopping up the watch videos feed page
  6555.  
  6556. const [mainColumn, elDialog] = vf_isTheHouseDirty();
  6557. // console.info(log + 'mopUpTheWatchVideosFeed(); video type:', VARS.vfType, '; mainColumn:', mainColumn, '; elDialog:', elDialog);
  6558. if (mainColumn === null && elDialog === null) {
  6559. return;
  6560. }
  6561.  
  6562. const container = (elDialog ? elDialog : mainColumn);
  6563. // console.info(log + 'mopUpTheWatchVideosFeed(); container:', container);
  6564. if (container) {
  6565. let query;
  6566. let queryBlocks;
  6567. if (VARS.vfType === 'videos') {
  6568. // -- normal feed
  6569. query = ':scope > div > div:not([class]) > div';
  6570. queryBlocks = ':scope > div > div > div > div > div:nth-of-type(2) > div';
  6571. }
  6572. else if (VARS.vfType === 'search') {
  6573. // -- videos --> search
  6574. query = 'div[role="feed"] > div[role="article"]';
  6575. queryBlocks = ':scope > div > div > div > div > div > div > div:nth-of-type(2)';
  6576. }
  6577. else if (VARS.vfType === 'item') {
  6578. // -- videos --> search --> item (videos being listed below the video of interest)
  6579. // -- video - via link
  6580. query = 'div[id="watch_feed"] > div > div:nth-of-type(2) > div > div > div > div:nth-of-type(2) > div > div > div > div';
  6581. queryBlocks = ':scope > div > div > div > div > div:nth-of-type(2) > div';
  6582. }
  6583. else {
  6584. return;
  6585. }
  6586.  
  6587. // console.info(log + 'mopUpTheWatchVideosFeed(); container:', container, '; type:', VARS.vfType, '; query:', query);
  6588.  
  6589. if (VARS.vfType !== 'search') {
  6590. const posts = container.querySelectorAll(query);
  6591. // console.info(log + 'mopUpTheWatchVideosFeed(); container:', container, '; type:', VARS.vfType, '; query:', query, '; posts:', posts);
  6592. for (const post of posts) {
  6593. // if (post.innerHTML.length === 0) {
  6594. // continue;
  6595. // }
  6596. if (countDescendants(post) < 3) {
  6597. // -- bad post - purge it.
  6598. // -- fb sometimes "forget" to finish creating a post
  6599. // console.info(log + 'videos - dummy post found and removed ... next post:', post.parentElement.nextElementSibling);
  6600. // -- disabled the .remove() as it can sometimes be a bit too aggressive ...
  6601. // post.parentElement.remove();
  6602. continue;
  6603. }
  6604.  
  6605. let hideReason = '';
  6606.  
  6607. // -- add the open post in new tab link+icon.
  6608. if ((VARS.vfType === 'videos') && (post[postPropDS] === undefined)) {
  6609. vf_setPostLinkToOpenInNewTab(post);
  6610. }
  6611.  
  6612. if (post.hasAttribute(postAtt)) {
  6613. // -- already hidden
  6614. hideReason = 'hidden';
  6615. }
  6616. // else if ((post[postPropDS] !== undefined) && (parseInt(post[postPropDS]) >= VARS.scanCountMaxLoop)) {
  6617. // // -- skip these - already been scanned a few times
  6618. // // console.info(log + 'video; skipping;', post[postPropDS], VARS.scanCountMaxLoop, post);
  6619. // }
  6620. else {
  6621. doLightDusting(post);
  6622.  
  6623. if (hideReason === '' && VARS.Options.VF_SPONSORED && isSponsored(post)) {
  6624. hideReason = KeyWords.SPONSORED;
  6625. }
  6626. if (hideReason === '' && VARS.Options.VF_LIVE) {
  6627. hideReason = vf_isVideoLive(post);
  6628. }
  6629. if (hideReason === '' && VARS.Options.VF_INSTAGRAM) {
  6630. hideReason = vf_isInstagram(post);
  6631. }
  6632. if (hideReason === '' && VARS.Options.VF_DUPLICATE_VIDEOS) {
  6633. // -- vf_hideDuplicateVideos() will hide the duplicates
  6634. // -- returns nothing.
  6635. vf_hideDuplicateVideos(post, query);
  6636. if (post.hasAttribute(postAtt)) {
  6637. // -- prevent doubling up of hiding the video post.
  6638. hideReason === 'hidden';
  6639. }
  6640. }
  6641. if (hideReason === '' && VARS.Options.VF_BLOCKED_ENABLED) {
  6642. hideReason = vf_isBlockedText(post, queryBlocks);
  6643. }
  6644. // console.info(log + 'mopUpTheWatchVideosFeed(); ::: hideReason:', hideReason, post, queryBlocks);
  6645. }
  6646.  
  6647. if (hideReason.length > 0) {
  6648. if (hideReason !== 'hidden') {
  6649. // -- post not yet hidden, hide it.
  6650. vf_hidePost(post, hideReason);
  6651. }
  6652. }
  6653. else {
  6654. // -- not a hidden post
  6655. // -- run pause animation (useful to hide those animated posts/comments)
  6656. if (VARS.Options.VF_ANIMATED_GIFS_PAUSE) {
  6657. // console.info(log + 'pausing animations ...');
  6658. swatTheMosquitos(post);
  6659. }
  6660. // -- hide info boxes
  6661. if (VARS.hideAnInfoBox) {
  6662. scrubInfoBoxes(post);
  6663. }
  6664. // -- nb: videos do not have a number of shares box
  6665.  
  6666. // -- hide sponsored blocks (appears between video & comments)
  6667. vf_scrubSponsoredBlock(post);
  6668. }
  6669.  
  6670. // -- check for sponsored content beneath the video ...
  6671. vf_hideSponsoredBlock(post, query, queryBlocks);
  6672. }
  6673. }
  6674. else {
  6675. // -- search videos
  6676. // -- structure is different from regular video feed
  6677. // -- thumbnail on left, text on right
  6678. const posts = document.querySelectorAll(query);
  6679. for (const post of posts) {
  6680.  
  6681. let hideReason = '';
  6682.  
  6683. if (post.hasAttribute(postAtt)) {
  6684. // -- already hidden
  6685. hideReason = 'hidden';
  6686. }
  6687. else {
  6688. if (VARS.Options.VF_BLOCKED_ENABLED) {
  6689. hideReason = vf_isBlockedText(post, queryBlocks);
  6690. }
  6691. }
  6692.  
  6693. if (hideReason.length > 0) {
  6694. if (hideReason !== 'hidden') {
  6695. // -- post not yet hidden, hide it.
  6696. vf_hidePost(post, hideReason);
  6697. }
  6698. }
  6699. else {
  6700. // -- not a hidden post
  6701. }
  6702. }
  6703. }
  6704.  
  6705. container.setAttribute(mainColumnAtt, container.innerHTML.length.toString());
  6706. VARS.noChangeCounter = 0;
  6707. }
  6708.  
  6709. if (elDialog) {
  6710. // -- viewing a post in a "dialog-box" (triggered by viewing more comments)
  6711. if (VARS.Options.NF_ANIMATED_GIFS_PAUSE) {
  6712. swatTheMosquitos(elDialog);
  6713. }
  6714. // elDialog.setAttribute(mainColumnAtt, elDialog.innerHTML.length.toString());
  6715. // VARS.noChangeCounter = 0;
  6716. }
  6717.  
  6718. }
  6719.  
  6720. function mp_hideBox(box, reason) {
  6721. box.setAttribute(VARS.hideWithNoCaptionAtt, '');
  6722. box.setAttribute(postAtt, sanitizeReason(reason));
  6723. if (VARS.Options.VERBOSITY_DEBUG) {
  6724. box.setAttribute(VARS.showAtt, '');
  6725. }
  6726. }
  6727.  
  6728. function mp_stopTrackingDirtIntoMyHouse() {
  6729. // -- remove tracking bits
  6730. const collectionOfLinks = document.querySelectorAll('a[href*="/?ref="]');
  6731. for (const trackingLink of collectionOfLinks) {
  6732. trackingLink.href = trackingLink.href.split('/?ref')[0];
  6733. }
  6734. }
  6735.  
  6736. function mp_hideSponsoredItems() {
  6737.  
  6738. // -- landing page listing - items above categories
  6739. // -- sponsored posts have <span> as the first child element ...
  6740. const query = `div[${mainColumnAtt}] > div > div > div > div > div > div[style] > span`;
  6741. const items = document.querySelectorAll(query);
  6742. for (const item of items) {
  6743. // - item's container
  6744. // const box = item.closest('div[style]');
  6745. const box = item.parentElement;
  6746. if (box.hasAttribute(postAttMPSkip)) {
  6747. if (box.innerHTML.length == box.getAttribute(postAttMPSkip)) {
  6748. continue;
  6749. }
  6750. }
  6751. mp_hideBox(box, KeyWords.SPONSORED);
  6752. }
  6753. }
  6754.  
  6755. function mopUpTheMarketplaceFeed() {
  6756. // -- mopping up parts of the Marketplace ...
  6757.  
  6758. const mainColumn = mp_isTheHouseDirty();
  6759. // console.info(log + 'clean(); mainColumn:', mainColumn, VARS.mpType);
  6760. if (mainColumn === null) {
  6761. return;
  6762. }
  6763.  
  6764. mp_stopTrackingDirtIntoMyHouse();
  6765.  
  6766. // console.info(log + 'mopUpTheMarketplaceFeed(); mpType:', VARS.mpType);
  6767.  
  6768. if (VARS.mpType === 'marketplace' || VARS.mpType === 'item') {
  6769. // - standard marketplace page
  6770. // - on the item page, there's listing of items to sell ... (similar structure to standard marketplace page)
  6771.  
  6772.  
  6773. if (VARS.Options.MP_SPONSORED) {
  6774. // -- sponsored items (above sections) (untested)
  6775. mp_hideSponsoredItems();
  6776.  
  6777. // -- sponsored items in "categories"
  6778. // -- two parts - heading + item
  6779. // -- nb: adguard base filter hides the heading, but not the item
  6780. const queryHeadings = `div:not([${postAtt}]) > a[href="/ads/about/?entry_product=ad_preferences"], div:not([${postAtt}]) > object > a[href="/ads/about/?entry_product=ad_preferences"]`;
  6781. const headings = document.querySelectorAll(queryHeadings);
  6782.  
  6783. let queryItems = `div[style]:not([${postAtt}]) > span > div:first-of-type > a:not([href*="marketplace"])`;
  6784. let items = document.querySelectorAll(queryItems);
  6785. if (items.length === 0) {
  6786. // -- structure changed in Nov 2023.
  6787. queryItems = `div[style]:not([${postAtt}]) > span > div:first-of-type > div > a:not([href*="marketplace"])`;
  6788. items = document.querySelectorAll(queryItems);
  6789. }
  6790.  
  6791. // console.info(log+'marketplace(); headings:', headings);
  6792. // console.info(log+'marketplace(); items:', items);
  6793. // console.info(log+'marketplace(); bool:', (VARS.Options.MP_SPONSORED && (headings.length > 0) && (items.length > 0)));
  6794.  
  6795. if (VARS.Options.MP_SPONSORED && (headings.length > 0) && (items.length > 0)) {
  6796. for (const heading of headings) {
  6797. // heading = heading.parentElement;
  6798. mp_hideBox(heading.parentElement, KeyWords.SPONSORED);
  6799. }
  6800. for (const item of items) {
  6801. // const parentItem = climbUpTheTree(item, 3);
  6802. const parentItem = climbUpTheTree(item, 4);
  6803. mp_hideBox(parentItem, KeyWords.SPONSORED);
  6804. }
  6805. }
  6806. }
  6807.  
  6808. if (VARS.Options.MP_BLOCKED_ENABLED) {
  6809. mp_doBlockingByBlockedText();
  6810. }
  6811. }
  6812. if (VARS.mpType === 'item') {
  6813. // -- viewing a marketplace item - a small sponsored box often shows up on the right.
  6814. if (VARS.Options.MP_SPONSORED) {
  6815. const elDialog = document.querySelector('div[role="dialog"]');
  6816. if (elDialog) {
  6817. // -- viewing a mp item via mp feed (no new tab or reloaded page)
  6818. const query = `span h2 [href*="/ads/about/"]:not([${postAtt}])`;
  6819. const elLink = elDialog.querySelector(query);
  6820. if (elLink) {
  6821. const box = elLink.closest('h2').closest('span');
  6822. mp_hideBox(box, KeyWords.SPONSORED);
  6823. elLink.setAttribute(postAtt, KeyWords.SPONSORED);
  6824. }
  6825. }
  6826. else {
  6827. // -- viewing a mp item in a new tab / reloaded page.
  6828. const query = `div[${mainColumnAtt}] span h2 [href*="/ads/about/"]:not([${postAtt}])`;
  6829. const elLink = document.querySelector(query);
  6830. if (elLink) {
  6831. const box = elLink.closest('h2').closest('span');
  6832. mp_hideBox(box, KeyWords.SPONSORED);
  6833. elLink.setAttribute(postAtt, KeyWords.SPONSORED);
  6834. }
  6835. }
  6836. }
  6837. }
  6838. else if ((VARS.mpType === 'category') || (VARS.mpType === 'search')) {
  6839. // - viewing a markplace category or marketplace search results
  6840. // - (both have similar layout)
  6841. if (VARS.Options.MP_SPONSORED) {
  6842. // -- sponsored items
  6843. mp_hideSponsoredItems();
  6844. // const query = `a[href*="/ads/"]:not([${postAtt}])`;
  6845. // const elements = document.querySelectorAll(query);
  6846. // for (const element of elements) {
  6847. // // console.info(log + 'mp-clean:', element);
  6848. // element.setAttribute(postAtt, element.innerHTML.length);
  6849. // const itemBox = climbUpTheTree(element.parentElement.closest('a'), 3);
  6850. // mp_hideBox(itemBox, KeyWords.SPONSORED);
  6851. // }
  6852. }
  6853. if (VARS.Options.MP_BLOCKED_ENABLED) {
  6854. mp_doBlockingByBlockedText();
  6855. }
  6856. }
  6857.  
  6858. mainColumn.setAttribute(mainColumnAtt, mainColumn.innerHTML.length.toString());
  6859. VARS.noChangeCounter = 0;
  6860.  
  6861. }
  6862.  
  6863. function mopUpTheSearchFeed() {
  6864. // mopping up the search feed / results
  6865. // -- (nb: has similar layout to news feed stream)
  6866. // -- "borrow" news feed's text filter.
  6867.  
  6868. const mainColumn = sf_isTheHouseDirty();
  6869. if (mainColumn === null) {
  6870. return;
  6871. }
  6872.  
  6873. if (VARS.Options.NF_BLOCKED_ENABLED) {
  6874. // const query = 'div[role="feed"] > div';
  6875. const query = 'div[role="feed"] > div > div';
  6876. const posts = Array.from(document.querySelectorAll(query));
  6877.  
  6878. for (const post of posts) {
  6879.  
  6880. if (post.innerHTML.length === 0) {
  6881. continue;
  6882. }
  6883.  
  6884. let hideReason = '';
  6885. let isSponsoredPost = false;
  6886.  
  6887. if (post.hasAttribute(postAtt)) {
  6888. hideReason = 'hidden';
  6889. }
  6890. else {
  6891. if (VARS.Options.NF_SPONSORED && isSponsored(post)) {
  6892. hideReason = KeyWords.SPONSORED;
  6893. isSponsoredPost = true;
  6894. }
  6895. if (hideReason === '' && VARS.Options.NF_BLOCKED_ENABLED) {
  6896. hideReason = nf_isBlockedText(post);
  6897. }
  6898. }
  6899.  
  6900. if (hideReason.length > 0) {
  6901. // -- increment hidden count
  6902. VARS.echoCount++;
  6903. if (hideReason !== 'hidden') {
  6904. // -- post not yet hidden, hide it.
  6905. nf_hidePost(post, hideReason, isSponsoredPost);
  6906. }
  6907. }
  6908. else {
  6909. // -- not a hidden post
  6910. // -- reset hidden count
  6911. VARS.echoCount = 0;
  6912. // -- run pause animation (useful to hide those animated comments)
  6913. if (VARS.Options.NF_ANIMATED_GIFS_PAUSE) {
  6914. // console.info(log + 'pausing animations ...');
  6915. swatTheMosquitos(post);
  6916. }
  6917. // -- hide info boxes
  6918. if (VARS.hideAnInfoBox) {
  6919. scrubInfoBoxes(post);
  6920. }
  6921. }
  6922. }
  6923. }
  6924.  
  6925. mainColumn.setAttribute(mainColumnAtt, mainColumn.innerHTML.length.toString());
  6926. VARS.noChangeCounter = 0;
  6927.  
  6928. }
  6929.  
  6930. function mopUpTheReelFeed(caller) {
  6931.  
  6932. // -- Reels ...
  6933.  
  6934. // -- saveUserOptions will also call this function ...
  6935.  
  6936. // -- nb: videos from the watch videos feed are also collected in the query ..
  6937. // -- .. but they do not have a container with [data-video-id] attribute
  6938. // -- .. and therefore skipped ...
  6939.  
  6940. // -- nb: setting VARS.isRF determines if this function is called or not.
  6941.  
  6942. // console.info(log + 'mopUpTheReelFeed(); ', VARS.isRF, VARS.isRF_InTimeoutMode);
  6943.  
  6944. if (!VARS.isRF) {
  6945. // -- no longer in Reels Feed
  6946. VARS.isRF_InTimeoutMode = false;
  6947. return;
  6948. }
  6949. if (caller !== 'self' && VARS.isRF_InTimeoutMode === true) {
  6950. // -- saveUserOptions or another function is calling and TimeoutMode is currently active.
  6951. return;
  6952. }
  6953.  
  6954. const videoRules = `[data-video-id] video:not([${rvAtt}])`;
  6955. const videos = document.querySelectorAll(videoRules);
  6956.  
  6957. // console.info(log + 'mopUpTheReelFeed(); videos:', caller, videos);
  6958.  
  6959. for (const video of videos) {
  6960. // -- get the video's container's child element
  6961. const elVideoId = video.closest('[data-video-id]');
  6962. if (elVideoId) {
  6963. // -- get the video's container
  6964. const videoContainer = elVideoId.parentElement;
  6965. if (videoContainer) {
  6966. if (VARS.Options.REELS_CONTROLS === true) {
  6967. // -- get the video's description + audio track info container.
  6968. const descriptionOverlay = videoContainer.nextElementSibling;
  6969. if (descriptionOverlay) {
  6970. // -- make room to display the controls by moving the description element up a bit ...
  6971. const elDescriptionContainer = descriptionOverlay.children[0];
  6972. elDescriptionContainer.setAttribute('style', `margin-bottom:${VARS.isChromium ? '4.5' : '2.25'}rem;`);
  6973. // -- enable controls on the video
  6974. video.setAttribute('controls', 'true');
  6975. // -- hide the video's sibling (makes it easier to click on the video's controls)
  6976. const sibling = video.nextElementSibling;
  6977. if (sibling) {
  6978. sibling.setAttribute('style', 'display:none;');
  6979. }
  6980. }
  6981. }
  6982. if (VARS.Options.REELS_DISABLE_LOOPING === true) {
  6983. // -- stop the video
  6984. video.addEventListener('ended', function (ev) {
  6985. ev.target.pause();
  6986. });
  6987. }
  6988. video.setAttribute(rvAtt, '1');
  6989. }
  6990. else {
  6991. // -- a video from the watch videos feed
  6992. // -- hidden underneath the reels feed overlay)
  6993. }
  6994. }
  6995. }
  6996. // -- call me again in a few ms ...
  6997. VARS.isRF_InTimeoutMode = true;
  6998. setTimeout(function () {
  6999. mopUpTheReelFeed('self');
  7000. }, 1000);
  7001. }
  7002.  
  7003. function mopUpTheProfilePage() {
  7004. // -- profile pages
  7005.  
  7006. const proceed = VARS.Options.PP_BLOCKED_ENABLED || VARS.Options.PP_ANIMATED_GIFS_POSTS || VARS.Options.PP_ANIMATED_GIFS_PAUSE;
  7007. // console.info(log + "mopUpTheProfilePage(); proceed:", proceed, "PP_BLOCKED_ENABLED:", VARS.Options.PP_BLOCKED_ENABLED, "PP_ANIMATED_GIFS_POSTS:", VARS.Options.PP_ANIMATED_GIFS_POSTS, "PP_ANIMATED_GIFS_PAUSE:", VARS.Options.PP_ANIMATED_GIFS_PAUSE);
  7008. if (!proceed) {
  7009. return;
  7010. }
  7011.  
  7012. const [mainColumn, elDialog] = pp_isTheHouseDirty();
  7013. // console.info(log + "mopUpTheProfilePage(); mainC:", mainColumn, "elDialog:", elDialog);
  7014. if (mainColumn === null && elDialog === null) {
  7015. return;
  7016. }
  7017.  
  7018. // console.info(log + 'mopUpTheProfilePage(); size changed, doing some cleaning ... (mainColumn / elDialog)', mainColumn, elDialog);
  7019.  
  7020. if (mainColumn) {
  7021. // profile page feed stream ...
  7022. //const query = 'div[role="main"] > div > div > div > div:nth-of-type(2) > div:nth-of-type(2) > div > div';
  7023. //const query = 'div[role="main"] > div > div > div > div:nth-of-type(2) > div:nth-of-type(2) > div > div[class]';
  7024. const query = 'div[role="main"] > div > div > div > div:nth-of-type(2) > div:not([class]) > div > div[class]';
  7025. const posts = Array.from(document.querySelectorAll(query));
  7026.  
  7027. //console.info(log + 'mopUpTheProfilePage(); # of posts: ' + posts.length);
  7028.  
  7029. for (const post of posts) {
  7030. if (post.innerHTML.length === 0) {
  7031. continue;
  7032. }
  7033.  
  7034. let hideReason = '';
  7035. const isSponsoredPost = false;
  7036.  
  7037. if (post.hasAttribute(postAtt)) {
  7038. hideReason = 'hidden';
  7039. }
  7040. else {
  7041. if (hideReason === '' && VARS.Options.PP_ANIMATED_GIFS_POSTS) {
  7042. hideReason = nf_hasAnimatedGifContent(post);
  7043. }
  7044. if (hideReason === '' && VARS.Options.PP_BLOCKED_ENABLED) {
  7045. hideReason = pp_isBlockedText(post);
  7046. }
  7047. }
  7048.  
  7049. //console.info(log + 'mopUpTheProfilePage(); hideReason:', hideReason, post );
  7050.  
  7051. if (hideReason.length > 0) {
  7052. if (hideReason !== 'hidden') {
  7053. nf_hidePost(post, hideReason, isSponsoredPost);
  7054. }
  7055. }
  7056. else {
  7057. // -- run pause animation (useful to hide those animated comments)
  7058. if (VARS.Options.PP_ANIMATED_GIFS_PAUSE) {
  7059. // console.info(log + 'pausing animations ...');
  7060. swatTheMosquitos(post);
  7061. }
  7062. // -- hide info boxes
  7063. if (VARS.hideAnInfoBox) {
  7064. scrubInfoBoxes(post);
  7065. }
  7066. }
  7067. }
  7068. mainColumn.setAttribute(mainColumnAtt, mainColumn.innerHTML.length.toString());
  7069. VARS.noChangeCounter = 0;
  7070. }
  7071.  
  7072. if (elDialog) {
  7073. // -- viewing a post in a "dialog-box" (triggered by viewing more comments)
  7074. if (VARS.Options.PP_ANIMATED_GIFS_PAUSE) {
  7075. swatTheMosquitos(elDialog);
  7076. }
  7077. elDialog.setAttribute(mainColumnAtt, elDialog.innerHTML.length.toString());
  7078. VARS.noChangeCounter = 0;
  7079. }
  7080.  
  7081. }
  7082.  
  7083. // **** TIMING COMPONENT ****
  7084.  
  7085. let firstRun = true;
  7086. let prevScrollY = window.scrollY;
  7087. let lastCleaningTime = 0;
  7088. let sleepDuration = 50;
  7089.  
  7090. function processPage(eventType = 'timing') {
  7091.  
  7092. // console.info(log + 'processPage(); (being) :: currentTime:', currentTime, '; lastCleaningTime:', lastCleaningTime, '; oldDuration:', oldDuration, '; sleepDuration:', sleepDuration, '; elapsedTime:', elapsedTime, '; counter: ', VARS.noChangeCounter);
  7093.  
  7094. const currentTime = new Date().getTime();
  7095. const elapsedTime = currentTime - lastCleaningTime;
  7096. const oldDuration = sleepDuration;
  7097.  
  7098. if (eventType == 'url-changed') {
  7099. // -- url has changed ...
  7100. setFeedSettings();
  7101. // console.info(`${log}processPage(); :: url has changed; isAF: ${VARS.isAF}; isNF: ${VARS.isNF}; isGF: ${VARS.isGF}; isVF: ${VARS.isVF}; isMF: ${VARS.isMF}; isSF: ${VARS.isSF}; isRF: ${VARS.isRF};`);
  7102. }
  7103. else if (eventType === 'scrolling') {
  7104. // -- page is scrolling ...
  7105. if (sleepDuration < 151) {
  7106. // -- .. and sleep duration is quite short ...
  7107. return;
  7108. }
  7109. // -- else: sleep duration is quite long, so trigger a clean up request now ...
  7110. }
  7111. else if (elapsedTime < sleepDuration) {
  7112. // -- called to early ...
  7113. // console.info(log + 'processPage(); (early) :: currentTime:', currentTime, '; lastCleaningTime:', lastCleaningTime, '; oldDuration:', oldDuration, '; sleepDuration:', sleepDuration, '; elapsedTime:', elapsedTime, '; counter: ', VARS.noChangeCounter);
  7114. return;
  7115. }
  7116.  
  7117. // -- do some mopping up ...
  7118. if (VARS.isNF) {
  7119. mopUpTheNewsFeed();
  7120. }
  7121. else if (VARS.isGF) {
  7122. mopUpTheGroupsFeed();
  7123. }
  7124. else if (VARS.isVF) {
  7125. mopUpTheWatchVideosFeed();
  7126. }
  7127. else if (VARS.isMF) {
  7128. mopUpTheMarketplaceFeed();
  7129. }
  7130. else if (VARS.isSF) {
  7131. mopUpTheSearchFeed();
  7132. }
  7133. else if (VARS.isRF) {
  7134. mopUpTheReelFeed('sleeping');
  7135. }
  7136. else if (VARS.isPP) {
  7137. mopUpTheProfilePage();
  7138. }
  7139.  
  7140. // -- sleep for a few ms and run me again ...
  7141. // -- noChangeCounter is recording the number of loops where no changes have been detected
  7142. if (VARS.isAF) {
  7143. if (VARS.noChangeCounter < 16) {
  7144. sleepDuration = 50;
  7145. }
  7146. else if (VARS.noChangeCounter < 31) {
  7147. sleepDuration = 75;
  7148. }
  7149. else if (VARS.noChangeCounter < 46) {
  7150. sleepDuration = 100;
  7151. }
  7152. else if (VARS.noChangeCounter < 61) {
  7153. sleepDuration = 150;
  7154. }
  7155. else {
  7156. sleepDuration = 1000;
  7157. }
  7158. }
  7159.  
  7160. // console.info(`${log}processPage(); > setFeedSettings() :: isAF: ${VARS.isAF}; isNF: ${VARS.isNF}; isGF: ${VARS.isGF}; isVF: ${VARS.isVF}; isMF: ${VARS.isMF}; isSF: ${VARS.isSF}; isRF: ${VARS.isRF};`);
  7161. // console.info(log + 'processPage(); 3 :: timing: ', timing, '; counter: ', VARS.noChangeCounter, '; isScrolling: ', isScrolling);
  7162.  
  7163. // -- have a nap and then scan again...
  7164. lastCleaningTime = currentTime;
  7165. // console.info(log + 'processPage(); (end) :: currentTime:', currentTime, '; lastCleaningTime:', lastCleaningTime, '; oldDuration:', oldDuration, '; sleepDuration:', sleepDuration, '; elapsedTime:', elapsedTime, '; counter: ', VARS.noChangeCounter);
  7166. setTimeout(processPage, sleepDuration);
  7167. }
  7168.  
  7169. function startUp() {
  7170. // -- run code soon as the elements HEAD, BDDY and variable Options are ready/available.
  7171. // -- or when page url has changed ...
  7172. if (document.head && document.body && VARS.optionsReady) {
  7173. if (firstRun) {
  7174. GM.registerMenuCommand(KeyWords.GM_MENU_SETTINGS, toggleDialog);
  7175. addCSS();
  7176. window.setTimeout(addExtraCSS, 150); // fb is sometimes laggy ...
  7177. buildMoppingDialog();
  7178. // -- for reels's controls - chromium browsers needs more spacing ...
  7179. // -- requires: @grant unsafeWindow
  7180. VARS.isChromium = !!unsafeWindow.chrome && /Chrome|CriOS/.test(navigator.userAgent);
  7181. // -- dictionary of words to use in filters.
  7182. buildDictionaries();
  7183. firstRun = false;
  7184. }
  7185.  
  7186. // -- add some event listeners to detect if something is being changed ...
  7187. window.addEventListener('scroll', function () {
  7188. // -- wakey wakey!
  7189. let currentScrollY = window.scrollY;
  7190. let scrollingDistance = Math.abs(currentScrollY - prevScrollY);
  7191. if (scrollingDistance > 20) {
  7192. processPage('scrolling');
  7193. }
  7194. });
  7195.  
  7196. window.addEventListener('popstate', function () {
  7197. // -- closed a fb "dialog" (e.g. popout-reel from a news-feed)
  7198. // -- fb mimics the back-button action
  7199. processPage('url-changed');
  7200. });
  7201.  
  7202.  
  7203. let timerId = setInterval(function () {
  7204. // -- watch the url-change (new page being loaded)
  7205. if (VARS.prevURL !== window.location.href) {
  7206. // console.info(log + 'url-changed:', VARS.prevURL, window.location.href);
  7207. processPage('url-changed');
  7208. }
  7209. }, 500);
  7210.  
  7211. // -- start scanning
  7212. // -- (processPage will call setFeedSettings ..)
  7213. processPage('url-changed');
  7214. }
  7215. else {
  7216. setTimeout(startUp, 10);
  7217. }
  7218. }
  7219.  
  7220. function handleClassListChange() {
  7221. let modeHasChanged = false;
  7222. let modeNow = isDarkMode();
  7223. if (VARS.isDarkMode === null) {
  7224. modeHasChanged = true;
  7225. }
  7226. else if (VARS.isDarkMode !== modeNow) {
  7227. modeHasChanged = true;
  7228. }
  7229.  
  7230. if (modeHasChanged) {
  7231. // -- update CSS
  7232.  
  7233. VARS.isDarkMode = modeNow;
  7234. }
  7235. }
  7236.  
  7237. // Initial check
  7238. handleClassListChange();
  7239.  
  7240. // -- Create an observer instance
  7241. const observer = new MutationObserver((mutationsList) => {
  7242. for (let mutation of mutationsList) {
  7243. if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
  7244. handleClassListChange();
  7245. }
  7246. }
  7247. });
  7248.  
  7249. // -- Start observing the target node for configured mutations
  7250. observer.observe(document.documentElement, {
  7251. attributes: true, // Observe changes to attributes
  7252. attributeFilter: ['class'] // Only observe changes to the 'class' attribute
  7253. });
  7254.  
  7255. // setTimeout(startUp, 50);
  7256. startUp();
  7257.  
  7258. })();