123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- use reqwest::{header::HeaderMap, Client};
- use serde_json::{json, Value};
- use tracing::info;
- use crate::{error::NtitledError, ost::data::DownloadResponse};
- use super::data::{Login, LoginResponse, SearchResponse};
- const BASE_URL: &str = "https://api.opensubtitles.com/api/v1";
- #[derive(Debug, Clone)]
- pub struct OSClient {
- client: Client,
- token: String,
- }
- impl OSClient {
- pub async fn init() -> Self {
- let api_key = std::env::var("API_KEY").expect("missing API_KEY in env");
- let username = std::env::var("USERNAME").expect("missing USERNAME in env");
- let password = std::env::var("PASSWORD").expect("missing PASSWORD in env");
- let token = std::env::var("JWT").ok();
- if let Some(token) = token {
- info!("Initialising client with token");
- OSClient::new(api_key, token)
- .await
- .expect("error while loading client")
- } else {
- info!("Initialising client with login");
- OSClient::new_with_login(api_key, username, password)
- .await
- .expect("error while loading client")
- }
- }
- async fn new_with_login(
- api_key: String,
- username: String,
- password: String,
- ) -> Result<Self, NtitledError> {
- let mut headers = HeaderMap::new();
- headers.append("Api-Key", api_key.parse().expect("invalid api_key"));
- let client = reqwest::ClientBuilder::new()
- .default_headers(headers)
- .user_agent("Ntitled v1.0")
- .build()
- .expect("invalid client configuration");
- let login = Login {
- username: &username,
- password: &password,
- };
- let req = client
- .post(format!("{BASE_URL}/login"))
- .json(&login)
- .build()?;
- let res: LoginResponse = client.execute(req).await?.json().await?;
- let Some((_, token)) = res.token.split_once(' ') else {
- return Err(NtitledError::Message(format!(
- "received invalid token: {}",
- res.token
- )));
- };
- #[cfg(feature = "debug")]
- {
- dbg!(&token);
- use std::fmt::Write;
- let env_file =
- std::fs::read_to_string(".env").expect("env file required in debug mode");
- let mut new_env = String::new();
- let mut jwt_written = false;
- for line in env_file.lines() {
- if line.starts_with("JWT") {
- writeln!(new_env, "JWT = \"{token}\"").unwrap();
- jwt_written = true;
- } else {
- writeln!(new_env, "{line}").unwrap();
- }
- }
- if !jwt_written {
- writeln!(new_env, "JWT = \"{token}\"").unwrap();
- }
- println!("New env:\n{new_env}");
- std::fs::write(".env", new_env).expect("error while writing new env");
- }
- Ok(Self {
- client,
- token: token.to_string(),
- })
- }
- async fn new(api_key: String, token: String) -> Result<Self, NtitledError> {
- let mut headers = HeaderMap::new();
- headers.append("Api-Key", api_key.parse().expect("invalid api_key"));
- headers.append(
- "Authorization",
- format!("Bearer {token}")
- .parse()
- .expect("invalid bearer token"),
- );
- let client = reqwest::ClientBuilder::new()
- .default_headers(headers)
- .user_agent("Ntitled v1.0")
- .build()
- .expect("invalid client configuration");
- Ok(Self { client, token })
- }
- pub async fn search(
- &self,
- title: &str,
- hash: Option<&str>,
- ) -> Result<SearchResponse, NtitledError> {
- let mut req = self
- .client
- .get(format!("{BASE_URL}/subtitles"))
- .query(&[("query", title)]);
- if let Some(hash) = hash {
- req = req.query(&[("moviehash", hash)])
- }
- let req = req.build()?;
- let res: Value = self.client.execute(req).await?.json().await?;
- #[cfg(feature = "debug")]
- {
- std::fs::write("response.json", res.to_string()).unwrap();
- }
- Ok(serde_json::from_value(res)?)
- }
- pub async fn download_subtitles(
- &self,
- file_id: usize,
- full_path: &str,
- ) -> Result<(), NtitledError> {
- let req = self
- .client
- .post(format!("{BASE_URL}/download"))
- .body(json!({"file_id": file_id}).to_string())
- .header("content-type", "application/json")
- .header("accept", "application/json")
- .bearer_auth(&self.token)
- .build()?;
- let res: Value = self.client.execute(req).await?.json().await?;
- #[cfg(feature = "debug")]
- dbg!(&res);
- let res = serde_json::from_value(res)?;
- let response = match res {
- DownloadResponse::Error { message } => {
- println!("Error while downloading subtitles: {message}");
- return Err(NtitledError::Message(message));
- }
- DownloadResponse::Success(response) => response,
- };
- let req = self.client.get(response.link).build()?;
- let res = self.client.execute(req).await?.text().await?;
- info!("Writing subtitles to {full_path}");
- std::fs::write(full_path, res)?;
- Ok(())
- }
- }
|