Greasy Fork is available in English.

Sync Twitter To Flomo

create a flomo note of current twitter thread, need flomo api

  1. // ==UserScript==
  2. // @name Sync Twitter To Flomo
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.9
  5. // @description create a flomo note of current twitter thread, need flomo api
  6. // @author fankaidev
  7. // @match https://x.com/*
  8. // @match https://twitter.com/*
  9. // @connect flomoapp.com
  10. // @grant GM_xmlhttpRequest
  11. // @require https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. "use strict";
  17.  
  18. let mainUser = "";
  19. let mainDate = "";
  20. let title = "";
  21.  
  22. function getTweetUser(tweet) {
  23. let div = tweet.querySelector('div[data-testid="User-Name"]');
  24. if (!div) {
  25. return "";
  26. }
  27. return div.textContent.split("·")[0].split("@");
  28. }
  29.  
  30. function getTweetText(tweet) {
  31. let tweetText = tweet.querySelector('div[data-testid="tweetText"]');
  32. if (!tweetText) {
  33. return "";
  34. }
  35. return tweetText.textContent;
  36. }
  37.  
  38. function getTweetTime(tweet) {
  39. let divs = tweet.getElementsByTagName("time");
  40. for (let div of divs) {
  41. if (div.textContent.length > 8) {
  42. return div.textContent;
  43. }
  44. }
  45. return "";
  46. }
  47.  
  48. function quoteText(inputString) {
  49. let lines = inputString.split("\n");
  50. let modifiedLines = lines.map((line) => "> " + line);
  51. let result = modifiedLines.join("\n");
  52. return result;
  53. }
  54.  
  55. function getTweetContent(tweetDiv) {
  56. // console.log(tweetDiv);
  57. let tweetContent = "";
  58. let tweetLinks = [];
  59. if (tweetDiv) {
  60. let user = getTweetUser(tweetDiv);
  61. if (!user) {
  62. return { content: tweetContent, links: tweetLinks };
  63. }
  64. let txt = getTweetText(tweetDiv);
  65. let ts = getTweetTime(tweetDiv);
  66. tweetContent += `${user[0].trim()} @${user[1]}\n`;
  67. if (ts) {
  68. tweetContent += `${ts}\n`;
  69. if (!mainDate) {
  70. mainDate = ts;
  71. mainUser = user[0];
  72. title = _.truncate(txt, {'length': 120, 'omission': '...' });
  73. }
  74. }
  75. tweetContent += `---\n${txt}\n`;
  76. let linkDivs = tweetDiv.querySelectorAll('div[role="link"]');
  77. let subLinks = [];
  78. let subContents = [];
  79. for (let linkDiv of linkDivs) {
  80. const { content, links } = getTweetContent(linkDiv);
  81. subLinks.push(...links);
  82. subContents.push(content);
  83. }
  84. let imgs = tweetDiv.getElementsByTagName("img");
  85. for (let img of imgs) {
  86. if (img.src.includes("twimg.com/media") && !subLinks.includes(img.src)) {
  87. tweetLinks.push(img.src);
  88. tweetContent += `${img.src}\n`;
  89. }
  90. }
  91. for (let content of subContents) {
  92. if (content.trim()) {
  93. tweetContent += `\n${quoteText(content)}\n`;
  94. }
  95. }
  96. }
  97. // console.log("links", tweetLinks)
  98. return { content: tweetContent, links: tweetLinks };
  99. }
  100.  
  101. function getFullContent() {
  102. mainUser = "";
  103. mainDate = "";
  104. title = "";
  105. var fullContent = "";
  106. const primaryColumn = document.querySelector('div[data-testid="primaryColumn"]');
  107. if (!primaryColumn) {
  108. console.log("primaryColumn not found");
  109. return "";
  110. }
  111. const postsSection = primaryColumn.firstChild.getElementsByTagName("section")[0];
  112. if (!postsSection) {
  113. console.log("missing posts");
  114. return "";
  115. }
  116. // console.log("section", postsSection)
  117. // console.log("section", postsSection.getElementsByTagName('div')[0].firstChild)
  118. const postDivs = postsSection.getElementsByTagName("div")[0].firstChild.children;
  119. if (postDivs.length === 0) {
  120. console.log("missing posts");
  121. return "";
  122. }
  123. // console.log("len of posts:", postDivs.length);
  124. for (let postDiv of postDivs) {
  125. if (postDiv.textContent === "More Tweets" || postDiv.textContent.startsWith("Discover more")) {
  126. break;
  127. }
  128. if (Array.from(postDiv.getElementsByTagName("span")).some(span => span.innerText.trim() === "Ad")) {
  129. console.log("skip ad block", postDiv);
  130. continue;
  131. }
  132. let tweetDiv = postDiv.getElementsByTagName("article")[0];
  133. let { content } = getTweetContent(tweetDiv);
  134. if (content) {
  135. fullContent += `======\n${content}\n`;
  136. }
  137. }
  138. fullContent = `#twitter\n${mainUser}: ${title}\n${window.location.href}\n` + fullContent;
  139.  
  140. console.log("full content:\n", fullContent);
  141. return fullContent;
  142. }
  143.  
  144. function createFlomoButton(flomoApi, fullContent) {
  145. const img = document.createElement("img");
  146.  
  147. img.style.cursor = "pointer";
  148. img.style.width = "32px";
  149. img.style.height = "32px";
  150.  
  151. img.onclick = function () {
  152. GM_xmlhttpRequest({
  153. method: "POST",
  154. url: flomoApi,
  155. headers: { "Content-Type": "application/json" },
  156. data: JSON.stringify({ content: fullContent }),
  157. onload: function (response) {
  158. console.log("Success:", response.status);
  159. },
  160. onerror: function (response) {
  161. console.log("Error:", response.statusText);
  162. },
  163. });
  164. img.style.width = "36px";
  165. img.style.height = "36px";
  166. setTimeout(function () {
  167. img.style.width = "32px";
  168. img.style.height = "32px";
  169. }, 200);
  170. };
  171.  
  172. GM_xmlhttpRequest({
  173. method: "GET",
  174. url: "https://flomoapp.com/images/logo-192x192.png",
  175. responseType: "blob",
  176. onload: function (response) {
  177. img.src = URL.createObjectURL(response.response);
  178. },
  179. });
  180.  
  181. const div = document.createElement("div");
  182. div.appendChild(img);
  183. div.style.display = "flex";
  184. div.style.alignItems = "center";
  185. div.style.justifyContent = "center";
  186. div.style.width = "36px";
  187. div.style.marginLeft = "50px";
  188.  
  189. return div;
  190. }
  191.  
  192. function fetchFlomoApi() {
  193. let flomoApi = localStorage.getItem("flomo_api");
  194. if (!flomoApi) {
  195. flomoApi = prompt("Please enter the flomo_api from https://v.flomoapp.com/mine/?source=incoming_webhook");
  196. if (flomoApi) {
  197. localStorage.setItem("flomo_api", flomoApi);
  198. alert("flomo_api has been saved.");
  199. } else {
  200. alert("No key entered. flomo_api was not saved.");
  201. return false;
  202. }
  203. }
  204. return flomoApi;
  205. }
  206.  
  207. // Function to insert text into the sidebar
  208. function process() {
  209. console.log("try process");
  210. const fullContent = getFullContent();
  211. if (!fullContent) {
  212. return false;
  213. }
  214. const flomoApi = fetchFlomoApi();
  215. if (!flomoApi) {
  216. return true;
  217. }
  218. const flomoButton = createFlomoButton(flomoApi, fullContent);
  219. const buttons = document.querySelector('div[role="group"]');
  220. buttons.appendChild(flomoButton);
  221.  
  222. return true;
  223. }
  224.  
  225. let processing = false;
  226. function tryProcess() {
  227. if (processing) {
  228. console.log("already processing, skip");
  229. return;
  230. }
  231. if (!window.location.href.includes("status")) {
  232. console.log("skip process url:", window.location.href);
  233. return;
  234. }
  235. var interval = setInterval(function () {
  236. try {
  237. processing = true;
  238. if (process()) {
  239. clearInterval(interval);
  240. processing = false;
  241. }
  242. } catch (error) {
  243. console.log(error);
  244. clearInterval(interval);
  245. processing = false;
  246. }
  247. }, 500);
  248. }
  249.  
  250. (function (history) {
  251. const pushState = history.pushState;
  252. const replaceState = history.replaceState;
  253. history.pushState = function (state, title, url) {
  254. const result = pushState.apply(history, arguments);
  255. window.dispatchEvent(new Event("pushstate"));
  256. window.dispatchEvent(new Event("locationchange"));
  257. return result;
  258. };
  259. history.replaceState = function (state, title, url) {
  260. const result = replaceState.apply(history, arguments);
  261. window.dispatchEvent(new Event("replacestate"));
  262. window.dispatchEvent(new Event("locationchange"));
  263. return result;
  264. };
  265. window.addEventListener("popstate", () => {
  266. window.dispatchEvent(new Event("locationchange"));
  267. });
  268. })(window.history);
  269.  
  270. window.addEventListener("locationchange", () => {
  271. console.log("URL changed to:", window.location.href);
  272. setTimeout(tryProcess, 1000);
  273. });
  274. })();