Twitter media-only filter toggle.

Toggle non-media tweets on and off on the home timeline, for the power-viewer!

  1. // ==UserScript==
  2. // @name Twitter media-only filter toggle.
  3. // @version 0.22
  4. // @description Toggle non-media tweets on and off on the home timeline, for the power-viewer!
  5. // @author Cro
  6. // @match https://*.twitter.com/*
  7. // @match https://*.x.com/*
  8. // @run-at document-idle
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @namespace https://greasyfork.org/users/10865
  12. // @icon https://www.google.com/s2/favicons?domain=twitter.com
  13. // @license MIT
  14. // ==/UserScript==
  15. /* jshint esversion: 6 */
  16.  
  17. (function() {
  18. 'use strict';
  19. let storage_key = 'cro-media-toggle';
  20. let show_all = GM_getValue(storage_key);
  21.  
  22. let create_ui = function(target)
  23. {
  24. let button = document.createElement('button');
  25. button.innerText = show_all ? 'Showing all home tweets' : 'Showing only media home tweets';
  26. button.onclick = function(event)
  27. {
  28. show_all = !show_all;
  29. GM_setValue(storage_key, show_all);
  30. location.reload();
  31. };
  32. target.prepend(button);
  33. };
  34.  
  35. let walk_objects = function*(obj)
  36. {
  37. let stack = Object.entries(obj);
  38. while (stack.length > 0)
  39. {
  40. let entry = stack.pop();
  41. yield entry;
  42. if (entry[1] != null && typeof(entry[1]) == 'object')
  43. {
  44. stack = stack.concat(Object.entries(entry[1]));
  45. }
  46. }
  47. };
  48.  
  49. let find_objects_at_keys = (obj, keys) => Array.from(walk_objects(obj)).filter(e => keys.includes(e[0])).map(e => e[1]);
  50. let any_key_in_obj = (obj, keys) => Array.from(walk_objects(obj)).some(e => keys.includes(e[0]));
  51. let target_entry_types = ['tweet-', 'promoted-tweet-', 'home-conversation-'];
  52. let media_types = ['media', 'card'];
  53. let has_media = (obj) => !target_entry_types.some(e => obj.entryId.startsWith(e)) || any_key_in_obj(obj, media_types);
  54.  
  55. let update_data = function(data)
  56. {
  57. if (show_all || location.pathname != '/home')
  58. {
  59. return;
  60. }
  61. for (let obj of find_objects_at_keys(data, ['instructions']))
  62. {
  63. for (let subobj of obj)
  64. {
  65. if (subobj.hasOwnProperty('entries'))
  66. {
  67. subobj.entries = subobj.entries.filter(has_media);
  68. }
  69. }
  70. }
  71. };
  72.  
  73. let old_parse = JSON.parse;
  74. let unsafe_window_parse = unsafeWindow.JSON.parse;
  75. let new_unsafe_window_parse = function(string)
  76. {
  77. let data = old_parse(string);
  78. try
  79. {
  80. if (data != null)
  81. {
  82. update_data(data);
  83. }
  84. }
  85. catch(error)
  86. {
  87. console.log(error);
  88. }
  89. return unsafe_window_parse(JSON.stringify(data));;
  90. };
  91. exportFunction(new_unsafe_window_parse, unsafeWindow.JSON, { defineAs: 'parse' });
  92.  
  93. // Wait for twitter's react crap finish loading things.
  94. let scan_interval = setInterval(function()
  95. {
  96. let target = document.body.querySelector('nav[role="navigation"]');
  97. if (target)
  98. {
  99. clearInterval(scan_interval);
  100. create_ui(target);
  101. }
  102. }, 10);
  103. })();