Send browser cookies/UA for live auth

Client builds the Cookie header as 'auth=<value>; oc_locale=en' from the bare
auth value the config supplies, so oc_locale defaults to English and the
scrape selectors match. Legacy full-header configs pass through. Default
User-Agent is a desktop Chrome string; --user-agent still overrides. Fix
relative-time past tense ('1m ago' not 'ago1m'). Verified live against a real
OpenCode account: balance, three usage frames, and reset times all parse.
This commit is contained in:
ocd 2026-07-30 01:22:56 +03:00
parent 41f35aec34
commit 4463a9b51d
3 changed files with 28 additions and 5 deletions

View File

@ -22,6 +22,24 @@ pub fn go_url(workspace: &str) -> String {
GO_BASE_URL.replace("{workspace}", workspace)
}
/// Build the Cookie header from the bare `auth` value. OpenCode expects an
/// `auth` cookie plus an `oc_locale` cookie. We pin `oc_locale=en` so the page
/// renders in English and the scrape selectors match the captured pages.
///
/// If `cookie` already looks like a full header (contains `=` and `;`), pass
/// it through unchanged so legacy configs keep working.
fn cookie_header(cookie: &str) -> String {
let trimmed = cookie.trim();
if trimmed.contains('=') && (trimmed.contains(';') || trimmed.starts_with("auth=")) {
// Caller supplied a full header. Use as-is, but ensure oc_locale.
if trimmed.contains("oc_locale") {
return trimmed.to_string();
}
return format!("{trimmed}; oc_locale=en");
}
format!("auth={trimmed}; oc_locale=en")
}
pub struct HttpClient {
client: reqwest::Client,
retries: u8,
@ -59,8 +77,9 @@ impl HttpClient {
let resp = self
.client
.get(url)
.header("cookie", cookie)
.header("accept", "text/html,application/xhtml+xml,*/*;q=0.8")
.header("cookie", cookie_header(cookie))
.header("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
.header("accept-language", "en-US,en;q=0.9")
.send()
.await
.map_err(|e| {

View File

@ -18,6 +18,11 @@ use clap::Parser;
use cli::{CacheAction, Cli, Command, ConfigAction, RawFormat};
use model::{AccountReport, ReportStatus};
/// User-Agent sent on every request. Looks like a normal desktop browser so
/// OpenCode serves the same HTML the scrape selectors were built against. The
/// user can override with `--user-agent`.
const DEFAULT_USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
fn main() -> ExitCode {
let cli = Cli::parse();
let rt = match tokio::runtime::Builder::new_multi_thread()
@ -131,7 +136,7 @@ fn load_and_options(cli: &Cli) -> anyhow::Result<(config::Config, fetch::FetchOp
let user_agent = cli
.user_agent
.clone()
.unwrap_or_else(|| format!("ocd/{}", env!("CARGO_PKG_VERSION")));
.unwrap_or_else(|| DEFAULT_USER_AGENT.to_string());
let ttl = cli.ttl.or(cfg.default_ttl_secs).unwrap_or(180);
let opts = fetch::FetchOptions {
ttl_secs: ttl,

View File

@ -14,9 +14,8 @@ pub fn relative(iso: &str) -> String {
let now = Utc::now();
let d = t.signed_duration_since(now);
let secs = d.num_seconds();
let sign = if secs >= 0 { "in " } else { " ago" };
let body = dur_words(secs.abs());
format!("{sign}{body}")
if secs >= 0 { format!("in {body}") } else { format!("{body} ago") }
}
/// Absolute UTC string like "2025-08-31 17:00 UTC".