index.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. // PicoNotes Plugin for ShadowMan
  2. // API v2.0.0 — Consolidated tools (3 instead of 19)
  3. var config = shadowman.config.get();
  4. var apiKey = config.api_key;
  5. var baseUrl = 'https://piconotes.eu';
  6. shadowman.log.info('PicoNotes plugin loaded (API v2.0.0, consolidated)');
  7. shadowman.log.info('API key configured: ' + (apiKey ? 'yes (length: ' + apiKey.length + ', prefix: ' + apiKey.substring(0, 8) + ')' : 'NO!'));
  8. // ── Helpers ──────────────────────────────────────────────────────────────
  9. function apiRequest(method, path, body, contentType) {
  10. // Authorization: PicoNotes API key format
  11. var headers = {
  12. 'Authorization': 'API ' + apiKey,
  13. 'Prefer': 'return=representation'
  14. };
  15. // Content-Type for POST/PATCH requests
  16. if (method !== 'GET' && method !== 'DELETE') {
  17. if (contentType) {
  18. headers['Content-Type'] = contentType;
  19. } else {
  20. headers['Content-Type'] = 'application/json';
  21. }
  22. }
  23. var url = baseUrl + path;
  24. var response;
  25. shadowman.log.debug('API Request: ' + method + ' ' + path);
  26. if (method === 'GET') {
  27. response = shadowman.http.get(url, { headers: headers });
  28. } else if (method === 'POST') {
  29. if (contentType && contentType !== 'application/json') {
  30. response = shadowman.http.request(url, { method: 'POST', body: body, headers: headers });
  31. } else {
  32. shadowman.log.debug('POST body: ' + JSON.stringify(body));
  33. response = shadowman.http.request(url, {
  34. method: 'POST',
  35. body: JSON.stringify(body),
  36. headers: headers
  37. });
  38. }
  39. } else if (method === 'PATCH') {
  40. response = shadowman.http.request(url, { method: 'PATCH', body: JSON.stringify(body), headers: headers });
  41. } else if (method === 'DELETE') {
  42. response = shadowman.http.del(url, { headers: headers });
  43. }
  44. shadowman.log.debug('API Response: ' + response.status + ' - ' + (response.body ? response.body : 'empty'));
  45. if (response.status >= 400) {
  46. var errorDetail = response.body;
  47. try {
  48. var errorJson = JSON.parse(response.body);
  49. if (errorJson.error) errorDetail = errorJson.error;
  50. if (errorJson.message) errorDetail = errorJson.message;
  51. if (errorJson.detail) errorDetail = errorJson.detail;
  52. } catch (e) {}
  53. shadowman.log.error('API Error ' + response.status + ': ' + errorDetail);
  54. return { error: 'API error ' + response.status, details: errorDetail, status: response.status };
  55. }
  56. try {
  57. return response.json();
  58. } catch (e) {
  59. return response.body || { success: true, status: response.status };
  60. }
  61. }
  62. function getPageUserId(pageId) {
  63. shadowman.log.info('Getting user_id for page: ' + pageId);
  64. var result = apiRequest('GET', '/api/pages/' + pageId);
  65. if (result.error) {
  66. shadowman.log.error('Failed to get page: ' + result.error + ' - ' + result.details);
  67. return result;
  68. }
  69. // API v2.0.0: a válasz lehet { data: {...} }, { ... }, vagy [ {...} ]
  70. var page = result;
  71. if (result.data) {
  72. page = result.data;
  73. } else if (Array.isArray(result)) {
  74. page = result[0];
  75. }
  76. shadowman.log.debug('Page data: ' + JSON.stringify(page));
  77. if (!page) {
  78. shadowman.log.error('Page not found: ' + pageId);
  79. return { error: 'Page not found: ' + pageId };
  80. }
  81. if (!page.user_id) {
  82. shadowman.log.error('Page missing user_id: ' + JSON.stringify(page));
  83. return { error: 'Could not resolve user_id for page ' + pageId };
  84. }
  85. shadowman.log.info('Resolved user_id: ' + page.user_id);
  86. return { user_id: page.user_id };
  87. }
  88. // ── Tool 1: pages ─────────────────────────────────────────────
  89. shadowman.tools.register('pages', function(args) {
  90. var action = args.action;
  91. // ─── list ───
  92. if (action === 'list') {
  93. var params = [];
  94. if (args.parent_id !== undefined) {
  95. if (args.parent_id === 'null' || args.parent_id === null) {
  96. params.push('parent_id=is.null');
  97. } else {
  98. params.push('parent_id=eq.' + args.parent_id);
  99. }
  100. }
  101. params.push('order=' + (args.order || 'sort_order.asc'));
  102. params.push('select=id,title,icon,parent_id,sort_order,created_at,updated_at,user_id');
  103. return apiRequest('GET', '/api/pages?' + params.join('&'));
  104. }
  105. // ─── get ───
  106. if (action === 'get') {
  107. if (!args.page_id) return { error: 'page_id is required' };
  108. return apiRequest('GET', '/api/pages/' + args.page_id);
  109. }
  110. // ─── create ───
  111. if (action === 'create') {
  112. if (!args.title) return { error: 'title is required' };
  113. // Fix: resolve user_id from list if not provided
  114. var userId = '0v2AJNrePA8VGUlL';
  115. var page = { title: args.title, user_id: userId };
  116. if (args.id) page.id = args.id;
  117. if (args.icon) page.icon = args.icon;
  118. if (args.parent_id !== undefined) page.parent_id = args.parent_id;
  119. if (args.sort_order !== undefined) page.sort_order = args.sort_order;
  120. if (args.is_template !== undefined) page.is_template = args.is_template;
  121. shadowman.log.info('Creating page with user_id: ' + JSON.stringify(page));
  122. return apiRequest('POST', '/api/pages', page);
  123. }
  124. // ─── update ───
  125. if (action === 'update') {
  126. if (!args.page_id) return { error: 'page_id is required' };
  127. var updates = {};
  128. if (args.title !== undefined) updates.title = args.title;
  129. if (args.icon !== undefined) updates.icon = args.icon;
  130. if (args.parent_id !== undefined) updates.parent_id = args.parent_id;
  131. if (args.sort_order !== undefined) updates.sort_order = args.sort_order;
  132. return apiRequest('PATCH', '/api/pages/' + args.page_id, updates);
  133. }
  134. // ─── delete ───
  135. if (action === 'delete') {
  136. if (!args.page_id) return { error: 'page_id is required' };
  137. return apiRequest('DELETE', '/api/pages/' + args.page_id);
  138. }
  139. // ─── get_content ───
  140. if (action === 'get_content') {
  141. if (!args.page_id) return { error: 'page_id is required' };
  142. shadowman.log.info('get_content called for page: ' + args.page_id);
  143. var u = getPageUserId(args.page_id);
  144. if (u.error) return u;
  145. var storageUrl = '/api/storage/pages/' + u.user_id + '/' + args.page_id + '.md';
  146. shadowman.log.info('Fetching content from: ' + storageUrl);
  147. var resp = shadowman.http.get(baseUrl + storageUrl, { headers: { 'Authorization': 'API ' + apiKey } });
  148. shadowman.log.debug('Storage response: ' + resp.status);
  149. if (resp.status >= 400) {
  150. var errDetail = resp.body;
  151. try {
  152. var errJson = JSON.parse(resp.body);
  153. if (errJson.error) errDetail = errJson.error;
  154. if (errJson.message) errDetail = errJson.message;
  155. } catch (e) {}
  156. shadowman.log.error('Failed to get content: ' + resp.status + ' - ' + errDetail);
  157. return { error: 'Failed to get content: ' + resp.status, details: errDetail, status: resp.status };
  158. }
  159. return { page_id: args.page_id, content: resp.body };
  160. }
  161. // ─── update_content ───
  162. if (action === 'update_content') {
  163. if (!args.page_id) return { error: 'page_id is required' };
  164. if (!args.content && args.content !== '') return { error: 'content is required' };
  165. shadowman.log.info('update_content called for page: ' + args.page_id);
  166. var u2 = getPageUserId(args.page_id);
  167. if (u2.error) return u2;
  168. var storageUrl2 = baseUrl + '/api/storage/pages/' + u2.user_id + '/' + args.page_id + '.md?upsert=true';
  169. shadowman.log.info('Updating content at: ' + storageUrl2);
  170. var resp2 = shadowman.http.request(storageUrl2, {
  171. method: 'POST',
  172. headers: { 'Authorization': 'API ' + apiKey, 'Content-Type': 'text/markdown' },
  173. body: args.content
  174. });
  175. shadowman.log.debug('Storage update response: ' + resp2.status);
  176. if (resp2.status >= 400) {
  177. var errDetail2 = resp2.body;
  178. try {
  179. var errJson2 = JSON.parse(resp2.body);
  180. if (errJson2.error) errDetail2 = errJson2.error;
  181. if (errJson2.message) errDetail2 = errJson2.message;
  182. } catch (e) {}
  183. shadowman.log.error('Failed to update content: ' + resp2.status + ' - ' + errDetail2);
  184. return { error: 'Failed to update content: ' + resp2.status, details: errDetail2, status: resp2.status };
  185. }
  186. return { success: true, page_id: args.page_id };
  187. }
  188. // ─── list_versions ───
  189. if (action === 'list_versions') {
  190. if (!args.page_id) return { error: 'page_id is required' };
  191. shadowman.log.info('list_versions called for page: ' + args.page_id);
  192. var u3 = getPageUserId(args.page_id);
  193. if (u3.error) return u3;
  194. var versionsUrl = '/api/storage/pages/' + u3.user_id + '/' + args.page_id + '.md/versions';
  195. shadowman.log.info('Fetching versions from: ' + versionsUrl);
  196. return apiRequest('GET', versionsUrl);
  197. }
  198. return { error: 'Unknown action: ' + action + '. Valid: list, get, create, update, delete, get_content, update_content, list_versions' };
  199. });
  200. // ── Tool 2: sharing ───────────────────────────────────────────
  201. shadowman.tools.register('sharing', function(args) {
  202. var action = args.action;
  203. // ─── list_links ───
  204. if (action === 'list_links') {
  205. if (!args.page_id) return { error: 'page_id is required' };
  206. return apiRequest('GET', '/api/page_share_links?page_id=' + args.page_id);
  207. }
  208. // ─── create_link ───
  209. if (action === 'create_link') {
  210. if (!args.page_id) return { error: 'page_id is required' };
  211. var link = { page_id: args.page_id };
  212. if (args.permission) link.permission = args.permission;
  213. if (args.short_code) link.short_code = args.short_code;
  214. if (args.expires_at) link.expires_at = args.expires_at;
  215. return apiRequest('POST', '/api/page_share_links', link);
  216. }
  217. // ─── delete_link ───
  218. if (action === 'delete_link') {
  219. if (!args.link_id) return { error: 'link_id is required' };
  220. return apiRequest('DELETE', '/api/page_share_links/' + args.link_id);
  221. }
  222. // ─── check_lock ───
  223. if (action === 'check_lock') {
  224. if (!args.page_id) return { error: 'page_id is required' };
  225. return apiRequest('GET', '/api/page_locks?page_id=' + args.page_id);
  226. }
  227. // ─── acquire_lock ───
  228. if (action === 'acquire_lock') {
  229. if (!args.page_id) return { error: 'page_id is required' };
  230. return apiRequest('POST', '/api/page_locks', {
  231. page_id: args.page_id,
  232. display_name: args.display_name || 'ShadowMan'
  233. });
  234. }
  235. // ─── release_lock ───
  236. if (action === 'release_lock') {
  237. if (!args.page_id) return { error: 'page_id is required' };
  238. return apiRequest('DELETE', '/api/page_locks/' + args.page_id);
  239. }
  240. return { error: 'Unknown action: ' + action + '. Valid: list_links, create_link, delete_link, check_lock, acquire_lock, release_lock' };
  241. });
  242. // ── Tool 3: images ────────────────────────────────────────────
  243. shadowman.tools.register('images', function(args) {
  244. var action = args.action;
  245. // ─── list ───
  246. if (action === 'list') {
  247. return apiRequest('GET', '/api/storage/images');
  248. }
  249. // ─── upload ───
  250. if (action === 'upload') {
  251. if (!args.filename) return { error: 'filename is required' };
  252. if (!args.content_type) return { error: 'content_type is required' };
  253. if (!args.image_data) return { error: 'image_data (base64) is required' };
  254. shadowman.log.info('Uploading image: ' + args.filename);
  255. var imageData = shadowman.utils.base64DecodeBytes(args.image_data);
  256. var resp = shadowman.http.request(baseUrl + '/api/storage/images/' + args.filename, {
  257. method: 'POST',
  258. headers: { 'Authorization': 'API ' + apiKey, 'Content-Type': args.content_type },
  259. body: imageData
  260. });
  261. if (resp.status >= 400) {
  262. var errDetail = resp.body;
  263. try {
  264. var errJson = JSON.parse(resp.body);
  265. if (errJson.error) errDetail = errJson.error;
  266. if (errJson.message) errDetail = errJson.message;
  267. } catch (e) {}
  268. shadowman.log.error('Upload failed: ' + resp.status + ' - ' + errDetail);
  269. return { error: 'Upload failed: ' + resp.status, details: errDetail };
  270. }
  271. try { return resp.json(); } catch (e) { return { success: true, status: resp.status }; }
  272. }
  273. // ─── delete ───
  274. if (action === 'delete') {
  275. if (!args.filename) return { error: 'filename is required' };
  276. return apiRequest('DELETE', '/api/storage/images/' + args.filename);
  277. }
  278. // ─── create_alias ───
  279. if (action === 'create_alias') {
  280. if (!args.image_path) return { error: 'image_path is required' };
  281. return apiRequest('POST', '/api/storage/alias', { bucket: 'images', path: args.image_path });
  282. }
  283. // ─── list_usage ───
  284. if (action === 'list_usage') {
  285. if (!args.page_id) return { error: 'page_id is required' };
  286. return apiRequest('GET', '/api/page_image_usage?page_id=' + args.page_id);
  287. }
  288. // ─── track_usage ───
  289. if (action === 'track_usage') {
  290. if (!args.file_id) return { error: 'file_id is required' };
  291. if (!args.page_id) return { error: 'page_id is required' };
  292. var usage = { file_id: args.file_id, page_id: args.page_id };
  293. if (args.alias_url) usage.alias_url = args.alias_url;
  294. return apiRequest('POST', '/api/page_image_usage', usage);
  295. }
  296. return { error: 'Unknown action: ' + action + '. Valid: list, upload, delete, create_alias, list_usage, track_usage' };
  297. });