From b77bdfbcfe3182965fd8c263bb81d10a7dbc6dcb Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 4 Aug 2020 11:23:06 -0400 Subject: [PATCH] :sparkles: Feature/twake node (#815) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️ ADD Twake node * Convert space in tab * :sparkles: Twake-Node * :zap: Improvements * :zap: Small improvements Co-authored-by: Benoit --- .../credentials/TwakeCloudApi.credentials.ts | 17 ++ .../credentials/TwakeServerApi.credentials.ts | 29 ++ .../nodes/Twake/GenericFunctions.ts | 67 +++++ packages/nodes-base/nodes/Twake/Twake.node.ts | 247 ++++++++++++++++++ packages/nodes-base/nodes/Twake/twake.png | Bin 0 -> 6079 bytes packages/nodes-base/package.json | 3 + 6 files changed, 363 insertions(+) create mode 100644 packages/nodes-base/credentials/TwakeCloudApi.credentials.ts create mode 100644 packages/nodes-base/credentials/TwakeServerApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Twake/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Twake/Twake.node.ts create mode 100644 packages/nodes-base/nodes/Twake/twake.png diff --git a/packages/nodes-base/credentials/TwakeCloudApi.credentials.ts b/packages/nodes-base/credentials/TwakeCloudApi.credentials.ts new file mode 100644 index 0000000000..8cd47128aa --- /dev/null +++ b/packages/nodes-base/credentials/TwakeCloudApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class TwakeCloudApi implements ICredentialType { + name = 'twakeCloudApi'; + displayName = 'Twake API'; + properties = [ + { + displayName: 'Workspace Key', + name: 'workspaceKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/credentials/TwakeServerApi.credentials.ts b/packages/nodes-base/credentials/TwakeServerApi.credentials.ts new file mode 100644 index 0000000000..b1377ce743 --- /dev/null +++ b/packages/nodes-base/credentials/TwakeServerApi.credentials.ts @@ -0,0 +1,29 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class TwakeServerApi implements ICredentialType { + name = 'twakeServerApi'; + displayName = 'Twake API'; + properties = [ + { + displayName: 'Host URL', + name: 'hostUrl', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Public ID', + name: 'publicId', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Private API Key', + name: 'privateApiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Twake/GenericFunctions.ts b/packages/nodes-base/nodes/Twake/GenericFunctions.ts new file mode 100644 index 0000000000..84aaf9e20b --- /dev/null +++ b/packages/nodes-base/nodes/Twake/GenericFunctions.ts @@ -0,0 +1,67 @@ +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + OptionsWithUri, +} from 'request'; +/** + * Make an API request to Twake + * + * @param {IHookFunctions} this + * @param {string} method + * @param {string} url + * @param {object} body + * @returns {Promise} + */ +export async function twakeApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: object, query?: object, uri?: string): Promise { // tslint:disable-line:no-any + + const authenticationMethod = this.getNodeParameter('twakeVersion', 0, 'twakeCloudApi') as string; + + const options: OptionsWithUri = { + headers: {}, + method, + body, + qs: query, + uri: uri || `https://connectors.albatros.twakeapp.com/n8n${resource}`, + json: true, + }; + + + if (authenticationMethod === 'cloud') { + const credentials = this.getCredentials('twakeCloudApi'); + options.headers!.Authorization = `Bearer ${credentials!.workspaceKey}`; + + } else { + + const credentials = this.getCredentials('twakeServerApi'); + options.auth = { user: credentials!.publicId as string, pass: credentials!.privateApiKey as string }; + options.uri = `${credentials!.hostUrl}/api/v1${resource}`; + } + + try { + return await this.helpers.request!(options); + } catch (error) { + if( error.error.code === "ECONNREFUSED"){ + throw new Error('Twake host is not accessible!'); + + } + if (error.statusCode === 401) { + // Return a clear error + throw new Error('The Twake credentials are not valid!'); + } + + if (error.response && error.response.body && error.response.body.errors) { + // Try to return the error prettier + const errorMessages = error.response.body.errors.map((errorData: { message: string }) => { + return errorData.message; + }); + throw new Error(`Twake error response [${error.statusCode}]: ${errorMessages.join(' | ')}`); + } + + // If that data does not exist for some reason return the actual error + throw error; + } +} diff --git a/packages/nodes-base/nodes/Twake/Twake.node.ts b/packages/nodes-base/nodes/Twake/Twake.node.ts new file mode 100644 index 0000000000..d890e6b6cd --- /dev/null +++ b/packages/nodes-base/nodes/Twake/Twake.node.ts @@ -0,0 +1,247 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + INodeExecutionData, + IDataObject, + INodeType, + INodeTypeDescription, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; + +import { + twakeApiRequest, +} from './GenericFunctions'; + +export class Twake implements INodeType { + description: INodeTypeDescription = { + displayName: 'Twake', + name: 'twake', + group: ['transform'], + version: 1, + icon: 'file:twake.png', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Twake API', + defaults: { + name: 'Twake', + color: '#7168ee', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'twakeCloudApi', + required: true, + displayOptions: { + show: { + twakeVersion: [ + 'cloud', + ], + }, + }, + }, + { + name: 'twakeServerApi', + required: true, + displayOptions: { + show: { + twakeVersion: [ + 'server', + ], + }, + }, + }, + ], + properties: [ + { + displayName: 'Twake Version', + name: 'twakeVersion', + type: 'options', + options: [ + { + name: 'Cloud', + value: 'cloud', + }, + // { + // name: 'Server (Self Hosted)', + // value: 'server', + // }, + ], + default: 'cloud', + }, + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Message', + value: 'message', + description: 'Send data to the message app', + }, + ], + default: 'message', + description: 'The operation to perform.', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'message', + ], + }, + }, + options: [ + { + name: 'Send', + value: 'send', + description: 'Send a message', + }, + ], + default: 'send', + description: 'The operation to perform.', + }, + { + displayName: 'Channel ID', + name: 'channelId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getChannels', + }, + displayOptions: { + show: { + operation: [ + 'send', + ], + }, + }, + default: '', + description: `Channel's ID`, + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'send', + ], + }, + }, + default: '', + description: 'Message content', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'send', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Sender Icon', + name: 'senderIcon', + type: 'string', + default: '', + description: 'URL of the image/icon', + }, + { + displayName: 'Sender Name', + name: 'senderName', + type: 'string', + default: '', + description: 'Sender name', + }, + ], + }, + ], + }; + + methods = { + loadOptions: { + async getChannels(this: ILoadOptionsFunctions): Promise { + const responseData = await twakeApiRequest.call(this, 'POST', '/channel', {}); + if (responseData === undefined) { + throw new Error('No data got returned'); + } + + const returnData: INodePropertyOptions[] = []; + for (const channel of responseData) { + returnData.push({ + name: channel.name, + value: channel.id, + }); + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = (items.length as unknown) as number; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + if (resource === 'message') { + if (operation === 'send') { + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const message: IDataObject = { + channel_id: this.getNodeParameter('channelId', i), + content: { + formatted: this.getNodeParameter('content', i) as string, + }, + hidden_data: { + allow_delete: 'everyone', + }, + }; + + if (additionalFields.senderName) { + //@ts-ignore + message.hidden_data!.custom_title = additionalFields.senderName as string; + } + + if (additionalFields.senderIcon) { + //@ts-ignore + message.hidden_data!.custom_icon = additionalFields.senderIcon as string; + } + + const body = { + object: message, + }; + + const endpoint = '/actions/message/save'; + + responseData = await twakeApiRequest.call(this, 'POST', endpoint, body); + + responseData = responseData.object; + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Twake/twake.png b/packages/nodes-base/nodes/Twake/twake.png new file mode 100644 index 0000000000000000000000000000000000000000..3b8408938bb415e37bc1e9bac6e545138d076d2e GIT binary patch literal 6079 zcmY*dbzD?k*Bw9@x<$IXh8$oRknZk|hZs%#64hm z8>lJ(>PG2z?-x-{X6nw`+5p~rjtjuXAO~Rmk=)+^3|av8-y8tYz@Yn=8(?t%qrn6K zVqF0b{?S<6;~&cZXZjbhiZK4`ScLhn8d`+)U;ZbL5$D|J9`HQX%)J2sLAE~wBcSZD z-@TQ&tC5+HnYI?h!2>R6@96QDlVAYc^G_B48UVSca3>#o<^Z^xyEi02hV^d+lg3i0JZmVbEpJ z_euY2>`BwU{&bgM=ng4Sfc<-j+ z!$oavm!U>N6}3Gw)kT$7#mHn4Yv9JnsZUBt%|4G(s}6Jlt^K%2jrP9FWq|1r)U^Dp z>h=`6okWoeDqSL%(yJ$6Ul|$G)}5c|<+3xV;gf2Xi93xeXaPASDUmLMi0#RTI^9nkR)?i;MQA6exs_zlfXgruo?IXt|F6K5e(}R)50k!t+ zrIL#u+2bUl`*b01BeFIaIXgwjNy6VAM>=4itEf_k@)R_u#(*oeKXsdiws}d{&5+D$ zmNiVR7o8o2-QL{1QPAzTZ07EGAUoE1yFIvNU>-XDq$`QtRnD73NAi^&@cweJa(1NZ^NM02Y$0cBpOh`{LHA*M7lubOXkLx z&W@*d1m_QHAyNW7KAVqr7OoEc3{+F9)lJj24h`hlOQ9%FR*FCABt5{+%dX?JVUm zqNJvfQo_m_e{|OoKV3m#9;PiX*eaHWLmzyQ+6?_wVn!uMl_RPf-o)&P40D0)hEe-3 zEhD^q)W@?%$gV>iP@1AJWd>dPX4(8bS-M}bK5rE?Wfxe8Smf;dMFPxTD z6+T=qzRGXrO=HFUgcv%FvMWSLMilU>s7wDJG-J{ zzKrss8Vu5X9o=b?VO;TOBd21i@i0grM~c1ChXzch=B@j~K409l-Zswn?0wAoUPF0lz0&uCua z2^H&1lI54JnoT)$z&1(LYEw`H7wsbHxbVgkH8JATFhn*qoh? zxW9$MGMQs5WMreIMt>h~kzEaXYhT^XmR}+;)>j%uzJ5M`iOhc6ZC9Zb;>&Wa=c*M* zNo7C$Jw!USS_o^(d@cA*RY{1<{(+iDDeOev^{);)Qqadzex_x;4F8oRVWY}@EXn=n zMgFE;ccTbmUqzat`WDQXVX(7!J@bZa;XXH~yc@gKD{h+*wSxM^)wz7yrw>39eb%K? zn}`zhh$NTr{610iGt#wg5z^Vq3|tD{tc|3x2h_=Iuj=!aakh5oMXSWG^Itud8;l1F z_SZ?sl6J}aW<*_vm-=8a4ChD4u+Jo};I2*}b=m7M!qHNE$Qg5x!cmh|@at<%NUEWO zi)c^I4sYFT*!z^L)#UO+DYFG_oC~u|N<4}QA;#Tgh#zc_M4!@*=1}?k%dVJf>%0yk zxKYRKioR^oZi}Yx@TU>~jHKm)B&i9DjsbofJugs2}(;e2OYY=5tzvXpP+7QO`m8h^d0MGS-Oum z1{2CWULnbE7ONQ+ekp1Co1?5u(#rZv+9uM!SjGmpZJc#VsXZZJ+2#Y?VqMhasJWk! z`C2LxVt0zB6NwRb6Ng7^Eg&c9ip%--tzzL{=?CsYtOIWwvY;m}s>Ir9vZ*g+r?M{S z5)l)_!*7A73K0bU987Jiwbd%kZoDt+X2H zH%UOGUZBhS@4r2Ntbe7kqaC46jBe5LdOxoU{~SOq0qv+7q@s$<376%}++?S5>lwn| zo)<07JCovRxV?qJ-N^Dz_X^EoRF$ddfeJ#$)bTo=ox|^LtljuOp1h0653$z-TibR~ z7?U*yv`4GsI24SH?nT=ec8gfXo0im!9DJVOX|q3h>nIl^s?bdwsr`I=!~3XRnRQHd zCN*70uu_pkHoWjg-!w*rBIm^~q2z6>&Z7{Efm(J6nr^lTqgaZoO#bGrA>|PjA9)Ki&D~y%;*FkHGUEzM z@qFvy0}Cq6)K9%W^y1MLR>_7z2s!Zx(QL6<*IKNxLd3e(5BIL7mpU8Gnqz?D&6QXqc*Ov(g=>ptV*n4gKw=<&{T)K>dd9 zrg%!~?q~i{f#spuK9ax;+*BBe4r82?AmPpQb^>B|Q<-11IQWO{e&hZsa@g9gqbNZe zqiY!Ou<*z!7qgUSh6ryvLj5-UlRW>tq@Etxw$5xzAawv^i2WS*u_wGd=KBCvyOcm8igD+xQ$2%|zT%r!TB+P?z zBbUuD-EdR$D-{mNO=^;k0TEw%$9P`Oi}3D`f(A>Z_ZovtHOUE!u@eE_w?6)=B!K>uz0zWLs;Oer z=kbm)<=lYSE;-Mtg&TKPE&AwyE`9Kt;_`60Y{dbL*^^or zmk09a2gZ4W-?F)vIrktR?8k=bZs6Ruv*hR5~X20&BCtU-v@X*+r z@q<>Q=&<~DO~bBlRt6_y6sZPKO8!IAOj|iz=aZYA?wbHEt(F-i;Hh(^UQ)FQ&(j_rZEwC0r(CI68@+_iR4MB$GCe zhwDH|%B#JM1FqmvPw~@`ymQ4o={0@WBKC)E`-DiGhZ}-D^WVCwZi@ zoD>jUbkX7R%y5fR7lU}oA-ZZLFo#^;lGtx4L}S<2q^vATrV&;}&4MBlG|atBIe19q zBgFLJp*@0HvW^%Ub!7h8b&G$|FyoA+WYA5|VLaSXZr3}D(78rI1Hu334*kg%m*k+I z8t}A71`lUbp8;5F>n}y)MSU0|-8W^#e73c^i3PGtArmtsz{VK&C`V~CCYxKVdr%iG zd;=k0WV2Dp4o#Gc!!kC;K0^7c=cpW+{2UF_8;F6jj|@d#SeqLq@?zFV2ASC{Z7A^r zR}weZ_!3XKT1L92Oeb^{e>w_VdLiaBdV8;m&%>K=!g@+JR&*Qp`bmd;dlH$}S9UWi z9&Ubs(Kh}9Y6lXHq1gf!Tq$jJLT{z|@hZcQ}mpjDw ztRIYcpXa1{gP#^Zcb4T^o;ICiZEbz$*mT;ONG`>2wd1G&RHX0!f@h5|Hy*XH%v#o+ zZRm6#%KJ^@@HMdv3%CytdC_yZo&4<8g6Cv{p8IVu_T!htuC#LzZ|44bz~*f-a`t)# z!&Ds}%~gmS4pi@@-EWlw4oSx6u1H&1!e7CbU`(Mm# zPQ>PUMs~m_ulU%eex=)14mhDqH-AzjT(9(tHNRll@Z{Q#jX=HmVZ-KHDWJZWHaIG$ znnY*$rHZ3h^2b{Sq3^T;U#_V8&@}1947KxH4{j{Z!Bbv?lWNVuew2@6vo=<0#RJbe z$;h;v`Ai>etXsx7&g>dV=oVxwKWjDVM9paibV2uq};N8fbi!!HKB zF3Qo@9|Rjwfm!XB?U7bNismRL+P?6gQ_8bvy)S}Jn+^gSct|{|2g9IWCus8e&JOmz zB(g5}ERzzG@aCpn;5?&EK7ftriAc8vUpJc46$RaKiJp#QiJqPkz;m8gl#&zU2h?R{ z4WCehs-h2de)WEveT*h7rb!{&f;n%=aFIZS#3Kqw_Drp;GoFG?H{SQU$l3S&avLf- z*=P7QLkC~D8&85Z7>-%Yn4hM^6naXn_4v;CIxW6nRZE*tPWo z-7X2|W^Fqg_)+T(NvcAn_Im9lz?Gw+Q3mm=)gN@NgkCO`Lduf?`yoi4fgY6QKzxW< zb5;}ri?bn7kTGp_RWBA0^zIW&#F6yTTc`eR!$t-Xns37NR0w1dbU$R<{Y{fnZg;_A z;kQ{3<7>sI(Qi7@^u87g`At;pkN9v6*nD5|wT9L~2!MevtA=u(@-2czDLxzOPH(Op zO|L4k5(wA1P-0cdIH74mnZk~9&gj=~RuVRA2w1OvCU<9#zm}{cItLt8aaC-7Y=eoh zQB(hJACKa9+pS&X!0tSansmJ z<`pJHi+h3sJUgM ztaQ|)O}u;)+28BU`B8Iu^P|(os;<#T+1=l?K)Gw&w8Tq?yIi}+Y@3twF)H2(&ug;r z^?>)UYpP3X!|aNro0ViI{Hl)|?8Bdannzc}xotnw95b@QFQk6Nbzck|bZ3rbI*$kB4ce;@N zT(|yGOrz|B)>n_Wq47Amgq3h@3`?UR_#tG!y4MO_VmS!Y9%f^#VO|Q($cz5PS|hrI&E)h zH}3ozqHL?!xmUGMadv3T1h#67voCmT^ci&xo_bT;VrK<(Kq5k*8)6wlJJHsPOkupq zuL^9q@_*sO92}n()2?60oPYON@gB>Wx&{F}!|RG43<`t^d1;o3{NSsd74tKyrD^XM zFEwA~*PCH%PS_L5cvAj^@;O7sGY5VonpXwRz3IIM>@H$BsA=vEV5&BW<7E@(%Y>6OkP?dUUmCefH79Lo1lrF@7c)>RD=8s_8Lss^;^SE69<0T z3kT1)l52mP_Dj1!^K|yFyGIXq)XxO?+A3F$B}BrlZiS3F>xEs>3-t!(l9TRpE8Q!9 zu@x{dG4=m;;3;5OJNasry=aI3(QocHj7MpeM)V>+%(=tDTCeZ124zX50d6-dW)F3= zd|W@>aA=3nWl#)T;i0(U8II|F5`UCphzU1tg)LfLja4EcZ&8)$o&{pN8*wc#+ z+oR@0Sm=HmpI@#mZoE6LC&vx}E9Doj*aw6Fua?{IAB~rpf3jE3I)vIr5Pb`N5Ydkj zC&45&!Bj`uMUuE;Wl7@puxw(vtpdj;FF&c|-_6Ic rpDj}>r8@$a&1<$N&7U42%&(X_woaF0-0k!K{KZyR)={ccu#5a3{C_GS literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index b2fd3c01c6..23ef2c5ec2 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -149,6 +149,8 @@ "dist/credentials/TypeformApi.credentials.js", "dist/credentials/TypeformOAuth2Api.credentials.js", "dist/credentials/TogglApi.credentials.js", + "dist/credentials/TwakeCloudApi.credentials.js", + "dist/credentials/TwakeServerApi.credentials.js", "dist/credentials/UpleadApi.credentials.js", "dist/credentials/VeroApi.credentials.js", "dist/credentials/WebflowApi.credentials.js", @@ -318,6 +320,7 @@ "dist/nodes/Twilio/Twilio.node.js", "dist/nodes/Twitter/Twitter.node.js", "dist/nodes/Typeform/TypeformTrigger.node.js", + "dist/nodes/Twake/Twake.node.js", "dist/nodes/Uplead/Uplead.node.js", "dist/nodes/Vero/Vero.node.js", "dist/nodes/Webflow/WebflowTrigger.node.js",