FB - Clean my feeds

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

当前为 2022-05-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name FB - Clean my feeds
  3. // @description Hide Sponsored and Suggested posts in FB's News Feed, Groups Feed, Watch Videos Feed and Marketplace Feed
  4. // @namespace https://greasyfork.org/users/812551
  5. // @supportURL https://github.com/zbluebugz/facebook-clean-my-feeds/issues
  6. // @version 3.18
  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. v3.18 :: May 2022
  17. Bug fix for Sponsored post detection code (non Flex branch)
  18. Added Italian (incomplete)
  19. Other language updates
  20. News Feed - third column - option to hide: "Groups / Suggested for you" box
  21. News Feed - option to hide "Recommended post"
  22. Search Feed - All & Posts: hide Sponsored posts
  23. Minor code & UI tweaks
  24. v3.17 :: May 2022
  25. Updated detection code for: Sponsored posts in Marketplace & Videos; (FB changed it)
  26. Updated detection code for: Create Room, Sponsored block (news feed, third column), Stories (FB changed it)
  27. Revised sponsored detection code
  28. Added "LIVE" filter for watch/video feed
  29. Added option relocate CMF's button and panel + change CMF's border colour
  30. Added light/dark mode theme
  31. Added Vietnamese (incomplete)
  32. v3.16 :: May 2022
  33. Added Sponsored * Paid for ___ detection code
  34. v3.15 :: May 2022
  35. Updated Sponsored detection code (Chrome)
  36. v3.14 :: May 2022:
  37. Updated Sponsored detection code (FB changed it)
  38. v3.13 :: April 2022:
  39. Updated Sponsored detection code (FB changed it)
  40. Added "Reels and short videos" to News feed block list
  41. Tweaked some minor bits
  42. v3.12 :: January 2022:
  43. Added a dialog box for users to toggle options
  44. Added option to hide News and Groups posts based on text (partial match)
  45. Added option to save/export options
  46. Added Espanol and Čeština(Czech) (incomplete)
  47. Added option to hide "Take a survey" and "FB 2 Meta" info boxes.
  48. v3.11 :: 20/11/2021:
  49. Rewrite
  50. Changed timings to MutationsObserver.
  51. Adjusted sponsored word detection block
  52. Adjusted suggestions text detection block
  53. Added extra Suggestions keywords
  54. Added detection for Groups Feed, Videos Feed (Watch), MarketPlace Feed
  55. Added option to hide Information Boxes (e.g. Covid Information, Global Climate Info)
  56. Added right rail(column) hide sponsored block
  57. Added German and French (incomplete)
  58. Added option to display 'post is hidden' text
  59. Added option to hide videos based on text (partial match)
  60.  
  61.  
  62. Attribution: Mop & bucket icon:
  63. - made by Freepik (https://www.freepik.com) @ flaticon (https://www.flaticon.com/)
  64. - page: https://www.flaticon.com/premium-icon/mop_2383747
  65.  
  66. To do :::
  67. - complete language translation (based on FB's wording/spelling)
  68. - investigate Private/Icognito/InPrivate Mode (idb doesn't work)
  69.  
  70. Instructions on how to use:
  71. - In FB, top right corner or bottom left corner, click on the "Clean my feeds" icon (mop + bucket)
  72. - Toggle the various options
  73. - Click Save then Close.
  74. - It is recommended that you Export your settings every now and then. (When your browser flushes the cache, your settings are deleted).
  75.  
  76.  
  77. \\\ --- No need to amend any of the code below --- ///
  78. */
  79.  
  80. (async function() {
  81.  
  82. 'use strict';
  83.  
  84. // - console log "label" - used for filtering console logs.
  85. const log = '-- fbm :: ';
  86.  
  87. // - idb-keyval - indexedDB wrapper
  88. // -- needs the "@require https://unpkg.com/idb-keyval@6.0.3/dist/umd.js" entry.
  89. // -- which functions do we want to use from the idb-keyval?
  90. const { get, set, createStore } = idbKeyval;
  91. // - override idb-keyval's default db and store names.
  92. let DBVARS = {
  93. DBName: 'dbCMF',
  94. DBStore: 'Mopping',
  95. DBKey: 'Options',
  96. optionsReady: false,
  97. ostore: null,
  98. }
  99. // - make sure the db's store exists ...
  100. DBVARS.ostore = createStore(DBVARS.DBName, DBVARS.DBStore);
  101.  
  102. // - post attribute (used for detecting changes within a post)
  103. const postAtt = 'msz';
  104. const postAttIB = 'msz-ib';
  105.  
  106. // *** *** Language components *** ***
  107. const KeyWords = {
  108. // *** Which languages have been setup:
  109. // - 'en' is default.
  110. LANGUAGES : ['en', 'pt', 'de', 'fr', 'es', 'cs','vi', 'it'],
  111.  
  112. SPONSORED : {
  113. // English
  114. 'en': 'Sponsored',
  115. // Português (Portugal)
  116. 'pt': 'Patrocinado',
  117. // Deutsch (Germany)
  118. 'de': 'Gesponsert',
  119. // Français (France)
  120. 'fr': 'Sponsorisé',
  121. // Espanol (Spain)
  122. 'es': 'Publicidad',
  123. // Čeština (Czechia)
  124. 'cs': 'Sponzorováno',
  125. // Tiếng Việt (Vietnam)
  126. 'vi': 'Được tài trợ',
  127. // Italino (Italy)
  128. 'it': 'Sponsorizzato',
  129. },
  130. // marketplace 'sponsored' word ... somtimes fb has a different spelling
  131. MP_SPONSORED : {
  132. 'en': 'Sponsored',
  133. 'pt': 'Patrocinado',
  134. 'de': 'Gesponsert',
  135. 'fr': 'Sponsorisée',
  136. 'es': 'Publicidad',
  137. 'cs': 'Sponzorováno',
  138. 'vi': 'Được tài trợ',
  139. 'it': 'Sponsorizzata',
  140. },
  141. // *** Verbosity:
  142. VERBOSITY : {
  143. 'en': ['1 post hidden. Rule: ', ' posts hidden'],
  144. 'pt': ['1 postagem oculta. Regra: ', ' postagens ocultas'],
  145. 'de': ['1 Beitrag ausgeblendet. Regel: ', ' Beiträge versteckt'],
  146. 'fr': ['1 poste caché. Règle: ', ' posts cachés'],
  147. 'es': ['1 publicación oculta. Regla: ', ' publicaciones ocultas'],
  148. 'cs': ['1 příspěvek byl skryt. Pravidlo: ', ' příspěvků skrytých'],
  149. 'vi': ['1 bài bị ẩn. Quy tắc: ', ' bài viết ẩn'],
  150. 'it': ['1 post nascosto Regola: ', ' post nascosti'],
  151. },
  152.  
  153. // *** Instructions for adding a Suggestion keywords ***
  154. // 1) Create keyword under relevant feed
  155. // 2) Enter all language entries (must use FB wording).
  156. // - If unknown, use EN's word(s) and add "// -- need translation" comment
  157. // - Also set isSuggestion & defaultEnabled.
  158. // 3) The code will then do the rest ...
  159. // 4) NB: Placement of keyword determines the display order.
  160. // *** --- ***
  161.  
  162. // *** News Feed ::
  163. // - Stories
  164. NF_STORIES : {
  165. 'en': 'Stories',
  166. 'pt': 'Histórias',
  167. 'de': 'Stories',
  168. 'fr': 'Stories',
  169. 'es': 'Historias',
  170. 'cs': 'Příběhy',
  171. 'vi': 'Những câu chuyện',
  172. 'it': 'Storia',
  173. 'isSuggestion': false,
  174. 'defaultEnabled': false,
  175. },
  176. // - create room
  177. NF_CREATE_ROOM : {
  178. 'en': 'Create room',
  179. 'pt': 'Criar sala',
  180. 'de': 'Room erstellen',
  181. 'fr': 'Créer un salon',
  182. 'es': 'Crear sala',
  183. 'cs': 'Vytvořit místnost',
  184. 'vi': 'Tạo phòng họp mặt',
  185. 'it': 'Crea stanza',
  186. 'isSuggestion': false,
  187. 'defaultEnabled': false,
  188. },
  189. // - People you may know
  190. NF_PEOPLE_YOU_MAY_KNOW : {
  191. 'en': 'People you may know',
  192. 'pt': 'Pessoas que talvez conheças',
  193. 'de': 'Personen, die du kennen könntest',
  194. 'fr': 'Connaissez-vous...',
  195. 'es': 'Personas que quizá conozcas',
  196. 'cs': 'Koho možná znáte',
  197. 'vi': 'Những người bạn có thể biết',
  198. 'it': 'Persone che potresti conoscere',
  199. 'isSuggestion': true,
  200. 'defaultEnabled': false,
  201. },
  202. // - Paid partnership
  203. // -- page you follow is "sponsoring" another page's post (e.g. job)
  204. NF_PAID_PARTNERSHIP : {
  205. 'en': 'Paid partnership',
  206. 'pt': 'Parceria paga',
  207. 'de': 'Bezahlte Werbepartnerschaft', // (Paid advertising partnership)
  208. 'fr': 'Partenariat rémunéré',
  209. 'es': 'Colaboración pagada', // (Paid collaboration)
  210. 'cs': 'Placené partnerství',
  211. 'vi': 'Mối quan hệ tài trợ',
  212. 'it': 'Partnership pubblicizzata',
  213. 'isSuggestion': true,
  214. 'defaultEnabled': true,
  215. },
  216. // Sponsored · Paid for by ______
  217. NF_SPONSORED_PAID : {
  218. 'en': 'Sponsored · Paid for by ______',
  219. 'pt': 'Patrocinado · Financiado por ______',
  220. 'de': 'Gesponsert · Finanziert von ______',
  221. 'fr': 'Sponsorisé · Financé par ______',
  222. 'es': 'Publicidad · Pagado por ______',
  223. 'cs': 'Sponzorováno · Platí za to ______',
  224. 'vi': 'Sponsored · Paid for by ______', // --- needs translation
  225. 'it': 'Sponsored · Paid for by ______', // --- needs translation ('Sponsorizzato · Pagato da ______' ?)
  226. 'isSuggestion': false,
  227. 'defaultEnabled': true
  228. },
  229. // - Suggested for you
  230. NF_SUGGESTED_FOR_YOU : {
  231. 'en': 'Suggested for you',
  232. 'pt': 'Sugestões para ti',
  233. 'de': 'Vorschläge für dich',
  234. 'fr': 'Suggestions pour vous',
  235. 'es': 'Sugerencias para ti',
  236. 'cs': 'Návrhy pro vás',
  237. 'vi': 'Gợi ý cho bạn',
  238. 'vi': 'Suggested for you', // --- needs translation
  239. 'it': 'Suggeriti per te', // --- (correct translation?)
  240. 'isSuggestion': true,
  241. 'defaultEnabled': false,
  242. },
  243. // - Recommended post (usually appears on fresh accounts)
  244. NF_RECOMMENDED_POST : {
  245. 'en': 'Recommended post',
  246. 'pt': 'Publicação recomendada',
  247. 'de': 'Empfohlener Beitrag',
  248. 'fr': 'Publication recommandée',
  249. 'es': 'Publicación recomendada',
  250. 'cs': 'Doporučený příspěvek',
  251. 'vi': 'Bài viết đề xuất',
  252. 'it': 'Post suggerito',
  253. 'isSuggestion': true,
  254. 'defaultEnabled': false,
  255. },
  256. // - Suggested pages
  257. NF_SUGGESTED_PAGES : {
  258. 'en': 'Suggested Pages',
  259. 'pt': 'Páginas sugeridas',
  260. 'de': 'Vorgeschlagene Seiten',
  261. 'fr': 'Pages suggérées',
  262. 'es': 'Páginas sugeridas',
  263. 'cs': 'Navrhované stránky',
  264. 'vi': 'Suggested Pages', // --- needs translation
  265. 'it': 'Suggested Pages', // --- needs translation
  266. 'isSuggestion': true,
  267. 'defaultEnabled': false,
  268. },
  269. // - Suggested events
  270. NF_SUGGESTED_EVENTS : {
  271. 'en': 'Suggested Events',
  272. 'pt': 'Eventos Sugeridos',
  273. 'de': 'Suggested Events', // --- needs translation
  274. 'fr': 'Suggested Events', // --- needs translation
  275. 'es': 'Suggested Events', // --- needs translation
  276. 'cs': 'Suggested Events', // --- needs translation
  277. 'vi': 'Suggested Events', // --- needs translation
  278. 'it': 'Suggested Events', // --- needs translation
  279. 'isSuggestion': true,
  280. 'defaultEnabled': false,
  281. },
  282. // - Events you may like
  283. NF_EVENTS_YOU_MAY_LIKE : {
  284. 'en': 'Events you may like',
  285. 'pt': 'Events you may like', // --- needs translation
  286. 'de': 'Events you may like', // --- needs translation
  287. 'fr': 'Évènements qui pourraient vous intéresser', // (Events that may/might interest you )
  288. 'es': 'Eventos que te pueden gustar',
  289. 'cs': 'Events you may like', // --- needs translation
  290. 'vi': 'Events you may like', // --- needs translation
  291. 'it': 'Events you may like', // --- needs translation
  292. 'isSuggestion': true,
  293. 'defaultEnabled': false,
  294. },
  295. // - Videos just for you
  296. NF_VIDEOS_JUST_FOR_YOU : {
  297. 'en': 'Videos just for you',
  298. 'pt': 'Vídeos só para ti',
  299. 'de': 'Videos just for you', // --- needs translation
  300. 'fr': 'Videos just for you', // --- needs translation
  301. 'es': 'Videos just for you', // --- needs translation
  302. 'cs': 'Videos just for you', // --- needs translation
  303. 'vi': 'Videos just for you', // --- needs translation
  304. 'it': 'Videos just for you', // --- needs translation
  305. 'isSuggestion': true,
  306. 'defaultEnabled': false,
  307. },
  308. // - Page you could subscribe to
  309. NF_PAGE_SUBSCRIBE_TO : {
  310. 'en': 'Page you could subscribe to', // --- needs translation (not seen in EN, but seen in DE)
  311. 'pt': 'Page you could subscribe to', // --- needs translation
  312. 'de': 'Seite, die du abonnieren könntest',
  313. 'fr': 'Page you could subscribe to', // --- needs translation
  314. 'es': 'Page you could subscribe to', // --- needs translation
  315. 'cs': 'Page you could subscribe to', // --- needs translation
  316. 'vi': 'Page you could subscribe to', // --- needs translation
  317. 'it': 'Page you could subscribe to', // --- needs translation
  318. 'isSuggestion': true,
  319. 'defaultEnabled': false,
  320. },
  321. // Reels and short videos
  322. NF_REELS_SHORT_VIDEOS : {
  323. 'en': 'Reels and short videos',
  324. //'pt': 'Vídeos do Reels e vídeos curtos',
  325. 'pt': 'Vídeos do Reels e vídeos de curta duração',
  326. 'de': 'Reels und Kurzvideos',
  327. 'fr': 'Reels et vidéos courtes',
  328. 'es': 'Reels y vídeos cortos',
  329. 'cs': 'Sekvence a krátká videa',
  330. 'vi': 'Reels và video ngắn',
  331. 'it': 'Reel e video brevi',
  332. 'isSuggestion': true,
  333. 'defaultEnabled': false,
  334. },
  335. // -- Sponsored box in right-hand column
  336. NF_THIRD_COLUMN_SPONSORED : {
  337. 'en': 'Sponsored box (right-hand column)',
  338. 'pt': 'Caixa patrocinada (coluna da direita)',
  339. 'de': 'Gesponserte Box (rechte Spalte)',
  340. 'fr': 'Encadré sponsorisé (colonne de droite)',
  341. 'es': 'Cuadro patrocinado (columna de la derecha)',
  342. 'cs': 'Sponzorovaný box (pravý sloupec)',
  343. 'vi': 'Hộp tài trợ (cột bên phải))',
  344. 'it': 'Casella sponsorizzato (colonna di destra)',
  345. 'defaultEnabled': true,
  346. },
  347. // -- Suggested for you
  348. NF_THIRD_COLUMN_SUGGESTED_FOR_YOU : {
  349. 'en': 'Suggested for you (right-hand column)',
  350. 'pt': 'Sugestões para ti (coluna da direita)',
  351. 'de': 'Vorschläge für dich (rechte Spalte)',
  352. 'fr': 'Suggestions pour vous (colonne de droite)',
  353. 'es': 'Sugerencias para ti (columna de la derecha)',
  354. 'cs': 'Návrhy pro vás (pravý sloupec)',
  355. 'vi': 'Gợi ý cho bạn (cột bên phải))',
  356. 'it': 'Suggeriti per te (colonna di destra)',
  357. 'defaultEnabled': false,
  358. },
  359.  
  360. // *** Groups Feed ::
  361. // -- nb: some of these rules overlap each other
  362. // -- "Join" and "Join Group" are listed in most non-subscribed group posts,
  363. // if both of these keywords are enabled, then the other keywords are "redundant"
  364. // - New for you
  365. // -- usually shows up at top of feed.
  366. GF_NEW_FOR_YOU : {
  367. 'en': 'New for you',
  368. 'pt': 'Novidades para ti',
  369. 'de': 'Neu für dich',
  370. 'fr': 'Nouveautés',
  371. 'es': 'Novedades para ti',
  372. 'cs': 'Novinky pro vás',
  373. 'vi': 'New for you', // --- needs translation
  374. 'it': 'New for you', // --- needs translation
  375. 'isSuggestion': true,
  376. 'defaultEnabled': false,
  377. },
  378. // - Suggested for you (Groups you might be interested in.)
  379. GF_SUGGESTED_FOR_YOU_GROUPS : {
  380. 'en': 'Suggested for you',
  381. 'pt': 'Sugestões para ti',
  382. 'de': 'Vorschläge für dich',
  383. 'fr': 'Suggestions pour vous',
  384. 'es': 'Sugerencias para ti',
  385. 'cs': 'Návrhy pro vás',
  386. 'vi': 'Gợi ý cho bạn',
  387. 'it': 'Suggeriti per te', // --- needs translation (correct?)
  388. 'isSuggestion': true,
  389. 'defaultEnabled': false,
  390. },
  391. // - Paid partnership
  392. // -- a page you follow is "sponsoring" another page's post (e.g. job)
  393. GF_PAID_PARTNERSHIP : {
  394. 'en': 'Paid partnership',
  395. 'pt': 'Parceria paga',
  396. 'de': 'Bezahlte Werbepartnerschaft', // (Paid advertising partnership)
  397. 'fr': 'Partenariat rémunéré',
  398. 'es': 'Colaboración pagada', // (Paid collaboration)
  399. 'cs': 'Placené partnerství',
  400. 'vi': 'Mối quan hệ tài trợ',
  401. 'it': 'Partnership pubblicizzata',
  402. 'isSuggestion': true,
  403. 'defaultEnabled': true,
  404. },
  405. // - Suggested groups
  406. // -- box of groups - may need to use the view/see more keyword
  407. GF_SUGGESTED_GROUPS : {
  408. 'en': 'Suggested groups',
  409. 'pt': 'Grupos sugeridos',
  410. 'de': 'Vorgeschlagene Gruppen',
  411. 'fr': 'Groupes suggérés',
  412. 'es': 'Grupos sugeridos',
  413. 'cs': 'Navrhované skupiny',
  414. 'vi': 'Nhóm gợi ý',
  415. 'it': 'Gruppi suggeriti',
  416. 'isSuggestion': true,
  417. 'defaultEnabled': false,
  418. },
  419. // - Suggested post from a public group
  420. // -- lots of posts from groups not subscribed too
  421. GF_SUGGESTED_POST_PUBLIC_GROUP : {
  422. 'en': 'Suggested post from a public group',
  423. 'pt': 'Publicação sugerida de um grupo público',
  424. 'de': 'Vorgeschlagener Beitrag aus einer öffentlichen Gruppe',
  425. 'fr': 'Publication suggérée d’un groupe public',
  426. 'es': 'Publicación sugerida de un grupo público',
  427. 'cs': 'Navrhovaný příspěvek z veřejné skupiny', // proposed contribution from public group
  428. 'vi': 'Bài viết gợi ý từ nhóm công khai',
  429. 'it': 'Post suggerito di un gruppo pubblico',
  430. 'isSuggestion': true,
  431. 'defaultEnabled': false,
  432. },
  433. // - Post from public group
  434. // -- lots of posts from groups not subscribed too
  435. /* May 2022 - disabled.
  436. GF_POST_PUBLIC_GROUP : {
  437. 'en': 'Post from public group',
  438. 'pt': 'Postagem de grupo público',
  439. 'de': 'Post from public group', // --- needs translation
  440. 'fr': 'Post from public group', // --- needs translation
  441. 'es': 'Post from public group', // --- needs translation
  442. 'cs': 'Post from public group', // --- needs translation
  443. 'vi': 'Post from public group', // --- needs translation
  444. 'it': 'Post from public group', // --- needs translation
  445. 'isSuggestion': true,
  446. 'defaultEnabled': false,
  447. },
  448. */
  449. // - From a group that your friend is in
  450. GF_FROM_A_GROUP_YOUR_FRIEND_IS_IN : {
  451. 'en': 'From a group that your friend is in',
  452. 'pt': 'De um grupo em que o teu amigo/a é membro',
  453. 'de': 'Aus einer Gruppe, in der dein/e Freund/in ist',
  454. 'fr': 'D’un groupe dont votre ami(e) est membre',
  455. 'es': 'De un grupo al que tu amigo pertenece',
  456. 'cs': 'Ze skupiny, kde je váš přítel',
  457. 'vi': 'From a group that your friend is in', // --- needs translation
  458. 'it': 'From a group that your friend is in', // --- needs translation
  459. 'isSuggestion': true,
  460. 'defaultEnabled': false,
  461. },
  462. // - Friends' groups
  463. // -- usually shows up at top of feed.
  464. GF_FRIENDS_GROUPS : {
  465. 'en': 'Friends\' groups',
  466. 'pt': 'Grupos dos amigos',
  467. 'de': 'Gruppen von Freunden',
  468. 'fr': 'Friends\' groups', // --- needs translation
  469. 'es': 'Friends\' groups', // --- needs translation
  470. 'cs': 'Friends\' groups', // --- needs translation
  471. 'vi': 'Friends\' groups', // --- needs translation
  472. 'it': 'Friends\' groups', // --- needs translation
  473. 'isSuggestion': true,
  474. 'defaultEnabled': false,
  475. },
  476. // - Popular near you / in your area
  477. GF_POPULAR_NEAR_YOU : {
  478. 'en': 'Popular near you',
  479. 'pt': 'Populares perto de ti',
  480. 'de': 'Beliebt in deiner Nähe',
  481. 'fr': 'Popular near you', // --- needs translation
  482. 'es': 'Popular near you', // --- needs translation
  483. 'cs': 'Popular near you', // --- needs translation
  484. 'vi': 'Popular near you', // --- needs translation
  485. 'it': 'Popular near you', // --- needs translation
  486. 'isSuggestion': true,
  487. 'defaultEnabled': false,
  488. },
  489. // - See More Groups - from post's heading "More like XYZ" / "Others similar to XYZ" (where XYZ is a group you've joined)
  490. // -- nb: some non-subscribed group posts also have this keyword.
  491. GF_SEE_MORE_GROUPS : {
  492. 'en': 'See More Groups',
  493. 'pt': 'Ver mais grupos',
  494. 'de': 'Weitere Gruppen ansehen',
  495. 'fr': 'Voir plus de groupes',
  496. 'es': 'Ver más grupos',
  497. 'cs': 'Zobrazit další skupiny',
  498. 'vi': 'Xem thêm nhóm',
  499. 'it': 'Vedi altri gruppi',
  500. 'isSuggestion': true,
  501. 'defaultEnabled': false,
  502. },
  503. // - Because you viewed a similar post (but not from a subscribed group)
  504. GF_BECAUSE_YOU_VIEWED_A_SIMILAR_POST : {
  505. 'en': 'Because you viewed a similar post',
  506. 'pt': 'Porque viste uma publicação semelhante',
  507. 'de': 'Weil du dir einen ähnlichen Beitrag angesehen hast',
  508. 'fr': 'Parce que vous avez consulté une publication similaire',
  509. 'es': 'Porque has visto una publicación similar',
  510. 'cs': 'Protože jste se díval na podobný příspěvek',
  511. 'vi': 'Xem thêm bài viết tương tự',
  512. 'it': 'Because you viewed a similar post',
  513. 'isSuggestion': true,
  514. 'defaultEnabled': false,
  515. },
  516. // - Because you viewed a similar group
  517. GF_BECAUSE_YOU_VIEWED_A_SIMILAR_GROUP : {
  518. 'en': 'Because you viewed a similar group',
  519. 'pt': 'Because you viewed a similar group', // --- needs translation
  520. 'de': 'Because you viewed a similar group', // --- needs translation
  521. 'fr': 'Because you viewed a similar group', // --- needs translation
  522. 'es': 'Because you viewed a similar group', // --- needs translation
  523. 'cs': 'Protože jste zobrazil podobnou skupinu',
  524. 'vi': 'Vì bạn đã xem một nhóm tương tự',
  525. 'it': 'Because you viewed a similar group', // --- needs translation
  526. 'isSuggestion': true,
  527. 'defaultEnabled': false,
  528. },
  529. // - Based on your recent activity
  530. GF_YOUR_RECENT_ACTIVITY: {
  531. 'en': 'Based on your recent activity',
  532. 'pt': 'Based on your recent activity', // --- needs translation
  533. 'de': 'Based on your recent activity', // --- needs translation
  534. 'fr': 'Based on your recent activity', // --- needs translation
  535. 'es': 'Based on your recent activity', // --- needs translation
  536. 'cs': 'Based on your recent activity', // --- needs translation
  537. 'vi': 'Based on your recent activity', // --- needs translation
  538. 'it': 'Based on your recent activity', // --- needs translation
  539. 'isSuggestion': true,
  540. 'defaultEnabled': false,
  541. },
  542. // - Join Group
  543. // -- one of two generic join a group
  544. // -- (bit like a catch-all rule - placed these to rules @ end of list.)
  545. GF_JOIN_GROUP_1 : {
  546. 'en': 'Join Group',
  547. 'pt': 'Aderir ao grupo',
  548. 'de': 'Gruppe beitreten',
  549. 'fr': 'Rejoindre le groupe',
  550. 'es': 'Unirte al grupo',
  551. 'cs': 'Přidat se ke skupině',
  552. 'vi': 'Tham gia nhóm',
  553. 'it': 'Iscriviti al gruppo',
  554. 'isSuggestion': true,
  555. 'defaultEnabled': false,
  556. },
  557. // - "Join" / "Sign up" / "Subscribe" button/link
  558. // -- one of two generic join a group
  559. // -- (bit like a catch-all rule)
  560. GF_JOIN_GROUP_2 : {
  561. 'en': 'Join',
  562. 'pt': 'Aderir',
  563. 'de': 'Beitreten',
  564. 'fr': 'Rejoindre',
  565. 'es': 'Unirte',
  566. 'cs': 'Přidat se',
  567. 'vi': 'Tham gia',
  568. 'it': 'Iscriviti',
  569. 'isSuggestion': true,
  570. 'defaultEnabled': false,
  571. },
  572.  
  573. // *** Watch Videos Feed
  574. // - Paid partnership
  575. // -- page you follow is "sponsoring" another page's video post (e.g. job)
  576. VF_PAID_PARTNERSHIP_VIDEOS : {
  577. 'en': 'Paid partnership',
  578. 'pt': 'Parceria paga',
  579. 'de': 'Bezahlte Werbepartnerschaft', // (Paid advertising partnership)
  580. 'fr': 'Partenariat rémunéré',
  581. 'es': 'Colaboración pagada', // (Paid collaboration)
  582. 'cs': 'Placené partnerství',
  583. 'vi': 'Mối quan hệ tài trợ',
  584. 'it': 'Partnership pubblicizzata',
  585. 'isSuggestion': true,
  586. 'defaultEnabled': true,
  587. },
  588. VF_NEW_FOR_YOU_VIDEOS : {
  589. 'en': 'New for you',
  590. 'pt': 'Novidades para ti',
  591. 'de': 'Neu für dich',
  592. 'fr': 'Nouveautés',
  593. 'es': 'Novedades para ti',
  594. 'cs': 'Novinky pro vás',
  595. 'vi': 'New for you', // --- needs translation
  596. 'it': 'New for you', // --- needs translation
  597. 'isSuggestion': true,
  598. 'defaultEnabled': false,
  599. },
  600. VF_LIVE : {
  601. 'en': 'LIVE',
  602. 'pt': 'DIRETO',
  603. 'de': 'LIVE',
  604. 'fr': 'EN DIRECT',
  605. 'es': 'ESTRENO',
  606. 'cs': 'ŽIVĚ',
  607. 'vi': 'TRỰC TIẾP',
  608. 'it': 'IN DIRETTA',
  609. 'isSuggestion': false,
  610. 'defaultEnabled': false,
  611. },
  612.  
  613. // *** Miscellaneous/Other items
  614. // -- info box - coronavirus
  615. OTHER_INFO_BOX_CORONAVIRUS : {
  616. 'en': 'Coronavirus (information box)',
  617. 'pt': 'Coronavírus (caixa de informações)',
  618. 'de': 'Coronavirus (Infobox)',
  619. 'fr': 'Coronavirus (encadré d\'information)',
  620. 'es': 'Coronavirus (cuadro de información)',
  621. 'cs': 'Coronavirus (informační box)',
  622. 'vi': 'Virus corona (hộp thông tin)',
  623. 'it': 'Coronavirus (casella informativa)',
  624. 'isInfoBox': true,
  625. 'defaultEnabled': false,
  626. 'pathMatch': '/coronavirus_info/', // -- the partial path name to match.
  627. },
  628. // -- info box - climate science
  629. OTHER_INFO_BOX_CLIMATE_SCIENCE : {
  630. 'en': 'Climate Science (information box)',
  631. 'pt': 'Ciência do Clima (caixa de informações)',
  632. 'de': 'Klimawissenschaft (Infobox)',
  633. 'fr': 'Science du climat (encadré d\'information)',
  634. 'es': 'Ciencia del clima (cuadro de información)',
  635. 'cs': 'Klimatická věda (informační box)',
  636. 'vi': 'Khoa học khí hậu (hộp thông tin)',
  637. 'it': 'Scienza del clima (casella informativa)',
  638. 'isInfoBox': true,
  639. 'defaultEnabled': false,
  640. 'pathMatch': '/climatescienceinfo/',
  641. },
  642. // -- info box - subscribe
  643. OTHER_INFO_BOX_SUBSCRIBE : {
  644. 'en': 'Subscribe (information box)',
  645. 'pt': 'Assine (caixa de informações)',
  646. 'de': 'Abonnieren (Infobox)',
  647. 'fr': 'S’abonner (encadré d\'information)',
  648. 'es': 'Suscribir (cuadro de información)',
  649. 'cs': 'Odebírat (informační box)',
  650. 'vi': 'Đăng kí (hộp thông tin)',
  651. 'it': 'Iscriviti (casella informativa)',
  652. 'isInfoBox': true,
  653. 'defaultEnabled': false,
  654. 'pathMatch': '/support/',
  655. },
  656. // -- nf - top of feed - "invitation to a survey"
  657. OTHER_SURVEY : {
  658. 'en': 'See Survey Details',
  659. 'pt': 'Veja os detalhes da pesquisa',
  660. 'de': 'Siehe Umfragedetails ',
  661. 'fr': 'Voir les détails de l\'enquête',
  662. 'es': 'Consulte los detalles de la encuesta',
  663. 'cs': 'Viz Podrobnosti průzkumu',
  664. 'vi': 'Xem chi tiết khảo sát',
  665. 'it': 'Vedi i dettagli del sondaggio',
  666. 'pathMatch': '/survey/',
  667. 'isTopOfNFFeed': true,
  668. 'defaultEnabled': false,
  669. },
  670. // -- nf - top of feed - "fb 2 m"
  671. OTHER_FB_RENAMED : {
  672. 'en': 'The Facebook company is now called Meta',
  673. 'pt': 'A empresa do Facebook agora se chama Meta',
  674. 'de': 'Das Facebook-Unternehmen heißt jetzt Meta',
  675. 'fr': 'La société Facebook s\'appelle désormais Meta',
  676. 'es': 'La compañía de Facebook ahora se llama Meta',
  677. 'cs': 'Facebooková společnost se nyní jmenuje Meta',
  678. 'vi': 'Công ty Facebook bây giờ được gọi là Meta',
  679. 'it': 'La società di Facebook si chiama ora Meta',
  680. 'urlMatch': 'about.facebook.com/meta/',
  681. 'isTopOfNFFeed': true,
  682. 'defaultEnabled': false,
  683. },
  684.  
  685. // *** Dialog box
  686. // - Title
  687. DLG_TITLE : {
  688. 'en': 'Clean my feeds',
  689. 'pt': 'Limpe meus feeds',
  690. 'de': 'Bereinige meine Feeds',
  691. 'fr': 'Nettoyer mes flux',
  692. 'es': 'Limpia mis feeds',
  693. 'cs': 'Vyčistěte mé kanály',
  694. 'vi': 'Làm sạch nguồn cấp dữ liệu của tôi',
  695. 'it': 'Pulisci i miei feed',
  696. },
  697. DLG_NF : {
  698. 'en': 'News Feed',
  699. 'pt': 'Feed de notícias',
  700. 'de': 'Newsfeed',
  701. 'fr': 'Fil de nouvelles',
  702. 'es': 'Feed de noticias',
  703. 'cs': 'Informační kanál',
  704. 'vi': 'Nguồn cấp tin tức',
  705. 'it': 'Feed di notizie', // news section
  706. },
  707. DLG_GF : {
  708. 'en': 'Groups Feed',
  709. 'pt': 'Feed de grupos',
  710. 'de': 'Gruppen-Feed',
  711. 'fr': 'Flux de groupes',
  712. 'es': 'Feed de grupos',
  713. 'cs': 'Skupinový kanál',
  714. 'vi': 'Nguồn cấp dữ liệu Nhóm',
  715. 'it': 'Feed di gruppo',
  716. },
  717. DLG_VF : {
  718. 'en': 'Videos Feed',
  719. 'pt': 'Feed de vídeos',
  720. 'de': 'Video-Feed',
  721. 'fr': 'Flux de vidéos',
  722. 'es': 'Feed de vídeos',
  723. 'cs': 'Video kanál',
  724. 'vi': 'Nguồn cấp dữ liệu video',
  725. 'it': 'Feed di video',
  726. },
  727. DLG_MP : {
  728. 'en': 'Marketplace Feed',
  729. 'pt': 'Feed de mercado',
  730. 'de': 'Marktplatz-Feed',
  731. 'fr': 'Flux de la place de marché',
  732. 'es': 'Feed de Marketplace',
  733. 'cs': 'Marketplace kanál',
  734. 'vi': 'Nguồn cấp dữ liệu Marketplace',
  735. 'it': 'Feed id Marketplace',
  736. },
  737. DLG_OTHER : {
  738. 'en': 'Miscellaneous items',
  739. 'pt': 'Itens miscelâneos',
  740. 'de': 'Sonstige Gegenstände',
  741. 'fr': 'Articles divers',
  742. 'es': 'Artículos diversos',
  743. 'cs': 'Různé položky',
  744. 'vi': 'Những thứ linh tinh',
  745. 'it': 'Articoli vari',
  746. },
  747. DLG_NF_BLOCK : {
  748. 'en': 'News Feed - text filter',
  749. 'pt': 'Feed de notícias - filtro de texto',
  750. 'de': 'Newsfeed - Textfilter',
  751. 'fr': 'Fil de nouvelles - filtre de texte',
  752. 'es': 'Feed de noticias: filtro de texto',
  753. 'cs': 'Informační kanál - textový filtr',
  754. 'vi': 'Nguồn cấp tin tức - bộ lọc văn bản',
  755. 'it': 'Feed di notizie - filtro di testo',
  756. },
  757. DLG_GF_BLOCK : {
  758. 'en': 'Groups Feed - text filter',
  759. 'pt': 'Feed de grupos - filtro de texto',
  760. 'de': 'Gruppen-Feed - Textfilter',
  761. 'fr': 'Flux de groupes - filtre de texte',
  762. 'es': 'Feed de grupos: filtro de texto',
  763. 'cs': 'Skupinový kanál - textový filtr',
  764. 'vi': 'Nguồn cấp dữ liệu Nhóm - bộ lọc văn bản',
  765. 'it': 'Feed di gruppo - filtro di testo',
  766. },
  767. DLG_VF_BLOCK : {
  768. 'en': 'Videos Feed - text filter',
  769. 'pt': 'Feed de vídeos - filtro de texto',
  770. 'de': 'Video-Feed - Textfilter',
  771. 'fr': 'Flux de vidéos - filtre de texte',
  772. 'es': 'Feed de videos - filtro de texto',
  773. 'cs': 'Video kanál - textový filtr',
  774. 'vi': 'Nguồn cấp dữ liệu video - bộ lọc văn bản',
  775. 'it': 'Feed di video - filtro di testo',
  776. },
  777. DLG_BLOCK_NEW_LINE : {
  778. 'en': '(separate words or phrases with a line break)',
  779. 'pt': '(separe palavras ou frases com quebras de linha)',
  780. 'de': '(trennen Sie Wörter oder Sätze mit Zeilenumbrüchen)',
  781. 'fr': '(mots ou phrases séparés avec des sauts de ligne)',
  782. 'es': '(palabras o frases separadas con saltos de línea)',
  783. 'cs': '(oddělte slova nebo fráze na nový řádek)',
  784. 'vi': '(tách các từ hoặc cụm từ bằng dấu ngắt dòng)',
  785. 'it': '(separare parole o frasi con un\'interruzione di riga)',
  786. },
  787. NF_BLOCKED_ENABLED : {
  788. 'en': 'Enabled',
  789. 'pt': 'Habilidoso',
  790. 'de': 'Ermöglichte',
  791. 'fr': 'Activé',
  792. 'es': 'Habilitadas',
  793. 'cs': 'Zapnuto',
  794. 'vi': 'Đã kích hoạt',
  795. 'it': 'Abilita opzione',
  796. },
  797. GF_BLOCKED_ENABLED : {
  798. 'en': 'Enabled',
  799. 'pt': 'Habilidoso',
  800. 'de': 'Ermöglichte',
  801. 'fr': 'Activé',
  802. 'es': 'Habilitadas',
  803. 'cs': 'Zapnuto',
  804. 'vi': 'Đã kích hoạt',
  805. 'it': 'Abilita opzione',
  806. },
  807. VF_BLOCKED_ENABLED : {
  808. 'en': 'Enabled',
  809. 'pt': 'Habilidoso',
  810. 'de': 'Ermöglichte',
  811. 'fr': 'Activé',
  812. 'es': 'Habilitadas',
  813. 'cs': 'Zapnuto',
  814. 'vi': 'Đã kích hoạt',
  815. 'it': 'Abilita opzione',
  816. },
  817. DLG_VERBOSITY : {
  818. 'en': 'Verbosity',
  819. 'pt': 'Verbosidade',
  820. 'de': 'Ausführlichkeit',
  821. 'fr': 'Verbosité',
  822. 'es': 'Verbosidad',
  823. 'cs': 'Výřečnost',
  824. 'vi': 'Tính dài dòng',
  825. 'it': 'Verbosità',
  826. },
  827. DLG_VERBOSITY_MESSAGE : {
  828. 'en': 'Display a message if a post is hidden',
  829. 'pt': 'Exibir uma mensagem se uma postagem estiver oculta',
  830. 'de': 'Nachricht anzeigen, wenn ein Beitrag ausgeblendet ist',
  831. 'fr': 'Afficher un message si une publication est masquée',
  832. 'es': 'Mostrar un mensaje si una publicación está oculta',
  833. 'cs': 'Zobrazit zprávu, pokud je příspěvek skrytý',
  834. 'vi': 'Hiển thị một tin nhắn nếu một bài đăng bị ẩn',
  835. 'it': 'Visualizza un messaggio se un post è nascosto',
  836. },
  837. VERBOSITY_NO_MESSAGE : {
  838. 'en': 'no message',
  839. 'pt': 'nenhuma mensagem',
  840. 'de': 'keine Nachricht',
  841. 'fr': 'pas de message',
  842. 'es': 'Sin mensaje',
  843. 'cs': 'žádná zpráva',
  844. 'vi': 'không có tin nhắn',
  845. 'it': 'Nessun messaggio',
  846. },
  847. VERBOSITY_COLOUR : {
  848. 'en': 'Text colour',
  849. 'pt': 'Cor do texto',
  850. 'de': 'Textfarbe',
  851. 'fr': 'Couleur du texte',
  852. 'es': 'Color del texto',
  853. 'cs': 'Barva textu',
  854. 'vi': 'Màu văn bản',
  855. 'it': 'Colore del testo',
  856. },
  857. VERBOSITY_BG_COLOUR : {
  858. 'en': 'Background colour',
  859. 'pt': 'Cor de fundo',
  860. 'de': 'Hintergrundfarbe',
  861. 'fr': 'Couleur de fond',
  862. 'es': 'Color de fondo',
  863. 'cs': 'Barva pozadí',
  864. 'vi': 'Màu nền',
  865. 'it': 'Colore di sfondo',
  866. },
  867. VERBOSITY_DEBUG : {
  868. 'en': 'Highlight "hidden" posts',
  869. 'pt': 'Destacar postagens "ocultas"',
  870. 'de': 'Markieren Sie "versteckte" Beiträge',
  871. 'fr': 'Mettez en surbrillance les messages « cachés »',
  872. 'es': 'Destacar publicaciones "ocultas"',
  873. 'cs': 'Zvýrazněte „skryté“ příspěvky',
  874. 'vi': 'Đánh dấu các bài đăng "ẩn"',
  875. 'it': 'Evidenzia i post "nascosti"',
  876. },
  877. // CMF's customisations
  878. CMF_CUSTOMISATIONS : {
  879. 'en': 'Customisations',
  880. 'pt': 'Personalizações',
  881. 'de': 'Anpassungen',
  882. 'fr': 'Personnalisations',
  883. 'es': 'Personalizaciones',
  884. 'cs': 'Přizpůsobení',
  885. 'vi': 'Các tùy chỉnh',
  886. 'it': 'Personalizzazioni',
  887. },
  888. CMF_BTN_LOCATION : {
  889. 'en': 'Location of Clean my feeds\' button',
  890. 'pt': 'Localização do botão Limpe meus feeds',
  891. 'de': 'Position der Schaltfläche "Bereinige meine Feeds"',
  892. 'fr': 'Emplacement du bouton Nettoyer mes flux',
  893. 'es': 'Ubicación del botón Limpia mis feeds',
  894. 'cs': 'Umístění tlačítka Vyčistěte mé kanály',
  895. 'vi': 'Vị trí của nút Làm sạch nguồn cấp dữ liệu của tôi',
  896. 'it': 'Posizione del pulsante Pulisci i miei feed',
  897. },
  898. CMF_BTN_OPTION : {
  899. 'en': ['bottom left', 'top right'],
  900. 'pt': ['inferior esquerdo', 'superior direito'],
  901. 'de': ['unten links', 'oben rechts'],
  902. 'fr': ['en bas à gauche', 'en haut à droite'],
  903. 'es': ['abajo a la izquierda', 'arriba a la derecha'],
  904. 'cs': ['vlevo dole', 'vpravo nahoře'],
  905. 'vi': ['dưới cùng bên trái', 'trên cùng bên phải'],
  906. 'it': ['in basso a sinistra', 'in alto a destra'],
  907. 'defaultValue': 0,
  908. },
  909. CMF_DIALOG_LOCATION : {
  910. 'en': 'Location of Clean my feeds\' dialog box',
  911. 'pt': 'Localização da caixa de diálogo Limpe meus feeds',
  912. 'de': 'Position des Dialogfelds "Bereinige meine Feeds"',
  913. 'fr': 'Emplacement de la boîte de dialogue Nettoyer mes flux',
  914. 'es': 'Ubicación del cuadro de diálogo Limpia mis feeds',
  915. 'cs': 'Umístění dialogového okna Vyčistěte mé kanály',
  916. 'vi': 'Vị trí của hộp thoại Làm sạch nguồn cấp dữ liệu của tôi',
  917. 'it': 'Posizione della finestra di dialogo Pulisci i miei feed',
  918. },
  919. CMF_DIALOG_OPTION : {
  920. 'en': ['left side', 'right side'],
  921. 'pt': ['lado esquerdo', 'lado direito'],
  922. 'de': ['linke Seite', 'rechte Seite'],
  923. 'fr': ['côté gauche', 'côté droit'],
  924. 'es': ['lado izquierdo', 'lado derecho'],
  925. 'cs': ['levá strana', 'pravá strana'],
  926. 'vi': ['bên trái', 'bên phải'],
  927. 'it': ['lato sinistro', 'lato destro'],
  928. 'defaultValue': 0,
  929. },
  930. CMF_BORDER_COLOUR : {
  931. 'en': 'Border colour',
  932. 'pt': 'Cor da borda',
  933. 'de': 'Farbe der Umrandung',
  934. 'fr': 'Couleur de bordure',
  935. 'es': 'Color de borde',
  936. 'cs': 'Barva ohraničení',
  937. 'vi': 'Màu viền',
  938. 'it': 'Colore del bordo',
  939. },
  940. CMF_BORDER_OPTION : {
  941. 'defaultValue': 'orangered',
  942. },
  943. DLG_TIPS : {
  944. 'en': 'Tips"',
  945. 'pt': 'Pontas',
  946. 'de': 'Tipps',
  947. 'fr': 'Des astuces',
  948. 'es': 'Consejos',
  949. 'cs': 'Tipy',
  950. 'vi': 'Thủ thuật',
  951. 'it': 'Suggerimenti',
  952. },
  953. DLG_TIPS_CONTENT : {
  954. '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.',
  955. '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.',
  956. '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.',
  957. '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.',
  958. '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.',
  959. '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í.',
  960. '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. Sử 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.',
  961. '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.',
  962. },
  963. DLG_BUTTONS : {
  964. 'en': ['Save', 'Close', 'Export', 'Import'],
  965. 'pt': ['Salvar', 'Fechar', 'Exportar', 'Importar'],
  966. 'de': ['Speichern', 'Schließen', 'Exportieren', 'Importieren'],
  967. 'fr': ['Sauvegarder', 'Fermer', 'Exporter', 'Importer'],
  968. 'es': ['Guardar', 'Cerrar', 'Exportar', 'Importar'],
  969. 'cs': ['Zachránit', 'Zavřít', 'Export', 'Import'],
  970. 'vi': ['Lưu', 'Đóng', 'Xuất', 'Nhập'],
  971. 'it': ['Salva', 'Chiudi', 'Esportare', 'Importare'],
  972. },
  973. };
  974. // *** *** end of language components *** ***
  975.  
  976. // - Feed Details variables
  977. // -- nb: setFeedSettings() adjusts some of these settings.
  978. const VARS = {
  979. // - langauge (default to EN)
  980. language: '',
  981. // - user options
  982. Options: {},
  983. // - blocked text
  984. Filters: {},
  985. // - Sponsored word
  986. sponsoredWord: [],
  987. sponsoredWordMP: [],
  988. sponsoredPaidForWords: [],
  989. // - Suggestions
  990. // -- "current" feed
  991. suggestions : [],
  992. // - block text - partial matches (heading block, content block)
  993. // -- "current" feed. lc = lower case.
  994. blockText: false,
  995. blockTextMatch: [],
  996. blockTextMatchLC: [],
  997. // -- news feed suggestions
  998. nfSuggestions: [],
  999. // -- groups feed suggestions
  1000. gfSuggestions: [],
  1001. // -- videos feed suggestions
  1002. vfSuggestions: [],
  1003.  
  1004. // - URLs for Info boxes - Information boxes that appear between the post and comments
  1005. // -- e.g. coronavirus, climate science.
  1006. // -- hide the info box, not the post.
  1007. // -- paths' values must be in lowercase. code does partial match.
  1008. // -- set pathMatch on the relevant Keywords entry.
  1009. infoBoxes: false,
  1010. infoBoxesPaths: [],
  1011.  
  1012. // - Query String selectors for getting a collection of Feed posts / elements
  1013. QS : '',
  1014. newsFeedQS: 'div[role="feed"] > div',
  1015. groupsFeedQS: 'div[role="feed"] > div',
  1016. // - News and Groups feeds post's blocks (posts have 1-4 blocks)
  1017. // -- used by the fn extractTextContent() and fn doMoppingInfoBox()
  1018. postBlocksQS: ':scope > div > div > div > div > div > div > div > div > div > div > div > div > div',
  1019. // - groups feed intro posts - exclude procseed post(s)
  1020. // --- two variations in stucture
  1021. groupsNonFeedsQS: `div[role="main"] > div > div > div > div:nth-of-type(2) > div:not([${postAtt}]) ,
  1022. div[role="main"] div[role="main"] > div > div > div > div:first-of-type > div > div:first-of-type > div:not([${postAtt}])`,
  1023. // - non regular feed post blocks
  1024. nonRegularPostBlocksQS: ':scope > div > div > div > div > div > div > div:first-of-type',
  1025. // - videos feed
  1026. videosFeedQS: 'div#watch_feed > div > div > div > div > div > div[class], #watch_feed div[data-pagelet="MainFeed"] > div > div > div > div' ,
  1027. videosFeedQS2: 'div[id="watch_feed"] > div:not([class]) > div[class]:nth-of-type(2) > div > div > div:not([class]) > div[class] > div[class] > div:not([class]) > div[class]',
  1028. // - video feed post's blocks
  1029. videoBlockQS: ':scope > div > div > div > div > div:nth-of-type(2) > div',
  1030. // - video "new video for you" (post above feed)
  1031. videoNonFeedQS: '[id=watch_feed] > div > div:first-of-type > div',
  1032. videNonFeedPostBlock: ':scope > div:first-of-type',
  1033. // - marketplace - exclude boxes already processed (pre May 2022)
  1034. marketplaceQS1: `div[data-pagelet="MainFeed"] div[data-pagelet^="BrowseFeedUpsell"]:not([${postAtt}])`,
  1035. // - marketplace - exclude boxes already processed (May 2022 ->).
  1036. marketplaceQS2: `div[role="main"] a[href^="/ads/"]:not([${postAtt}])`,
  1037. // - third column - sponsored box - set by addCSS()
  1038. thirdColQS1: '',
  1039. // - third column - groups suggested for you - set by addCSS() (May 2022 ->)
  1040. thirdColQS2: '',
  1041. // - create room (pre May 2022)
  1042. createRoomQS1: `div[data-pagelet="VideoChatHomeUnit"]:not([${postAtt}]) , div[data-pagelet="VideoChatHomeUnitNoDDD"]:not([${postAtt}])`,
  1043. // - create room (May 2022 ->)
  1044. createRoomQS2: `div:not([${postAtt}]) > div > div > div > div[data-visualcompletion="ignore-dynamic"][class=""] i[data-visualcompletion="css-img"]`,
  1045. // - stories - (May 2022 ->)
  1046. storiesQS1: '[id="ssrb_stories_start"]',
  1047. // - stories - (May 2022 ->) - becareful, may hide main feed if stories slow to show (hence [aria-label] attribute) ...
  1048. storiesQS2: `div[role="main"] > div > div > div > div:nth-of-type(2):not([${postAtt}]) > div[aria-label]`,
  1049. // - sponsored - paid for
  1050. sponsoredPaidForQS: '[role="button"]',
  1051.  
  1052. // - search page, "all" (top)
  1053. searchTopQS: 'div[role="feed"] > div',
  1054.  
  1055. // - Feed toggles
  1056. isNF : false,
  1057. isGF : false,
  1058. isVF : false,
  1059. isMP : false,
  1060. isAF : false,
  1061. isSF : false,
  1062.  
  1063. // marketplace feed type (std | category)
  1064. mpType: '',
  1065. // marketplace - viewing an item
  1066. mpItem: false,
  1067.  
  1068. // remember current URL - used for page change detection
  1069. prevURL : '',
  1070. prevPathname : '',
  1071.  
  1072. // number of posts to check/inspect
  1073. // - need to re-process existing posts as sometimes fb is slow/late to populate/update them
  1074. // - nb: fb has 2-3 "dummies" at the bottom of the feed.
  1075. inspectPostCount: 16,
  1076. // element containing echo message about post(s) being hidden
  1077. echoEl: null,
  1078. // how many consecutive posts are hidden
  1079. echoCount: 0,
  1080. // count of checks made for non-feed posts
  1081. nfpLoopCount: 0,
  1082. // max checks for non-feed posts
  1083. nfpLoopCountLimit: 128,
  1084.  
  1085. // indicate if stories was found and stop looking for it
  1086. storiesFound: false,
  1087. // indicate if create-room was found and stop looking for it
  1088. crFound: false,
  1089. // indicate if right-rail was found and stop looking for it
  1090. // (code will set to true to stop hunting for third column)
  1091. tcFound: false,
  1092. // indicate if fb-meta was found and stop looking for it
  1093. f2mFound: false,
  1094. // indicate if survey was found and stop looking for it
  1095. surveyFound: false,
  1096. otherLoopCount: 0,
  1097. otherLoopCountLimit: 32,
  1098.  
  1099. // StyleSheet Id
  1100. cssID : '',
  1101. // CSS class names
  1102. cssHide : '',
  1103. cssHideEl : '',
  1104. cssEcho : '',
  1105. // toggle dialog button (visible if is a Feed page)
  1106. btnToggleEl : null,
  1107. // - script's logo
  1108. 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">' +
  1109. '<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.' +
  1110. '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"/>' +
  1111. '</g></svg>'
  1112. };
  1113.  
  1114. // -- which language is the FB page in?
  1115. function setLanguageAndOptions(){
  1116. // - run this function when HEAD is available.
  1117. // - language (default to EN)
  1118. // - also run getUserOptions().
  1119. if (document.head) {
  1120. let lang = document.head.parentNode.lang || 'en';
  1121. VARS.language = (KeyWords.LANGUAGES.indexOf(lang) >= 0) ? lang : 'en';
  1122. // - sponsored word
  1123. VARS.sponsoredWord = KeyWords.SPONSORED[VARS.language];
  1124. VARS.sponsoredWordMP = KeyWords.MP_SPONSORED[VARS.language];
  1125. VARS.sponsoredPaidForWords = KeyWords.NF_SPONSORED_PAID[VARS.language].replaceAll('_','').trim();
  1126. // ...
  1127. let result = getUserOptions()
  1128. .then(() => {
  1129. return true;
  1130. });
  1131. }
  1132. else {
  1133. setTimeout(setLanguageAndOptions, 5);
  1134. }
  1135. }
  1136.  
  1137. // -- posts CSS
  1138. function addCSS() {
  1139. // - CSS styles for hiding or highlighting the selected posts / element
  1140.  
  1141. function generateRandomName() {
  1142. // - generate random names (first letter must be an alphabet)
  1143. let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  1144. let str = chars.charAt(Math.floor(Math.random() * (chars.length-10)));
  1145. for (let i = 0; i < 12; i++) {
  1146. str += chars.charAt(Math.floor(Math.random() * chars.length));
  1147. }
  1148. return str;
  1149. }
  1150. let isNewCSS, head, styleEl, css;
  1151.  
  1152. isNewCSS = true;
  1153. if (VARS.cssID !== '') {
  1154. // Grab the existing Stylesheet
  1155. styleEl = document.getElementById(VARS.cssID);
  1156. if (styleEl) {
  1157. // -- zap out the "old" styles.
  1158. styleEl.replaceChildren();
  1159. isNewCSS = false;
  1160. }
  1161. }
  1162. if (isNewCSS) {
  1163. // Create the new Stylesheet
  1164. VARS.cssID = generateRandomName().toUpperCase();
  1165. head = document.getElementsByTagName('head')[0];
  1166. styleEl = document.createElement('style');
  1167. styleEl.setAttribute('type', 'text/css');
  1168. styleEl.setAttribute('id', VARS.cssID);
  1169.  
  1170. // - remember class names (for other functions to use)
  1171. VARS.cssHide = generateRandomName(); // - the parent element - hide it's child element
  1172. VARS.cssHideEl = generateRandomName(); // - the elment to hide (mainly for marketplace)
  1173. VARS.cssEcho = generateRandomName();
  1174. }
  1175.  
  1176. // - insert Styles (as classes)
  1177. // - NF/GF/VF
  1178. // -- remove margins
  1179. if (VARS.Options.VERBOSITY_DEBUG === false) {
  1180. // -- not debugging, remove margins
  1181. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide}, .${VARS.cssHideEl} {margin:0 !important;}`));
  1182. }
  1183. // -- post wrapper's first child div (mainly for news, groups and video feeds posts)
  1184. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide} > div:first-of-type, `));
  1185. // -- post wrapper's element (mainly for marketplace posts)
  1186. styleEl.appendChild(document.createTextNode(`.${VARS.cssHideEl}, `));
  1187. // -- news, groups & video posts' info boxes
  1188. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide} [${postAttIB}] `));
  1189. // -- which styles to apply?
  1190. if (VARS.Options.VERBOSITY_DEBUG === true) {
  1191. styleEl.appendChild(document.createTextNode(' {border:5px dotted orange !important; width:66%;}'));
  1192. }
  1193. else {
  1194. styleEl.appendChild(document.createTextNode(' {display:none !important;}'));
  1195. }
  1196.  
  1197. // - echo msg
  1198. let colourMsg = (VARS.Options.VERBOSITY_COLOUR === '') ? '' : `color: ${VARS.Options.VERBOSITY_COLOUR}; `;
  1199. colourMsg += (VARS.Options.VERBOSITY_BG_COLOUR === '') ? '' : `background-color: ${VARS.Options.VERBOSITY_BG_COLOUR}; `;
  1200. css = `margin:1.25rem 0 1.5rem 0 !important; padding:0.75rem 1rem; border-radius:0.55rem; font-style:italic; ${colourMsg}`;
  1201. styleEl.appendChild(document.createTextNode(`.${VARS.cssHide} > p {${css}}`));
  1202.  
  1203. // - dailog box CSS
  1204. // --- dialog box
  1205. // -- position + flex
  1206. let bcolour = (VARS.Options.CMF_BORDER_COLOUR === '') ? KeyWords.CMF_BORDER_OPTION.defaultValue : VARS.Options.CMF_BORDER_COLOUR;
  1207. // - left / right done in fn addExtraCSS().
  1208. 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;`;
  1209. styleEl.appendChild(document.createTextNode(`.fb-cmf {${css}}`));
  1210. styleEl.appendChild(document.createTextNode('.__fb-light-mode .fb-cmf {background-color: #fefefa !important;}'));
  1211. styleEl.appendChild(document.createTextNode('.__fb-dark-mode .fb-cmf {background-color:var(--web-wash) !important;}'));
  1212.  
  1213. // -- header
  1214. css = 'display:flex; justify-content: space-between;';
  1215. styleEl.appendChild(document.createTextNode(`.fb-cmf header {${css}}`));
  1216.  
  1217. css = 'flex-grow:0; align-self:auto; width:75px; text-align:left;';
  1218. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-icon {${css}}`));
  1219. css = 'width:64px; height:64px; margin:2px 0;'
  1220. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-icon svg {${css}}`));
  1221.  
  1222. css = 'flex-grow:2; align-self:auto;';
  1223. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-title {${css}}`));
  1224. css = 'padding-top:1.25rem;';
  1225. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-lang-1 {${css}}`));
  1226. css = 'padding-top:0.75rem;';
  1227. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-lang-2 {${css}}`));
  1228.  
  1229. css = 'font-size:1.35rem; font-weight: 700; text-align:center;';
  1230. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-title > div {${css}}`));
  1231. css = 'display:block; font-size:0.8rem; text-align:center;';
  1232. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-title > small {${css}}`));
  1233.  
  1234. css = 'flex-grow:0; align-self:auto; width:75px; text-align:right; padding: 1.5rem 0 0 0;';
  1235. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-close {${css}}`));
  1236. css = 'width:1.75rem; height:1.5rem; font-family: monospace;';
  1237. styleEl.appendChild(document.createTextNode(`.fb-cmf header .fb-cmf-close button {${css}}`));
  1238.  
  1239. // -- content
  1240. css = `flex:1; overflow:auto; border:2px double ${bcolour}; border-radius:0.5rem; color: var(--primary-text);`;
  1241. styleEl.appendChild(document.createTextNode(`.fb-cmf div.content {${css}}`));
  1242. css = 'padding:1rem; text-align:center;';
  1243. styleEl.appendChild(document.createTextNode(`.fb-cmf footer.buttons {${css}}`));
  1244. css = 'margin:0.5rem; border-color:lightgrey;';
  1245. styleEl.appendChild(document.createTextNode(`.fb-cmf fieldset {${css}}`));
  1246. css = 'font-weight:700;';
  1247. styleEl.appendChild(document.createTextNode(`.fb-cmf fieldset legend {${css}}`));
  1248. css = 'display:inline-block; padding:0.125rem 0; width:95%; color: var(--primary-text); font-weight: normal;';
  1249. styleEl.appendChild(document.createTextNode(`.fb-cmf fieldset label {${css}}`));
  1250. css = 'margin: 0 0.5rem 0 0; vertical-align:baseline;';
  1251. styleEl.appendChild(document.createTextNode(`.fb-cmf fieldset label input {${css}}`));
  1252. css = 'color:darkgrey;';
  1253. styleEl.appendChild(document.createTextNode(`.fb-cmf fieldset label[disabled] {${css}}`));
  1254. css = 'width:100%; height:12rem;';
  1255. styleEl.appendChild(document.createTextNode(`.fb-cmf fieldset textarea {${css}}`));
  1256. css = 'background-color: lightgrey;';
  1257. styleEl.appendChild(document.createTextNode(`.__fb-dark-mode .fb-cmf fieldset textarea, .__fb-dark-mode .fb-cmf fieldset input[type="input"] {${css}}`));
  1258. // -- footer - buttons
  1259. css = 'margin-left: 1rem; margin-right:1rem;';
  1260. styleEl.appendChild(document.createTextNode(`.fb-cmf .buttons button {${css}}`));
  1261. // -- footer - file input
  1262. styleEl.appendChild(document.createTextNode('.fb-cmf .fileInput {display:none;}'));
  1263. // -- footer - import results
  1264. css = 'font-style:italic; margin-top: 1rem;';
  1265. styleEl.appendChild(document.createTextNode(`.fb-cmf .fileResults {${css}}`));
  1266. // -- show dialog box (default is not to show)
  1267. css = 'opacity:1; transform:scale(1);';
  1268. styleEl.appendChild(document.createTextNode(`.fb-cmf.show {${css}}`));
  1269. // - add above styles to HEAD.
  1270. if (isNewCSS) {
  1271. head.appendChild(styleEl);
  1272. }
  1273.  
  1274. // - set the right-rail query selector - excludes the hide class.
  1275. // -- first rule is May 2022 ->, second is pre May 2022.
  1276. VARS.thirdColQS1 = `div[role="complementary"] > div:first-of-type:not(.${VARS.cssHide}) > div > div > div > div > span, div[data-pagelet="RightRail"] > div:first-of-type:not(.${VARS.cssHide}) > span`;
  1277. // -- groups - suggested for you, May 2022 ->
  1278. VARS.thirdColQS2 = `div[role="complementary"] > div:first-of-type:not(.${VARS.cssHide}) > div > div > div > div > div`;
  1279. }
  1280. function addExtraCSS() {
  1281. // - extra CSS styles
  1282. // - fb can sometimes be a bit slow in loading certain parts of the site ...
  1283. // - ... this function is called several ms later ...
  1284. // - ... and when saving the options (via save button)
  1285. let cmfBtnLocation = KeyWords.CMF_BTN_OPTION.defaultValue;
  1286. let cmfDlgLocation = KeyWords.CMF_DIALOG_OPTION.defaultValue
  1287. if (VARS.Options.hasOwnProperty('CMF_BTN_LOCATION')) {
  1288. if (VARS.Options.CMF_BTN_LOCATION.toString() !== '') {
  1289. cmfBtnLocation = VARS.Options.CMF_BTN_LOCATION;
  1290. }
  1291. if (VARS.Options.CMF_DIALOG_LOCATION.toString() !== '') {
  1292. cmfDlgLocation = VARS.Options.CMF_DIALOG_LOCATION;
  1293. }
  1294. }
  1295. cmfBtnLocation = cmfBtnLocation.toString();
  1296. cmfDlgLocation = cmfDlgLocation.toString();
  1297.  
  1298. let styleEl, css;
  1299.  
  1300. // Grab the existing Stylesheet and amend it
  1301. styleEl = document.getElementById(VARS.cssID);
  1302.  
  1303. // - button's location.
  1304. if (cmfBtnLocation === '1') {
  1305. // - top right - has the buttons running across the top of the page (pre May 2022).
  1306. css = 'margin-right: 42px;';
  1307. if (document.querySelector('[role="banner"]')) {
  1308. // - oldish FB structure has menu buttons across the top (changed for some users in Apr/May 2022)
  1309. styleEl.appendChild(document.createTextNode(`div[role="banner"] > div:last-of-type div[role="navigation"] {${css}}`));
  1310. }
  1311. css = 'position:fixed; top:0.5rem; right:0.5rem; display:none;';
  1312. }
  1313. else {
  1314. // - cmfBtnLocation === "0"
  1315. // - bottom left - has the buttons running down the side of the page (May 2022 ->).
  1316. css = 'position:fixed; bottom:3.2rem; left:1.1rem; display:none;';
  1317. }
  1318. styleEl.appendChild(document.createTextNode(`.fb-cmf-toggle {${css}}`));
  1319. // btn - basic styling.
  1320. styleEl.appendChild(document.createTextNode('.fb-cmf-toggle {border-radius:0.3rem;}'));
  1321. styleEl.appendChild(document.createTextNode('.fb-cmf-toggle svg {height:32px; width:32px;}'))
  1322. styleEl.appendChild(document.createTextNode('.fb-cmf-toggle:hover {cursor:pointer;}'));
  1323. // - dialog box's display
  1324. styleEl.appendChild(document.createTextNode('.fb-cmf-toggle.show {display:block;}'));
  1325. // - dialog box's left/right + animated open/close behaviour
  1326. if (cmfDlgLocation === '1') {
  1327. // - right
  1328. css = 'right:0.35rem; transform:scale(0);transform-origin:top right; transition:transform .45s ease, opacity .25s ease; ';
  1329. }
  1330. else {
  1331. // - cmfDlgLocation === '0' (left)
  1332. css = 'left:5rem; transform:scale(0);transform-origin:center center; transition:transform .45s ease, opacity .25s ease; ';
  1333. }
  1334. styleEl.appendChild(document.createTextNode(`.fb-cmf {${css}}`));
  1335. }
  1336.  
  1337. // -- get the user's settings ...
  1338. async function getUserOptions() {
  1339. // -- read in the saved data, else set defaults.
  1340. let changed = false;
  1341. // - reset Options
  1342. VARS.Options = new Object();
  1343.  
  1344. // - has the user previously saved options?
  1345. // -- if yes, the update Options
  1346. let result = await get(DBVARS.DBKey, DBVARS.ostore)
  1347. .then((values) => {
  1348. if (values) {
  1349. VARS.Options = JSON.parse(values);
  1350. return 1; // -- has data
  1351. }
  1352. else {
  1353. return 0; // -- no data (first time)
  1354. }
  1355. })
  1356. .catch((err) => {
  1357. console.info(`${log}getuserOptions() > get() - Error:`, err);
  1358. });
  1359. if (VARS.VERBOSITY_DEBUG) {
  1360. console.info(`${log}getUserOptions() > get():`, result);
  1361. }
  1362.  
  1363. // -- check that all variables exists ... if not, assign them default values..
  1364. // -- Sponsored (always enabled)
  1365. if (!VARS.Options.hasOwnProperty('NF_SPONSORED')) { VARS.Options.NF_SPONSORED = true; changed = true; }
  1366. if (!VARS.Options.hasOwnProperty('GF_SPONSORED')) { VARS.Options.GF_SPONSORED = true; changed = true; }
  1367. if (!VARS.Options.hasOwnProperty('VF_SPONSORED')) { VARS.Options.VF_SPONSORED = true; changed = true; }
  1368. if (!VARS.Options.hasOwnProperty('MP_SPONSORED')) { VARS.Options.MP_SPONSORED = true; changed = true; }
  1369.  
  1370. // -- rename keys
  1371. let okey = 'OTHER_STORIES';
  1372. let nkey = 'NF_STORIES';
  1373. if (VARS.Options.hasOwnProperty(okey)) {
  1374. VARS.Options[nkey] = VARS.Options[okey];
  1375. delete VARS.Options[okey];
  1376. }
  1377. if (!VARS.Options.hasOwnProperty(nkey)) { VARS.Options[nkey] = KeyWords[nkey].defaultEnabled; changed = true; }
  1378.  
  1379. okey = 'OTHER_CREATE_ROOM';
  1380. nkey = 'NF_CREATE_ROOM';
  1381. if (VARS.Options.hasOwnProperty(okey)) {
  1382. VARS.Options[nkey] = VARS.Options[okey];
  1383. delete VARS.Options[okey];
  1384. }
  1385. if (!VARS.Options.hasOwnProperty(nkey)) { VARS.Options[nkey] = KeyWords[nkey].defaultEnabled; changed = true; }
  1386.  
  1387. okey = 'OTHER_THIRD_COLUMN_SPONSORED';
  1388. nkey = 'NF_THIRD_COLUMN_SPONSORED';
  1389. if (VARS.Options.hasOwnProperty(okey)) {
  1390. VARS.Options[nkey] = VARS.Options[okey];
  1391. delete VARS.Options[okey];
  1392. }
  1393. if (!VARS.Options.hasOwnProperty(nkey)) { VARS.Options[nkey] = KeyWords[nkey].defaultEnabled; changed = true; }
  1394.  
  1395. okey = 'OTHER_THIRD_COLUMN_SUGGESTED_FOR_YOU';
  1396. nkey = 'NF_THIRD_COLUMN_SUGGESTED_FOR_YOU';
  1397. if (VARS.Options.hasOwnProperty(okey)) {
  1398. VARS.Options[nkey] = VARS.Options[okey];
  1399. delete VARS.Options[okey];
  1400. }
  1401. if (!VARS.Options.hasOwnProperty(nkey)) { VARS.Options[nkey] = KeyWords[nkey].defaultEnabled; changed = true; }
  1402.  
  1403. // -- which suggestions / info boxes / top of NF feed items have been enabled?
  1404. VARS.infoBoxes = false;
  1405. VARS.infoBoxesPaths = [];
  1406. for (const key in KeyWords) {
  1407. // -- is this key one of the News, Groups or Videos?
  1408. if (KeyWords[key].isSuggestion) {
  1409. // - does this key exist? if not, set default value.
  1410. if (!VARS.Options.hasOwnProperty(key)) {
  1411. VARS.Options[key] = KeyWords[key].defaultEnabled;
  1412. changed = true;
  1413. }
  1414. // - is this suggestion enabled? if yes, add to the relevant suggestions array.
  1415. if (VARS.Options[key]) {
  1416. // - nb: slice(0,2) gives you nf,gf,vf,mp.
  1417. VARS[`${key.slice(0,2).toLowerCase()}Suggestions`].push(KeyWords[key][VARS.language]);
  1418. }
  1419. }
  1420. else if (KeyWords[key].isInfoBox) {
  1421. // -- information boxes (e.g. coronavirus, climate science, subscribe)
  1422. // -- (appears between post's content and comments)
  1423. if (!VARS.Options.hasOwnProperty(key)) {
  1424. VARS.Options[key] = KeyWords[key].defaultEnabled;
  1425. changed = true;
  1426. }
  1427. if (VARS.Options[key]) {
  1428. VARS.infoBoxes = true;
  1429. VARS.infoBoxesPaths.push(KeyWords[key].pathMatch);
  1430. }
  1431. }
  1432. else if (KeyWords[key].isTopOfNFFeed) {
  1433. // -- top of nf (appears @ top of nf, not a regular post)
  1434. if (!VARS.Options.hasOwnProperty(key)) {
  1435. VARS.Options[key] = KeyWords[key].defaultEnabled;
  1436. changed = true;
  1437. }
  1438. }
  1439. }
  1440. let key = "NF_SPONSORED_PAID";
  1441. if (!VARS.Options.hasOwnProperty(key)) { VARS.Options[key] = KeyWords[key].defaultEnabled; changed = true; }
  1442. key = "VF_LIVE";
  1443. if (!VARS.Options.hasOwnProperty(key)) { VARS.Options[key] = KeyWords[key].defaultEnabled; changed = true; }
  1444.  
  1445. // -- all other options.
  1446. if (!VARS.Options.hasOwnProperty('NF_BLOCKED_ENABLED')) { VARS.Options.NF_BLOCKED_ENABLED = true; changed = true; }
  1447. if (!VARS.Options.hasOwnProperty('NF_BLOCKED_TEXT')) { VARS.Options.NF_BLOCKED_TEXT = ''; changed = true; }
  1448. if (!VARS.Options.hasOwnProperty('GF_BLOCKED_ENABLED')) { VARS.Options.GF_BLOCKED_ENABLED = true; changed = true; }
  1449. if (!VARS.Options.hasOwnProperty('GF_BLOCKED_TEXT')) { VARS.Options.GF_BLOCKED_TEXT = ''; changed = true; }
  1450. if (!VARS.Options.hasOwnProperty('VF_BLOCKED_ENABLED')) { VARS.Options.VF_BLOCKED_ENABLED = true; changed = true; }
  1451. if (!VARS.Options.hasOwnProperty('VF_BLOCKED_TEXT')) { VARS.Options.VF_BLOCKED_TEXT = ''; changed = true; }
  1452.  
  1453. if (!VARS.Options.hasOwnProperty('VERBOSITY_LEVEL')) { VARS.Options.VERBOSITY_LEVEL = '2'; changed = true; }
  1454. if (!VARS.Options.hasOwnProperty('VERBOSITY_COLOUR')) { VARS.Options.VERBOSITY_COLOUR = ''; changed = true; }
  1455. if (!VARS.Options.hasOwnProperty('VERBOSITY_BG_COLOUR')) { VARS.Options.VERBOSITY_BG_COLOUR = 'lightgrey'; changed = true; }
  1456. if (!VARS.Options.hasOwnProperty('VERBOSITY_DEBUG')) { VARS.Options.VERBOSITY_DEBUG = false; changed = true; }
  1457.  
  1458. if (!VARS.Options.hasOwnProperty('CMF_BTN_LOCATION')) { VARS.Options.CMF_BTN_LOCATION = KeyWords.CMF_BTN_OPTION.defaultValue; changed = true; }
  1459. if (!VARS.Options.hasOwnProperty('CMF_DIALOG_LOCATION')) { VARS.Options.CMF_DIALOG_LOCATION = KeyWords.CMF_DIALOG_OPTION.defaultValue; changed = true; }
  1460. if (!VARS.Options.hasOwnProperty('CMF_BORDER_COLOUR')) { VARS.Options.CMF_BORDER_COLOUR = ''; changed = true; }
  1461.  
  1462. if (changed) {
  1463. // - save the changes ...
  1464. // -- usually happen if first time setup or change in Options' variables.
  1465. let result = await set(DBVARS.DBKey, JSON.stringify(VARS.Options), DBVARS.ostore)
  1466. .then(() => {
  1467. return true;
  1468. })
  1469. .catch((err) => {
  1470. console.info(`${log}getUserOptions() > changed > saving - failed, Error:`, err);
  1471. return false;
  1472. });
  1473. if (VARS.Options.VERBOSITY_DEBUG) {
  1474. if (result) {
  1475. console.info(`${log}Changed - success`);
  1476. }
  1477. else {
  1478. console.info(`${log}Changed - failed`);
  1479. }
  1480. }
  1481. }
  1482.  
  1483. // - third column - sponsored -found flag - default is false;
  1484. // (set to true to stop mopping up third-col)
  1485. VARS.tcFound_Sponsored = !(VARS.Options.NF_THIRD_COLUMN_SPONSORED);
  1486. VARS.tcFound_Suggested4U = !(VARS.Options.NF_THIRD_COLUMN_SUGGESTED_FOR_YOU);
  1487.  
  1488. // - split the blocks of texts
  1489. splitBlocksOfTexts();
  1490. DBVARS.optionsReady = true;
  1491. }
  1492.  
  1493. function splitBlocksOfTexts() {
  1494. // split the blocks of texts entries into arrays
  1495. // also, get lower case versions of them
  1496. VARS.Filters = new Object();
  1497. VARS.Filters.NF_BLOCKED_TEXT = [];
  1498. VARS.Filters.GF_BLOCKED_TEXT = [];
  1499. VARS.Filters.VF_BLOCKED_TEXT = [];
  1500. VARS.Filters.NF_BLOCKED_TEXT_LC = [];
  1501. VARS.Filters.GF_BLOCKED_TEXT_LC = [];
  1502. VARS.Filters.VF_BLOCKED_TEXT_LC = [];
  1503. if (VARS.Options.NF_BLOCKED_ENABLED) {
  1504. VARS.Filters.NF_BLOCKED_TEXT = VARS.Options.NF_BLOCKED_TEXT.split('¦¦');
  1505. VARS.Filters.NF_BLOCKED_TEXT_LC = VARS.Filters.NF_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  1506. }
  1507. VARS.Filters.NF_BLOCKED_ENABLED = ((VARS.Filters.NF_BLOCKED_TEXT.length > 0) && (VARS.Filters.NF_BLOCKED_TEXT[0] !== ''));
  1508.  
  1509. if (VARS.Options.GF_BLOCKED_ENABLED) {
  1510. VARS.Filters.GF_BLOCKED_TEXT = VARS.Options.GF_BLOCKED_TEXT.split('¦¦');
  1511. VARS.Filters.GF_BLOCKED_TEXT_LC = VARS.Filters.GF_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  1512. }
  1513. VARS.Filters.GF_BLOCKED_ENABLED = ((VARS.Filters.GF_BLOCKED_TEXT.length > 0) && (VARS.Filters.GF_BLOCKED_TEXT[0] !== ''));
  1514.  
  1515. if (VARS.Options.VF_BLOCKED_ENABLED) {
  1516. VARS.Filters.VF_BLOCKED_TEXT = VARS.Options.VF_BLOCKED_TEXT.split('¦¦');
  1517. VARS.Filters.VF_BLOCKED_TEXT_LC = VARS.Filters.VF_BLOCKED_TEXT.map(btext => btext.toLowerCase());
  1518. }
  1519. VARS.Filters.VF_BLOCKED_ENABLED = ((VARS.Filters.VF_BLOCKED_TEXT.length > 0) && (VARS.Filters.VF_BLOCKED_TEXT[0] !== ''));
  1520. }
  1521. // -- run some functions now - not dependent on HEAD being available.
  1522. // (includes getUserOptions())
  1523. setLanguageAndOptions();
  1524.  
  1525. // -- dailog box for displaying options (called in runMO)
  1526. function buildMoppingDialog() {
  1527. // build the dialog box component ...
  1528. // -- BODY must be available for use.
  1529. // -- used for displaying/getting/setting the various options
  1530.  
  1531. function createCB(cbName, cbKeyWord, cbReadOnly=false) {
  1532. let div = document.createElement('div');
  1533. let cb = document.createElement('input');
  1534. cb.type = 'checkbox';
  1535. cb.name = cbName;
  1536. cb.value = cbKeyWord;
  1537. cb.checked = VARS.Options[cbKeyWord];
  1538. let label = document.createElement('label');
  1539. if (cbReadOnly) {
  1540. cb.checked = true;
  1541. cb.disabled = true;
  1542. label.setAttribute('disabled', 'disabled');
  1543. }
  1544. label.appendChild(cb);
  1545. if (KeyWords[cbKeyWord]) {
  1546. label.appendChild(document.createTextNode(KeyWords[cbKeyWord][VARS.language]));
  1547. }
  1548. else if (['NF_SPONSORED','GF_SPONSORED','VF_SPONSORED'].indexOf(cbKeyWord) >=0) {
  1549. // -- nb: above 3x NF_ values are not in KeyWords, but MP_SPONSORED is ...
  1550. label.appendChild(document.createTextNode(KeyWords['SPONSORED'][VARS.language]));
  1551. }
  1552. else {
  1553. label.appendChild(document.createTextNode(cbKeyWord));
  1554. }
  1555. div.appendChild(label);
  1556. return div;
  1557. }
  1558. function createRB(rbName, rbValue, rbLabelText) {
  1559. let div = document.createElement('div');
  1560. let rb = document.createElement('input');
  1561. rb.type = 'radio';
  1562. rb.name = rbName;
  1563. rb.value = rbValue;
  1564. rb.checked = (VARS.Options[rbName] === rbValue);
  1565. let label = document.createElement('label');
  1566. label.appendChild(rb);
  1567. label.appendChild(document.createTextNode(rbLabelText));
  1568. div.appendChild(label);
  1569. return div;
  1570. }
  1571. function createInput(iName, iLabel) {
  1572. let div = document.createElement('div');
  1573. let input = document.createElement('input');
  1574. input.type = 'text';
  1575. input.name = iName;
  1576. input.value = VARS.Options[iName];
  1577. let label = document.createElement('label');
  1578. label.appendChild(document.createTextNode(iLabel));
  1579. label.appendChild(document.createElement('br'));
  1580. label.appendChild(input);
  1581. div.appendChild(label);
  1582. return div;
  1583. }
  1584.  
  1585. function createDialog() {
  1586. let dlg, hdr, hdr1, hdr2, hdr3, htxt, stxt, btn, cnt, fs, l, s, ta, footer;
  1587.  
  1588. // -- wrapper
  1589. dlg = document.createElement('div');
  1590. dlg.id = 'fbcmf';
  1591. dlg.className = 'fb-cmf'; // class "show" reveals the dialog.
  1592. // -- header (logo + title + close button)
  1593. hdr = document.createElement('header');
  1594. hdr1 = document.createElement('div');
  1595. hdr1.className = 'fb-cmf-icon';
  1596. hdr1.innerHTML = VARS.logoHTML;
  1597.  
  1598. hdr2 = document.createElement('div');
  1599. hdr2.className = 'fb-cmf-title';
  1600. htxt = document.createElement('div');
  1601. htxt.textContent = KeyWords.DLG_TITLE['en'];
  1602. hdr2.appendChild(htxt);
  1603. if (VARS.language !== 'en') {
  1604. stxt = document.createElement('small');
  1605. stxt.textContent = `(${KeyWords.DLG_TITLE[VARS.language]})`;
  1606. hdr2.appendChild(stxt);
  1607. hdr2.classList.add('fb-cmf-lang-2');
  1608. }
  1609. else {
  1610. hdr2.classList.add('fb-cmf-lang-1')
  1611. }
  1612.  
  1613. hdr3 = document.createElement('div');
  1614. hdr3.className = 'fb-cmf-close';
  1615. btn = document.createElement('button');
  1616. btn.textContent = 'X';
  1617. btn.addEventListener("click", toggleMD, false);
  1618. hdr3.appendChild(btn);
  1619.  
  1620. hdr.appendChild(hdr1);
  1621. hdr.appendChild(hdr2);
  1622. hdr.appendChild(hdr3);
  1623. dlg.appendChild(hdr);
  1624.  
  1625. // content container
  1626. cnt = document.createElement('div');
  1627. cnt.classList.add('content');
  1628.  
  1629. // -- News Feed options
  1630. fs = document.createElement('fieldset');
  1631. l = document.createElement('legend');
  1632. l.textContent = KeyWords.DLG_NF[VARS.language];
  1633. fs.appendChild(l);
  1634. fs.appendChild(createCB('cbNF', 'NF_SPONSORED', true));
  1635. fs.appendChild(createCB('cbNF', 'NF_STORIES', false));
  1636. fs.appendChild(createCB('cbNF', 'NF_CREATE_ROOM', false));
  1637. for (const key in KeyWords) {
  1638. if (key.slice(0,3) === 'NF_' && KeyWords[key].isSuggestion) {
  1639. fs.appendChild(createCB('cbNF', key));
  1640. }
  1641. }
  1642. fs.appendChild(createCB('cbNF', 'NF_SPONSORED_PAID'));
  1643. fs.appendChild(createCB('cbNF', 'NF_THIRD_COLUMN_SPONSORED', false));
  1644. fs.appendChild(createCB('cbNF', 'NF_THIRD_COLUMN_SUGGESTED_FOR_YOU', false));
  1645. cnt.appendChild(fs);
  1646.  
  1647. // -- Groups Feed options
  1648. fs = document.createElement('fieldset');
  1649. l = document.createElement('legend');
  1650. l.textContent = KeyWords.DLG_GF[VARS.language];
  1651. fs.appendChild(l);
  1652. fs.appendChild(createCB('cbGF', 'GF_SPONSORED', true));
  1653. for (const key in KeyWords) {
  1654. if (key.slice(0,3) === 'GF_' && KeyWords[key].isSuggestion) {
  1655. fs.appendChild(createCB('cbGF', key));
  1656. }
  1657. }
  1658. cnt.appendChild(fs);
  1659.  
  1660. // -- Watch/Videos Feed options
  1661. fs = document.createElement('fieldset');
  1662. l = document.createElement('legend');
  1663. l.textContent = KeyWords.DLG_VF[VARS.language];
  1664. fs.appendChild(l);
  1665. fs.appendChild(createCB('cbVF', 'VF_SPONSORED', true));
  1666. for (const key in KeyWords) {
  1667. if (key.slice(0,3) === 'VF_' && KeyWords[key].isSuggestion) {
  1668. fs.appendChild(createCB('cbVF', key));
  1669. }
  1670. }
  1671. fs.appendChild(createCB('cbVF', 'VF_LIVE'));
  1672. cnt.appendChild(fs);
  1673.  
  1674. // -- MarketPlace option(s)
  1675. fs = document.createElement('fieldset');
  1676. l = document.createElement('legend');
  1677. l.textContent = KeyWords.DLG_MP[VARS.language];
  1678. fs.appendChild(l);
  1679. fs.appendChild(createCB('cbMP', 'MP_SPONSORED', true));
  1680. cnt.appendChild(fs);
  1681.  
  1682. // -- Other items options
  1683. fs = document.createElement('fieldset');
  1684. l = document.createElement('legend');
  1685. l.textContent = KeyWords.DLG_OTHER[VARS.language];
  1686. fs.appendChild(l);
  1687. for (const key in KeyWords) {
  1688. if (KeyWords[key].isInfoBox) {
  1689. fs.appendChild(createCB('cbOther', key));
  1690. }
  1691. }
  1692. fs.appendChild(createCB('cbOther', 'OTHER_SURVEY'));
  1693. fs.appendChild(createCB('cbOther', 'OTHER_FB_RENAMED'));
  1694. cnt.appendChild(fs);
  1695.  
  1696. // -- Keywords to block - News Feed
  1697. fs = document.createElement('fieldset');
  1698. l = document.createElement('legend');
  1699. l.textContent = KeyWords.DLG_NF_BLOCK[VARS.language];
  1700. fs.appendChild(l);
  1701. fs.appendChild(createCB('cbNFBT', 'NF_BLOCKED_ENABLED'));
  1702. s = document.createElement('small');
  1703. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE[VARS.language]));
  1704. fs.appendChild(s);
  1705. ta = document.createElement('textarea');
  1706. ta.name = 'NF_BLOCKED_TEXT';
  1707. ta.textContent = VARS.Filters.NF_BLOCKED_TEXT.join('\n');
  1708. fs.appendChild(ta);
  1709. cnt.appendChild(fs);
  1710.  
  1711. // -- Keywords to block - Groups Feed
  1712. fs = document.createElement('fieldset');
  1713. l = document.createElement('legend');
  1714. l.textContent = KeyWords.DLG_GF_BLOCK[VARS.language];
  1715. fs.appendChild(l);
  1716. fs.appendChild(createCB('cbGFBT', 'GF_BLOCKED_ENABLED'));
  1717. s = document.createElement('small');
  1718. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE[VARS.language]));
  1719. fs.appendChild(s);
  1720. ta = document.createElement('textarea');
  1721. ta.name = 'GF_BLOCKED_TEXT';
  1722. ta.textContent = VARS.Filters.GF_BLOCKED_TEXT.join('\n');
  1723. fs.appendChild(ta);
  1724. cnt.appendChild(fs);
  1725.  
  1726. // -- Keywords to block - Watch/Videos Feed
  1727. fs = document.createElement('fieldset');
  1728. l = document.createElement('legend');
  1729. l.textContent = KeyWords.DLG_VF_BLOCK[VARS.language];
  1730. fs.appendChild(l);
  1731. fs.appendChild(createCB('cbVFBT', 'VF_BLOCKED_ENABLED'));
  1732. s = document.createElement('small');
  1733. s.appendChild(document.createTextNode(KeyWords.DLG_BLOCK_NEW_LINE[VARS.language]));
  1734. fs.appendChild(s);
  1735. ta = document.createElement('textarea');
  1736. ta.name = 'VF_BLOCKED_TEXT';
  1737. ta.textContent = VARS.Filters.VF_BLOCKED_TEXT.join('\n');
  1738. fs.appendChild(ta);
  1739. cnt.appendChild(fs);
  1740.  
  1741. // -- Verbosity
  1742. fs = document.createElement('fieldset');
  1743. l = document.createElement('legend');
  1744. l.textContent = KeyWords.DLG_VERBOSITY[VARS.language];
  1745. fs.appendChild(l);
  1746. s = document.createElement('span');
  1747. s.appendChild(document.createTextNode(`${KeyWords.DLG_VERBOSITY_MESSAGE[VARS.language]}:`));
  1748. fs.appendChild(s);
  1749. fs.appendChild(createRB('VERBOSITY_LEVEL', '0', `<${KeyWords.VERBOSITY_NO_MESSAGE[VARS.language]}>`));
  1750. fs.appendChild(createRB('VERBOSITY_LEVEL', '1', `${KeyWords.VERBOSITY[VARS.language][0]}______`));
  1751. fs.appendChild(createRB('VERBOSITY_LEVEL', '2', `7${KeyWords.VERBOSITY[VARS.language][1]}`));
  1752. fs.appendChild(document.createElement('br'));
  1753. fs.appendChild(createInput('VERBOSITY_COLOUR', `${KeyWords.VERBOSITY_COLOUR[VARS.language]}:`));
  1754. fs.appendChild(createInput('VERBOSITY_BG_COLOUR', `${KeyWords.VERBOSITY_BG_COLOUR[VARS.language]}:`));
  1755. fs.appendChild(document.createElement('br'));
  1756. fs.appendChild(createCB('cbVD', 'VERBOSITY_DEBUG'));
  1757. cnt.appendChild(fs);
  1758.  
  1759. // -- cmf customisations
  1760. fs = document.createElement('fieldset');
  1761. l = document.createElement('legend');
  1762. l.textContent = KeyWords.CMF_CUSTOMISATIONS[VARS.language];
  1763. fs.appendChild(l);
  1764. fs.appendChild(document.createTextNode(`${KeyWords.CMF_BTN_LOCATION[VARS.language]}:`));
  1765. fs.appendChild(createRB('CMF_BTN_LOCATION', '0', KeyWords.CMF_BTN_OPTION[VARS.language][0]));
  1766. fs.appendChild(createRB('CMF_BTN_LOCATION', '1', KeyWords.CMF_BTN_OPTION[VARS.language][1]));
  1767. fs.appendChild(document.createElement('br'));
  1768. fs.appendChild(document.createTextNode(`${KeyWords.CMF_DIALOG_LOCATION[VARS.language]}:`));
  1769. fs.appendChild(createRB('CMF_DIALOG_LOCATION', '0', KeyWords.CMF_DIALOG_OPTION[VARS.language][0]));
  1770. fs.appendChild(createRB('CMF_DIALOG_LOCATION', '1', KeyWords.CMF_DIALOG_OPTION[VARS.language][1]));
  1771. fs.appendChild(document.createElement('br'));
  1772. fs.appendChild(createInput('CMF_BORDER_COLOUR', `${KeyWords.CMF_BORDER_COLOUR[VARS.language]}:`));
  1773. cnt.appendChild(fs);
  1774.  
  1775. // -- tips
  1776. fs = document.createElement('fieldset');
  1777. l = document.createElement('legend');
  1778. l.textContent = KeyWords.DLG_TIPS[VARS.language];
  1779. fs.appendChild(l);
  1780. s = document.createElement('span');
  1781. s.appendChild(document.createTextNode(KeyWords.DLG_TIPS_CONTENT[VARS.language]));
  1782. fs.appendChild(s);
  1783. cnt.appendChild(fs);
  1784.  
  1785. dlg.appendChild(cnt);
  1786.  
  1787. // -- Actions (buttons)
  1788. footer = document.createElement('footer');
  1789. footer.classList.add('buttons');
  1790. btn = document.createElement('button');
  1791. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][0]; // save
  1792. btn.addEventListener("click", saveUserOptions, false);
  1793. footer.appendChild(btn);
  1794. btn = document.createElement('button');
  1795. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][1]; // close
  1796. btn.addEventListener("click", toggleMD, false);
  1797. footer.appendChild(btn);
  1798. btn = document.createElement('button');
  1799. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][2]; // export
  1800. btn.addEventListener("click", exportUserOptions, false);
  1801. footer.appendChild(btn);
  1802. btn = document.createElement('button');
  1803. btn.textContent = KeyWords.DLG_BUTTONS[VARS.language][3]; // import
  1804. btn.setAttribute('id', 'BTNImport');
  1805. footer.appendChild(btn);
  1806. // -- file input field is hidden, but triggered by the Import button.
  1807. let fileImport = document.createElement('input');
  1808. fileImport.setAttribute('type', 'file');
  1809. fileImport.setAttribute('id', `FI${postAtt}`);
  1810. fileImport.classList.add('fileInput');
  1811. footer.appendChild(fileImport);
  1812. // -- import results
  1813. let div = document.createElement('div');
  1814. div.classList.add('fileResults');
  1815. footer.appendChild(div);
  1816.  
  1817. dlg.appendChild(footer);
  1818.  
  1819. document.body.appendChild(dlg);
  1820.  
  1821. // -- add event listeners to the import button and file input field
  1822. let fileInput = document.getElementById(`FI${postAtt}`);
  1823. fileInput.addEventListener('change', importUserOptions, false);
  1824. // -- make the btn Import trigger file input ...
  1825. let btnImport = document.getElementById('BTNImport');
  1826. btnImport.addEventListener('click', function(){fileInput.click()}, false);
  1827. }
  1828. function updateDialog() {
  1829. let content = document.getElementById('fbcmf').querySelector('.content');
  1830. if (content) {
  1831. let cbs = Array.from(content.querySelectorAll('input[type="checkbox"]'));
  1832. cbs.forEach(cb => {
  1833. if (VARS.Options.hasOwnProperty(cb.value)) {
  1834. cb.checked = VARS.Options[cb.value];
  1835. }
  1836. });
  1837. // let rbs = content.querySelectorAll('input[type="radio"]:checked');
  1838. let rbs = content.querySelectorAll('input[type="radio"]');
  1839. rbs.forEach(rb => {
  1840. if (VARS.Options.hasOwnProperty(rb.name)) {
  1841. rb.checked = VARS.Options[rb.name];
  1842. }
  1843. });
  1844. let tas = Array.from(content.querySelectorAll('textarea'));
  1845. tas.forEach(ta => {
  1846. if (VARS.Options.hasOwnProperty(ta.name)) {
  1847. ta.value = VARS.Options[ta.name].replaceAll('¦¦', '\n');
  1848. }
  1849. });
  1850. let inputs = Array.from(content.querySelectorAll('input[type="text"]'));
  1851. inputs.forEach(inp => {
  1852. if (VARS.Options.hasOwnProperty[inp.name]) {
  1853. inp.value = VARS.Options[inp.name];
  1854. }
  1855. });
  1856. }
  1857. }
  1858.  
  1859. async function saveUserOptions(event, source='dialog') {
  1860. // -- save Options in indexeddb as JSON.
  1861. if (source === 'dialog') {
  1862. let md, cbs, rbs, tas, inputs;
  1863.  
  1864. // -- grab the dialog box and get the various options.
  1865. md = document.getElementById('fbcmf');
  1866. // -- checkboxes
  1867. cbs = Array.from(md.querySelectorAll('input[type="checkbox"]'));
  1868. cbs.forEach( cb => {
  1869. VARS.Options[cb.value] = cb.checked;
  1870. });
  1871. // -- radios
  1872. rbs = md.querySelectorAll('input[type="radio"]:checked');
  1873. rbs.forEach(rb => {
  1874. VARS.Options[rb.name] = rb.value;
  1875. });
  1876. // -- text input
  1877. inputs = Array.from(md.querySelectorAll('input[type="text"]'));
  1878. inputs.forEach(inp => {
  1879. VARS.Options[inp.name] = inp.value;
  1880. });
  1881. // -- Blocked text (textareas)
  1882. tas = md.querySelectorAll('textarea');
  1883. tas.forEach(ta => {
  1884. let txtn = ta.value.split('\n');
  1885. let txts = [];
  1886. txtn.forEach(txt => {
  1887. if (txt.trim().length > 0) {
  1888. txts.push(txt); // -- do not trim - retain entry as is.
  1889. }
  1890. });
  1891. VARS.Options[ta.name] = txts.join('¦¦');
  1892. });
  1893. }
  1894.  
  1895. // -- clear out items that are not valid.
  1896. let md = document.getElementById('fbcmf');
  1897. let inputs = Array.from(md.querySelectorAll('input:not([type="file"]), textarea'));
  1898. let validNames = [];
  1899. inputs.forEach(inp => {
  1900. validNames.push( (inp.type === 'checkbox') ? inp.value : inp.name);
  1901. });
  1902. for (let key in VARS.Options) {
  1903. if (validNames.indexOf(key) < 0) {
  1904. if (VARS.Options.VERBOSITY_DEBUG) {
  1905. console.info(`${log}SUO : deleting key:`, key);
  1906. }
  1907. delete VARS.Options[key];
  1908. }
  1909. }
  1910.  
  1911. // -- save options
  1912. let result = await set(DBVARS.DBKey, JSON.stringify(VARS.Options), DBVARS.ostore)
  1913. .then(() => {
  1914. // if (VARS.Options.VERBOSITY_DEBUG) {
  1915. // console.info(`${log}saveUserOptions() > set() -> Saved, Options:`, VARS.Options);
  1916. // }
  1917. // -- refresh options and split blocks of texts
  1918. let result2 = getUserOptions()
  1919. .then(() => {
  1920. return true;
  1921. });
  1922. return result2;
  1923. })
  1924. .catch((err) => {
  1925. console.info(`${log}saveUserOptions() > set() -> Error:`, err);
  1926. return false;
  1927. });
  1928. if (VARS.VERBOSITY_DEBUG) {
  1929. console.info(`${log}saveUserOptions() > set() -> Saved:`, result);
  1930. }
  1931. // - update some variables.
  1932. if (result) {
  1933. setFeedSettings(true);
  1934. addCSS();
  1935. addExtraCSS();
  1936. }
  1937. document.querySelector('#fbcmf .fileResults').innerText = `Last Saved @ ${(new Date).toTimeString().slice(0,8)}`;
  1938. }
  1939.  
  1940. function exportUserOptions() {
  1941. // -- export user's options into a text file.
  1942. // console.info(`${log}exportUserOptions() : Options:`,VARS.Options);
  1943. let exportOptions = document.createElement("a");
  1944. exportOptions.href = window.URL.createObjectURL(new Blob([JSON.stringify(VARS.Options)], {type: "text/plain"}));
  1945. exportOptions.download = 'fb - clean my feeds - settings.json';
  1946. exportOptions.click();
  1947. exportOptions.remove();
  1948. document.querySelector('#fbcmf .fileResults').innerText = 'Exported: fb - clean my feeds - settings.json';
  1949. }
  1950. function importUserOptions(event) {
  1951. // -- import user's options froma text file.
  1952. let fileResults = document.querySelector('#fbcmf .fileResults');
  1953. let file = event.target.files[0];
  1954. let fileN = event.target.files[0].name;
  1955. // -- setup reader for reading in the file
  1956. let reader = new FileReader();
  1957. // -- what to do when reader is called.
  1958. reader.onload = (file) => {
  1959. try {
  1960. let fileContent = JSON.parse(file.target.result);
  1961. if (fileContent.hasOwnProperty('NF_SPONSORED') &&
  1962. fileContent.hasOwnProperty('GF_SPONSORED') &&
  1963. fileContent.hasOwnProperty('VF_SPONSORED') &&
  1964. fileContent.hasOwnProperty('MP_SPONSORED')
  1965. ) {
  1966. VARS.Options = fileContent;
  1967. //console.info(`${log}importUserOptions > reader.onload: Options:`, VARS.Options);
  1968. // -- save the file to the db
  1969. // -- save will run getUserOptions();
  1970. let result = saveUserOptions(null, 'file')
  1971. .then(() => {
  1972. updateDialog();
  1973. fileResults.innerText = `File imported: ${fileN}`;
  1974. return true;
  1975. });
  1976. }
  1977. else {
  1978. fileResults.innerText = `File NOT imported: ${fileN}`;
  1979. }
  1980. }
  1981. catch (e) {
  1982. fileResults.innerText = `File NOT imported: ${fileN}`;
  1983. }
  1984. }
  1985. // -- call reader to read in the file ...
  1986. reader.readAsText(file);
  1987. }
  1988.  
  1989. function toggleMD() {
  1990. let dlg = document.getElementById('fbcmf');
  1991. dlg.classList.toggle('show');
  1992. }
  1993.  
  1994. function createToggleButton() {
  1995. let btn = document.createElement('button');
  1996. btn.innerHTML = VARS.logoHTML;
  1997. btn.id = 'fbcmfToggle';
  1998. btn.title = KeyWords.DLG_TITLE[VARS.language];
  1999. btn.className = 'fb-cmf-toggle fb-cmf-icon';
  2000. document.body.appendChild(btn);
  2001. btn.addEventListener("click", toggleMD, false);
  2002. VARS.btnToggleEl = btn;
  2003. }
  2004.  
  2005. createToggleButton();
  2006. createDialog();
  2007. }
  2008. // --- end of dailog code.
  2009.  
  2010.  
  2011.  
  2012. // adjust some settings if URL has changed.
  2013. function setFeedSettings(forceUpdate=false) {
  2014. if ((VARS.prevURL !== window.location.href) || forceUpdate) {
  2015. // - remember current page's URL
  2016. VARS.prevURL = window.location.href;
  2017. VARS.prevPathname = window.location.pathname;
  2018. // - reset feeds flags
  2019. VARS.isNF = false;
  2020. VARS.isGF = false;
  2021. VARS.isVF = false;
  2022. VARS.isMP = false;
  2023. VARS.isSF = false;
  2024. if (VARS.prevPathname === '/') {
  2025. VARS.isNF = true;
  2026. VARS.QS = VARS.newsFeedQS;
  2027. VARS.suggestions = VARS.nfSuggestions;
  2028. VARS.blockText = VARS.Filters.NF_BLOCKED_ENABLED;
  2029. VARS.blockTextMatch = VARS.Filters.NF_BLOCKED_TEXT;
  2030. VARS.blockTextMatchLC = VARS.Filters.NF_BLOCKED_TEXT_LC;
  2031. }
  2032. else if (['/groups/feed/', '/groups/feed'].indexOf(VARS.prevPathname) >= 0) {
  2033. VARS.isGF = true;
  2034. VARS.QS = VARS.groupsFeedQS;
  2035. VARS.suggestions = VARS.gfSuggestions;
  2036. VARS.blockText = VARS.Filters.GF_BLOCKED_ENABLED;
  2037. VARS.blockTextMatch = VARS.Filters.GF_BLOCKED_TEXT;
  2038. VARS.blockTextMatchLC = VARS.Filters.GF_BLOCKED_TEXT_LC;
  2039. }
  2040. else if (['/watch/', '/watch'].indexOf(VARS.prevPathname) >= 0) {
  2041. VARS.isVF = true;
  2042. if (VARS.prevURL.indexOf('?ref=search&') >= 0) {
  2043. // searched & watching a video, has more videos below it. has a slightly different layout structure.
  2044. VARS.QS = VARS.videosFeedQS2;
  2045. }
  2046. else {
  2047. // bog standard video page/layout.
  2048. VARS.QS = VARS.videosFeedQS;
  2049. }
  2050. VARS.suggestions = VARS.vfSuggestions;
  2051. VARS.blockText = VARS.Filters.VF_BLOCKED_ENABLED;
  2052. VARS.blockTextMatch = VARS.Filters.VF_BLOCKED_TEXT;
  2053. VARS.blockTextMatchLC = VARS.Filters.VF_BLOCKED_TEXT_LC;
  2054. }
  2055. else if (VARS.prevPathname.indexOf('/marketplace') >=0) {
  2056. VARS.isMP = true;
  2057. VARS.QS = VARS.marketplaceQS;
  2058. VARS.suggestions = [];
  2059. VARS.blockText = false;
  2060. VARS.blockTextMatch = [];
  2061. VARS.blockTextMatchLC = [];
  2062. VARS.mpType = '';
  2063. VARS.mpItem = false;
  2064. //let mpf = Array.from(document.querySelectorAll('div[data-pagelet="MainFeed"]')); // pre May 2022
  2065. let mpf = Array.from(document.querySelectorAll('div[role="main"]')); // May 2022+
  2066. if (VARS.prevPathname.indexOf('/category/') >=0 ) {
  2067. // - category feed (doesn't have the data-pagelet attribute)
  2068. VARS.mpType = 'category';
  2069. }
  2070. else if (mpf.length > 0) {
  2071. // - standard feed (main feed + locations)
  2072. VARS.mpType = 'std';
  2073. }
  2074. else {
  2075. // - possibly a category page.
  2076. mpf = Array.from(document.querySelectorAll('div[aria-label*="Marketplace"][role="main"]'));
  2077. if (mpf.length === 1) {
  2078. VARS.mpType = 'category'
  2079. }
  2080. else {
  2081. // - page is slow to be loaded, treat as std page
  2082. VARS.mpType = 'std';
  2083. }
  2084. }
  2085. if (VARS.isMP && VARS.prevPathname.indexOf('/item/') >=0) {
  2086. VARS.mpItem = true;
  2087. }
  2088. // console.info(`${log}setFeedSettings() : isMP, mpType, mpItem:`, VARS.isMP, VARS.mpType, VARS.mpItem);
  2089. }
  2090. else if (['/search/top/', '/search/top', '/search/posts/', '/search/posts'].indexOf(VARS.prevPathname) >=0) {
  2091. // -- search results page : "All" and "Posts"
  2092. VARS.isSF = true;
  2093. VARS.QS = VARS.searchTopQS;
  2094. VARS.suggestions = [];
  2095. VARS.blockText = false;
  2096. VARS.blockTextMatch = [];
  2097. VARS.blockTextMatchLC = [];
  2098. }
  2099. else {
  2100. VARS.QS = '';
  2101. VARS.suggestions = [];
  2102. VARS.blockText = false;
  2103. VARS.blockTextMatch = [];
  2104. VARS.blockTextMatchLC = [];
  2105. }
  2106. VARS.isAF = (VARS.isNF || VARS.isGF || VARS.isVF || VARS.isMP || VARS.isSF);
  2107.  
  2108. if (VARS.isAF) {
  2109. if (VARS.btnToggleEl) VARS.btnToggleEl.classList.add('show');
  2110. }
  2111. else {
  2112. if (VARS.btnToggleEl) VARS.btnToggleEl.classList.remove('show');
  2113. }
  2114.  
  2115. // - reset count of consecutive posts hidden
  2116. VARS.echoCount = 0;
  2117. // - reset non-feed-posts count
  2118. VARS.nfpLoopCount = 0;
  2119. // - reset stories found flag
  2120. VARS.storiesFound = (VARS.Options.NF_STORIES === false);
  2121. // - reset create-room found flag
  2122. VARS.crFound = (VARS.Options.NF_CREATE_ROOM === false);
  2123. // - reset third-column found flags
  2124. // (set to true to stop mopping up the tc)
  2125. VARS.tcFound_Sponsored = (VARS.Options.NF_THIRD_COLUMN_SPONSORED === false);
  2126. VARS.tcFound_Suggested4U = (VARS.Options.NF_THIRD_COLUMN_SUGGESTED_FOR_YOU === false);
  2127.  
  2128. // - reset f2m and survey found flags
  2129. VARS.f2mFound = (VARS.Options.OTHER_FB_RENAMED === false);
  2130. VARS.surveyFound = (VARS.Options.OTHER_SURVEY === false);
  2131. // console.info(`${log}SF:`, VARS.surveyFound);
  2132. VARS.otherLoopCount = 0;
  2133.  
  2134. // console.info(`${log}setFeedSettings() : VARS:`, VARS.isAF, VARS.isNF, VARS.isGF, VARS.isVF, VARS.isMP, VARS.isSF);
  2135. return true;
  2136. }
  2137. else {
  2138. return false;
  2139. }
  2140. }
  2141. function scanTreeForText(theNode) {
  2142. let arrayTextValues = [];
  2143. let n,
  2144. walk = document.createTreeWalker(theNode, NodeFilter.SHOW_TEXT, null, false);
  2145. while ((n = walk.nextNode())) {
  2146. let val = n.textContent.trim();
  2147. if ((val !== '') && (val.length > 1)) {
  2148. // - keep 2+ char strings.
  2149. arrayTextValues.push(val);
  2150. }
  2151. }
  2152. return arrayTextValues;
  2153. }
  2154.  
  2155. function extractTextContent(post, selector, maxBlocks ) {
  2156. // - get the text node values of the regular feed posts
  2157. // -- scan the top portion of the posts (first maxBlocks blocks)
  2158. // -- parameters:
  2159. // post: post to scan
  2160. // selector: querySelector's query
  2161. // maxBlocks: max number of blocks to scan
  2162. let blocks = Array.from(post.querySelectorAll(selector));
  2163. let arrayTextValues = [];
  2164. if (blocks.length) {
  2165. // - process first maxBlocks blocks
  2166. // - block 0 = Suggested headings, block 1 = title/heading, block 2 = content, block 3 = info box / comments, block 4 = comments
  2167. // - nb: some suggested posts only have one block ...
  2168. let bL = Math.min(maxBlocks, blocks.length);
  2169. for (let b = 0; b < bL; b++) {
  2170. if (blocks[b].innerHTML.length > 0) {
  2171. arrayTextValues = arrayTextValues.concat(scanTreeForText(blocks[b]));
  2172. }
  2173. }
  2174. }
  2175. return arrayTextValues;
  2176. }
  2177. function extractTextContentVF(post, selector, whichBlock) {
  2178. // - get the text node values of the regular feed posts
  2179. // -- scan a certain block in the posts
  2180. // -- parameters:
  2181. // post: post to scan
  2182. // selector: querySelector's query
  2183. // whichBlock: the block to scan for text (0 = first block ...)
  2184. let blocks = Array.from(post.querySelectorAll(selector));
  2185. let arrayTextValues = [];
  2186. if ((blocks.length-1) >= whichBlock) {
  2187. // - block 0 = Suggested headings, block 1 = title/heading, block 2 = content, block 3 = info box / comments, block 4 = comments
  2188. // - nb: some suggested posts only have one block ...
  2189. let blockToScan = blocks[whichBlock];
  2190. if (blockToScan.innerHTML.length > 0) {
  2191. arrayTextValues = arrayTextValues.concat(scanTreeForText(blockToScan));
  2192. }
  2193. }
  2194. return arrayTextValues;
  2195. }
  2196.  
  2197. function echoHiddenPost(post, reason) {
  2198. if (VARS.isMP) {
  2199. // -- marketplace don't display a msg.
  2200. return true;
  2201. }
  2202. if ((parseInt(VARS.Options.VERBOSITY_LEVEL, 10) > 0) && (reason !== '')) {
  2203. if (VARS.Options.VERBOSITY_LEVEL === '1') {
  2204. VARS.echoCount = 1;
  2205. }
  2206. if (VARS.echoCount < 2) {
  2207. // - 1 post hidden
  2208. let echoEl = document.createElement('p');
  2209. echoEl.textContent = KeyWords.VERBOSITY[VARS.language][0] + reason;
  2210. // - add after post being hidden (issue with first post being hidden & fb updating it)
  2211. post = post.querySelector(':scope div:first-of-type');
  2212. if (post){
  2213. post.after(echoEl);
  2214. VARS.echoEl = echoEl;
  2215. return true;
  2216. }
  2217. else {
  2218. // post has been changed while being processed (very rare)
  2219. return false;
  2220. }
  2221. }
  2222. else {
  2223. // - 2+ posts hidden
  2224. VARS.echoEl.textContent = VARS.echoCount + KeyWords.VERBOSITY[VARS.language][1];
  2225. return true;
  2226. }
  2227. }
  2228. return true;
  2229. }
  2230. function hide(post, reason) {
  2231. // hide something ..
  2232. // - also call up echo 'post is hidden' text functions
  2233. if (echoHiddenPost(post, reason)) {
  2234. post.classList.add( (VARS.isMP) ? VARS.cssHideEl : VARS.cssHide);
  2235. // - enable the following if wanting to inspect each post's reason for being hidden (in developer's tools)
  2236. post.setAttribute(`${postAtt}-rule`, reason);
  2237. }
  2238. }
  2239. function checkText(text) {
  2240. // - fb is using ASCII code 160 for whitespace ...
  2241. return text.replaceAll(String.fromCharCode(160), String.fromCharCode(32));
  2242. }
  2243.  
  2244. function isSponsored(post) {
  2245. // Is it a sponsored post?
  2246. // -- find the block of code that usually holds the post's timestamp / sponsored text.
  2247. // -- nb: fb uses SPAN or B ...
  2248.  
  2249. let daText = '';
  2250.  
  2251. // -- try the SPAN structure (w Flex)
  2252. let elWrapper = post.querySelector('span > span > span > a[href="#"] > span > span[class] > span[style], span > span > span > a[href*="/ads/"] > span > span[class] > span[style]');
  2253. if (elWrapper) {
  2254. // -- found a regular post structure
  2255. let arrText = [];
  2256. let cs = window.getComputedStyle(elWrapper);
  2257. // wrapper's order - set to 0 if has a value (css will ignore other values)
  2258. let wrapperOrder = (cs.order !== "") ? 0 : -1;
  2259. elWrapper.childNodes.forEach((cn) => {
  2260. if (cn.nodeType === Node.ELEMENT_NODE) {
  2261. let cs = window.getComputedStyle(cn);
  2262. if ((cs.position === 'relative') && (cs.display != 'none')) {
  2263. arrText[parseInt(cs.order, 10)] = cn.textContent;
  2264. }
  2265. }
  2266. else if ((cn.nodeType === Node.TEXT_NODE) && (wrapperOrder >= 0)) {
  2267. let nv = cn.nodeValue.replaceAll(String.fromCharCode(10), '');
  2268. if (nv.length > 0) {
  2269. arrText[wrapperOrder] = nv;
  2270. }
  2271. }
  2272. });
  2273. daText = checkText(arrText.join('')).trim();
  2274. }
  2275. else {
  2276. // -- try the B structure (no Flex)
  2277. elWrapper = post.querySelector('span > span > span > a[href="#"] > span > span[class] > b[class], span > span > span > a[href*="/ads/"] > span > span[class] > b[class]');
  2278. if (elWrapper) {
  2279. // -- found a regular post structure (Portugese, Italian)
  2280. daText = '';
  2281. elWrapper.childNodes.forEach((cn) => {
  2282. if (cn.nodeType === Node.ELEMENT_NODE) {
  2283. let cs = window.getComputedStyle(cn);
  2284. if ((cs.position === 'relative') && (cs.display != 'none')) {
  2285. daText += cn.textContent;
  2286. }
  2287. }
  2288. else if (cn.nodeType === Node.TEXT_NODE) {
  2289. let nv = cn.nodeValue.replaceAll(String.fromCharCode(10), '');
  2290. if (nv.length > 0) {
  2291. daText += nv;
  2292. }
  2293. }
  2294. });
  2295. daText = checkText(daText).trim();
  2296. }
  2297. }
  2298. // console.info(`${log}is Sponsored post:`, `>${daText}<`, elWrapper);
  2299. return ((daText.length > 0) && (VARS.sponsoredWord === daText));
  2300.  
  2301. }
  2302. function isSuggested(post, isRegularPost) {
  2303. // - check for suggestions
  2304. // -- regular posts - scan first 2 blocks, otherwise first block.
  2305. let ptexts = (isRegularPost) ? extractTextContent(post, VARS.postBlocksQS, 2) : extractTextContent(post, VARS.nonRegularPostBlocksQS, 1);
  2306. let suggestionIndex = -1;
  2307. for (let p = 0, ptL = ptexts.length; p < ptL; p++) {
  2308. suggestionIndex = VARS.suggestions.indexOf(ptexts[p]);
  2309. // console.info(log + 'isSuggested:', suggestionIndex, p, ptexts, VARS.suggestions, post);
  2310. if (suggestionIndex >= 0) {
  2311. break;
  2312. }
  2313. }
  2314. return suggestionIndex;
  2315. }
  2316. function isBlockedText(post) {
  2317. // - check for blocked text - partial text match
  2318. // -- regular posts - scan first 1st & 3rd blocks
  2319. let ptexts = (VARS.isVF) ? extractTextContent(post, VARS.videoBlockQS, 1) : extractTextContent(post, VARS.postBlocksQS, 3);
  2320. ptexts = ptexts.join(' ').toLowerCase();
  2321. let blockedIndex = -1;
  2322. for (let b = 0, btL = VARS.blockTextMatchLC.length; b < btL; b++) {
  2323. blockedIndex = ptexts.indexOf(VARS.blockTextMatchLC[b]);
  2324. if (blockedIndex >= 0) {
  2325. // before breaking out, set the index position of the blocked text that matched.
  2326. blockedIndex = b;
  2327. break;
  2328. }
  2329. }
  2330. return blockedIndex;
  2331. }
  2332. function isSponsoredPaidFor(post) {
  2333. // - check for 'Sponsored · Paid for by ______'
  2334. if (VARS.Options.NF_SPONSORED_PAID === true) {
  2335. // look for certain elements
  2336. let els = Array.from(post.querySelectorAll(VARS.sponsoredPaidForQS));
  2337. // scan the first few elements for the keyword ...
  2338. if (els.length > 0) {
  2339. let eL = Math.min(5, els.length);
  2340. for (let i = 0; i < eL; i++ ) {
  2341. let etxt = els[i].textContent;
  2342. if (etxt.indexOf(VARS.sponsoredPaidForWords) >= 0) {
  2343. return true;
  2344. }
  2345. }
  2346. }
  2347. }
  2348. }
  2349. function isVideoLive(post) {
  2350. // - check for "LIVE" indicator on videos
  2351. if (VARS.Options.VF_LIVE === true) {
  2352. let ptexts = extractTextContentVF(post, VARS.videoBlockQS, 1);
  2353. if (ptexts.length >0) {
  2354. return (ptexts[0].toUpperCase() === KeyWords.VF_LIVE[VARS.language].toUpperCase());
  2355. }
  2356. else
  2357. {
  2358. return false
  2359. }
  2360. }
  2361. return false;
  2362. }
  2363.  
  2364. function doMoppingStories() {
  2365. if (VARS.Options.NF_STORIES) {
  2366. // let stories = Array.from(document.querySelectorAll(VARS.storiesQS1));
  2367. // if (stories.length > 0) {
  2368. // for (let i = 0; i < stories.length; i++) {
  2369. // let sbox = stories[i].nextElementSibling;
  2370. // if (sbox.nodeName === 'DIV') {
  2371. // if (!sbox.hasAttribute(postAtt)) {
  2372. // let astories = sbox.querySelector('a[href^="/stories/"]');
  2373. // if (astories) {
  2374. // sbox.setAttribute(postAtt, sbox.innerHTML.length);
  2375. // VARS.storiesFound = true;
  2376. // hide(sbox, '');
  2377. // sbox.setAttribute(`${postAtt}-rule`, KeyWords.NF_STORIES[VARS.language]);
  2378. // }
  2379. // }
  2380. // }
  2381. // }
  2382. // }
  2383. let stories = Array.from(document.querySelectorAll(VARS.storiesQS2));
  2384. if (stories.length > 0) {
  2385. for (let i = 0; i < stories.length; i++) {
  2386. let sbox = stories[i].parentElement;
  2387. if (!sbox.hasAttribute(postAtt)) {
  2388. let slink = sbox.querySelector('a[href^="/stories/"]');
  2389. if (slink) {
  2390. sbox.setAttribute(postAtt, sbox.innerHTML.length);
  2391. VARS.storiesFound = true;
  2392. hide(sbox, '');
  2393. sbox.setAttribute(`${postAtt}-rule`, KeyWords.NF_STORIES[VARS.language]);
  2394. }
  2395. }
  2396. }
  2397. }
  2398. }
  2399. }
  2400. function doMoppingCreateRoom() {
  2401. if (VARS.Options.NF_CREATE_ROOM) {
  2402. let createRoom = Array.from(document.querySelectorAll(VARS.createRoomQS1));
  2403. if (createRoom.length > 0) {
  2404. // pre May 2022
  2405. for (let i = 0; i < createRoom.length; i++) {
  2406. createRoom[i].setAttribute(postAtt, createRoom[i].innerHTML.length);
  2407. // - get the room's wrapper and hide the room at that level.
  2408. createRoom[i] = createRoom[i].parentElement.parentElement;
  2409. // - stop checking for create room element
  2410. VARS.crFound = true;
  2411. hide(createRoom[i], '');
  2412. createRoom[i].setAttribute(`${postAtt}-rule`, KeyWords.NF_CREATE_ROOM[VARS.language]);
  2413. break;
  2414. }
  2415. }
  2416. else {
  2417. // May 2022 ->
  2418. createRoom = Array.from(document.querySelectorAll(VARS.createRoomQS2));
  2419. if (createRoom.length > 0) {
  2420. for (let i = 0; i < createRoom.length; i++) {
  2421. let createRoomWrapper = createRoom[i].parentElement.closest('div[data-visualcompletion').parentElement.parentElement.parentElement.parentElement;
  2422. createRoomWrapper.setAttribute(postAtt, createRoomWrapper.innerHTML.length);
  2423. // - stop checking for create room element
  2424. VARS.crFound = true;
  2425. hide(createRoomWrapper, '');
  2426. createRoomWrapper.setAttribute(`${postAtt}-rule`, KeyWords.NF_CREATE_ROOM[VARS.language]);
  2427. break;
  2428. }
  2429. }
  2430. }
  2431. }
  2432. }
  2433. let tcCountFound = 0;
  2434. function doMoppingThirdColumn(tcEntry, tcbox) {
  2435. // - third column, sponsored box.
  2436. if (tcEntry === 1) {
  2437. if (tcbox) {
  2438. if (!tcbox.classList.contains(VARS.cssHide)) {
  2439. let ptexts = scanTreeForText(tcbox);
  2440. // console.info(`${log}tcbox tc:`, ptexts);
  2441. if (ptexts.indexOf(VARS.sponsoredWord) >= 0) {
  2442. VARS.echoCount = 0;
  2443. hide(tcbox, VARS.sponsoredWord);
  2444. // make it stop checking third-col.
  2445. tcCountFound++;
  2446. if (tcCountFound > 3) {
  2447. VARS.tcFound_Sponsored = true;
  2448. }
  2449. }
  2450. }
  2451. }
  2452. }
  2453. // - third column, groups suggested for you (news feed)
  2454. else if (tcEntry === 2) {
  2455. if (tcbox) {
  2456. if (!tcbox.classList.contains(VARS.cssHide)) {
  2457. let ptexts = scanTreeForText(tcbox);
  2458. // console.info(`${log}tcbox scanTreeForText():`, KeyWords.NF_SUGGESTED_FOR_YOU[VARS.language], ptexts);
  2459. let pidx = ptexts.indexOf(KeyWords.NF_SUGGESTED_FOR_YOU[VARS.language]) ;
  2460. if (pidx === 0 || pidx === 1) {
  2461. VARS.echoCount = 0;
  2462. hide(tcbox, KeyWords.NF_SUGGESTED_FOR_YOU[VARS.language]);
  2463. // make it stop checking third-col.
  2464. tcCountFound++;
  2465. if (tcCountFound > 3) {
  2466. VARS.tcFound_Suggested4U = true;
  2467. }
  2468. }
  2469. }
  2470. }
  2471. }
  2472. }
  2473. function doMoppingInfoBoxes(post) {
  2474. // hide the info boxes that appear in posts having a certain topic.
  2475. if((VARS.infoBoxes) && (VARS.infoBoxesPaths.length > 0)){
  2476. let blocks; // - post's major blocks (sections)
  2477. let minNumBlocks; // - minimum number of blocks in this post that has an info box
  2478. let infoBlock; // - which block has the info box
  2479. if (VARS.isNF || VARS.isGF) {
  2480. // - block 0 = friend posted then commented | shop added | suggested
  2481. // - block 1 = title/heading, date/time | group name, author, date/time
  2482. // - block 2 = content
  2483. // - block 3 = info box OR comments
  2484. // - block 4 = comments (if no info box)
  2485. blocks = post.querySelectorAll(`${VARS.postBlocksQS}:not([msz])`);
  2486. minNumBlocks = 5;
  2487. infoBlock = 3;
  2488. }
  2489. else if (VARS.isVF) {
  2490. // - block 0 = title/heading,
  2491. // - block 1 = video
  2492. // - block 2 = info box OR comments
  2493. // - block 3 = comments (if no info box)
  2494. blocks = post.querySelectorAll(`${VARS.videoBlockQS}:not([msz])`);
  2495. minNumBlocks = 4;
  2496. infoBlock = 2;
  2497. }
  2498. else {
  2499. return;
  2500. }
  2501. if (blocks.length >= minNumBlocks) {
  2502. let block = blocks[infoBlock];
  2503. if (!block.hasAttribute(postAtt)) {
  2504. for (let j = 0, jL = VARS.infoBoxesPaths.length; j < jL; j++) {
  2505. let links = Array.from(block.querySelectorAll(`a[href*="${VARS.infoBoxesPaths[j]}"]`));
  2506. //console.info(log+"checking:", VARS.infoBoxesPaths[j], links);
  2507. if (links.length > 0) {
  2508. block.setAttribute(postAtt, block.innerHTML.length);
  2509. block.setAttribute(`${postAtt}-IB`, VARS.infoBoxesPaths[j]);
  2510. // - hide with no echo msg.
  2511. hide(block, '');
  2512. break;
  2513. }
  2514. }
  2515. }
  2516. }
  2517. }
  2518. }
  2519.  
  2520. function doMoppingOthers() {
  2521. // hide fb is meta and survey boxes
  2522. let mainFeed = document.querySelector('div[role="feed"]');
  2523. if (mainFeed) {
  2524. let parentEl = mainFeed.parentElement.parentElement;
  2525. if (parentEl.tagName !== 'BODY') {
  2526. // - [role="feed"] must exists
  2527. // -- if parentElement is BODY, skip this round ...
  2528. // -- not all elements have been created - 'div[role="feed"]' one of the first few ...
  2529. if (VARS.f2mFound === false) {
  2530. let linkEl = parentEl.querySelector(`a[href*="facebook.com/meta/"]:not([${postAtt}])`);
  2531. if (linkEl) {
  2532. // -- grab the container (7 parent nodes up)
  2533. let boxEl = linkEl.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement;
  2534. linkEl.setAttribute(postAtt, linkEl.innerHTML.length);
  2535. boxEl.setAttribute(postAtt, boxEl.innerHTML.length);
  2536. hide(boxEl, KeyWords.OTHER_FB_RENAMED[VARS.language]); // - fb removes the hidden message, so skip that bit.
  2537. }
  2538. VARS.f2mFound = true;
  2539. }
  2540. //console.info(`${log}vSF:`, VARS.surveyFound, VARS.surveyFound === false, VARS.otherLoopCount);
  2541. if (VARS.surveyFound === false) {
  2542. let linkEl = parentEl.querySelector(`a[href*="/survey/"]:not([${postAtt}])`);
  2543. if (linkEl) {
  2544. // -- grab the container (7 parent nodes up)
  2545. let boxEl = linkEl.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement;
  2546. linkEl.setAttribute(postAtt, linkEl.innerHTML.length);
  2547. boxEl.setAttribute(postAtt, boxEl.innerHTML.length);
  2548. hide(boxEl, KeyWords.OTHER_SURVEY[VARS.language]); // - fb removes the hidden message, so skip that bit.
  2549. }
  2550. //VARS.surveyFound = true; - disabled 25/03/2022 - fb recreated after being hidden.
  2551. }
  2552. VARS.otherLoopCount++;
  2553. if (VARS.otherLoopCount >= VARS.otherLoopCountLimit) {
  2554. VARS.surveyFound = true;
  2555. VARS.f2mFound = true;
  2556. }
  2557. }
  2558. }
  2559. }
  2560.  
  2561. function doMopping() {
  2562. // News/Groups/Videos/Search Feed
  2563. let posts = Array.from(document.querySelectorAll(VARS.QS));
  2564. if (posts.length) {
  2565. // - consecutive hidden posts count
  2566. VARS.echoCount = 0;
  2567. // - skip the first lot of posts already processed
  2568. let quickScanCount = 0;
  2569. if (posts.length - VARS.inspectPostCount > 0) {
  2570. quickScanCount = posts.length - VARS.inspectPostCount;
  2571. for (let i = 0; i < quickScanCount; i++) {
  2572. if(posts[i].classList.contains(VARS.cssHide)) {
  2573. VARS.echoCount++;
  2574. }
  2575. else {
  2576. VARS.echoCount = 0;
  2577. }
  2578. }
  2579. }
  2580. // - check the posts
  2581. for (let i = quickScanCount, iL = posts.length; i < iL; i++) {
  2582. let post = posts[i];
  2583. if (post.textContent.length > 0 ) {
  2584. let hiding = false;
  2585. if (post.classList.contains(VARS.cssHide)) {
  2586. hiding = true;
  2587. VARS.echoCount++;
  2588. }
  2589. else if ((post.hasAttribute(postAtt) && (parseInt(post.getAttribute(postAtt), 10) === post.innerHTML.length))) {
  2590. // post size has not changed
  2591. // (if already hidden, previous rule would have caught it)
  2592. hiding = false;
  2593. }
  2594. else {
  2595. // - post is new or updated
  2596.  
  2597. // - record size of post
  2598. post.setAttribute(postAtt, post.innerHTML.length);
  2599.  
  2600. // - check for suggestions, blocked text, info boxes.
  2601. if (VARS.isNF || VARS.isGF || VARS.isVF) {
  2602. let suggestionIndex = isSuggested(post, true);
  2603. if (suggestionIndex >= 0) {
  2604. VARS.echoCount++;
  2605. hiding = true;
  2606. hide(post, VARS.suggestions[suggestionIndex]);
  2607. break;
  2608. }
  2609. else if (isSponsored(post)) {
  2610. VARS.echoCount++;
  2611. hiding = true;
  2612. hide(post, VARS.sponsoredWord);
  2613. break;
  2614. }
  2615. else if (VARS.isNF && isSponsoredPaidFor(post)) {
  2616. // - (news feed only)
  2617. VARS.echoCount++;
  2618. hiding = true;
  2619. hide(post, VARS.sponsoredPaidForWords);
  2620. break;
  2621. }
  2622. else if (VARS.isVF && isVideoLive(post)) {
  2623. // - (video feed only) - is !!!LIVE!!!
  2624. VARS.echoCount++;
  2625. hiding = true;
  2626. hide(post, KeyWords.VF_LIVE[VARS.language]);
  2627. break;
  2628. }
  2629. if (!hiding && VARS.blockText) {
  2630. // - try partial text matches
  2631. let blockedIndex = isBlockedText(post);
  2632. if (blockedIndex >= 0) {
  2633. VARS.echoCount++;
  2634. hiding = true;
  2635. hide(post, VARS.blockTextMatch[blockedIndex]);
  2636. break;
  2637. }
  2638. }
  2639. if (!hiding) {
  2640. // -- info boxes that appear between post article and comments.
  2641. doMoppingInfoBoxes(post);
  2642. }
  2643. }
  2644. else if (VARS.isSF) {
  2645. if (isSponsored(post)) {
  2646. VARS.echoCount++;
  2647. hiding = true;
  2648. hide(post, VARS.sponsoredWord);
  2649. break;
  2650. }
  2651. }
  2652. }
  2653. // - a clean post ..
  2654. if (!hiding) {
  2655. VARS.echoCount = 0;
  2656. }
  2657. }
  2658. }
  2659. }
  2660. }
  2661. function doMoppingNonFeedPosts(nfQS) {
  2662. // check Groups' and Videos' non-feed post(s)
  2663. // - these are the "intro" posts that appear above the feed's title.
  2664. // -- this function is called repeatedly a few times - up to VARS.inspectPostCount.
  2665. // (due to some posts being latecomers)
  2666. let posts = Array.from(document.querySelectorAll(nfQS));
  2667. if (posts.length > 0) {
  2668. for (let i = 0, iL = posts.length; i < iL; i++) {
  2669. let post = posts[i];
  2670. if ((post.innerHTML.length < 129) || (post.textContent.length < 1)) {
  2671. // skip (flag them to be ignored)
  2672. if (!post.hasAttribute(postAtt)) {
  2673. post.setAttribute(postAtt, post.innerHTML.length);
  2674. }
  2675. }
  2676. else {
  2677. let suggIdx = isSuggested(post, false);
  2678. if (suggIdx >= 0) {
  2679. VARS.echoCount = 1;
  2680. hide(post, VARS.suggestions[suggIdx]);
  2681. post.setAttribute(postAtt, post.innerHTML.length);
  2682. }
  2683. }
  2684. }
  2685. }
  2686. VARS.nfpLoopCount++;
  2687. }
  2688. function doMoppingMP() {
  2689. // MarketPlace Feeds
  2690. if (VARS.mpType === 'std') {
  2691. // -- MainFeed:
  2692. // --- get collection of blocks (which haven't been read/processed)
  2693. let mpblocks = Array.from(document.querySelectorAll(VARS.marketplaceQS1));
  2694. if (mpblocks.length > 0){
  2695. // - pre May 2022 structure
  2696. for (let i = 0, iL = mpblocks.length; i < iL; i++) {
  2697. let mpblock = mpblocks[i];
  2698. // console.info(`${log}mpblock:`, mpblock);
  2699. // - does this block of boxes have the a sponsored one?
  2700. // -- use the href*=/ads/ detection method - not search for the sponsored word
  2701. let splinks = Array.from(mpblock.querySelectorAll(`a[href*="/ads/"]:not([${postAtt}])`));
  2702. if (splinks.length > 0) {
  2703. // -- hide the heading (first of splinks)
  2704. let mpBox = splinks[0].parentElement;
  2705. hide(mpBox, VARS.sponsoredWordMP);
  2706. // -- hide the content (second of splinks)
  2707. mpBox = splinks[1].parentElement.closest('a').parentElement.parentElement.parentElement;
  2708. hide(mpBox, VARS.sponsoredWordMP);
  2709. splinks[0].setAttribute(postAtt, splinks[0].innerHTML.length);
  2710. splinks[1].setAttribute(postAtt, splinks[1].innerHTML.length);
  2711. }
  2712. mpblock.setAttribute(postAtt, mpblock.innerHTML.length);
  2713. }
  2714. }
  2715. else {
  2716. // - May 2022 structure
  2717. let spLinks = Array.from(document.querySelectorAll(VARS.marketplaceQS2));
  2718. if (spLinks.length > 0 ) {
  2719. for (let i = 0, iL = spLinks.length; i < iL; i++) {
  2720. let link = spLinks[i];
  2721. let pbox = link.parentElement;
  2722. if (pbox.nodeName === "OBJECT") {
  2723. // - content
  2724. pbox = pbox.closest('a').parentElement.parentElement.parentElement;
  2725. }
  2726. else {
  2727. // - heading (do nothing)
  2728. }
  2729. if (pbox.innerHTML.length > 0) {
  2730. link.setAttribute(postAtt, link.innerHTML.length);
  2731. pbox.setAttribute(postAtt, pbox.innerHTML.length);
  2732. hide(pbox, VARS.sponsoredWordMP);
  2733. }
  2734. }
  2735. }
  2736. }
  2737.  
  2738. if (VARS.mpItem) {
  2739. doMoppingMPItem();
  2740. }
  2741. }
  2742. else if (VARS.mpType === 'category') {
  2743. // -- Viewing a MP category
  2744. let splinks = Array.from(document.querySelectorAll(`a[href*="/ads/"]:not([${postAtt}])`));
  2745. if (splinks.length > 0) {
  2746. for (let i = 0, iL = splinks.length; i < iL; i++) {
  2747. let splink = splinks[i];
  2748. let spbox = splink.parentElement.closest('span div a');
  2749. if (spbox !== null) {
  2750. // -- found the sponsored box.
  2751. spbox = spbox.parentElement.parentElement.parentElement;
  2752. spbox.setAttribute(postAtt, spbox.innerHTML.length);
  2753. splink.setAttribute(postAtt, splink.innerHTML.length);
  2754. hide(spbox, VARS.sponsoredWordMP);
  2755. // (no break out - several sponsored boxes found)
  2756. }
  2757. }
  2758. }
  2759. if (VARS.mpItem) {
  2760. doMoppingMPItem();
  2761. }
  2762. }
  2763. }
  2764. function doMoppingMPItem() {
  2765. // -- viewing a MP Item and a small sponsored box is showing up on the right.
  2766. let splinks = Array.from(document.querySelectorAll(`a[href*="/ads/"]:not([${postAtt}])`));
  2767. // console.info(`${log}MPItem() - splinks:`, splinks);
  2768. if (splinks.length > 0){
  2769. for (let i = 0, iL = splinks.length; i < iL; i++) {
  2770. let splink = splinks[i];
  2771. if (splink.closest('div[data-pagelet^="BrowseFeedUpsell"]') === null) {
  2772. // -- found the sponsored box inside the mp item box.
  2773. // -- mp item do not have a parent element having data-pagelet attribute.
  2774. let spbox = splink.parentElement.closest('h2');
  2775. if (spbox) {
  2776. spbox = spbox.closest('span');
  2777. hide(spbox, VARS.sponsoredWordMP);
  2778. splink.setAttribute(postAtt, splink.innerHTML.length);
  2779. // (there's only one sponsored box - so break out)
  2780. break;
  2781. }
  2782. }
  2783. }
  2784. }
  2785. }
  2786.  
  2787. // ** Mutations processor
  2788. function bodyMutating(mutations) {
  2789. for (let mutation of mutations) {
  2790. if (mutation.type === 'childList') {
  2791. if (VARS.prevURL !== window.location.href) {
  2792. // - page url has changed ... refresh the bodyObserver.
  2793. runMO();
  2794. // console.info(`${log}runMO(): A/N/G/V/M:`, VARS.isAF, VARS.isNF, VARS.isGF, VARS.isVF, VARS.isMP);
  2795. }
  2796. else if (VARS.isAF) {
  2797. for (let i = 0; i < mutation.addedNodes.length; i++) {
  2798. let mnode = mutation.addedNodes[i];
  2799. // -- There's a MarketPlace SPAN node that has Sponsored text ...
  2800. // -- NF, GF & VF don't need to check SPAN nodes ... so exclude those NODES for performance reasons.
  2801. let safeNode = (['SCRIPT', 'LINK', undefined, 'FORM'].indexOf(mnode.tagName) < 0) ;
  2802. let doCleaning = safeNode ? ((VARS.isMP) ? true : (mnode.tagName === 'DIV')) : false ;
  2803. if (doCleaning) {
  2804. // console.info(`${log}m.an:`, VARS.isMP, mnode.innerHTML.length, mnode.textContent.length, mnode);
  2805. if ((mnode.innerHTML.length < 129) || (mnode.textContent.length === 0)) {
  2806. // - skip these ...
  2807. // console.info(`${log}m.an: - skipping`, mnode);
  2808. }
  2809. else if (VARS.isNF) {
  2810. if (VARS.storiesFound === false) {
  2811. doMoppingStories();
  2812. }
  2813. if (VARS.crFound === false) {
  2814. doMoppingCreateRoom();
  2815. }
  2816. if ((VARS.tcFound_Sponsored === false) || (VARS.tcFound_Suggested4U === false)) {
  2817. let tcbox = document.querySelector(VARS.thirdColQS1);
  2818. if (tcbox && tcbox.innerHTML.length > 64) {
  2819. doMoppingThirdColumn(1, tcbox);
  2820. }
  2821. tcbox = document.querySelector(VARS.thirdColQS2);
  2822. if (tcbox && tcbox.innerHTML.length > 64) {
  2823. doMoppingThirdColumn(2, tcbox);
  2824. }
  2825. }
  2826. if ((VARS.f2mFound === false) || (VARS.surveyFound === false)){
  2827. doMoppingOthers();
  2828. }
  2829. if (VARS.storiesFound && VARS.crFound && (VARS.tcFound_Sponsored || VARS.tcFound_Suggested4U) && VARS.f2mFound && VARS.surveyFound) {
  2830. VARS.nfpLoopCount = VARS.nfpLoopCountLimit + 1;
  2831. }
  2832. else {
  2833. VARS.nfpLoopCount++;
  2834. }
  2835.  
  2836. doMopping();
  2837. break;
  2838. }
  2839. else if (VARS.isGF) {
  2840. if (VARS.nfpLoopCount < VARS.nfpLoopCountLimit) {
  2841. doMoppingNonFeedPosts(VARS.groupsNonFeedsQS);
  2842. }
  2843. doMopping();
  2844. break;
  2845. }
  2846. else if (VARS.isVF) {
  2847. if (VARS.nfpLoopCount < VARS.nfpLoopCountLimit) {
  2848. doMoppingNonFeedPosts(VARS.videoNonFeedQS);
  2849. }
  2850. doMopping();
  2851. break;
  2852. }
  2853. else if (VARS.isMP) {
  2854. doMoppingMP();
  2855. break;
  2856. }
  2857. else if (VARS.isSF) {
  2858. doMopping();
  2859. break;
  2860. }
  2861. }
  2862. }
  2863. }
  2864. }
  2865. }
  2866. }
  2867.  
  2868. // ** Mutation Observer
  2869. let bodyObserver = new MutationObserver(bodyMutating);
  2870. // ** MO starter / restarter
  2871. const DEBUG_START = false;
  2872. let firstRun = true;
  2873. function runMO() {
  2874. // run code soon as the elements HEAD, BDDY and Options are ready/available.
  2875. // or when page url has changed ...
  2876. if (document.head && document.body && DBVARS.optionsReady) {
  2877. if (DEBUG_START) console.info(`${log}runMO : HEAD/BODY/Options available`);
  2878. if (firstRun) {
  2879. addCSS();
  2880. window.setTimeout(addExtraCSS, 150); // fb is sometimes laggy ...
  2881. buildMoppingDialog();
  2882. firstRun = false;
  2883. }
  2884. if (setFeedSettings()) {
  2885. if (DEBUG_START) console.info(`${log}runMO : feed settings have been reset, A/N/G/V/M:`, VARS.isAF, VARS.isNF, VARS.isGF, VARS.isVF, VARS.isMP);
  2886. // - clear out mutations not yet processed ...
  2887. let mutations = bodyObserver.takeRecords();
  2888. bodyObserver.disconnect();
  2889. // - and start up the osbserver again.
  2890. bodyObserver.observe(document.body, {childList: true, subtree: true, attributes: false});
  2891. }
  2892. }
  2893. else {
  2894. if (DEBUG_START) console.info(`${log}HEAD/BODY/Options not available`);
  2895. setTimeout(runMO, 10);
  2896. }
  2897. }
  2898. runMO();
  2899. })();