Twitter Non-native Retweet MOD

Add "Retweet with comments" link to tweets

  1. // ==UserScript==
  2. // @name Twitter Non-native Retweet MOD
  3. // @version 1.0.140930
  4. // @namespace @sapikachu
  5. // @description Add "Retweet with comments" link to tweets
  6. // @include http://twitter.com/*
  7. // @include https://twitter.com/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. // http://wiki.greasespot.net/Content_Script_Injection
  13. function contentEval(source) {
  14. // Check for function input.
  15. if ('function' == typeof source) {
  16. // Execute this function with no arguments, by adding parentheses.
  17. // One set around the function, required for valid syntax, and a
  18. // second empty set calls the surrounded function.
  19. source = '(' + source + ')();'
  20. }
  21.  
  22. // Create a script node holding this source code.
  23. var script = document.createElement('script');
  24. script.setAttribute("type", "application/javascript");
  25. script.textContent = source;
  26.  
  27. // Insert the script node into the page, so it will run, and immediately
  28. // remove it to clean up.
  29. document.body.appendChild(script);
  30. document.body.removeChild(script);
  31. }
  32.  
  33.  
  34. function register() {
  35. // originally by @jamespgilbert (http://userscripts.org/scripts/show/70467), modified by @SAPikachu
  36.  
  37. var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
  38.  
  39. // Save or make a fake console object for debugging
  40. var console = {};
  41.  
  42. for (var i = 0; i < names.length; i++) {
  43. if (window["console"]) {
  44. console[names[i]] = window.console[names[i]];
  45. } else {
  46. console[names[i]] = function() {};
  47. }
  48. }
  49. // Twitter disables console object after loading, we make it available again for using in firebug
  50. window._console = console;
  51.  
  52. window.showRetweetBox = function (link) {
  53. setTimeout(function() {
  54. var impl = function($) {
  55. try
  56. {
  57. var tweetElem = $(link).parents().filter(".js-actionable-tweet").eq(0);
  58. if (tweetElem.size() == 0) {
  59. alert("Can't locate tweet element.");
  60. return;
  61. };
  62. var userElem = tweetElem.find(".account-group .username b").eq(0);
  63. var userName = $.trim(userElem.html());
  64. if (!userElem.length) {
  65. userElem = tweetElem.find(".ProfileTweet-originalAuthor .ProfileTweet-screenname");
  66. userName = $.trim(userElem.text()).substr(1);
  67. }
  68.  
  69. var contentElem = $(".js-tweet-text", tweetElem).eq(0).clone();
  70. $("a", contentElem).each(function() {
  71. var o = $(this);
  72. var expanded = o.data("ultimate-url") || o.data("expanded-url");
  73. if (expanded) {
  74. o.text(expanded);
  75. };
  76. });
  77. var content = " RT @" + userName + " " + $.trim(contentElem.text());
  78.  
  79. $("#global-new-tweet-button").trigger(
  80. "uiOpenTweetDialog",
  81. {
  82. defaultText: content,
  83. canTweetDefaultText: !0,
  84. cursorPosition: 0
  85. }
  86. );
  87. // Dirty hack to preserve conversation stream
  88. // Seems tweetElem.data("tweet-id") will parse the
  89. // id as float and give us wrong result
  90. $(document).trigger(
  91. "uiOverrideTweetBoxOptions",
  92. { id: tweetElem.attr("data-tweet-id") }
  93. );
  94. } catch (e) {
  95. console.error(e);
  96. }
  97. };
  98. if (window["jQuery"]) {
  99. impl(window["jQuery"]);
  100. } else {
  101. // Use loadrunner from the page
  102. using("core/jquery", impl);
  103. }
  104. }, 0);
  105. };
  106.  
  107. function insertRetweetLink(parent, text) {
  108. if (parent.getElementsByClassName("action-rtwc-container").length > 0) {
  109. return;
  110. }
  111. var ref = parent.getElementsByClassName("action-fav-container")[0];
  112. var realrt = document.createElement("li");
  113. realrt.className = "action-rtwc-container";
  114. realrt.innerHTML = '<a href="#" onclick="showRetweetBox(this); return false;" class="with-icn"><span class="Icon Icon--retweet" style="color: inherit"></span> <b>' + text + '</b></a>';
  115. parent.insertBefore(realrt, ref);
  116. }
  117.  
  118. function insertRetweetLink2(parent, text) {
  119. if (parent.getElementsByClassName("ProfileTweet-action--rtwc").length > 0) {
  120. return;
  121. }
  122. var ref = parent.getElementsByClassName("ProfileTweet-action--favorite")[0];
  123. var realrt = document.createElement("div");
  124. realrt.className = "ProfileTweet-action ProfileTweet-action--retweet ProfileTweet-action--rtwc js-toggle-state js-toggle-rt";
  125. realrt.innerHTML = '<button title="Retweet with comment" href="#" onclick="showRetweetBox(this); return false;" class="ProfileTweet-actionButton js-tooltip js-actionButton"><span class="Icon Icon--retweet" style="color: inherit"></span> <span class="u-isHiddenVisually">' + text + '</span></button>';
  126. parent.insertBefore(realrt, ref);
  127. }
  128.  
  129. function addRetweetLink(item) {
  130. var actionsArray;
  131. try {
  132. actionsArray = item.getElementsByClassName("tweet-actions") || [];
  133. } catch (e) {
  134. return;
  135. }
  136. for (var i = 0; i < actionsArray.length; i++) {
  137. insertRetweetLink(actionsArray[i], "RT w/ C");
  138. }
  139. try {
  140. actionsArray = item.getElementsByClassName("ProfileTweet-actionList") || [];
  141. } catch (e) {
  142. return;
  143. }
  144. for (var i = 0; i < actionsArray.length; i++) {
  145. insertRetweetLink2(actionsArray[i], "w/ C");
  146. }
  147. }
  148.  
  149. function addMultipleRetweetLinks(elements) {
  150. if (elements.length > 0) {
  151. for (var i = 0; i < elements.length; i++) {
  152. // due to some unknown reason, we need to create a scope and capture the node object, otherwise something strange will happen (elements will be cleared etc.)
  153. (function() {
  154. var node = elements[i];
  155. setTimeout(function() {
  156. addRetweetLink(node);
  157. }, 0);
  158. })();
  159. }
  160. }
  161. }
  162.  
  163. function addMultipleRetweetLinksForContainer(klass) {
  164. var container = document.getElementsByClassName(klass);
  165. if (container.length) {
  166. tweets = container[0].getElementsByClassName("js-stream-tweet");
  167. addMultipleRetweetLinks(tweets);
  168. }
  169. }
  170.  
  171. function addLinksToTimeline() {
  172. var timeline = document.getElementById("timeline") || document.getElementById("discover_items");
  173. var tweets;
  174. if (timeline) {
  175. tweets = timeline.getElementsByClassName("js-stream-item");
  176. addMultipleRetweetLinks(tweets);
  177. }
  178. tweets = document.getElementsByClassName("permalink-tweet");
  179. if (tweets.length) {
  180. addMultipleRetweetLinks(tweets);
  181. addMultipleRetweetLinksForContainer("permalink-in-reply-tos");
  182. addMultipleRetweetLinksForContainer("permalink-replies");
  183. }
  184. addMultipleRetweetLinksForContainer("GridTimeline");
  185. }
  186. function setupTimeline() {
  187. addLinksToTimeline();
  188. var timeline = document.getElementById("timeline");
  189. var observer = new MutationObserver(function(mutations) {
  190. mutations.forEach(function(mutation) {
  191. if (mutation.addedNodes) {
  192. for (var i = 0; i < mutation.addedNodes.length; i++) {
  193. var target = mutation.addedNodes[i];
  194. if (!target.getElementsByClassName) {
  195. continue;
  196. }
  197. var cl = target.className || "";
  198. if (cl.indexOf("js-stream-tweet") >= 0 || cl.indexOf("stream-item") >= 0) {
  199. addRetweetLink(target);
  200. } else if (cl.indexOf("js-conversation-replies")) {
  201. replies = target.getElementsByClassName("js-actionable-tweet");
  202. addMultipleRetweetLinks(replies);
  203. } else if (target.id === "timeline") {
  204. addLinksToTimeline();
  205. }
  206. }
  207. }
  208. });
  209. });
  210. observer.observe(document.body, { subtree: true, childList: true });
  211. }
  212.  
  213. if (document.readyState == "complete" || document.readySate == "loaded" || document.readyState == "interactive") {
  214. setupTimeline();
  215. } else {
  216. document.addEventListener(
  217. 'DOMContentLoaded', setupTimeline, false
  218. );
  219. }
  220. }
  221.  
  222. contentEval(register);
  223. })();
  224.