- // ==UserScript==
- // @name leetcode2notion
- // @namespace wuyifff
- // @version 1.2
- // @description Save LeetCode problems to Notion after clicking a button.
- // @author wuyifff
- // @match https://leetcode.cn/problems/*
- // @match https://leetcode.com/problems/*
- // @connect api.notion.com
- // @icon https://www.google.com/s2/favicons?sz=64&domain=leetcode.com
- // @grant GM_xmlhttpRequest
- // @license MIT
- // @homepage https://github.com/wuyifff/leetcode2notion
- // ==/UserScript==
-
- (function() {
- 'use strict';
- // replace to your own token and ID
- const notionToken = ''; // Notion API token
- const databaseId = ''; // Notion database ID
-
- // 1. add save button
- // select language button (optional)
- let currentMinutes = 0;
- let currentSeconds = 0;
- function addUIElements() {
- // 1.1 save button
- const button = document.createElement("button");
- button.innerHTML = "Save to Notion";
- button.style.position = "fixed";
- button.style.bottom = "10px";
- button.style.right = "10px";
- button.style.zIndex = 1000;
- button.style.padding = "10px 20px";
- button.style.backgroundColor = "#4CAF50";
- button.style.color = "white";
- button.style.border = "none";
- button.style.borderRadius = "5px";
- button.style.cursor = "pointer";
- button.onclick = saveProblemToNotion;
-
- // 1.2 save language button (disabled)
- const select = document.createElement("select");
- select.id = "languageSelect";
- select.style.position = "fixed";
- select.style.bottom = "50px";
- select.style.right = "10px";
- select.style.zIndex = 1000;
- select.style.padding = "10px";
- select.style.backgroundColor = "#4CAF50";
- select.style.color = "white";
- select.style.border = "none";
- select.style.borderRadius = "5px";
- select.style.cursor = "pointer";
- const optionPython = document.createElement("option");
- optionPython.value = "python";
- optionPython.innerText = "Python";
- const optionCpp = document.createElement("option");
- optionCpp.value = "cpp";
- optionCpp.innerText = "C++";
- select.appendChild(optionPython);
- select.appendChild(optionCpp);
-
- const container = document.createElement("div");
- container.id = "save"
- container.style.display = "flex";
- container.style.flexDirection = "column";
- container.style.alignItems = "center";
- container.style.marginLeft = "10px";
- //container.appendChild(select);
- container.appendChild(button);
- container.style.position = "fixed";
- container.style.bottom = "10px";
- container.style.right = "10px";
- document.body.appendChild(container);
- }
- function addTimer() {
- // Create timer span if it doesn't exist
- let timerSpan = document.querySelector('#timerSpan');
- if (!timerSpan) {
- timerSpan = document.createElement("span");
- timerSpan.id = "timerSpan";
- timerSpan.className = 'ml-2 group/nav-back cursor-pointer gap-2 hover:text-lc-icon-primary dark:hover:text-dark-lc-icon-primary flex items-center h-[32px] transition-none hover:bg-fill-quaternary dark:hover:bg-fill-quaternary text-gray-60 dark:text-gray-60 px-2';
-
- // Append the timer span to the target location
- const targetDiv = document.getElementById('ide-top-btns');
- if (targetDiv) {
- targetDiv.appendChild(timerSpan);
- console.log("append timer success");
- } else {
- console.log("no ide-top-btns element!");
- }
- }
- }
-
- function updateTimer() {
- const now = new Date().getTime(); // Get the current time
- const elapsedTime = now - startTime; // Calculate the elapsed milliseconds
- const totalSeconds = Math.floor(elapsedTime / 1000); // Convert to seconds
- currentMinutes = Math.floor(totalSeconds / 60);
- currentSeconds = totalSeconds % 60;
- const formattedMinutes = currentMinutes < 10 ? `0${currentMinutes}` : currentMinutes;
- const formattedSeconds = currentSeconds < 10 ? `0${currentSeconds}` : currentSeconds;
-
- // Make sure timerSpan is available
- let timerSpan = document.querySelector('#timerSpan');
- if (!timerSpan) {
- addTimer();
- timerSpan = document.querySelector('#timerSpan'); // Re-select after creation
- }
-
- // Update the timer content
- timerSpan.textContent = `Time: ${formattedMinutes}:${formattedSeconds}`;
- }
-
- // 2. get leetcode problem info
- function getProblemData() {
- const title = document.querySelector('.text-title-large a')?.innerText || 'No title found';
- const difficultyElement = document.querySelector("div[class*='text-difficulty-']");
- const difficulty = difficultyElement ? difficultyElement.innerText : 'No difficulty found';
- const url = window.location.href;
- const tagElements = document.querySelectorAll("a[href*='/tag/']");
- const tagTexts = Array.from(tagElements).map(element => element.innerText);
-
- const codeDiv = document.querySelector('.view-lines.monaco-mouse-cursor-text[role="presentation"]');
- let codeText = '';
- if (codeDiv) {
- const codeLines = codeDiv.querySelectorAll('div');
- codeText = Array.from(codeLines).map(line => line.innerText).join('\n');
- } else {
- codeText = 'No code found';
- }
- //console.log(codeText);
- //const selectedLanguage = document.getElementById("languageSelect").value;
- const selectedLanguage = 'python';
- return {
- title: title,
- difficulty: difficulty,
- url: url,
- tag: tagTexts,
- code: codeText,
- language: selectedLanguage,
- time: currentMinutes
- };
- }
-
- // 3. save to notion and check if duplicate
- async function saveProblemToNotion() {
- const problemData = getProblemData();
- console.log(problemData);
-
- const searchUrl = `https://api.notion.com/v1/search`;
- const searchBody = {
- "query": problemData.title,
- "filter": {
- "value": "page",
- "property": "object"
- },
- "sort": {
- "direction": "ascending",
- "timestamp": "last_edited_time"
- }
- };
-
- GM_xmlhttpRequest({
- method: 'POST',
- url: searchUrl,
- headers: {
- 'Authorization': `Bearer ${notionToken}`,
- 'Content-Type': 'application/json',
- 'Notion-Version': '2022-06-28'
- },
- data: JSON.stringify(searchBody),
- onload: function(searchResponse) {
- if (searchResponse.status === 200) {
- const searchResult = JSON.parse(searchResponse.responseText);
- const existingPage = searchResult.results.find(result => result.properties?.Title?.title[0]?.text?.content === problemData.title);
-
- if (existingPage) {
- const existingPageUrl = existingPage.url;
- alert('Problem already exists in Notion! Opening existing page...');
- window.open(existingPageUrl, '_blank');
- } else {
- createNewNotionPage(problemData);
- }
- } else {
- console.error('Error searching Notion database', searchResponse.responseText);
- alert('Failed to search Notion database. Check the console for details.');
- }
- },
- onerror: function(error) {
- console.error('Error in searching Notion database', error);
- alert('An error occurred while searching Notion database.');
- }
- });
- }
-
- // 4. create new page
- function createNewNotionPage(problemData) {
- const tags = problemData.tag.map(tag => ({
- name: tag
- }));
-
- const url = `https://api.notion.com/v1/pages`;
- const body = {
- parent: { database_id: databaseId },
- properties: {
- 'Title': {
- title: [
- {
- text: {
- content: problemData.title
- }
- }
- ]
- },
- 'Difficulty': {
- select: {
- name: problemData.difficulty
- }
- },
- 'Link': {
- url: problemData.url
- },
- 'Date': {
- date: {
- start: new Date().toISOString().split('T')[0] // format YYYY-MM-DD
- }
- },
- 'Tags': {
- multi_select: tags
- },
- 'Time': {
- number: problemData.time
- },
- },
- children: [
- {
- object: 'block',
- type: 'code',
- code: {
- rich_text: [
- {
- type: 'text',
- text: {
- content: problemData.code
- }
- }
- ],
- language: problemData.language
- }
- }
- ]
- };
-
- GM_xmlhttpRequest({
- method: 'POST',
- url: url,
- headers: {
- 'Authorization': `Bearer ${notionToken}`,
- 'Content-Type': 'application/json',
- 'Notion-Version': '2022-06-28'
- },
- data: JSON.stringify(body),
- onload: function(response) {
- if (response.status === 200) {
- const responseData = JSON.parse(response.responseText);
- const notionPageUrl = responseData.url;
- alert('Problem saved to Notion!');
- window.open(notionPageUrl, '_blank');
- } else {
- console.error('Failed to save to Notion', response.responseText);
- alert('Failed to save to Notion. Check the console for more details.');
- }
- },
- onerror: function(error) {
- console.error('Error in saving to Notion', error);
- alert('An error occurred while saving to Notion.');
- }
- });
- }
-
- addUIElements();
- let startTime; // Record the start time
- setTimeout(function() {
- startTime = new Date().getTime();
- var tmp = setInterval(updateTimer, 1000); // update every second
- }, 5000); // delay 5 seconds
- })();