Skip to content

Commit 87adb85

Browse files
authored
Merge pull request game-by-virtuals#79 from game-by-virtuals/twitter-game-auth
Twitter game auth
2 parents 5e835bf + 840c770 commit 87adb85

File tree

7 files changed

+413
-79
lines changed

7 files changed

+413
-79
lines changed

plugins/twitter/README.md

Lines changed: 80 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,86 @@ The Twitter plugin is a lightweight wrapper over commonly-used twitter API calls
55
## Installation
66
From this directory (`twitter`), run the installation:
77
```bash
8-
pip install -e .
8+
poetry install
99
```
1010

1111
## Usage
12-
1. 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).
13-
2. Create a project app, generate the following credentials and set the following environment variables (e.g. using a `.bashrc` or a `.zshrc` file):
14-
- `TWITTER_API_KEY`
15-
- `TWITTER_API_SECRET_KEY`
16-
- `TWITTER_ACCESS_TOKEN`
17-
- `TWITTER_ACCESS_TOKEN_SECRET`
18-
19-
3. Import and initialize the plugin to use in your worker:
20-
```python
21-
import os
22-
from twitter_plugin_gamesdk.twitter_plugin import TwitterPlugin
23-
24-
# Define your options with the necessary credentials
25-
options = {
26-
"id": "test_twitter_worker",
27-
"name": "Test Twitter Worker",
28-
"description": "An example Twitter Plugin for testing.",
29-
"credentials": {
30-
"apiKey": os.environ.get("TWITTER_API_KEY"),
31-
"apiSecretKey": os.environ.get("TWITTER_API_SECRET_KEY"),
32-
"accessToken": os.environ.get("TWITTER_ACCESS_TOKEN"),
33-
"accessTokenSecret": os.environ.get("TWITTER_ACCESS_TOKEN_SECRET"),
34-
},
35-
}
36-
# Initialize the TwitterPlugin with your options
37-
twitter_plugin = TwitterPlugin(options)
38-
39-
# Post a tweet
40-
post_tweet_fn = twitter_plugin.get_function('post_tweet')
41-
post_tweet_fn("Hello world!")
42-
```
43-
You can refer to `test_twitter.py` for more examples on how to call the twitter functions.
12+
1. Choose one of the 2 options to initialise the Twitter plugin
13+
- `GameTwitterPlugin`
14+
- This allows one to leverage GAME's X enterprise API credentials (i.e. higher rate limits)
15+
- `TwitterPlugin`
16+
- This allows one to use their own X API credentials
17+
2. Initialise plugin objects, run set-up scripts and use plugin functions
18+
- `GameTwitterPlugin`
19+
- To get the access token to us this plugin, run the following command
20+
```bash
21+
poetry run twitter-plugin-gamesdk auth -k <GAME_API_KEY>
22+
```
23+
You will see the following output:
24+
```bash
25+
Waiting for authentication...
26+
27+
Visit the following URL to authenticate:
28+
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
29+
```
30+
After authenticating, you will receive the following message:
31+
```bash
32+
Authenticated! Here's your access token:
33+
apx-<xxx>
34+
```
35+
- Set the access token as an environment variable called `GAME_TWITTER_ACCESS_TOKEN` (e.g. using a `.bashrc` or a `.zshrc` file):
36+
- Import and initialize the plugin to use in your worker:
37+
```python
38+
import os
39+
from twitter_plugin_gamesdk.game_twitter_plugin import GameTwitterPlugin
40+
41+
# Define your options with the necessary credentials
42+
options = {
43+
"id": "test_game_twitter_plugin",
44+
"name": "Test GAME Twitter Plugin",
45+
"description": "An example GAME Twitter Plugin for testing.",
46+
"credentials": {
47+
"gameTwitterAccessToken": os.environ.get("GAME_TWITTER_ACCESS_TOKEN")
48+
},
49+
}
50+
# Initialize the TwitterPlugin with your options
51+
game_twitter_plugin = GameTwitterPlugin(options)
52+
53+
# Post a tweet
54+
post_tweet_fn = game_twitter_plugin.get_function('post_tweet')
55+
post_tweet_fn("Hello world!")
56+
```
57+
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.
58+
59+
- `TwitterPlugin`
60+
- 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).
61+
- Create a project app, generate the following credentials and set the following environment variables (e.g. using a `.bashrc` or a `.zshrc` file):
62+
- `TWITTER_API_KEY`
63+
- `TWITTER_API_SECRET_KEY`
64+
- `TWITTER_ACCESS_TOKEN`
65+
- `TWITTER_ACCESS_TOKEN_SECRET`
66+
- Import and initialize the plugin to use in your worker:
67+
```python
68+
import os
69+
from twitter_plugin_gamesdk.twitter_plugin import TwitterPlugin
70+
71+
# Define your options with the necessary credentials
72+
options = {
73+
"id": "test_twitter_worker",
74+
"name": "Test Twitter Worker",
75+
"description": "An example Twitter Plugin for testing.",
76+
"credentials": {
77+
"apiKey": os.environ.get("TWITTER_API_KEY"),
78+
"apiSecretKey": os.environ.get("TWITTER_API_SECRET_KEY"),
79+
"accessToken": os.environ.get("TWITTER_ACCESS_TOKEN"),
80+
"accessTokenSecret": os.environ.get("TWITTER_ACCESS_TOKEN_SECRET"),
81+
},
82+
}
83+
# Initialize the TwitterPlugin with your options
84+
twitter_plugin = TwitterPlugin(options)
85+
86+
# Post a tweet
87+
post_tweet_fn = twitter_plugin.get_function('post_tweet')
88+
post_tweet_fn("Hello world!")
89+
```
90+
You can refer to `examples/test_twitter.py` for more examples on how to call the twitter functions.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import os
2+
from twitter_plugin_gamesdk.game_twitter_plugin import GameTwitterPlugin
3+
4+
# Define your options with the necessary credentials
5+
options = {
6+
"id": "test_game_twitter_plugin",
7+
"name": "Test GAME Twitter Plugin",
8+
"description": "An example GAME Twitter Plugin for testing.",
9+
"credentials": {
10+
"gameTwitterAccessToken": os.environ.get("GAME_TWITTER_ACCESS_TOKEN")
11+
},
12+
}
13+
14+
# Initialize the TwitterPlugin with your options
15+
game_twitter_plugin = GameTwitterPlugin(options)
16+
17+
# Test case 1: Post a Tweet
18+
print("\nRunning Test Case 1: Post a Tweet")
19+
post_tweet_fn = game_twitter_plugin.get_function('post_tweet')
20+
post_tweet_fn("Hello world! This is a test tweet from the GAME Twitter Plugin!")
21+
print("Posted tweet!")
22+
23+
# Test case 2: Reply to a Tweet
24+
print("\nRunning Test Case 2: Reply to a Tweet")
25+
reply_tweet_fn = game_twitter_plugin.get_function('reply_tweet')
26+
reply_tweet_fn(tweet_id=1879472470362816626, reply="Hey! This is a test reply!")
27+
print("Replied to tweet!")
28+
29+
# Test case 3: Quote a Tweet
30+
print("\nRunning Test Case 3: Quote a Tweet")
31+
quote_tweet_fn = game_twitter_plugin.get_function('quote_tweet')
32+
quote_tweet_fn(tweet_id=1879472470362816626, quote="Hey! This is a test quote tweet!")
33+
print("Quoted tweet!")
34+
35+
# Test case 4: Search Tweets
36+
print("\nRunning Test Case 4: Search Tweets")
37+
search_tweets_fn = game_twitter_plugin.get_function('search_tweets')
38+
response = search_tweets_fn(query="Python")
39+
print(f"Searched tweets: {response}")
40+
41+
# Test case 5: Get authenticated user
42+
print("\nRunning Test Case 5: Get details of authenticated user")
43+
get_authenticated_user_fn = game_twitter_plugin.get_function('get_authenticated_user')
44+
response = get_authenticated_user_fn()
45+
print(f"Got details of authenticated user: {response}")
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
# Define your options with the necessary credentials
55
options = {
6-
"id": "test_twitter_worker",
7-
"name": "Test Twitter Worker",
6+
"id": "test_twitter_plugin",
7+
"name": "Test Twitter Plugin",
88
"description": "An example Twitter Plugin for testing.",
99
"credentials": {
1010
"bearerToken": os.environ.get("TWITTER_BEARER_TOKEN"),
@@ -19,16 +19,16 @@
1919
twitter_plugin = TwitterPlugin(options)
2020

2121
# Test case 1: Post a Tweet
22-
print("Running Test Case 1: Post a Tweet")
22+
print("\nRunning Test Case 1: Post a Tweet")
2323
post_tweet_fn = twitter_plugin.get_function('post_tweet')
24-
post_tweet_fn("Hi! Hello world! This is a test tweet from the Twitter Plugin!")
24+
post_tweet_fn("Hello world! This is a test tweet from the Twitter Plugin!")
2525
print("Posted tweet!")
2626

2727
# Test case 2: Reply to a Tweet
2828
print("\nRunning Test Case 2: Reply to a Tweet")
2929
reply_tweet_fn = twitter_plugin.get_function('reply_tweet')
3030
reply_tweet_fn(tweet_id=1879472470362816626, reply="Hey! This is a test reply!")
31-
print("Liked tweet!")
31+
print("Replied to tweet!")
3232

3333
# Test case 3: Like a Tweet
3434
print("\nRunning Test Case 3: Like a Tweet")

plugins/twitter/pyproject.toml

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,18 @@
11
[build-system]
2-
requires = ["hatchling"]
3-
build-backend = "hatchling.build"
2+
requires = ["poetry-core>=1.0.0"]
3+
build-backend = "poetry.core.masonry.api"
44

5-
[project]
5+
[tool.poetry]
66
name = "twitter_plugin_gamesdk"
7-
version = "0.1.0"
8-
authors = [
9-
{ name = "Celeste Ang", email = "[email protected]" },
10-
]
7+
version = "0.2.0"
118
description = "Twitter Plugin for Python SDK for GAME by Virtuals"
12-
requires-python = ">=3.8"
13-
classifiers = [
14-
"Programming Language :: Python :: 3",
15-
"Programming Language :: Python :: 3.8",
16-
"Programming Language :: Python :: 3.9",
17-
"Programming Language :: Python :: 3.10",
18-
"Programming Language :: Python :: 3.11",
19-
"License :: OSI Approved :: MIT License",
20-
"Operating System :: OS Independent",
21-
"Development Status :: 3 - Alpha",
22-
"Intended Audience :: Developers",
23-
"Topic :: Software Development :: Libraries :: Python Modules",
24-
]
25-
dependencies = [
26-
"tweepy>=4.15.0"
27-
]
9+
authors = ["Celeste Ang <[email protected]>"]
10+
homepage = "https://github.com/game-by-virtuals/game-python"
11+
repository = "https://github.com/game-by-virtuals/game-python"
2812

29-
[tool.hatch.build.targets.wheel]
30-
packages = ["twitter_plugin_gamesdk"]
13+
[tool.poetry.dependencies]
14+
python = ">=3.9"
15+
tweepy = ">=4.15.0"
3116

32-
[project.urls]
33-
"Homepage" = "https://github.com/game-by-virtuals/game-python"
34-
"Bug Tracker" = "https://github.com/game-by-virtuals/game-python"
17+
[tool.poetry.scripts]
18+
twitter-plugin-gamesdk = "twitter_plugin_gamesdk.game_twitter_auth:start"
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import argparse
2+
import requests
3+
import webbrowser
4+
from http.server import BaseHTTPRequestHandler, HTTPServer
5+
from urllib.parse import urlparse, parse_qs
6+
from typing import Optional
7+
from dataclasses import dataclass
8+
9+
BASE_URL = "https://twitter.game.virtuals.io/accounts"
10+
11+
12+
@dataclass
13+
class AuthCredentials:
14+
"""
15+
Data class to hold authentication credentials.
16+
"""
17+
api_key: str
18+
access_token: Optional[str] = None
19+
20+
21+
class AuthHandler(BaseHTTPRequestHandler):
22+
"""
23+
Handles OAuth authentication callback from Twitter.
24+
"""
25+
26+
def do_GET(self) -> None:
27+
parsed_url = urlparse(self.path)
28+
if parsed_url.path == "/callback":
29+
query_params = parse_qs(parsed_url.query)
30+
code: Optional[str] = query_params.get("code", [None])[0]
31+
state: Optional[str] = query_params.get("state", [None])[0]
32+
33+
if code and state:
34+
access_token = AuthManager.verify_auth(code, state)
35+
self.send_response(200)
36+
self.send_header("Content-type", "text/plain")
37+
self.end_headers()
38+
self.wfile.write("Authentication successful! You may close this window and return to the terminal.")
39+
print("Authentication successful!")
40+
print(f"Access Token: {access_token}")
41+
self.server.shutdown() # Stop the server after successful auth
42+
else:
43+
self.send_response(400)
44+
self.end_headers()
45+
self.wfile.write("Invalid request")
46+
else:
47+
self.send_response(404)
48+
self.end_headers()
49+
self.wfile.write("Not Found")
50+
51+
52+
class AuthManager:
53+
"""
54+
Manages OAuth authentication flow.
55+
"""
56+
57+
@staticmethod
58+
def get_login_url(api_key: str) -> str:
59+
"""
60+
Fetches the login URL from the authentication server.
61+
"""
62+
response = requests.get(f"{BASE_URL}/auth", headers={"x-api-key": api_key})
63+
response.raise_for_status()
64+
return response.json().get("url")
65+
66+
@staticmethod
67+
def verify_auth(code: str, state: str) -> str:
68+
"""
69+
Verifies authentication and retrieves an access token.
70+
"""
71+
response = requests.get(f"{BASE_URL}/verify", params={"code": code, "state": state})
72+
response.raise_for_status()
73+
return response.json().get("token")
74+
75+
@staticmethod
76+
def start_authentication(api_key: str, port: int = 8714) -> None:
77+
"""
78+
Starts a temporary web server to handle authentication.
79+
"""
80+
SERVER_ADDRESS = ("", port)
81+
HANDLER_CLASS = AuthHandler
82+
with HTTPServer(SERVER_ADDRESS, HANDLER_CLASS) as server:
83+
login_url = AuthManager.get_login_url(api_key)
84+
print("Waiting for authentication...")
85+
print("Visit the following URL to authenticate:")
86+
print(login_url)
87+
webbrowser.open(login_url)
88+
server.serve_forever()
89+
90+
def start() -> None:
91+
"""
92+
Entry point for the game twitter auth process.
93+
"""
94+
parser = argparse.ArgumentParser(prog="game-twitter-plugin", description="CLI to authenticate and interact with GAME's Twitter API")
95+
parser.add_argument("auth", help="Authenticate with Twitter API", nargs='?')
96+
parser.add_argument("-k", "--key", help="Project's API key", required=True, type=str)
97+
args = parser.parse_args()
98+
AuthManager.start_authentication(args.key)
99+
100+
if __name__ == "__main__":
101+
start()

0 commit comments

Comments
 (0)