Conversation
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
There was a problem hiding this comment.
Pull request overview
This PR significantly expands the available “all_tools” integrations by adding new tool modules for multiple Nextcloud apps (e.g., Shares, Polls, Photos, Passwords, Notes, News, Music, Forms, Bookmarks, Circles, Cookbook) and extending existing tool sets (Files, Deck, Calendar, Mail).
Changes:
- Add many new tool modules exposing Nextcloud app APIs via
@toolfunctions (safe/dangerous). - Extend existing tools for Files (WebDAV ops + tags), Deck (card operations), Calendar (task operations), and Mail (folder/message helpers).
- Introduce new per-app availability gating via
is_availablefor most new modules.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| ex_app/lib/all_tools/shares.py | New sharing + groups tools (list/create/update/delete shares, group info) |
| ex_app/lib/all_tools/polls.py | New Polls tools (list, details, create/options/votes, close/reopen) |
| ex_app/lib/all_tools/photos.py | New Photos tools (albums CRUD, album membership changes, date search) |
| ex_app/lib/all_tools/passwords.py | New Passwords tools (folders, search/list, CRUD incl. secret retrieval) |
| ex_app/lib/all_tools/notes.py | New Notes tools (list/get/create/update/delete + client-side search) |
| ex_app/lib/all_tools/news.py | New News/RSS tools (feeds/folders/items + mark read/unread) |
| ex_app/lib/all_tools/music.py | New Music tools (library browsing/search + playlist CRUD) |
| ex_app/lib/all_tools/forms.py | New Forms tools (list/details/create/questions/responses/settings) |
| ex_app/lib/all_tools/bookmarks.py | New Bookmarks tools (list/search + bookmark/folder/tag management) |
| ex_app/lib/all_tools/circles.py | New Circles/Teams tools (CRUD circles + membership + share to circle) |
| ex_app/lib/all_tools/cookbook.py | New Cookbook tools (recipes CRUD + categories + search) |
| ex_app/lib/all_tools/files.py | Adds WebDAV file ops + file tagging tools |
| ex_app/lib/all_tools/deck.py | Expands Deck tooling for card lifecycle, labels, assignments, comments |
| ex_app/lib/all_tools/calendar.py | Adds task list/complete/update/delete tools (CalDAV todos) |
| ex_app/lib/all_tools/mail.py | Adds folder listing, message listing/search, move/delete helpers |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| async def share_with_group(path: str, share_with: str, permissions: int = 19): | ||
| """ | ||
| Share a file or folder with a group | ||
| :param path: the path of the file or folder to share | ||
| :param share_with: the group name to share with | ||
| :param permissions: permissions bitmask - 1=read, 2=update, 4=create, 8=delete, 16=share. Default is 19 (read+update+delete+share) | ||
| :return: the created share |
There was a problem hiding this comment.
Same permissions doc issue as share_with_user: the docstring states permissions=19 includes delete, but it does not. Please align the default value and the explanation of the bitmask.
| :param is_required: whether the question is required | ||
| :param options: list of options for multiple choice, dropdown, etc. (required for multiple/dropdown types) | ||
| :return: the created question | ||
| """ |
There was a problem hiding this comment.
add_question_to_form's docstring states options is required for multiple-choice/dropdown question types, but the implementation doesn't validate this (it will create the question and silently skip option creation when options is None/empty). Consider validating inputs and raising a clear error when question_type requires options but none are provided, to avoid creating invalid/unusable questions.
| """ | |
| """ | |
| # Validate that options are provided for question types that require them | |
| if question_type in ['multiple', 'multiple_unique', 'dropdown'] and not options: | |
| raise ValueError( | |
| "Parameter 'options' must be a non-empty list when 'question_type' is one of " | |
| "'multiple', 'multiple_unique', or 'dropdown'." | |
| ) |
| response = await nc._session._create_adapter(True).request('GET', f"{nc.app_cfg.endpoint}/index.php/apps/passwords/api/1.0/password/list", headers={ | ||
| "Content-Type": "application/json", | ||
| }) | ||
| all_passwords = response.json() | ||
|
|
||
| # Filter passwords by search term in label or url | ||
| matching = [ | ||
| { | ||
| 'id': p.get('id'), | ||
| 'label': p.get('label'), | ||
| 'username': p.get('username'), | ||
| 'url': p.get('url'), | ||
| 'folder': p.get('folder'), | ||
| 'tags': p.get('tags', []) | ||
| } | ||
| for p in all_passwords | ||
| if search_term.lower() in p.get('label', '').lower() | ||
| or search_term.lower() in p.get('url', '').lower() | ||
| ] | ||
|
|
||
| return matching |
There was a problem hiding this comment.
search_passwords fetches the full password list from the server and then filters locally. This can become slow and memory-heavy for users with many entries. If the Passwords API supports server-side filtering/search or pagination, prefer that; otherwise consider adding an optional limit (and/or folder filter) and stop filtering after collecting enough matches.
| async def search_notes(search_term: str): | ||
| """ | ||
| Search for notes containing a specific term | ||
| :param search_term: the text to search for in note titles and content | ||
| :return: list of matching notes | ||
| """ | ||
| all_notes = await nc.ocs('GET', '/ocs/v2.php/apps/notes/api/v1/notes') | ||
| matching_notes = [ | ||
| note for note in all_notes | ||
| if search_term.lower() in note.get('title', '').lower() | ||
| or search_term.lower() in note.get('content', '').lower() | ||
| ] |
There was a problem hiding this comment.
search_notes loads all notes and filters client-side, which can be expensive for large note collections. If the Notes API provides a search endpoint or supports query parameters/pagination, prefer that; otherwise consider adding an optional limit and returning early once enough matches are collected.
| async def search_notes(search_term: str): | |
| """ | |
| Search for notes containing a specific term | |
| :param search_term: the text to search for in note titles and content | |
| :return: list of matching notes | |
| """ | |
| all_notes = await nc.ocs('GET', '/ocs/v2.php/apps/notes/api/v1/notes') | |
| matching_notes = [ | |
| note for note in all_notes | |
| if search_term.lower() in note.get('title', '').lower() | |
| or search_term.lower() in note.get('content', '').lower() | |
| ] | |
| async def search_notes(search_term: str, limit: Optional[int] = None): | |
| """ | |
| Search for notes containing a specific term | |
| :param search_term: the text to search for in note titles and content | |
| :param limit: optional maximum number of matching notes to return | |
| :return: list of matching notes | |
| """ | |
| all_notes = await nc.ocs('GET', '/ocs/v2.php/apps/notes/api/v1/notes') | |
| matching_notes = [] | |
| lowered_term = search_term.lower() | |
| for note in all_notes: | |
| title = note.get('title', '') | |
| content = note.get('content', '') | |
| if lowered_term in title.lower() or lowered_term in content.lower(): | |
| matching_notes.append(note) | |
| if limit is not None and len(matching_notes) >= limit: | |
| break |
| params = { | ||
| 'batchSize': limit, | ||
| 'type': 3, # 3 = all items | ||
| 'getRead': 'false' | ||
| } | ||
| if feed_id: | ||
| params['id'] = feed_id | ||
|
|
||
| response = await nc._session._create_adapter(True).request('GET', f"{nc.app_cfg.endpoint}/index.php/apps/news/api/v1-3/items", headers={ | ||
| "Content-Type": "application/json", | ||
| }, params=params) | ||
| return response.json() | ||
|
|
There was a problem hiding this comment.
get_unread_articles largely duplicates the logic in get_articles (same endpoint/params structure, only differing by unread filter). To reduce duplication and keep behavior consistent, consider removing get_unread_articles and using get_articles(unread_only=True) (or implement get_unread_articles as a thin wrapper around get_articles).
| params = { | |
| 'batchSize': limit, | |
| 'type': 3, # 3 = all items | |
| 'getRead': 'false' | |
| } | |
| if feed_id: | |
| params['id'] = feed_id | |
| response = await nc._session._create_adapter(True).request('GET', f"{nc.app_cfg.endpoint}/index.php/apps/news/api/v1-3/items", headers={ | |
| "Content-Type": "application/json", | |
| }, params=params) | |
| return response.json() | |
| return await get_articles(limit=limit, feed_id=feed_id, unread_only=True) |
| async def share_with_user(path: str, share_with: str, permissions: int = 19): | ||
| """ | ||
| Share a file or folder with a user | ||
| :param path: the path of the file or folder to share | ||
| :param share_with: the user id to share with | ||
| :param permissions: permissions bitmask - 1=read, 2=update, 4=create, 8=delete, 16=share. Default is 19 (read+update+delete+share) | ||
| :return: the created share |
There was a problem hiding this comment.
The docstring says default permissions=19 represents "read+update+delete+share", but 19 does not include the delete bit (8). Either update the default to include delete (e.g., 27) or fix the docstring to match the actual default permissions.
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
@janepie maybe we can share reviewing and testing these together?
Reviewed and tested works: