├── .gitignore ├── LICENSE.md ├── README.md ├── pocket2notion.py ├── requirements.txt └── settings.py /.gitignore: -------------------------------------------------------------------------------- 1 | config.py 2 | .env 3 | *__pycache__ 4 | *.html -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jeffrey Jacob 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

PickPocket

5 |

6 | A python script to transfer all your Pocket links to a database in Notion. 7 |
8 | Explore the docs 9 | · 10 | File issues and feature requests here 11 |

12 |

13 | 14 | 15 | 16 | 17 | ## Table of Contents 18 | 19 | - [Table of Contents](#table-of-contents) 20 | - [About The Project](#about-the-project) 21 | - [Getting Started](#getting-started) 22 | - [Prerequisites](#prerequisites) 23 | - [Setup & Installation](#setup--installation) 24 | - [Usage](#usage) 25 | - [Roadmap](#roadmap) 26 | - [Contributing](#contributing) 27 | - [License](#license) 28 | - [Contact](#contact) 29 | - [Acknowledgements](#acknowledgements) 30 | 31 | 32 | 33 | 34 | ## About The Project 35 | 36 | A python script to copy all your Pocket saves to a database in Notion. Current iteration is based off the HTML export option provided by the folks at Pocket. 37 | 38 | **Intended for** 39 | - Those who are looking to transfer their reading lists from Pocket to Notion. 40 | - Those who want to keep track and analyze what they read. 41 | 42 | 43 | 44 | ## Getting Started 45 | 46 | To get a local copy up and running follow these simple steps. 47 | 48 | ### Prerequisites 49 | 50 | * A Pocket account to retreive your saved content from. 51 | * A Notion account to store your links. 52 | * Python 3 on your system to run the code. 53 | 54 | ### Setup & Installation 55 | 56 | 1. Clone this repository. 57 | ```sh 58 | git clone https://github.com/paperboi/PickPocket.git 59 | ``` 60 | 2. Navigate to the directory and install the pre-requisite packages using 61 | ```sh 62 | pip install -r requirements.txt 63 | ``` 64 | 65 | 66 | ## Usage 67 | 1. Export your Pocket saves from [here](https://help.getpocket.com/article/1015-exporting-your-pocket-list). 68 | 2. Duplicate this [database template](https://www.notion.so/personaljeff/e4a0751a114842c6b2b238218e52e7d2?v=062127a6aa4341fb98e6d74b0eadfc4c) to your Notion workspace. 69 | 3. Since this code requires access of non-public pages, an authentication token from your Notion page is required. This token is stored in the `token_v2` cookie. This can be found in the *Storage* tab of your browser's developer tools. 70 | - For Chrome: Open Developer Tools (*Menu > Other tools > Developer Tools*), navigate to Application tab and go to *Storage\Cookies* to find the token. 71 | 4. Store the path to your HTML file, the address to your database and the `token_v2` value as `PATH_POCKET_FILE`, `NOTION_TABLE_ID` and `NOTION_TOKEN` respectively in a `.env` file in the same directory you have cloned this repository to. 72 | 5. To execute the script, navigate to the directory and run 73 | ```sh 74 | python pocket2notion.py 75 | ``` 76 | 77 | 78 | ## Roadmap 79 | 80 | See the [open issues](https://github.com/paperboi/PickPocket/issues) for a list of proposed features (and known issues). 81 | 82 | 83 | 84 | ## Contributing 85 | 86 | 87 | Any contributions you make are **greatly appreciated**. 88 | 89 | 1. Fork the Project 90 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 91 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 92 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 93 | 5. Open a Pull Request 94 | 95 | 96 | 97 | 98 | ## License 99 | 100 | Distributed under the MIT License. See [LICENSE](https://github.com/paperboi/PickPocket/blob/master/LICENSE.md) for more information. 101 | 102 | 103 | 104 | 105 | ## Contact 106 | 107 | Jeffrey Jacob - [@DullBlackWall](https://twitter.com/DullBlackWall) - jeffreysamjacob@gmail.com 108 | 109 | Project Link: [https://github.com/paperboi/PickPocket](https://github.com/paperboi/PickPocket) 110 | 111 | 112 | 113 | 114 | ## Acknowledgements 115 | 116 | * [K.P. Govind](https://github.com/reisub0) for clearing my doubts every step of the way. 117 | * [Jamie Alexandre](https://github.com/jamalex/) for the powerful [notion-py](https://github.com/jamalex/notion-py) API. 118 | -------------------------------------------------------------------------------- /pocket2notion.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from time import sleep 3 | from bs4 import BeautifulSoup 4 | from settings import PATH_POCKET_FILE, NOTION_TOKEN, NOTION_TABLE_ID 5 | 6 | from random import choice 7 | from uuid import uuid1 8 | 9 | from datetime import datetime 10 | from notion.client import NotionClient 11 | from notion.collection import NotionDate 12 | 13 | class PocketListItem: 14 | title = "" 15 | url = "" 16 | tags = [] 17 | addedOn = 0 18 | readStatus = None 19 | 20 | def __init__(self, title, url, tags, addedOn, readStatus): 21 | self.title = title 22 | self.url = url 23 | self.tags = tags 24 | self.addedOn = addedOn 25 | self.readStatus = readStatus 26 | if self.title == self.url: 27 | self._fetchTitleFromURL() 28 | self._addToNotion() 29 | 30 | def _fetchTitleFromURL(self): 31 | r = requests.get(self.url) 32 | sleep(1) 33 | soup = BeautifulSoup(r.content, 'lxml') 34 | self.title = soup.select_one('title').text 35 | 36 | def _addToNotion(self): 37 | row = cv.collection.add_row() 38 | row.title = self.title 39 | row.url = self.url 40 | self._setTag(row, 'prop', self.tags) 41 | row.added_on = NotionDate(datetime.fromtimestamp(self.addedOn)) 42 | row.read = self.readStatus 43 | # print(f"{index}/{len(allPocketListItems)} added") 44 | 45 | def _addNewTag(self, schema, prop, tag): 46 | dupe = next( 47 | (o for o in prop["options"] if o["value"] == tag), None 48 | ) 49 | if dupe: 50 | raise ValueError(f'{tag} already exists in the schema!') 51 | 52 | prop["options"].append( 53 | {"id": str(uuid1()), "value": tag, "color": choice(colors)} 54 | ) 55 | try: 56 | cv.collection.set("schema", schema) 57 | except (RecursionError, UnicodeEncodeError): 58 | pass 59 | 60 | def _setTag(self, page, prop, new_values): 61 | schema = cv.collection.get("schema") 62 | new_values_set = set(new_values) 63 | 64 | if new_values == ['']: 65 | return [] 66 | 67 | prop = next( 68 | (v for k, v in schema.items() if v["name"] == 'Tags'), None 69 | ) 70 | 71 | if "options" not in prop: prop["options"] = [] 72 | 73 | current_options_set = set( 74 | [o["value"] for o in prop["options"]] 75 | ) 76 | intersection = new_values_set.intersection(current_options_set) 77 | 78 | if len(new_values_set) > len(intersection): 79 | difference = [v for v in new_values_set if v not in intersection] 80 | for d in difference: 81 | self._addNewTag(schema, prop, d) 82 | page.set_property('Tags', new_values) 83 | 84 | def itemAlreadyExists(itemURL): 85 | global allRows 86 | if allRows == []: 87 | return False 88 | for eachRow in allRows: 89 | if itemURL == eachRow.url: 90 | return True 91 | print(f"Adding {itemURL} to the list") 92 | return False 93 | 94 | def retrieveAllPocketItems(): 95 | with open(PATH_POCKET_FILE, encoding='utf8', errors='ignore') as fp: 96 | soup = BeautifulSoup(fp,'html.parser') 97 | itemList = soup.h1.find_next("h1") 98 | 99 | articles = itemList.find_all_previous("a") 100 | for eachItem in articles: 101 | if itemAlreadyExists(eachItem['href']): 102 | continue 103 | url = eachItem['href'] 104 | title = eachItem.get_text() 105 | tags = eachItem['tags'].split(',') 106 | addedOn = int(eachItem['time_added']) 107 | readStatus = False 108 | PocketListItem(title,url,tags,addedOn,readStatus) 109 | 110 | # Retreiving the items from the user's Archive list next. 111 | articles = itemList.find_all_next("a") 112 | for eachItem in articles: 113 | if itemAlreadyExists(eachItem['href']): 114 | continue 115 | url = eachItem['href'] 116 | title = eachItem.get_text() 117 | tags = eachItem['tags'].split(',') 118 | addedOn = int(eachItem['time_added']) 119 | readStatus = True 120 | PocketListItem(title,url,tags,addedOn,readStatus) 121 | 122 | colors = ['default', 'gray', 'brown', 'orange', 'yellow', 'green', 'blue', 'purple', 'pink', 'red'] 123 | 124 | client = NotionClient(token_v2= NOTION_TOKEN) 125 | cv = client.get_collection_view(NOTION_TABLE_ID) 126 | allRows = cv.collection.get_rows() 127 | 128 | print(cv.parent.views) 129 | 130 | retrieveAllPocketItems() 131 | print("Transfer successfully completed") -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperboi/PickPocket/e3f39b135bffeaf376b962a7dff520fdcef0ae96/requirements.txt -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from decouple import config 3 | 4 | # Store these values in an .env file in this directory 5 | PATH_POCKET_FILE = config('PATH_POCKET_FILE') 6 | NOTION_TOKEN = config('NOTION_TOKEN') 7 | NOTION_TABLE_ID = config('NOTION_TABLE_ID') --------------------------------------------------------------------------------