2023-01-27 03:22:44 -08:00
|
|
|
import type { OptionsWithUrl } from 'request';
|
2020-06-01 17:42:38 -07:00
|
|
|
|
2023-01-27 03:22:44 -08:00
|
|
|
import type {
|
2020-06-01 17:42:38 -07:00
|
|
|
IExecuteFunctions,
|
|
|
|
IExecuteSingleFunctions,
|
2020-10-01 05:01:39 -07:00
|
|
|
IHookFunctions,
|
2020-06-01 17:42:38 -07:00
|
|
|
ILoadOptionsFunctions,
|
|
|
|
} from 'n8n-core';
|
|
|
|
|
2023-01-27 03:22:44 -08:00
|
|
|
import type { IBinaryKeyData, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
|
|
|
import { NodeApiError, NodeOperationError, sleep } from 'n8n-workflow';
|
2020-06-01 17:42:38 -07:00
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
export async function twitterApiRequest(
|
|
|
|
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions,
|
|
|
|
method: string,
|
|
|
|
resource: string,
|
2022-12-02 06:25:21 -08:00
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
body: any = {},
|
|
|
|
qs: IDataObject = {},
|
|
|
|
uri?: string,
|
|
|
|
option: IDataObject = {},
|
|
|
|
): Promise<any> {
|
2020-06-01 17:42:38 -07:00
|
|
|
let options: OptionsWithUrl = {
|
|
|
|
method,
|
|
|
|
body,
|
|
|
|
qs,
|
2023-01-19 04:37:19 -08:00
|
|
|
url: uri || `https://api.twitter.com/1.1${resource}`,
|
2020-10-22 06:46:03 -07:00
|
|
|
json: true,
|
2020-06-01 17:42:38 -07:00
|
|
|
};
|
|
|
|
try {
|
|
|
|
if (Object.keys(option).length !== 0) {
|
|
|
|
options = Object.assign({}, options, option);
|
|
|
|
}
|
|
|
|
if (Object.keys(body).length === 0) {
|
|
|
|
delete options.body;
|
|
|
|
}
|
2020-11-03 14:01:38 -08:00
|
|
|
if (Object.keys(qs).length === 0) {
|
|
|
|
delete options.qs;
|
|
|
|
}
|
2020-06-01 17:42:38 -07:00
|
|
|
//@ts-ignore
|
|
|
|
return await this.helpers.requestOAuth1.call(this, 'twitterOAuth1Api', options);
|
|
|
|
} catch (error) {
|
2021-04-16 09:33:36 -07:00
|
|
|
throw new NodeApiError(this.getNode(), error);
|
2020-06-01 17:42:38 -07:00
|
|
|
}
|
|
|
|
}
|
2020-06-07 14:29:13 -07:00
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
export async function twitterApiRequestAllItems(
|
|
|
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
|
|
|
propertyName: string,
|
|
|
|
method: string,
|
|
|
|
endpoint: string,
|
2022-12-02 06:25:21 -08:00
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
body: any = {},
|
|
|
|
query: IDataObject = {},
|
|
|
|
): Promise<any> {
|
2020-06-07 14:29:13 -07:00
|
|
|
const returnData: IDataObject[] = [];
|
|
|
|
|
|
|
|
let responseData;
|
2020-08-10 09:19:26 -07:00
|
|
|
|
2020-06-07 14:29:13 -07:00
|
|
|
query.count = 100;
|
2020-08-10 09:19:26 -07:00
|
|
|
|
2020-06-07 14:29:13 -07:00
|
|
|
do {
|
|
|
|
responseData = await twitterApiRequest.call(this, method, endpoint, body, query);
|
|
|
|
query.since_id = responseData.search_metadata.max_id;
|
|
|
|
returnData.push.apply(returnData, responseData[propertyName]);
|
2022-12-02 12:54:28 -08:00
|
|
|
} while (responseData.search_metadata?.next_results);
|
2020-06-07 14:29:13 -07:00
|
|
|
|
|
|
|
return returnData;
|
|
|
|
}
|
|
|
|
|
2020-11-03 14:01:59 -08:00
|
|
|
export function chunks(buffer: Buffer, chunkSize: number) {
|
2020-06-07 14:29:13 -07:00
|
|
|
const result = [];
|
|
|
|
const len = buffer.length;
|
|
|
|
let i = 0;
|
|
|
|
|
|
|
|
while (i < len) {
|
2022-08-17 08:50:24 -07:00
|
|
|
result.push(buffer.slice(i, (i += chunkSize)));
|
2020-06-07 14:29:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2020-11-03 14:01:38 -08:00
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
export async function uploadAttachments(
|
|
|
|
this: IExecuteFunctions,
|
|
|
|
binaryProperties: string[],
|
|
|
|
items: INodeExecutionData[],
|
|
|
|
i: number,
|
|
|
|
) {
|
2020-11-03 14:01:38 -08:00
|
|
|
const uploadUri = 'https://upload.twitter.com/1.1/media/upload.json';
|
2020-11-03 14:01:59 -08:00
|
|
|
|
2020-11-03 14:01:38 -08:00
|
|
|
const media: IDataObject[] = [];
|
|
|
|
|
|
|
|
for (const binaryPropertyName of binaryProperties) {
|
|
|
|
const binaryData = items[i].binary as IBinaryKeyData;
|
|
|
|
|
|
|
|
if (binaryData === undefined) {
|
2022-08-17 08:50:24 -07:00
|
|
|
throw new NodeOperationError(
|
|
|
|
this.getNode(),
|
|
|
|
'No binary data set. So file can not be written!',
|
|
|
|
{ itemIndex: i },
|
|
|
|
);
|
2020-11-03 14:01:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!binaryData[binaryPropertyName]) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let attachmentBody = {};
|
|
|
|
let response: IDataObject = {};
|
|
|
|
|
2021-08-20 09:08:40 -07:00
|
|
|
const dataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
2022-08-17 08:50:24 -07:00
|
|
|
const isAnimatedWebp = dataBuffer.toString().indexOf('ANMF') !== -1;
|
2020-11-03 14:01:38 -08:00
|
|
|
|
|
|
|
const isImage = binaryData[binaryPropertyName].mimeType.includes('image');
|
|
|
|
|
|
|
|
if (isImage && isAnimatedWebp) {
|
2022-08-17 08:50:24 -07:00
|
|
|
throw new NodeOperationError(
|
|
|
|
this.getNode(),
|
|
|
|
'Animated .webp images are not supported use .gif instead',
|
|
|
|
{ itemIndex: i },
|
|
|
|
);
|
2020-11-03 14:01:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isImage) {
|
2022-12-02 12:54:28 -08:00
|
|
|
const form = {
|
2020-11-03 14:01:38 -08:00
|
|
|
media_data: binaryData[binaryPropertyName].data,
|
|
|
|
};
|
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
response = await twitterApiRequest.call(this, 'POST', '', {}, {}, uploadUri, {
|
2022-12-02 12:54:28 -08:00
|
|
|
form,
|
2022-08-17 08:50:24 -07:00
|
|
|
});
|
2020-11-03 14:01:38 -08:00
|
|
|
|
|
|
|
media.push(response);
|
|
|
|
} else {
|
|
|
|
// https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-init
|
|
|
|
|
2022-12-02 12:54:28 -08:00
|
|
|
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
2021-08-20 09:08:40 -07:00
|
|
|
|
2020-11-03 14:01:38 -08:00
|
|
|
attachmentBody = {
|
|
|
|
command: 'INIT',
|
2022-12-02 12:54:28 -08:00
|
|
|
total_bytes: binaryDataBuffer.byteLength,
|
2020-11-03 14:01:38 -08:00
|
|
|
media_type: binaryData[binaryPropertyName].mimeType,
|
|
|
|
};
|
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
response = await twitterApiRequest.call(this, 'POST', '', {}, {}, uploadUri, {
|
|
|
|
form: attachmentBody,
|
|
|
|
});
|
2020-11-03 14:01:38 -08:00
|
|
|
|
|
|
|
const mediaId = response.media_id_string;
|
|
|
|
|
|
|
|
// break the data on 5mb chunks (max size that can be uploaded at once)
|
|
|
|
|
2022-12-02 12:54:28 -08:00
|
|
|
const binaryParts = chunks(binaryDataBuffer, 5242880);
|
2020-11-03 14:01:38 -08:00
|
|
|
|
|
|
|
let index = 0;
|
|
|
|
|
|
|
|
for (const binaryPart of binaryParts) {
|
|
|
|
//https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-append
|
|
|
|
|
|
|
|
attachmentBody = {
|
|
|
|
name: binaryData[binaryPropertyName].fileName,
|
|
|
|
command: 'APPEND',
|
|
|
|
media_id: mediaId,
|
|
|
|
media_data: Buffer.from(binaryPart).toString('base64'),
|
|
|
|
segment_index: index,
|
|
|
|
};
|
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
response = await twitterApiRequest.call(this, 'POST', '', {}, {}, uploadUri, {
|
|
|
|
form: attachmentBody,
|
|
|
|
});
|
2020-11-03 14:01:38 -08:00
|
|
|
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
//https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-finalize
|
|
|
|
|
|
|
|
attachmentBody = {
|
|
|
|
command: 'FINALIZE',
|
|
|
|
media_id: mediaId,
|
|
|
|
};
|
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
response = await twitterApiRequest.call(this, 'POST', '', {}, {}, uploadUri, {
|
|
|
|
form: attachmentBody,
|
|
|
|
});
|
2020-11-03 14:01:38 -08:00
|
|
|
|
|
|
|
// data has not been uploaded yet, so wait for it to be ready
|
|
|
|
if (response.processing_info) {
|
2022-08-17 08:50:24 -07:00
|
|
|
const { check_after_secs } = response.processing_info as IDataObject;
|
2022-11-08 08:06:00 -08:00
|
|
|
await sleep((check_after_secs as number) * 1000);
|
2020-11-03 14:01:38 -08:00
|
|
|
}
|
2020-11-03 14:01:59 -08:00
|
|
|
|
2020-11-03 14:01:38 -08:00
|
|
|
media.push(response);
|
|
|
|
}
|
|
|
|
|
|
|
|
return media;
|
|
|
|
}
|
2020-11-03 14:01:59 -08:00
|
|
|
}
|