├── LICENSE ├── hypothesis_zotero.py ├── readme.md ├── screenshots ├── Screenshot_2018-08-16_21-18-20.png └── Screenshot_2018-08-16_23-13-28.png └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 John David Pressman 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 | -------------------------------------------------------------------------------- /hypothesis_zotero.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from tkinter import * 4 | import h_annot 5 | from pyzotero import zotero 6 | 7 | ZOTERO_API_UPDATE_LIMIT = 50 8 | 9 | def save_transfer_settings(settings_path): 10 | """Save the currently entered transfer settings from the application form to 11 | settings_path.""" 12 | try: 13 | with open(settings_path, "w") as outfile: 14 | settings = {"library_id": libraryid_w.get(), 15 | "zot_api_key":zot_api_key_w.get(), 16 | "hyp_username":hyp_username_w.get(), 17 | "hyp_api_key":hyp_api_key_w.get(), 18 | "num2grab":number_to_grab_w.get()} 19 | json.dump(settings, outfile) 20 | progress_indicator.set("Transfer settings saved!") 21 | return True 22 | except FileNotFoundError: 23 | try: 24 | os.mkdir(os.path.split(settings_path)[0]) 25 | save_transfer_settings(settings_path) 26 | except PermissionError: 27 | progress_indicator.set("No permission to save to home folder.") 28 | return False 29 | 30 | def load_transfer_settings(settings_path): 31 | """Load the settings used to transfer annotations from Hypothesis into Zotero 32 | into the application form.""" 33 | try: 34 | with open(settings_path) as infile: 35 | settings = json.load(infile) 36 | libraryid_w.insert(0, settings["library_id"]) 37 | zot_api_key_w.insert(0,settings["zot_api_key"]) 38 | hyp_username_w.insert(0,settings["hyp_username"]) 39 | hyp_api_key_w.insert(0,settings["hyp_api_key"]) 40 | number_to_grab_w.delete(0,END) 41 | number_to_grab_w.insert(0,settings["num2grab"]) 42 | return True 43 | except FileNotFoundError: 44 | return False 45 | 46 | 47 | def format_converted_note(annotation): 48 | """Format an annotation so that it translates properly into Zotero note markup.""" 49 | annotated_text = extract_exact(annotation) 50 | annotation_text = annotation["text"] 51 | return """

{}

52 |
53 |

{}

""".format(annotated_text, annotation_text) 54 | 55 | def extract_exact(annotation): 56 | try: 57 | annotation["target"][0]["selector"] 58 | except KeyError as e: 59 | print(annotation) 60 | return "" 61 | for selector in annotation["target"][0]["selector"]: 62 | try: 63 | return selector["exact"] 64 | except KeyError: 65 | continue 66 | return None 67 | 68 | def extract_note_tags(notes): 69 | tags = set() 70 | for note in notes: 71 | for tag in note['data']['tags']: 72 | tags.add(tag['tag']) 73 | return tags 74 | 75 | def grab(): 76 | grab_button.config(state=DISABLED) 77 | progress_indicator.set("In progress...") 78 | root.update() 79 | library_id = libraryid_w.get() 80 | zot_api_key = zot_api_key_w.get() 81 | hyp_username = hyp_username_w.get() 82 | hyp_api_key = hyp_api_key_w.get() 83 | 84 | zot = zotero.Zotero(library_id, 'user', zot_api_key) 85 | 86 | num2grab = number_to_grab_w.get() 87 | items = zot.top(limit=num2grab) 88 | progress_indicator.set("Zotero library downloaded") 89 | root.update() 90 | 91 | for entry_i in enumerate(items): 92 | progress_indicator.set("Processing notes...({} of {})".format(entry_i[0]+1,len(items))) 93 | root.update() 94 | entry = entry_i[1] 95 | entry_children = zot.children(entry['key']) 96 | notes = [note for note in entry_children if note['data']['itemType'] == 'note'] 97 | tags = extract_note_tags(notes) 98 | entry_annotations = json.loads(h_annot.api.search(hyp_api_key, 99 | url=entry['data']['url'], 100 | user=hyp_username))["rows"] 101 | note_imports = [] 102 | for annotation in entry_annotations: 103 | if annotation["id"] in tags: 104 | continue 105 | else: 106 | template = zot.item_template("note") 107 | template['tags'] = (annotation['tags'].copy() + 108 | [{"tag": annotation["id"], "type":1}] + 109 | [{"tag": "hyp-annotation", "type":1}]) 110 | template['note'] = format_converted_note(annotation) 111 | note_imports.append(template) 112 | #TODO: Fix this so it doesn't break if you have more than 50 annotations on a document 113 | zot.create_items(note_imports,entry["key"]) 114 | progress_indicator.set("Done!") 115 | grab_button.config(state=NORMAL) 116 | 117 | root = Tk() 118 | root.title("Zotero Hypothesis Importer") 119 | 120 | frame = Frame(root, width=100, height=100) 121 | frame.pack() 122 | 123 | # Add widgets to get auth info for API calls 124 | libraryid_label = Label(frame, text="Library ID:") 125 | libraryid_w = Entry(frame, width=25) 126 | zot_api_key_label = Label(frame, text="Zotero API Key:") 127 | zot_api_key_w = Entry(frame, width=25) 128 | hyp_username_label = Label(frame, text="Hypothesis Username:") 129 | hyp_username_w = Entry(frame, width=25) 130 | hyp_api_key_label = Label(frame, text="Hypothesis API Key:") 131 | hyp_api_key_w = Entry(frame, width=25) 132 | number_to_grab_label = Label(frame,text="Grab last N items:") 133 | number_to_grab_w = Entry(frame, width=25) 134 | number_to_grab_w.insert(0,"50") 135 | 136 | # Lay out widgets on application window 137 | 138 | libraryid_label.grid(row=1) 139 | libraryid_w.grid(row=1,column=1) 140 | 141 | zot_api_key_label.grid(row=2) 142 | zot_api_key_w.grid(row=2,column=1) 143 | 144 | hyp_username_label.grid(row=3) 145 | hyp_username_w.grid(row=3,column=1) 146 | 147 | hyp_api_key_label.grid(row=4) 148 | hyp_api_key_w.grid(row=4,column=1) 149 | 150 | number_to_grab_label.grid(row=5) 151 | number_to_grab_w.grid(row=5,column=1) 152 | 153 | grab_button = Button(frame, text="Grab", command=grab) 154 | grab_button.grid(row=6) 155 | 156 | # Button to save transfer settings 157 | save_button = Button(frame, text="Save Settings", 158 | command=lambda: save_transfer_settings( 159 | os.path.expanduser("~/.hypzot/transfer_settings.json") 160 | )) 161 | save_button.grid(row=6,column=1) 162 | 163 | # Add progress indicators 164 | progress_indicator = StringVar() 165 | progress_indicator.set("Waiting...") 166 | grab_zotero_library_label = Label(frame, text="Progress:") 167 | grab_zotero_library_i = Label(frame, textvariable=progress_indicator) 168 | 169 | grab_zotero_library_label.grid(row=7) 170 | grab_zotero_library_i.grid(row=7,column=1) 171 | 172 | load_transfer_settings(os.path.expanduser("~/.hypzot/transfer_settings.json")) 173 | 174 | root.mainloop() 175 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Zotero-Hypothesis Importer # 2 | 3 | ## Introduction ## 4 | 5 | This program scans through a [Zotero](https://zotero.com) library and checks 6 | each URL for [Hypothesis](https://hypothes.is) annotations. If annotations are 7 | found it imports them into the Zotero library as note objects with their 8 | associated tags. 9 | 10 | ## Installation ## 11 | 12 | ### Windows ### 13 | 14 | Coming soon. 15 | 16 | ### Linux ### 17 | 18 | At the command line, do the following: 19 | 20 | Navigate to the directory where you'd like to install the program: 21 | 22 | cd /home/my_username/my_software_folder/ 23 | 24 | Git clone this repository: 25 | 26 | git clone https://github.com/JD-P/hypothesis-zotero 27 | 28 | Change directory to the cloned repository: 29 | 30 | cd hypothesis-zotero 31 | 32 | Create a virtual environment for the programs dependencies to go: 33 | 34 | virtualenv --python=python3 venv 35 | 36 | Activate the virtualenvironment using the 'source' command: 37 | 38 | source venv/bin/activate 39 | 40 | Use setuptools to install the program dependencies: 41 | 42 | python3 setup.py install 43 | 44 | Run the program: 45 | 46 | python3 hypothesis_zotero.py 47 | 48 | Then move on to the "Use" sections for instructions on how to use the program. 49 | 50 | ## Use & Operation ## 51 | 52 | You need 4 pieces of information to run the program: 53 | 54 | ![Screenshot of the program in operation with the text fields blank except for number to grab, which is filled out by default with '50'](screenshots/Screenshot_2018-08-16_21-18-20.png) 55 | 56 | - The userID for your Zotero API usage, [available here](https://www.zotero.org/settings/keys). 57 | - A developer API key for Zotero, [you can make one here](https://www.zotero.org/settings/keys/new). 58 | - Your Hypothesis username, which should just be the username you use to access 59 | the service. 60 | - Your Hypothesis Developer API key, [available here](https://hypothes.is/account/developer). 61 | 62 | Once you have these four things you put them into the appropriate slots in the 63 | programs interface. You also need to specify how many items back in your personal 64 | library the program should search through, the default is fifty but it's possible 65 | to specify more. (Note: In version 0.1 this will actually crash the program, but 66 | in future versions it shouldn't.) 67 | 68 | After you've put all the information in, you should probably press 'save settings' 69 | so that the transfer settings will be saved to a file for your next session. This 70 | will save you time when you want to use the program again later. 71 | 72 | ![The program in operation, showing the (censored) fields filled out and the progress indicator in the bottom right corner of the screen.](screenshots/Screenshot_2018-08-16_23-13-28.png) 73 | 74 | Finally, press 'grab' and the transfer will begin. A progress indicator shows 75 | how far along the program is in your document set. Once it's finished the progress 76 | indicator will read 'done' and you can begin using your notes in Zotero. 77 | -------------------------------------------------------------------------------- /screenshots/Screenshot_2018-08-16_21-18-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JD-P/hypothesis-zotero/097f38b0284f48bfac6b66eed6515c6a68338c3e/screenshots/Screenshot_2018-08-16_21-18-20.png -------------------------------------------------------------------------------- /screenshots/Screenshot_2018-08-16_23-13-28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JD-P/hypothesis-zotero/097f38b0284f48bfac6b66eed6515c6a68338c3e/screenshots/Screenshot_2018-08-16_23-13-28.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name="hypothesis-zotero", 5 | version="0.1", 6 | install_requires=['pyzotero>=1.3.6','python-hypothesis'], 7 | #TODO: Change this to upstream dependency once upstream merges some kind of python3 compatibility 8 | dependency_links=['git+https://github.com/JD-P/python-hypothesis.git#egg=python-hypothesis'], 9 | author="John David Pressman", 10 | author_email="jd@jdpressman.com", 11 | description="This program scans through a Zotero library and checks each URL for Hypothesis annotations. If annotations are found it imports them into the Zotero library as note objects with their associated tags.", 12 | license="MIT", 13 | url="https://github.com/JD-P/hypothesis-zotero") 14 | 15 | --------------------------------------------------------------------------------