diff --git a/plugins/twitter/README.md b/plugins/twitter/README.md index 185b2a06..c6de3689 100644 --- a/plugins/twitter/README.md +++ b/plugins/twitter/README.md @@ -3,88 +3,97 @@ The Twitter plugin is a lightweight wrapper over commonly-used twitter API calls. It can be used as a executable on its own or by combining multiple of these into an executable. ## Installation + From this directory (`twitter`), run the installation: + ```bash poetry install ``` ## Usage -1. Choose one of the 2 options to initialise the Twitter plugin - - `GameTwitterPlugin` - - This allows one to leverage GAME's X enterprise API credentials (i.e. higher rate limits) - - `TwitterPlugin` - - This allows one to use their own X API credentials -2. Initialise plugin objects, run set-up scripts and use plugin functions - - `GameTwitterPlugin` - - To get the access token to us this plugin, run the following command - ```bash - poetry run twitter-plugin-gamesdk auth -k - ``` - You will see the following output: - ```bash - Waiting for authentication... - - Visit the following URL to authenticate: - https://x.com/i/oauth2/authorize?response_type=code&client_id=VVdyZ0t4WFFRMjBlMzVaczZyMzU6MTpjaQ&redirect_uri=http%3A%2F%2Flocalhost%3A8714%2Fcallback&state=866c82c0-e3f6-444e-a2de-e58bcc95f08b&code_challenge=K47t-0Mcl8B99ufyqmwJYZFB56fiXiZf7f3euQ4H2_0&code_challenge_method=s256&scope=tweet.read%20tweet.write%20users.read%20offline.access - ``` - After authenticating, you will receive the following message: - ```bash - Authenticated! Here's your access token: - apx- - ``` - - Set the access token as an environment variable called `GAME_TWITTER_ACCESS_TOKEN` (e.g. using a `.bashrc` or a `.zshrc` file): - - Import and initialize the plugin to use in your worker: - ```python - import os - from twitter_plugin_gamesdk.game_twitter_plugin import GameTwitterPlugin - - # Define your options with the necessary credentials - options = { - "id": "test_game_twitter_plugin", - "name": "Test GAME Twitter Plugin", - "description": "An example GAME Twitter Plugin for testing.", - "credentials": { - "gameTwitterAccessToken": os.environ.get("GAME_TWITTER_ACCESS_TOKEN") - }, - } - # Initialize the TwitterPlugin with your options - game_twitter_plugin = GameTwitterPlugin(options) - - # Post a tweet - post_tweet_fn = game_twitter_plugin.get_function('post_tweet') - post_tweet_fn("Hello world!") - ``` - You can refer to `examples/test_game_twitter.py` for more examples on how to call the twitter functions. Note that there is a limited number of functions available. If you require more, please submit a feature requests via Github Issues. - - - `TwitterPlugin` - - If you don't already have one, create a X (twitter) account and navigate to the [developer portal](https://developer.x.com/en/portal/dashboard). - - Create a project app, generate the following credentials and set the following environment variables (e.g. using a `.bashrc` or a `.zshrc` file): - - `TWITTER_API_KEY` - - `TWITTER_API_SECRET_KEY` - - `TWITTER_ACCESS_TOKEN` - - `TWITTER_ACCESS_TOKEN_SECRET` - - Import and initialize the plugin to use in your worker: - ```python - import os - from twitter_plugin_gamesdk.twitter_plugin import TwitterPlugin - - # Define your options with the necessary credentials - options = { - "id": "test_twitter_worker", - "name": "Test Twitter Worker", - "description": "An example Twitter Plugin for testing.", - "credentials": { - "apiKey": os.environ.get("TWITTER_API_KEY"), - "apiSecretKey": os.environ.get("TWITTER_API_SECRET_KEY"), - "accessToken": os.environ.get("TWITTER_ACCESS_TOKEN"), - "accessTokenSecret": os.environ.get("TWITTER_ACCESS_TOKEN_SECRET"), - }, - } - # Initialize the TwitterPlugin with your options - twitter_plugin = TwitterPlugin(options) - - # Post a tweet - post_tweet_fn = twitter_plugin.get_function('post_tweet') - post_tweet_fn("Hello world!") - ``` - You can refer to `examples/test_twitter.py` for more examples on how to call the twitter functions. \ No newline at end of file + +The Twitter plugin can be initialized in one of two ways: + +1. Using GAME's X enterprise API credentials (higher rate limits) + + - To get the access token for this option, run the following command: + + ```bash + poetry run twitter-plugin-gamesdk auth -k + ``` + + You will see the following output: + + ```bash + Waiting for authentication... + + Visit the following URL to authenticate: + https://x.com/i/oauth2/authorize?response_type=code&client_id=VVdyZ0t4WFFRMjBlMzVaczZyMzU6MTpjaQ&redirect_uri=http%3A%2F%2Flocalhost%3A8714%2Fcallback&state=866c82c0-e3f6-444e-a2de-e58bcc95f08b&code_challenge=K47t-0Mcl8B99ufyqmwJYZFB56fiXiZf7f3euQ4H2_0&code_challenge_method=s256&scope=tweet.read%20tweet.write%20users.read%20offline.access + ``` + + After authenticating, you will receive the following message: + + ```bash + Authenticated! Here's your access token: + apx- + ``` + + - Set the access token as an environment variable called `GAME_TWITTER_ACCESS_TOKEN` (e.g. using a `.bashrc` or a `.zshrc` file). + - Import and initialize the plugin to use in your worker: + + ```python + import os + from twitter_plugin_gamesdk.twitter_plugin import TwitterPlugin + + # Define your options with the necessary credentials + options = { + "credentials": { + "gameTwitterAccessToken": os.environ.get("GAME_TWITTER_ACCESS_TOKEN") + }, + } + # Initialize the TwitterPlugin with your options + twitter_plugin = TwitterPlugin(options) + + # Post a tweet + post_tweet_fn = twitter_plugin.get_function('post_tweet') + post_tweet_fn("Hello world!") + ``` + +2. Using your own X API credentials + + - If you don't already have one, create a X (twitter) account and navigate to the [developer portal](https://developer.x.com/en/portal/dashboard). + - Create a project app, generate the following credentials and set them as environment variables (e.g. using a `.bashrc` or a `.zshrc` file): + - `TWITTER_BEARER_TOKEN` + - `TWITTER_API_KEY` + - `TWITTER_API_SECRET_KEY` + - `TWITTER_ACCESS_TOKEN` + - `TWITTER_ACCESS_TOKEN_SECRET` + - Import and initialize the plugin to use in your worker: + + ```python + import os + from twitter_plugin_gamesdk.twitter_plugin import TwitterPlugin + + # Define your options with the necessary credentials + options = { + "credentials": { + "bearerToken": os.environ.get("TWITTER_BEARER_TOKEN"), + "apiKey": os.environ.get("TWITTER_API_KEY"), + "apiSecretKey": os.environ.get("TWITTER_API_SECRET_KEY"), + "accessToken": os.environ.get("TWITTER_ACCESS_TOKEN"), + "accessTokenSecret": os.environ.get("TWITTER_ACCESS_TOKEN_SECRET"), + }, + } + # Initialize the TwitterPlugin with your options + twitter_plugin = TwitterPlugin(options) + + # Post a tweet + post_tweet_fn = twitter_plugin.twitter_client.create_tweet + post_tweet_fn(text="Hello world! This is a test tweet from the Twitter Plugin!") + ``` + +For detailed documentation on each function's parameters and usage, please refer to the [Tweepy Client Documentation](https://docs.tweepy.org/en/stable/client.html). + +Example usage: + +You can refer to the example files in the `examples` directory for more examples on how to call the twitter functions. diff --git a/plugins/twitter/examples/test_game_twitter.py b/plugins/twitter/examples/test_game_twitter.py deleted file mode 100644 index f17c3b41..00000000 --- a/plugins/twitter/examples/test_game_twitter.py +++ /dev/null @@ -1,69 +0,0 @@ -import os -from twitter_plugin_gamesdk.game_twitter_plugin import GameTwitterPlugin - -# Define your options with the necessary credentials -options = { - "id": "test_game_twitter_plugin", - "name": "Test GAME Twitter Plugin", - "description": "An example GAME Twitter Plugin for testing.", - "credentials": { - "gameTwitterAccessToken": os.environ.get("GAME_TWITTER_ACCESS_TOKEN") - }, -} - -# Initialize the TwitterPlugin with your options -game_twitter_plugin = GameTwitterPlugin(options) - -# Test case 1: Post a Tweet -print("\nRunning Test Case 1: Post a Tweet") -post_tweet_fn = game_twitter_plugin.get_function('post_tweet') -post_tweet_fn(tweet="Hello world! This is a test tweet from the GAME Twitter Plugin!", media_ids=[]) -print("Posted tweet!") - -# Test case 2: Reply to a Tweet -print("\nRunning Test Case 2: Reply to a Tweet") -reply_tweet_fn = game_twitter_plugin.get_function('reply_tweet') -reply_tweet_fn(tweet_id=1879472470362816626, reply="Hey! This is a test reply!", media_ids=[]) -print("Replied to tweet!") - -# Test case 3: Quote a Tweet -print("\nRunning Test Case 3: Quote a Tweet") -quote_tweet_fn = game_twitter_plugin.get_function('quote_tweet') -quote_tweet_fn(tweet_id=1879472470362816626, quote="Hey! This is a test quote tweet!", media_ids=[]) -print("Quoted tweet!") - -# Test case 4: Search Tweets -print("\nRunning Test Case 4: Search Tweets") -search_tweets_fn = game_twitter_plugin.get_function('search_tweets') -response = search_tweets_fn(query="Python") -print(f"Searched tweets: {response}") - -# Test case 5: Get authenticated user -print("\nRunning Test Case 5: Get details of authenticated user") -get_authenticated_user_fn = game_twitter_plugin.get_function('get_authenticated_user') -response = get_authenticated_user_fn() -print(f"Got details of authenticated user: {response}") - -# Test case 6: Get my mentions -print("\nRunning Test Case 6: Get my mentions") -mentions_fn = game_twitter_plugin.get_function('mentions') -response = mentions_fn() -print(f"My mentions: {response}") - -# Test case 7: Get my followers -print("\nRunning Test Case 7: Get list of users who are followers of me") -followers_fn = game_twitter_plugin.get_function('followers') -response = followers_fn() -print(f"My followers: {response}") - -# Test case 8: Get ppl following me -print("\nRunning Test Case 8: Get list of users I am following") -following_fn = game_twitter_plugin.get_function('following') -response = following_fn() -print(f"Users I am following: {response}") - -# Test case 9: Upload media -print("\nRunning Test Case 9: Upload media") -with open("sample_media/media_file.png", "rb") as f: - media_id = game_twitter_plugin.upload_media(f) - print(f"Uploaded media_id: {media_id}") \ No newline at end of file diff --git a/plugins/twitter/examples/test_twitter.py b/plugins/twitter/examples/test_twitter.py index ec259dc0..ef1f60d3 100644 --- a/plugins/twitter/examples/test_twitter.py +++ b/plugins/twitter/examples/test_twitter.py @@ -1,11 +1,8 @@ -import os from twitter_plugin_gamesdk.twitter_plugin import TwitterPlugin # Define your options with the necessary credentials -options = { - "id": "test_twitter_plugin", - "name": "Test Twitter Plugin", - "description": "An example Twitter Plugin for testing.", +# Using your own X API credentials +""" options = { "credentials": { "bearerToken": os.environ.get("TWITTER_BEARER_TOKEN"), "apiKey": os.environ.get("TWITTER_API_KEY"), @@ -13,6 +10,13 @@ "accessToken": os.environ.get("TWITTER_ACCESS_TOKEN"), "accessTokenSecret": os.environ.get("TWITTER_ACCESS_TOKEN_SECRET"), }, +} """ + +# Using GAME Twitter API credentials +options = { + "credentials": { + "gameTwitterAccessToken": "apx-xxx", + }, } # Initialize the TwitterPlugin with your options @@ -20,44 +24,60 @@ # Test case 1: Post a Tweet print("\nRunning Test Case 1: Post a Tweet") -post_tweet_fn = twitter_plugin.get_function('post_tweet') -post_tweet_fn("Hello world! This is a test tweet from the Twitter Plugin!", media_ids=[]) +post_tweet_fn = twitter_plugin.twitter_client.create_tweet +post_tweet_fn(text="Hello world! This is a test tweet from the Twitter Plugin!") print("Posted tweet!") -# Test case 2: Reply to a Tweet -print("\nRunning Test Case 2: Reply to a Tweet") -reply_tweet_fn = twitter_plugin.get_function('reply_tweet') -reply_tweet_fn(tweet_id=1879472470362816626, reply="Hey! This is a test reply!", media_ids=[]) +# Test case 2: Post a Tweet with Media +print("\nRunning Test Case 2: Post a Tweet with Media") +print("\nUpload media") +with open("sample_media/media_file.png", "rb") as f: + media_id = twitter_plugin.twitter_client.upload_media(f) + print(f"Uploaded media_id: {media_id}") +post_tweet_fn = twitter_plugin.twitter_client.create_tweet +post_tweet_fn(text="Hello world! This is a test tweet with media from the Twitter Plugin!", media_ids=[media_id]) +print("Posted tweet with media!") + + +# Test case 3: Reply to a Tweet +print("\nRunning Test Case 3: Reply to a Tweet") +reply_tweet_fn = twitter_plugin.twitter_client.create_tweet +reply_tweet_fn(in_reply_to_tweet_id=1915274034100809968, text="Hey! This is a test reply!") print("Replied to tweet!") -# Test case 3: Like a Tweet -print("\nRunning Test Case 3: Like a Tweet") -like_tweet_fn = twitter_plugin.get_function('like_tweet') -like_tweet_fn(tweet_id=1879472470362816626) +# Test case 4: Like a Tweet +print("\nRunning Test Case 4: Like a Tweet") +like_tweet_fn = twitter_plugin.twitter_client.like +like_tweet_fn(tweet_id=1915274034100809968) print("Liked tweet!") -# Test case 4: Quote a Tweet -print("\nRunning Test Case 4: Quote a Tweet") -quote_tweet_fn = twitter_plugin.get_function('quote_tweet') -quote_tweet_fn(tweet_id=1879472470362816626, quote="Hey! This is a test quote tweet!", media_ids=[]) +# Test case 5: Quote a Tweet +print("\nRunning Test Case 5: Quote a Tweet") +quote_tweet_fn = twitter_plugin.twitter_client.create_tweet +quote_tweet_fn(quote_tweet_id=1915274034100809968, text="Hey! This is a test quote tweet!") print("Quoted tweet!") -# Test case 5: Get Metrics -print("\nRunning Test Case 5: Get Metrics") -get_metrics_fn = twitter_plugin.get_function('get_metrics') -metrics = get_metrics_fn() +# Test case 6: Get Metrics +print("\nRunning Test Case 6: Get Metrics") +get_metrics_fn = twitter_plugin.twitter_client.get_me +metrics = get_metrics_fn(user_fields=["public_metrics"]) print("Metrics:", metrics) -# Test case 6: Get User From Handle -print("\nRunning Test Case 6: Get User From Handle") -get_user_fn = twitter_plugin.get_function('get_user_from_handle') -user_id = get_user_fn('celesteanglm') -print("user_id:", user_id) - -# Test case 7: Get User Mentions -print("\nRunning Test Case 7: Get User Mentions") -get_user_fn = twitter_plugin.get_function("get_user_from_handle") -user_id = get_user_fn("GAME_Virtuals") -get_user_mentions_fn = twitter_plugin.get_function("get_user_mentions") -user_mentions = get_user_mentions_fn(user_id, max_results=100) -print("user_mentions:", user_mentions) \ No newline at end of file +# Test case 7: Get User From Handle +print("\nRunning Test Case 7: Get User From Handle") +get_user_fn = twitter_plugin.twitter_client.get_user +user = get_user_fn(username='celesteanglm', user_fields=["public_metrics"]) +print("user:", user) + +# Test case 8: Get User Mentions +print("\nRunning Test Case 8: Get User Mentions") +get_user_fn = twitter_plugin.twitter_client.get_user +user = get_user_fn(username="GAME_Virtuals") +get_user_mentions_fn = twitter_plugin.twitter_client.get_users_mentions +user_mentions = get_user_mentions_fn( id = user['data']['id'], + max_results = 10, + tweet_fields = ["id", "created_at", "text"], + expansions = ["attachments.media_keys"], + media_fields = ["url"]) +print("user_mentions:", user_mentions) + diff --git a/plugins/twitter/poetry.lock b/plugins/twitter/poetry.lock index 107db41a..3f60ebfc 100644 --- a/plugins/twitter/poetry.lock +++ b/plugins/twitter/poetry.lock @@ -1,117 +1,117 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "certifi" -version = "2025.1.31" +version = "2025.4.26" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["main"] files = [ - {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, - {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, + {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"}, + {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"}, ] [[package]] name = "charset-normalizer" -version = "3.4.1" +version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, - {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, - {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, + {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, + {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, ] [[package]] @@ -188,48 +188,46 @@ requests = ">=2.0.0" rsa = ["oauthlib[signedtoken] (>=3.0.0)"] [[package]] -name = "tweepy" -version = "4.15.0" -description = "Twitter library for Python" +name = "urllib3" +version = "2.4.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "tweepy-4.15.0-py3-none-any.whl", hash = "sha256:64adcea317158937059e4e2897b3ceb750b0c2dd5df58938c2da8f7eb3b88e6a"}, - {file = "tweepy-4.15.0.tar.gz", hash = "sha256:1345cbcdf0a75e2d89f424c559fd49fda4d8cd7be25cd5131e3b57bad8a21d76"}, + {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, + {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, ] -[package.dependencies] -oauthlib = ">=3.2.0,<4" -requests = ">=2.27.0,<3" -requests-oauthlib = ">=1.2.0,<3" - [package.extras] -async = ["aiohttp (>=3.7.3,<4)", "async-lru (>=1.0.3,<3)"] -dev = ["coverage (>=4.4.2)", "coveralls (>=2.1.0)", "tox (>=3.21.0)"] -docs = ["myst-parser (==0.15.2)", "readthedocs-sphinx-search (==0.1.1)", "sphinx (==4.2.0)", "sphinx-hoverxref (==0.7b1)", "sphinx-tabs (==3.2.0)", "sphinx_rtd_theme (==1.0.0)"] -socks = ["requests[socks] (>=2.27.0,<3)"] -test = ["urllib3 (<2)", "vcrpy (>=1.10.3)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] -name = "urllib3" -version = "2.3.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "virtuals-tweepy" +version = "0.1.2" +description = "A fork of Tweepy adapted to work with Virtuals's enterprise Twitter endpoint" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, - {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, + {file = "virtuals_tweepy-0.1.2-py3-none-any.whl", hash = "sha256:a9497d1f1c5a208a952ff5581912a93441e8745b714c0cc4adb3a7db51bb402c"}, + {file = "virtuals_tweepy-0.1.2.tar.gz", hash = "sha256:ae679f1582a2f097c5f480ec43a926f4f599ad2bbf94bf1dc32691d55268c019"}, ] +[package.dependencies] +oauthlib = ">=3.2.0,<4" +requests = ">=2.27.0,<3" +requests-oauthlib = ">=1.2.0,<3" + [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +async-support = ["aiohttp (>=3.7.3,<4)", "async-lru (>=1.0.3,<3)"] +dev = ["coverage (>=4.4.2)", "coveralls (>=2.1.0)", "tox (>=3.21.0)"] +test = ["urllib3 (<2)", "vcrpy (>=1.10.3)"] [metadata] lock-version = "2.1" python-versions = ">=3.9" -content-hash = "c5f7fef3354005f1a49085c65559d1cb878e2c07f1c1c51f51dfa70844d5d102" +content-hash = "f1b55163345bc05bd4c195f8f37f8bd3f13b10dc741ad042ecd134a588fba6c3" diff --git a/plugins/twitter/pyproject.toml b/plugins/twitter/pyproject.toml index 6e547c2b..a99dcae9 100644 --- a/plugins/twitter/pyproject.toml +++ b/plugins/twitter/pyproject.toml @@ -12,7 +12,7 @@ repository = "https://github.com/game-by-virtuals/game-python" [tool.poetry.dependencies] python = ">=3.9" -tweepy = ">=4.15.0" +virtuals-tweepy = "^0.1.3" [tool.poetry.scripts] twitter-plugin-gamesdk = "twitter_plugin_gamesdk.game_twitter_auth:start" \ No newline at end of file diff --git a/plugins/twitter/twitter_plugin_gamesdk/game_twitter_plugin.py b/plugins/twitter/twitter_plugin_gamesdk/game_twitter_plugin.py deleted file mode 100644 index ab703c19..00000000 --- a/plugins/twitter/twitter_plugin_gamesdk/game_twitter_plugin.py +++ /dev/null @@ -1,205 +0,0 @@ -""" -GAME Twitter Plugin for the GAME SDK. - -This plugin provides a wrapper around the Twitter API using a GAME twitter endpoint (maintained by GAME devs), -enabling GAME SDK agents to interact with Twitter programmatically using the access token from -game_twitter_auth.py. It supports common Twitter operations like posting tweets, replying and quoting. - -Example: - ```python - options = { - "id": "twitter_agent", - "name": "Twitter Bot", - "description": "A Twitter bot that posts updates", - "credentials": { - "gameTwitterAccessToken": "your_access_token" - } - } - - game_twitter_plugin = GameTwitterPlugin(options) - post_tweet_fn = game_twitter_plugin.get_function('post_tweet') - post_tweet_fn("Hello, World!") - ``` -""" - -import requests -from typing import Optional, Dict, Any, Callable, List -import os -import logging - - -class GameTwitterPlugin: - """ - Used to make Twitter API requests using GAME access token - """ - def __init__(self, options: Dict[str, Any]) -> None: - """ - Initialize the client with an access token and base URL. - """ - # Set credentials - self.id = options.get("id", "game_twitter_plugin") - self.name = options.get("name", "GAME Twitter Plugin") - self.description = options.get( - "description", - "A plugin that executes tasks within Twitter, capable of posting, replying, quoting, and liking tweets, and getting metrics.", - ) - credentials = options.get("credentials") - if not credentials: - raise ValueError("Twitter API credentials are required.") - # Set GAME Twitter configs, e.g. base URL - self.base_url = "https://twitter.game.virtuals.io/tweets" - self.headers = { - "Content-Type": "application/json", - "x-api-key": credentials.get("gameTwitterAccessToken", None), - } - # Define internal function mappings - self._functions: Dict[str, Callable[..., Any]] = { - "reply_tweet": self._reply_tweet, - "post_tweet": self._post_tweet, - "like_tweet": self._like_tweet, - "quote_tweet": self._quote_tweet, - "search_tweets": self._search_tweets, - "get_authenticated_user": self._get_authenticated_user, - "mentions": self._mentions, - "followers": self._followers, - "following": self._following - } - # Configure logging - logging.basicConfig(level=logging.INFO) - self.logger: logging.Logger = logging.getLogger(__name__) - - @property - def available_functions(self) -> List[str]: - """ - Get a list of all available Twitter functions. - - Returns: - List[str]: Names of all available functions in this plugin. - """ - return list(self._functions.keys()) - - def get_function(self, fn_name: str) -> Callable: - """ - Retrieve a specific Twitter function by name. - - Args: - fn_name (str): Name of the function to retrieve. - - Returns: - Callable: The requested function. - - Raises: - ValueError: If the requested function name is not found. - - Example: - ```python - post_tweet = twitter_plugin.get_function('post_tweet') - post_tweet("Hello from GAME SDK!") - ``` - """ - if fn_name not in self._functions: - raise ValueError( - f"Function '{fn_name}' not found. Available functions: {', '.join(self.available_functions)}" - ) - return self._functions[fn_name] - - def _fetch_api(self, endpoint: str, method: str = "GET", data: Optional[Dict] = None): - """ - Generic method to handle API requests. - """ - url = f"{self.base_url}{endpoint}" - response = requests.request(method, url, headers=self.headers, json=data) - - if response.status_code not in [200, 201]: - raise Exception(f"Error {response.status_code}: {response.text}") - - return response.json() - - def _post_tweet(self, tweet: str, media_ids: Optional[List[str]] = None) -> Dict[str, Any]: - """ - Post a tweet with optional media. - """ - if media_ids and len(media_ids) > 4: - raise ValueError("media_ids cannot contain more than 4 items.") - payload = {"content": tweet} - if media_ids: - payload["mediaIds"] = media_ids - return self._fetch_api("/post", "POST", data=payload) - - def _search_tweets(self, query: str) -> Dict[str, Any]: - """ - Search for tweets. - """ - return self._fetch_api(f"/search?query={requests.utils.quote(query)}", "GET") - - def _reply_tweet(self, tweet_id: int, reply: str, media_ids: Optional[str] = None) -> None: - """ - Reply to a tweet. - """ - if media_ids and len(media_ids) > 4: - raise ValueError("media_ids cannot contain more than 4 items.") - payload = {"content": reply} - if media_ids: - payload["mediaIds"] = media_ids - return self._fetch_api(f"/reply/{tweet_id}", "POST", data=payload) - - def _like_tweet(self, tweet_id: int) -> None: - """ - Like a tweet. - """ - return self._fetch_api(f"/like/{tweet_id}", "POST") - - def _quote_tweet(self, tweet_id: int, quote: str, media_ids: Optional[str] = None) -> None: - """ - Quote a tweet. - """ - if media_ids and len(media_ids) > 4: - raise ValueError("media_ids cannot contain more than 4 items.") - payload = {"content": quote} - if media_ids: - payload["mediaIds"] = media_ids - return self._fetch_api(f"/quote/{tweet_id}", "POST", data=payload) - - def _get_authenticated_user(self) -> Dict[str, Any]: - """ - Get details of the authenticated user. - """ - return self._fetch_api("/me", "GET") - - def _mentions(self, pagination_token: Optional[str] = None) -> Dict[str, Any]: - """ - Get mentions of the authenticated user. - """ - endpoint = "/mentions" - if pagination_token: - endpoint += f"?paginationToken={paginationToken}" - return self._fetch_api(endpoint, "GET") - - def _followers(self, pagination_token: Optional[str] = None) -> Dict[str, Any]: - """ - Get followers of the authenticated user. - """ - endpoint = "/followers" - if pagination_token: - endpoint += f"?paginationToken={paginationToken}" - return self._fetch_api(endpoint, "GET") - - def _following(self, pagination_token: Optional[str] = None) -> Dict[str, Any]: - """ - Get list of users whom the authenticated user is following. - """ - endpoint = "/following" - if pagination_token: - endpoint += f"?paginationToken={paginationToken}" - return self._fetch_api(endpoint, "GET") - - def upload_media(self, media: bytes) -> str: - """ - Uploads media (e.g. image, video) to X and returns the media ID. - """ - response = requests.post( - url = f"{self.base_url}/media", - headers = {k: v for k, v in self.headers.items() if k != "Content-Type"}, - files = {"file": media} - ) - return response.json().get("mediaId") \ No newline at end of file diff --git a/plugins/twitter/twitter_plugin_gamesdk/twitter_plugin.py b/plugins/twitter/twitter_plugin_gamesdk/twitter_plugin.py index 1c0e3911..51416084 100644 --- a/plugins/twitter/twitter_plugin_gamesdk/twitter_plugin.py +++ b/plugins/twitter/twitter_plugin_gamesdk/twitter_plugin.py @@ -26,10 +26,9 @@ ``` """ -import tweepy +import virtuals_tweepy import logging -from typing import Dict, Callable, Any, Optional, List, Callable - +from typing import Dict, Any class TwitterPlugin: """ @@ -58,232 +57,24 @@ class TwitterPlugin: def __init__(self, options: Dict[str, Any]) -> None: # Set credentials - self.id = options.get("id", "twitter_plugin") - self.name = options.get("name", "Twitter Plugin") - self.description = options.get( - "description", - "A plugin that executes tasks within Twitter, capable of posting, replying, quoting, and liking tweets, and getting metrics.", - ) + self.base_url = options.get("base_url", "https://twitter.game.virtuals.io") + '/tweets' credentials = options.get("credentials") if not credentials: raise ValueError("Twitter API credentials are required.") # Init Tweepy client - self.twitter_client: tweepy.Client = tweepy.Client( + self.twitter_client: virtuals_tweepy.Client = virtuals_tweepy.Client( bearer_token = credentials.get("bearerToken"), consumer_key = credentials.get("apiKey"), consumer_secret = credentials.get("apiSecretKey"), access_token = credentials.get("accessToken"), access_token_secret=credentials.get("accessTokenSecret"), - return_type = dict + return_type = dict, + game_twitter_access_token = credentials.get("gameTwitterAccessToken"), ) - # Define internal function mappings - self._functions: Dict[str, Callable[..., Any]] = { - "get_metrics": self._get_metrics, - "reply_tweet": self._reply_tweet, - "post_tweet": self._post_tweet, - "like_tweet": self._like_tweet, - "quote_tweet": self._quote_tweet, - "get_user_from_handle": self._get_user_from_handle, - "get_user_mentions": self._get_user_mentions - } # Configure logging logging.basicConfig(level=logging.INFO) self.logger: logging.Logger = logging.getLogger(__name__) - - @property - def available_functions(self) -> List[str]: - """ - Get a list of all available Twitter functions. - - Returns: - List[str]: Names of all available functions in this plugin. - """ - return list(self._functions.keys()) - - def get_function(self, fn_name: str) -> Callable: - """ - Retrieve a specific Twitter function by name. - - Args: - fn_name (str): Name of the function to retrieve. - - Returns: - Callable: The requested function. - - Raises: - ValueError: If the requested function name is not found. - - Example: - ```python - post_tweet = twitter_plugin.get_function('post_tweet') - post_tweet("Hello from GAME SDK!") - ``` - """ - if fn_name not in self._functions: - raise ValueError( - f"Function '{fn_name}' not found. Available functions: {', '.join(self.available_functions)}" - ) - return self._functions[fn_name] - - def _get_metrics(self) -> Dict[str, int]: - """ - Get engagement metrics for the authenticated user. - - Returns: - Dict[str, int]: User metrics including followers, following, and tweets. - - Raises: - tweepy.TweepyException: If there's an error accessing the user metrics. - """ - try: - user = self.twitter_client.get_me(user_fields=["public_metrics"]) - if not user or not user.get("data", None): - self.logger.warning("Failed to fetch user metrics.") - return {} - public_metrics = user.get("data", {}).get("public_metrics", None) - return { - "followers": public_metrics.get("followers_count", 0), - "following": public_metrics.get("following_count", 0), - "tweets": public_metrics.get("tweet_count", 0), - } - except tweepy.TweepyException as e: - self.logger.error(f"Failed to fetch metrics: {e}") - return {} - - def _reply_tweet(self, tweet_id: int, reply: str, media_ids: Optional[str] = None) -> None: - """ - Reply to a specific tweet. - - Args: - tweet_id (int): ID of the tweet to reply to. - reply (str): Content of the reply. - - Raises: - tweepy.TweepyException: If there's an error posting the reply. - """ - try: - if media_ids and len(media_ids) > 4: - raise ValueError("media_ids cannot contain more than 4 items.") - self.twitter_client.create_tweet(in_reply_to_tweet_id=tweet_id, text=reply) - self.logger.info(f"Successfully replied to tweet {tweet_id}.") - except tweepy.TweepyException as e: - self.logger.error(f"Failed to reply to tweet {tweet_id}: {e}") - - def _post_tweet(self, tweet: str, media_ids: Optional[str] = None) -> Dict[str, Any]: - """ - Post a new tweet. - - Args: - tweet (str): Content of the tweet. - - Returns: - Dict[str, Any]: Details of the posted tweet. - - Raises: - tweepy.TweepyException: If there's an error posting the tweet. - """ - try: - if media_ids and len(media_ids) > 4: - raise ValueError("media_ids cannot contain more than 4 items.") - self.twitter_client.create_tweet(text=tweet) - self.logger.info("Tweet posted successfully.") - except tweepy.TweepyException as e: - self.logger.error(f"Failed to post tweet: {e}") - - def _like_tweet(self, tweet_id: int) -> None: - """ - Like a specific tweet. - - Args: - tweet_id (int): ID of the tweet to like. - - Raises: - tweepy.TweepyException: If there's an error liking the tweet. - """ - try: - self.twitter_client.like(tweet_id) - self.logger.info(f"Tweet {tweet_id} liked successfully.") - except tweepy.TweepyException as e: - self.logger.error(f"Failed to like tweet {tweet_id}: {e}") - - def _quote_tweet(self, tweet_id: int, quote: str, media_ids: Optional[str] = None) -> None: - """ - Quote a specific tweet with additional text. - - Args: - tweet_id (int): ID of the tweet to quote. - quote (str): Text to add to the quote. - - Raises: - tweepy.TweepyException: If there's an error posting the quote tweet. - """ - try: - self.twitter_client.create_tweet(quote_tweet_id=tweet_id, text=quote, media_ids=media_ids) - self.logger.info(f"Successfully quoted tweet {tweet_id}.") - except tweepy.TweepyException as e: - self.logger.error(f"Failed to quote tweet {tweet_id}: {e}") - - def _get_user_from_handle(self, username) -> Optional[int]: - """ - Extract the Twitter user ID from a profile URL using TwitterClient. - """ - try: - # Fetch user information using the Twitter client - user = self.twitter_client.get_user(username=username) - return user['data']['id'] - except tweepy.TweepyException as e: - self.logger.warning(f"Error fetching user data: {e}") - return None - - def _get_user_mentions(self, user_id: int, end_time=None, max_results: int = 10) -> Optional[List[Dict]]: - """ - Fetch mentions for a specific user - """ - try: - # Fetch mentions using the Twitter client - mentions = self.twitter_client.get_users_mentions( - id = user_id, - end_time = end_time, - max_results = max_results, - tweet_fields = ["id", "created_at", "text"], - expansions = ["attachments.media_keys"], - media_fields = ["url"] - ) - if not mentions['data']: - return None - # Create a mapping of media keys to media URLs - media_dict = {} - media_list = mentions.get('includes', {}).get('media', []) - if media_list: - for media in media_list: - if 'url' in media: - media_dict[media['media_key']] = media['url'] - result = [] - for mention in mentions['data']: - media_keys = mention.get('attachments', {}).get('media_keys', []) - # Get image urls from media_dict - media_urls = [] - for media_key in media_keys: - if media_key in media_dict: - media_urls.append(media_dict[media_key]) - # Append tweet text and media URLs to the result - result.append({ - "id": mention["id"], - "text": mention["text"], - "media_urls": media_urls - }) - return result - except tweepy.TweepyException as e: - self.logger.warning(f"Error fetching user mentions: {e}") - return [] - def _get_tweets(self, username: str, max_results: int = 100) -> Optional[List[Dict]]: - """ - Fetch tweets for a specific user - """ - try: - tweets = self.twitter_client.get_users_tweets(username, max_results=max_results) - return tweets - except tweepy.TweepyException as e: - self.logger.warning(f"Error fetching tweets: {e}") - return [] + self.game_twitter_access_token = credentials.get("gameTwitterAccessToken") + +