├── .gitignore ├── README.md ├── leetcode_to_notion.ipynb └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeetCode Notion Table Template and Integration 2 | 3 | This repository contains a template for recording LeetCode problems and a notebook that automates the process of inserting an entry into a Notion table based on a LeetCode link. The notebook extracts information such as problem title, number, link, difficulty, and problem description from the LeetCode website and inserts it into the specified Notion table. 4 | 5 | ## LeetCode Template 6 | The template I have created and use is available [here](https://glacier-bell-9e7.notion.site/1ea48ceede1b4482b204f8053b036feb?v=e62ae37861394f3fbafbae51612eaf2d&pvs=4). Feel free to use it. If you prefer to use your own table, make sure the column names (Problem, Name, Link, Difficulty) are consistent with mine for the integration. If not, you may need to modify the script accordingly. 7 | 8 | ## Prerequisites 9 | If you are using Google Colab (which I recommend), there are no prerequisites other than opening this [Google Colab notebook](https://colab.research.google.com/drive/1LSwHkBYbvZmieAkK7NABZdZ4iShFgpQR?usp=sharing), making a copy, and skipping to the "Setup" section. 10 | 11 | If you prefer to run the notebook in Jupyter Notebook/VS code, make sure you have the following prerequisites installed: 12 | 13 | - Python 3.6 or above 14 | - Required Python libraries: requests, beautifulsoup4, pandas, notion (You can install them by running `pip3 install -r requirements.txt`) 15 | 16 | ## Setup 17 | 1. Follow the tutorial from the [official Notion API documentation](https://developers.notion.com/docs/create-a-notion-integration) from step 1 to step 3. 18 | 19 | 2. Make a copy of your **Notion integration token** and **database ID** of your table (copy the link of the table or just open it in the browser to view the link). 20 | 21 | 3. Open the `leetcode_to_notion.ipynb` notebook using Jupyter Notebook. 22 | 23 | 4. In the notebook, provide your Notion API token and database URL. 24 | 25 | 5. Run All, and make sure to provide the LeetCode link when prompted. 26 | 27 | 6. The notebook will extract the necessary information from the LeetCode page and insert it into the specified Notion table. 28 | 29 | 30 | Please let me know if you have any further questions or need additional assistance. 31 | -------------------------------------------------------------------------------- /leetcode_to_notion.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "01186ea1-7344-4d92-a810-87dedb72309f", 7 | "metadata": { 8 | "id": "01186ea1-7344-4d92-a810-87dedb72309f" 9 | }, 10 | "outputs": [], 11 | "source": [ 12 | "import requests\n", 13 | "from bs4 import BeautifulSoup\n", 14 | "import requests\n", 15 | "import json\n", 16 | "import re" 17 | ] 18 | }, 19 | { 20 | "attachments": {}, 21 | "cell_type": "markdown", 22 | "id": "ysehb82enxsY", 23 | "metadata": { 24 | "id": "ysehb82enxsY" 25 | }, 26 | "source": [ 27 | "## TODO:" 28 | ] 29 | }, 30 | { 31 | "attachments": {}, 32 | "cell_type": "markdown", 33 | "id": "xupY6riZUcnp", 34 | "metadata": { 35 | "id": "xupY6riZUcnp" 36 | }, 37 | "source": [ 38 | "\n", 39 | "\n", 40 | "1. follow [this tutorial](https://developers.notion.com/docs/create-a-notion-integration) from step 1 to step 3 to create a Notion integration and connect to your leetcode table\n", 41 | "2. Go to your notion table's page and copy the link to get the database_id:\n", 42 | "\n", 43 | "3. That's it! RUN ALL and paste the link to the problem!" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "id": "ZJQE2scYnrQ9", 50 | "metadata": { 51 | "id": "ZJQE2scYnrQ9" 52 | }, 53 | "outputs": [], 54 | "source": [ 55 | "# Set your Notion API key\n", 56 | "api_key = \"your_secret_key_here\"\n", 57 | "# Set the database ID\n", 58 | "database_id = 'database_id_here'" 59 | ] 60 | }, 61 | { 62 | "attachments": {}, 63 | "cell_type": "markdown", 64 | "id": "MWPjKS0Rn2Oc", 65 | "metadata": { 66 | "id": "MWPjKS0Rn2Oc" 67 | }, 68 | "source": [ 69 | "## paste the link here everytime you run" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "id": "0eOkeTLFNJkI", 76 | "metadata": { 77 | "colab": { 78 | "base_uri": "https://localhost:8080/" 79 | }, 80 | "id": "0eOkeTLFNJkI", 81 | "outputId": "c571fd26-aba6-4930-be9b-1885f0df2cda" 82 | }, 83 | "outputs": [ 84 | { 85 | "name": "stdout", 86 | "output_type": "stream", 87 | "text": [ 88 | "https://leetcode.com/problems/rotate-image/description/\n" 89 | ] 90 | } 91 | ], 92 | "source": [ 93 | "leetcode_url = input()\n" 94 | ] 95 | }, 96 | { 97 | "attachments": {}, 98 | "cell_type": "markdown", 99 | "id": "PVVojK1jn97r", 100 | "metadata": { 101 | "id": "PVVojK1jn97r" 102 | }, 103 | "source": [ 104 | "--------------------------------------------\n", 105 | "# You don't have to modifiy anything below\n", 106 | "--------------------------------------------" 107 | ] 108 | }, 109 | { 110 | "attachments": {}, 111 | "cell_type": "markdown", 112 | "id": "lNN25_28r6Ny", 113 | "metadata": { 114 | "id": "lNN25_28r6Ny" 115 | }, 116 | "source": [ 117 | "## extracting leetcode info" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "id": "96a147e8-745c-4868-8bbd-0507fe07353e", 124 | "metadata": { 125 | "id": "96a147e8-745c-4868-8bbd-0507fe07353e" 126 | }, 127 | "outputs": [], 128 | "source": [ 129 | "def remove_continuous_whitespace(text):\n", 130 | " clean_text = re.sub(r'\\n\\s*\\n+', '\\n', text)\n", 131 | " return clean_text\n", 132 | "def remove_html_tags(text):\n", 133 | " clean_text = re.sub('<.*?>', '', text)\n", 134 | " return clean_text\n", 135 | "def extract_leetcode_info(url):\n", 136 | " # Send a GET request to the provided URL\n", 137 | " response = requests.get(url)\n", 138 | "\n", 139 | " # Create a BeautifulSoup object to parse the HTML content\n", 140 | " soup = BeautifulSoup(response.content, 'html.parser')\n", 141 | " # print(soup.prettify())\n", 142 | " props_json = json.loads(soup.select(\"#__NEXT_DATA__\")[0].text)\n", 143 | " premium = False\n", 144 | " for query in props_json[\"props\"][\"pageProps\"][\"dehydratedState\"][\"queries\"]:\n", 145 | " data = query[\"state\"][\"data\"]\n", 146 | " # print(data)\n", 147 | " if \"question\" in data:\n", 148 | " if \"isPaidOnly\" in data[\"question\"]:\n", 149 | " if data[\"question\"][\"isPaidOnly\"]:\n", 150 | " premium = True\n", 151 | " if \"content\" in data[\"question\"]:\n", 152 | " problem_description = remove_continuous_whitespace(\n", 153 | " remove_html_tags(data[\"question\"][\"content\"]))\n", 154 | " if \"questionFrontendId\" in data[\"question\"]:\n", 155 | " problem_number = data[\"question\"][\"questionFrontendId\"]\n", 156 | " if \"title\" in data[\"question\"]:\n", 157 | " problem_name = data[\"question\"][\"title\"]\n", 158 | "\n", 159 | " if \"difficulty\" in data[\"question\"]:\n", 160 | " problem_difficulty = data[\"question\"][\"difficulty\"]\n", 161 | " if premium:\n", 162 | " print(\"premium problem, cannot obtain problem description\")\n", 163 | " problem_description = \"\"\n", 164 | " else:\n", 165 | " # Filter out paragraphs after the paragraph containing \"
\"\n", 166 | " filtered_paragraphs = []\n", 167 | " for paragraph in problem_description.split('\\n'):\n", 168 | " if paragraph == \" \":\n", 169 | " break\n", 170 | " filtered_paragraphs.append(paragraph)\n", 171 | "\n", 172 | " # # Join the filtered paragraphs into a single string\n", 173 | " problem_description = \"\\n\".join(filtered_paragraphs).strip()\n", 174 | "\n", 175 | " # Return the extracted information as a dictionary\n", 176 | " return {\n", 177 | " 'name': problem_name,\n", 178 | " 'number': problem_number,\n", 179 | " 'description': problem_description,\n", 180 | " 'difficulty': problem_difficulty.lower(),\n", 181 | " }" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "id": "b34303b6-97b0-4dbb-a123-0a8820b259ae", 188 | "metadata": { 189 | "id": "b34303b6-97b0-4dbb-a123-0a8820b259ae" 190 | }, 191 | "outputs": [], 192 | "source": [ 193 | "# Example usage\n", 194 | "# leetcode_url = 'https://leetcode.com/problems/move-zeroes/description/'\n", 195 | "info = extract_leetcode_info(leetcode_url)\n", 196 | "problem_name = info['name']\n", 197 | "problem_number = info['number']\n", 198 | "problem_description = info['description']\n", 199 | "problem_difficulty = info['difficulty']\n", 200 | "\n", 201 | "print('Problem Name:', problem_name)\n", 202 | "print('Problem Number:', problem_number)\n", 203 | "print('Problem Description:', problem_description)\n", 204 | "print('Problem Difficulty:', problem_difficulty)" 205 | ] 206 | }, 207 | { 208 | "attachments": {}, 209 | "cell_type": "markdown", 210 | "id": "OmOMhwxZsBjF", 211 | "metadata": { 212 | "id": "OmOMhwxZsBjF" 213 | }, 214 | "source": [ 215 | "## Notion API" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": null, 221 | "id": "0f8bb868-41bc-4ee5-b760-9526b608d95c", 222 | "metadata": { 223 | "id": "0f8bb868-41bc-4ee5-b760-9526b608d95c" 224 | }, 225 | "outputs": [], 226 | "source": [ 227 | "\n", 228 | "# Set the Notion API endpoint for retrieving a database\n", 229 | "endpoint_database = f'https://api.notion.com/v1/databases/{database_id}'\n", 230 | "endpoint_pages = f'https://api.notion.com/v1/pages'\n", 231 | "endpoint_query = f'https://api.notion.com/v1/databases/{database_id}/query'\n", 232 | "# Set the headers with the API key and content type\n", 233 | "headers = {\n", 234 | " 'Authorization': f'Bearer {api_key}',\n", 235 | " 'Content-Type': 'application/json',\n", 236 | " 'Notion-Version': '2022-06-28'\n", 237 | "}" 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": null, 243 | "id": "1hMAbBkgq1hk", 244 | "metadata": { 245 | "id": "1hMAbBkgq1hk" 246 | }, 247 | "outputs": [], 248 | "source": [ 249 | "exist = False\n", 250 | "payload = {\n", 251 | " 'filter': {\n", 252 | " 'property': 'Problem',\n", 253 | " 'number': {\n", 254 | " 'equals': int(problem_number)\n", 255 | " }\n", 256 | " }\n", 257 | "}\n", 258 | "response = requests.post(endpoint_query.format(database_id=database_id), headers=headers, data=json.dumps(payload))\n", 259 | "# Check the response status\n", 260 | "if response.status_code == 200:\n", 261 | " # Get the response JSON data\n", 262 | " data = response.json()\n", 263 | "\n", 264 | " # Check if any results were found\n", 265 | " if data.get('results'):\n", 266 | " # A page with the specified problem number exists\n", 267 | " print('A page with the problem number exists.')\n", 268 | " exist = True\n", 269 | " else:\n", 270 | " # No page with the specified problem number exists\n", 271 | " print('No page with the problem number exists.')\n", 272 | "else:\n", 273 | " # Query request failed\n", 274 | " print('Failed to query pages:', response.json())" 275 | ] 276 | }, 277 | { 278 | "cell_type": "code", 279 | "execution_count": null, 280 | "id": "818bc4ae-8a07-4e4b-bd6b-fff05a27ff5d", 281 | "metadata": { 282 | "id": "818bc4ae-8a07-4e4b-bd6b-fff05a27ff5d" 283 | }, 284 | "outputs": [], 285 | "source": [ 286 | "\n", 287 | "option_id = ''\n", 288 | "color = ''\n", 289 | "if problem_difficulty == 'Easy':\n", 290 | " option_id = 'iui['\n", 291 | " color = 'green'\n", 292 | "elif problem_difficulty == 'Medium':\n", 293 | " option_id = 'Wk\\t'\n", 294 | " color = 'purple'\n", 295 | "elif problem_difficulty == 'Hard':\n", 296 | " option_id = 'D=mB'\n", 297 | " color = 'red'\n", 298 | "# Set the properties of the new record\n", 299 | "new_record = {\n", 300 | " 'parent': {'database_id': database_id},\n", 301 | " 'properties': {\n", 302 | " 'Problem': {'number': int(problem_number)},\n", 303 | " 'Name': {'title': [{'text': {'content': problem_name}}]},\n", 304 | " 'Link': {'url': leetcode_url},\n", 305 | " 'Difficulty': {\n", 306 | " 'select' : {\n", 307 | " 'name': problem_difficulty\n", 308 | " }\n", 309 | " }\n", 310 | " }\n", 311 | "}\n", 312 | "\n", 313 | "\n", 314 | "if not exist:\n", 315 | " # Send a POST request to create the new record\n", 316 | " response = requests.post(endpoint_pages, json=new_record, headers=headers)\n", 317 | "\n", 318 | " # Get the JSON response\n", 319 | " json_response = response.json()\n", 320 | "\n", 321 | " # Print the response\n", 322 | " # print(json_response)\n", 323 | " block_id = json_response['id']\n", 324 | "else:\n", 325 | " print('A page with the problem number exists.')" 326 | ] 327 | }, 328 | { 329 | "cell_type": "code", 330 | "execution_count": null, 331 | "id": "3d4fd32c-5b78-4fe5-a762-f3b36f30507e", 332 | "metadata": { 333 | "id": "3d4fd32c-5b78-4fe5-a762-f3b36f30507e" 334 | }, 335 | "outputs": [], 336 | "source": [ 337 | "def heading_2(title):\n", 338 | " return {\n", 339 | " 'object': 'block',\n", 340 | " 'type': 'heading_2',\n", 341 | " 'heading_2': {\n", 342 | " 'rich_text': [\n", 343 | " {\n", 344 | " 'type': 'text',\n", 345 | " 'text': {\n", 346 | " 'content': title,\n", 347 | " }\n", 348 | " }\n", 349 | " ]\n", 350 | " }\n", 351 | " }\n", 352 | "def paragraph(content):\n", 353 | " return {\n", 354 | " 'object': 'block',\n", 355 | " 'type': 'paragraph',\n", 356 | " # 'paragraph': {'rich_text': [{'type': 'text', 'text': {'content': 'Implement a Queue by linked list. Support the following basic methods:', 'link': None},\n", 357 | " 'paragraph': {\n", 358 | " 'rich_text': [\n", 359 | " {\n", 360 | " 'type': 'text',\n", 361 | " 'text': {\n", 362 | " 'content': content,\n", 363 | " }\n", 364 | " }\n", 365 | " ]\n", 366 | " }\n", 367 | " }\n", 368 | "\n", 369 | "\n", 370 | "def code():\n", 371 | " return {\n", 372 | " 'object': 'block',\n", 373 | " 'type': 'code',\n", 374 | " # 'paragraph': {'rich_text': [{'type': 'text', 'text': {'content': 'Implement a Queue by linked list. Support the following basic methods:', 'link': None},\n", 375 | " 'code': {\n", 376 | " 'caption': [\n", 377 | " {\n", 378 | " 'type': 'text',\n", 379 | " 'text': {\n", 380 | " 'content': 'O(N) time complexity',\n", 381 | " }\n", 382 | " }\n", 383 | " ],\n", 384 | " 'rich_text': [\n", 385 | " {\n", 386 | " 'type': 'text',\n", 387 | " 'text': {\n", 388 | " 'content': ''\n", 389 | " }\n", 390 | " }\n", 391 | " ],\n", 392 | " 'language': 'python'\n", 393 | " }\n", 394 | " }\n", 395 | "\n", 396 | "\n" 397 | ] 398 | }, 399 | { 400 | "cell_type": "code", 401 | "execution_count": null, 402 | "id": "7c398beb-01cf-48ea-9edc-251d9357c98e", 403 | "metadata": { 404 | "id": "7c398beb-01cf-48ea-9edc-251d9357c98e" 405 | }, 406 | "outputs": [], 407 | "source": [ 408 | "block_content = [\n", 409 | "\n", 410 | " heading_2(\"Problem\"),\n", 411 | " paragraph(problem_description),\n", 412 | " heading_2(\"Discussion\"),\n", 413 | " # paragraph(\"like in interview\"),\n", 414 | " heading_2(\"Solution\"),\n", 415 | " # paragraph(\"your solution here\"),\n", 416 | " heading_2(\"Clarification & Difficulties\"),\n", 417 | " # paragraph(\" \"),\n", 418 | " heading_2(\"Code\"),\n", 419 | " code()\n", 420 | "\n", 421 | "\n", 422 | " # Add more blocks as needed\n", 423 | "]\n", 424 | "\n", 425 | "payload = {\n", 426 | " 'children': block_content\n", 427 | "}\n", 428 | "\n", 429 | "if not exist:\n", 430 | " endpoint_page_children = f'https://api.notion.com/v1/blocks/{block_id}/children'\n", 431 | " response = requests.patch(endpoint_page_children, json=payload, headers=headers)\n", 432 | "\n", 433 | " # Get the JSON response\n", 434 | " json_response = response.json()\n", 435 | "\n", 436 | " # Print the response\n", 437 | " # print(json_response)\n", 438 | "# else:\n", 439 | "# print('A page with the problem number exists.')" 440 | ] 441 | }, 442 | { 443 | "attachments": {}, 444 | "cell_type": "markdown", 445 | "id": "yJp2IL-MsXY3", 446 | "metadata": { 447 | "id": "yJp2IL-MsXY3" 448 | }, 449 | "source": [ 450 | "## result" 451 | ] 452 | }, 453 | { 454 | "cell_type": "code", 455 | "execution_count": null, 456 | "id": "c800ab1f-d048-40f1-bf17-40c4e0c0ab4d", 457 | "metadata": { 458 | "id": "c800ab1f-d048-40f1-bf17-40c4e0c0ab4d" 459 | }, 460 | "outputs": [], 461 | "source": [ 462 | "if not exist:\n", 463 | " # Print the response\n", 464 | " print(json_response)\n", 465 | "else:\n", 466 | " print('A page with the problem number exists.')" 467 | ] 468 | } 469 | ], 470 | "metadata": { 471 | "colab": { 472 | "collapsed_sections": [ 473 | "lNN25_28r6Ny", 474 | "OmOMhwxZsBjF" 475 | ], 476 | "provenance": [] 477 | }, 478 | "kernelspec": { 479 | "display_name": "Python 3 (ipykernel)", 480 | "language": "python", 481 | "name": "python3" 482 | }, 483 | "language_info": { 484 | "codemirror_mode": { 485 | "name": "ipython", 486 | "version": 3 487 | }, 488 | "file_extension": ".py", 489 | "mimetype": "text/x-python", 490 | "name": "python", 491 | "nbconvert_exporter": "python", 492 | "pygments_lexer": "ipython3", 493 | "version": "3.8.10" 494 | } 495 | }, 496 | "nbformat": 4, 497 | "nbformat_minor": 5 498 | } 499 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | beautifulsoup4 3 | notion 4 | --------------------------------------------------------------------------------