leetcode2notion

Save LeetCode problems to Notion after clicking a button.

Versione datata 08/09/2024. Vedi la nuova versione l'ultima versione.

  1. // ==UserScript==
  2. // @name leetcode2notion
  3. // @namespace wuyifff
  4. // @version 1.0
  5. // @description Save LeetCode problems to Notion after clicking a button.
  6. // @author wuyifff
  7. // @match https://leetcode.cn/problems/*
  8. // @match https://leetcode.com/problems/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=leetcode.com
  10. // @grant GM_xmlhttpRequest
  11. // @license MIT
  12. // @homepage https://github.com/wuyifff/leetcode2notion
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17. // replace to your own token and ID
  18. const notionToken = '';
  19. const databaseId = '';
  20.  
  21. // 1. add save button
  22. // select language button (optional)
  23. function addUIElements() {
  24. const button = document.createElement("button");
  25. button.innerHTML = "Save to Notion";
  26. button.style.position = "fixed";
  27. button.style.bottom = "10px";
  28. button.style.right = "10px";
  29. button.style.zIndex = 1000;
  30. button.style.padding = "10px 20px";
  31. button.style.backgroundColor = "#4CAF50";
  32. button.style.color = "white";
  33. button.style.border = "none";
  34. button.style.borderRadius = "5px";
  35. button.style.cursor = "pointer";
  36. button.onclick = saveProblemToNotion;
  37.  
  38. const select = document.createElement("select");
  39. select.id = "languageSelect";
  40. select.style.position = "fixed";
  41. select.style.bottom = "50px";
  42. select.style.right = "10px";
  43. select.style.zIndex = 1000;
  44. select.style.padding = "10px";
  45. select.style.backgroundColor = "#4CAF50";
  46. select.style.color = "white";
  47. select.style.border = "none";
  48. select.style.borderRadius = "5px";
  49. select.style.cursor = "pointer";
  50.  
  51. const optionPython = document.createElement("option");
  52. optionPython.value = "python";
  53. optionPython.innerText = "Python";
  54.  
  55. const optionCpp = document.createElement("option");
  56. optionCpp.value = "cpp";
  57. optionCpp.innerText = "C++";
  58.  
  59. select.appendChild(optionPython);
  60. select.appendChild(optionCpp);
  61.  
  62. const container = document.createElement("div");
  63. container.style.display = "flex";
  64. container.style.flexDirection = "column";
  65. container.style.alignItems = "center";
  66. container.style.marginLeft = "10px";
  67. //container.appendChild(select);
  68. container.appendChild(button);
  69.  
  70. container.style.position = "fixed";
  71. container.style.bottom = "10px";
  72. container.style.right = "10px";
  73. document.body.appendChild(container);
  74. }
  75.  
  76. // 2. get leetcode problem info
  77. function getProblemData() {
  78. const title = document.querySelector('.text-title-large a')?.innerText || 'No title found';
  79. const difficultyElement = document.querySelector("div[class*='text-difficulty-']");
  80. const difficulty = difficultyElement ? difficultyElement.innerText : 'No difficulty found';
  81. const url = window.location.href;
  82. const tagElements = document.querySelectorAll("a[href*='/tag/']");
  83. const tagTexts = Array.from(tagElements).map(element => element.innerText);
  84.  
  85. const codeDiv = document.querySelector('.view-lines.monaco-mouse-cursor-text[role="presentation"]');
  86. let codeText = '';
  87. if (codeDiv) {
  88. const codeLines = codeDiv.querySelectorAll('div');
  89. codeText = Array.from(codeLines).map(line => line.innerText).join('\n');
  90. } else {
  91. codeText = 'No code found';
  92. }
  93. //console.log(codeText);
  94. //const selectedLanguage = document.getElementById("languageSelect").value;
  95. const selectedLanguage = 'python';
  96. return {
  97. title: title,
  98. difficulty: difficulty,
  99. url: url,
  100. tag: tagTexts,
  101. code: codeText,
  102. language: selectedLanguage
  103. };
  104. }
  105.  
  106. // 3. save to notion and check if duplicate
  107. async function saveProblemToNotion() {
  108. const problemData = getProblemData();
  109. console.log(problemData);
  110.  
  111. const searchUrl = `https://api.notion.com/v1/search`;
  112. const searchBody = {
  113. "query": problemData.title,
  114. "filter": {
  115. "value": "page",
  116. "property": "object"
  117. },
  118. "sort": {
  119. "direction": "ascending",
  120. "timestamp": "last_edited_time"
  121. }
  122. };
  123.  
  124. GM_xmlhttpRequest({
  125. method: 'POST',
  126. url: searchUrl,
  127. headers: {
  128. 'Authorization': `Bearer ${notionToken}`,
  129. 'Content-Type': 'application/json',
  130. 'Notion-Version': '2022-06-28'
  131. },
  132. data: JSON.stringify(searchBody),
  133. onload: function(searchResponse) {
  134. if (searchResponse.status === 200) {
  135. const searchResult = JSON.parse(searchResponse.responseText);
  136. const existingPage = searchResult.results.find(result => result.properties?.Title?.title[0]?.text?.content === problemData.title);
  137.  
  138. if (existingPage) {
  139. const existingPageUrl = existingPage.url;
  140. alert('Problem already exists in Notion! Opening existing page...');
  141. window.open(existingPageUrl, '_blank');
  142. } else {
  143. createNewNotionPage(problemData);
  144. }
  145. } else {
  146. console.error('Error searching Notion database', searchResponse.responseText);
  147. alert('Failed to search Notion database. Check the console for details.');
  148. }
  149. },
  150. onerror: function(error) {
  151. console.error('Error in searching Notion database', error);
  152. alert('An error occurred while searching Notion database.');
  153. }
  154. });
  155. }
  156.  
  157. // 4. create new page
  158. function createNewNotionPage(problemData) {
  159. const tags = problemData.tag.map(tag => ({
  160. name: tag
  161. }));
  162.  
  163. const url = `https://api.notion.com/v1/pages`;
  164. const body = {
  165. parent: { database_id: databaseId },
  166. properties: {
  167. 'Title': {
  168. title: [
  169. {
  170. text: {
  171. content: problemData.title
  172. }
  173. }
  174. ]
  175. },
  176. 'Difficulty': {
  177. select: {
  178. name: problemData.difficulty
  179. }
  180. },
  181. 'Link': {
  182. url: problemData.url
  183. },
  184. 'Date': {
  185. date: {
  186. start: new Date().toISOString().split('T')[0] // format YYYY-MM-DD
  187. }
  188. },
  189. 'Tags': {
  190. multi_select: tags
  191. }
  192. },
  193. children: [
  194. {
  195. object: 'block',
  196. type: 'code',
  197. code: {
  198. rich_text: [
  199. {
  200. type: 'text',
  201. text: {
  202. content: problemData.code
  203. }
  204. }
  205. ],
  206. language: problemData.language
  207. }
  208. }
  209. ]
  210. };
  211.  
  212. GM_xmlhttpRequest({
  213. method: 'POST',
  214. url: url,
  215. headers: {
  216. 'Authorization': `Bearer ${notionToken}`,
  217. 'Content-Type': 'application/json',
  218. 'Notion-Version': '2022-06-28'
  219. },
  220. data: JSON.stringify(body),
  221. onload: function(response) {
  222. if (response.status === 200) {
  223. const responseData = JSON.parse(response.responseText);
  224. const notionPageUrl = responseData.url;
  225. alert('Problem saved to Notion!');
  226. window.open(notionPageUrl, '_blank');
  227. } else {
  228. console.error('Failed to save to Notion', response.responseText);
  229. alert('Failed to save to Notion. Check the console for more details.');
  230. }
  231. },
  232. onerror: function(error) {
  233. console.error('Error in saving to Notion', error);
  234. alert('An error occurred while saving to Notion.');
  235. }
  236. });
  237. }
  238.  
  239. addUIElements();
  240.  
  241. })();