chore: Use get app helper to get application data

This commit is contained in:
Faruk AYDIN
2022-10-06 15:49:05 +03:00
parent a58a59788d
commit 0825eb36e4
52 changed files with 139 additions and 1761 deletions

View File

@@ -1,12 +0,0 @@
import TwitterClient from './client';
import CreateTweet from './actions/create-tweet';
export default class Actions {
client: TwitterClient;
createTweet: CreateTweet;
constructor(client: TwitterClient) {
this.client = client;
this.createTweet = new CreateTweet(client);
}
}

View File

@@ -1,17 +0,0 @@
import TwitterClient from '../client';
export default class CreateTweet {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run() {
const tweet = await this.client.createTweet.run(
this.client.step.parameters.tweet as string
);
return tweet;
}
}

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-label="Twitter" role="img" viewBox="0 0 512 512">
<rect width="512" height="512" rx="15%" fill="#1da1f2"/>
<path fill="#fff" d="M437 152a72 72 0 01-40 12a72 72 0 0032-40a72 72 0 01-45 17a72 72 0 00-122 65a200 200 0 01-145-74a72 72 0 0022 94a72 72 0 01-32-7a72 72 0 0056 69a72 72 0 01-32 1a72 72 0 0067 50a200 200 0 01-105 29a200 200 0 00309-179a200 200 0 0035-37"/>
</svg>

Before

Width:  |  Height:  |  Size: 422 B

View File

@@ -1,51 +0,0 @@
import type { IAuthentication, IField } from '@automatisch/types';
import { URLSearchParams } from 'url';
import TwitterClient from './client';
export default class Authentication implements IAuthentication {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async createAuthData() {
const appFields = this.client.connection.appData.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const callbackUrl = appFields.value;
const response = await this.client.oauthRequestToken.run(callbackUrl);
const responseData = Object.fromEntries(new URLSearchParams(response.data));
return {
url: `${TwitterClient.baseUrl}/oauth/authorize?oauth_token=${responseData.oauth_token}`,
accessToken: responseData.oauth_token,
accessSecret: responseData.oauth_token_secret,
};
}
async verifyCredentials() {
const response = await this.client.verifyAccessToken.run();
const responseData = Object.fromEntries(new URLSearchParams(response.data));
return {
consumerKey: this.client.connection.formattedData.consumerKey as string,
consumerSecret: this.client.connection.formattedData
.consumerSecret as string,
accessToken: responseData.oauth_token,
accessSecret: responseData.oauth_token_secret,
userId: responseData.user_id,
screenName: responseData.screen_name,
};
}
async isStillVerified() {
try {
await this.client.getCurrentUser.run();
return true;
} catch (error) {
return false;
}
}
}

View File

@@ -1,40 +0,0 @@
import TwitterClient from '../index';
export default class CreateTweet {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run(text: string) {
try {
const token = {
key: this.client.connection.formattedData.accessToken as string,
secret: this.client.connection.formattedData.accessSecret as string,
};
const requestData = {
url: `${TwitterClient.baseUrl}/2/tweets`,
method: 'POST',
};
const authHeader = this.client.oauthClient.toHeader(
this.client.oauthClient.authorize(requestData, token)
);
const response = await this.client.httpClient.post(
`/2/tweets`,
{ text },
{ headers: { ...authHeader } }
);
const tweet = response.data.data;
return tweet;
} catch (error) {
const errorMessage = error.response.data.detail;
throw new Error(`Error occured while creating a tweet: ${errorMessage}`);
}
}
}

View File

@@ -1,35 +0,0 @@
import TwitterClient from '../index';
export default class GetCurrentUser {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run() {
const token = {
key: this.client.connection.formattedData.accessToken as string,
secret: this.client.connection.formattedData.accessSecret as string,
};
const requestPath = '/2/users/me';
const requestData = {
url: `${TwitterClient.baseUrl}${requestPath}`,
method: 'GET',
};
const authHeader = this.client.oauthClient.toHeader(
this.client.oauthClient.authorize(requestData, token)
);
const response = await this.client.httpClient.get(requestPath, {
headers: { ...authHeader },
});
const currentUser = response.data.data;
return currentUser;
}
}

View File

@@ -1,45 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import TwitterClient from '../index';
export default class GetUserByUsername {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run(username: string) {
const token = {
key: this.client.connection.formattedData.accessToken as string,
secret: this.client.connection.formattedData.accessSecret as string,
};
const requestPath = `/2/users/by/username/${username}`;
const requestData = {
url: `${TwitterClient.baseUrl}${requestPath}`,
method: 'GET',
};
const authHeader = this.client.oauthClient.toHeader(
this.client.oauthClient.authorize(requestData, token)
);
const response = await this.client.httpClient.get(requestPath, {
headers: { ...authHeader },
});
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}`
);
}
const user = response.data.data;
return user;
}
}

View File

@@ -1,70 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import { URLSearchParams } from 'url';
import TwitterClient from '../index';
import omitBy from 'lodash/omitBy';
import isEmpty from 'lodash/isEmpty';
export default class GetUserFollowers {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run(userId: string, lastInternalId?: string) {
const token = {
key: this.client.connection.formattedData.accessToken as string,
secret: this.client.connection.formattedData.accessSecret as string,
};
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/${userId}/followers${
queryParams.toString() ? `?${queryParams.toString()}` : ''
}`;
const requestData = {
url: `${TwitterClient.baseUrl}${requestPath}`,
method: 'GET',
};
const authHeader = this.client.oauthClient.toHeader(
this.client.oauthClient.authorize(requestData, token)
);
response = await this.client.httpClient.get(requestPath, {
headers: { ...authHeader },
});
if (response.data.meta.result_count > 0) {
response.data.data.forEach((tweet: IJSONObject) => {
if (!lastInternalId || Number(tweet.id) > Number(lastInternalId)) {
followers.push(tweet);
} else {
return;
}
});
}
} while (response.data.meta.next_token && 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;
}
}

View File

@@ -1,71 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import { URLSearchParams } from 'url';
import TwitterClient from '../index';
import omitBy from 'lodash/omitBy';
import isEmpty from 'lodash/isEmpty';
export default class GetUserTweets {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run(userId: string, lastInternalId?: string) {
const token = {
key: this.client.connection.formattedData.accessToken as string,
secret: this.client.connection.formattedData.accessSecret as string,
};
let response;
const tweets: IJSONObject[] = [];
do {
const params: IJSONObject = {
since_id: lastInternalId,
pagination_token: response?.data?.meta?.next_token,
};
const queryParams = new URLSearchParams(omitBy(params, isEmpty));
const requestPath = `/2/users/${userId}/tweets${
queryParams.toString() ? `?${queryParams.toString()}` : ''
}`;
const requestData = {
url: `${TwitterClient.baseUrl}${requestPath}`,
method: 'GET',
};
const authHeader = this.client.oauthClient.toHeader(
this.client.oauthClient.authorize(requestData, token)
);
response = await this.client.httpClient.get(requestPath, {
headers: { ...authHeader },
});
if (response.data.meta.result_count > 0) {
response.data.data.forEach((tweet: IJSONObject) => {
if (!lastInternalId || Number(tweet.id) > Number(lastInternalId)) {
tweets.push(tweet);
} else {
return;
}
});
}
} while (response.data.meta.next_token && 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 tweets;
}
}

View File

@@ -1,42 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import TwitterClient from '../index';
export default class OAuthRequestToken {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run(callbackUrl: string) {
try {
const requestData = {
url: `${TwitterClient.baseUrl}/oauth/request_token`,
method: 'POST',
data: { oauth_callback: callbackUrl },
};
const authHeader = this.client.oauthClient.toHeader(
this.client.oauthClient.authorize(requestData)
);
const response = await this.client.httpClient.post(
`/oauth/request_token`,
null,
{
headers: { ...authHeader },
}
);
return response;
} catch (error) {
const errorMessages = error.response.data.errors
.map((error: IJSONObject) => error.message)
.join(' ');
throw new Error(
`Error occured while verifying credentials: ${errorMessages}`
);
}
}
}

View File

@@ -1,74 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import { URLSearchParams } from 'url';
import TwitterClient from '../index';
import omitBy from 'lodash/omitBy';
import isEmpty from 'lodash/isEmpty';
import qs from 'qs';
export default class SearchTweets {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run(searchTerm: string, lastInternalId?: string) {
const token = {
key: this.client.connection.formattedData.accessToken as string,
secret: this.client.connection.formattedData.accessSecret as string,
};
let response;
const tweets: {
data: IJSONObject[];
error: IJSONObject | null;
} = {
data: [],
error: null,
};
do {
const params: IJSONObject = {
query: searchTerm,
since_id: 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()}` : ''
}`;
const requestData = {
url: `${TwitterClient.baseUrl}${requestPath}`,
method: 'GET',
};
const authHeader = this.client.oauthClient.toHeader(
this.client.oauthClient.authorize(requestData, token)
);
response = await this.client.httpClient.get(requestPath, {
headers: { ...authHeader },
});
if (response.integrationError) {
tweets.error = response.integrationError;
return tweets;
}
if (response.data.meta.result_count > 0) {
response.data.data.forEach((tweet: IJSONObject) => {
if (!lastInternalId || Number(tweet.id) > Number(lastInternalId)) {
tweets.data.push(tweet);
} else {
return;
}
});
}
} while (response.data.meta.next_token && lastInternalId);
return tweets;
}
}

View File

@@ -1,20 +0,0 @@
import TwitterClient from '../index';
export default class VerifyAccessToken {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run() {
try {
return await this.client.httpClient.post(
`/oauth/access_token?oauth_verifier=${this.client.connection.formattedData.oauthVerifier}&oauth_token=${this.client.connection.formattedData.accessToken}`,
null
);
} catch (error) {
throw new Error(error.response.data);
}
}
}

View File

@@ -1,64 +0,0 @@
import { IFlow, IStep, IConnection } from '@automatisch/types';
import OAuth from 'oauth-1.0a';
import crypto from 'crypto';
import createHttpClient, { IHttpClient } from '../../../helpers/http-client';
import OAuthRequestToken from './endpoints/oauth-request-token';
import VerifyAccessToken from './endpoints/verify-access-token';
import GetCurrentUser from './endpoints/get-current-user';
import GetUserByUsername from './endpoints/get-user-by-username';
import GetUserTweets from './endpoints/get-user-tweets';
import CreateTweet from './endpoints/create-tweet';
import SearchTweets from './endpoints/search-tweets';
import GetUserFollowers from './endpoints/get-user-followers';
export default class TwitterClient {
flow: IFlow;
step: IStep;
connection: IConnection;
oauthClient: OAuth;
httpClient: IHttpClient;
oauthRequestToken: OAuthRequestToken;
verifyAccessToken: VerifyAccessToken;
getCurrentUser: GetCurrentUser;
getUserByUsername: GetUserByUsername;
getUserTweets: GetUserTweets;
createTweet: CreateTweet;
searchTweets: SearchTweets;
getUserFollowers: GetUserFollowers;
static baseUrl = 'https://api.twitter.com';
constructor(connection: IConnection, flow?: IFlow, step?: IStep) {
this.connection = connection;
this.flow = flow;
this.step = step;
this.httpClient = createHttpClient({ baseURL: TwitterClient.baseUrl });
const consumerData = {
key: this.connection.formattedData.consumerKey as string,
secret: this.connection.formattedData.consumerSecret as string,
};
this.oauthClient = new OAuth({
consumer: consumerData,
signature_method: 'HMAC-SHA1',
hash_function(base_string, key) {
return crypto
.createHmac('sha1', key)
.update(base_string)
.digest('base64');
},
});
this.oauthRequestToken = new OAuthRequestToken(this);
this.verifyAccessToken = new VerifyAccessToken(this);
this.getCurrentUser = new GetCurrentUser(this);
this.getUserByUsername = new GetUserByUsername(this);
this.getUserTweets = new GetUserTweets(this);
this.createTweet = new CreateTweet(this);
this.searchTweets = new SearchTweets(this);
this.getUserFollowers = new GetUserFollowers(this);
}
}

View File

@@ -1,27 +0,0 @@
import {
IService,
IAuthentication,
IFlow,
IStep,
IConnection,
} from '@automatisch/types';
import Authentication from './authentication';
import Triggers from './triggers';
import Actions from './actions';
import TwitterClient from './client';
export default class Twitter implements IService {
client: TwitterClient;
authenticationClient: IAuthentication;
triggers: Triggers;
actions: Actions;
constructor(connection: IConnection, flow?: IFlow, step?: IStep) {
this.client = new TwitterClient(connection, flow, step);
this.authenticationClient = new Authentication(this.client);
this.triggers = new Triggers(this.client);
this.actions = new Actions(this.client);
}
}

View File

@@ -1,339 +0,0 @@
{
"name": "Twitter",
"key": "twitter",
"iconUrl": "{BASE_URL}/apps/twitter/assets/favicon.svg",
"docUrl": "https://automatisch.io/docs/twitter",
"authDocUrl": "https://automatisch.io/docs/connections/twitter",
"primaryColor": "2DAAE1",
"supportsConnections": true,
"baseUrl": "https://api.twitter.com",
"fields": [
{
"key": "oAuthRedirectUrl",
"label": "OAuth Redirect URL",
"type": "string",
"required": true,
"readOnly": true,
"value": "{WEB_APP_URL}/app/twitter/connections/add",
"placeholder": null,
"description": "When asked to input an OAuth callback or redirect URL in Twitter OAuth, enter the URL above.",
"clickToCopy": true
},
{
"key": "consumerKey",
"label": "API Key",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"clickToCopy": false
},
{
"key": "consumerSecret",
"label": "API Secret",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"clickToCopy": false
}
],
"authenticationSteps": [
{
"step": 1,
"type": "mutation",
"name": "createConnection",
"arguments": [
{
"name": "key",
"value": "{key}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 2,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
},
{
"step": 3,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 4,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.oauth_verifier}"
}
]
}
]
},
{
"step": 5,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
}
],
"reconnectionSteps": [
{
"step": 1,
"type": "mutation",
"name": "resetConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 2,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 3,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 4,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 5,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.oauth_verifier}"
}
]
}
]
},
{
"step": 6,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
}
],
"triggers": [
{
"name": "My Tweets",
"key": "myTweets",
"pollInterval": 15,
"description": "Will be triggered when you tweet something new.",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"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"
}
]
},
{
"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"
}
]
},
{
"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"
}
]
}
],
"actions": [
{
"name": "Create Tweet",
"key": "createTweet",
"description": "Will create a tweet.",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseAction",
"name": "Set up action",
"arguments": [
{
"label": "Tweet body",
"key": "tweet",
"type": "string",
"required": true,
"description": "The content of your new tweet.",
"variables": true
}
]
},
{
"key": "testStep",
"name": "Test action"
}
]
}
]
}

View File

@@ -1,21 +0,0 @@
import TwitterClient from './client';
import UserTweets from './triggers/user-tweets';
import SearchTweets from './triggers/search-tweets';
import MyTweets from './triggers/my-tweets';
import MyFollowers from './triggers/my-followers';
export default class Triggers {
client: TwitterClient;
userTweets: UserTweets;
searchTweets: SearchTweets;
myTweets: MyTweets;
myFollowers: MyFollowers;
constructor(client: TwitterClient) {
this.client = client;
this.userTweets = new UserTweets(client);
this.searchTweets = new SearchTweets(client);
this.myTweets = new MyTweets(client);
this.myFollowers = new MyFollowers(client);
}
}

View File

@@ -1,28 +0,0 @@
import TwitterClient from '../client';
export default class MyFollowers {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run(lastInternalId: string) {
return this.getFollowers(lastInternalId);
}
async testRun() {
return this.getFollowers();
}
async getFollowers(lastInternalId?: string) {
const { username } = await this.client.getCurrentUser.run();
const user = await this.client.getUserByUsername.run(username as string);
const tweets = await this.client.getUserFollowers.run(
user.id,
lastInternalId
);
return tweets;
}
}

View File

@@ -1,25 +0,0 @@
import TwitterClient from '../client';
export default class MyTweets {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run() {
return this.getTweets(lastInternalId);
}
async testRun() {
return this.getTweets();
}
async getTweets(lastInternalId?: string) {
const { username } = await this.client.getCurrentUser.run();
const user = await this.client.getUserByUsername.run(username as string);
const tweets = await this.client.getUserTweets.run(user.id, lastInternalId);
return tweets;
}
}

View File

@@ -1,26 +0,0 @@
import TwitterClient from '../client';
export default class SearchTweets {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run(lastInternalId: string) {
return this.getTweets(lastInternalId);
}
async testRun() {
return this.getTweets();
}
async getTweets(lastInternalId?: string) {
const tweets = await this.client.searchTweets.run(
this.client.step.parameters.searchTerm as string,
lastInternalId
);
return tweets;
}
}

View File

@@ -1,27 +0,0 @@
import TwitterClient from '../client';
export default class UserTweets {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run(lastInternalId: string) {
return this.getTweets(lastInternalId);
}
async testRun() {
return this.getTweets();
}
async getTweets(lastInternalId?: string) {
const user = await this.client.getUserByUsername.run(
this.client.step.parameters.username as string
);
const tweets = await this.client.getUserTweets.run(user.id, lastInternalId);
return tweets;
}
}