Skip to content

Commit

Permalink
feat: cookie login
Browse files Browse the repository at this point in the history
  • Loading branch information
luoling8192 committed Mar 3, 2025
1 parent 95074bb commit 4c1040f
Show file tree
Hide file tree
Showing 10 changed files with 537 additions and 70 deletions.
25 changes: 19 additions & 6 deletions services/twitter-services/.env.example
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
# BrowserBase 配置
# BrowserBase Config
BROWSERBASE_API_KEY=your_api_key_here

# Twitter 账号配置
# Browser Config
BROWSER_HEADLESS=false
BROWSER_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
BROWSER_VIEWPORT_WIDTH=1280
BROWSER_VIEWPORT_HEIGHT=800
BROWSER_TIMEOUT=30000
BROWSER_REQUEST_TIMEOUT=20000
BROWSER_REQUEST_RETRIES=2

# Twitter Account Config
TWITTER_USERNAME=your_twitter_username
TWITTER_PASSWORD=your_twitter_password
# Cookie can be in two formats:
# 1. JSON format: {"auth_token":"xxx","ct0":"yyy"}
# 2. document.cookie format: auth_token=xxx; ct0=yyy
TWITTER_COOKIES=

# 适配器配置
# Adapter Config
ENABLE_AIRI=false
AIRI_URL=http://localhost:3000
AIRI_TOKEN=your_airi_token

ENABLE_MCP=true
MCP_PORT=8080

# 系统配置
LOG_LEVEL=info # 可选: error, warn, info, verbose, debug
LOG_FORMAT=pretty # 可选: json, pretty
# System Config
LOG_LEVEL=info # Optional: error, warn, info, verbose, debug
LOG_FORMAT=pretty # Optional: json, pretty
CONCURRENCY=1
35 changes: 20 additions & 15 deletions services/twitter-services/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Using listhen for optimized development experience, including automatic browser

#### 5.2.1 Authentication Service (Auth Service)

Handles Twitter login and session maintenance.
Handles Twitter session detection and maintenance. Uses a manual login approach where the service opens the Twitter login page and waits for the user to complete the authentication process. After successful login, the service can save cookies for future sessions, allowing cookie-based authentication in subsequent uses.

#### 5.2.2 Timeline Service (Timeline Service)

Expand Down Expand Up @@ -196,15 +196,23 @@ async function main() {
// Create Twitter service
const twitter = new TwitterService(browser)

// Login
await twitter.login({
username: 'your-username',
password: 'your-password'
})
// Initiate manual login and wait for user to complete authentication
const loggedIn = await twitter.login({})

// Get timeline
const tweets = await twitter.getTimeline({ count: 10 })
console.log(tweets)
if (loggedIn) {
console.log('Login successful')

// Export cookies for future use (optional)
const cookies = await twitter.exportCookies()
console.log('Cookies saved for future use:', Object.keys(cookies).length)

// Get timeline
const tweets = await twitter.getTimeline({ count: 10 })
console.log(tweets)
}
else {
console.error('Login failed')
}

// Release resources
await browser.close()
Expand All @@ -222,14 +230,10 @@ async function startAiriModule() {

const twitter = new TwitterService(browser)

// Create Airi adapter
// Create Airi adapter (no credentials needed for manual login)
const airiAdapter = new AiriAdapter(twitter, {
url: process.env.AIRI_URL,
token: process.env.AIRI_TOKEN,
credentials: {
username: process.env.TWITTER_USERNAME,
password: process.env.TWITTER_PASSWORD
}
token: process.env.AIRI_TOKEN
})

// Start adapter
Expand Down Expand Up @@ -289,6 +293,7 @@ For example, adding "Get Tweets from a Specific User" functionality:
- **Monitoring & Alerts**: Monitor service status and Twitter access limitations
- **Selector Updates**: Regularly validate and update selector configurations
- **Session Management**: Optimize session management to improve stability
- **Cookie Management**: Implement secure storage for saved session cookies

## 12. Project Roadmap

Expand Down
2 changes: 1 addition & 1 deletion services/twitter-services/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"license": "MIT",
"scripts": {
"start": "tsx src/main.ts",
"dev": "tsx watch src/main.ts",
"dev": "tsx src/main.ts",
"dev:mcp": "tsx src/dev-server.ts",
"postinstall": "playwright install chromium"
},
Expand Down
15 changes: 15 additions & 0 deletions services/twitter-services/src/adapters/browser-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ export interface BrowserAdapter {
*/
getElements: (selector: string) => Promise<ElementHandle[]>

/**
* Get all cookies from the browser context
* This includes HTTP_ONLY cookies that can't be accessed via document.cookie
*/
getAllCookies: () => Promise<Array<{
name: string
value: string
domain?: string
path?: string
expires?: number
httpOnly?: boolean
secure?: boolean
sameSite?: 'Strict' | 'Lax' | 'None'
}>>

/**
* Get screenshot
*/
Expand Down
112 changes: 112 additions & 0 deletions services/twitter-services/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,117 @@ program
}
})

// Export cookies command
program
.command('export-cookies')
.description('Login and export cookies for later use')
.option('-o, --output <path>', 'Output cookies to file', 'twitter-cookies.json')
.option('-f, --format <format>', 'Cookie format (json or string)', 'json')
.action(async (options) => {
try {
const configManager = createDefaultConfig()
const config = configManager.getConfig()

// Initialize browser
const browser = new StagehandBrowserAdapter(config.browser.apiKey)
await browser.initialize(config.browser)

// Create service and login
const twitterService = new TwitterService(browser)

if (!config.twitter.credentials) {
throw new Error('Cannot get Twitter credentials, please check configuration')
}

const loggedIn = await twitterService.login(config.twitter.credentials)

if (!loggedIn) {
throw new Error('Login failed, please check credentials')
}

console.log('Login successful, exporting cookies...')

// Export cookies - specify the format based on user option
const cookieFormat = options.format === 'string' ? 'string' : 'object'
const cookies = await twitterService.exportCookies(cookieFormat)

if (cookieFormat === 'string') {
// Save raw string to file
fs.writeFileSync(options.output, cookies as string)
}
else {
// Save JSON to file
fs.writeFileSync(options.output, JSON.stringify(cookies, null, 2))
}

console.log(`Cookies saved to ${options.output} in ${cookieFormat} format`)

// Close browser
await browser.close()
}
catch (error) {
console.error('Failed to export cookies:', errorToMessage(error))
process.exit(1)
}
})

// Login with cookies file command
program
.command('login-with-cookies')
.description('Login using cookies from a file')
.option('-i, --input <path>', 'Input cookies file', 'twitter-cookies.json')
.option('-t, --test', 'Test login only, do not perform other actions', false)
.action(async (options) => {
try {
const configManager = createDefaultConfig()
const config = configManager.getConfig()

// Load cookies from file
if (!fs.existsSync(options.input)) {
throw new Error(`Cookies file not found: ${options.input}`)
}

console.log(`Loading cookies from ${options.input}`)
const cookies = JSON.parse(fs.readFileSync(options.input, 'utf8'))

// Initialize browser
const browser = new StagehandBrowserAdapter(config.browser.apiKey)
await browser.initialize(config.browser)

// Create service and login with cookies
const twitterService = new TwitterService(browser)
const credentials = {
username: config.twitter.credentials?.username || '',
password: config.twitter.credentials?.password || '',
cookies,
}

console.log('Attempting to login with cookies...')
const loggedIn = await twitterService.login(credentials)

if (loggedIn) {
console.log('Login successful!')

if (options.test) {
console.log('Login test passed, exiting.')
}
else {
// Perform additional actions if needed
console.log('Add more actions here when needed')
}
}
else {
throw new Error('Login failed, cookies may be expired')
}

// Close browser
await browser.close()
}
catch (error) {
console.error('Failed to login with cookies:', errorToMessage(error))
process.exit(1)
}
})

// Parse command line arguments
program.parse()
38 changes: 31 additions & 7 deletions services/twitter-services/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,47 @@ export interface Config {
* Default configuration
*/
export function getDefaultConfig(): Config {
// Parse cookies from environment variable if provided
let cookiesFromEnv: Record<string, string> | undefined

if (process.env.TWITTER_COOKIES) {
// Try to parse as JSON first
try {
cookiesFromEnv = JSON.parse(process.env.TWITTER_COOKIES)
}
catch {
// If JSON parsing fails, treat as document.cookie format string
cookiesFromEnv = process.env.TWITTER_COOKIES
.split(';')
.map(v => v.split('='))
.reduce((acc, v) => {
// Skip empty values
if (v.length < 2)
return acc
acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim())
return acc
}, {} as Record<string, string>)
}
}

return {
browser: {
apiKey: process.env.BROWSERBASE_API_KEY || '', // Move apiKey to browser config
headless: true,
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
headless: process.env.BROWSER_HEADLESS === 'true',
userAgent: process.env.BROWSER_USER_AGENT || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
viewport: {
width: 1280,
height: 800,
width: Number.parseInt(process.env.BROWSER_VIEWPORT_WIDTH || '1280'),
height: Number.parseInt(process.env.BROWSER_VIEWPORT_HEIGHT || '800'),
},
timeout: 30000,
requestTimeout: 20000,
requestRetries: 2,
timeout: Number.parseInt(process.env.BROWSER_TIMEOUT || '30000'),
requestTimeout: Number.parseInt(process.env.BROWSER_REQUEST_TIMEOUT || '20000'),
requestRetries: Number.parseInt(process.env.BROWSER_REQUEST_RETRIES || '2'),
},
twitter: {
credentials: {
username: process.env.TWITTER_USERNAME || '',
password: process.env.TWITTER_PASSWORD || '',
cookies: cookiesFromEnv,
},
defaultOptions: {
timeline: {
Expand Down
Loading

0 comments on commit 4c1040f

Please sign in to comment.