Omnivore Everything

save all browsing history to Omnivore

当前为 2024-06-30 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Omnivore Everything
  3. // @namespace Violentmonkey Scripts
  4. // @version 0.8
  5. // @description save all browsing history to Omnivore
  6. // @author fankaidev
  7. // @match *://*/*
  8. // @exclude *://omnivore.app/*
  9. // @exclude *://cubox.pro/*
  10. // @exclude *://readwise.io/*
  11. // @exclude *://localhost:*/*
  12. // @exclude *://127.0.0.1:*/*
  13. // @grant GM_xmlhttpRequest
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @grant GM_registerMenuCommand
  17. // @require https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/crypto-js.js
  18. // @license MIT
  19. // ==/UserScript==
  20.  
  21. (function () {
  22. "use strict";
  23. function uuid() {
  24. return CryptoJS.lib.WordArray.random(16).toString(CryptoJS.enc.Hex);
  25. }
  26.  
  27. let hrefHistory = [];
  28.  
  29. const omnivoreSchema = {
  30. saveUrl: `
  31. mutation SaveUrl($input: SaveUrlInput!) {
  32. saveUrl(input: $input) {
  33. ... on SaveSuccess {
  34. url
  35. clientRequestId
  36. }
  37. ... on SaveError {
  38. errorCodes
  39. message
  40. }
  41. }
  42. }`,
  43. savePage: `
  44. mutation SavePage($input: SavePageInput!) {
  45. savePage(input: $input) {
  46. ... on SaveSuccess {
  47. url
  48. clientRequestId
  49. }
  50. ... on SaveError {
  51. errorCodes
  52. message
  53. }
  54. }
  55. }`,
  56. };
  57.  
  58. // Function to get or initialize global state
  59. function getGlobalState(key, defaultValue) {
  60. return GM_getValue(key, defaultValue);
  61. }
  62.  
  63. // Function to update global state
  64. function updateGlobalState(key, value) {
  65. GM_setValue(key, value);
  66. }
  67.  
  68. function getApiKey() {
  69. let apiKey = getGlobalState("omnivoreApiKey", null);
  70. if (!apiKey) {
  71. apiKey = prompt("[Omni] Please enter Omnivore API key:", "");
  72. if (apiKey) {
  73. updateGlobalState("omnivoreApiKey", apiKey);
  74. } else {
  75. console.error("[Omni] No API key provided. Script will not function correctly.");
  76. }
  77. }
  78. return apiKey;
  79. }
  80.  
  81. function changeEndpoint() {
  82. let newApiKey = prompt("[Omni] Enter Omnivore API key:", getGlobalState("omnivoreApiKey", ""));
  83. if (newApiKey) {
  84. updateGlobalState("omnivoreApiKey", newApiKey);
  85. console.log("[Omni] API key updated to", newApiKey);
  86. }
  87. }
  88.  
  89. function getBlacklistPattern() {
  90. let val = getGlobalState("blacklistPattern", null);
  91. if (!val) {
  92. val = `omnivore.app/.*|readwise.io/.*|cubox.pro/.*|localhost:.*`;
  93. updateGlobalState("blacklistPattern", val);
  94. }
  95. return new RegExp(val.trim());
  96. }
  97.  
  98. function getWhitelistPattern() {
  99. let val = getGlobalState("whitelistPattern", null);
  100. if (!val) {
  101. val = ".*";
  102. updateGlobalState("whitelistPattern", val);
  103. }
  104. return new RegExp(val.trim());
  105. }
  106.  
  107. function changeEndpoint() {
  108. let newApiKey = prompt("[Omni] Enter Omnivore API key:", getGlobalState("omnivoreApiKey", ""));
  109. if (newApiKey) {
  110. updateGlobalState("omnivoreApiKey", newApiKey);
  111. console.log("[Omni] API key updated to", newApiKey);
  112. }
  113. }
  114.  
  115. function changeBlacklistPattern() {
  116. let newVal = prompt("[Omni] blacklist pattern in regex", getGlobalState("blacklistPattern", ""));
  117. updateGlobalState("blacklistPattern", newVal);
  118. console.log("[Omni] blacklist patterns", newVal);
  119. }
  120.  
  121. function changeWhitelistPattern() {
  122. let newVal = prompt("[Omni] whitelist pattern in regex", getGlobalState("whitelistPattern", ""));
  123. updateGlobalState("whitelistPattern", newVal);
  124. console.log("[Omni] whitelist patterns", newVal);
  125. }
  126.  
  127. GM_registerMenuCommand("Change Omnivore API key", changeEndpoint);
  128. GM_registerMenuCommand("Change blacklist pattern", changeBlacklistPattern);
  129. GM_registerMenuCommand("Change whitelist pattern", changeWhitelistPattern);
  130.  
  131. function savePage(url) {
  132. const variables = {
  133. input: {
  134. url,
  135. title: document.title,
  136. originalContent: document.documentElement.outerHTML,
  137. source: "chrome",
  138. clientRequestId: uuid(),
  139. },
  140. };
  141. const apiKey = getApiKey();
  142. const apiUrl = "https://api-prod.omnivore.app/api/graphql";
  143. if (!apiKey || !apiUrl) {
  144. return;
  145. }
  146. GM_xmlhttpRequest({
  147. method: "POST",
  148. url: apiUrl,
  149. headers: {
  150. "Content-Type": "application/json",
  151. Authorization: apiKey,
  152. },
  153. data: JSON.stringify({ query: omnivoreSchema.savePage, variables }),
  154. onload: function (response) {
  155. if (response.status === 200) {
  156. console.log("[Omni] saved page to omnivore");
  157. } else {
  158. console.error("[Omni] Failed to save to omnivore", response.responseText);
  159. }
  160. },
  161. onerror: function (error) {
  162. console.error("Request failed:", error);
  163. },
  164. });
  165. }
  166.  
  167. function saveUrl(url) {
  168. const variables = {
  169. input: {
  170. url,
  171. source: "chrome",
  172. clientRequestId: uuid(),
  173. },
  174. };
  175. const apiKey = getApiKey();
  176. const apiUrl = "https://api-prod.omnivore.app/api/graphql";
  177. if (!apiKey || !apiUrl) {
  178. return;
  179. }
  180. GM_xmlhttpRequest({
  181. method: "POST",
  182. url: apiUrl,
  183. headers: {
  184. "Content-Type": "application/json",
  185. Authorization: apiKey,
  186. },
  187. data: JSON.stringify({ query: omnivoreSchema.saveUrl, variables }),
  188. onload: function (response) {
  189. if (response.status === 200) {
  190. console.log("[Omni] saved url to omnivore");
  191. } else {
  192. console.error("[Omni] Failed to save to omnivore", response.responseText);
  193. }
  194. },
  195. onerror: function (error) {
  196. console.error("Request failed:", error);
  197. },
  198. });
  199. }
  200.  
  201. function process() {
  202. const url = window.location.href.split("#")[0];
  203. if (hrefHistory.includes(url)) {
  204. console.log("[Omni] skip processed url", url);
  205. return;
  206. }
  207. console.log("[Omni] processing url", url);
  208. hrefHistory.push(url);
  209.  
  210. const blacklistPattern = getBlacklistPattern();
  211. const whitelistPattern = getWhitelistPattern();
  212. if (!whitelistPattern.test(url)) {
  213. console.log("[Omni] ignore non-whitelisted url");
  214. return;
  215. }
  216. if (blacklistPattern.test(url)) {
  217. console.log("[Omni] ignore blacklisted url");
  218. return;
  219. }
  220.  
  221. if (document.contentType === "application/pdf") {
  222. saveUrl(url);
  223. } else {
  224. savePage(url);
  225. }
  226. }
  227.  
  228. function scheduleProcess() {
  229. if (window.self === window.top) {
  230. console.log(`[Omni] current href is`, window.location.href);
  231. setTimeout(() => {
  232. process();
  233. }, 5000);
  234. }
  235. }
  236.  
  237. // Intercept pushState and replaceState
  238. const originalPushState = history.pushState;
  239. const originalReplaceState = history.replaceState;
  240. history.pushState = function () {
  241. originalPushState.apply(this, arguments);
  242. scheduleProcess();
  243. };
  244.  
  245. history.replaceState = function () {
  246. originalReplaceState.apply(this, arguments);
  247. scheduleProcess();
  248. };
  249. window.addEventListener("load", function () {
  250. scheduleProcess();
  251. });
  252. window.addEventListener("popstate", function (event) {
  253. scheduleProcess();
  254. });
  255. })();