Skip to content

Commit

Permalink
Merge pull request #231 from crestalnetwork/improve/small-fixes
Browse files Browse the repository at this point in the history
Improve: small fixes
  • Loading branch information
taiyangc authored Feb 11, 2025
2 parents 85ad0d8 + 792d4a3 commit e0a23ab
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 22 deletions.
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
# Changelog

## 2025-02-11

### Improvements
- Twitter account link support redirect after authorization

## 2025-02-05

### New Features
- Acolyt integration

## 2025-02-04

### Improvements
- split scheduler to new service
- split singleton to new service

## 2025-02-03

### Breaking Changes
- Use async every where

## 2025-02-02

### Bug Fixes
- Fix bugs in twitter account binding

## 2025-02-01

### New Features
- Readonly API for better performance

## 2025-01-30

### New Features
- LLM creativity in agent config
- Agent memory cleanup by token count

## 2025-01-28

### New Features
- Enso tx CDP wallet broadcast

## 2025-01-27

### New Features
Expand Down
30 changes: 20 additions & 10 deletions app/services/twitter/oauth2.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Twitter OAuth2 authentication module."""

from urllib.parse import urlencode

from fastapi import APIRouter, Depends
from pydantic import BaseModel
from requests.auth import HTTPBasicAuth
Expand Down Expand Up @@ -30,11 +32,17 @@ def __init__(self, *, client_id, redirect_uri, scope, client_secret=None):
self._client.create_code_verifier(128), "S256"
)

def get_authorization_url(self, agent_id: str):
"""Get the authorization URL to redirect the user to"""
def get_authorization_url(self, agent_id: str, result_uri: str):
"""Get the authorization URL to redirect the user to
Args:
agent_id: ID of the agent to authenticate
result_uri: URI to redirect to after authorization
"""
state_params = {"agent_id": agent_id, "result_uri": result_uri}
authorization_url, _ = self.authorization_url(
"https://twitter.com/i/oauth2/authorize",
state=agent_id,
"https://x.com/i/oauth2/authorize",
state=urlencode(state_params),
code_challenge=self.code_challenge,
code_challenge_method="S256",
)
Expand All @@ -45,7 +53,7 @@ def get_token(self, authorization_response):
authorization response URL
"""
return super().fetch_token(
"https://api.twitter.com/2/oauth2/token",
"https://api.x.com/2/oauth2/token",
authorization_response=authorization_response,
auth=self.auth,
include_client_id=True,
Expand All @@ -55,7 +63,7 @@ def get_token(self, authorization_response):
def refresh(self, refresh_token: str):
"""Refresh token"""
return super().refresh_token(
"https://api.twitter.com/2/oauth2/token",
"https://api.x.com/2/oauth2/token",
refresh_token=refresh_token,
include_client_id=True,
)
Expand Down Expand Up @@ -93,26 +101,28 @@ class TwitterAuthResponse(BaseModel):
response_model=TwitterAuthResponse,
dependencies=[Depends(verify_jwt)],
)
async def get_twitter_auth_url(agent_id: str) -> TwitterAuthResponse:
async def get_twitter_auth_url(agent_id: str, result_uri: str) -> TwitterAuthResponse:
"""Get Twitter OAuth2 authorization URL.
Args:
agent_id: ID of the agent to authenticate
result_uri: URI to redirect to after authorization
Returns:
Object containing agent_id and authorization URL
"""
url = oauth2_user_handler.get_authorization_url(agent_id)
url = oauth2_user_handler.get_authorization_url(agent_id, result_uri)
return TwitterAuthResponse(agent_id=agent_id, url=url)


def get_authorization_url(agent_id: str) -> str:
def get_authorization_url(agent_id: str, result_uri: str) -> str:
"""Get Twitter OAuth2 authorization URL.
Args:
agent_id: ID of the agent to authenticate
result_uri: URI to redirect to after authorization
Returns:
Authorization URL with agent_id as state parameter
"""
return oauth2_user_handler.get_authorization_url(agent_id)
return oauth2_user_handler.get_authorization_url(agent_id, result_uri)
71 changes: 62 additions & 9 deletions app/services/twitter/oauth2_callback.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Twitter OAuth2 callback handler."""

from datetime import datetime, timezone
from urllib.parse import parse_qs, urlencode, urlparse

import tweepy
from fastapi import APIRouter, HTTPException
from starlette.responses import JSONResponse
from starlette.responses import JSONResponse, RedirectResponse

from app.config.config import config
from app.services.twitter.oauth2 import oauth2_user_handler
Expand All @@ -13,6 +14,22 @@
router = APIRouter(prefix="/callback/auth", tags=["Callback"])


def is_valid_url(url: str) -> bool:
"""Check if a URL is valid.
Args:
url: URL to validate
Returns:
bool: True if URL is valid, False otherwise
"""
try:
result = urlparse(url)
return all([result.scheme, result.netloc])
except (ValueError, AttributeError, TypeError):
return False


@router.get("/twitter")
async def twitter_oauth_callback(
state: str,
Expand All @@ -25,11 +42,11 @@ async def twitter_oauth_callback(
them in the database.
Args:
state: Agent ID from authorization request
state: URL-encoded state containing agent_id and result_uri
code: Authorization code from Twitter
Returns:
JSONResponse with success message
JSONResponse or RedirectResponse depending on result_uri
Raises:
HTTPException: If state/code is missing or token exchange fails
Expand All @@ -38,7 +55,16 @@ async def twitter_oauth_callback(
raise HTTPException(status_code=400, detail="Missing state or code parameter")

try:
agent_id = state
# Parse state parameter
state_params = parse_qs(state)
agent_id = state_params.get("agent_id", [""])[0]
result_uri = state_params.get("result_uri", [""])[0]

if not agent_id:
raise HTTPException(
status_code=400, detail="Missing agent_id in state parameter"
)

agent = await Agent.get(agent_id)
if not agent:
raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found")
Expand All @@ -64,21 +90,48 @@ async def twitter_oauth_callback(
client = tweepy.Client(bearer_token=token["access_token"], return_type=dict)
me = client.get_me(user_auth=False)

username = None
if me and "data" in me:
agent_data.twitter_id = me.get("data").get("id")
agent_data.twitter_username = me.get("data").get("username")
username = me.get("data").get("username")
agent_data.twitter_username = username
agent_data.twitter_name = me.get("data").get("name")

# Commit changes
await agent_data.save()

return JSONResponse(
content={"message": "Authentication successful, you can close this window"},
status_code=200,
)
# Handle response based on result_uri
if result_uri and is_valid_url(result_uri):
params = {"twitter_auth": "success", "username": username}
redirect_url = (
f"{result_uri}{'&' if '?' in result_uri else '?'}{urlencode(params)}"
)
return RedirectResponse(url=redirect_url)
else:
return JSONResponse(
content={
"message": "Authentication successful, you can close this window",
"username": username,
},
status_code=200,
)
except HTTPException as http_exc:
# Handle error response
if result_uri and is_valid_url(result_uri):
params = {"twitter_auth": "failed", "error": str(http_exc.detail)}
redirect_url = (
f"{result_uri}{'&' if '?' in result_uri else '?'}{urlencode(params)}"
)
return RedirectResponse(url=redirect_url)
# Re-raise HTTP exceptions to preserve their status codes
raise http_exc
except Exception as e:
# Handle error response for unexpected errors
if result_uri and is_valid_url(result_uri):
params = {"twitter_auth": "failed", "error": str(e)}
redirect_url = (
f"{result_uri}{'&' if '?' in result_uri else '?'}{urlencode(params)}"
)
return RedirectResponse(url=redirect_url)
# For unexpected errors, use 500 status code
raise HTTPException(status_code=500, detail=str(e))
4 changes: 1 addition & 3 deletions skills/acolyt/ask.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,11 @@ class AcolytAskGpt(AcolytBaseTool):
"""

name: str = "acolyt_ask_gpt"
description: str = (
"""
description: str = """
This tool allows users to ask questions which are then sent to the Acolyt API. this should be run if the user requests to
ask Acolyt explicitly.
The API response is processed and summarized before being returned to the user.
"""
)
args_schema: Type[BaseModel] = AcolytAskGptInput

def _run(self, question: str) -> AcolytAskGptOutput:
Expand Down

0 comments on commit e0a23ab

Please sign in to comment.