Greasy Fork镜像 支持简体中文。

FB - Clean my feeds

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

目前為 2022-10-02 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name FB - Clean my feeds
  3. // @description Hide Sponsored and Suggested posts in FB's News Feed, Groups Feed, Watch Videos Feed and Marketplace Feed
  4. // @namespace https://gf.qytechs.cn/users/812551
  5. // @supportURL https://github.com/zbluebugz/facebook-clean-my-feeds/issues
  6. // @version 4.03
  7. // @author zbluebugz (https://github.com/zbluebugz/)
  8. // @require https://unpkg.com/idb-keyval@6.0.3/dist/umd.js
  9. // @match https://*.facebook.com/*
  10. // @grant none
  11. // @license MIT; https://opensource.org/licenses/MIT
  12. // @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==
  13. // @run-at document-start
  14. // ==/UserScript==
  15. /*
  16. v4.03 :: October 2022
  17. Updated news feed selection code
  18. v4.02 :: September 2022
  19. Removed invalid UserScript tag
  20. v4.01 :: September 2022
  21. Major rewrite - less dependent on language text for matching component/posts
  22. Various Suggestions/Recommendations combined into one option
  23. Added 簡體中文 (Chinese Simplified)
  24. Added 中國傳統的 (Chinese Traditional)
  25. Added 日本 (Japan)
  26. Added Sumoi (Finland)
  27. Added Türkçe (Turkey)
  28. v3.28 :: September 2022
  29. Code tweaks
  30. v3.27 :: August 2022
  31. Keywords and code tweaks
  32.  
  33. Attribution: Mop & bucket icon:
  34. - made by Freepik (https://www.freepik.com) @ flaticon (https://www.flaticon.com/)
  35. - page: https://www.flaticon.com/premium-icon/mop_2383747
  36.  
  37.  
  38. To do :::
  39. - investigate Private/Icognito/InPrivate Mode (idb doesn't work in Firefox's private mode)
  40. - review events in news feed
  41.  
  42. Instructions on how to use:
  43. - In FB, top right corner or bottom left corner, click on the "Clean my feeds" icon (mop + bucket)
  44. - Toggle the various options
  45. - Click Save then Close.
  46. - It is recommended that you Export your settings every now and then.
  47. (When your browser flushes the cache, your settings are deleted).
  48.  
  49.  
  50. \\\ --- No need to amend any of the code below --- ///
  51. */
  52.  
  53. (async function () {
  54.  
  55. 'use strict';
  56.  
  57. // *** *** Language components *** ***
  58. const KeyWords = {
  59. // *** Which languages have been setup (for dialog box)
  60. // -- not used as keywords for detection of anything
  61.  
  62. // - 'en' is default.
  63. LANGUAGES: [
  64. 'en', // English
  65. 'pt', // Português (Portugal and Brazil)
  66. 'de', // Deutsch (Germany)
  67. 'fr', // Français (France)
  68. 'es', // Espanol (Spain)
  69. 'cs', // Čeština (Czechia)
  70. 'vi', // Tiếng Việt (Vietnam)
  71. 'it', // Italino (Italy)
  72. 'lv', // Latviešu (Latvia)
  73. 'pl', // Polski (Poland)
  74. 'nl', // Nederlands (Netherlands)
  75. 'he', // עִברִית (Hebrew)
  76. 'ar', // العربية (Arabic)
  77. 'id', // Bahasa Indonesia (Indonesia)
  78. 'zh-Hans', // Chinese (Simplified)
  79. 'zh-Hant', // Chinese (Traditional)
  80. 'ja', // Japanese (Japan)
  81. 'fi', // Suomi - Finnish (Finland)
  82. 'tr', // Türkçe (Turkey)
  83. ],
  84.  
  85. // - Sponsored
  86. SPONSORED: {
  87. 'en': 'Sponsored',
  88. 'pt': 'Patrocinado',
  89. 'de': 'Gesponsert',
  90. 'fr': 'Sponsorisé',
  91. 'es': 'Publicidad',
  92. 'cs': 'Sponzorováno',
  93. 'vi': 'Được tài trợ',
  94. 'it': 'Sponsorizzato',
  95. 'lv': 'Apmaksāta reklāma',
  96. 'pl': 'Sponsorowane',
  97. 'nl': 'Gesponsord',
  98. 'he': 'ממומן',
  99. 'ar': 'مُموَّل',
  100. 'id': 'Bersponsor',
  101. 'zh-Hans': '赞助内容',
  102. 'zh-Hant': '贊助',
  103. 'ja': '広告',
  104. 'fi': 'Sponsoroitu',
  105. 'tr': 'Sponsorlu',
  106. },
  107.  
  108. // *** News Feed ::
  109.  
  110. // - "Stories | Reels | Rooms" tablist box
  111. // - must have the "Stories | Reels | Rooms" pattern (including double quotes)
  112. // -- those words must match fb's words.
  113. NF_TABLIST_STORIES_REELS_ROOMS: {
  114. 'en': '"Stories | Reels | Rooms" tabs list box',
  115. 'pt': 'Caixa de listagem da guia "Stories | Vídeos do Reels | Salas"',
  116. 'de': 'Listenfeld der Registerkarte "Stories | Reels | Rooms"',
  117. 'fr': 'Zone de liste de l\'onglet "Stories | Reels | Salons"',
  118. 'es': 'Cuadro de lista de la pestaña "Historias | Reels | Salas"',
  119. 'cs': 'Seznam karet "Stories | Reels | Místnosti"',
  120. 'vi': 'Hộp danh sách tab "Tin | Reels | Phòng họp mặt"',
  121. 'it': 'Casella di riepilogo della scheda "Storie | Reels | Stanze"',
  122. 'lv': 'Cilnes "Stāsti | Video rullīši | Rooms" sarakstlodziņš',
  123. 'pl': 'Pole listy zakładki "Relacje | Reels | Pokoje"',
  124. 'nl': 'Keuzelijst tabblad "Verhalen | Reels | Ruimtes"',
  125. 'he': 'תיבת רשימה של כרטיסיות "סטוריז | Reels | חדרים"',
  126. 'ar': '"القصص | ريلز | الغرف" مربع قائمة علامات تبويب',
  127. 'id': 'Kotak daftar tab "Cerita | Reels | Forum"',
  128. 'zh-Hans': '“快拍|Reels|畅聊室”选项卡列表框',
  129. 'zh-Hant': '"限時動態|Reels|包廂"选项卡列表框',
  130. 'ja': '「Stories | Reels | Rooms」タブのリストボックス',
  131. 'fi': '"Tarinat | Reels | Rooms" -välilehtien luetteloruutu',
  132. 'tr': '"Hikayeler | Makaralar | Odalar" sekmeleri liste kutusu',
  133. 'defaultEnabled': false
  134. },
  135.  
  136. // - People you may know:
  137. NF_PEOPLE_YOU_MAY_KNOW: {
  138. 'en': 'People you may know',
  139. 'pt': 'Pessoas que talvez conheças',
  140. 'de': 'Personen, die du kennen könntest',
  141. 'fr': 'Connaissez-vous...',
  142. 'es': 'Personas que quizá conozcas',
  143. 'cs': 'Koho možná znáte',
  144. 'vi': 'Những người bạn có thể biết',
  145. 'it': 'Persone che potresti conoscere',
  146. 'lv': 'Cilvēki, kurus tu varētu pazīt',
  147. 'pl': 'Osoby, które możesz znać',
  148. 'nl': 'Mensen die je misschien kent',
  149. 'he': 'אנשים שאולי אתה מכיר',
  150. 'ar': 'أشخاص قد تعرفهم',
  151. 'id': 'Orang yang Mungkin Anda Kenal',
  152. 'zh-Hans': '你可能认识的人',
  153. 'zh-Hant': '你可能認識的人',
  154. 'ja': 'あなたが知っているかもしれない人々',
  155. 'fi': 'Ihmiset, jotka saatat tuntea',
  156. 'tr': 'Tanıyor olabileceğin kişiler',
  157. 'defaultEnabled': false
  158. },
  159.  
  160. // - Paid partnership:
  161. // - page you follow is "sponsoring" another page's post (e.g. job)
  162. NF_PAID_PARTNERSHIP: {
  163. 'en': 'Paid partnership',
  164. 'pt': 'Parceria paga',
  165. 'de': 'Bezahlte Werbepartnerschaft',
  166. 'fr': 'Partenariat rémunéré',
  167. 'es': 'Colaboración pagada',
  168. 'cs': 'Placené partnerství',
  169. 'vi': 'Mối quan hệ tài trợ',
  170. 'it': 'Partnership pubblicizzata',
  171. 'lv': 'Apmaksāta sadarbība',
  172. 'pl': 'Post sponsorowany',
  173. 'nl': 'Betaald partnerschap',
  174. 'he': 'שותפות בתשלום',
  175. 'ar': 'شراكة مدفوعة',
  176. 'id': 'Kemitraan berbayar',
  177. 'zh-Hans': '付费合伙',
  178. 'zh-Hant': '付费合伙',
  179. 'ja': '有償パートナーシップ',
  180. 'fi': 'Maksettu kumppanuus',
  181. 'tr': 'ücretli ortaklık',
  182. 'defaultEnabled': true
  183. },
  184.  
  185. // - Sponsored · Paid for by ______ :
  186. // - (paid advertising)
  187. NF_SPONSORED_PAID: {
  188. 'en': 'Sponsored · Paid for by ______',
  189. 'pt': 'Patrocinado · Financiado por ______',
  190. 'de': 'Gesponsert · Finanziert von ______',
  191. 'fr': 'Sponsorisé · Financé par ______',
  192. 'es': 'Publicidad · Pagado por ______',
  193. 'cs': 'Sponzorováno · Platí za to ______',
  194. 'vi': 'Được tài trợ · Tài trợ bởi ______',
  195. 'it': 'Sponsorizzato · Finanziato da ______',
  196. 'lv': 'Apmaksāta reklāma · Apmaksā ______',
  197. 'pl': 'Sponsorowane · Opłacona przez ______',
  198. 'nl': 'Gesponsord · Betaald door ______',
  199. 'he': 'ממומן · שולם על ידי ______',
  200. 'ar': 'برعاية · مدفوعة بواسطة ______',
  201. 'id': 'Disponsori · Dibayar oleh ______',
  202. 'zh-Hans': '赞助 · 由 ______ 付费',
  203. 'zh-Hant': '赞助 · 由 ______ 付费',
  204. 'ja': '後援 · ______ による支払い',
  205. 'fi': 'Sponsoroitu · Maksaja ______',
  206. 'tr': 'Sponsorlu · ______ tarafından ödendi',
  207. 'defaultEnabled': true
  208. },
  209.  
  210. // - Various Suggested/recommendations type posts
  211. NF_SUGGESTIONS: {
  212. 'en': 'Suggestions / Recommendations',
  213. 'pt': 'Sugestões / Recomendações',
  214. 'de': 'Vorschläge / Empfehlungen',
  215. 'fr': 'Suggestions / Recommandations',
  216. 'es': 'Sugerencias / Recomendaciones',
  217. 'cs': 'Návrhy / Doporučení',
  218. 'vi': 'Đề xuất / Khuyến nghị',
  219. 'it': 'Suggerimenti / Raccomandazioni',
  220. 'lv': 'Ieteikumi',
  221. 'pl': 'Sugestie / Zalecenia',
  222. 'nl': 'Suggesties / Aanbevelingen',
  223. 'he': 'הצעות / המלצות',
  224. 'ar': 'الاقتراحات / التوصيات',
  225. 'id': 'Saran / Rekomendasi',
  226. 'zh-Hans': '建议',
  227. 'zh-Hant': '建議',
  228. 'ja': '提案/推奨事項',
  229. 'fi': 'Ehdotuksia / Suosituksia',
  230. 'tr': 'Öneriler',
  231. 'defaultEnabled': false
  232. },
  233.  
  234. // - Reels and short videos:
  235. NF_REELS_SHORT_VIDEOS: {
  236. 'en': 'Reels and short videos',
  237. 'pt': 'Vídeos do Reels e vídeos de curta duração',
  238. 'de': 'Reels und Kurzvideos',
  239. 'fr': 'Reels et vidéos courtes',
  240. 'es': 'Reels y vídeos cortos',
  241. 'cs': 'Sekvence a krátká videa',
  242. 'vi': 'Reels và video ngắn',
  243. 'it': 'Reel e video brevi',
  244. 'lv': 'Reels un īsi videoklipi',
  245. 'pl': 'Rolki i krótkie filmy',
  246. 'nl': 'Reels en korte video\'s',
  247. 'he': 'סרטוני Reels וקטעי וידאו קצרים',
  248. 'ar': 'ريلز ومقاطع الفيديو القصيرة',
  249. 'id': 'Reels dan Video Pendek',
  250. 'zh-Hans': '卷轴和短视频',
  251. 'zh-Hant': '卷轴和短视频',
  252. 'ja': 'リールとショート動画',
  253. 'fi': 'Keloja ja lyhyitä videoita',
  254. 'tr': 'Makaralar ve kısa videolar',
  255. 'defaultEnabled': false
  256. },
  257.  
  258. // - Reel/short video posts
  259. NF_SHORT_REEL_VIDEO: {
  260. 'en': 'Reel/short video',
  261. 'pt': 'Rolo/vídeo curto',
  262. 'de': 'Reel/kurzes Video',
  263. 'fr': 'Bobine/courte vidéo',
  264. 'es': 'Reel/video corto',
  265. 'cs': 'Naviják/krátké video',
  266. 'vi': 'Reel / video ngắn',
  267. 'it': 'Bobina/breve video',
  268. 'lv': 'Ruļļa/īss video',
  269. 'pl': 'Reel/krótki film',
  270. 'nl': 'Spoel/korte video',
  271. 'he': 'סליל/סרטון קצר',
  272. 'ar': 'بكرة / فيديو قصير',
  273. 'id': 'Reel/video pendek',
  274. 'zh-Hans': '卷轴/短视频',
  275. 'zh-Hant': '捲軸/短視頻',
  276. 'ja': 'リール/ショートビデオ',
  277. 'fi': 'Kela/lyhyt video',
  278. 'tr': 'makara/kısa video',
  279. 'defaultEnabled': false
  280. },
  281.  
  282. // - pause animated GIFs:
  283. NF_ANIMATED_GIFS: {
  284. 'en': 'Pause animated GIFs',
  285. 'pt': 'Pausar GIFs animados',
  286. 'de': 'Animierte GIFs pausieren',
  287. 'fr': 'Mettre en pause les GIF animés',
  288. 'es': 'Pausar GIF animados',
  289. 'cs': 'Pozastavit animované GIFy',
  290. 'vi': 'Tạm dừng các ảnh GIF động',
  291. 'it': 'Metti in pausa le GIF animate',
  292. 'lv': 'Apturiet animētos GIF',
  293. 'pl': 'Wstrzymaj animowane GIF-y',
  294. 'nl': 'Geanimeerde GIF\'s pauzeren',
  295. 'he': 'השהה קובצי GIF מונפשים',
  296. 'ar': 'وقفة GIF المتحركة',
  297. 'id': 'Jeda GIF animasi',
  298. 'zh-Hans': '暂停动画 GIF',
  299. 'zh-Hant': '暫停動畫 GIF',
  300. 'ja': 'アニメーション GIF を一時停止する',
  301. 'fi': 'Keskeytä animoidut GIF-kuvat',
  302. 'tr': 'Hareketli GIF\'leri duraklat',
  303. 'defaultEnabled': false
  304. },
  305.  
  306.  
  307. // *** Groups Feed ::
  308.  
  309. // - Paid partnership:
  310. // - a page you follow is "sponsoring" another page's post (e.g. job)
  311. GF_PAID_PARTNERSHIP: {
  312. 'en': 'Paid partnership',
  313. 'pt': 'Parceria paga',
  314. 'de': 'Bezahlte Werbepartnerschaft',
  315. 'fr': 'Partenariat rémunéré',
  316. 'es': 'Colaboración pagada',
  317. 'cs': 'Placené partnerství',
  318. 'vi': 'Mối quan hệ tài trợ',
  319. 'it': 'Partnership pubblicizzata',
  320. 'lv': 'Apmaksāta sadarbība',
  321. 'pl': 'Post sponsorowany',
  322. 'nl': 'Betaald partnerschap',
  323. 'he': 'שותפות בתשלום',
  324. 'ar': 'شراكة مدفوعة',
  325. 'id': 'Kemitraan berbayar',
  326. 'zh-Hans': '有偿合作',
  327. 'zh-Hant': '付费合伙',
  328. 'ja': '有償パートナーシップ',
  329. 'fi': 'Maksettu kumppanuus',
  330. 'tr': 'ücretli ortaklık',
  331. 'defaultEnabled': true
  332. },
  333.  
  334. // - Various suggested/recommendations:
  335. GF_SUGGESTIONS: {
  336. 'en': 'Suggestions / Recommendations',
  337. 'pt': 'Sugestões / Recomendações',
  338. 'de': 'Vorschläge / Empfehlungen',
  339. 'fr': 'Suggestions / Recommandations',
  340. 'es': 'Sugerencias / Recomendaciones',
  341. 'cs': 'Návrhy / Doporučení',
  342. 'vi': 'Đề xuất / Khuyến nghị',
  343. 'it': 'Suggerimenti / Raccomandazioni',
  344. 'lv': 'Ieteikumi',
  345. 'pl': 'Sugestie / Zalecenia',
  346. 'nl': 'Suggesties / Aanbevelingen',
  347. 'he': 'הצעות / המלצות',
  348. 'ar': 'الاقتراحات / التوصيات',
  349. 'id': 'Saran / Rekomendasi',
  350. 'zh-Hans': '建议/建议',
  351. 'zh-Hant': '建議',
  352. 'ja': '提案/推奨事項',
  353. 'fi': 'Ehdotuksia / Suosituksia',
  354. 'tr': 'Öneriler',
  355. 'defaultEnabled': false
  356. },
  357.  
  358. // - Reel/short video posts
  359. GF_SHORT_REEL_VIDEO: {
  360. 'en': 'Reel/short video',
  361. 'pt': 'Rolo/vídeo curto',
  362. 'de': 'Reel/kurzes Video',
  363. 'fr': 'Bobine/courte vidéo',
  364. 'es': 'Reel/video corto',
  365. 'cs': 'Naviják/krátké video',
  366. 'vi': 'Reel / video ngắn',
  367. 'it': 'Bobina/breve video',
  368. 'lv': 'Ruļļa/īss video',
  369. 'pl': 'Reel/krótki film',
  370. 'nl': 'Spoel/korte video',
  371. 'he': 'סליל/סרטון קצר',
  372. 'ar': 'بكرة / فيديو قصير',
  373. 'id': 'Reel/video pendek',
  374. 'zh-Hans': '卷轴和短视频',
  375. 'zh-Hant': '卷轴和短视频',
  376. 'ja': 'リールとショートビデオ',
  377. 'fi': 'Keloja ja lyhyitä videoita',
  378. 'tr': 'makara/kısa video',
  379. 'defaultEnabled': false
  380. },
  381.  
  382. // - pause animated GIFs:
  383. GF_ANIMATED_GIFS: {
  384. 'en': 'Pause animated GIFs',
  385. 'pt': 'Pausar GIFs animados',
  386. 'de': 'Animierte GIFs pausieren',
  387. 'fr': 'Mettre en pause les GIF animés',
  388. 'es': 'Pausar GIF animados',
  389. 'cs': 'Pozastavit animované GIFy',
  390. 'vi': 'Tạm dừng các ảnh GIF động',
  391. 'it': 'Metti in pausa le GIF animate',
  392. 'lv': 'Apturiet animētos GIF',
  393. 'pl': 'Wstrzymaj animowane GIF-y',
  394. 'nl': 'Geanimeerde GIF\'s pauzeren',
  395. 'he': 'השהה קובצי GIF מונפשים',
  396. 'ar': 'وقفة GIF المتحركة',
  397. 'id': 'Jeda GIF animasi',
  398. 'zh-Hans': '暂停动画 GIF',
  399. 'zh-Hant': '暫停動畫 GIF',
  400. 'ja': 'リール/ショートビデオ',
  401. 'fi': 'Kela/lyhyt video',
  402. 'tr': 'Hareketli GIF\'leri duraklat',
  403. 'defaultEnabled': false,
  404. },
  405.  
  406. // *** Watch Videos Feed ::
  407.  
  408. // - pause animated GIFs:
  409. VF_ANIMATED_GIFS: {
  410. 'en': 'Pause animated GIFs',
  411. 'pt': 'Pausar GIFs animados',
  412. 'de': 'Animierte GIFs pausieren',
  413. 'fr': 'Mettre en pause les GIF animés',
  414. 'es': 'Pausar GIF animados',
  415. 'cs': 'Pozastavit animované GIFy',
  416. 'vi': 'Tạm dừng các ảnh GIF động',
  417. 'it': 'Metti in pausa le GIF animate',
  418. 'lv': 'Apturiet animētos GIF',
  419. 'pl': 'Wstrzymaj animowane GIF-y',
  420. 'nl': 'Geanimeerde GIF\'s pauzeren',
  421. 'he': 'השהה קובצי GIF מונפשים',
  422. 'ar': 'وقفة GIF المتحركة',
  423. 'id': 'Jeda GIF animasi',
  424. 'zh-Hans': '暂停动画 GIF',
  425. 'zh-Hant': '暫停動畫 GIF',
  426. 'ja': 'アニメーション GIF を一時停止する',
  427. 'fi': 'Keskeytä animoidut GIF-kuvat',
  428. 'tr': 'Hareketli GIF\'leri duraklat',
  429. 'defaultEnabled': false,
  430. },
  431.  
  432. // *** Miscellaneous/Other items ::
  433.  
  434. // - Info box - coronavirus:
  435. OTHER_INFO_BOX_CORONAVIRUS: {
  436. 'en': 'Coronavirus (information box)',
  437. 'pt': 'Coronavírus (caixa de informações)',
  438. 'de': 'Coronavirus (Infobox)',
  439. 'fr': 'Coronavirus (encadré d\'information)',
  440. 'es': 'Coronavirus (cuadro de información)',
  441. 'cs': 'Coronavirus (informační box)',
  442. 'vi': 'Virus corona (hộp thông tin)',
  443. 'it': 'Coronavirus (casella informativa)',
  444. 'lv': 'Koronavīruss (informācijas lodziņš)',
  445. 'pl': 'Koronawirus (skrzynka informacyjna)',
  446. 'nl': 'Coronavirus (informatiebox)',
  447. 'he': 'וירוס קורונה (תיבת מידע)',
  448. 'ar': 'فيروس كورونا (صندوق المعلومات)',
  449. 'id': 'Virus Corona (kotak informasi)',
  450. 'zh-Hans': '冠状病毒(信息框)',
  451. 'zh-Hant': '冠狀病毒(信息框)',
  452. 'ja': 'コロナウイルス(インフォメーションボックス)',
  453. 'fi': 'Koronavirus (tietolaatikko)',
  454. 'tr': 'Koronavirüs (bilgi kutusu)',
  455. 'defaultEnabled': false,
  456. 'pathMatch': '/coronavirus_info/',
  457. },
  458.  
  459. // - Info box - climate science
  460. OTHER_INFO_BOX_CLIMATE_SCIENCE: {
  461. 'en': 'Climate Science (information box)',
  462. 'pt': 'Ciência do Clima (caixa de informações)',
  463. 'de': 'Klimawissenschaft (Infobox)',
  464. 'fr': 'Science du climat (encadré d\'information)',
  465. 'es': 'Ciencia del clima (cuadro de información)',
  466. 'cs': 'Klimatická věda (informační box)',
  467. 'vi': 'Khoa học khí hậu (hộp thông tin)',
  468. 'it': 'Scienza del clima (casella informativa)',
  469. 'lv': 'Klimata zinātne (informācijas lodziņš)',
  470. 'pl': 'Nauka o klimacie (skrzynka informacyjna)',
  471. 'nl': 'Klimaatwetenschap (informatiebox)',
  472. 'he': 'מדע האקלים (תיבת מידע)',
  473. 'ar': 'علوم المناخ (صندوق المعلومات)',
  474. 'id': 'Ilmu iklim (kotak informasi)',
  475. 'zh-Hans': '气候科学(信息框)',
  476. 'zh-Hant': '氣候科學(信息框)',
  477. 'ja': '気候科学(情報ボックス)',
  478. 'fi': 'Ilmastotiede (tietolaatikko)',
  479. 'tr': 'İklim Bilimi (bilgi kutusu)',
  480. 'defaultEnabled': false,
  481. 'pathMatch': '/climatescienceinfo/',
  482. },
  483.  
  484. // - Info box - subscribe
  485. OTHER_INFO_BOX_SUBSCRIBE: {
  486. 'en': 'Subscribe (information box)',
  487. 'pt': 'Assine (caixa de informações)',
  488. 'de': 'Abonnieren (Infobox)',
  489. 'fr': 'S’abonner (encadré d\'information)',
  490. 'es': 'Suscribir (cuadro de información)',
  491. 'cs': 'Odebírat (informační box)',
  492. 'vi': 'Đăng kí (hộp thông tin)',
  493. 'it': 'Iscriviti (casella informativa)',
  494. 'lv': 'Abonēt (informācijas lodziņš)',
  495. 'pl': 'Subskrybuj (pole informacyjne)',
  496. 'nl': 'Abonneren (informatievak)',
  497. 'he': 'הירשם (תיבת מידע)',
  498. 'ar': '(صندوق المعلومات) الاشتراك',
  499. 'id': 'Berlangganan (kotak informasi)',
  500. 'zh-Hans': '订阅(信息框)',
  501. 'zh-Hant': '訂閱(信息框)',
  502. 'ja': '購読する(情報ボックス)',
  503. 'fi': 'Rekisteröidy (tietolaatikko)',
  504. 'tr': 'Abone ol (bilgi kutusu)',
  505. 'defaultEnabled': false,
  506. 'pathMatch': '/support/',
  507. },
  508.  
  509. // *** Dialog box ::
  510.  
  511. // - CMF's dialog title:
  512. DLG_TITLE: {
  513. 'en': 'Clean my feeds',
  514. 'pt': 'Limpe meus feeds',
  515. 'de': 'Bereinige meine Feeds',
  516. 'fr': 'Nettoyer mes flux',
  517. 'es': 'Limpia mis feeds',
  518. 'cs': 'Vyčistěte mé kanály',
  519. 'vi': 'Làm sạch nguồn cấp dữ liệu của tôi',
  520. 'it': 'Pulisci i miei feed',
  521. 'lv': 'Tīrīt manas plūsmas',
  522. 'pl': 'Wyczyść moje kanały',
  523. 'nl': 'Schoon mijn feeds',
  524. 'he': 'תנקה את הזנות שלי',
  525. 'ar': 'تنظيف خلاصاتي',
  526. 'id': 'Bersihkan feed saya',
  527. 'zh-Hans': '清理我的提要',
  528. 'zh-Hant': '清理我的提要',
  529. 'ja': 'フィードをクリーンアップ',
  530. 'fi': 'Puhdista syötteeni',
  531. 'tr': 'Feed\'lerimi temizle',
  532. },
  533.  
  534. // - label for News Feed:
  535. DLG_NF: {
  536. 'en': 'News Feed',
  537. 'pt': 'Feed de notícias',
  538. 'de': 'Newsfeed',
  539. 'fr': 'Fil de nouvelles',
  540. 'es': 'Feed de noticias',
  541. 'cs': 'Informační kanál',
  542. 'vi': 'Nguồn cấp tin tức',
  543. 'it': 'Feed di notizie', // news section
  544. 'lv': 'Ziņu plūsma',
  545. 'pl': 'Kanał aktualności',
  546. 'nl': 'Nieuwsfeed',
  547. 'he': 'ניוז פיד',
  548. 'ar': 'الأخبار تغذية',
  549. 'id': 'Umpan Berita',
  550. 'zh-Hans': '新闻提要',
  551. 'zh-Hant': '新聞提要',
  552. 'ja': 'ニュースフィード',
  553. 'fi': 'Uutisvirta',
  554. 'tr': 'Haber akışı',
  555. },
  556.  
  557. // - label for Groups Feed:
  558. DLG_GF: {
  559. 'en': 'Groups Feed',
  560. 'pt': 'Feed de grupos',
  561. 'de': 'Gruppen-Feed',
  562. 'fr': 'Flux de groupes',
  563. 'es': 'Feed de grupos',
  564. 'cs': 'Skupinový kanál',
  565. 'vi': 'Nguồn cấp dữ liệu Nhóm',
  566. 'it': 'Feed di gruppo',
  567. 'lv': 'Grupu plūsma',
  568. 'pl': 'Kanał grup',
  569. 'nl': 'Groepsfeed',
  570. 'he': 'פיד קבוצות',
  571. 'ar': 'مجموعات تغذية',
  572. 'id': 'Umpan Grup',
  573. 'zh-Hans': '群组提要',
  574. 'zh-Hant': '群組供稿',
  575. 'ja': 'グループ フィード',
  576. 'fi': 'Ryhmäsyöte',
  577. 'tr': 'Gruplar Feed\'i',
  578. },
  579.  
  580. // - label for Videos Feed:
  581. DLG_VF: {
  582. 'en': 'Videos Feed',
  583. 'pt': 'Feed de vídeos',
  584. 'de': 'Video-Feed',
  585. 'fr': 'Flux de vidéos',
  586. 'es': 'Feed de vídeos',
  587. 'cs': 'Video kanál',
  588. 'vi': 'Nguồn cấp dữ liệu video',
  589. 'it': 'Feed di video',
  590. 'lv': 'Video plūsma',
  591. 'pl': 'Kanał wideo',
  592. 'nl': 'Videofeed',
  593. 'he': 'צפה בפיד הסרטונים',
  594. 'ar': 'الفيديو تغذية',
  595. 'id': 'Umpan Video',
  596. 'zh-Hans': '视频提要',
  597. 'zh-Hant': '視頻提要',
  598. 'ja': '動画フィード',
  599. 'fi': 'Videosyöte',
  600. 'tr': 'Video Beslemelerini İzle',
  601. },
  602.  
  603. // - label for Marketplace Feed:
  604. DLG_MP: {
  605. 'en': 'Marketplace Feed',
  606. 'pt': 'Feed de mercado',
  607. 'de': 'Marktplatz-Feed',
  608. 'fr': 'Flux de la place de marché',
  609. 'es': 'Feed de Marketplace',
  610. 'cs': 'Marketplace kanál',
  611. 'vi': 'Nguồn cấp dữ liệu Marketplace',
  612. 'it': 'Feed id Marketplace',
  613. 'lv': 'Marketplace',
  614. 'pl': 'Kanał Marketplace',
  615. 'nl': 'Marktplaatsfeed',
  616. 'he': 'זירת מסחר',
  617. 'ar': 'السوق تغذية',
  618. 'id': 'Umpan Marketplace',
  619. 'zh-Hans': '市场提要',
  620. 'zh-Hant': '市場供稿',
  621. 'ja': 'マーケットプレイス フィード',
  622. 'fi': 'Marketplace-syöte',
  623. 'tr': 'Pazar Yeri Feed\'i',
  624. },
  625.  
  626. // - label for Miscellaneous/Other items:
  627. DLG_OTHER: {
  628. 'en': 'Miscellaneous items',
  629. 'pt': 'Itens miscelâneos',
  630. 'de': 'Sonstige Gegenstände',
  631. 'fr': 'Articles divers',
  632. 'es': 'Artículos diversos',
  633. 'cs': 'Různé položky',
  634. 'vi': 'Những thứ linh tinh',
  635. 'it': 'Articoli vari',
  636. 'lv': 'Dažādas vienumus',
  637. 'pl': 'Różne pozycje',
  638. 'nl': 'Diversen',
  639. 'he': 'פריטים שונים',
  640. 'ar': 'عناصر متنوعة',
  641. 'id': 'Barang lain-lain',
  642. 'zh-Hans': '杂件',
  643. 'zh-Hant': '雜件',
  644. 'ja': 'その他のアイテム',
  645. 'fi': 'Sekalaiset tavarat',
  646. 'tr': 'Diğer öğeler',
  647. },
  648.  
  649. // - text filter for News Feed:
  650. DLG_NF_BLOCK: {
  651. 'en': 'News Feed - text filter',
  652. 'pt': 'Feed de notícias - filtro de texto',
  653. 'de': 'Newsfeed - Textfilter',
  654. 'fr': 'Fil de nouvelles - filtre de texte',
  655. 'es': 'Feed de noticias: filtro de texto',
  656. 'cs': 'Informační kanál - textový filtr',
  657. 'vi': 'Nguồn cấp tin tức - bộ lọc văn bản',
  658. 'it': 'Feed di notizie - filtro di testo',
  659. 'lv': 'Ziņu plūsma - teksta filtrs',
  660. 'pl': 'Kanał aktualności - filtr tekstu',
  661. 'nl': 'Nieuwsfeed - tekstfilter',
  662. 'he': 'מסנן טקסט - ניוז פיד',
  663. 'ar': 'موجز الأخبار - مرشح النص',
  664. 'id': 'Umpan Berita - filter teks',
  665. 'zh-Hans': '新闻提要 - 文本过滤器',
  666. 'zh-Hant': '新聞提要 - 文本過濾器',
  667. 'ja': 'ニュースフィード - テキストフィルター',
  668. 'fi': 'Uutissyöte - tekstisuodatin',
  669. 'tr': 'Haber akışı - metin filtresi',
  670. 'defaultEnabled': false,
  671. },
  672.  
  673. // - text filter for Groups Feed:
  674. DLG_GF_BLOCK: {
  675. 'en': 'Groups Feed - text filter',
  676. 'pt': 'Feed de grupos - filtro de texto',
  677. 'de': 'Gruppen-Feed - Textfilter',
  678. 'fr': 'Flux de groupes - filtre de texte',
  679. 'es': 'Feed de grupos: filtro de texto',
  680. 'cs': 'Skupinový kanál - textový filtr',
  681. 'vi': 'Nguồn cấp dữ liệu Nhóm - bộ lọc văn bản',
  682. 'it': 'Feed di gruppo - filtro di testo',
  683. 'lv': 'Grupu plūsma - teksta filtrs',
  684. 'pl': 'Kanał grup - filtr tekstu',
  685. 'nl': 'Groepsfeed - tekstfilter',
  686. 'he': 'פיד קבוצות - מסנן טקסט',
  687. 'ar': 'تغذية المجموعات - مرشح النص',
  688. 'id': 'Umpan Grup - filter teks',
  689. 'zh-Hans': '组提要 - 文本过滤器',
  690. 'zh-Hant': '組提要 - 文本過濾器',
  691. 'ja': 'グループ フィード - テキスト フィルタ',
  692. 'fi': 'Ryhmäsyöte - tekstisuodatin',
  693. 'tr': 'Gruplar Feed\'i - metin filtresi',
  694. 'defaultEnabled': false,
  695. },
  696.  
  697. // - text filter for Vidoes Feed:
  698. DLG_VF_BLOCK: {
  699. 'en': 'Videos Feed - text filter',
  700. 'pt': 'Feed de vídeos - filtro de texto',
  701. 'de': 'Video-Feed - Textfilter',
  702. 'fr': 'Flux de vidéos - filtre de texte',
  703. 'es': 'Feed de videos: filtro de texto',
  704. 'cs': 'Video kanál - textový filtr',
  705. 'vi': 'Nguồn cấp dữ liệu video - bộ lọc văn bản',
  706. 'it': 'Feed di video - filtro di testo',
  707. 'lv': 'Video plūsma - teksta filtrs',
  708. 'pl': 'Kanał wideo - filtr tekstu',
  709. 'nl': 'Videofeed - tekstfilter',
  710. 'he': 'צפה בפיד סרטונים - מסנן טקסט',
  711. 'ar': 'تغذية الفيديو - مرشح النص',
  712. 'id': 'Umpan Video - filter teks',
  713. 'zh-Hans': '视频提要 - 文本过滤器',
  714. 'zh-Hant': '視頻提要 - 文本過濾器',
  715. 'ja': '動画フィード - テキスト フィルター',
  716. 'fi': 'Videosyöte - tekstisuodatin',
  717. 'tr': 'Video Beslemelerini İzle - metin filtresi',
  718. 'defaultEnabled': false,
  719. },
  720.  
  721. // - text filter - separate keywords with new line:
  722. DLG_BLOCK_NEW_LINE: {
  723. 'en': '(separate words or phrases with a line break)',
  724. 'pt': '(separe palavras ou frases com quebras de linha)',
  725. 'de': '(trennen Sie Wörter oder Sätze mit Zeilenumbrüchen)',
  726. 'fr': '(mots ou phrases séparés avec des sauts de ligne)',
  727. 'es': '(palabras o frases separadas con saltos de línea)',
  728. 'cs': '(oddělte slova nebo fráze na nový řádek)',
  729. 'vi': '(tách các từ hoặc cụm từ bằng dấu ngắt dòng)',
  730. 'it': '(separare parole o frasi con un\'interruzione di riga)',
  731. 'lv': '(atdaliet vārdus vai frāzes ar rindas pārtraukumu)',
  732. 'pl': '(oddziel słowa lub wyrażenia z podziałem wiersza)',
  733. 'nl': '(scheid woorden of woordgroepen met een regeleinde)',
  734. 'he': '(הפרד מילים או ביטויים עם מעבר שורה)',
  735. 'ar': '(افصل الكلمات أو العبارات بفاصل أسطر)',
  736. 'id': '(pisahkan kata atau frasa dengan jeda baris)',
  737. 'zh-Hans': '(用换行符分隔单词或短语)',
  738. 'zh-Hant': '(用換行符分隔單詞或短語)',
  739. 'ja': '(改行で単語または語句を区切ります)',
  740. 'fi': '(erottele sanat tai lauseet rivinvaihdolla)',
  741. 'tr': '(sözcükleri veya tümcecikleri satır sonu ile ayırın)',
  742. },
  743.  
  744. NF_BLOCKED_ENABLED: {
  745. 'en': 'Enabled',
  746. 'pt': 'Habilidoso',
  747. 'de': 'Ermöglichte',
  748. 'fr': 'Activé',
  749. 'es': 'Habilitadas',
  750. 'cs': 'Zapnuto',
  751. 'vi': 'Đã kích hoạt',
  752. 'it': 'Abilita opzione',
  753. 'lv': 'Iespējots',
  754. 'pl': 'Włączone',
  755. 'nl': 'Ingeschakeld',
  756. 'he': 'מופעל',
  757. 'ar': 'تمكين',
  758. 'id': 'Diaktifkan',
  759. 'zh-Hans': '启用',
  760. 'zh-Hant': '啟用',
  761. 'ja': '有効化',
  762. 'fi': 'Ota vaihtoehto käyttöön',
  763. 'tr': 'Etkinleştirildi',
  764. },
  765.  
  766. GF_BLOCKED_ENABLED: {
  767. 'en': 'Enabled',
  768. 'pt': 'Habilidoso',
  769. 'de': 'Ermöglichte',
  770. 'fr': 'Activé',
  771. 'es': 'Habilitadas',
  772. 'cs': 'Zapnuto',
  773. 'vi': 'Đã kích hoạt',
  774. 'it': 'Abilita opzione',
  775. 'lv': 'Iespējots',
  776. 'pl': 'Włączone',
  777. 'nl': 'Ingeschakeld',
  778. 'he': 'מופעל',
  779. 'ar': 'تمكين',
  780. 'id': 'Diaktifkan',
  781. 'zh-Hans': '启用',
  782. 'zh-Hant': '啟用',
  783. 'ja': '有効化',
  784. 'fi': 'Ota vaihtoehto käyttöön',
  785. 'tr': 'Etkinleştirildi',
  786. },
  787.  
  788. VF_BLOCKED_ENABLED: {
  789. 'en': 'Enabled',
  790. 'pt': 'Habilidoso',
  791. 'de': 'Ermöglichte',
  792. 'fr': 'Activé',
  793. 'es': 'Habilitadas',
  794. 'cs': 'Zapnuto',
  795. 'vi': 'Đã kích hoạt',
  796. 'it': 'Abilita opzione',
  797. 'lv': 'Iespējots',
  798. 'pl': 'Włączone',
  799. 'nl': 'Ingeschakeld',
  800. 'he': 'מופעל',
  801. 'ar': 'تمكين',
  802. 'id': 'Diaktifkan',
  803. 'zh-Hans': '启用',
  804. 'zh-Hant': '啟用',
  805. 'ja': '有効化',
  806. 'fi': 'Ota vaihtoehto käyttöön',
  807. 'tr': 'Etkinleştirildi',
  808. },
  809.  
  810. // - label for Verbosity
  811. DLG_VERBOSITY: {
  812. 'en': 'Verbosity',
  813. 'pt': 'Verbosidade',
  814. 'de': 'Ausführlichkeit',
  815. 'fr': 'Verbosité',
  816. 'es': 'Verbosidad',
  817. 'cs': 'Výřečnost',
  818. 'vi': 'Tính dài dòng',
  819. 'it': 'Verbosità',
  820. 'lv': 'Runīgums',
  821. 'pl': 'Włączone',
  822. 'nl': 'Ingeschakeld',
  823. 'he': 'ורבוסיטי',
  824. 'ar': 'الإسهاب',
  825. 'id': 'Verbositas',
  826. 'zh-Hans': '详细程度',
  827. 'zh-Hant': '详细程度',
  828. 'ja': '詳細度',
  829. 'fi': 'Monisanaisuus',
  830. 'tr': 'Ayrıntı',
  831. 'defaultValue': '1',
  832. },
  833.  
  834. // - label for display a message if a post is hidden:
  835. DLG_VERBOSITY_MESSAGE: {
  836. 'en': 'Display a message if a post is hidden',
  837. 'pt': 'Exibir uma mensagem se uma postagem estiver oculta',
  838. 'de': 'Nachricht anzeigen, wenn ein Beitrag ausgeblendet ist',
  839. 'fr': 'Afficher un message si une publication est masquée',
  840. 'es': 'Mostrar un mensaje si una publicación está oculta',
  841. 'cs': 'Zobrazit zprávu, pokud je příspěvek skrytý',
  842. 'vi': 'Hiển thị một tin nhắn nếu một bài đăng bị ẩn',
  843. 'it': 'Visualizza un messaggio se un post è nascosto',
  844. 'lv': 'Parādīt ziņojumu, ja raksts ir paslēpts',
  845. 'pl': 'Wyświetlaj wiadomość, jeśli wpis jest ukryty',
  846. 'nl': 'Een bericht weergeven als een artikel verborgen is',
  847. 'he': 'הצג הודעה אם פוסט מוסתר',
  848. 'ar': 'اعرض رسالة إذا كانت المشاركة مخفية',
  849. 'id': 'Tampilkan pesan jika kiriman disembunyikan',
  850. 'zh-Hans': '如果帖子被隐藏,则显示消息',
  851. 'zh-Hant': '如果帖子被隱藏,則顯示消息',
  852. 'ja': '投稿が非表示の場合にメッセージを表示する',
  853. 'fi': 'Näytä viesti, jos postaus on piilotettu',
  854. 'tr': 'Bir gönderi gizlenmişse bir mesaj göster',
  855. },
  856.  
  857. // - Verbosity - say nothing:
  858. VERBOSITY_NO_MESSAGE: {
  859. 'en': 'no message',
  860. 'pt': 'nenhuma mensagem',
  861. 'de': 'keine Nachricht',
  862. 'fr': 'pas de message',
  863. 'es': 'Sin mensaje',
  864. 'cs': 'žádná zpráva',
  865. 'vi': 'không có tin nhắn',
  866. 'it': 'Nessun messaggio',
  867. 'lv': 'Nekādu ziņojumu',
  868. 'pl': 'nie ma wiadomości',
  869. 'nl': 'geen bericht',
  870. 'he': 'אין הודעה',
  871. 'ar': 'لا توجد رسالة',
  872. 'id': 'tidak ada pesan',
  873. 'zh-Hans': '没有消息',
  874. 'zh-Hant': '沒有消息',
  875. 'ja': 'メッセージなし',
  876. 'fi': 'ei viestiä',
  877. 'tr': 'esaj yok',
  878. },
  879.  
  880. // - notification
  881. VERBOSITY_MESSAGE: {
  882. 'en': ['1 post hidden. Rule: ', ' posts hidden'],
  883. 'pt': ['1 postagem oculta. Regra: ', ' postagens ocultas'],
  884. 'de': ['1 Beitrag ausgeblendet. Regel: ', ' Beiträge versteckt'],
  885. 'fr': ['1 poste caché. Règle: ', ' posts cachés'],
  886. 'es': ['1 publicación oculta. Regla: ', ' publicaciones ocultas'],
  887. 'cs': ['1 příspěvek byl skryt. Pravidlo: ', ' příspěvků skrytých'],
  888. 'vi': ['1 bài bị ẩn. Quy tắc: ', ' bài viết ẩn'],
  889. 'it': ['1 post nascosto Regola: ', ' post nascosti'],
  890. 'lv': ['1 ziņa ir paslēpta. Noteikums: ', ' ziņas ir paslēptas'],
  891. 'pl': ['Ukryto 1 post. Reguła: ', ' posty ukryte'],
  892. 'nl': ['1 post verborgen. Regel: ', ' posts verborgen'],
  893. 'he': ['פוסט אחד מוסתר. כלל: ', ' פוסטים מוסתרים'],
  894. 'ar': ['مشاركة واحدة مخفية. حكم: ', ' المشاركات المخفية'],
  895. 'id': ['1 pos disembunyikan. Aturan: ', ' postingan disembunyikan'],
  896. 'zh-Hans': ['1 个帖子已隐藏。 规则: ', ' 个帖子已隐藏'],
  897. 'zh-Hant': ['1 個帖子已隱藏。 規則: ', ' 個帖子已隱藏'],
  898. 'ja': ['1 件の投稿が非表示になっています。 ルール: ', ' 件の投稿が非表示'],
  899. 'fi': ['1 viesti piilotettu. Sääntö: ', ' viestiä piilotettu'],
  900. 'tr': ['1 gönderi gizlendi. Kural: ',' gönderi gizlendi'],
  901. },
  902.  
  903. // - colour of the verbosity message:
  904. VERBOSITY_MESSAGE_COLOUR: {
  905. 'en': 'Text colour',
  906. 'pt': 'Cor do texto',
  907. 'de': 'Textfarbe',
  908. 'fr': 'Couleur du texte',
  909. 'es': 'Color del texto',
  910. 'cs': 'Barva textu',
  911. 'vi': 'Màu văn bản',
  912. 'it': 'Colore del testo',
  913. 'lv': 'Teksta krāsa',
  914. 'pl': 'Kolor tekstu',
  915. 'nl': 'Tekstkleur',
  916. 'he': 'צבע טקסט',
  917. 'ar': 'لون النص',
  918. 'id': 'Warna teks',
  919. 'zh-Hans': '文字颜色',
  920. 'zh-Hant': '文字顏色',
  921. 'ja': 'テキストの色',
  922. 'fi': 'Tekstin väri',
  923. 'tr': 'Metin rengi',
  924. },
  925.  
  926. // - background colour of the verbosity message:
  927. VERBOSITY_MESSAGE_BG_COLOUR: {
  928. 'en': 'Background colour',
  929. 'pt': 'Cor de fundo',
  930. 'de': 'Hintergrundfarbe',
  931. 'fr': 'Couleur de fond',
  932. 'es': 'Color de fondo',
  933. 'cs': 'Barva pozadí',
  934. 'vi': 'Màu nền',
  935. 'it': 'Colore di sfondo',
  936. 'lv': 'Fona krāsa',
  937. 'pl': 'Kolor tła',
  938. 'nl': 'Achtergrondkleur',
  939. 'he': 'צבע הרקע',
  940. 'ar': 'لون الخلفية',
  941. 'id': 'Warna latar belakang',
  942. 'zh-Hans': '背景颜色',
  943. 'zh-Hant': '背景色',
  944. 'ja': '背景色',
  945. 'fi': 'Taustaväri',
  946. 'tr': 'Arka plan rengi',
  947. 'defaultValue': 'LightGrey',
  948. },
  949.  
  950. // - debugging - show "hidden" posts
  951. VERBOSITY_DEBUG: {
  952. 'en': 'Highlight "hidden" posts',
  953. 'pt': 'Destacar postagens "ocultas"',
  954. 'de': 'Markieren Sie "versteckte" Beiträge',
  955. 'fr': 'Mettez en surbrillance les messages « cachés »',
  956. 'es': 'Destacar publicaciones "ocultas"',
  957. 'cs': 'Zvýrazněte „skryté“ příspěvky',
  958. 'vi': 'Đánh dấu các bài đăng "ẩn"',
  959. 'it': 'Evidenzia i post "nascosti"',
  960. 'lv': 'Izceliet "slēptos" rakstus',
  961. 'pl': 'Wyróżnij „ukryte” posty',
  962. 'nl': 'Highlight "verborgen" artikelen',
  963. 'he': 'הדגש פוסטים "מוסתרים"',
  964. 'ar': 'تسليط الضوء على المشاركات "المخفية"',
  965. 'id': 'Sorot postingan "tersembunyi"',
  966. 'zh-Hans': '突出显示“隐藏”的帖子',
  967. 'zh-Hant': '突出顯示“隱藏”的帖子',
  968. 'ja': '「非表示」の投稿を強調表示する',
  969. 'fi': 'Korosta "piilotetut" postaus',
  970. 'tr': '"Gizli" gönderileri vurgulayın',
  971. 'defaultValue': false,
  972. },
  973.  
  974. // - customisation of cmf's dialog box:
  975. CMF_CUSTOMISATIONS: {
  976. 'en': 'Customisations',
  977. 'pt': 'Personalizações',
  978. 'de': 'Anpassungen',
  979. 'fr': 'Personnalisations',
  980. 'es': 'Personalizaciones',
  981. 'cs': 'Přizpůsobení',
  982. 'vi': 'Các tùy chỉnh',
  983. 'it': 'Personalizzazioni',
  984. 'lv': 'Personalizēšana',
  985. 'pl': 'Personalizacja',
  986. 'nl': 'Personalisaties',
  987. 'he': 'התאמות אישיות',
  988. 'ar': 'التخصيصات',
  989. 'id': 'Kustomisasi',
  990. 'zh-Hans': '定制化',
  991. 'zh-Hant': '定制化',
  992. 'ja': 'カスタマイズ',
  993. 'fi': 'Räätälöinnit',
  994. 'tr': 'özelleştirmeler',
  995. },
  996.  
  997. // - label for location of button:
  998. CMF_BTN_LOCATION: {
  999. 'en': 'Location of Clean my feeds\' button',
  1000. 'pt': 'Localização do botão Limpe meus feeds',
  1001. 'de': 'Position der Schaltfläche "Bereinige meine Feeds"',
  1002. 'fr': 'Emplacement du bouton Nettoyer mes flux',
  1003. 'es': 'Ubicación del botón Limpia mis feeds',
  1004. 'cs': 'Umístění tlačítka Vyčistěte mé kanály',
  1005. 'vi': 'Vị trí của nút Làm sạch nguồn cấp dữ liệu của tôi',
  1006. 'it': 'Posizione del pulsante Pulisci i miei feed',
  1007. 'lv': 'Pogas Tīrīt manas plūsmas atrašanās vieta',
  1008. 'pl': 'Lokalizacja przycisku Wyczyść moje kanały',
  1009. 'nl': 'Locatie van de knop Mijn feeds opschonen',
  1010. 'he': 'תנקה את הזנות שלי מיקום הכפתור',
  1011. 'ar': 'موقع الزر "تنظيف خلاصاتي"',
  1012. 'id': 'Lokasi tombol Bersihkan umpan saya',
  1013. 'zh-Hans': '“清理我的提要”按钮位置',
  1014. 'zh-Hant': '“清理我的提要”按鈕的位置',
  1015. 'ja': '「フィードをクリーンアップ」ボタンの配置',
  1016. 'fi': 'Puhdista syötteeni -painikkeen sijainti',
  1017. 'tr': '"Feed\'lerimi temizle" için düğmenin konumu',
  1018. },
  1019.  
  1020. // - location of button:
  1021. CMF_BTN_OPTION: {
  1022. 'en': ['bottom left', 'top right'],
  1023. 'pt': ['inferior esquerdo', 'superior direito'],
  1024. 'de': ['unten links', 'oben rechts'],
  1025. 'fr': ['en bas à gauche', 'en haut à droite'],
  1026. 'es': ['abajo a la izquierda', 'arriba a la derecha'],
  1027. 'cs': ['vlevo dole', 'vpravo nahoře'],
  1028. 'vi': ['dưới cùng bên trái', 'trên cùng bên phải'],
  1029. 'it': ['in basso a sinistra', 'in alto a destra'],
  1030. 'lv': ['apakšējā kreisajā stūrī', 'augšējā labajā stūrī'],
  1031. 'pl': ['lewy dolny róg', 'prawy górny róg'],
  1032. 'nl': ['linksonder', 'rechtsboven'],
  1033. 'he': ['שמאל למטה', 'ימינה למעלה'],
  1034. 'ar': ['أسفل اليسار', 'أعلى اليمين'],
  1035. 'id': ['kiri bawah', 'kanan atas'],
  1036. 'zh-Hans': ['左下方', '右上'],
  1037. 'zh-Hant': ['左下方', '右上'],
  1038. 'ja': ['下左', '上右'],
  1039. 'fi': ['alhaalla vasemmalla', 'ylhäällä oikealle'],
  1040. 'tr': ['sol alt', 'sağ üst'],
  1041. 'defaultValue': '0',
  1042. },
  1043.  
  1044. // - label for location of dialog:
  1045. CMF_DIALOG_LOCATION: {
  1046. 'en': 'Location of Clean my feeds\' dialog box',
  1047. 'pt': 'Localização da caixa de diálogo Limpe meus feeds',
  1048. 'de': 'Position des Dialogfelds "Bereinige meine Feeds"',
  1049. 'fr': 'Emplacement de la boîte de dialogue Nettoyer mes flux',
  1050. 'es': 'Ubicación del cuadro de diálogo Limpia mis feeds',
  1051. 'cs': 'Umístění dialogového okna Vyčistěte mé kanály',
  1052. 'vi': 'Vị trí của hộp thoại Làm sạch nguồn cấp dữ liệu của tôi',
  1053. 'it': 'Posizione della finestra di dialogo Pulisci i miei feed',
  1054. 'lv': 'Dialoglodziņa Tīrīt manas plūsmas atrašanās vieta',
  1055. 'pl': 'Lokalizacja okna dialogowego Wyczyść moje kanały',
  1056. 'nl': 'Locatie van het dialoogvenster Mijn feeds opschonen',
  1057. 'he': 'מיקום תיבת הדו-שיח "נקה את ההזנות שלי"',
  1058. 'ar': 'موقع مربع الحوار "تنظيف موجز ويباتي"',
  1059. 'id': 'Lokasi kotak dialog Bersihkan umpan saya',
  1060. 'zh-Hans': '“清理我的提要”对话框位置',
  1061. 'zh-Hant': '“清理我的提要”對話框的位置',
  1062. 'ja': '[フィードの消去] ダイアログ ボックスの配置',
  1063. 'fi': 'Puhdista syötteeni -valintaikkunan sijainti',
  1064. 'tr': '"Feed\'lerimi temizle" iletişim kutusunun konumu',
  1065. },
  1066.  
  1067. // - location of dialog:
  1068. CMF_DIALOG_OPTION: {
  1069. 'en': ['left side', 'right side'],
  1070. 'pt': ['lado esquerdo', 'lado direito'],
  1071. 'de': ['linke Seite', 'rechte Seite'],
  1072. 'fr': ['côté gauche', 'côté droit'],
  1073. 'es': ['lado izquierdo', 'lado derecho'],
  1074. 'cs': ['levá strana', 'pravá strana'],
  1075. 'vi': ['bên trái', 'bên phải'],
  1076. 'it': ['lato sinistro', 'lato destro'],
  1077. 'lv': ['kreisā puse', 'labā puse'],
  1078. 'pl': ['lewa strona', 'prawa strona'],
  1079. 'nl': ['linkerkant', 'rechterkant'],
  1080. 'he': ['צד שמאל', 'צד ימין'],
  1081. 'ar': ['الجهه اليسرى', 'الجانب الصحيح'],
  1082. 'id': ['sisi kiri', 'sisi kanan'],
  1083. 'zh-Hans': ['左边', '右边'],
  1084. 'zh-Hant': ['左邊', '右邊'],
  1085. 'ja': ['左側', '右側'],
  1086. 'fi': ['vasen puoli', 'oikea puoli'],
  1087. 'tr': ['sol yan', 'sağ yan'],
  1088. 'defaultValue': '0',
  1089. },
  1090.  
  1091. // - dialog's border colour:
  1092. CMF_BORDER_COLOUR: {
  1093. 'en': 'Border colour',
  1094. 'pt': 'Cor da borda',
  1095. 'de': 'Farbe der Umrandung',
  1096. 'fr': 'Couleur de bordure',
  1097. 'es': 'Color de borde',
  1098. 'cs': 'Barva ohraničení',
  1099. 'vi': 'Màu viền',
  1100. 'it': 'Colore del bordo',
  1101. 'lv': 'Apmales krāsa',
  1102. 'pl': 'Kolor obramowania',
  1103. 'nl': 'Randkleur',
  1104. 'he': 'צבע גבול',
  1105. 'ar': 'لون الحدود',
  1106. 'id': 'Warna perbatasan',
  1107. 'zh-Hans': '边框颜色',
  1108. 'zh-Hant': '邊框顏色',
  1109. 'ja': 'ボーダーカラー',
  1110. 'fi': 'Reunuksen väri',
  1111. 'tr': 'Kenarlık rengi',
  1112. 'defaultValue': 'OrangeRed',
  1113. },
  1114.  
  1115. // - label for tips:
  1116. DLG_TIPS: {
  1117. 'en': 'Tips',
  1118. 'pt': 'Pontas',
  1119. 'de': 'Tipps',
  1120. 'fr': 'Des astuces',
  1121. 'es': 'Consejos',
  1122. 'cs': 'Tipy',
  1123. 'vi': 'Thủ thuật',
  1124. 'it': 'Suggerimenti',
  1125. 'lv': 'Padomi',
  1126. 'pl': 'Sugestia',
  1127. 'nl': 'Tips',
  1128. 'he': 'טיפים',
  1129. 'ar': 'تلميحات',
  1130. 'id': 'Tips',
  1131. 'zh-Hans': '提示',
  1132. 'zh-Hant': '提示',
  1133. 'ja': 'ヒント',
  1134. 'fi': 'Vinkkejä',
  1135. 'tr': 'Ipuçları',
  1136. },
  1137.  
  1138. // - tip's content:
  1139. DLG_TIPS_CONTENT: {
  1140. 'en': 'Clearing your browser\'s cache will reset your settings to their default values.\n\nUse the "Export" and "Import" buttons to backup and restore your customised settings.',
  1141. 'pt': 'Limpar o cache do navegador redefinirá suas configurações para os valores padrão.\n\nUse os botões "Exportar" e "Importar" para fazer backup e restaurar suas configurações personalizadas.',
  1142. 'de': 'Wenn Sie den Cache Ihres Browsers leeren, werden Ihre Einstellungen auf die Standardwerte zurückgesetzt.\n\nVerwenden Sie die Schaltflächen "Exportieren" und "Importieren", um Ihre benutzerdefinierten Einstellungen zu sichern und wiederherzustellen.',
  1143. 'fr': 'Vider le cache de votre navigateur réinitialisera vos paramètres à leurs valeurs par défaut.\n\nUtilisez les boutons "Exporter" et "Importer" pour sauvegarder et restaurer vos paramètres personnalisés.',
  1144. 'es': 'Limpiar la memoria caché de su navegador restablecerá la configuración a sus valores predeterminados.\n\nUtilice los botones "Exportar" e "Importar" para hacer una copia de seguridad y restaurar su configuración personalizada.',
  1145. 'cs': 'Vymazáním mezipaměti prohlížeče obnovíte výchozí hodnoty nastavení.\n\nPomocí tlačítek "Export" a "Import" zálohujte a obnovte svá přizpůsobená nastavení.',
  1146. 'vi': 'Xóa bộ nhớ cache của trình duyệt sẽ đặt lại cài đặt của bạn về các giá trị mặc định của chúng.\n\nSử dụng các nút "Xuất" và "Nhập" để sao lưu và khôi phục cài đặt tùy chỉnh của bạn.',
  1147. 'it': 'La cancellazione della cache del browser ripristinerà le impostazioni ai valori predefiniti.\n\nUtilizza i pulsanti "Esporta" e "Importa" per eseguire il backup e ripristinare le impostazioni personalizzate.',
  1148. 'lv': 'Iztīrot pārlūkprogrammas kešatmiņu, iestatījumi tiks atiestatīti uz noklusējuma vērtībām.\n\nIzmantojiet pogas "Eksportēt" un "Importēt", lai dublētu un atjaunotu pielāgotos iestatījumus.',
  1149. 'pl': 'Wyczyszczenie pamięci podręcznej przeglądarki spowoduje zresetowanie ustawień do wartości domyślnych.\n\nUżyj przycisków „Eksportuj” i „Importuj”, aby wykonać kopię zapasową i przywrócić niestandardowe ustawienia.',
  1150. 'nl': 'Als u de cache van uw browser wist, worden uw instellingen teruggezet naar hun standaardwaarden.\n\nGebruik de knoppen "Exporteren" en "Importeren" om een back-up te maken van uw aangepaste instellingen en deze te herstellen.',
  1151. 'he': 'מחיקת ההיסטורה בדפדפן תנקה את ההגדרות ותחזיר אותם לברירת המחדל.\n\nהשתמש ב"ייצא" ו"ייבא" כדי לגבות ולהחזיר את ההגדרות שלך',
  1152. 'ar': 'سيؤدي مسح ذاكرة التخزين المؤقت للمتصفح إلى إعادة تعيين الإعدادات إلى قيمها الافتراضية.\n\nاستخدم الزرين "تصدير" و "استيراد" للنسخ الاحتياطي واستعادة الإعدادات المخصصة.',
  1153. 'id': 'Menghapus cache browser Anda akan mengatur ulang pengaturan Anda ke nilai defaultnya.\n\nGunakan tombol "Ekspor" dan "Impor" untuk mencadangkan dan memulihkan pengaturan khusus Anda.',
  1154. 'zh-Hans': '清除浏览器缓存会将您的设置重置为默认值。\n\n使用“导出”和“导入”按钮来备份和恢复您的自定义设置。',
  1155. 'zh-Hant': '清除瀏覽器緩存會將您的設置重置為默認值。\n\n使用“導出”和“導入”按鈕來備份和恢復您的自定義設置。',
  1156. 'ja': 'ブラウザのキャッシュをクリアすると、設定がデフォルト値にリセットされます。\n\n[エクスポート] および [インポート] ボタンを使用して、カスタマイズした設定をバックアップおよび復元します。',
  1157. 'fi': 'Selaimen välimuistin tyhjentäminen palauttaa asetuksesi oletusarvoihinsa.\n\nKäytä "Vie"- ja "Tuo"-painikkeita varmuuskopioidaksesi ja palauttaaksesi mukautetut asetukset.',
  1158. 'tr': 'Tarayıcınızın önbelleğini temizlemek, ayarlarınızı varsayılan değerlerine sıfırlayacaktır. \n\nÖzelleştirilmiş ayarlarınızı yedeklemek ve geri yüklemek için "Dışa Aktar" ve "İçe Aktar" düğmelerini kullanın.',
  1159. },
  1160.  
  1161. // - dailog's action buttons:
  1162. DLG_BUTTONS: {
  1163. 'en': ['Save', 'Close', 'Export', 'Import'],
  1164. 'pt': ['Salvar', 'Fechar', 'Exportar', 'Importar'],
  1165. 'de': ['Speichern', 'Schließen', 'Exportieren', 'Importieren'],
  1166. 'fr': ['Sauvegarder', 'Fermer', 'Exporter', 'Importer'],
  1167. 'es': ['Guardar', 'Cerrar', 'Exportar', 'Importar'],
  1168. 'cs': ['Zachránit', 'Zavřít', 'Export', 'Import'],
  1169. 'vi': ['Lưu', 'Đóng', 'Xuất', 'Nhập'],
  1170. 'it': ['Salva', 'Chiudi', 'Esportare', 'Importare'],
  1171. 'lv': ['Saglabājiet', 'Aizveriet', 'Eksportēt', 'Importēt'],
  1172. 'pl': ['Zapisz', 'Zamknij', 'Eksport', 'Import'],
  1173. 'nl': ['Opslaan', 'Sluiten', 'Exporteren', 'Importeren'],
  1174. 'he': ['שמור', 'סגור', 'ייצא', 'ייבא'],
  1175. 'ar': ['حفظ', 'قريب', 'يصدّر', 'يستورد'],
  1176. 'id': ['Simpan', 'Tutup', 'Ekspor', 'Impor'],
  1177. 'zh-Hans': ['节省', '关', '出口', '进口'],
  1178. 'zh-Hant': ['節省', '關', '出口', '進口'],
  1179. 'ja': ['セーブ', 'クローズ', '輸出する', '輸入'],
  1180. 'fi': ['Tallentaa', 'Sulkea', 'Vienti', 'Tuonti'],
  1181. 'tr': ['Kaydetmek', 'Kapat', 'İhracat', 'İçe aktarmak'],
  1182. }
  1183. };
  1184.  
  1185. // *** *** end of language components *** ***
  1186.  
  1187. // - console log "label" - used for filtering console logs.
  1188. const log = '-- fbcmf :: ';
  1189.  
  1190. // - idb-keyval - indexedDB wrapper
  1191. // -- needs the "@require https://unpkg.com/idb-keyval@6.0.3/dist/umd.js" entry.
  1192. // -- which functions do we want to use from the idb-keyval?
  1193. const { get, set, createStore } = idbKeyval;
  1194. // - override idb-keyval's default db and store names.
  1195. let DBVARS = {
  1196. DBName: 'dbCMF',
  1197. DBStore: 'Mopping',
  1198. DBKey: 'Options',
  1199. optionsReady: false,
  1200. ostore: null
  1201. }
  1202. // - make sure the db's store exists ...
  1203. DBVARS.ostore = createStore(DBVARS.DBName, DBVARS.DBStore);
  1204.  
  1205. // - post attribute - hidden and reason
  1206. const postAtt = 'msz';
  1207. // - post property - # of light dusting duties done
  1208. const postPropDS = 'mszDusted';
  1209.  
  1210. // - Feed Details variables
  1211. // -- nb: setFeedSettings() adjusts some of these settings.
  1212. const VARS = {
  1213. // - langauge (default to EN)
  1214. language: '',
  1215. // - user options
  1216. Options: {},
  1217. // - blocked text
  1218. Filters: {},
  1219.  
  1220. scanCountStart: 0,
  1221. scanCountMaxLoop: 11,
  1222.  
  1223. // - Feed toggles
  1224. isNF: false, // news
  1225. isGF: false, // groups
  1226. isVF: false, // videos
  1227. isMF: false, // marketplace
  1228. isAF: false, // all feeds
  1229. isSF: false, // search feed
  1230.  
  1231. // groups feed type : 'group' = single group; 'groups' = multiple groups;
  1232. gfType: '',
  1233.  
  1234. // watch/videos feed type : 'vidoes' = normal feed; 'search' = search videos;
  1235. vfType: '',
  1236.  
  1237. // marketplace feed type: 'marketplace' = default view; 'category' = category view; 'item' = viewing an item; 'search' = search results;
  1238. mpType: '',
  1239.  
  1240. // remember current URL - used for page change detection
  1241. prevURL: '',
  1242. prevPathname: '',
  1243.  
  1244. // element containing echo message about post(s) being hidden
  1245. echoEl: null,
  1246. // how many consecutive posts have been hidden
  1247. echoCount: 0,
  1248.  
  1249. // StyleSheet Id
  1250. cssID: '',
  1251. // CSS class names
  1252. cssHide: '',
  1253. cssHideEl: '',
  1254. cssEcho: '',
  1255. // toggle dialog button (visible if is a Feed page)
  1256. btnToggleEl: null,
  1257. // - script's logo
  1258. 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>'
  1259. };
  1260.  
  1261. // -- which language is the FB page in?
  1262. function setLanguageAndOptions() {
  1263. // - run this function when HEAD is available.
  1264. // - language (default to EN)
  1265. // - also run getUserOptions().
  1266. if (document.head) {
  1267. let lang = document.head.parentNode.lang || 'en';
  1268. VARS.language = (KeyWords.LANGUAGES.indexOf(lang) >= 0) ? lang : 'en';
  1269.  
  1270. // ...
  1271. let result = getUserOptions().then(() => {
  1272. return true;
  1273. });
  1274. }
  1275. else {
  1276. setTimeout(setLanguageAndOptions, 5);
  1277. }
  1278. }
  1279.  
  1280. // -- posts CSS
  1281. function addCSS() {
  1282. // - CSS styles for hiding or highlighting the selected posts / element
  1283.  
  1284. function generateRandomName() {
  1285. // - generate random names (first letter must be an alphabet)
  1286. let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  1287. let str = chars.charAt(Math.floor(Math.random() * (chars.length - 10)));
  1288. for (let i = 0; i < 12; i++) {
  1289. str += chars.charAt(Math.floor(Math.random() * chars.length));
  1290. }
  1291. return str;
  1292. }
  1293.  
  1294. let head, styleEl, css;
  1295. let isNewCSS = true;
  1296.  
  1297. if (VARS.cssID !== '') {
  1298. // - Reset the existing Stylesheet
  1299. styleEl = document.getElementById(VARS.cssID);
  1300. if (styleEl) {
  1301. styleEl.replaceChildren();
  1302. isNewCSS = false;
  1303. }
  1304. }
  1305. if (isNewCSS) {
  1306. // - Create the new Stylesheet head + classnames
  1307. VARS.cssID = generateRandomName().toUpperCase();
  1308. head = document.getElementsByTagName('head')[0];
  1309. styleEl = document.createElement('style');
  1310. styleEl.setAttribute('type', 'text/css');
  1311. styleEl.setAttribute('id', VARS.cssID);
  1312.  
  1313. // - remember class names (for other functions to use)
  1314. VARS.cssHide = generateRandomName(); // - the parent element - hides the child element
  1315. VARS.cssHideEl = generateRandomName(); // - the elment to hide - where there's no child element
  1316. VARS.cssEcho = generateRandomName();
  1317.  
  1318. }
  1319. // - insert Styles (as classes)
  1320. // - NF/GF/VF
  1321. // -- remove margins
  1322. if (VARS.Options.VERBOSITY_DEBUG === false) {
  1323. // -- not debugging, remove margins
  1324. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide}, .${VARS.cssHideEl} {margin:0 !important;}`));
  1325. }
  1326. // -- post wrapper's first child element (either div or span) (mainly for news, groups and video feeds posts)
  1327. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide} > div, `));
  1328. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide} > span, `));
  1329. // -- post wrapper's element (mainly for marketplace posts + some aside boxes)
  1330. styleEl.appendChild(document.createTextNode(`.${VARS.cssHideEl} `));
  1331. // -- which styles to apply?
  1332. // --- (display:block is for those span tags being a nth-of-child element.)
  1333. if (VARS.Options.VERBOSITY_DEBUG === true) {
  1334. styleEl.appendChild(document.createTextNode(` {border:5px dotted ${VARS.Options.CMF_BORDER_COLOUR}; width:66%; display:block;}`));
  1335. }
  1336. else {
  1337. styleEl.appendChild(document.createTextNode(' {display:none;}'));
  1338. }
  1339.  
  1340. // - echo msg
  1341. let colourMsg = (VARS.Options.VERBOSITY_MESSAGE_COLOUR === '') ? '' : `color: ${VARS.Options.VERBOSITY_MESSAGE_COLOUR}; `;
  1342. colourMsg += (VARS.Options.VERBOSITY_MESSSAGE_BG_COLOUR === '') ? '' : `background-color: ${VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR}; `;
  1343. //css = `margin:1.35rem 0 !important; padding:0.75rem 1rem; border-radius:0.55rem; width:85%; font-style:italic; ${colourMsg}`;
  1344. css = `margin:1.35rem auto; padding:0.75rem 1rem; border-radius:0.55rem; width:85%; font-style:italic; ${colourMsg}`;
  1345. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide} > p {${css}}`));
  1346.  
  1347. // - toggle hidden post's visibility ...
  1348. // -- default (hide)
  1349. css = 'display:inline-block; padding:0 0.75rem 0 0;';
  1350. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide} > p > div {${css}}`));
  1351. css = 'transform: rotate(180deg);transition: transform 0.10s linear;';
  1352. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide} > p > div > button {${css}}`));
  1353. // -- visible
  1354. css = `border:3px dotted ${VARS.Options.CMF_BORDER_COLOUR} !important; border-radius:1rem;margin-bottom:1rem !important;`;
  1355. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide}.show {${css}}`));
  1356. css = 'margin-top:0.5rem; margin-bottom:0.5rem;';
  1357. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide}.show > p {${css}}`));
  1358. css = 'display:block !important;';
  1359. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide}.show > div {${css}}`));
  1360. css = 'transform: rotate(360deg);transition: transform 0.15s linear;';
  1361. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide}.show > p > div > button {${css}}`));
  1362.  
  1363. // - dailog box CSS
  1364. // --- dialog box; position + flex
  1365. let bcolour = (VARS.Options.CMF_BORDER_COLOUR === '') ? KeyWords.CMF_BORDER_COLOUR.defaultValue : VARS.Options.CMF_BORDER_COLOUR;
  1366. // - left / right done in fn addExtraCSS().
  1367. css = `position:fixed; top:0.15rem; bottom:0.15rem; display:flex; flex-direction:column; width:30rem; padding:0 1rem; z-index:5; color: var(--primary-text); border:2px solid ${bcolour}; border-radius:1rem; opacity:0;`;
  1368. styleEl.appendChild(document.createTextNode(`.fb-cmf {${css}}`));
  1369. styleEl.appendChild(document.createTextNode('.__fb-light-mode .fb-cmf {background-color:#fefefa !important;}'));
  1370. styleEl.appendChild(document.createTextNode('.__fb-dark-mode .fb-cmf {background-color:var(--web-wash) !important;}'));
  1371. styleEl.appendChild(document.createTextNode('.fb-cmf {background-color:floralwhite;}')); // -- fall back colour.
  1372.  
  1373. // -- header
  1374. css = 'display:flex; justify-content:space-between; direction:ltr;';
  1375. styleEl.appendChild(document.createTextNode(`.fb-cmf header {${css}}`));
  1376.  
  1377. css = 'flex-grow:0; align-self:auto; width:75px; text-align:left; order:1;';
  1378. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-icon {${css}}`));
  1379. css = 'width:64px; height:64px; margin:2px 0;'
  1380. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-icon svg {${css}}`));
  1381.  
  1382. css = 'flex-grow:2; align-self:auto; order:2;';
  1383. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-title {${css}}`));
  1384. css = 'padding-top:1.25rem;';
  1385. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-lang-1 {${css}}`));
  1386. css = 'padding-top:0.75rem;';
  1387. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-lang-2 {${css}}`));
  1388.  
  1389. css = 'font-size:1.35rem; font-weight: 700; text-align:center;';
  1390. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-title > div {${css}}`));
  1391. css = 'display:block; font-size:0.8rem; text-align:center;';
  1392. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-title > small {${css}}`));
  1393.  
  1394. css = 'flex-grow:0; align-self:auto; width:75px; text-align:right; padding: 1.5rem 0 0 0; order:3;';
  1395. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-close {${css}}`));
  1396. css = 'width:1.75rem; height:1.5rem; font-family: monospace;';
  1397. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-close button {${css}}`));
  1398.  
  1399. // -- content
  1400. css = `flex:1; overflow:auto; border:2px double ${bcolour}; border-radius:0.5rem; color: var(--primary-text);`;
  1401. styleEl.appendChild(document.createTextNode(`.fb-cmf div.content {${css}}`));
  1402. css = 'padding:1rem; text-align:center;';
  1403. styleEl.appendChild(document.createTextNode(`.fb-cmf footer.buttons {${css}}`));
  1404. css = 'margin:0.5rem; border-color:LightGrey;';
  1405. styleEl.appendChild(document.createTextNode(`.fb-cmf fieldset {${css}}`));
  1406. css = 'font-weight:700;';
  1407. styleEl.appendChild(document.createTextNode(`.fb-cmf fieldset legend {${css}}`));
  1408. css = 'display:inline-block; padding:0.125rem 0; width:95%; color: var(--primary-text); font-weight: normal;';
  1409. styleEl.appendChild(document.createTextNode(`.fb-cmf fieldset label {${css}}`));
  1410. css = 'margin: 0 0.5rem 0 0.5rem; vertical-align:baseline;'; // left & right margins for RTL & LTR text
  1411. styleEl.appendChild(document.createTextNode(`.fb-cmf fieldset label input {${css}}`));
  1412. css = 'color:darkgrey;';
  1413. styleEl.appendChild(document.createTextNode(`.fb-cmf fieldset label[disabled] {${css}}`));
  1414. css = 'width:100%; height:12rem;';
  1415. styleEl.appendChild(document.createTextNode(`.fb-cmf fieldset textarea {${css}}`));
  1416. css = 'background-color:var(--comment-background); color:var(--primary-text);';
  1417. styleEl.appendChild(document.createTextNode(`.__fb-dark-mode .fb-cmf fieldset textarea, .__fb-dark-mode .fb-cmf fieldset input[type="input"] {${css}}`));
  1418. // -- footer - buttons
  1419. css = 'margin-left: 1rem; margin-right:1rem;';
  1420. styleEl.appendChild(document.createTextNode(`.fb-cmf .buttons button {${css}}`));
  1421. // -- footer - file input
  1422. styleEl.appendChild(document.createTextNode('.fb-cmf .fileInput {display:none;}'));
  1423. // -- footer - import results
  1424. css = 'font-style:italic; margin-top: 1rem;';
  1425. styleEl.appendChild(document.createTextNode(`.fb-cmf .fileResults {${css}}`));
  1426. // -- show dialog box (default is not to show)
  1427. css = 'opacity:1; transform:scale(1);';
  1428. styleEl.appendChild(document.createTextNode(`.fb-cmf.show {${css}}`));
  1429. // - add above styles to HEAD.
  1430. if (isNewCSS) {
  1431. head.appendChild(styleEl);
  1432. }
  1433. }
  1434.  
  1435. function addExtraCSS() {
  1436. // - extra CSS styles
  1437. // - fb can sometimes be a bit slow in loading certain parts of the site ...
  1438. // - ... this function is called several ms later ...
  1439. // - ... and when saving the options (via save button)
  1440. let cmfBtnLocation = KeyWords.CMF_BTN_OPTION.defaultValue;
  1441. let cmfDlgLocation = KeyWords.CMF_DIALOG_OPTION.defaultValue
  1442. if (VARS.Options.hasOwnProperty('CMF_BTN_OPTION')) {
  1443. if (VARS.Options.CMF_BTN_OPTION.toString() !== '') {
  1444. cmfBtnLocation = VARS.Options.CMF_BTN_OPTION;
  1445. }
  1446. }
  1447. if (VARS.Options.hasOwnProperty('CMF_DIALOG_OPTION')) {
  1448. if (VARS.Options.CMF_DIALOG_OPTION.toString() !== '') {
  1449. cmfDlgLocation = VARS.Options.CMF_DIALOG_OPTION;
  1450. }
  1451. }
  1452. cmfBtnLocation = cmfBtnLocation.toString();
  1453. cmfDlgLocation = cmfDlgLocation.toString();
  1454.  
  1455. // Grab the existing Stylesheet and amend it
  1456. let styleEl = document.getElementById(VARS.cssID);
  1457.  
  1458. let css;
  1459.  
  1460. // - button's location.
  1461. if (cmfBtnLocation === '1') {
  1462. // - top right - has the buttons running across the top of the page (pre May 2022).
  1463. css = 'margin-right: 42px;';
  1464. if (document.querySelector('[role="banner"]')) {
  1465. // - oldish FB structure has menu buttons across the top (changed for some users in Apr/May 2022)
  1466. styleEl.appendChild(document.createTextNode(`div[role="banner"] > div:last-of-type div[role="navigation"] {${css}}`));
  1467. }
  1468. css = 'position:fixed; top:0.5rem; right:0.5rem; display:none;';
  1469. }
  1470. else {
  1471. // - cmfBtnLocation === "0"
  1472. // - bottom left - has the buttons running down the side of the page (May 2022 ->).
  1473. css = 'position:fixed; bottom:4.25rem; left:1.1rem; display:none;';
  1474. }
  1475. styleEl.appendChild(document.createTextNode(`.fb-cmf-toggle {${css}}`));
  1476. // - btn - basic styling.
  1477. styleEl.appendChild(document.createTextNode('.fb-cmf-toggle {border-radius:0.3rem;}'));
  1478. styleEl.appendChild(document.createTextNode('.fb-cmf-toggle svg {height:32px; width:32px;}'))
  1479. styleEl.appendChild(document.createTextNode('.fb-cmf-toggle:hover {cursor:pointer;}'));
  1480. // - dialog box's display
  1481. styleEl.appendChild(document.createTextNode('.fb-cmf-toggle.show {display:block;}'));
  1482. // - dialog box's left/right + animated open/close behaviour
  1483. if (cmfDlgLocation === '1') {
  1484. // - right
  1485. css = 'right:0.35rem; transform:scale(0);transform-origin:top right;';
  1486. }
  1487. else {
  1488. // - cmfDlgLocation === '0' (left)
  1489. css = 'left:5rem; transform:scale(0);transform-origin:center center;';
  1490. }
  1491. styleEl.appendChild(document.createTextNode(`.fb-cmf {${css + 'transition:transform .45s ease, opacity .25s ease;'}}`));
  1492. }
  1493.  
  1494. // -- get the user's settings ...
  1495. async function getUserOptions() {
  1496. // -- read in the saved data, else set defaults.
  1497. let changed = false;
  1498. // - reset Options
  1499. VARS.Options = new Object();
  1500.  
  1501. // - has the user previously saved options?
  1502. // -- if yes, the update Options
  1503. let result = await get(DBVARS.DBKey, DBVARS.ostore).then((values) => {
  1504. if (values) {
  1505. // -- has data
  1506. VARS.Options = JSON.parse(values);
  1507. return 1;
  1508. }
  1509. else {
  1510. // -- no data (first time)
  1511. return 0;
  1512. }
  1513. }).catch((err) => {
  1514. console.info(`${log}getuserOptions() > get() - Error:`, err);
  1515. });
  1516. if (VARS.VERBOSITY_DEBUG) {
  1517. console.info(`${log}getUserOptions() > get(): ${result}`);
  1518. }
  1519.  
  1520. // -- check that all variables exists ... if not, assign them default values..
  1521. // -- Sponsored (always enabled)
  1522. if (!VARS.Options.hasOwnProperty('NF_SPONSORED')) {
  1523. VARS.Options.NF_SPONSORED = true;
  1524. changed = true;
  1525. }
  1526. if (!VARS.Options.hasOwnProperty('GF_SPONSORED')) {
  1527. VARS.Options.GF_SPONSORED = true;
  1528. changed = true;
  1529. }
  1530. if (!VARS.Options.hasOwnProperty('VF_SPONSORED')) {
  1531. VARS.Options.VF_SPONSORED = true;
  1532. changed = true;
  1533. }
  1534. if (!VARS.Options.hasOwnProperty('MP_SPONSORED')) {
  1535. VARS.Options.MP_SPONSORED = true;
  1536. changed = true;
  1537. }
  1538.  
  1539. // -- which version is stored?
  1540. // -- v2.x-v3.x have "create_room". v4.x doesn't.
  1541. // -- if v2-3, upgrade to v4.
  1542. if ((result === 1) && VARS.Options.hasOwnProperty('NF_CREATE_ROOM')) {
  1543. if (!VARS.Options.hasOwnProperty('NF_SUGGESTIONS')) {
  1544. VARS.Options.NF_SUGGESTIONS = (
  1545. VARS.Options.NF_EVENTS_YOU_MAY_LIKE ||
  1546. VARS.Options.NF_RECOMMENDED_POST ||
  1547. VARS.Options.NF_SUGGESTED_EVENTS ||
  1548. VARS.Options.NF_SUGGESTED_FOR_YOU ||
  1549. VARS.Options.NF_SUGGESTED_PAGES ||
  1550. VARS.Options.NF_THIRD_COLUMN_SUGGESTED_FOR_YOU
  1551. );
  1552. }
  1553. if (!VARS.Options.hasOwnProperty('GF_SUGGESTIONS')) {
  1554. VARS.Options.GF_SUGGESTIONS = (
  1555. VARS.Options.GF_BECAUSE_YOU_VIEWED_A_SIMILAR_GROUP ||
  1556. VARS.Options.GF_BECAUSE_YOU_VIEWED_A_SIMILAR_POST ||
  1557. VARS.Options.GF_FRIENDS_GROUPS ||
  1558. VARS.Options.GF_FROM_A_GROUP_YOUR_FRIEND_IS_IN ||
  1559. VARS.Options.GF_JOIN_GROUP ||
  1560. VARS.Options.GF_NEW_FOR_YOU ||
  1561. VARS.Options.GF_POPULAR_NEAR_YOU ||
  1562. VARS.Options.GF_SEE_MORE_GROUPS ||
  1563. VARS.Options.GF_SUGGESTED_FOR_YOU_GROUPS ||
  1564. VARS.Options.GF_SUGGESTED_GROUPS ||
  1565. VARS.Options.GF_SUGGESTED_POST_PUBLIC_GROUP ||
  1566. VARS.Options.GF_YOUR_RECENT_ACTIVITY
  1567. );
  1568. }
  1569. if (!VARS.Options.hasOwnProperty('VERBOSITY_MESSAGE_COLOUR')) {
  1570. VARS.Options.VERBOSITY_MESSAGE_COLOUR = VARS.Options.VERBOSITY_COLOUR;
  1571. }
  1572. if (!VARS.Options.hasOwnProperty('VERBOSITY_MESSAGE_BG_COLOUR')) {
  1573. VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR = VARS.Options.VERBOSITY_BG_COLOUR;
  1574. }
  1575. if (!VARS.Options.hasOwnProperty('CMF_DIALOG_OPTION')) {
  1576. VARS.Options.CMF_DIALOG_OPTION = VARS.Options.CMF_DIALOG_LOCATION;
  1577. }
  1578. if (!VARS.Options.hasOwnProperty('CMF_BTN_OPTION')) {
  1579. VARS.Options.CMF_BTN_OPTION = VARS.Options.CMF_BTN_LOCATION;
  1580. }
  1581. }
  1582.  
  1583. // -- which option has been enabled / disabled?
  1584. VARS.hideAnInfoBox = false;
  1585. for (const key in KeyWords) {
  1586. if (key.slice(0, 3) === 'NF_' && key.slice(0, 10) !== 'NF_BLOCKED') {
  1587. if (!VARS.Options.hasOwnProperty(key)) {
  1588. VARS.Options[key] = KeyWords[key].defaultEnabled;
  1589. changed = true;
  1590. }
  1591. }
  1592. else if (key.slice(0, 3) === 'GF_' && key.slice(0, 10) !== 'GF_BLOCKED') {
  1593. if (!VARS.Options.hasOwnProperty(key)) {
  1594. VARS.Options[key] = KeyWords[key].defaultEnabled;
  1595. changed = true;
  1596. }
  1597. }
  1598. else if (key.slice(0, 3) === 'VF_' && key.slice(0, 10) !== 'VF_BLOCKED') {
  1599. if (!VARS.Options.hasOwnProperty(key)) {
  1600. VARS.Options[key] = KeyWords[key].defaultEnabled;
  1601. changed = true;
  1602. }
  1603. }
  1604. else if (key.slice(0, 10) === 'OTHER_INFO') {
  1605. if (!VARS.Options.hasOwnProperty(key)) {
  1606. VARS.Options[key] = KeyWords[key].defaultEnabled;
  1607. changed = true;
  1608. }
  1609. if (VARS.Options[key]) {
  1610. VARS.hideAnInfoBox = true;
  1611. }
  1612. }
  1613. }
  1614.  
  1615. // -- all other options.
  1616. if (!VARS.Options.hasOwnProperty('NF_BLOCKED_ENABLED')) {
  1617. VARS.Options.NF_BLOCKED_ENABLED = KeyWords.NF_BLOCKED_ENABLED.defaultEnabled;
  1618. changed = true;
  1619. }
  1620. if (!VARS.Options.hasOwnProperty('NF_BLOCKED_TEXT')) {
  1621. VARS.Options.NF_BLOCKED_TEXT = '';
  1622. changed = true;
  1623. }
  1624.  
  1625. if (!VARS.Options.hasOwnProperty('GF_BLOCKED_ENABLED')) {
  1626. VARS.Options.GF_BLOCKED_ENABLED = KeyWords.GF_BLOCKED_ENABLED.defaultEnabled;
  1627. changed = true;
  1628. }
  1629. if (!VARS.Options.hasOwnProperty('GF_BLOCKED_TEXT')) {
  1630. VARS.Options.GF_BLOCKED_TEXT = '';
  1631. changed = true;
  1632. }
  1633.  
  1634. if (!VARS.Options.hasOwnProperty('VF_BLOCKED_ENABLED')) {
  1635. VARS.Options.VF_BLOCKED_ENABLED = KeyWords.VF_BLOCKED_ENABLED.defaultEnabled;
  1636. changed = true;
  1637. }
  1638. if (!VARS.Options.hasOwnProperty('VF_BLOCKED_TEXT')) {
  1639. VARS.Options.VF_BLOCKED_TEXT = '';
  1640. changed = true;
  1641. }
  1642.  
  1643. if (!VARS.Options.hasOwnProperty('VERBOSITY_LEVEL')) {
  1644. VARS.Options.VERBOSITY_LEVEL = KeyWords.DLG_VERBOSITY.defaultValue;
  1645. changed = true;
  1646. }
  1647. if (!VARS.Options.hasOwnProperty('VERBOSITY_MESSAGE_COLOUR')) {
  1648. VARS.Options.VERBOSITY_MESSAGE_COLOUR = '';
  1649. changed = true;
  1650. }
  1651. if (!VARS.Options.hasOwnProperty('VERBOSITY_MESSAGE_BG_COLOUR')) {
  1652. VARS.Options.VERBOSITY_MESSAGE_BG_COLOUR = KeyWords.VERBOSITY_MESSAGE_BG_COLOUR.defaultValue;
  1653. changed = true;
  1654. }
  1655. if (!VARS.Options.hasOwnProperty('VERBOSITY_DEBUG')) {
  1656. VARS.Options.VERBOSITY_DEBUG = KeyWords.VERBOSITY_DEBUG.defaultValue;
  1657. changed = true;
  1658. }
  1659.  
  1660. if (!VARS.Options.hasOwnProperty('CMF_BTN_OPTION')) {
  1661. VARS.Options.CMF_BTN_OPTION = KeyWords.CMF_BTN_OPTION.defaultValue;
  1662. changed = true;
  1663. }
  1664. if (!VARS.Options.hasOwnProperty('CMF_DIALOG_OPTION')) {
  1665. VARS.Options.CMF_DIALOG_OPTION = KeyWords.CMF_DIALOG_OPTION.defaultValue;
  1666. changed = true;
  1667. }
  1668. if (!VARS.Options.hasOwnProperty('CMF_BORDER_COLOUR')) {
  1669. VARS.Options.CMF_BORDER_COLOUR = KeyWords.CMF_BORDER_COLOUR.defaultValue;
  1670. changed = true;
  1671. }
  1672. else {
  1673. if (VARS.Options.CMF_BORDER_COLOUR === '') {
  1674. VARS.Options.CMF_BORDER_COLOUR = KeyWords.CMF_BORDER_COLOUR.defaultValue;
  1675. }
  1676. }
  1677.  
  1678. if (changed) {
  1679. // - save the changes ...
  1680. // -- usually happen if first time setup or change in Options' variables.
  1681. let result = await set(DBVARS.DBKey, JSON.stringify(VARS.Options), DBVARS.ostore).then(() => {
  1682. return true;
  1683. }).catch((err) => {
  1684. console.info(`${log}getUserOptions() > changed > saving - failed, Error: ${err}`);
  1685. return false;
  1686. });
  1687. if (VARS.Options.VERBOSITY_DEBUG) {
  1688. if (result) {
  1689. console.info(`${log}Changed - success`);
  1690. }
  1691. else {
  1692. console.info(`${log}Changed - failed`);
  1693. }
  1694. }
  1695. }
  1696.  
  1697. // split the blocks of texts entries into arrays and translate to lowercase.
  1698. VARS.Filters = new Object();
  1699. // -- news feed
  1700. VARS.Filters.NF_BLOCKED_TEXT = [];
  1701. VARS.Filters.NF_BLOCKED_TEXT_LC = [];
  1702. if (VARS.Options.NF_BLOCKED_ENABLED) {
  1703. VARS.Filters.NF_BLOCKED_TEXT = VARS.Options.NF_BLOCKED_TEXT.split('¦¦');
  1704. VARS.Filters.NF_BLOCKED_TEXT_LC = VARS.Filters.NF_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  1705. }
  1706. VARS.Filters.NF_BLOCKED_ENABLED = ((VARS.Filters.NF_BLOCKED_TEXT.length > 0) && (VARS.Filters.NF_BLOCKED_TEXT[0] !== ''));
  1707. // -- groups feed
  1708. VARS.Filters.GF_BLOCKED_TEXT = [];
  1709. VARS.Filters.GF_BLOCKED_TEXT_LC = [];
  1710. if (VARS.Options.GF_BLOCKED_ENABLED) {
  1711. VARS.Filters.GF_BLOCKED_TEXT = VARS.Options.GF_BLOCKED_TEXT.split('¦¦');
  1712. VARS.Filters.GF_BLOCKED_TEXT_LC = VARS.Filters.GF_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  1713. }
  1714. VARS.Filters.GF_BLOCKED_ENABLED = ((VARS.Filters.GF_BLOCKED_TEXT.length > 0) && (VARS.Filters.GF_BLOCKED_TEXT[0] !== ''));
  1715. // -- watch videos feed
  1716. VARS.Filters.VF_BLOCKED_TEXT = [];
  1717. VARS.Filters.VF_BLOCKED_TEXT_LC = [];
  1718. if (VARS.Options.VF_BLOCKED_ENABLED) {
  1719. VARS.Filters.VF_BLOCKED_TEXT = VARS.Options.VF_BLOCKED_TEXT.split('¦¦');
  1720. VARS.Filters.VF_BLOCKED_TEXT_LC = VARS.Filters.VF_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  1721. }
  1722. VARS.Filters.VF_BLOCKED_ENABLED = ((VARS.Filters.VF_BLOCKED_TEXT.length > 0) && (VARS.Filters.VF_BLOCKED_TEXT[0] !== ''));
  1723.  
  1724. DBVARS.optionsReady = true;
  1725. }
  1726.  
  1727. // -- run some functions now - not dependent on HEAD being available.
  1728. // (includes getUserOptions())
  1729. setLanguageAndOptions();
  1730.  
  1731. // -- dailog box for displaying options (called in runMO)
  1732. function buildMoppingDialog() {
  1733. // build the dialog box component ...
  1734. // -- BODY must be available for use.
  1735. // -- used for displaying/getting/setting the various options
  1736.  
  1737. function createCB(cbName, cbKeyWord, cbReadOnly = false) {
  1738. let div = document.createElement('div');
  1739. let cb = document.createElement('input');
  1740. cb.type = 'checkbox';
  1741. cb.name = cbName;
  1742. cb.value = cbKeyWord;
  1743. cb.checked = VARS.Options[cbKeyWord];
  1744. let label = document.createElement('label');
  1745. if (cbReadOnly) {
  1746. cb.checked = true;
  1747. cb.disabled = true;
  1748. label.setAttribute('disabled', 'disabled');
  1749. }
  1750. label.appendChild(cb);
  1751. if (KeyWords[cbKeyWord]) {
  1752. if (Array.isArray(KeyWords[cbKeyWord][VARS.language]) === false) {
  1753. label.appendChild(document.createTextNode(KeyWords[cbKeyWord][VARS.language]));
  1754. }
  1755. else {
  1756. label.appendChild(document.createTextNode(Array.from(KeyWords[cbKeyWord][VARS.language]).join(', ')));
  1757. }
  1758. }
  1759. else if (['NF_SPONSORED', 'GF_SPONSORED', 'VF_SPONSORED'].indexOf(cbKeyWord) >= 0) {
  1760. label.appendChild(document.createTextNode(KeyWords.SPONSORED[VARS.language]));
  1761. }
  1762. else {
  1763. label.appendChild(document.createTextNode(cbKeyWord));
  1764. }
  1765. div.appendChild(label);
  1766. return div;
  1767. }
  1768.  
  1769. function createRB(rbName, rbValue, rbLabelText) {
  1770. let div = document.createElement('div');
  1771. let rb = document.createElement('input');
  1772. rb.type = 'radio';
  1773. rb.name = rbName;
  1774. rb.value = rbValue;
  1775. rb.checked = (VARS.Options[rbName] === rbValue);
  1776. let label = document.createElement('label');
  1777. label.appendChild(rb);
  1778. label.appendChild(document.createTextNode(rbLabelText));
  1779. div.appendChild(label);
  1780. return div;
  1781. }
  1782.  
  1783. function createInput(iName, iLabel) {
  1784. let div = document.createElement('div');
  1785. let input = document.createElement('input');
  1786. input.type = 'text';
  1787. input.name = iName;
  1788. input.value = VARS.Options[iName];
  1789. let label = document.createElement('label');
  1790. label.appendChild(document.createTextNode(iLabel));
  1791. label.appendChild(document.createElement('br'));
  1792. label.appendChild(input);
  1793. div.appendChild(label);
  1794. return div;
  1795. }
  1796.  
  1797. function createDialog() {
  1798. let dlg, hdr, hdr1, hdr2, hdr3, htxt, stxt, btn, cnt, fs, l, s, ta, footer;
  1799.  
  1800. // -- wrapper
  1801. dlg = document.createElement('div');
  1802. dlg.id = 'fbcmf';
  1803. dlg.className = 'fb-cmf';
  1804. // class "show" reveals the dialog.
  1805. // -- header (logo + title + close button)
  1806. hdr = document.createElement('header');
  1807. hdr1 = document.createElement('div');
  1808. hdr1.className = 'fb-cmf-icon';
  1809. hdr1.innerHTML = VARS.logoHTML;
  1810.  
  1811. hdr2 = document.createElement('div');
  1812. hdr2.className = 'fb-cmf-title';
  1813. htxt = document.createElement('div');
  1814. htxt.textContent = KeyWords.DLG_TITLE['en'];
  1815. hdr2.appendChild(htxt);
  1816. if (VARS.language !== 'en') {
  1817. stxt = document.createElement('small');
  1818. stxt.textContent = `(${KeyWords.DLG_TITLE[VARS.language]
  1819. })`;
  1820. hdr2.appendChild(stxt);
  1821. hdr2.classList.add('fb-cmf-lang-2');
  1822. }
  1823. else {
  1824. hdr2.classList.add('fb-cmf-lang-1')
  1825. }
  1826. hdr3 = document.createElement('div');
  1827. hdr3.className = 'fb-cmf-close';
  1828. btn = document.createElement('button');
  1829. btn.textContent = 'X';
  1830. btn.addEventListener('click', toggleMD, false);
  1831. hdr3.appendChild(btn);
  1832.  
  1833. hdr.appendChild(hdr1);
  1834. hdr.appendChild(hdr2);
  1835. hdr.appendChild(hdr3);
  1836. dlg.appendChild(hdr);
  1837.  
  1838. // content container
  1839. cnt = document.createElement('div');
  1840. cnt.classList.add('content');
  1841.  
  1842. // -- News Feed options
  1843. fs = document.createElement('fieldset');
  1844. l = document.createElement('legend');
  1845. l.textContent = KeyWords.DLG_NF[VARS.language];
  1846. fs.appendChild(l);
  1847. fs.appendChild(createCB('cbNF', 'NF_SPONSORED', true));
  1848. for (const key in KeyWords) {
  1849. if (key.slice(0, 3) === 'NF_' && key.slice(0, 10) !== 'NF_BLOCKED') {
  1850. fs.appendChild(createCB('cbNF', key));
  1851. }
  1852. }
  1853. cnt.appendChild(fs);
  1854.  
  1855. // -- Keywords to block - News Feed
  1856. fs = document.createElement('fieldset');
  1857. l = document.createElement('legend');
  1858. l.textContent = KeyWords.DLG_NF_BLOCK[VARS.language];
  1859. fs.appendChild(l);
  1860. fs.appendChild(createCB('cbNFBT', 'NF_BLOCKED_ENABLED'));
  1861. s = document.createElement('small');
  1862. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE[VARS.language]));
  1863. fs.appendChild(s);
  1864. ta = document.createElement('textarea');
  1865. ta.name = 'NF_BLOCKED_TEXT';
  1866. ta.textContent = VARS.Filters.NF_BLOCKED_TEXT.join('\n');
  1867. fs.appendChild(ta);
  1868. cnt.appendChild(fs);
  1869.  
  1870. // -- Groups Feed options
  1871. fs = document.createElement('fieldset');
  1872. l = document.createElement('legend');
  1873. l.textContent = KeyWords.DLG_GF[VARS.language];
  1874. fs.appendChild(l);
  1875. fs.appendChild(createCB('cbGF', 'GF_SPONSORED', true));
  1876. for (const key in KeyWords) {
  1877. if (key.slice(0, 3) === 'GF_' && key.slice(0, 10) !== 'GF_BLOCKED') {
  1878. fs.appendChild(createCB('cbGF', key));
  1879. }
  1880. }
  1881. cnt.appendChild(fs);
  1882.  
  1883. // -- Keywords to block - Groups Feed
  1884. fs = document.createElement('fieldset');
  1885. l = document.createElement('legend');
  1886. l.textContent = KeyWords.DLG_GF_BLOCK[VARS.language];
  1887. fs.appendChild(l);
  1888. fs.appendChild(createCB('cbGFBT', 'GF_BLOCKED_ENABLED'));
  1889. s = document.createElement('small');
  1890. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE[VARS.language]));
  1891. fs.appendChild(s);
  1892. ta = document.createElement('textarea');
  1893. ta.name = 'GF_BLOCKED_TEXT';
  1894. ta.textContent = VARS.Filters.GF_BLOCKED_TEXT.join('\n');
  1895. fs.appendChild(ta);
  1896. cnt.appendChild(fs);
  1897.  
  1898.  
  1899. // -- Watch Videos Feed options
  1900. fs = document.createElement('fieldset');
  1901. l = document.createElement('legend');
  1902. l.textContent = KeyWords.DLG_VF[VARS.language];
  1903. fs.appendChild(l);
  1904. fs.appendChild(createCB('cbVF', 'VF_SPONSORED', true));
  1905. for (const key in KeyWords) {
  1906. if (key.slice(0, 3) === 'VF_' && key.slice(0, 10) !== 'VF_BLOCKED') {
  1907. fs.appendChild(createCB('cbVF', key));
  1908. }
  1909. }
  1910. cnt.appendChild(fs);
  1911.  
  1912. // -- Keywords to block - Watch Videos Feed
  1913. fs = document.createElement('fieldset');
  1914. l = document.createElement('legend');
  1915. l.textContent = KeyWords.DLG_VF_BLOCK[VARS.language];
  1916. fs.appendChild(l);
  1917. fs.appendChild(createCB('cbVFBT', 'VF_BLOCKED_ENABLED'));
  1918. s = document.createElement('small');
  1919. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE[VARS.language]));
  1920. fs.appendChild(s);
  1921. ta = document.createElement('textarea');
  1922. ta.name = 'VF_BLOCKED_TEXT';
  1923. ta.textContent = VARS.Filters.VF_BLOCKED_TEXT.join('\n');
  1924. fs.appendChild(ta);
  1925. cnt.appendChild(fs);
  1926.  
  1927. // -- MarketPlace option(s)
  1928. fs = document.createElement('fieldset');
  1929. l = document.createElement('legend');
  1930. l.textContent = KeyWords.DLG_MP[VARS.language];
  1931. fs.appendChild(l);
  1932. fs.appendChild(createCB('cbMP', 'MP_SPONSORED', true));
  1933. cnt.appendChild(fs);
  1934.  
  1935. // -- Other items options
  1936. fs = document.createElement('fieldset');
  1937. l = document.createElement('legend');
  1938. l.textContent = KeyWords.DLG_OTHER[VARS.language];
  1939. fs.appendChild(l);
  1940. for (const key in KeyWords) {
  1941. if (key.slice(0, 10) === 'OTHER_INFO') {
  1942. fs.appendChild(createCB('cbOther', key));
  1943. }
  1944. }
  1945. cnt.appendChild(fs);
  1946.  
  1947. // -- Verbosity
  1948. fs = document.createElement('fieldset');
  1949. l = document.createElement('legend');
  1950. l.textContent = KeyWords.DLG_VERBOSITY[VARS.language];
  1951. fs.appendChild(l);
  1952. s = document.createElement('span');
  1953. s.appendChild(document.createTextNode(`${KeyWords.DLG_VERBOSITY_MESSAGE[VARS.language]}:`));
  1954. fs.appendChild(s);
  1955. fs.appendChild(createRB('VERBOSITY_LEVEL', '0', `<${KeyWords.VERBOSITY_NO_MESSAGE[VARS.language]}>`));
  1956. fs.appendChild(createRB('VERBOSITY_LEVEL', '1', `${KeyWords.VERBOSITY_MESSAGE[VARS.language][0]}______`));
  1957. fs.appendChild(createRB('VERBOSITY_LEVEL', '2', `7${KeyWords.VERBOSITY_MESSAGE[VARS.language][1]}`));
  1958. fs.appendChild(document.createElement('br'));
  1959. fs.appendChild(createInput('VERBOSITY_MESSAGE_COLOUR', `${KeyWords.VERBOSITY_MESSAGE_COLOUR[VARS.language]}:`));
  1960. fs.appendChild(createInput('VERBOSITY_MESSAGE_BG_COLOUR', `${KeyWords.VERBOSITY_MESSAGE_BG_COLOUR[VARS.language]}:`));
  1961. fs.appendChild(document.createElement('br'));
  1962. fs.appendChild(createCB('cbVD', 'VERBOSITY_DEBUG'));
  1963. cnt.appendChild(fs);
  1964.  
  1965. // -- cmf customisations
  1966. fs = document.createElement('fieldset');
  1967. l = document.createElement('legend');
  1968. l.textContent = KeyWords.CMF_CUSTOMISATIONS[VARS.language];
  1969. fs.appendChild(l);
  1970. fs.appendChild(document.createTextNode(`${KeyWords.CMF_BTN_LOCATION[VARS.language]}:`));
  1971. fs.appendChild(createRB('CMF_BTN_OPTION', '0', KeyWords.CMF_BTN_OPTION[VARS.language][0]));
  1972. fs.appendChild(createRB('CMF_BTN_OPTION', '1', KeyWords.CMF_BTN_OPTION[VARS.language][1]));
  1973. fs.appendChild(document.createElement('br'));
  1974. fs.appendChild(document.createTextNode(`${KeyWords.CMF_DIALOG_LOCATION[VARS.language]}:`));
  1975. fs.appendChild(createRB('CMF_DIALOG_OPTION', '0', KeyWords.CMF_DIALOG_OPTION[VARS.language][0]));
  1976. fs.appendChild(createRB('CMF_DIALOG_OPTION', '1', KeyWords.CMF_DIALOG_OPTION[VARS.language][1]));
  1977. fs.appendChild(document.createElement('br'));
  1978. fs.appendChild(createInput('CMF_BORDER_COLOUR', `${KeyWords.CMF_BORDER_COLOUR[VARS.language]}:`));
  1979. cnt.appendChild(fs);
  1980.  
  1981. // -- tips
  1982. fs = document.createElement('fieldset');
  1983. l = document.createElement('legend');
  1984. l.textContent = KeyWords.DLG_TIPS[VARS.language];
  1985. fs.appendChild(l);
  1986. s = document.createElement('span');
  1987. s.appendChild(document.createTextNode(KeyWords.DLG_TIPS_CONTENT[VARS.language]));
  1988. fs.appendChild(s);
  1989. cnt.appendChild(fs);
  1990.  
  1991. dlg.appendChild(cnt);
  1992.  
  1993. // -- Actions (buttons)
  1994. footer = document.createElement('footer');
  1995. footer.classList.add('buttons');
  1996. btn = document.createElement('button');
  1997. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][0]; // save
  1998. btn.addEventListener('click', saveUserOptions, false);
  1999. footer.appendChild(btn);
  2000. btn = document.createElement('button');
  2001. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][1]; // close
  2002. btn.addEventListener('click', toggleMD, false);
  2003. footer.appendChild(btn);
  2004. btn = document.createElement('button');
  2005. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][2]; // export
  2006. btn.addEventListener('click', exportUserOptions, false);
  2007. footer.appendChild(btn);
  2008. btn = document.createElement('button');
  2009. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][3]; // import
  2010. btn.setAttribute('id', 'BTNImport');
  2011. footer.appendChild(btn);
  2012. // -- file input field is hidden, but triggered by the Import button.
  2013. let fileImport = document.createElement('input');
  2014. fileImport.setAttribute('type', 'file');
  2015. fileImport.setAttribute('id', `FI${postAtt}`);
  2016. fileImport.classList.add('fileInput');
  2017. footer.appendChild(fileImport);
  2018. // -- import results
  2019. let div = document.createElement('div');
  2020. div.classList.add('fileResults');
  2021. footer.appendChild(div);
  2022.  
  2023. dlg.appendChild(footer);
  2024.  
  2025. document.body.appendChild(dlg);
  2026.  
  2027. // -- add event listeners to the import button and file input field
  2028. let fileInput = document.getElementById(`FI${postAtt}`);
  2029. fileInput.addEventListener('change', importUserOptions, false);
  2030. // -- make the btn Import trigger file input ...
  2031. let btnImport = document.getElementById('BTNImport');
  2032. btnImport.addEventListener('click', function () {
  2033. fileInput.click()
  2034. }, false);
  2035. }
  2036.  
  2037. function updateDialog() {
  2038. let content = document.getElementById('fbcmf').querySelector('.content');
  2039. if (content) {
  2040. let cbs = Array.from(content.querySelectorAll('input[type="checkbox"]'));
  2041. cbs.forEach(cb => {
  2042. if (VARS.Options.hasOwnProperty(cb.value)) {
  2043. cb.checked = VARS.Options[cb.value];
  2044. }
  2045. });
  2046. let rbs = content.querySelectorAll('input[type="radio"]');
  2047. rbs.forEach(rb => {
  2048. if (VARS.Options.hasOwnProperty(rb.name)) {
  2049. rb.checked = VARS.Options[rb.name];
  2050. }
  2051. });
  2052. let tas = Array.from(content.querySelectorAll('textarea'));
  2053. tas.forEach(ta => {
  2054. if (VARS.Options.hasOwnProperty(ta.name)) {
  2055. ta.value = VARS.Options[ta.name].replaceAll('¦¦', '\n');
  2056. }
  2057. });
  2058. let inputs = Array.from(content.querySelectorAll('input[type="text"]'));
  2059. inputs.forEach(inp => {
  2060. if (VARS.Options.hasOwnProperty[inp.name]) {
  2061. inp.value = VARS.Options[inp.name];
  2062. }
  2063. });
  2064. }
  2065. }
  2066.  
  2067. async function saveUserOptions(event, source = 'dialog') {
  2068. // -- save Options in indexeddb as JSON.
  2069. if (source === 'dialog') {
  2070. let md, cbs, rbs, tas, inputs;
  2071.  
  2072. // -- grab the dialog box and get the various options.
  2073. md = document.getElementById('fbcmf');
  2074. // -- checkboxes
  2075. cbs = Array.from(md.querySelectorAll('input[type="checkbox"]'));
  2076. cbs.forEach(cb => {
  2077. VARS.Options[cb.value] = cb.checked;
  2078. });
  2079. // -- radios
  2080. rbs = md.querySelectorAll('input[type="radio"]:checked');
  2081. rbs.forEach(rb => {
  2082. VARS.Options[rb.name] = rb.value;
  2083. });
  2084. // -- text input
  2085. inputs = Array.from(md.querySelectorAll('input[type="text"]'));
  2086. inputs.forEach(inp => {
  2087. VARS.Options[inp.name] = inp.value;
  2088. });
  2089. // -- Blocked text (textareas)
  2090. tas = md.querySelectorAll('textarea');
  2091. tas.forEach(ta => {
  2092. let txtn = ta.value.split('\n');
  2093. let txts = [];
  2094. txtn.forEach(txt => {
  2095. if (txt.trim().length > 0) {
  2096. txts.push(txt); // -- do not trim - retain entry as is.
  2097. }
  2098. });
  2099. VARS.Options[ta.name] = txts.join('¦¦');
  2100. });
  2101. }
  2102.  
  2103. // -- clear out items that are not valid.
  2104. let md = document.getElementById('fbcmf');
  2105. let inputs = Array.from(md.querySelectorAll('input:not([type="file"]), textarea'));
  2106. let validNames = [];
  2107. inputs.forEach(inp => {
  2108. validNames.push((inp.type === 'checkbox') ? inp.value : inp.name);
  2109. });
  2110. for (let key in VARS.Options) {
  2111. if (validNames.indexOf(key) < 0) {
  2112. if (VARS.Options.VERBOSITY_DEBUG) {
  2113. console.info(`${log}SUO : deleting key:`, key);
  2114. }
  2115. delete VARS.Options[key];
  2116. }
  2117. }
  2118.  
  2119. // -- save options
  2120. let result = await set(DBVARS.DBKey, JSON.stringify(VARS.Options), DBVARS.ostore).then(() => {
  2121. // -- refresh options and split blocks of texts
  2122. let result2 = getUserOptions().then(() => {
  2123. return true;
  2124. });
  2125. return result2;
  2126. }).catch((err) => {
  2127. console.info(`${log}saveUserOptions() > set() -> Error:`, err);
  2128. return false;
  2129. });
  2130. if (VARS.VERBOSITY_DEBUG) {
  2131. console.info(`${log}saveUserOptions() > set() -> Saved:`, result);
  2132. }
  2133. // - update some variables.
  2134. if (result) {
  2135. setFeedSettings(true);
  2136. addCSS();
  2137. addExtraCSS();
  2138. }
  2139. document.querySelector('#fbcmf .fileResults').textContent = `Last Saved @ ${(new Date).toTimeString().slice(0, 8)}`;
  2140.  
  2141. // -- reset the posts and do the cleaning/mopping up again ...
  2142. if (VARS.isAF) {
  2143. // -- "reset" scan counts
  2144. VARS.scanCountStart += 100;
  2145. VARS.scanCountMaxLoop += 100;
  2146. // -- "purge" notifications
  2147. let elements = Array.from(document.querySelectorAll(`p[${postAtt}]`));
  2148. if (elements.length > 0) {
  2149. elements.forEach(el => {
  2150. let elParent = el.parentElement;
  2151. elParent.removeChild(el);
  2152. });
  2153. }
  2154. // -- remove attribute
  2155. elements = Array.from(document.querySelectorAll(`[${postAtt}]`));
  2156. if (elements.length > 0) {
  2157. elements.forEach(el => {
  2158. el.removeAttribute(postAtt);
  2159. el.classList.remove(VARS.cssHide);
  2160. el.classList.remove(VARS.cssHideEl);
  2161. });
  2162. }
  2163. // -- check that the classes have been removed
  2164. elements = Array.from(document.querySelectorAll(`.${VARS.cssHide}`));
  2165. if (elements.length > 0) {
  2166. elements.forEach(el => {
  2167. el.classList.remove(VARS.cssHide);
  2168. el.classList.remove(VARS.cssHideEl);
  2169. });
  2170. }
  2171. elements = Array.from(document.querySelectorAll(`.${VARS.cssHideEl}`));
  2172. if (elements.length > 0) {
  2173. elements.forEach(el => {
  2174. el.classList.remove(VARS.cssHideEl);
  2175. el.classList.remove(VARS.cssHide);
  2176. });
  2177. }
  2178.  
  2179. if (VARS.isNF) {
  2180. mopUpTheNewsFeed();
  2181. }
  2182. else if (VARS.isGF) {
  2183. mopUpTheGroupsFeed();
  2184. }
  2185. else if (VARS.isVF) {
  2186. mopUpTheWatchVideosFeed();
  2187. }
  2188. else if (VARS.isMF) {
  2189. mopUpTheMarketplaceFeed();
  2190. }
  2191. else if (VARS.isSF) {
  2192. mopUpTheSearchFeed();
  2193. }
  2194. }
  2195. }
  2196.  
  2197. function exportUserOptions() {
  2198. // -- export user's options into a text file.
  2199. let exportOptions = document.createElement("a");
  2200. exportOptions.href = window.URL.createObjectURL(new Blob([JSON.stringify(VARS.Options)], { type: "text/plain" }));
  2201. exportOptions.download = 'fb - clean my feeds - settings.json';
  2202. exportOptions.click();
  2203. exportOptions.remove();
  2204. document.querySelector('#fbcmf .fileResults').textContent = 'Exported: fb - clean my feeds - settings.json';
  2205. }
  2206.  
  2207. function importUserOptions(event) {
  2208. // -- import user's options from a text file.
  2209. let fileResults = document.querySelector('#fbcmf .fileResults');
  2210. let file = event.target.files[0];
  2211. let fileN = event.target.files[0].name;
  2212. // -- setup reader for reading in the file
  2213. let reader = new FileReader();
  2214. // -- what to do when reader is called.
  2215. reader.onload = (file) => {
  2216. try {
  2217. let fileContent = JSON.parse(file.target.result);
  2218. if (fileContent.hasOwnProperty('NF_SPONSORED') && fileContent.hasOwnProperty('GF_SPONSORED') && fileContent.hasOwnProperty('VF_SPONSORED') && fileContent.hasOwnProperty('MP_SPONSORED')) {
  2219. VARS.Options = fileContent;
  2220. // console.info(`${log}importUserOptions > reader.onload: Options:`, VARS.Options);
  2221. // -- save the file to the db
  2222. // -- save will run getUserOptions();
  2223. let result = saveUserOptions(null, 'file').then(() => {
  2224. updateDialog();
  2225. fileResults.textContent = `File imported: ${fileN}`;
  2226. return true;
  2227. });
  2228. }
  2229. else {
  2230. fileResults.textContent = `File NOT imported: ${fileN}`;
  2231. }
  2232. } catch (e) {
  2233. fileResults.textContent = `File NOT imported: ${fileN}`;
  2234. }
  2235. }
  2236. // -- call reader to read in the file ...
  2237. reader.readAsText(file);
  2238. }
  2239.  
  2240. function toggleMD() {
  2241. let dlg = document.getElementById('fbcmf');
  2242. dlg.classList.toggle('show');
  2243. }
  2244.  
  2245. function createToggleButton() {
  2246. let btn = document.createElement('button');
  2247. btn.innerHTML = VARS.logoHTML;
  2248. btn.id = 'fbcmfToggle';
  2249. btn.title = KeyWords.DLG_TITLE[VARS.language];
  2250. btn.className = 'fb-cmf-toggle fb-cmf-icon';
  2251. document.body.appendChild(btn);
  2252. btn.addEventListener('click', toggleMD, false);
  2253. VARS.btnToggleEl = btn;
  2254. }
  2255.  
  2256. createToggleButton();
  2257. createDialog();
  2258. }
  2259. // --- end of dailog code.
  2260.  
  2261.  
  2262. // adjust some settings - if URL has changed.
  2263. function setFeedSettings(forceUpdate = false) {
  2264. if ((VARS.prevURL !== window.location.href) || forceUpdate) {
  2265. // - remember current page's URL
  2266. VARS.prevURL = window.location.href;
  2267. VARS.prevPathname = window.location.pathname;
  2268. VARS.prevQuery = window.location.search;
  2269. // - reset feeds flags
  2270. VARS.isNF = false;
  2271. VARS.isGF = false;
  2272. VARS.isVF = false;
  2273. VARS.isMF = false;
  2274. VARS.isSF = false;
  2275. if ((VARS.prevPathname === '/') || (VARS.prevPathname === '/home.php')) {
  2276. // -- news feed
  2277. VARS.isNF = true;
  2278. }
  2279. else if (VARS.prevPathname.indexOf('/groups/') >= 0) {
  2280. // -- groups feed
  2281. VARS.isGF = true;
  2282. if (VARS.prevPathname.indexOf('/groups/feed') >= 0) {
  2283. VARS.gfType = 'groups'
  2284. }
  2285. else if (VARS.prevPathname.indexOf('/groups/search') >= 0) {
  2286. VARS.gfType = 'search';
  2287. }
  2288. else {
  2289. VARS.gfType = 'group';
  2290. }
  2291. }
  2292. else if (VARS.prevPathname.indexOf('/watch') >= 0) {
  2293. // -- watch videos feed
  2294. VARS.isVF = true;
  2295. if (VARS.prevPathname.indexOf('/watch/search') >= 0) {
  2296. VARS.vfType = 'search';
  2297. }
  2298. else if (VARS.prevQuery.indexOf('?ref=seach') >= 0) {
  2299. VARS.vfType = 'item';
  2300. }
  2301. else if (VARS.prevQuery.indexOf('?v=') >= 0) {
  2302. VARS.vfType = 'item';
  2303. }
  2304. else {
  2305. VARS.vfType = 'videos';
  2306. }
  2307. }
  2308. else if (VARS.prevPathname.indexOf('/marketplace') >= 0) {
  2309. // -- marketplace
  2310. VARS.isMF = true;
  2311. if (VARS.isMF && VARS.prevPathname.indexOf('/item/') >= 0) {
  2312. // - viewing an item
  2313. VARS.mpType = 'item';
  2314. }
  2315. else if (VARS.prevPathname.indexOf('/search') >= 0) {
  2316. // - searching within marketplace ... (has similar layout to category feed)
  2317. VARS.mpType = 'search';
  2318. }
  2319. else if (VARS.prevPathname.indexOf('/category/') >= 0) {
  2320. // - category feed
  2321. VARS.mpType = 'category';
  2322. }
  2323. else {
  2324. VARS.mpType = 'marketplace';
  2325. }
  2326. }
  2327. else if (VARS.prevPathname.indexOf('/commerce/listing/') >= 0) {
  2328. // - a group's for sale post - redirected to marketplace ...
  2329. // - same layout as a marketplace item.
  2330. VARS.isMF = true;
  2331. VARS.mpType = 'item';
  2332. }
  2333. else if (['/search/top/', '/search/top', '/search/posts/', '/search/posts', '/search/pages/'].indexOf(VARS.prevPathname) >= 0) {
  2334. // -- search results page : "All" and "Posts"
  2335. VARS.isSF = true;
  2336. }
  2337.  
  2338. VARS.isAF = (VARS.isNF || VARS.isGF || VARS.isVF || VARS.isMF || VARS.isSF);
  2339.  
  2340. // when to display the cmf button
  2341. if (VARS.isAF) {
  2342. if (VARS.btnToggleEl) {
  2343. VARS.btnToggleEl.classList.add('show');
  2344. }
  2345. }
  2346. else {
  2347. if (VARS.btnToggleEl) {
  2348. VARS.btnToggleEl.classList.remove('show');
  2349. }
  2350. }
  2351.  
  2352. // - reset consecutive count of hidden posts
  2353. VARS.echoCount = 0;
  2354.  
  2355. // console.info(`${log}setFeedSettings() :: isAF: ${VARS.isAF}; isNF: ${VARS.isNF}; isGF: ${VARS.isGF}; isVF: ${VARS.isVF}; isMF: ${VARS.isMF}; isSF: ${VARS.isSF}`);
  2356. return true;
  2357. }
  2358. else {
  2359. return false;
  2360. }
  2361. }
  2362.  
  2363. function doLightDusting(post) {
  2364. // - remove 'dusty' elements that interfere with querySelectorAll, nth-of-type, :not() queries.
  2365. // -- needs to run a few times to be effective.
  2366. let scanCount = VARS.scanCountStart;
  2367. if (post[postPropDS] !== undefined) {
  2368. scanCount = parseInt(post[postPropDS]);
  2369. scanCount = (scanCount < VARS.scanCountStart) ? VARS.scanCountStart : scanCount;
  2370. }
  2371. if (scanCount < VARS.scanCountMaxLoop) {
  2372. let dustySpots = Array.from(post.querySelectorAll('[data-0="0"]'));
  2373. if (dustySpots.length > 0) {
  2374. for (let i = dustySpots.length - 1; i >= 0; i--) {
  2375. dustySpots[i].parentElement.removeChild(dustySpots[i]);
  2376. }
  2377. }
  2378. scanCount++;
  2379. post[postPropDS] = scanCount;
  2380. }
  2381. }
  2382.  
  2383. function scanTreeForText(theNode) {
  2384. let arrayTextValues = [];
  2385. let n,
  2386. walk = document.createTreeWalker(theNode, NodeFilter.SHOW_TEXT, null, false);
  2387. while ((n = walk.nextNode())) {
  2388. let val = n.textContent.trim();
  2389. if ((val !== '') && (val.length > 1)) {
  2390. // - keep 2+ char strings.
  2391. arrayTextValues.push(val);
  2392. }
  2393. }
  2394. return arrayTextValues;
  2395. }
  2396.  
  2397. function extractTextContent(post, selector, maxBlocks) {
  2398. // - get the text node values of the regular feed posts
  2399. // -- scan the top portion of the posts (first maxBlocks blocks)
  2400. // -- parameters:
  2401. // post: post to scan
  2402. // selector: querySelector's query
  2403. // maxBlocks: max number of blocks to scan
  2404. let blocks = Array.from(post.querySelectorAll(selector));
  2405. // console.info(log+'extractTC:', selector, maxBlocks, blocks, post);
  2406. let arrayTextValues = [];
  2407. if (blocks.length) {
  2408. // - process first maxBlocks blocks
  2409. // - block 0 = Suggested headings, block 1 = title/heading, block 2 = content, block 3 = info box / comments, block 4 = comments
  2410. // - nb: some suggested posts only have one block ...
  2411. let bL = Math.min(maxBlocks, blocks.length);
  2412. for (let b = 0; b < bL; b++) {
  2413. if (blocks[b].innerHTML.length > 0) {
  2414. arrayTextValues = arrayTextValues.concat(scanTreeForText(blocks[b]));
  2415. }
  2416. }
  2417. }
  2418. return arrayTextValues;
  2419. }
  2420.  
  2421. function createHiddenNote(reason) {
  2422. let echoEl = document.createElement('p');
  2423. echoEl.setAttribute(postAtt, '');
  2424. let echoBtnBox = document.createElement('div');
  2425. let echoBtn = document.createElement('button');
  2426. echoBtn.textContent = '___';
  2427. echoBtn.addEventListener('click', togglePost, false);
  2428. echoBtnBox.appendChild(echoBtn);
  2429. echoEl.appendChild(echoBtnBox);
  2430. echoEl.appendChild(document.createTextNode(KeyWords.VERBOSITY_MESSAGE[VARS.language][0] + reason));
  2431. return echoEl;
  2432. }
  2433.  
  2434. function hideFeature(post, reason) {
  2435. // hide something - keep it out of the regular feed stuff.
  2436. // - no counter
  2437. if ((parseInt(VARS.Options.VERBOSITY_LEVEL, 10) > 0) && (reason !== '')) {
  2438. let echoEl = createHiddenNote(reason);
  2439. let postFirstChild = post.querySelector(':scope > :first-child');
  2440. if (postFirstChild) {
  2441. postFirstChild.before(echoEl);
  2442. }
  2443. else {
  2444. // post has been changed while being processed (very rare)
  2445. }
  2446. }
  2447. post.classList.add(VARS.cssHide);
  2448. post.setAttribute(postAtt, reason);
  2449. }
  2450.  
  2451. function togglePost(evBtn) {
  2452. let elPost = evBtn.target.parentElement.parentElement.closest('div');
  2453. elPost.classList.toggle('show');
  2454. }
  2455.  
  2456. function hidePost(post, reason) {
  2457. // -- hide something ..
  2458. // -- if requested, echo post is hidden ..
  2459. if (VARS.isMF) {
  2460. // -- marketplace don't display a msg.
  2461. }
  2462. else if ((parseInt(VARS.Options.VERBOSITY_LEVEL, 10) > 0) && (reason !== '')) {
  2463. if (VARS.Options.VERBOSITY_LEVEL === '1') {
  2464. VARS.echoCount = 1;
  2465. }
  2466. if (VARS.echoCount < 2) {
  2467. // - 1 post hidden
  2468. let echoEl = createHiddenNote(reason);
  2469. let postFirstChild = post.querySelector(':scope > :first-child');
  2470. if (postFirstChild) {
  2471. postFirstChild.before(echoEl);
  2472. VARS.echoEl = echoEl;
  2473. }
  2474. else {
  2475. // post has been changed while being processed (very rare)
  2476. }
  2477. }
  2478. else {
  2479. // - 2+ posts hidden
  2480. VARS.echoEl.textContent = VARS.echoCount + KeyWords.VERBOSITY_MESSAGE[VARS.language][1];
  2481. }
  2482. }
  2483. post.classList.add(VARS.cssHide);
  2484. post.setAttribute(postAtt, reason);
  2485. }
  2486.  
  2487. function hideBlock(block, reason) {
  2488. //console.info(log + 'hiding block:', reason, block);
  2489. block.classList.add(VARS.cssHide);
  2490. block.setAttribute(postAtt, reason);
  2491. }
  2492.  
  2493. function cleanText(text) {
  2494. // - fb is using ASCII code 160 for whitespace ...
  2495. return text.replaceAll(String.fromCharCode(160), String.fromCharCode(32));
  2496. }
  2497.  
  2498. function isSponsored(post) {
  2499. // Is it a sponsored post?
  2500. // -- grab the block of code that usually holds the post's timestamp / sponsored text.
  2501. // -- there are various methods for displaying sponsored text.
  2502. // -- applies to News feed, Groups feed, Videos Feed
  2503. // -- mopUpTheMarketplaceFeed() looks after marketplace feed sponsored posts/items.
  2504.  
  2505. let daText = '';
  2506.  
  2507. // -- which method is FB using?
  2508.  
  2509. // -- try the shadow-root
  2510. // -- querySelector cannot find attribute "xlink:href", so we trick it ..
  2511. // -- [*|href] will match both html href and svg xlink:href, then use :not([href]) to exclude html href
  2512. let elWrapper = post.querySelector('span[id] > span > span > a[href] span > span > svg > use[*|href]:not([href]');
  2513. if (elWrapper !== null) {
  2514. let theID = elWrapper.getAttributeNS('http://www.w3.org/1999/xlink', 'href'); // the attribute has the "#" ...
  2515. let elSRB = document.querySelector('div > svg > ' + theID); // -- a TEXT element has the ID field.
  2516. daText = elSRB.textContent;
  2517. }
  2518. else {
  2519. // -- try the Flex/Order structure
  2520. elWrapper = post.querySelector(
  2521. 'span > span > span > a[href^="?"] > span > span[class] > [style*="order"], ' +
  2522. 'span > span > span > a[href="#"] > span > span[class] > [style*="order"], ' +
  2523. 'span > span > span > a[href^="?"] > span > span[class] > [style*="display"], ' +
  2524. 'span > span > span > a[href="#"] > span > span[class] > [style*="display"], ' +
  2525. 'span > span > span > a[href*="/ads/"] > span > span[class] > [style*="order"], ' +
  2526. 'span > span > span > a[href*="/ads/"] > span > span[class] > [style*="display"]'
  2527. );
  2528.  
  2529. if (elWrapper) {
  2530. // -- found a regular post structure
  2531. let arrText = [];
  2532. let cs = window.getComputedStyle(elWrapper);
  2533. // wrapper's order - set to 0 if has a value (css will ignore other values)
  2534. let wrapperOrder = (cs.order !== "") ? 0 : -1;
  2535. elWrapper.childNodes.forEach((cn) => {
  2536. if (cn.nodeType === Node.ELEMENT_NODE) {
  2537. let cs = window.getComputedStyle(cn);
  2538. if ((cs.position === 'relative') && (cs.display != 'none')) {
  2539. arrText[parseInt(cs.order, 10)] = cn.textContent;
  2540. }
  2541. }
  2542. else if ((cn.nodeType === Node.TEXT_NODE) && (wrapperOrder >= 0)) {
  2543. let nv = cn.nodeValue.replaceAll(String.fromCharCode(10), '');
  2544. if (nv.length > 0) {
  2545. arrText[wrapperOrder] = nv;
  2546. }
  2547. }
  2548. });
  2549. daText = arrText.join('');
  2550. }
  2551. else {
  2552. // -- try the non-Flex/Order structure
  2553. elWrapper = post.querySelector(
  2554. 'span > span > span > a[href^="?"] > span > span[class] > [class], ' +
  2555. 'span > span > span > a[href="#"] > span > span[class] > [class], ' +
  2556. 'span > span > span > a[href*="/ads/"] > span > span[class] > [class]'
  2557. );
  2558. if (elWrapper) {
  2559. // -- found a regular post structure
  2560. daText = '';
  2561. elWrapper.childNodes.forEach((cn) => {
  2562. if (cn.nodeType === Node.ELEMENT_NODE) {
  2563. let cs = window.getComputedStyle(cn);
  2564. if ((cs.position === 'relative') && (cs.display != 'none')) {
  2565. daText += cn.textContent;
  2566. }
  2567. }
  2568. else if (cn.nodeType === Node.TEXT_NODE) {
  2569. let nv = cn.nodeValue.replaceAll(String.fromCharCode(10), '');
  2570. if (nv.length > 0) {
  2571. daText += nv;
  2572. }
  2573. }
  2574. });
  2575. }
  2576. else {
  2577. // --- try the non-obfuscated structure
  2578. elWrapper = post.querySelector(
  2579. 'span > span > span > a[href="#"] > span, ' +
  2580. 'span > span > span > a[href*="/ads/"] > span'
  2581. );
  2582. if (elWrapper && elWrapper.children.length == 0) {
  2583. daText = elWrapper.textContent;
  2584. }
  2585. }
  2586. }
  2587. }
  2588. if (elWrapper !== null) {
  2589. // -- have a possible sponsored post ...
  2590. // --- regex pattern:
  2591. // --- [0-9] = ASCII digits
  2592. // --- [\u0660-\u0669] = Arabic digits
  2593. // --- שעה אחת = Hebrew for "one hour";
  2594. // --- ഒരു മണിക്കൂർ = Malaylam for "one/an hour"
  2595. // --- if pattern.test(...) is true, then not a sponsored post (sponsored posts do not have numbers)
  2596. // --- nb: regex testing - pattern.test() is quicker than string.match(pattern)
  2597. const pattern = /([0-9]|[\u0660-\u0669]|שעה אחת|ഒരു മണിക്കൂർ|^$)/;
  2598. daText = cleanText(daText).trim();
  2599. // console.info(`${log}is Sponsored post: >${daText}< >${!pattern.test(daText)}<`, post, elWrapper);
  2600. return ((daText.length > 0) && (!pattern.test(daText)));
  2601. }
  2602. else {
  2603. // -- not a sponsored post structure
  2604. return false;
  2605. }
  2606. }
  2607.  
  2608. function querySelectorAllNoChildren(container = document, query, minText = 0) {
  2609. if (query === '') {
  2610. return [];
  2611. };
  2612. let elements = container.querySelectorAll(query);
  2613. if (elements) {
  2614. return Array.from(elements).filter((el) => {
  2615. return ((el.children.length === 0) && (el.textContent.length >= minText));
  2616. });
  2617. }
  2618. else {
  2619. return [];
  2620. }
  2621. }
  2622.  
  2623. function nf_isSuggested(post) {
  2624. // - check if any of the suggestions / recommendations type post
  2625. let query = ':scope > div > div > div > div > div > div > div > div > div > div > div > div:nth-of-type(2) > div > div:nth-of-type(1) > div > div > div > div > span';
  2626. let suggestion = querySelectorAllNoChildren(post, query, 0);
  2627. return (suggestion.length === 0) ? '' : KeyWords.NF_SUGGESTIONS[VARS.language];
  2628. }
  2629.  
  2630. function gf_isSuggested(post) {
  2631. // - check if any of the suggestions / recommendations type post
  2632. // -- get the blocks/sections, then have a look for <i> in 1st block (providing there's more than 1 block)
  2633. // -- (query bypasses the dusty elements)
  2634. let blocksQuery = 'div[aria-posinset] > div > div > div > div > div > div > div > div';
  2635. let blocks = post.querySelectorAll(blocksQuery);
  2636. if (blocks.length > 0) {
  2637. let suggIcon = blocks[0].querySelector('i[data-visualcompletion="css-img"][style]');
  2638. return (suggIcon === null) ? '' : KeyWords.GF_SUGGESTIONS[VARS.language];
  2639. }
  2640. else {
  2641. return '';
  2642. }
  2643. }
  2644.  
  2645. function nf_isPeopleYouMayKnow(post) {
  2646. let query = 'a[href*="/friends/suggestions/"][role="link"]';
  2647. let pymk = post.querySelectorAll(query);
  2648. return (pymk.length === 0) ? '' : KeyWords.NF_PEOPLE_YOU_MAY_KNOW[VARS.language];
  2649. }
  2650.  
  2651. function nf_isPaidPartnership(post) {
  2652. let query = 'span[dir] > span[id] a[href^="/business/help/"]';
  2653. let paidPartnership = post.querySelector(query);
  2654. return (paidPartnership === null) ? '' : KeyWords.NF_PAID_PARTNERSHIP[VARS.language];
  2655. }
  2656. function nf_isSponsoredPaidBy(post) {
  2657. let query = 'div:nth-child(2) > div > div:nth-child(2) > span[class] > span[id] > div:nth-child(2)';
  2658. let sponsoredPaidBy = querySelectorAllNoChildren(post, query, 1);
  2659. return (sponsoredPaidBy.length === 0) ? '' : KeyWords.NF_SPONSORED_PAID[VARS.language];
  2660. }
  2661.  
  2662. function nf_isReelsAndShortVideos(post) {
  2663. // -- reels and short videos (multiple)
  2664. let query = 'div a[href="/reel/?s=ifu_see_more"]';
  2665. let rasv = post.querySelector(query);
  2666. return (rasv === null) ? '' : KeyWords.NF_REELS_SHORT_VIDEOS[VARS.language];
  2667. }
  2668.  
  2669. function nf_isShortReelVideo(post) {
  2670. // -- reel/short video post (single)
  2671. // -- post must have only one reel link
  2672. let query = 'a[href*="/reel/"]';
  2673. let rsv = Array.from(post.querySelectorAll(query));
  2674. return (rsv.length !== 1) ? '' : KeyWords.NF_SHORT_REEL_VIDEO[VARS.language];
  2675. }
  2676.  
  2677. function gf_isShortReelVideo(post) {
  2678. // -- reel/short video post (single)
  2679. // -- post must have only one reel link
  2680. let query = 'a[href*="/reel/"]';
  2681. let rsv = Array.from(post.querySelectorAll(query));
  2682. return (rsv.length !== 1) ? '' : KeyWords.NF_SHORT_REEL_VIDEO[VARS.language];
  2683. }
  2684.  
  2685. function nf_isBlockedText(post) {
  2686. // - check for blocked text - partial text match
  2687. // -- news feed post's blocks (have 1-4 blocks)
  2688. // -- scan 1st & 3rd blocks
  2689. // -- used by the fn extractTextContent() and fn doMoppingInfoBox()
  2690. let query = 'div[aria-posinset] > div > div > div > div > div > div > div > div';
  2691. let ptexts = extractTextContent(post, query, 3);
  2692. // console.info(log+'nf_isBlockedText:', ptexts, post);
  2693. ptexts = ptexts.join(' ').toLowerCase();
  2694. let blockedText = '';
  2695. for (let b = 0, btL = VARS.Filters.NF_BLOCKED_TEXT_LC.length; b < btL; b++) {
  2696. if (ptexts.indexOf(VARS.Filters.NF_BLOCKED_TEXT_LC[b]) >= 0) {
  2697. // before breaking out, get the text that matched.
  2698. blockedText = VARS.Filters.NF_BLOCKED_TEXT[b];
  2699. break;
  2700. }
  2701. }
  2702. return blockedText;
  2703. }
  2704. function gf_isBlockedText(post) {
  2705. // - check for blocked text - partial text match
  2706. // -- groups feed post's blocks (have 1-4 blocks)
  2707. // -- scan first 3 blocks
  2708. let query = 'div[aria-posinset] > div > div > div > div > div > div > div > div';
  2709. let ptexts = extractTextContent(post, query, 3);
  2710. // console.info(log+'gf_isBlockedText:', ptexts, post);
  2711. ptexts = ptexts.join(' ').toLowerCase();
  2712. let blockedText = '';
  2713. for (let b = 0, btL = VARS.Filters.GF_BLOCKED_TEXT_LC.length; b < btL; b++) {
  2714. if (ptexts.indexOf(VARS.Filters.GF_BLOCKED_TEXT_LC[b]) >= 0) {
  2715. // before breaking out, get the text that matched.
  2716. blockedText = VARS.Filters.GF_BLOCKED_TEXT[b];
  2717. break;
  2718. }
  2719. }
  2720. return blockedText;
  2721. }
  2722. function vf_isBlockedText(post, queryBlocks) {
  2723. // - check for blocked text - partial text match
  2724. // -- regular videos feed post's blocks (have 1-3 blocks)
  2725. // -- scan 1st block only
  2726. let ptexts = extractTextContent(post, queryBlocks, 1);
  2727. // console.info(log+'vf_isBlockedText:', ptexts, post);
  2728. ptexts = ptexts.join(' ').toLowerCase();
  2729. let blockedText = '';
  2730. for (let b = 0, btL = VARS.Filters.VF_BLOCKED_TEXT_LC.length; b < btL; b++) {
  2731. if (ptexts.indexOf(VARS.Filters.VF_BLOCKED_TEXT_LC[b]) >= 0) {
  2732. // before breaking out, get the text that matched.
  2733. blockedText = VARS.Filters.VF_BLOCKED_TEXT[b];
  2734. break;
  2735. }
  2736. }
  2737. return blockedText;
  2738. }
  2739. function vf_scrubSponsoredBlock(post) {
  2740. // - some videos have a sponsored block beneath the video block/section
  2741. let query = ':scope > div > div > div > div > div:nth-of-type(2) > div:nth-of-type(3) a';
  2742. let element = post.querySelector(query);
  2743. if (element !== null) {
  2744. element = element.closest('div');
  2745. hideBlock(element, KeyWords.SPONSORED[VARS.language]);
  2746. }
  2747. }
  2748.  
  2749. function swatTheMosquitos(post) {
  2750. // - scan the post for any gifs that is animating (pausing them once)
  2751. let query = 'div[role="button"][aria-label*="GIF"]:not([msz]) > i:not([data-visualcompletion])';
  2752. let agifs = Array.from(post.querySelectorAll(query));
  2753. // console.info('pausing, agifs::', agifs);
  2754. if (agifs.length > 0) {
  2755. agifs.forEach(gif => {
  2756. // mimic user clicking on animating gif
  2757. // - which will trigger fb's click event.
  2758. // - grab the A tag that is displayed when paused (uses Opacity)
  2759. let gpar = gif.parentElement.parentElement.parentElement;
  2760. let sib = gpar.querySelector(':scope > a');
  2761. if (sib) {
  2762. let sibCS = window.getComputedStyle(sib);
  2763. if (sibCS.opacity === '0') {
  2764. // 0 = animating; 1 = paused;
  2765. gif.parentElement.click();
  2766. // console.info(log + 'swatTheMosquitos() - paused', gif);
  2767. }
  2768. gif.parentElement.setAttribute(postAtt, '1');
  2769. }
  2770. });
  2771. }
  2772. }
  2773.  
  2774. function nf_scrubTheTabbies() {
  2775. // - tablist : stories | reels | rooms
  2776. // -- appears at top of NF
  2777. let query = `div[role="main"] > div > div > div > div > div > div > div > div[role="tablist"]:not([${postAtt}])`;
  2778. let tabLists = Array.from(document.querySelectorAll(query));
  2779. if (tabLists.length > 0) {
  2780. for (let tabList of tabLists) {
  2781. // - parent is 4 levels up.
  2782. let par = tabList.parentElement.parentElement.parentElement.parentElement;
  2783. tabList.setAttribute(postAtt, 'tab list');
  2784. hideFeature(par, KeyWords.NF_TABLIST_STORIES_REELS_ROOMS[VARS.language]);
  2785. }
  2786. }
  2787. }
  2788.  
  2789. function nf_cleanTheConsoleTable(item = 'Sponsored') {
  2790. // -- mopping up the news feed aside panel. item values: Sponosored | Suggestions
  2791. let query = `div[role="complementary"] > div > div > div > span ~ div:first-of-type > div:not([data-visualcompletion])`;
  2792. let asideBoxes = Array.from(document.querySelectorAll(query));
  2793. // console.info(log + 'aside:', asideBoxes);
  2794. if (asideBoxes.length > 0) {
  2795. if (asideBoxes[0].childElementCount > 0) {
  2796. let elItem = null;
  2797. let reason = '';
  2798. if (item === 'Sponsored') {
  2799. elItem = asideBoxes[0].querySelector(`:scope > span:not([${postAtt}])`);
  2800. reason = KeyWords.SPONSORED[VARS.language];
  2801. }
  2802. else if (item === 'Suggestions') {
  2803. elItem = asideBoxes[0].querySelector(`:scope > div:not([${postAtt}])`);
  2804. reason = KeyWords.NF_SUGGESTIONS[VARS.language];
  2805. }
  2806. if (elItem) {
  2807. if (elItem.innerHTML.length > 0) {
  2808. if (elItem.querySelectorAll('a[href="/events/birthdays/"]').length === 0) {
  2809. // -- not a birthday event
  2810. elItem.classList.add(VARS.cssHideEl);
  2811. elItem.setAttribute(postAtt, reason);
  2812. }
  2813. }
  2814. }
  2815. }
  2816. }
  2817. }
  2818.  
  2819. function gf_cleanTheConsoleTable(item = 'Suggestions') {
  2820. // mopping up the groups feed aside panel - suggested
  2821. if (item === 'Suggestions') {
  2822. let query = `a[href*="/groups/discover"]:not([${postAtt}]) > span > span`;
  2823. let asideBoxes = querySelectorAllNoChildren(document, query, 1);
  2824. // console.info(log + 'aside:', asideBoxes);
  2825. if (asideBoxes.length > 0) {
  2826. for (let asideBox of asideBoxes) {
  2827. // parent is 21 levels up ...
  2828. let par = asideBox;
  2829. for (let i = 0; i < 21; i++) {
  2830. par = par.parentElement;
  2831. }
  2832. asideBox.closest('a').setAttribute(postAtt, KeyWords.GF_SUGGESTIONS[VARS.language]);
  2833. hideFeature(par, KeyWords.GF_SUGGESTIONS[VARS.language]);
  2834. }
  2835. }
  2836. }
  2837. }
  2838.  
  2839. function scrubInfoBoxes(post) {
  2840. // hide the "truth" info boxes that appear in posts having a certain topic.
  2841.  
  2842. // -- post needs 5 blocks / sections.
  2843. // -- info box sometimes appear in block 3.
  2844.  
  2845. // - block 0 = friend posted then commented | shop added | suggested
  2846. // - block 1 = title/heading, date/time | group name, author, date/time
  2847. // - block 2 = content
  2848. // - block 3 = info box OR comments
  2849. // - block 4 = comments (if no info box)
  2850.  
  2851. let hiding = false;
  2852. let query = `[role="article"] > div > div > div > div > div > div > div > div:not([${postAtt}])`;
  2853.  
  2854. if (VARS.Options.OTHER_INFO_BOX_CLIMATE_SCIENCE) {
  2855. let blocks = post.querySelectorAll(query);
  2856. if (blocks.length >= 5) {
  2857. let block = blocks[3];
  2858. let link = block.querySelector(`a[href*="${KeyWords.OTHER_INFO_BOX_CLIMATE_SCIENCE.pathMatch}"]`);
  2859. if (link !== null) {
  2860. hideBlock(block, KeyWords.OTHER_INFO_BOX_CLIMATE_SCIENCE[VARS.language]);
  2861. hiding = true;
  2862. }
  2863. }
  2864. }
  2865. if (!hiding && VARS.Options.OTHER_INFO_BOX_CORONAVIRUS) {
  2866. let blocks = post.querySelectorAll(query);
  2867. if (blocks.length >= 5) {
  2868. let block = blocks[3];
  2869. let link = block.querySelector(`a[href*="${KeyWords.OTHER_INFO_BOX_CORONAVIRUS.pathMatch}"]`);
  2870. if (link !== null) {
  2871. hideBlock(block, KeyWords.OTHER_INFO_BOX_CORONAVIRUS[VARS.language]);
  2872. hiding = true;
  2873. }
  2874. }
  2875. }
  2876. if (!hiding && VARS.Options.OTHER_INFO_BOX_SUBSCRIBE) {
  2877. let blocks = post.querySelectorAll(query);
  2878. if (blocks.length >= 5) {
  2879. let block = blocks[3];
  2880. let link = block.querySelector(`a[href*="${KeyWords.OTHER_INFO_BOX_SUBSCRIBE.pathMatch}"]`);
  2881. if (link !== null) {
  2882. hideBlock(block, KeyWords.OTHER_INFO_BOX_SUBSCRIBE[VARS.language]);
  2883. hiding = true;
  2884. }
  2885. }
  2886. }
  2887. }
  2888.  
  2889. function mopUpTheNewsFeed() {
  2890. // mopping up the news feed page
  2891.  
  2892. // -- aside's sponsored
  2893. nf_cleanTheConsoleTable('Sponsored');
  2894.  
  2895. // -- aside's suggestions
  2896. if (VARS.Options.NF_SUGGESTIONS) {
  2897. nf_cleanTheConsoleTable('Suggestions');
  2898. }
  2899.  
  2900. // -- tab list - not part of the general news feed stream
  2901. if (VARS.Options.NF_TABLIST_STORIES_REELS_ROOMS) {
  2902. nf_scrubTheTabbies();
  2903. }
  2904.  
  2905. // -- news feed stream ...
  2906. let query = 'span[id="ssrb_feed_start"] ~ div > div';
  2907. let posts = Array.from(document.querySelectorAll(query));
  2908. if (posts.length) {
  2909. // console.info(log+'---> mopUpTheNewsFeed()');
  2910. for (let post of posts) {
  2911.  
  2912. if (post.innerHTML.length > 0) {
  2913.  
  2914. let hideReason = '';
  2915.  
  2916. if (post.hasAttribute(postAtt)) {
  2917. // -- already flagged ...
  2918. hideReason = 'hidden';
  2919. }
  2920. else if ((post[postPropDS] !== undefined) && (parseInt(post[postPropDS]) >= VARS.scanCountMaxLoop)) {
  2921. // -- skip these - already been scanned a few times
  2922. }
  2923. else {
  2924. doLightDusting(post);
  2925.  
  2926. if (isSponsored(post)) {
  2927. hideReason = KeyWords.SPONSORED[VARS.language];
  2928. }
  2929. if (hideReason === '' && VARS.Options.NF_PAID_PARTNERSHIP) {
  2930. hideReason = nf_isPaidPartnership(post);
  2931. }
  2932. if (hideReason === '' && VARS.Options.NF_REELS_SHORT_VIDEOS) {
  2933. hideReason = nf_isReelsAndShortVideos(post);
  2934. }
  2935. if (hideReason === '' && VARS.Options.NF_PEOPLE_YOU_MAY_KNOW) {
  2936. hideReason = nf_isPeopleYouMayKnow(post);
  2937. }
  2938. if (hideReason === '' && VARS.Options.NF_SUGGESTIONS) {
  2939. hideReason = nf_isSuggested(post);
  2940. }
  2941. if (hideReason === '' && VARS.Options.NF_SPONSORED_PAID) {
  2942. hideReason = nf_isSponsoredPaidBy(post);
  2943. }
  2944. if (hideReason === '' && VARS.Options.NF_SHORT_REEL_VIDEO) {
  2945. hideReason = nf_isShortReelVideo(post);
  2946. }
  2947. if (hideReason === '' && VARS.Options.NF_BLOCKED_ENABLED) {
  2948. hideReason = nf_isBlockedText(post);
  2949. }
  2950. }
  2951.  
  2952. if (hideReason.length > 0) {
  2953. // -- increment hidden count
  2954. VARS.echoCount++;
  2955. if (hideReason !== 'hidden') {
  2956. // -- post not yet hidden, hide it.
  2957. hidePost(post, hideReason);
  2958. }
  2959. }
  2960. else {
  2961. // -- not a hidden post
  2962. // -- reset hidden count
  2963. VARS.echoCount = 0;
  2964. // -- run pause animation (useful to hide those animated comments)
  2965. if (VARS.Options.NF_ANIMATED_GIFS) {
  2966. swatTheMosquitos(post);
  2967. }
  2968. // -- hide info boxes
  2969. if (VARS.hideAnInfoBox) {
  2970. scrubInfoBoxes(post);
  2971. }
  2972. }
  2973. }
  2974. }
  2975. // console.info(log+'<--- mopUpTheNewsFeed()');
  2976. }
  2977. }
  2978.  
  2979. function mopUpTheGroupsFeed() {
  2980. // mopping up the groups feed page
  2981.  
  2982. // console.info(log+'mopUpTheGroupsFeed(), gfType:', VARS.gfType);
  2983.  
  2984. if (VARS.gfType === 'groups' || VARS.gfType === 'search') {
  2985. // - main groups feed.
  2986. // - search groups (same layout as groups feed)
  2987.  
  2988. // -- aside's suggestions (also appears above feed on narrow pages)
  2989. if (VARS.Options.GF_SUGGESTIONS) {
  2990. gf_cleanTheConsoleTable('Suggestions');
  2991. }
  2992.  
  2993. // -- groups feed stream ...
  2994. let query = 'div[role="feed"] > div';
  2995. let posts = Array.from(document.querySelectorAll(query));
  2996. if (posts.length) {
  2997. // console.info(log+'---> mopUpTheGroupsFeed() - multiple groups');
  2998. for (let post of posts) {
  2999.  
  3000. if (post.innerHTML.length > 0) {
  3001.  
  3002. let hideReason = '';
  3003.  
  3004. if (post.hasAttribute(postAtt)) {
  3005. // -- already flagged
  3006. hideReason = 'hidden';
  3007. }
  3008. else if ((post[postPropDS] !== undefined) && (parseInt(post[postPropDS]) >= VARS.scanCountMaxLoop)) {
  3009. // -- skip these - already been scanned a few times
  3010. }
  3011. else {
  3012. doLightDusting(post);
  3013.  
  3014. if (isSponsored(post)) {
  3015. hideReason = KeyWords.SPONSORED[VARS.language];
  3016. }
  3017. if (hideReason === '' && VARS.Options.GF_SUGGESTIONS) {
  3018. hideReason = gf_isSuggested(post);
  3019. }
  3020. // if (hideReason === '' && VARS.Options.GF_PAID_PARTNERSHIP) {
  3021. // //console.info(log + 'mopUpTheGroupsFeed(), ---- Paid partnership - needs code ----')
  3022. // }
  3023. if (hideReason === '' && VARS.Options.GF_SHORT_REEL_VIDEO) {
  3024. hideReason = gf_isShortReelVideo(post);
  3025. }
  3026. if (hideReason === '' && VARS.Options.GF_BLOCKED_ENABLED) {
  3027. hideReason = gf_isBlockedText(post);
  3028. }
  3029. }
  3030.  
  3031. if (hideReason.length > 0) {
  3032. // -- increment hidden count
  3033. VARS.echoCount++;
  3034. if (hideReason !== 'hidden') {
  3035. // -- post not yet hidden, hide it.
  3036. hidePost(post, hideReason)
  3037. }
  3038. }
  3039. else {
  3040. // -- not a hidden post
  3041. // -- reset hidden count
  3042. VARS.echoCount = 0;
  3043. // -- run pause animation (useful to hide those animated comments)
  3044. if (VARS.Options.GF_ANIMATED_GIFS) {
  3045. // console.info(log + 'pausing animations ...');
  3046. swatTheMosquitos(post);
  3047. }
  3048. // -- hide info boxes
  3049. if (VARS.hideAnInfoBox) {
  3050. scrubInfoBoxes(post);
  3051. }
  3052. }
  3053. }
  3054. // console.info(log+'mopUpTheGroupsFeed:', hideReason, VARS.echoCount, post);
  3055. }
  3056. // console.info(log+'<--- mopUpTheGroupsFeed() - multiple groups');
  3057. }
  3058. }
  3059. else {
  3060. // - single group ...
  3061. let query = 'div[role="feed"] > div';
  3062. let posts = Array.from(document.querySelectorAll(query));
  3063. if (posts.length) {
  3064. // console.info(log+'---> mopUpTheGroupsFeed() - single group');
  3065. for (let post of posts) {
  3066.  
  3067. if (post.innerHTML.length > 0) {
  3068.  
  3069. let hideReason = '';
  3070.  
  3071. if (post.hasAttribute(postAtt)) {
  3072. // -- already flagged
  3073. hideReason = 'hidden';
  3074. }
  3075. else if ((post[postPropDS] !== undefined) && (parseInt(post[postPropDS]) >= VARS.scanCountMaxLoop)) {
  3076. // -- skip these - already scanned a few times
  3077. }
  3078. else {
  3079. doLightDusting(post);
  3080.  
  3081. if (hideReason === '' && VARS.Options.GF_SHORT_REEL_VIDEO) {
  3082. hideReason = gf_isShortReelVideo(post);
  3083. }
  3084. if (VARS.Options.GF_BLOCKED_ENABLED) {
  3085. hideReason = gf_isBlockedText(post);
  3086. }
  3087. }
  3088.  
  3089. if (hideReason.length > 0) {
  3090. // -- increment hidden counter
  3091. VARS.echoCount++;
  3092. if (hideReason !== 'hidden') {
  3093. // -- post not yet hidden, hide it.
  3094. hidePost(post, hideReason)
  3095. }
  3096. }
  3097. else {
  3098. // -- not a hidden post
  3099. // -- reset hidden count
  3100. VARS.echoCount = 0;
  3101. // -- run pause animation (useful to hide those animated comments)
  3102. if (VARS.Options.GF_ANIMATED_GIFS) {
  3103. // console.info(log + 'pausing animations ...');
  3104. swatTheMosquitos(post);
  3105. }
  3106. // -- hide info boxes
  3107. if (VARS.hideAnInfoBox) {
  3108. scrubInfoBoxes(post);
  3109. }
  3110. }
  3111. }
  3112. }
  3113. // console.info(log+'<--- mopUpTheGroupsFeed() - single group');
  3114. }
  3115. }
  3116. }
  3117.  
  3118. function mopUpTheWatchVideosFeed() {
  3119. // mopping up the watch videos feed page
  3120.  
  3121. let query;
  3122. let queryBlocks;
  3123. if (VARS.vfType === 'videos') {
  3124. // -- normal feed
  3125. query = 'div[id="watch_feed"] > div > div > div > div > div > div';
  3126. queryBlocks = ':scope > div > div > div > div > div:nth-of-type(2) > div';
  3127. }
  3128. else if (VARS.vfType === 'search') {
  3129. // -- videos --> search
  3130. query = 'div[role="feed"] > div[role="article"]';
  3131. queryBlocks = ':scope > div > div > div > div > div > div > div:nth-of-type(2)';
  3132. }
  3133. else if (VARS.vfType === 'item') {
  3134. // -- videos --> search --> item (videos being listed below the video of interest)
  3135. // -- vidoe - via link
  3136. query = 'div[id="watch_feed"] > div > div:nth-of-type(2) > div > div > div > div:nth-of-type(2) > div > div > div';
  3137. queryBlocks = ':scope > div > div > div > div > div:nth-of-type(2) > div';
  3138. }
  3139. else {
  3140. return;
  3141. }
  3142. if (VARS.vfType !== 'search') {
  3143. let posts = Array.from(document.querySelectorAll(query));
  3144. if (posts.length) {
  3145. // console.info(log+'---> mopUpTheWatchVideosFeed()');
  3146. for (let post of posts) {
  3147.  
  3148. if (post.innerHTML.length > 0) {
  3149.  
  3150. let hideReason = '';
  3151.  
  3152. if (post.hasAttribute(postAtt)) {
  3153. // -- already hidden
  3154. hideReason = 'hidden';
  3155. }
  3156. else if ((post[postPropDS] !== undefined) && (parseInt(post[postPropDS]) >= VARS.scanCountMaxLoop)) {
  3157. // -- skip these - already been scanned a few times
  3158. }
  3159. else {
  3160. doLightDusting(post);
  3161.  
  3162. if (isSponsored(post)) {
  3163. hideReason = KeyWords.SPONSORED[VARS.language];
  3164. }
  3165. if (hideReason === '' && VARS.Options.VF_BLOCKED_ENABLED) {
  3166. hideReason = vf_isBlockedText(post, queryBlocks);
  3167. }
  3168. }
  3169.  
  3170. if (hideReason.length > 0) {
  3171. // -- increment hidden count
  3172. VARS.echoCount++;
  3173. if (hideReason !== 'hidden') {
  3174. // -- post not yet hidden, hide it.
  3175. hidePost(post, hideReason)
  3176. }
  3177. }
  3178. else {
  3179. // -- not a hidden post
  3180. // -- reset hidden count
  3181. VARS.echoCount = 0;
  3182. // -- run pause animation (useful to hide those animated comments)
  3183. if (VARS.Options.VF_ANIMATED_GIFS) {
  3184. // console.info(log + 'pausing animations ...');
  3185. swatTheMosquitos(post);
  3186. }
  3187. // -- hide info boxes
  3188. if (VARS.hideAnInfoBox) {
  3189. scrubInfoBoxes(post);
  3190. }
  3191. // -- hide sponsored blocks (appears between video & comments)
  3192. vf_scrubSponsoredBlock(post);
  3193. }
  3194. }
  3195. }
  3196. // console.info(log+'<--- mopUpTheWatchVideosFeed()');
  3197. }
  3198. }
  3199. else {
  3200. // -- search videos
  3201. // -- structure is different from regular video feed
  3202. // -- thumbnail on left, text on right
  3203. let posts = Array.from(document.querySelectorAll(query));
  3204. if (posts.length) {
  3205. // console.info(log+'---> mopUpTheWatchVideosFeed()');
  3206. for (let post of posts) {
  3207.  
  3208. let hideReason = '';
  3209.  
  3210. if (post.hasAttribute(postAtt)) {
  3211. // -- already hidden
  3212. hideReason = 'hidden';
  3213. }
  3214. else {
  3215. if (VARS.Options.VF_BLOCKED_ENABLED) {
  3216. hideReason = vf_isBlockedText(post, queryBlocks);
  3217. }
  3218. }
  3219.  
  3220. if (hideReason.length > 0) {
  3221. // -- increment hidden count
  3222. VARS.echoCount++;
  3223. if (hideReason !== 'hidden') {
  3224. // -- post not yet hidden, hide it.
  3225. hidePost(post, hideReason)
  3226. }
  3227. }
  3228. else {
  3229. // -- not a hidden post
  3230. // -- reset hidden count
  3231. VARS.echoCount = 0;
  3232. }
  3233. }
  3234. // console.info(log+'<--- mopUpTheWatchVideosFeed()');
  3235. }
  3236. }
  3237. }
  3238.  
  3239. function mp_hideBox(box, reason) {
  3240. box.classList.add(VARS.cssHideEl);
  3241. box.setAttribute(postAtt, reason);
  3242. }
  3243.  
  3244. function mopUpTheMarketplaceFeed() {
  3245. // mopping up parts of the Marketplace ...
  3246.  
  3247. if (VARS.mpType === 'item') {
  3248. // -- viewing a marketplace item - a small sponsored box often shows up on the right.
  3249. let query = `a[href*="/ads/"]:not([${postAtt}])`;
  3250. let elements = Array.from(document.querySelectorAll(query));
  3251. // console.info(`${log}MPItem() - elements:`, query, elements);
  3252. if (elements.length > 0) {
  3253. for (let element of elements) {
  3254. if (element.closest('div[data-pagelet^="BrowseFeedUpsell"]') === null) {
  3255. // -- found the sponsored box inside the mp item box.
  3256. // -- mp item do not have a parent element having data-pagelet attribute.
  3257. let spbox = element.parentElement.closest('h2');
  3258. if (spbox) {
  3259. spbox = spbox.closest('span');
  3260. mp_hideBox(spbox, KeyWords.SPONSORED[VARS.language]);
  3261. element.setAttribute(postAtt, KeyWords.SPONSORED[VARS.language]);
  3262. // (there's only one sponsored box - so break out)
  3263. break;
  3264. }
  3265. }
  3266. }
  3267. }
  3268. }
  3269. else if (VARS.mpType === 'marketplace') {
  3270. // - standard marketplace page
  3271. // -- "sponsored" is _not_ obfuscated;
  3272. // -- nb: adguard base filter hides the label, but not the item/product ...
  3273. let queryHeadings = `div:not([${postAtt}]) > a[href="/ads/about/?entry_product=ad_preferences"]`;
  3274. let queryItems = `div[class]:not([${postAtt}]) > span > div > div > div > a:not([href*="marketplace"])`;
  3275. let headings = document.querySelectorAll(queryHeadings);
  3276. let items = document.querySelectorAll(queryItems);
  3277. if ((headings.length > 0) && (items.length > 0)) {
  3278. for (let heading of headings) {
  3279. heading = heading.parentElement;
  3280. mp_hideBox(heading, KeyWords.SPONSORED[VARS.language]);
  3281. }
  3282. for (let item of items) {
  3283. item = item.closest('span').parentElement;
  3284. mp_hideBox(item, KeyWords.SPONSORED[VARS.language]);
  3285. }
  3286. }
  3287. }
  3288. else if ((VARS.mpType === 'category') || (VARS.mpType === 'search')) {
  3289. // - viewing a markplace category or marketplace search results
  3290. // - (both have similar layout)
  3291. let query = `a[href*="/ads/"]:not([${postAtt}])`;
  3292. let elements = document.querySelectorAll(query);
  3293. if (elements.length > 0) {
  3294. for (let element of elements) {
  3295. // console.info(log + 'mp-clean:', element);
  3296. element.setAttribute(postAtt, element.innerHTML.length);
  3297. let itemBox = element.parentElement.closest('a').parentElement.parentElement.parentElement;
  3298. mp_hideBox(itemBox, KeyWords.SPONSORED[VARS.language]);
  3299. }
  3300. }
  3301. }
  3302. }
  3303.  
  3304. function mopUpTheSearchFeed() {
  3305. // mopping up the search feed / results
  3306. // -- (nb: has similar layout to news feed stream)
  3307. // -- "borrow" news feed's text filter.
  3308. if (VARS.Options.NF_BLOCKED_ENABLED) {
  3309. let query = 'div[role="feed"] > div';
  3310. let posts = Array.from(document.querySelectorAll(query));
  3311. if (posts.length) {
  3312. // console.info(log + '---> mopUpTheSearchFeed()');
  3313. for (let post of posts) {
  3314.  
  3315. if (post.innerHTML.length > 0) {
  3316.  
  3317. let hideReason = '';
  3318.  
  3319. if (post.hasAttribute(postAtt)) {
  3320. hideReason = 'hidden';
  3321. }
  3322. else {
  3323. if (isSponsored(post)) {
  3324. hideReason = KeyWords.SPONSORED[VARS.language];
  3325. }
  3326. else {
  3327. if (VARS.NF_BLOCKED_ENABLED) {
  3328. hideReason = nf_isBlockedText(post);
  3329. }
  3330. }
  3331. }
  3332.  
  3333. if (hideReason.length > 0) {
  3334. // -- increment hidden count
  3335. VARS.echoCount++;
  3336. if (hideReason != 'hidden') {
  3337. // -- post not yet hidden, hide it.
  3338. hidePost(post, hideReason);
  3339. }
  3340. }
  3341. else {
  3342. // -- not a hidden post
  3343. // -- reset hidden count
  3344. VARS.echoCount = 0;
  3345. // -- run pause animation (useful to hide those animated comments)
  3346. if (VARS.Options.NF_ANIMATED_GIFS) {
  3347. // console.info(log + 'pausing animations ...');
  3348. swatTheMosquitos(post);
  3349. }
  3350. // -- hide info boxes
  3351. if (VARS.hideAnInfoBox) {
  3352. scrubInfoBoxes(post);
  3353. }
  3354. }
  3355. }
  3356. // console.info(log + 'mopUpTheSearchFeed:', hideReason, VARS.echoCount, post);
  3357. }
  3358. // console.info(log + '<--- mopUpTheSearchFeed()');
  3359. }
  3360. }
  3361. }
  3362.  
  3363.  
  3364. // ** Mutations processor
  3365. function bodyMutating(mutations) {
  3366. for (let mutation of mutations) {
  3367. if (mutation.type === 'childList') {
  3368. if (VARS.prevURL !== window.location.href) {
  3369. // - page url has changed ... refresh the bodyObserver.
  3370. runMO();
  3371. // console.info(`${log}runMO(): A/N/G/V/M:`, VARS.isAF, VARS.isNF, VARS.isGF, VARS.isVF, VARS.isMF);
  3372. }
  3373. else if (VARS.isAF) {
  3374. for (let i = 0; i < mutation.addedNodes.length; i++) {
  3375. let mnode = mutation.addedNodes[i];
  3376. if (['DIV', 'SPAN', 'A', 'B'].indexOf(mnode.tagName) >= 0) {
  3377. // console.info(`${log} mnode: ${mnode.tagName}; innerHTML: ${mnode.innerHTML.length}; textContent: ${mnode.textContent.length}; children: ${mnode.children.length};\nnode:`, mnode);
  3378. if ((mnode.innerHTML.length < 129) || (mnode.textContent.length === 0)) {
  3379. // - skip these ...
  3380. }
  3381. else if (VARS.isNF) {
  3382. mopUpTheNewsFeed();
  3383. }
  3384. else if (VARS.isGF) {
  3385. mopUpTheGroupsFeed();
  3386. }
  3387. else if (VARS.isVF) {
  3388. mopUpTheWatchVideosFeed();
  3389. }
  3390. else if (VARS.isMF) {
  3391. mopUpTheMarketplaceFeed();
  3392. }
  3393. else if (VARS.isSF) {
  3394. mopUpTheSearchFeed();
  3395. }
  3396. }
  3397. }
  3398. }
  3399. }
  3400. }
  3401. }
  3402.  
  3403. // ** Mutation Observer
  3404. let bodyObserver = new MutationObserver(bodyMutating);
  3405. // ** MO starter / restarter
  3406. let firstRun = true;
  3407.  
  3408. function runMO() {
  3409. // run code soon as the elements HEAD, BDDY and variable Options are ready/available.
  3410. // or when page url has changed ...
  3411. if (document.head && document.body && DBVARS.optionsReady) {
  3412. if (firstRun) {
  3413. addCSS();
  3414. window.setTimeout(addExtraCSS, 150); // fb is sometimes laggy ...
  3415. buildMoppingDialog();
  3416. firstRun = false;
  3417. }
  3418. if (setFeedSettings()) {
  3419. // - clear out mutations not yet processed ...
  3420. let mutations = bodyObserver.takeRecords();
  3421. bodyObserver.disconnect();
  3422. // - and start up the osbserver again.
  3423. bodyObserver.observe(document.body, {
  3424. childList: true,
  3425. subtree: true,
  3426. attributes: false
  3427. });
  3428. }
  3429. }
  3430. else {
  3431. // HEAD / BODY / Options not yet ready ...
  3432. // -- try again ...
  3433. setTimeout(runMO, 10);
  3434. }
  3435. }
  3436. runMO();
  3437.  
  3438. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址