tuitbot_server/routes/
scraper_session.rs1use std::sync::Arc;
4
5use axum::extract::State;
6use axum::Json;
7use serde::Deserialize;
8use serde_json::Value;
9use tuitbot_core::storage::accounts::account_scraper_session_path;
10use tuitbot_core::x_api::ScraperSession;
11
12use crate::account::AccountContext;
13use crate::error::ApiError;
14use crate::state::AppState;
15
16#[derive(Deserialize)]
18pub struct ImportSessionRequest {
19 pub auth_token: String,
21 pub ct0: String,
23 #[serde(default)]
25 pub username: Option<String>,
26}
27
28pub async fn get_scraper_session(
30 State(state): State<Arc<AppState>>,
31 ctx: AccountContext,
32) -> Result<Json<Value>, ApiError> {
33 let session_path = account_scraper_session_path(&state.data_dir, &ctx.account_id);
34 let session = ScraperSession::load(&session_path)
35 .map_err(|e| ApiError::Internal(format!("failed to read session: {e}")))?;
36
37 match session {
38 Some(s) => Ok(Json(serde_json::json!({
39 "exists": true,
40 "username": s.username,
41 "created_at": s.created_at,
42 }))),
43 None => Ok(Json(serde_json::json!({
44 "exists": false,
45 }))),
46 }
47}
48
49pub async fn import_scraper_session(
51 State(state): State<Arc<AppState>>,
52 ctx: AccountContext,
53 Json(body): Json<ImportSessionRequest>,
54) -> Result<Json<Value>, ApiError> {
55 if body.auth_token.trim().is_empty() || body.ct0.trim().is_empty() {
56 return Err(ApiError::BadRequest(
57 "auth_token and ct0 are required".to_string(),
58 ));
59 }
60
61 let session = ScraperSession {
62 auth_token: body.auth_token.trim().to_string(),
63 ct0: body.ct0.trim().to_string(),
64 username: body.username,
65 created_at: Some(chrono::Utc::now().to_rfc3339()),
66 };
67
68 let session_path = account_scraper_session_path(&state.data_dir, &ctx.account_id);
69
70 if let Some(parent) = session_path.parent() {
72 std::fs::create_dir_all(parent)
73 .map_err(|e| ApiError::Internal(format!("failed to create session directory: {e}")))?;
74 }
75
76 session
77 .save(&session_path)
78 .map_err(|e| ApiError::Internal(format!("failed to save session: {e}")))?;
79
80 tracing::info!(account_id = %ctx.account_id, "Browser session imported successfully");
81
82 Ok(Json(serde_json::json!({
83 "status": "imported",
84 "username": session.username,
85 "created_at": session.created_at,
86 })))
87}
88
89pub async fn delete_scraper_session(
91 State(state): State<Arc<AppState>>,
92 ctx: AccountContext,
93) -> Result<Json<Value>, ApiError> {
94 let session_path = account_scraper_session_path(&state.data_dir, &ctx.account_id);
95 let deleted = ScraperSession::delete(&session_path)
96 .map_err(|e| ApiError::Internal(format!("failed to delete session: {e}")))?;
97
98 Ok(Json(serde_json::json!({
99 "deleted": deleted,
100 })))
101}