Notes API
reptor.api.NotesAPI.NotesAPI
API client for interacting with SysReptor project notes or personal notes.
Example:
from reptor import Reptor
reptor = Reptor(
server=os.environ.get("REPTOR_SERVER"),
token=os.environ.get("REPTOR_TOKEN"),
personal_notes=False,
)
# NotesAPI is available as reptor.api.notes, e.g.:
reptor.api.notes.get_notes()base_endpoint
personal_note
get_notes
def get_notes() -> List[Note]Gets list of all notes in the current context (project notes or personal notes).
Returns:
List of notes for this project or user
Example:
reptor.api.notes.get_notes()get_note
def get_note(id: Optional[str] = None, title: Optional[str] = None) -> Optional[Note]Gets a single note by ID or title.
Parameters:
id(str, default:None) – Note ID to retrieve (prioritized over title)title(str, default:None) – Note title to search for
Returns:
Note object if found, None otherwise
Example:
# Get note by ID
note = reptor.api.notes.get_note(id="983a7e95-b2d9-4d57-984e-08496264cce8")
# Get note by title
note = reptor.api.notes.get_note(title="My Note")create_note
def create_note(title = 'Note by reptor', text = None, parent: Optional[str] = None, order = None, checked = None, icon = None) -> NoteCreates a new note.
Parameters:
title(str, default:'Note by reptor') – Note title. Defaults to "Note by reptor".text(str, default:None) – Note content. Defaults to None.parent(str, default:None) – Parent note ID for nested notes. Defaults to None.order(int, default:None) – Sort order for the note. Defaults to None.checked(bool, default:None) – Checkbox state for checklist notes. Defaults to None.icon(str, default:None) – Emoji icon for the note. Defaults to None.
Returns:
Created note object
Example:
reptor.api.notes.create_note(
title="My New Note",
text="This is the content",
icon="📝"
)write_note
def write_note(id: str = None, title: str = None, text: str, checked: bool = None, icon_emoji: str = None, order: int = 0, timestamp: bool = False, overwrite: bool = False, kwargs = {}) -> NoteUpdates notes, appends text to a note (or replaces it).
When id is provided, the existing note with that ID is updated. If title is also provided, the note is renamed as part of the same update. When id is omitted, title is used to look up an existing note or create a new one.
Parameters:
id(str, default:None) – Note ID to updatetitle(str, default:None) – Note title for lookup or creation whenidis omitted. Whenidis provided, this becomes the note's new title.text(str, default:'') – Append text to the note. Defaults to empty string.timestamp(bool, default:False) – Prepend timestamp to newly inserted text. Defaults to False.overwrite(bool, default:False) – Replace the note's existing text withtextinstead of appending. Use for content that represents a current state rather than a running log, e.g. a checklist whose rows change. Defaults to False.checked(bool, default:None) – Checkbox state for checklist notes.icon_emoji(str, default:None) – Emoji icon for the note.order(int, default:0) – Sort order for the note. Defaults to 0.
Returns:
The uploaded note after a successful PUT.
Example:
reptor.api.notes.write_note(
title="Security Finding",
text="Found vulnerability in authentication",
timestamp=True
)
# Replace the note content instead of appending
reptor.api.notes.write_note(
title="Checklist",
text="- [x] Recon\n- [ ] Exploit",
overwrite=True,
)
# Update a note by ID and rename it in the same call
reptor.api.notes.write_note(
id="983a7e95-b2d9-4d57-984e-08496264cce8",
title="Updated Title",
text="Renamed while appending new content",
)write_note_templates
def write_note_templates(note_templates: Union[NoteTemplate, List[NoteTemplate]], timestamp: bool = True, overwrite: bool = False, kwargs = {}) -> Optional[Note]Writes note templates, appending to existing notes by default.
Parameters:
note_templates(Union[NoteTemplate, List[NoteTemplate]]) – A single note template or a list of them.timestamp(bool, default:True) – Prepend timestamp to newly inserted text. Defaults to True.overwrite(bool, default:False) – Replace the existing note text instead of appending to it. Applies to child notes as well. Defaults to False.
Returns:
The last root note uploaded in this call (children are written recursively and are not returned).
set_icon
def set_icon(note_id: str = None, icon: str = None, id: str = None)Sets an emoji icon for a note.
Parameters:
note_id(str, default:None) – Note IDicon(str, default:None) – Emoji character to set as iconid((str, deprecated), default:None) – Deprecated. Use note_id instead.
Example:
reptor.api.notes.set_icon(note_id="983a7e95-b2d9-4d57-984e-08496264cce8", icon="🔒")upload_file
def upload_file(file: Optional[IO] = None, content: Optional[bytes] = None, filename: Optional[str] = None, caption: Optional[str] = None, note_id: Optional[str] = None, note_title: Optional[str] = None, parent_title: Optional[str] = None, kwargs = {})Uploads a file and append to note.
Parameters:
file(IO, default:None) – File object to uploadcontent(bytes, default:None) – File content as bytesfilename(str, default:None) – Name for the uploaded filecaption(str, default:None) – Caption for the file linknote_id(str, default:None) – ID of note to upload tonote_title(str, default:None) – Title of note to upload toparent_title(str, default:None) – Parent note title for organization**kwargs(default:{}) – Additional parameters for note writing
Example:
# Upload from file
with open("screenshot.png", "rb") as f:
reptor.api.notes.upload_file(
file=f,
note_title="Evidence",
caption="Login page screenshot"
)
# Upload from bytes
reptor.api.notes.upload_file(
content=b"file content",
filename="data.txt",
note_title="Files"
)render
def render(note_id: str = None, id: str = None) -> bytesRenders a note to PDF format.
Parameters:
note_id(str, default:None) – Note ID to renderid((str, deprecated), default:None) – Deprecated. Use note_id instead.
Returns:
PDF content as bytes
Example:
pdf_data = reptor.api.notes.render(note_id="note-uuid-here")
with open("note.pdf", "wb") as f:
f.write(pdf_data)delete_note
def delete_note(id: str)Deletes a note by ID.
Parameters:
id(str) – Note ID to delete
Example:
reptor.api.notes.delete_note("983a7e95-b2d9-4d57-984e-08496264cce8")duplicate
def duplicate(note_id: str = None, id: str = None) -> NoteCreates a note duplicate.
Parameters:
note_id(str, default:None) – Note ID to duplicateid((str, deprecated), default:None) – Deprecated. Use note_id instead.
Returns:
Duplicated note object
Example:
duplicate_note = reptor.api.notes.duplicate(note_id="note-uuid-here")
print(f"Duplicated note: {duplicate_note.title}")get_note_by_title
def get_note_by_title(title, parent = None, parent_title = None, any_parent = False) -> Optional[Note]get_or_create_note_by_title
def get_or_create_note_by_title(title, parent = None, parent_title = None, icon = None) -> NoteSource
class NotesAPI(APIClient):
"""API client for interacting with SysReptor project notes or personal notes.
Example:
```python
from reptor import Reptor
reptor = Reptor(
server=os.environ.get("REPTOR_SERVER"),
token=os.environ.get("REPTOR_TOKEN"),
personal_notes=False,
)
# NotesAPI is available as reptor.api.notes, e.g.:
reptor.api.notes.get_notes()
```
"""
def __init__(self, **kwargs) -> None:
super().__init__(require_project_id=False, **kwargs)
if self.personal_note:
self.base_endpoint = urljoin(
self.reptor.get_config().get_server(),
"api/v1/pentestusers/self/notes/",
)
elif self.project_id:
self.base_endpoint = urljoin(
self.reptor.get_config().get_server(),
f"api/v1/pentestprojects/{self.project_id}/notes/",
)
@property
def personal_note(self):
return self.reptor.get_config().get("personal_note")
def get_notes(self) -> typing.List[Note]:
"""Gets list of all notes in the current context (project notes or personal notes).
Returns:
List of notes for this project or user
Example:
```python
reptor.api.notes.get_notes()
```
"""
response = self.get(self.base_endpoint)
notes = list()
for note_data in response.json():
notes.append(Note(note_data))
return notes
def get_note(
self,
id: typing.Optional[str] = None,
title: typing.Optional[str] = None,
) -> typing.Optional[Note]:
"""Gets a single note by ID or title.
Args:
id (str, optional): Note ID to retrieve (prioritized over title)
title (str, optional): Note title to search for
Returns:
Note object if found, None otherwise
Example:
```python
# Get note by ID
note = reptor.api.notes.get_note(id="983a7e95-b2d9-4d57-984e-08496264cce8")
# Get note by title
note = reptor.api.notes.get_note(title="My Note")
```
"""
for note in self.get_notes():
if id:
if note.id == id:
return note
elif title:
if note.title == title:
return note
else:
raise ValueError("Either id or title must be provided")
def create_note(
self,
title="Note by reptor",
text=None,
parent: typing.Optional[str] = None,
order=None,
checked=None,
icon=None,
) -> Note:
"""Creates a new note.
Args:
title (str, optional): Note title. Defaults to "Note by reptor".
text (str, optional): Note content. Defaults to None.
parent (str, optional): Parent note ID for nested notes. Defaults to None.
order (int, optional): Sort order for the note. Defaults to None.
checked (bool, optional): Checkbox state for checklist notes. Defaults to None.
icon (str, optional): Emoji icon for the note. Defaults to None.
Returns:
Created note object
Example:
```python
reptor.api.notes.create_note(
title="My New Note",
text="This is the content",
icon="📝"
)
```
"""
if title is None:
raise ValueError("Note title must not be null.")
note = self.post(
self.base_endpoint,
json={
"order": order,
"parent": parent or None,
"checked": checked,
"title": title,
"text": text or "",
},
).json()
if icon:
self.set_icon(note_id=note.get("id"), icon=icon)
elif title == "Uploads":
self.set_icon(note_id=note.get("id"), icon="📤")
return Note(note)
def write_note(
self,
id: str = None,
title: str = None,
text: str = "",
checked: bool = None,
icon_emoji: str = None,
order: int = 0,
timestamp: bool = False,
overwrite: bool = False,
**kwargs,
) -> Note:
"""Updates notes, appends text to a note (or replaces it).
When ``id`` is provided, the existing note with that ID is updated. If
``title`` is also provided, the note is renamed as part of the same
update. When ``id`` is omitted, ``title`` is used to look up an
existing note or create a new one.
Args:
id (str, optional): Note ID to update
title (str, optional): Note title for lookup or creation when ``id`` is
omitted. When ``id`` is provided, this becomes the note's new title.
text (str, optional): Append text to the note. Defaults to empty string.
timestamp (bool, optional): Prepend timestamp to newly inserted text. Defaults to False.
overwrite (bool, optional): Replace the note's existing text with `text`
instead of appending. Use for content that represents a current state
rather than a running log, e.g. a checklist whose rows change. Defaults to False.
checked (bool, optional): Checkbox state for checklist notes.
icon_emoji (str, optional): Emoji icon for the note.
order (int, optional): Sort order for the note. Defaults to 0.
Returns:
The uploaded note after a successful PUT.
Example:
```python
reptor.api.notes.write_note(
title="Security Finding",
text="Found vulnerability in authentication",
timestamp=True
)
# Replace the note content instead of appending
reptor.api.notes.write_note(
title="Checklist",
text="- [x] Recon\\n- [ ] Exploit",
overwrite=True,
)
# Update a note by ID and rename it in the same call
reptor.api.notes.write_note(
id="983a7e95-b2d9-4d57-984e-08496264cce8",
title="Updated Title",
text="Renamed while appending new content",
)
```
"""
note_template = NoteTemplate.from_kwargs(
id=id,
title=title,
text=text,
checked=checked,
icon_emoji=icon_emoji,
order=order,
**kwargs
)
return self.write_note_templates(
note_template, timestamp=timestamp, overwrite=overwrite
)
def write_note_templates(
self,
note_templates: typing.Union[NoteTemplate, typing.List[NoteTemplate]],
timestamp: bool = True,
overwrite: bool = False,
**kwargs,
) -> typing.Optional[Note]:
"""Writes note templates, appending to existing notes by default.
Args:
note_templates: A single note template or a list of them.
timestamp (bool, optional): Prepend timestamp to newly inserted text. Defaults to True.
overwrite (bool, optional): Replace the existing note text instead of
appending to it. Applies to child notes as well. Defaults to False.
Returns:
The last root note uploaded in this call (children are written
recursively and are not returned).
"""
if not isinstance(note_templates, list):
note_templates = [note_templates]
written = None
for note_template in note_templates:
if note_template.id:
new_note = False
note = self.get_note(id=note_template.id)
if not note:
raise ValueError(f'Note with ID "{note_template.id}" does not exist.')
else:
if note_template.parent_title and not note_template.parent:
note_template.parent = self.get_or_create_note_by_title(
note_template.parent_title
).id
note = None
if not note_template.force_new:
note = self.get_note_by_title(
note_template.title,
parent=note_template.parent,
)
if note is None:
new_note = True
note = self.create_note(
title=note_template.title,
parent=note_template.parent or None,
)
else:
new_note = False
self.debug(f"Got note from server {note.title} ({note.id})")
# Prepare note (append content to existing note, or replace it)
note_text = "" if overwrite else (note.text + "\n\n" if note.text else "")
if (
timestamp and note_template.text
): # Only add timestamp if there is content
note_text += f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]"
if "\n" in note_template.text:
if not note_template.text.startswith("\n"):
note_text += "\n"
else:
note_text += ": "
note_text += note_template.text or ""
if new_note:
upload_note = Note.from_note_template(note_template)
upload_note.parent = note.parent
upload_note.id = note.id
upload_note.text = note_text
else:
upload_note = note
if note_template.title:
upload_note.title = note_template.title
if note_template.checked is not None:
upload_note.checked = note_template.checked
if note_template.icon_emoji:
upload_note.icon_emoji = note_template.icon_emoji
if note_template.order:
upload_note.order = note_template.order
upload_note.text = note_text
# Upload note and children recursively
self._upload_note(upload_note, **kwargs)
written = upload_note
for child in note_template.children:
child.parent = note.id
self.write_note_templates(
child, timestamp=timestamp, overwrite=overwrite, **kwargs
)
return written
def set_icon(self, note_id: str = None, icon: str = None, id: str = None):
"""Sets an emoji icon for a note.
Args:
note_id (str): Note ID
icon (str): Emoji character to set as icon
id (str, deprecated): Deprecated. Use note_id instead.
Example:
```python
reptor.api.notes.set_icon(note_id="983a7e95-b2d9-4d57-984e-08496264cce8", icon="🔒")
```
"""
# Handle deprecated 'id' parameter
if id is not None:
self.log.warning(
"The 'id' parameter is deprecated and will be removed in a future version. "
"Use 'note_id' instead."
)
if note_id is None:
note_id = id
if note_id is None:
raise ValueError("note_id parameter is required")
url = urljoin(self.base_endpoint, f"{note_id}/")
self.put(url, json={"icon_emoji": icon})
def upload_file(
self,
file: typing.Optional[typing.IO] = None,
content: typing.Optional[bytes] = None,
filename: typing.Optional[str] = None,
caption: typing.Optional[str] = None,
note_id: typing.Optional[str] = None,
note_title: typing.Optional[str] = None,
parent_title: typing.Optional[str] = None,
**kwargs,
):
"""Uploads a file and append to note.
Args:
file (typing.IO, optional): File object to upload
content (bytes, optional): File content as bytes
filename (str, optional): Name for the uploaded file
caption (str, optional): Caption for the file link
note_id (str, optional): ID of note to upload to
note_title (str, optional): Title of note to upload to
parent_title (str, optional): Parent note title for organization
**kwargs: Additional parameters for note writing
Example:
```python
# Upload from file
with open("screenshot.png", "rb") as f:
reptor.api.notes.upload_file(
file=f,
note_title="Evidence",
caption="Login page screenshot"
)
# Upload from bytes
reptor.api.notes.upload_file(
content=b"file content",
filename="data.txt",
note_title="Files"
)
```
"""
# Upload the file
upload_result = self._upload_file(
file=file,
content=content,
filename=filename,
)
if upload_result is None:
return
filename = upload_result["filename"]
filepath = upload_result["filepath"]
response_json = upload_result["response"]
# Prepare note content
if not note_id:
note_id = self.get_or_create_note_by_title(
note_title or "Uploads", parent_title=parent_title
).id
is_image = True if response_json.get("resource_type") == "image" else False
if is_image:
markdown_link = f"\n"
else:
markdown_link = f"\n[{caption or filename}]({filepath})"
# Write to note
self.write_note(
id=note_id,
text=markdown_link,
**kwargs,
)
def _upload_file(
self,
file: typing.Optional[typing.IO] = None,
content: typing.Optional[bytes] = None,
filename: typing.Optional[str] = None,
) -> typing.Optional[dict[str, typing.Any]]:
"""File upload without adding a reference (e.g., in a note).
Note that unreferences files will be cleaned up by SysReptor in the course of regular cleanup tasks.
Args:
file (typing.IO, optional): File object to upload
content (bytes, optional): File content as bytes
filename (str, optional): Name for the uploaded file
Returns:
dict: Upload result with 'filename', 'filepath', and 'response' keys
"""
assert file or content
assert not (file and content)
if file:
if file.name == "<stdin>":
self.display("Reading from stdin...")
elif not filename:
filename = basename(file.name)
# TODO this might be streamed to not load entire file to memory
try:
content = file.buffer.read() # type: ignore
except AttributeError:
content = file.read()
if not filename:
filetype = guess_filetype(content) or "dat"
filename = f"data.{filetype}"
if not content:
self.warning(f"{file.name} is empty. Will not upload.")
return None
if self.personal_note:
url = urljoin(self.base_endpoint, "upload/")
else:
url = urljoin(self.base_endpoint.rsplit("/", 2)[0], "upload/")
response_json = self.post(
url, files={"file": (filename, content)}, json_content=False
).json()
# Parse file path based on resource type
is_image = True if response_json.get("resource_type") == "image" else False
if is_image:
filepath = f"/images/name/{response_json['name']}"
else:
filepath = f"/files/name/{response_json['name']}"
return {"filename": filename, "filepath": filepath, "response": response_json}
def render(
self,
note_id: str = None,
id: str = None,
) -> bytes:
"""Renders a note to PDF format.
Args:
note_id (str): Note ID to render
id (str, deprecated): Deprecated. Use note_id instead.
Returns:
PDF content as bytes
Example:
```python
pdf_data = reptor.api.notes.render(note_id="note-uuid-here")
with open("note.pdf", "wb") as f:
f.write(pdf_data)
```
"""
# Handle deprecated 'id' parameter
if id is not None:
self.log.warning(
"The 'id' parameter is deprecated and will be removed in a future version. "
"Use 'note_id' instead."
)
if note_id is None:
note_id = id
if note_id is None:
raise ValueError("note_id parameter is required")
url = urljoin(self.base_endpoint, f"{note_id}/export-pdf/")
response = self.post(url, timeout=self.reptor.get_config().get_api_timeout_long())
response.raise_for_status()
return response.content
def delete_note(self, id: str):
"""Deletes a note by ID.
Args:
id (str): Note ID to delete
Example:
```python
reptor.api.notes.delete_note("983a7e95-b2d9-4d57-984e-08496264cce8")
```
"""
url = urljoin(self.base_endpoint, f"{id}/")
self.delete(url)
def duplicate(
self,
note_id: str = None,
id: str = None,
) -> Note:
"""Creates a note duplicate.
Args:
note_id (str): Note ID to duplicate
id (str, deprecated): Deprecated. Use note_id instead.
Returns:
Duplicated note object
Example:
```python
duplicate_note = reptor.api.notes.duplicate(note_id="note-uuid-here")
print(f"Duplicated note: {duplicate_note.title}")
```
"""
# Handle deprecated 'id' parameter
if id is not None:
self.log.warning(
"The 'id' parameter is deprecated and will be removed in a future version. "
"Use 'note_id' instead."
)
if note_id is None:
note_id = id
if note_id is None:
raise ValueError("note_id parameter is required")
url = urljoin(self.base_endpoint, f"{note_id}/copy/")
response = self.post(url)
response.raise_for_status()
return Note(response.json())
def get_note_by_title(
self,
title,
parent=None, # Preferred over parent_title
parent_title=None,
any_parent=False,
) -> typing.Optional[Note]:
if not parent and parent_title:
try:
parent = self.get_note_by_title(parent_title, any_parent=True).id # type: ignore
except AttributeError:
raise ValueError(f'Parent note "{parent_title}" does not exist.')
notes_list = self.get_notes()
for note in notes_list:
if note.title == title and (note.parent == parent or any_parent):
break
else:
return None
return note
def get_or_create_note_by_title(
self,
title,
parent=None, # Preferred over parent_title
parent_title=None,
icon=None,
) -> Note:
if not parent and parent_title:
parent = self.get_or_create_note_by_title(
parent_title, icon=icon
).id
note = self.get_note_by_title(title, parent=parent)
if not note:
# Note does not exist. Create.
note = self.create_note(title=title, parent=parent, icon=icon)
return note
def _upload_note(
self,
note: Note,
):
url = urljoin(self.base_endpoint, note.id, "")
r = self.put(url, json=note.to_dict())
try:
r.raise_for_status()
self.display(f'Note written to "{note.title}".')
except HTTPError as e:
raise HTTPError(
f'{str(e)} Are you uploading binary content to note? (Try "file" subcommand)'
) from e