├── 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 | 
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 | 
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 |
--------------------------------------------------------------------------------