// PicoNotes Plugin for ShadowMan // API v2.0.0 — Consolidated tools (3 instead of 19) var config = shadowman.config.get(); var apiKey = config.api_key; var baseUrl = 'https://piconotes.eu'; shadowman.log.info('PicoNotes plugin loaded (API v2.0.0, consolidated)'); shadowman.log.info('API key configured: ' + (apiKey ? 'yes (length: ' + apiKey.length + ', prefix: ' + apiKey.substring(0, 8) + ')' : 'NO!')); // ── Helpers ────────────────────────────────────────────────────────────── function apiRequest(method, path, body, contentType) { // Authorization: PicoNotes API key format var headers = { 'Authorization': 'API ' + apiKey, 'Prefer': 'return=representation' }; // Content-Type for POST/PATCH requests if (method !== 'GET' && method !== 'DELETE') { if (contentType) { headers['Content-Type'] = contentType; } else { headers['Content-Type'] = 'application/json'; } } var url = baseUrl + path; var response; shadowman.log.debug('API Request: ' + method + ' ' + path); if (method === 'GET') { response = shadowman.http.get(url, { headers: headers }); } else if (method === 'POST') { if (contentType && contentType !== 'application/json') { response = shadowman.http.request(url, { method: 'POST', body: body, headers: headers }); } else { shadowman.log.debug('POST body: ' + JSON.stringify(body)); response = shadowman.http.request(url, { method: 'POST', body: JSON.stringify(body), headers: headers }); } } else if (method === 'PATCH') { response = shadowman.http.request(url, { method: 'PATCH', body: JSON.stringify(body), headers: headers }); } else if (method === 'DELETE') { response = shadowman.http.del(url, { headers: headers }); } shadowman.log.debug('API Response: ' + response.status + ' - ' + (response.body ? response.body : 'empty')); if (response.status >= 400) { var errorDetail = response.body; try { var errorJson = JSON.parse(response.body); if (errorJson.error) errorDetail = errorJson.error; if (errorJson.message) errorDetail = errorJson.message; if (errorJson.detail) errorDetail = errorJson.detail; } catch (e) {} shadowman.log.error('API Error ' + response.status + ': ' + errorDetail); return { error: 'API error ' + response.status, details: errorDetail, status: response.status }; } try { return response.json(); } catch (e) { return response.body || { success: true, status: response.status }; } } function getPageUserId(pageId) { shadowman.log.info('Getting user_id for page: ' + pageId); var result = apiRequest('GET', '/api/pages/' + pageId); if (result.error) { shadowman.log.error('Failed to get page: ' + result.error + ' - ' + result.details); return result; } // API v2.0.0: a válasz lehet { data: {...} }, { ... }, vagy [ {...} ] var page = result; if (result.data) { page = result.data; } else if (Array.isArray(result)) { page = result[0]; } shadowman.log.debug('Page data: ' + JSON.stringify(page)); if (!page) { shadowman.log.error('Page not found: ' + pageId); return { error: 'Page not found: ' + pageId }; } if (!page.user_id) { shadowman.log.error('Page missing user_id: ' + JSON.stringify(page)); return { error: 'Could not resolve user_id for page ' + pageId }; } shadowman.log.info('Resolved user_id: ' + page.user_id); return { user_id: page.user_id }; } // ── Tool 1: pages ───────────────────────────────────────────── shadowman.tools.register('pages', function(args) { var action = args.action; // ─── list ─── if (action === 'list') { var params = []; if (args.parent_id !== undefined) { if (args.parent_id === 'null' || args.parent_id === null) { params.push('parent_id=is.null'); } else { params.push('parent_id=eq.' + args.parent_id); } } params.push('order=' + (args.order || 'sort_order.asc')); params.push('select=id,title,icon,parent_id,sort_order,created_at,updated_at,user_id'); return apiRequest('GET', '/api/pages?' + params.join('&')); } // ─── get ─── if (action === 'get') { if (!args.page_id) return { error: 'page_id is required' }; return apiRequest('GET', '/api/pages/' + args.page_id); } // ─── create ─── if (action === 'create') { if (!args.title) return { error: 'title is required' }; // Fix: resolve user_id from list if not provided var userId = '0v2AJNrePA8VGUlL'; var page = { title: args.title, user_id: userId }; if (args.id) page.id = args.id; if (args.icon) page.icon = args.icon; if (args.parent_id !== undefined) page.parent_id = args.parent_id; if (args.sort_order !== undefined) page.sort_order = args.sort_order; if (args.is_template !== undefined) page.is_template = args.is_template; shadowman.log.info('Creating page with user_id: ' + JSON.stringify(page)); return apiRequest('POST', '/api/pages', page); } // ─── update ─── if (action === 'update') { if (!args.page_id) return { error: 'page_id is required' }; var updates = {}; if (args.title !== undefined) updates.title = args.title; if (args.icon !== undefined) updates.icon = args.icon; if (args.parent_id !== undefined) updates.parent_id = args.parent_id; if (args.sort_order !== undefined) updates.sort_order = args.sort_order; return apiRequest('PATCH', '/api/pages/' + args.page_id, updates); } // ─── delete ─── if (action === 'delete') { if (!args.page_id) return { error: 'page_id is required' }; return apiRequest('DELETE', '/api/pages/' + args.page_id); } // ─── get_content ─── if (action === 'get_content') { if (!args.page_id) return { error: 'page_id is required' }; shadowman.log.info('get_content called for page: ' + args.page_id); var u = getPageUserId(args.page_id); if (u.error) return u; var storageUrl = '/api/storage/pages/' + u.user_id + '/' + args.page_id + '.md'; shadowman.log.info('Fetching content from: ' + storageUrl); var resp = shadowman.http.get(baseUrl + storageUrl, { headers: { 'Authorization': 'API ' + apiKey } }); shadowman.log.debug('Storage response: ' + resp.status); if (resp.status >= 400) { var errDetail = resp.body; try { var errJson = JSON.parse(resp.body); if (errJson.error) errDetail = errJson.error; if (errJson.message) errDetail = errJson.message; } catch (e) {} shadowman.log.error('Failed to get content: ' + resp.status + ' - ' + errDetail); return { error: 'Failed to get content: ' + resp.status, details: errDetail, status: resp.status }; } return { page_id: args.page_id, content: resp.body }; } // ─── update_content ─── if (action === 'update_content') { if (!args.page_id) return { error: 'page_id is required' }; if (!args.content && args.content !== '') return { error: 'content is required' }; shadowman.log.info('update_content called for page: ' + args.page_id); var u2 = getPageUserId(args.page_id); if (u2.error) return u2; var storageUrl2 = baseUrl + '/api/storage/pages/' + u2.user_id + '/' + args.page_id + '.md?upsert=true'; shadowman.log.info('Updating content at: ' + storageUrl2); var resp2 = shadowman.http.request(storageUrl2, { method: 'POST', headers: { 'Authorization': 'API ' + apiKey, 'Content-Type': 'text/markdown' }, body: args.content }); shadowman.log.debug('Storage update response: ' + resp2.status); if (resp2.status >= 400) { var errDetail2 = resp2.body; try { var errJson2 = JSON.parse(resp2.body); if (errJson2.error) errDetail2 = errJson2.error; if (errJson2.message) errDetail2 = errJson2.message; } catch (e) {} shadowman.log.error('Failed to update content: ' + resp2.status + ' - ' + errDetail2); return { error: 'Failed to update content: ' + resp2.status, details: errDetail2, status: resp2.status }; } return { success: true, page_id: args.page_id }; } // ─── list_versions ─── if (action === 'list_versions') { if (!args.page_id) return { error: 'page_id is required' }; shadowman.log.info('list_versions called for page: ' + args.page_id); var u3 = getPageUserId(args.page_id); if (u3.error) return u3; var versionsUrl = '/api/storage/pages/' + u3.user_id + '/' + args.page_id + '.md/versions'; shadowman.log.info('Fetching versions from: ' + versionsUrl); return apiRequest('GET', versionsUrl); } return { error: 'Unknown action: ' + action + '. Valid: list, get, create, update, delete, get_content, update_content, list_versions' }; }); // ── Tool 2: sharing ─────────────────────────────────────────── shadowman.tools.register('sharing', function(args) { var action = args.action; // ─── list_links ─── if (action === 'list_links') { if (!args.page_id) return { error: 'page_id is required' }; return apiRequest('GET', '/api/page_share_links?page_id=' + args.page_id); } // ─── create_link ─── if (action === 'create_link') { if (!args.page_id) return { error: 'page_id is required' }; var link = { page_id: args.page_id }; if (args.permission) link.permission = args.permission; if (args.short_code) link.short_code = args.short_code; if (args.expires_at) link.expires_at = args.expires_at; return apiRequest('POST', '/api/page_share_links', link); } // ─── delete_link ─── if (action === 'delete_link') { if (!args.link_id) return { error: 'link_id is required' }; return apiRequest('DELETE', '/api/page_share_links/' + args.link_id); } // ─── check_lock ─── if (action === 'check_lock') { if (!args.page_id) return { error: 'page_id is required' }; return apiRequest('GET', '/api/page_locks?page_id=' + args.page_id); } // ─── acquire_lock ─── if (action === 'acquire_lock') { if (!args.page_id) return { error: 'page_id is required' }; return apiRequest('POST', '/api/page_locks', { page_id: args.page_id, display_name: args.display_name || 'ShadowMan' }); } // ─── release_lock ─── if (action === 'release_lock') { if (!args.page_id) return { error: 'page_id is required' }; return apiRequest('DELETE', '/api/page_locks/' + args.page_id); } return { error: 'Unknown action: ' + action + '. Valid: list_links, create_link, delete_link, check_lock, acquire_lock, release_lock' }; }); // ── Tool 3: images ──────────────────────────────────────────── shadowman.tools.register('images', function(args) { var action = args.action; // ─── list ─── if (action === 'list') { return apiRequest('GET', '/api/storage/images'); } // ─── upload ─── if (action === 'upload') { if (!args.filename) return { error: 'filename is required' }; if (!args.content_type) return { error: 'content_type is required' }; if (!args.image_data) return { error: 'image_data (base64) is required' }; shadowman.log.info('Uploading image: ' + args.filename); var imageData = shadowman.utils.base64DecodeBytes(args.image_data); var resp = shadowman.http.request(baseUrl + '/api/storage/images/' + args.filename, { method: 'POST', headers: { 'Authorization': 'API ' + apiKey, 'Content-Type': args.content_type }, body: imageData }); if (resp.status >= 400) { var errDetail = resp.body; try { var errJson = JSON.parse(resp.body); if (errJson.error) errDetail = errJson.error; if (errJson.message) errDetail = errJson.message; } catch (e) {} shadowman.log.error('Upload failed: ' + resp.status + ' - ' + errDetail); return { error: 'Upload failed: ' + resp.status, details: errDetail }; } try { return resp.json(); } catch (e) { return { success: true, status: resp.status }; } } // ─── delete ─── if (action === 'delete') { if (!args.filename) return { error: 'filename is required' }; return apiRequest('DELETE', '/api/storage/images/' + args.filename); } // ─── create_alias ─── if (action === 'create_alias') { if (!args.image_path) return { error: 'image_path is required' }; return apiRequest('POST', '/api/storage/alias', { bucket: 'images', path: args.image_path }); } // ─── list_usage ─── if (action === 'list_usage') { if (!args.page_id) return { error: 'page_id is required' }; return apiRequest('GET', '/api/page_image_usage?page_id=' + args.page_id); } // ─── track_usage ─── if (action === 'track_usage') { if (!args.file_id) return { error: 'file_id is required' }; if (!args.page_id) return { error: 'page_id is required' }; var usage = { file_id: args.file_id, page_id: args.page_id }; if (args.alias_url) usage.alias_url = args.alias_url; return apiRequest('POST', '/api/page_image_usage', usage); } return { error: 'Unknown action: ' + action + '. Valid: list, upload, delete, create_alias, list_usage, track_usage' }; });