Significant-Gravitas / Auto-GPT-Plugins

Plugins for Auto-GPT
MIT License
3.9k stars 563 forks source link

Twitter Elevated Access required #157

Open solo2424 opened 1 year ago

solo2424 commented 1 year ago

I set up the twitter api with all the correct credentials. When I test by atempting to send a test tweet I get the following error. Any suggestion on how to fix this. I went to the link but it doesn't tell me how to request for elevated access, and I don't think this should be neccessary.

Command post_tweet returned: Error: 403 Forbidden 453 - You currently have Essential access which includes access to Twitter API v2 endpoints only. If you need access to this endpoint, you’ll need to apply for Elevated access via the Developer Portal. You can learn more here: https://developer.twitter.com/en/docs/twitter-api/getting-started/about-twitter-api#v2-access-leve

brk275 commented 1 year ago

you need to change the auth flow

jakebreaks commented 1 year ago

brk275 can you please elaborate or provide a link to the solution that you are suggesting?

brk275 commented 1 year ago

brk275 can you please elaborate or provide a link to the solution that you are suggesting?

started with these three. You also need a callback URL for pkce and tell twitter its a webpage and not a bot. My code isn't perfect but this got things working. Hopefully this can push this in the right direction.

import os
import tweepy

from urllib.parse import urlparse, parse_qs

os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

def get_code_and_state(redirect_response):
    # Parse the URL and extract the query string
    url = urlparse(redirect_response)
    query_string = parse_qs(url.query)

    # The 'code' parameter in the query string is the authorization code
    code = query_string.get('code', [None])[0]
    state = query_string.get('state', [None])[0]

    if not code:
        raise ValueError("No code found in redirect response")

    return code, state

# You get these from your application in Twitter Developer Portal
TW_CLIENT_ID = os.getenv('TW_CLIENT_ID')
TW_CLIENT_SECRET = os.getenv('TW_CLIENT_SECRET')  # This should be confidential
REDIRECT_URI = os.getenv('TW_REDIRECT_URI')  # This should be a localhost address

# Define the required scopes
SCOPES = ['tweet.read', 'tweet.write', 'users.read', 'follows.read', 'follows.write', 'like.read', 'like.write']

# Initialize the handler
oauth2_user_handler = tweepy.OAuth2UserHandler(
    client_id=TW_CLIENT_ID,
    redirect_uri=REDIRECT_URI,
    scope=SCOPES,
    client_secret=TW_CLIENT_SECRET
)

# Get the authorization URL and ask the user to visit it
authorization_url = oauth2_user_handler.get_authorization_url()
print(f'Please go to the following URL: {authorization_url}')

# Get the redirect response from the user
redirect_response = input('Paste the full redirect URL here:')

# Fetch the token using the redirect response URL
token = oauth2_user_handler.fetch_token(redirect_response)

# Save the token for future use
print(f'Token: {token}')

# Save the token to an environment variable
os.environ['TW_ACCESS_TOKEN'] = token['access_token']

# Initialize the client with the access token
client = tweepy.Client(token['access_token'])
from typing import Any, Dict, List, Optional, Tuple, TypedDict, TypeVar
from auto_gpt_plugin_template import AutoGPTPluginTemplate
import os
import tweepy
from datetime import datetime
datetime.now().isoformat()

PromptGenerator = TypeVar("PromptGenerator")

class Message(TypedDict):
    role: str
    content: str

from .oauth2_with_pkce import token

class AutoGPTTwitter(AutoGPTPluginTemplate):
    def __init__(self):
        super().__init__()
        self._name = "autogpt-twitter"
        self._version = "0.1.0"
        self._description = "Twitter API integrations using Tweepy."
        self.tweet_id = []
        self.tweets = []

        self.client = None

        # Read the tokens from the environment
        bearer_token = os.getenv('TW_BEARER_TOKEN')
        consumer_key = os.getenv('TW_CONSUMER_KEY')
        consumer_secret = os.getenv('TW_CONSUMER_SECRET')
        access_token = os.getenv('TW_ACCESS_TOKEN')
        access_token_secret = os.getenv('TW_ACCESS_TOKEN_SECRET')

        if all([bearer_token, consumer_key, consumer_secret, access_token, access_token_secret]):
            self.client = tweepy.Client(
                bearer_token=bearer_token,
                consumer_key=consumer_key,
                consumer_secret=consumer_secret,
                access_token=access_token,
                access_token_secret=access_token_secret
            )
        else:
            print("Some Twitter credentials not found.")

    client = tweepy.Client(os.getenv('TW_AUTH_TOKEN'))

    def can_handle_on_response(self) -> bool:
        """This method is called to check that the plugin can
        handle the on_response method.
        Returns:
            bool: True if the plugin can handle the on_response method."""
        return False

    def on_response(self, response: str, *args, **kwargs) -> str:
        """This method is called when a response is received from the model."""
        pass

    def can_handle_post_prompt(self) -> bool:
        """This method is called to check that the plugin can
        handle the post_prompt method.
        Returns:
            bool: True if the plugin can handle the post_prompt method."""
        return True

    def can_handle_on_planning(self) -> bool:
        """This method is called to check that the plugin can
        handle the on_planning method.
        Returns:
            bool: True if the plugin can handle the on_planning method."""
        return False

    def on_planning(
        self, prompt: PromptGenerator, messages: List[str]
    ) -> Optional[str]:
        """This method is called before the planning chat completeion is done.
        Args:
            prompt (PromptGenerator): The prompt generator.
            messages (List[str]): The list of messages.
        """
        pass

    def can_handle_post_planning(self) -> bool:
        """This method is called to check that the plugin can
        handle the post_planning method.
        Returns:
            bool: True if the plugin can handle the post_planning method."""
        return False

    def post_planning(self, response: str) -> str:
        """This method is called after the planning chat completeion is done.
        Args:
            response (str): The response.
        Returns:
            str: The resulting response.
        """
        pass

    def can_handle_pre_instruction(self) -> bool:
        """This method is called to check that the plugin can
        handle the pre_instruction method.
        Returns:
            bool: True if the plugin can handle the pre_instruction method."""
        return False

    def pre_instruction(self, messages: List[str]) -> List[str]:
        """This method is called before the instruction chat is done.
        Args:
            messages (List[str]): The list of context messages.
        Returns:
            List[str]: The resulting list of messages.
        """
        pass

    def can_handle_on_instruction(self) -> bool:
        """This method is called to check that the plugin can
        handle the on_instruction method.
        Returns:
            bool: True if the plugin can handle the on_instruction method."""
        return False

    def on_instruction(self, messages: List[str]) -> Optional[str]:
        """This method is called when the instruction chat is done.
        Args:
            messages (List[str]): The list of context messages.
        Returns:
            Optional[str]: The resulting message.
        """
        pass

    def can_handle_post_instruction(self) -> bool:
        """This method is called to check that the plugin can
        handle the post_instruction method.
        Returns:
            bool: True if the plugin can handle the post_instruction method."""
        return False

    def post_instruction(self, response: str) -> str:
        """This method is called after the instruction chat is done.
        Args:
            response (str): The response.
        Returns:
            str: The resulting response.
        """
        pass

    def can_handle_pre_command(self) -> bool:
        """This method is called to check that the plugin can
        handle the pre_command method.
        Returns:
            bool: True if the plugin can handle the pre_command method."""
        return False

    def pre_command(
        self, command_name: str, arguments: Dict[str, Any]
    ) -> Tuple[str, Dict[str, Any]]:
        """This method is called before the command is executed.
        Args:
            command_name (str): The command name.
            arguments (Dict[str, Any]): The arguments.
        Returns:
            Tuple[str, Dict[str, Any]]: The command name and the arguments.
        """
        pass

    def can_handle_post_command(self) -> bool:
        """This method is called to check that the plugin can
        handle the post_command method.
        Returns:
            bool: True if the plugin can handle the post_command method."""
        return False

    def post_command(self, command_name: str, response: str) -> str:
        """This method is called after the command is executed.
        Args:
            command_name (str): The command name.
            response (str): The response.
        Returns:
            str: The resulting response.
        """
        pass

    def can_handle_chat_completion(
        self,
        messages: list[Dict[Any, Any]],
        model: str,
        temperature: float,
        max_tokens: int,
    ) -> bool:
        """This method is called to check that the plugin can
        handle the chat_completion method.
        Args:
            messages (Dict[Any, Any]): The messages.
            model (str): The model name.
            temperature (float): The temperature.
            max_tokens (int): The max tokens.
        Returns:
            bool: True if the plugin can handle the chat_completion method."""
        return False

    def handle_chat_completion(
        self,
        messages: list[Dict[Any, Any]],
        model: str,
        temperature: float,
        max_tokens: int,
    ) -> str:
        """This method is called when the chat completion is done.
        Args:
            messages (Dict[Any, Any]): The messages.
            model (str): The model name.
            temperature (float): The temperature.
            max_tokens (int): The max tokens.
        Returns:
            str: The resulting response.
        """
        return None

    def post_prompt(self, prompt: PromptGenerator) -> PromptGenerator:
            """This method is called just after the generate_prompt is called,
                but actually before the prompt is generated.
            Args:
                prompt (PromptGenerator): The prompt generator.
            Returns:
                PromptGenerator: The prompt generator.
            """
            if self.client:
                from .twitter import (
                    get_mentions,
                    post_tweet,
                    search_twitter_user,
                    get_user
                )
                prompt.add_command(
                "get_user",
                "Get Twitter User",
    {
        "username": "<username>",
        "expansions": "<expansions>",
        "tweet_fields": "<tweet_fields>",
        "user_fields": "<user_fields>",
        "user_auth": "<user_auth>",
    },
    get_user,
)
                prompt.add_command(
                    "post_tweet", "Post Tweet", 
                    {"tweet_text": "<tweet_text>",}, post_tweet
                )
                prompt.add_command("get_mentions", "Get Twitter Mentions", {}, get_mentions)
                prompt.add_command(
                    "search_twitter_user",
                    "Search Twitter",
                    {
                        "user_id" : "<user_id>",
                        "end_time" : "<end_time>",
                        "tweet_fields" : "<tweet_fields>",
                        "exclude" : "<exclude>",
                        "max_results" : "<max_results>"
                    },
                    search_twitter_user,
                )

            return prompt

from __future__ import annotations
from . import AutoGPTTwitter
import pandas as pd
import tweepy 
from datetime import datetime
from typing import List, Union
from datetime import datetime
datetime.now().isoformat()

plugin = AutoGPTTwitter()

def post_tweet(
    tweet_text: str = None,
    direct_message_deep_link: str = None,
    for_super_followers_only: bool = None,
    place_id: str = None,
    media_ids: list[int | str] = None,
    media_tagged_user_ids: list[int | str] = None,
    poll_duration_minutes: int = None,
    poll_options: list[str] = None,
    quote_tweet_id: int | str = None,
    exclude_reply_user_ids: list[int | str] = None,
    in_reply_to_tweet_id: int | str = None,
    reply_settings: str = None,
    user_auth: bool = True
) -> str:
    """Posts a tweet to twitter.
    Args:
        tweet_text (str): The tweet to post.
        direct_message_deep_link (str): Tweets a link directly to a Direct Message conversation with an account.
        for_super_followers_only (bool): Allows you to Tweet exclusively for Super Followers.
        place_id (str): Place ID being attached to the Tweet for geo location.
        media_ids (list[int | str]): A list of Media IDs being attached to the Tweet.
        media_tagged_user_ids (list[int | str]): A list of User IDs being tagged in the Tweet with Media.
        poll_duration_minutes (int): Duration of the poll in minutes for a Tweet with a poll.
        poll_options (list[str]): A list of poll options for a Tweet with a poll.
        quote_tweet_id (int | str): Link to the Tweet being quoted.
        exclude_reply_user_ids (list[int | str]): A list of User IDs to be excluded from the reply Tweet.
        in_reply_to_tweet_id (int | str): Tweet ID of the Tweet being replied to.
        reply_settings (str): Settings to indicate who can reply to the Tweet.
        user_auth (bool): Whether or not to use OAuth 1.0a User Context to authenticate. Default is True.
    Returns:
        str: The tweet that was posted.
    """

    tweet = plugin.client.create_tweet(
        text=tweet_text,
        direct_message_deep_link=direct_message_deep_link,
        for_super_followers_only=for_super_followers_only,
        place_id=place_id,
        media_ids=media_ids,
        media_tagged_user_ids=media_tagged_user_ids,
        poll_duration_minutes=poll_duration_minutes,
        poll_options=poll_options,
        quote_tweet_id=quote_tweet_id,
        exclude_reply_user_ids=exclude_reply_user_ids,
        in_reply_to_tweet_id=in_reply_to_tweet_id,
        reply_settings=reply_settings,
        user_auth=user_auth
    )
    if response.ok:  # Check if the request was successful
        tweet = response.json()  # Parse the JSON response
        return f"Success! Tweet: {tweet.get('text', 'No text')}"
    else:
        return f"Error: {response.text}"

def get_mentions(
    user_id: int | str,
    end_time: datetime.datetime | str = None,
    expansions: list[str] | str = None,
    max_results: int = None,
    media_fields: list[str] | str = None,
    pagination_token: str = None,
    place_fields: list[str] | str = None,
    poll_fields: list[str] | str = None,
    since_id: int | str = None,
    start_time: datetime.datetime | str = None,
    tweet_fields: list[str] | str = None,
    until_id: int | str = None,
    user_fields: list[str] | str = None,
    user_auth: bool = False
) -> str | None:
    """Gets the most recent mention.

    Args:
        user_id (int | str): Unique identifier of the user for whom to return Tweets mentioning the user.
        end_time (datetime.datetime | str): The new UTC timestamp from which the Tweets will be provided.
        expansions (list[str] | str): expansions Parameter
        max_results (int): Specifies the number of Tweets to try and retrieve.
        media_fields (list[str] | str): media_fields
        pagination_token (str): This parameter is used to move forwards or backwards through ‘pages’ of results.
        place_fields (list[str] | str): place_fields
        poll_fields (list[str] | str): poll_fields
        since_id (int | str): Returns results with a Tweet ID greater than (that is, more recent than) the specified ‘since’ Tweet ID.
        start_time (datetime.datetime | str): The oldest UTC timestamp from which the Tweets will be provided.
        tweet_fields (list[str] | str): tweet_fields
        until_id (int | str): Returns results with a Tweet ID less less than (that is, older than) the specified ‘until’ Tweet ID.
        user_fields (list[str] | str): user_fields
        user_auth (bool): Whether or not to use OAuth 1.0a User Context to authenticate. Default is False.

    Returns:
        str | None: The most recent mention.
    """

    tweets = plugin.client.get_users_mentions(
        id=user_id,
        end_time=end_time,
        expansions=expansions,
        max_results=max_results,
        media_fields=media_fields,
        pagination_token=pagination_token,
        place_fields=place_fields,
        poll_fields=poll_fields,
        since_id=since_id,
        start_time=start_time,
        tweet_fields=['id', 'text', 'created_at', 'context_annotations'],
        until_id=until_id,
        user_fields=user_fields,
        user_auth=user_auth
    )

    for tweet in tweets:
        return (
            f"@{tweet.user.screen_name} Replied: {tweet.full_text}"
            f" Tweet ID: {tweet.id}"
        )  # Returns most recent mention

def get_user(
    id: Union[int, str, None] = None,
    username: Union[str, None] = None,
    expansions: Union[list[str], str, None] = None,
    tweet_fields: Union[list[str], str, None] = None,
    user_fields: Union[list[str], str, None] = None,
    user_auth: bool = False
) -> dict:
    """Gets information about a single user specified by the requested ID or username.

    Args:
        id (int | str | None): The ID of the user to lookup.
        username (str | None): The Twitter username (handle) of the user.
        expansions (list[str] | str | None): expansions Parameter
        tweet_fields (list[str] | str | None): tweet_fields
        user_fields (list[str] | str | None): user_fields
        user_auth (bool): Whether or not to use OAuth 1.0a User Context to authenticate. Default is False.

    Returns:
        dict: The user's information.
    """

    user = plugin.client.get_user(
        username=username,
        expansions=expansions,
        tweet_fields=tweet_fields,
        user_fields=user_fields,
        user_auth=user_auth
    )

    return user

def search_twitter_user(
    user_id: Union[int, str],
    end_time: Union[datetime, str, None] = None,
    exclude: Union[List[str], str, None] = None,
    expansions: Union[List[str], str, None] = None,
    max_results: int = None,
    media_fields: Union[List[str], str, None] = None,
    pagination_token: str = None,
    place_fields: Union[List[str], str, None] = None,
    poll_fields: Union[List[str], str, None] = None,
    since_id: Union[int, str, None] = None,
    start_time: Union[datetime, str, None] = None,
    tweet_fields: Union[List[str], str, None] = None,
    until_id: Union[int, str, None] = None,
    user_fields: Union[List[str], str, None] = None,
    user_auth: bool = False
) -> str:
    tweets = plugin.client.get_users_tweets(
        id=user_id,
        end_time=end_time,
        exclude=exclude,
        expansions=expansions,
        max_results=max_results,
        media_fields=media_fields,
        pagination_token=pagination_token,
        place_fields=place_fields,
        poll_fields=poll_fields,
        since_id=since_id,
        start_time=start_time,
        tweet_fields=['created_at', 'text', 'context_annotations'],  # corrected 'crested_at' to 'created_at'
        until_id=until_id,
        user_fields=user_fields,
        user_auth=user_auth
    )
    print(f"type of tweets: {type(tweets)}")  # print the type of tweets
    print(f"tweets: {tweets}")  # print the content of tweets

    columns = ["Time", "User", "ID", "Tweet"]
    data = []

    for tweet in tweets.data:
        row = [tweet['created_at'], tweet['author_id'], tweet['id'], tweet['text']]  # accessing fields as dictionary keys
        data.append(row)

    df = pd.DataFrame(data, columns=columns)

    print(df)```