fix(X (Formerly Twitter) Node): Change how tweet id is retrieved from quote URL (#9635)

Co-authored-by: Jonathan Bennetts <jonathan.bennetts@gmail.com>
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
Ria Scholz 2024-06-06 09:02:38 +02:00 committed by GitHub
parent be0dee2a15
commit 9853ecc5bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 73 additions and 6 deletions

View file

@ -83,12 +83,19 @@ export function returnId(tweetId: INodeParameterResourceLocator) {
if (tweetId.mode === 'id') {
return tweetId.value as string;
} else if (tweetId.mode === 'url') {
const value = tweetId.value as string;
const tweetIdMatch = value.includes('lists')
? value.match(/^https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/list(s)?\/(\d+)$/)
: value.match(/^https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(es)?\/(\d+)$/);
return tweetIdMatch?.[3] as string;
try {
const url = new URL(tweetId.value as string);
if (!/(twitter|x).com$/.test(url.hostname)) {
throw new ApplicationError('Invalid domain');
}
const parts = url.pathname.split('/');
if (parts.length !== 4 || parts[2] !== 'status' || !/^\d+$/.test(parts[3])) {
throw new ApplicationError('Invalid path');
}
return parts[3];
} catch (error) {
throw new ApplicationError('Not a valid tweet url', { level: 'warning', cause: error });
}
} else {
throw new ApplicationError(`The mode ${tweetId.mode} is not valid!`, { level: 'warning' });
}

View file

@ -1,4 +1,6 @@
import nock from 'nock';
import type { INodeParameterResourceLocator } from 'n8n-workflow';
import { returnId } from '../V2/GenericFunctions';
import { getWorkflowFilenames, testWorkflows } from '@test/nodes/Helpers';
const searchResult = {
@ -66,6 +68,7 @@ const searchResult = {
const meResult = {
data: { id: '1285192200213626880', name: 'Integration-n8n', username: 'IntegrationN8n' },
};
describe('Test Twitter Request Node', () => {
beforeAll(() => {
const baseUrl = 'https://api.twitter.com/2';
@ -85,3 +88,60 @@ describe('Test Twitter Request Node', () => {
const workflows = getWorkflowFilenames(__dirname);
testWorkflows(workflows);
});
describe('X / Twitter Node unit tests', () => {
describe('returnId', () => {
it('should return the id when mode is id', () => {
const tweetId: INodeParameterResourceLocator = {
__rl: true,
mode: 'id',
value: '12345',
};
expect(returnId(tweetId)).toBe('12345');
});
it('should extract the tweetId from url when the domain is twitter.com', () => {
const tweetId: INodeParameterResourceLocator = {
__rl: true,
mode: 'url',
value: 'https://twitter.com/user/status/12345?utm=6789',
};
expect(returnId(tweetId)).toBe('12345');
});
it('should extract the tweetId from url when the domain is x.com', () => {
const tweetId: INodeParameterResourceLocator = {
__rl: true,
mode: 'url',
value: 'https://x.com/user/status/12345?utm=6789',
};
expect(returnId(tweetId)).toBe('12345');
});
it('should throw an error when mode is not valid', () => {
const tweetId: INodeParameterResourceLocator = {
__rl: true,
mode: 'invalid',
value: 'https://twitter.com/user/status/12345',
};
expect(() => returnId(tweetId)).toThrow();
});
describe('should throw an error when the URL is not valid', () => {
test.each([
'https://twitter.com/user/',
'https://twitter.com/user/status/',
'https://twitter.com/user/profile/12345',
'https://twitter.com/search?param=12345',
])('%s', (value) => {
expect(() =>
returnId({
__rl: true,
mode: 'url',
value,
}),
).toThrow();
});
});
});
});