playit-gg 0.0.3

Unofficial Rust Wrapper For PlayIt.GG
Documentation
use crate::types::*;
use reqwest::header;
use reqwest::Client;
use reqwest::ClientBuilder;
use serde_json::from_str;
use std::collections::HashMap;
use std::env::consts;
use std::env::temp_dir;
use std::env::var;
use std::fs::create_dir_all;
use std::fs::read_to_string;
use std::fs::remove_file;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::process::Stdio;
use std::thread::sleep;
use std::time::Duration;
use url::Url;

pub async fn get_defaults(playit_opts: Option<PlayItOpts>) -> Result<PlayIt, String> {
  // Get Default Values
  let os: String = String::from(if cfg!(unix) {
    "lin"
  } else if cfg!(target_os = "windows") {
    "win"
  } else if cfg!(target_os = "macos") {
    "mac"
  } else {
    return Err(String::from("Not On A Supported Operating System"));
  });

  let arch: String = String::from({
    if !["x86_64", "arm", "aarch64"].contains(&consts::ARCH) {
      return Err(format!("Unsupported Architexture, {}", consts::ARCH));
    } else if consts::ARCH == "x86_64" {
      "x64"
    } else if consts::ARCH == "aarch64" {
      "arm64"
    } else {
      "arm"
    }
  });

  let dir: PathBuf = {
    let mut dir = temp_dir();
    dir.push("playit");
    create_dir_all(&dir).expect("Failed To Create The Temporary Directory");
    dir
  };

  let config_file: PathBuf = {
    let path = Path::new(&if os == String::from("lin") {
      if var("XDG_CONFIG_HOME").is_err() {
        format!("{}/.config/playit/config.json", var("HOME").unwrap())
      } else {
        format!("{}/playit/config.json", var("XDG_CONFIG_HOME").unwrap())
      }
    } else if os == "win" {
      format!("{}/playit/config.json", var("AppData").unwrap())
    } else {
      format!(
        "{}/Library/Application Support/playit/config.json",
        var("HOME").unwrap()
      )
    })
    .to_owned();

    remove_file(&path).ok();

    path
  };

  let destroyed: bool = false;
  let tunnels: Vec<Tunnel> = Vec::new();
  let started: bool = false;
  let used_packets: u8 = 0;
  let free_packets: u8 = 0;
  let connections: Vec<Connection> = Vec::new();
  let version: String = String::from("0.4.6");
  let download_urls: HashMap<String, Url> = {
    let mut download_urls: HashMap<String, Url> = HashMap::new();
    download_urls.insert(
      String::from("win"),
      Url::parse(&*format!(
        "https://playit.gg/downloads/playit-win_64-{}.exe",
        version
      ))
      .expect("Failed To Parse Download URL"),
    );
    download_urls.insert(
      String::from("lin"),
      Url::parse(&*format!(
        "https://playit.gg/downloads/playit-linux_64-{}",
        version
      ))
      .expect("Failed To Parse Download URL"),
    );
    download_urls.insert(
      String::from("mac"),
      Url::parse(&*format!(
        "https://playit.gg/downloads/playit-darwin_64-{}.zip",
        version
      ))
      .expect("Failed To Parse Download URL"),
    );
    download_urls.insert(
      String::from("arm64"),
      Url::parse(&*format!(
        "https://playit.gg/downloads/playit-aarch64-{}",
        version
      ))
      .expect("Failed To Parse Download URL"),
    );
    download_urls.insert(
      String::from("arm"),
      Url::parse(&*format!(
        "https://playit.gg/downloads/playit-armv7-{}",
        version
      ))
      .expect("Failed To Parse Download URL"),
    );
    download_urls
  };
  let binary_type: String = String::from(if os == "win" {
    "win"
  } else if os == "mac" {
    "mac"
  } else if os == "lin" && (arch == "arm" || arch == "arm64") {
    &*arch
  } else {
    "lin"
  });
  let binary: PathBuf = download(
    &String::from(dir.to_str().unwrap()),
    &binary_type,
    &version,
    &os,
    &download_urls,
  )
  .await;
  let playit: Child = {
    let mut env = HashMap::new();

    env.insert(String::from("NO_BROWSER"), String::from("true"));

    if !playit_opts.clone().is_none() {
      env.insert(
        String::from("PREFERRED_TUNNEL"),
        playit_opts
          .clone()
          .unwrap()
          .PREFERRED_TUNNEL
          .unwrap_or(String::from("")),
      );
      env.insert(
        String::from("PREFERRED_THRESHOLD"),
        playit_opts
          .clone()
          .unwrap()
          .PREFERRED_THRESHOLD
          .unwrap_or(50)
          .to_string(),
      );
      env.insert(
        String::from("NO_SPECIAL_LAN"),
        playit_opts
          .clone()
          .unwrap()
          .NO_SPECIAL_LAN
          .unwrap_or(false)
          .to_string(),
      );
    }

    exec(String::from(binary.to_str().unwrap()), None, Some(env)).unwrap()
  };
  let output: Vec<String> = Vec::new();
  let stdout: Vec<String> = Vec::new();
  let stderr: Vec<String> = Vec::new();
  let errors: Vec<String> = Vec::new();
  let warnings: Vec<String> = Vec::new();

  while !config_file.exists() {}
  let (agent_key, server) = loop {
    let config: Config = from_str::<Config>(
      &*read_to_string(&config_file).expect("Failed To Read PlayIt Config File"),
    )
    .expect("Failed to Parse PlayIt Config File");

    if !config.agent_key.is_none() && !config.preferred_tunnel.is_none() {
      break (config.agent_key.unwrap(), config.preferred_tunnel.unwrap());
    }

    sleep(Duration::from_secs(1));
  };

  let req_client: Client = {
    let mut headers: header::HeaderMap = header::HeaderMap::new();
    let mut auth: header::HeaderValue =
      header::HeaderValue::from_str(&format!("agent {}", agent_key)).unwrap();
    auth.set_sensitive(true);
    headers.insert(header::AUTHORIZATION, auth);
    ClientBuilder::new()
      .default_headers(headers)
      .build()
      .unwrap()
  };
  let api_path = Url::parse("https://api.playit.gg/").unwrap();

  return Ok(PlayIt {
    req_client,
    os,
    config_file,
    arch,
    dir,
    destroyed,
    tunnels,
    agent_key,
    started,
    playit,
    server,
    used_packets,
    free_packets,
    connections,
    version,
    download_urls,
    binary,
    binary_type,
    output,
    stdout,
    stderr,
    errors,
    warnings,
    api_path,
  });
}

// Small wrapper for `command`
pub fn exec(
  cmd: String,
  args: Option<Vec<String>>,
  envs: Option<HashMap<String, String>>,
) -> Result<Child, String> {
  let mut command: Command = Command::new(&*cmd);
  command
    .args(args.unwrap_or(Vec::new()))
    .envs(envs.unwrap_or(HashMap::new()))
    .stdin(Stdio::piped())
    .stderr(Stdio::piped())
    .stdout(Stdio::piped());

  #[cfg(target_os = "windows")]
  {
    use std::os::windows::process::CommandExt;
    command.creation_flags(0x08000000);
  }

  let command: Child = Child(
    command
      .spawn()
      .expect(&format!("Failed To Start Process: {}", &cmd)),
  );

  return Ok(command);
}

pub fn trim_newlines(string: &String) -> String {
  return string.trim_end().replace('\n', " ").replace('\r', "");
}

pub async fn download(
  dir: &String,
  binary_type: &String,
  version: &String,
  os: &String,
  download_urls: &HashMap<String, Url>,
) -> PathBuf {
  let file: PathBuf = Path::new(&format!(
    "{}/playit-{}-{}.{}",
    dir,
    binary_type,
    version,
    String::from(if os == "win" { "exe" } else { "bin" })
  ))
  .to_owned();

  if file.exists() {
    return file;
  }

  let mut _file: File = File::create(&file).expect("Failed To Download PlayIt");

  _file
    .write(
      &reqwest::get(download_urls[&*binary_type].clone())
        .await
        .unwrap()
        .bytes()
        .await
        .expect("Failed To Download PlayIt"),
    )
    .expect("Failed To Download PlayIt");

  #[cfg(unix)]
  {
    use std::os::unix::fs::PermissionsExt;
    _file.metadata().unwrap().permissions().set_mode(0x555);
  }

  return file;
}