'use strict'; const fetch = require('node-fetch'); const { URLSearchParams } = require('url'); var apiProto = {}; apiProto.__getApiDoc = async function(host){ try{ let setupRes = await fetch(host || this.host+'/pve-docs/api-viewer/apidoc.js', { method: 'GET'}); let text = await setupRes.text() text = text.split('// avoid errors when running without development tools')[0]; return eval(text += '; pveapi;'); }catch(error){ throw error; } } apiProto.__makeMap = async function(APItree){ APItree = APItree || await this.__getApiDoc(); let apiMap = {}; function __rec(obj){ for(let item of obj){ if(item.children){ __rec(item.children); } if(item.path){ delete item.children; apiMap[item.path] = item; item.methods = item.info; delete item.info; } } } __rec(APItree); return apiMap }; //these helper functions are to build the fetch call apiProto.__buildHeaders = function(){ let headers = {}; if(this.authInfo.apiToken){ headers = { "Authorization": `PVEAPIToken=${this.authInfo.username}=${this.authInfo.apiToken}` } }else{ headers = { 'Cookie': 'PVEAuthCookie='+this.user.ticket, 'CSRFPreventionToken': this.user.CSRFPreventionToken } } return headers; }; // this function takes an iterable of 'key':'value' pairs apiProto.__buildBody = function(props, data){ const params = new URLSearchParams(); for (let key in props){ if(data[key]){ params.append(key, data[key]) continue; } if(props[key].optional !==1){ // console.log(props[key]) throw new Error('MissingKey'); } } return params; }; apiProto.__getEndPoint = function(args){ let endpoint = this.apiMap[args.path] if(!args || !endpoint) throw new Error('endpointNotFound'); if(!endpoint.methods[args.method]) throw new Error('methodNotFound'); return endpoint; }; apiProto.__fetch = async function(method, path, params, data){ try{ let endpoint = this.__getEndPoint({path, method}); let fetchOptions = { method: method, headers: this.__buildHeaders(), } if(['PUT', 'POST'].includes(method)){ fetchOptions.body = this.__buildBody( endpoint.methods[method].parameters.properties, data ) } let HTTPres = await fetch(this.BASEURL+path, fetchOptions); // console.log(HTTPres) return { statusCode: HTTPres.status, statusText: HTTPres.statusText, json: (await HTTPres.json()).data, } }catch(error){ throw error; } }; apiProto.GET = async function(args){ try{ return await this.__fetch( 'GET', args.path, args.parama || {}, ); }catch(error){ throw error; } }; apiProto.DELETE = async function(args){ try{ return await this.__fetch( 'DELETE', args.path, args.parama || {}, ); }catch(error){ throw error; } }; apiProto.POST = async function(args){ try{ return await this.__fetch( 'POST', args.path, args.parama || {}, args.data || {} ); }catch(error){ throw error; } }; apiProto.PUT = async function(args){ try{ return await this.__fetch( 'PUT', args.path, args.parama || {}, args.data || {} ); }catch(error){ throw error; } }; apiProto.auth = async function(authInfo){ try{ this.user = (await this.POST({ path: '/access/ticket', data: authInfo || this.authInfo })).json return authInfo; }catch(error){ throw error; } }; apiProto.__init = async function(conf){ try{ this.user = {}; this.BASEURL = conf.host+'/api2/json'; this.apiMap = await this.__makeMap(); if(!this.authInfo.apiToken) await this.auth(); return this; }catch(error){ throw(error); } }; async function API(conf){ let instance = Object.create(apiProto); Object.assign(instance, conf); return await instance.__init(conf); } module.exports = API;