Merge pull request #435 from automatisch/refactor/user-tweet-trigger

refactor: Adjust architecture for twitter client and user tweet trigger
This commit is contained in:
Ömer Faruk Aydın
2022-08-12 00:13:48 +03:00
committed by GitHub
12 changed files with 305 additions and 108 deletions

View File

@@ -1,96 +1,37 @@
import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import HttpClient from '../../helpers/http-client';
import OAuth from 'oauth-1.0a';
import crypto from 'crypto';
import type { IAuthentication, IField } from '@automatisch/types';
import { URLSearchParams } from 'url';
import TwitterClient from './client';
export default class Authentication implements IAuthentication {
appData: IApp;
connectionData: IJSONObject;
client: TwitterClient;
client: HttpClient;
oauthClient: OAuth;
static baseUrl = 'https://api.twitter.com';
constructor(appData: IApp, connectionData: IJSONObject) {
this.appData = appData;
this.connectionData = connectionData;
this.client = new HttpClient({ baseURL: Authentication.baseUrl });
this.oauthClient = new OAuth({
consumer: {
key: this.connectionData.consumerKey as string,
secret: this.connectionData.consumerSecret as string,
},
signature_method: 'HMAC-SHA1',
hash_function(base_string, key) {
return crypto
.createHmac('sha1', key)
.update(base_string)
.digest('base64');
},
});
constructor(client: TwitterClient) {
this.client = client;
}
async createAuthData() {
const appFields = this.appData.fields.find(
const appFields = this.client.appData.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const callbackUrl = appFields.value;
const requestData = {
url: `${Authentication.baseUrl}/oauth/request_token`,
method: 'POST',
data: { oauth_callback: callbackUrl },
};
const authHeader = this.oauthClient.toHeader(
this.oauthClient.authorize(requestData)
);
try {
const response = await this.client.post(`/oauth/request_token`, null, {
headers: { ...authHeader },
});
const responseData = Object.fromEntries(
new URLSearchParams(response.data)
);
const response = await this.client.oauthRequestToken.run(callbackUrl);
const responseData = Object.fromEntries(new URLSearchParams(response.data));
return {
url: `${Authentication.baseUrl}/oauth/authorize?oauth_token=${responseData.oauth_token}`,
url: `${TwitterClient.baseUrl}/oauth/authorize?oauth_token=${responseData.oauth_token}`,
accessToken: responseData.oauth_token,
accessSecret: responseData.oauth_token_secret,
};
} catch (error) {
const errorMessages = error.response.data.errors
.map((error: IJSONObject) => error.message)
.join(' ');
throw new Error(
`Error occured while verifying credentials: ${errorMessages}`
);
}
}
async verifyCredentials() {
const verifiedCredentials = await this.client.post(
`/oauth/access_token?oauth_verifier=${this.connectionData.oauthVerifier}&oauth_token=${this.connectionData.accessToken}`,
null
);
const responseData = Object.fromEntries(
new URLSearchParams(verifiedCredentials.data)
);
const response = await this.client.verifyAccessToken.run();
const responseData = Object.fromEntries(new URLSearchParams(response.data));
return {
consumerKey: this.connectionData.consumerKey,
consumerSecret: this.connectionData.consumerSecret,
consumerKey: this.client.connectionData.consumerKey,
consumerSecret: this.client.connectionData.consumerSecret,
accessToken: responseData.oauth_token,
accessSecret: responseData.oauth_token_secret,
userId: responseData.user_id,
@@ -100,24 +41,7 @@ export default class Authentication implements IAuthentication {
async isStillVerified() {
try {
const token = {
key: this.connectionData.accessToken as string,
secret: this.connectionData.accessSecret as string,
};
const requestData = {
url: `${Authentication.baseUrl}/2/users/me`,
method: 'GET',
};
const authHeader = this.oauthClient.toHeader(
this.oauthClient.authorize(requestData, token)
);
await this.client.get(`/2/users/me`, {
headers: { ...authHeader },
});
await this.client.getCurrentUser.run();
return true;
} catch (error) {
return false;

View File

@@ -0,0 +1,31 @@
import TwitterClient from '../index';
export default class GetCurrentUser {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run() {
const token = {
key: this.client.connectionData.accessToken as string,
secret: this.client.connectionData.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)
);
return await this.client.httpClient.get(requestPath, {
headers: { ...authHeader },
});
}
}

View File

@@ -0,0 +1,44 @@
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.connectionData.accessToken as string,
secret: this.client.connectionData.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}`
);
}
return response;
}
}

View File

@@ -0,0 +1,44 @@
import { IJSONObject } from '@automatisch/types';
import TwitterClient from '../index';
export default class GetUserTweets {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run(userId: string) {
const token = {
key: this.client.connectionData.accessToken as string,
secret: this.client.connectionData.accessSecret as string,
};
const requestPath = `/2/users/${userId}/tweets`;
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}`
);
}
return response;
}
}

View File

@@ -0,0 +1,42 @@
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

@@ -0,0 +1,20 @@
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.connectionData.oauthVerifier}&oauth_token=${this.client.connectionData.accessToken}`,
null
);
} catch (error) {
throw new Error(error.response.data);
}
}
}

View File

@@ -0,0 +1,59 @@
import { IJSONObject, IApp } from '@automatisch/types';
import OAuth from 'oauth-1.0a';
import crypto from 'crypto';
import HttpClient 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';
export default class TwitterClient {
appData: IApp;
connectionData: IJSONObject;
parameters: IJSONObject;
oauthClient: OAuth;
httpClient: HttpClient;
oauthRequestToken: OAuthRequestToken;
verifyAccessToken: VerifyAccessToken;
getCurrentUser: GetCurrentUser;
getUserByUsername: GetUserByUsername;
getUserTweets: GetUserTweets;
static baseUrl = 'https://api.twitter.com';
constructor(
appData: IApp,
connectionData: IJSONObject,
parameters: IJSONObject
) {
this.connectionData = connectionData;
this.appData = appData;
this.parameters = parameters;
this.httpClient = new HttpClient({ baseURL: TwitterClient.baseUrl });
const consumerData = {
key: this.connectionData.consumerKey as string,
secret: this.connectionData.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);
}
}

View File

@@ -7,8 +7,11 @@ import {
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;
@@ -18,8 +21,10 @@ export default class Twitter implements IService {
connectionData: IJSONObject,
parameters: IJSONObject
) {
this.authenticationClient = new Authentication(appData, connectionData);
this.triggers = new Triggers(connectionData, parameters);
this.actions = new Actions(connectionData, parameters);
this.client = new TwitterClient(appData, connectionData, parameters);
this.authenticationClient = new Authentication(this.client);
this.triggers = new Triggers(this.client);
// this.actions = new Actions(connectionData, parameters);
}
}

View File

@@ -1,13 +1,12 @@
import { IJSONObject } from '@automatisch/types';
import MyTweet from './triggers/my-tweet';
import SearchTweet from './triggers/search-tweet';
import TwitterClient from './client';
import UserTweet from './triggers/user-tweet';
export default class Triggers {
myTweet: MyTweet;
searchTweet: SearchTweet;
client: TwitterClient;
userTweet: UserTweet;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
this.myTweet = new MyTweet(connectionData);
this.searchTweet = new SearchTweet(connectionData, parameters);
constructor(client: TwitterClient) {
this.client = client;
this.userTweet = new UserTweet(client);
}
}

View File

@@ -0,0 +1,30 @@
import TwitterClient from '../client';
export default class UserTweet {
client: TwitterClient;
constructor(client: TwitterClient) {
this.client = client;
}
async run() {
return this.getTweets();
}
async testRun() {
return this.getTweets();
}
async getTweets() {
const userResponse = await this.client.getUserByUsername.run(
this.client.parameters.username as string
);
const userId = userResponse.data.data.id;
const tweetsResponse = await this.client.getUserTweets.run(userId);
const tweets = tweetsResponse.data.data;
return tweets;
}
}

View File

@@ -30,7 +30,8 @@ const getConnectedApps = async (
.flat()
.filter(Boolean);
const usedApps = [...new Set(duplicatedUsedApps)];
const connectionKeys = connections.map((connection) => connection.key);
const usedApps = [...new Set([...duplicatedUsedApps, ...connectionKeys])];
apps = apps
.filter((app: IApp) => {

View File

@@ -171,8 +171,6 @@ export interface ITrigger {
}
export interface IAuthentication {
appData: IApp;
connectionData: IJSONObject;
client: unknown;
verifyCredentials(): Promise<IJSONObject>;
isStillVerified(): Promise<boolean>;