mod calendar;
mod compose;
mod draft_studio;
mod draft_tags;
mod drafts;
mod list;
mod scheduled;
use tuitbot_core::config::Config;
use tuitbot_core::storage::accounts::{account_scraper_session_path, account_token_path};
use crate::error::ApiError;
use crate::state::AppState;
pub use calendar::{calendar, schedule};
pub use compose::{compose, compose_thread, compose_tweet};
pub use draft_studio::{
archive_studio_draft, autosave_draft, create_draft_revision, create_studio_draft,
duplicate_studio_draft, get_studio_draft, list_draft_activity, list_draft_revisions,
list_studio_drafts, patch_draft_meta, reschedule_studio_draft, restore_from_revision,
restore_studio_draft, schedule_studio_draft, unschedule_studio_draft,
};
pub use draft_tags::{
assign_draft_tag, create_account_tag, list_account_tags, list_draft_tags, unassign_draft_tag,
};
pub use drafts::{
create_draft, delete_draft, edit_draft, get_draft_provenance, list_drafts, publish_draft,
schedule_draft,
};
pub use list::{list_threads, list_tweets};
pub use scheduled::{cancel_scheduled, edit_scheduled};
pub use calendar::{CalendarItem, CalendarQuery};
pub use compose::{ComposeRequest, ComposeThreadRequest, ComposeTweetRequest, ThreadBlockRequest};
pub use drafts::{CreateDraftRequest, EditDraftRequest, ScheduleDraftRequest};
pub use list::{ThreadsQuery, TweetsQuery};
pub use scheduled::EditScheduledRequest;
async fn read_approval_mode(state: &AppState, account_id: &str) -> Result<bool, ApiError> {
let config = read_effective_config(state, account_id).await?;
Ok(config.approval_mode)
}
pub(crate) async fn require_post_capable(
state: &AppState,
account_id: &str,
) -> Result<(), ApiError> {
let config = read_effective_config(state, account_id).await?;
let can_post = match config.x_api.provider_backend.as_str() {
"x_api" => {
let token_path = account_token_path(&state.data_dir, account_id);
token_path.exists()
}
"scraper" => {
let session_path = account_scraper_session_path(&state.data_dir, account_id);
session_path.exists()
}
_ => false,
};
if !can_post {
return Err(ApiError::BadRequest(
"Direct posting requires X API credentials or an imported browser session. \
Configure in Settings → X API."
.to_string(),
));
}
Ok(())
}
pub(crate) async fn can_post_for(state: &AppState, account_id: &str) -> bool {
let config = match read_effective_config(state, account_id).await {
Ok(c) => c,
Err(_) => return false,
};
match config.x_api.provider_backend.as_str() {
"x_api" => account_token_path(&state.data_dir, account_id).exists(),
"scraper" => account_scraper_session_path(&state.data_dir, account_id).exists(),
_ => false,
}
}
pub(crate) async fn read_effective_config(
state: &AppState,
account_id: &str,
) -> Result<Config, ApiError> {
state
.load_effective_config(account_id)
.await
.map_err(|e| ApiError::Internal(format!("failed to load effective config: {e}")))
}