atac 0.22.1

Arguably a Terminal API Client. Feature-full, free, open-source, offline and account-less.
use std::io::stdout;
use crate::app::app::App;
use crate::cli::commands::request_commands::send::SendCommand;
use crate::models::request::Request;
use crate::models::response::ResponseContent;
use anyhow::anyhow;
use parking_lot::RwLock;
use ratatui::backend::Backend;
use ratatui::layout::Rect;
use ratatui::prelude::CrosstermBackend;
use ratatui::{Terminal, TerminalOptions, Viewport};
use ratatui_image::picker::Picker;
use ratatui_image::{Resize, ResizeEncodeRender};
use std::sync::Arc;
use chrono::Local;
use futures_util::SinkExt;
use tokio::io;
use tokio::io::{AsyncBufReadExt, BufReader};
use tracing::info;
use crate::app::business_logic::request::http::send::send_http_request;
use crate::app::business_logic::request::ws::send::send_ws_request;
use crate::models::protocol::protocol::Protocol;
use crate::models::protocol::ws::message_type::MessageType;
use crate::models::protocol::ws::ws::{Message, Sender};

impl App<'_> {
    pub async fn cli_send_request(&mut self, collection_index: usize, request_index: usize, send_command: &SendCommand) -> anyhow::Result<()> {
        let local_request = self.get_request_as_local_from_indexes(&(collection_index, request_index));

        self.local_send_request(&send_command, local_request).await?;

        if self.config.should_save_requests_response() {
            self.save_collection_to_file(collection_index);
        }

        Ok(())
    }

    pub async fn cli_send_collection(&mut self, collection_name: &str, send_command: &SendCommand) -> anyhow::Result<()> {
        let collection_index = self.find_collection(collection_name)?;
        let collection = &self.collections[collection_index];

        let mut requests: Vec<Arc<RwLock<Request>>> = vec![];

        for request in &collection.requests {
            let local_request = request.clone();
            requests.push(local_request);
        }

        for request in requests {
            self.local_send_request(&send_command, request).await?;

            if self.config.should_save_requests_response() {
                self.save_collection_to_file(collection_index);
            }
        }

        Ok(())
    }

    pub async fn local_send_request(&mut self, send_command: &SendCommand, local_request: Arc<RwLock<Request>>) -> anyhow::Result<()> {
        let mut request = local_request.write();

         if let Some(env_name )= &send_command.env {
            let env_index = self.find_environment(env_name)?;
             self.selected_environment = env_index;
        };
        
        if send_command.request_name {
            println!("{}", request.name);
        }
        
        let prepared_request = match self.prepare_request(&mut request).await {
            Ok(prepared_request) => prepared_request,
            Err(error) => {
                if send_command.console {
                    if let Some(pre_request_output) = &request.console_output.pre_request_output {
                        println!("{}", pre_request_output);
                    }
                }

                return Err(anyhow!(error));
            }
        };

        let protocol = request.protocol.clone();

        drop(request);

        let local_env = self.get_selected_env_as_local();
        let response = match protocol {
            Protocol::HttpRequest(_) => send_http_request(prepared_request, local_request.clone(), &local_env).await?,
            Protocol::WsRequest(_) => send_ws_request(prepared_request, local_request.clone(), &local_env, self.received_response.clone()).await?,
        };

        let request = local_request.read();

        if send_command.status_code {
            println!("{}", response.status_code.as_ref().unwrap());
        }

        if send_command.duration {
            println!("{}", response.duration.unwrap());
        }

        if send_command.cookies {
            println!("{}", response.cookies.unwrap());
        }

        if send_command.headers {
            println!("{:?}", response.headers);
        }

        if send_command.console {
            let console_output = match (&request.console_output.post_request_output, &request.console_output.post_request_output) {
                (None, None) => &String::new(),
                (Some(pre_request_console_output), None) => pre_request_console_output,
                (None, Some(post_request_console_output)) => post_request_console_output,
                (Some(pre_request_console_output), Some(post_request_console_output)) => &format!("{pre_request_console_output}\n{post_request_console_output}")
            };

            println!("{}", console_output);
        }

        if !send_command.hide_content {
            match response.content {
                None => {},
                Some(content) => match content {
                    ResponseContent::Body(body) => println!("{}", body),
                    ResponseContent::Image(image) => match image.image {
                        None => {
                            println!("{:?}", image.data)
                        }
                        Some(dynamic_image) => {
                            let image_width = dynamic_image.width() as f32;
                            let image_height = dynamic_image.height() as f32;

                            let backend = CrosstermBackend::new(stdout());
                            let terminal_size = backend.size()?;

                            let width_ratio = terminal_size.width as f32 / image_width;
                            let height_ratio = terminal_size.height as f32 / image_height;

                            let ratio = width_ratio.min(height_ratio);

                            let mut terminal = Terminal::with_options(
                                backend,
                                TerminalOptions {
                                    viewport: Viewport::Inline((image_height * ratio) as u16),
                                }
                            )?;

                            let mut picker = Picker::from_query_stdio()
                                .unwrap_or(Picker::from_fontsize((7, 14)))
                                .new_resize_protocol(dynamic_image);

                            terminal.draw(|frame|
                                picker.resize_encode_render(
                                    &Resize::Fit(None),
                                    Rect {
                                        x: 0,
                                        y: 0,
                                        width: (image_width * ratio) as u16,
                                        height: (image_height * ratio) as u16,
                                    },
                                    frame.buffer_mut()
                                ))?;
                        }
                    }
                }
            };
        }
        drop(request);

        if let Protocol::WsRequest(_) = &protocol {
            let mut last_length = 0;
            let local_local_request = local_request.clone();

            tokio::spawn(async move {
                let stdin = io::stdin();
                let reader = BufReader::new(stdin);
                let mut lines = reader.lines();
                let mut buffer = String::new();

                loop {
                    if let Ok(Some(line)) = lines.next_line().await {
                        if line.ends_with("\u{1b}") {
                            let line = &line[..line.len() - 1];
                            buffer.push_str(&line);
                            buffer.push('\n');
                        }
                        else {
                            buffer.push_str(&line);
                            let text = buffer.clone();
                            buffer.clear();

                            let mut request = local_local_request.write();
                            let ws_request = request.get_ws_request_mut().unwrap();

                            if ws_request.is_connected {
                                if let Some(websocket) = &ws_request.websocket {
                                    info!("Sending message");

                                    websocket.tx.lock().send(reqwest_websocket::Message::Text(text.clone())).await.unwrap();

                                    ws_request.messages.push(Message {
                                        timestamp: Local::now(),
                                        sender: Sender::You,
                                        content: MessageType::Text(text),
                                    });
                                }
                            }
                        }
                    }
                }
            });

            loop {
                if let Some(request) = local_request.try_read() {
                    let ws_request = request.get_ws_request()?;

                    if !ws_request.is_connected {
                        break;
                    }

                    let messages = &ws_request.messages[last_length..];

                    for message in messages {
                        println!(
                            "=== {} - New {} message from {} ===\n{}",
                            message.timestamp.format("%H:%M:%S %d/%m/%Y").to_string(),
                            message.content.to_string(),
                            message.sender,
                            message.content.to_content()
                        )
                    }

                    last_length = ws_request.messages.len();
                }
            }
        }

        Ok(())
    }
}