-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Twitter tool #525
Merged
Merged
Twitter tool #525
Changes from 11 commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
77b266a
Added oauth button for twitter
ececcc0
Added api
d7b7150
Get tokens
f8cb05e
Oauth Flow
1d56496
Helper file for twitter tokens
a91a77a
Toolkit for twitter
c789df4
Tool to send tweets with multimediaenabled
b05d440
Minor changes
d587131
Added unit tests
541236a
Minor fix
bc9c7f7
Minor fix
8a6cc91
Error fixes in unit tests
6e6252b
Create testing.txt
Tarraann 8cee52c
Update test_send_tweets.py
Tarraann b02e7b5
Create testing.txt
Tarraann 55c975a
error fixing
Tarraann 01effb2
Update test_send_tweets.py
Tarraann 6b633ca
pr comments resolved
3a7bc1c
bug fixed
e6e2e7d
Modification in unit tests
d44ec07
Modification in unit tests
f372cc5
Changes in session for db
71116b1
Saving Oauth Tokens in Database instead of Local file
e50383e
Adding of organisation id
ccb1bf1
Db changes made
32eaf20
minor changes made
26a9e12
Changes in tests
bec1921
merge dev
Fluder-Paradyne 30ca409
Update twitter_toolkit.py
c7f951b
Fixed unit tests
bddc843
Added Controller for twitter apis
9a331b3
Added unit tests for twitter tokens:
b3202e3
Merge branch 'dev' into twitter-tool
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import os | ||
import pickle | ||
import json | ||
import hmac | ||
import time | ||
import random | ||
import base64 | ||
import hashlib | ||
import urllib.parse | ||
import http.client as http_client | ||
from superagi.config.config import get_config | ||
from sqlalchemy.orm import sessionmaker | ||
from superagi.models.db import connect_db | ||
from superagi.models.tool_config import ToolConfig | ||
from superagi.resource_manager.manager import ResourceManager | ||
|
||
class TwitterTokens: | ||
TransformerOptimus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def get_request_token(self,api_data): | ||
api_key = api_data["api_key"] | ||
api_secret_key = api_data["api_secret"] | ||
http_method = 'POST' | ||
base_url = 'https://api.twitter.com/oauth/request_token' | ||
|
||
params = { | ||
'oauth_callback': 'http://localhost:3000/api/oauth-twitter', | ||
'oauth_consumer_key': api_key, | ||
'oauth_nonce': self.gen_nonce(), | ||
'oauth_signature_method': 'HMAC-SHA1', | ||
'oauth_timestamp': int(time.time()), | ||
'oauth_version': '1.0' | ||
} | ||
|
||
params_sorted = sorted(params.items()) | ||
params_qs = '&'.join([f'{k}={self.percent_encode(str(v))}' for k, v in params_sorted]) | ||
|
||
base_string = f'{http_method}&{self.percent_encode(base_url)}&{self.percent_encode(params_qs)}' | ||
|
||
signing_key = f'{self.percent_encode(api_secret_key)}&' | ||
signature = hmac.new(signing_key.encode(), base_string.encode(), hashlib.sha1) | ||
params['oauth_signature'] = base64.b64encode(signature.digest()).decode() | ||
|
||
auth_header = 'OAuth ' + ', '.join([f'{k}="{self.percent_encode(str(v))}"' for k, v in params.items()]) | ||
|
||
headers = { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
'Authorization': auth_header | ||
} | ||
conn = http_client.HTTPSConnection("api.twitter.com") | ||
conn.request("POST", "/oauth/request_token", "", headers) | ||
res = conn.getresponse() | ||
response_data = res.read().decode('utf-8') | ||
conn.close() | ||
request_token_resp = dict(urllib.parse.parse_qsl(response_data)) | ||
return request_token_resp | ||
|
||
def percent_encode(self, val): | ||
return urllib.parse.quote(val, safe='') | ||
|
||
def gen_nonce(self): | ||
nonce = ''.join([str(random.randint(0, 9)) for i in range(32)]) | ||
return nonce | ||
|
||
def get_twitter_creds(self, toolkit_id): | ||
file_name = "twitter_credentials.pickle" | ||
root_dir = get_config('RESOURCES_OUTPUT_ROOT_DIR') | ||
file_path = file_name | ||
if root_dir is not None: | ||
root_dir = root_dir if root_dir.startswith("/") else os.getcwd() + "/" + root_dir | ||
root_dir = root_dir if root_dir.endswith("/") else root_dir + "/" | ||
file_path = root_dir + file_name | ||
else: | ||
file_path = os.getcwd() + "/" + file_name | ||
if os.path.exists(file_path): | ||
with open(file_path,'rb') as file: | ||
creds = pickle.load(file) | ||
if isinstance(creds, str): | ||
creds = json.loads(creds) | ||
engine = connect_db() | ||
TransformerOptimus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Session = sessionmaker(bind=engine) | ||
session = Session() | ||
twitter_creds = session.query(ToolConfig).filter(ToolConfig.toolkit_id == toolkit_id).all() | ||
api_key = "" | ||
api_key_secret = "" | ||
for credentials in twitter_creds: | ||
credentials = credentials.__dict__ | ||
if credentials["key"] == "TWITTER_API_KEY": | ||
api_key = credentials["value"] | ||
if credentials["key"] == "TWITTER_API_SECRET": | ||
api_key_secret = credentials["value"] | ||
creds["api_key"] = api_key | ||
creds["api_key_secret"] = api_key_secret | ||
return creds | ||
Tarraann marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import os | ||
import json | ||
import base64 | ||
import requests | ||
from typing import Any, Type | ||
from pydantic import BaseModel, Field | ||
from superagi.tools.base_tool import BaseTool | ||
from superagi.helper.twitter_tokens import TwitterTokens | ||
from requests_oauthlib import OAuth1 | ||
from requests_oauthlib import OAuth1Session | ||
from superagi.helper.resource_helper import ResourceHelper | ||
|
||
class SendTweetsInput(BaseModel): | ||
tweet_text: str = Field(..., description="Tweet text to be posted from twitter handle, if no value is given keep the default value as 'None'") | ||
is_media: bool = Field(..., description="'True' if there is any media to be posted with Tweet else 'False'.") | ||
media_num: int = Field(..., description="Integer value for the number of media files to be uploaded, default value is 0") | ||
media_files: list = Field(..., description="Name of the media files to be uploaded.") | ||
|
||
class SendTweetsTool(BaseTool): | ||
name: str = "Send Tweets Tool" | ||
args_schema: Type[BaseModel] = SendTweetsInput | ||
description: str = "Send and Schedule Tweets for your Twitter Handle" | ||
agent_id: int = None | ||
|
||
def _execute(self, is_media: bool, tweet_text: str = 'None', media_num: int = 0, media_files: list = []): | ||
toolkit_id = self.toolkit_config.toolkit_id | ||
creds = TwitterTokens().get_twitter_creds(toolkit_id) | ||
params = {} | ||
if is_media: | ||
media_ids = self.get_media_ids(media_files, creds) | ||
params["media"] = {"media_ids": media_ids} | ||
if tweet_text is not None: | ||
params["text"] = tweet_text | ||
tweet_response = self.send_tweets(params, creds) | ||
TransformerOptimus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if tweet_response.status_code == 201: | ||
return "Tweet posted successfully!!" | ||
else: | ||
return "Error posting tweet. (Status code: {})".format(tweet_response.status_code) | ||
|
||
|
||
def get_media_ids(self, media_files, creds): | ||
media_ids = [] | ||
oauth = OAuth1(creds["api_key"], | ||
client_secret=creds["api_key_secret"], | ||
resource_owner_key=creds["oauth_token"], | ||
resource_owner_secret=creds["oauth_token_secret"]) | ||
for file in media_files: | ||
file_path = self.get_file_path(file) | ||
image_data = open(file_path, 'rb').read() | ||
b64_image = base64.b64encode(image_data) | ||
upload_endpoint = 'https://upload.twitter.com/1.1/media/upload.json' | ||
Tarraann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
headers = {'Authorization': 'application/octet-stream'} | ||
response = requests.post(upload_endpoint, headers=headers, | ||
data={'media_data': b64_image}, | ||
auth=oauth) | ||
ids = json.loads(response.text)['media_id'] | ||
media_ids.append(str(ids)) | ||
|
||
return media_ids | ||
|
||
def get_file_path(self, file_name): | ||
Tarraann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
output_root_dir = ResourceHelper.get_root_output_dir() | ||
|
||
final_path = ResourceHelper.get_root_input_dir() + file_name | ||
if "{agent_id}" in final_path: | ||
final_path = final_path.replace("{agent_id}", str(self.agent_id)) | ||
|
||
if final_path is None or not os.path.exists(final_path): | ||
if output_root_dir is not None: | ||
final_path = ResourceHelper.get_root_output_dir() + file_name | ||
if "{agent_id}" in final_path: | ||
final_path = final_path.replace("{agent_id}", str(self.agent_id)) | ||
|
||
if final_path is None or not os.path.exists(final_path): | ||
raise FileNotFoundError(f"File '{file_name}' not found.") | ||
|
||
return final_path | ||
|
||
def send_tweets(self, params, creds): | ||
Tarraann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
tweet_endpoint = "https://api.twitter.com/2/tweets" | ||
oauth = OAuth1Session(creds["api_key"], | ||
client_secret=creds["api_key_secret"], | ||
resource_owner_key=creds["oauth_token"], | ||
resource_owner_secret=creds["oauth_token_secret"]) | ||
|
||
response = oauth.post(tweet_endpoint,json=params) | ||
return response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from abc import ABC | ||
from superagi.tools.base_tool import BaseToolkit, BaseTool | ||
from typing import Type, List | ||
from superagi.tools.twitter.send_tweets import SendTweetsTool | ||
|
||
|
||
class TwitterToolKit(BaseToolkit, ABC): | ||
TransformerOptimus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
name: str = "Twitter Toolkit" | ||
description: str = "Twitter Tool kit contains all tools related to Twitter" | ||
|
||
def get_tools(self) -> List[BaseTool]: | ||
return [SendTweetsTool()] | ||
|
||
def get_env_keys(self) -> List[str]: | ||
return ["TWITTER_API_KEY", "TWITTER_API_SECRET"] |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you move this to twitter_auth.rb in controller.