Skip to content

Commit

Permalink
Merge pull request #16 from e-alizadeh/master
Browse files Browse the repository at this point in the history
Add a simpler way to run the conversion
  • Loading branch information
e-alizadeh committed Jan 1, 2022
2 parents 3cb2d57 + 19bf53d commit 2e78a47
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 107 deletions.
44 changes: 40 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ The highlights are NOT saved in the PDF file unless you export the highlights in
If you annotate your files outside the new Zotero PDF reader, this library will not work with your PDF annotations as those are not retrievable from Zotero API.
In that case, you may want to use zotfile + mdnotes to extract the annotations and convert them into markdown files.


**_This library is for you if you annotate (highlight + note) using the Zotero's PDF reader (including the beta version in iOs)_**
**_This library is for you if you annotate (highlight + note) using the Zotero's PDF reader (including the Zotero iOS)_**

# Installation
You can install the library by running
Expand All @@ -19,13 +18,50 @@ pip install zotero2md
Note: If you do not have pip installed on your system, you can follow the instructions [here](https://pip.pypa.io/en/stable/installation/).

# Usage
Since we have to retrieve the notes from Zotero API, the minimum requirements are:
* **Zotero API key** [Required]: Create a new Zotero Key from [your Zotero settings](https://www.zotero.org/settings/key)
* **Zotero personal or group ID** [Required]:
* Your **personal library ID** (aka **userID**) can be found [here](https://www.zotero.org/settings/key) next to `Your userID for use in API calls is XXXXXX`.
* If you're using a **group library**, you can find the library ID by
1. Go to `https://www.zotero.org/groups/`
2. Click on the interested group.
3. You can find the library ID from the URL link that has format like *https://www.zotero.org/groups/<group_id>/group_name*. The number between `/groups/` and `/group_name` is the libarry ID.
* **Zotero library type** [Optional]: *"user"* (default) if using personal library and *"group"* if using group library.

Note that if you want to retrieve annotations and notes from a group, you should provide the group ID (`zotero_library_id=<group_id>`) and set the library type to group (`zotero_library_type="group"`).

## Approach 1 (Recommended)
After installing the library, open a Python terminal, and then execute the following:
```python
from zotero2md.zt2md import Zotero2Markdown

zt = Zotero2Markdown(
zotero_key="your_zotero_key",
zotero_library_id="your_zotero_id",
zotero_library_type="user", # "user" (default) or "group"
params_filepath="", # [Default values provided bellow] # The path to JSON file containing the custom parameters (See Section Custom Output Parameters).
include_annotations=True, # Default: True
include_notes=True, # Default: True
)
zt.run_all()
```
Just to make sure that all files are created, you can run `save_failed_items_to_txt()` to ensure that no file was
was failed to create. If a file or more failed to create, the filename (item title) and the corresponding Zotero
item key will be saved to a txt file.
```python
zt.save_failed_items_to_txt("failed_zotero_items.txt")
```

## Approach 2
For this approach, you need to download `output_to_md.py` script.
Run `python output_to_md.py -h` to get more information about all options.
```shell
python zotero2md/generate.py <zotero_key> <zotero_id>
python zotero2md/output_to_md.py <zotero_key> <zotero_id>
```

For instance, assuming zotero_key=abcd and zotero_id=1234, you can simply run the following:
```shell
python zotero2md/generate.py abcd 1234
python zotero2md/output_to_md.py abcd 1234
```


Expand Down
2 changes: 1 addition & 1 deletion zotero2md/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
ROOT_DIR: Path = Path(__file__).parent
TOP_DIR: Path = ROOT_DIR.parent

default_params = {
DEFAULT_PARAMS = {
"convertTagsToInternalLinks": True,
"doNotConvertFollowingTagsToLink": [],
"includeHighlightDate": True,
Expand Down
92 changes: 0 additions & 92 deletions zotero2md/generate.py

This file was deleted.

37 changes: 37 additions & 0 deletions zotero2md/output_to_md.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from argparse import ArgumentParser

from zotero2md.zt2md import Zotero2Markdown

if __name__ == "__main__":
parser = ArgumentParser(description="Generate Markdown files")
parser.add_argument(
"zotero_key", help="Zotero API key (visit https://www.zotero.org/settings/keys)"
)
parser.add_argument(
"zotero_user_id",
help="Zotero User ID (visit https://www.zotero.org/settings/keys)",
)
parser.add_argument(
"--library_type",
default="user",
help="Zotero Library type ('user': for personal library (default value), 'group': for a shared library)",
)
parser.add_argument(
"--config_filepath",
type=str,
help="Filepath to a .json file containing the path",
)

args = vars(parser.parse_args())

zt = Zotero2Markdown(
zotero_key=args["zotero_key"],
zotero_library_id=args["zotero_user_id"],
zotero_library_type=args["library_type"],
params_filepath=args.get("config_filepath", None),
include_annotations=True,
include_notes=True,
)

zt.run_all()
zt.save_failed_items_to_txt("failed_zotero_items.txt")
18 changes: 8 additions & 10 deletions zotero2md/zotero.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pyzotero.zotero_errors import ParamNotPassed, UnsupportedParams
from snakemd import Document, MDList, Paragraph

from zotero2md import default_params
from zotero2md import DEFAULT_PARAMS
from zotero2md.utils import sanitize_filename, sanitize_tag

_OUTPUT_DIR = Path("zotero_output")
Expand Down Expand Up @@ -81,7 +81,7 @@ def __init__(
self.parent_item_key = self.item_details["data"].get("parentItem", None)

# Load output configurations used for generating markdown files.
self.md_config = default_params
self.md_config = DEFAULT_PARAMS

if params_filepath:
with open(Path(params_filepath), "r") as f:
Expand Down Expand Up @@ -170,6 +170,7 @@ def __init__(
super().__init__(zotero_client, item_key, params_filepath)
self.item_annotations = item_annotations
self.item_notes = item_notes
self.failed_item: str = ""

self.doc = Document(
name="initial_filename"
Expand Down Expand Up @@ -277,7 +278,7 @@ def create_annotations_section(self, annotations: List) -> None:

self.doc.add_element(MDList(annots))

def generate_output(self) -> Union[None, Tuple[str, str]]:
def generate_output(self) -> None:
"""Generate the markdown file for a Zotero Item combining metadata, annotations
Returns
Expand All @@ -301,15 +302,12 @@ def generate_output(self) -> Union[None, Tuple[str, str]]:
with open(_OUTPUT_DIR.joinpath(output_filename), "w+") as f:
f.write(self.doc.render())
print(
f'File "{output_filename}" (item_key="{self.item_key}") was successfully created.'
f'File "{output_filename}" (item_key="{self.item_key}") was successfully created.\n'
)
return None
except:
print(
f'File "{output_filename}" (item_key="{self.item_key}") is failed to generate.\n'
f"SKIPPING..."
)
return output_filename, self.item_key
msg = f'File "{output_filename}" (item_key="{self.item_key}") is failed to generate.\n'
print(msg + "SKIPPING...\n")
self.failed_item = msg


def retrieve_all_annotations(zotero_client: Zotero) -> List[Dict]:
Expand Down
88 changes: 88 additions & 0 deletions zotero2md/zt2md.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from typing import Dict, List, Union

from zotero2md.utils import group_by_parent_item
from zotero2md.zotero import (
ItemAnnotationsAndNotes,
get_zotero_client,
retrieve_all_annotations,
retrieve_all_notes,
)


class Zotero2Markdown:
def __init__(
self,
zotero_key: str,
zotero_library_id: str,
zotero_library_type: str = "user",
params_filepath: str = None,
include_annotations: bool = True,
include_notes: bool = True,
):

self.zotero_client = get_zotero_client(
library_id=zotero_library_id,
library_type=zotero_library_type,
api_key=zotero_key,
)
self.config_filepath = params_filepath
self.include_annots = include_annotations
self.include_notes = include_notes
self.failed_items: List[str] = []

def run_all(self, params_filepath: Union[str, None] = None) -> None:
annots_grouped: Dict = {}
notes_grouped: Dict = {}
if self.include_annots:
annots_grouped = group_by_parent_item(
retrieve_all_annotations(self.zotero_client)
)
if self.include_notes:
notes_grouped = group_by_parent_item(retrieve_all_notes(self.zotero_client))

for i, item_key in enumerate(annots_grouped.keys()):
item = self.zotero_client.item(item_key)
parent_item_key = item["data"].get("parentItem", None)
print(f"File {i + 1} of {len(annots_grouped)} is under process ...")
zotero_item = ItemAnnotationsAndNotes(
zotero_client=self.zotero_client,
params_filepath=params_filepath,
item_annotations=annots_grouped[item_key],
item_notes=notes_grouped.get(parent_item_key, None),
item_key=item_key,
)
# del notes[parent_item_key]

if zotero_item.failed_item:
self.failed_items.append(zotero_item.failed_item)
else:
zotero_item.generate_output()

# for i, item_key in enumerate(notes.keys()):
# print(f"File {i + 1} of {len(highlights)} is under process ...")
# item = ItemAnnotationsAndNotes(
# zotero_client=zotero_client,
# params_filepath=params_filepath,
# item_annotations=None,
# item_notes=notes[item_key],
# item_key=item_key,
# )
# out = item.generate_output()
# if out:
# failed_files.append(out)
# print("\n")

def save_failed_items_to_txt(self, txt_filepath: str = ""):
if txt_filepath == "":
txt_filepath = "failed_zotero_items.txt"

if self.failed_items:
print(
f"\n {len(self.failed_items)} markdown files (with all their annotations and notes) failed to create."
)
with open(txt_filepath, "w") as f:
file_content = "\n".join(self.failed_items)
f.write(file_content)
print(f"List of all failed items are saved into {txt_filepath}")
else:
print("No failed item")

0 comments on commit 2e78a47

Please sign in to comment.