refactor: Redesign twitter trigger and actions

This commit is contained in:
Faruk AYDIN
2022-10-06 10:46:22 +03:00
parent 63ffd1f720
commit 77624acc01
18 changed files with 332 additions and 50 deletions

View File

@@ -1,7 +1,7 @@
import qs from 'qs'; import qs from 'qs';
import { IGlobalVariableForConnection } from '@automatisch/types'; import { IGlobalVariable } from '@automatisch/types';
const verifyCredentials = async ($: IGlobalVariableForConnection) => { const verifyCredentials = async ($: IGlobalVariable) => {
const headers = { const headers = {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
}; };

View File

@@ -1,12 +1,8 @@
import generateRequest from '../common/generate-request'; import generateRequest from '../common/generate-request';
import { import { IJSONObject, IField, IGlobalVariable } from '@automatisch/types';
IJSONObject,
IField,
IGlobalVariableForConnection,
} from '@automatisch/types';
import { URLSearchParams } from 'url'; import { URLSearchParams } from 'url';
export default async function createAuthData($: IGlobalVariableForConnection) { export default async function createAuthData($: IGlobalVariable) {
try { try {
const oauthRedirectUrlField = $.app.fields.find( const oauthRedirectUrlField = $.app.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl' (field: IField) => field.key == 'oAuthRedirectUrl'

View File

@@ -1,7 +1,7 @@
import { IGlobalVariableForConnection } from '@automatisch/types'; import { IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user'; import getCurrentUser from '../common/get-current-user';
const isStillVerified = async ($: IGlobalVariableForConnection) => { const isStillVerified = async ($: IGlobalVariable) => {
try { try {
await getCurrentUser($); await getCurrentUser($);
return true; return true;

View File

@@ -1,7 +1,7 @@
import { IGlobalVariableForConnection } from '@automatisch/types'; import { IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url'; import { URLSearchParams } from 'url';
const verifyCredentials = async ($: IGlobalVariableForConnection) => { const verifyCredentials = async ($: IGlobalVariable) => {
try { try {
const response = await $.http.post( const response = await $.http.post(
`/oauth/access_token?oauth_verifier=${$.auth.data.oauthVerifier}&oauth_token=${$.auth.data.accessToken}`, `/oauth/access_token?oauth_verifier=${$.auth.data.oauthVerifier}&oauth_token=${$.auth.data.accessToken}`,

View File

@@ -1,4 +1,4 @@
import { IGlobalVariableForConnection, IJSONObject } from '@automatisch/types'; import { IGlobalVariable, IJSONObject } from '@automatisch/types';
import oauthClient from './oauth-client'; import oauthClient from './oauth-client';
import { Token } from 'oauth-1.0a'; import { Token } from 'oauth-1.0a';
@@ -9,7 +9,7 @@ type IGenereateRequestOptons = {
}; };
const generateRequest = async ( const generateRequest = async (
$: IGlobalVariableForConnection, $: IGlobalVariable,
options: IGenereateRequestOptons options: IGenereateRequestOptons
) => { ) => {
const { requestPath, method, data } = options; const { requestPath, method, data } = options;

View File

@@ -1,7 +1,7 @@
import { IGlobalVariableForConnection } from '@automatisch/types'; import { IGlobalVariable, IJSONObject } from '@automatisch/types';
import generateRequest from './generate-request'; import generateRequest from './generate-request';
const getCurrentUser = async ($: IGlobalVariableForConnection) => { const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
const response = await generateRequest($, { const response = await generateRequest($, {
requestPath: '/2/users/me', requestPath: '/2/users/me',
method: 'GET', method: 'GET',

View File

@@ -1,10 +1,7 @@
import { IGlobalVariableForConnection, IJSONObject } from '@automatisch/types'; import { IGlobalVariable, IJSONObject } from '@automatisch/types';
import generateRequest from './generate-request'; import generateRequest from './generate-request';
const getUserByUsername = async ( const getUserByUsername = async ($: IGlobalVariable, username: string) => {
$: IGlobalVariableForConnection,
username: string
) => {
const response = await generateRequest($, { const response = await generateRequest($, {
requestPath: `/2/users/by/username/${username}`, requestPath: `/2/users/by/username/${username}`,
method: 'GET', method: 'GET',

View File

@@ -0,0 +1,59 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
import { URLSearchParams } from 'url';
import { omitBy, isEmpty } from 'lodash';
import generateRequest from './generate-request';
type GetUserFollowersOptions = {
userId: string;
lastInternalId?: string;
};
const getUserFollowers = async (
$: IGlobalVariable,
options: GetUserFollowersOptions
) => {
let response;
const followers: IJSONObject[] = [];
do {
const params: IJSONObject = {
pagination_token: response?.data?.meta?.next_token,
};
const queryParams = new URLSearchParams(omitBy(params, isEmpty));
const requestPath = `/2/users/${options.userId}/followers${
queryParams.toString() ? `?${queryParams.toString()}` : ''
}`;
response = await generateRequest($, {
requestPath,
method: 'GET',
});
if (response.data.meta.result_count > 0) {
response.data.data.forEach((tweet: IJSONObject) => {
if (
!options.lastInternalId ||
Number(tweet.id) > Number(options.lastInternalId)
) {
followers.push(tweet);
} else {
return;
}
});
}
} while (response.data.meta.next_token && options.lastInternalId);
if (response.data?.errors) {
const errorMessages = response.data.errors
.map((error: IJSONObject) => error.detail)
.join(' ');
throw new Error(`Error occured while fetching user data: ${errorMessages}`);
}
return followers;
};
export default getUserFollowers;

View File

@@ -1,26 +1,44 @@
import { IGlobalVariableForConnection, IJSONObject } from '@automatisch/types'; import { IGlobalVariable, IJSONObject } from '@automatisch/types';
import { URLSearchParams } from 'url'; import { URLSearchParams } from 'url';
import omitBy from 'lodash/omitBy'; import omitBy from 'lodash/omitBy';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import generateRequest from './generate-request'; import generateRequest from './generate-request';
import getCurrentUser from './get-current-user';
import getUserByUsername from './get-user-by-username';
type IGetUserTweetsOptions = {
currentUser: boolean;
userId?: string;
lastInternalId?: string;
};
const getUserTweets = async ( const getUserTweets = async (
$: IGlobalVariableForConnection, $: IGlobalVariable,
userId: string, options: IGetUserTweetsOptions
lastInternalId?: string
) => { ) => {
let username: string;
if (options.currentUser) {
const currentUser = await getCurrentUser($);
username = currentUser.username as string;
} else {
username = $.db.step.parameters.username as string;
}
const user = await getUserByUsername($, username);
let response; let response;
const tweets: IJSONObject[] = []; const tweets: IJSONObject[] = [];
do { do {
const params: IJSONObject = { const params: IJSONObject = {
since_id: lastInternalId, since_id: options.lastInternalId,
pagination_token: response?.data?.meta?.next_token, pagination_token: response?.data?.meta?.next_token,
}; };
const queryParams = new URLSearchParams(omitBy(params, isEmpty)); const queryParams = new URLSearchParams(omitBy(params, isEmpty));
const requestPath = `/2/users/${userId}/tweets${ const requestPath = `/2/users/${user.id}/tweets${
queryParams.toString() ? `?${queryParams.toString()}` : '' queryParams.toString() ? `?${queryParams.toString()}` : ''
}`; }`;
@@ -31,14 +49,17 @@ const getUserTweets = async (
if (response.data.meta.result_count > 0) { if (response.data.meta.result_count > 0) {
response.data.data.forEach((tweet: IJSONObject) => { response.data.data.forEach((tweet: IJSONObject) => {
if (!lastInternalId || Number(tweet.id) > Number(lastInternalId)) { if (
!options.lastInternalId ||
Number(tweet.id) > Number(options.lastInternalId)
) {
tweets.push(tweet); tweets.push(tweet);
} else { } else {
return; return;
} }
}); });
} }
} while (response.data.meta.next_token && lastInternalId); } while (response.data.meta.next_token && options.lastInternalId);
if (response.data.errors) { if (response.data.errors) {
const errorMessages = response.data.errors const errorMessages = response.data.errors

View File

@@ -1,8 +1,8 @@
import { IGlobalVariableForConnection } from '@automatisch/types'; import { IGlobalVariable } from '@automatisch/types';
import crypto from 'crypto'; import crypto from 'crypto';
import OAuth from 'oauth-1.0a'; import OAuth from 'oauth-1.0a';
const oauthClient = ($: IGlobalVariableForConnection) => { const oauthClient = ($: IGlobalVariable) => {
const consumerData = { const consumerData = {
key: $.auth.data.consumerKey as string, key: $.auth.data.consumerKey as string,
secret: $.auth.data.consumerSecret as string, secret: $.auth.data.consumerSecret as string,

View File

@@ -1,6 +1,4 @@
import { IGlobalVariable } from '@automatisch/types'; import { IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../../common/get-current-user';
import getUserByUsername from '../../common/get-user-by-username';
import getUserTweets from '../../common/get-user-tweets'; import getUserTweets from '../../common/get-user-tweets';
export default { export default {
@@ -20,18 +18,13 @@ export default {
], ],
async run($: IGlobalVariable) { async run($: IGlobalVariable) {
return this.getTweets($, await $.db.flow.lastInternalId()); return await getUserTweets($, {
currentUser: true,
lastInternalId: $.db.flow.lastInternalId,
});
}, },
async testRun($: IGlobalVariable) { async testRun($: IGlobalVariable) {
return this.getTweets($); return await getUserTweets($, { currentUser: true });
},
async getTweets($: IGlobalVariable, lastInternalId?: string) {
const { username } = await getCurrentUser($);
const user = await getUserByUsername($, username);
const tweets = await getUserTweets($, user.id, lastInternalId);
return tweets;
}, },
}; };

View File

@@ -0,0 +1,27 @@
import { IGlobalVariable } from '@automatisch/types';
import myFollowers from './my-followers';
export default {
name: 'New follower of me',
key: 'myFollowers',
pollInterval: 15,
description: 'Will be triggered when you have a new follower.',
substeps: [
{
key: 'chooseConnection',
name: 'Choose connection',
},
{
key: 'testStep',
name: 'Test trigger',
},
],
async run($: IGlobalVariable) {
return await myFollowers($, $.db.flow.lastInternalId);
},
async testRun($: IGlobalVariable) {
return await myFollowers($);
},
};

View File

@@ -0,0 +1,17 @@
import { IGlobalVariable } from "@automatisch/types";
import getCurrentUser from "../../common/get-current-user";
import getUserByUsername from "../../common/get-user-by-username";
import getUserFollowers from "../../common/get-user-followers";
const myFollowers = async( $: IGlobalVariable, lastInternalId?: string) => {
const { username } = await getCurrentUser($);
const user = await getUserByUsername($, username as string);
const tweets = await getUserFollowers($, {
userId: user.id,
lastInternalId
});
return tweets;
});
export default myFollowers;

View File

@@ -0,0 +1,45 @@
import { IGlobalVariable } from '@automatisch/types';
import searchTweets from './search-tweets';
export default {
name: 'Search Tweets',
key: 'searchTweets',
pollInterval: 15,
description:
'Will be triggered when any user tweet something containing a specific keyword, phrase, username or hashtag.',
substeps: [
{
key: 'chooseConnection',
name: 'Choose connection',
},
{
key: 'chooseTrigger',
name: 'Set up a trigger',
arguments: [
{
label: 'Search Term',
key: 'searchTerm',
type: 'string',
required: true,
},
],
},
{
key: 'testStep',
name: 'Test trigger',
},
],
async run($: IGlobalVariable) {
return await searchTweets($, {
searchTerm: $.db.step.parameters.searchTerm as string,
lastInternalId: $.db.flow.lastInternalId,
});
},
async testRun($: IGlobalVariable) {
return await searchTweets($, {
searchTerm: $.db.step.parameters.searchTerm as string,
});
},
};

View File

@@ -0,0 +1,65 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
import qs from 'qs';
import generateRequest from '../../common/generate-request';
import { omitBy, isEmpty } from 'lodash';
type ISearchTweetsOptions = {
searchTerm: string;
lastInternalId?: string;
};
const searchTweets = async (
$: IGlobalVariable,
options: ISearchTweetsOptions
) => {
let response;
const tweets: {
data: IJSONObject[];
error: IJSONObject | null;
} = {
data: [],
error: null,
};
do {
const params: IJSONObject = {
query: options.searchTerm,
since_id: options.lastInternalId,
pagination_token: response?.data?.meta?.next_token,
};
const queryParams = qs.stringify(omitBy(params, isEmpty));
const requestPath = `/2/tweets/search/recent${
queryParams.toString() ? `?${queryParams.toString()}` : ''
}`;
response = await generateRequest($, {
requestPath,
method: 'GET',
});
if (response.integrationError) {
tweets.error = response.integrationError;
return tweets;
}
if (response.data.meta.result_count > 0) {
response.data.data.forEach((tweet: IJSONObject) => {
if (
!options.lastInternalId ||
Number(tweet.id) > Number(options.lastInternalId)
) {
tweets.data.push(tweet);
} else {
return;
}
});
}
} while (response.data.meta.next_token && options.lastInternalId);
return tweets;
};
export default searchTweets;

View File

@@ -0,0 +1,46 @@
import { IGlobalVariable } from '@automatisch/types';
import getUserTweets from '../../common/get-user-tweets';
export default {
name: 'User Tweets',
key: 'userTweets',
pollInterval: 15,
description: 'Will be triggered when a specific user tweet something new.',
substeps: [
{
key: 'chooseConnection',
name: 'Choose connection',
},
{
key: 'chooseTrigger',
name: 'Set up a trigger',
arguments: [
{
label: 'Username',
key: 'username',
type: 'string',
required: true,
},
],
},
{
key: 'testStep',
name: 'Test trigger',
},
],
async run($: IGlobalVariable) {
return await getUserTweets($, {
currentUser: false,
userId: $.db.step.parameters.username as string,
lastInternalId: $.db.flow.lastInternalId,
});
},
async testRun($: IGlobalVariable) {
return await getUserTweets($, {
currentUser: false,
userId: $.db.step.parameters.username as string,
});
},
};

View File

@@ -1,29 +1,40 @@
import createHttpClient from './http-client'; import createHttpClient from './http-client';
import Connection from '../models/connection'; import Connection from '../models/connection';
import Flow from '../models/flow'; import Flow from '../models/flow';
import Step from '../models/step';
import { IJSONObject, IApp, IGlobalVariable } from '@automatisch/types'; import { IJSONObject, IApp, IGlobalVariable } from '@automatisch/types';
const globalVariable = ( const globalVariable = async (
connection: Connection, connection: Connection,
appData: IApp, appData: IApp,
flow?: Flow flow?: Flow,
): IGlobalVariable => { currentStep?: Step
): Promise<IGlobalVariable> => {
const lastInternalId = await flow?.lastInternalId();
return { return {
auth: { auth: {
set: async (args: IJSONObject) => { set: async (args: IJSONObject) => {
return await connection.$query().patchAndFetch({ await connection.$query().patchAndFetch({
formattedData: { formattedData: {
...connection.formattedData, ...connection.formattedData,
...args, ...args,
}, },
}); });
return null;
}, },
data: connection.formattedData, data: connection.formattedData,
}, },
app: appData, app: appData,
http: createHttpClient({ baseURL: appData.baseUrl }), http: createHttpClient({ baseURL: appData.baseUrl }),
db: { db: {
flow: flow, flow: {
lastInternalId,
},
step: {
parameters: currentStep?.parameters || {},
},
}, },
}; };
}; };

View File

@@ -197,13 +197,18 @@ export type IHttpClientParams = {
export type IGlobalVariable = { export type IGlobalVariable = {
auth: { auth: {
set: (args: IJSONObject) => Promise<IConnection>; set: (args: IJSONObject) => Promise<null>;
data: IJSONObject; data: IJSONObject;
}; };
app: IApp; app: IApp;
http: IHttpClient; http: IHttpClient;
db: { db: {
flow: IFlow; flow: {
lastInternalId: string;
};
step: {
parameters: IJSONObject;
}
}; };
}; };