diff options
Diffstat (limited to 'src-tauri/src/github')
-rw-r--r-- | src-tauri/src/github/mod.rs | 312 | ||||
-rw-r--r-- | src-tauri/src/github/pull_requests.rs | 398 | ||||
-rw-r--r-- | src-tauri/src/github/release_notes.rs | 244 |
3 files changed, 0 insertions, 954 deletions
diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs deleted file mode 100644 index 9bc3f834..00000000 --- a/src-tauri/src/github/mod.rs +++ /dev/null @@ -1,312 +0,0 @@ -pub mod pull_requests; -pub mod release_notes; - -use crate::constants::{ - APP_USER_AGENT, FLIGHTCORE_REPO_NAME, NORTHSTAR_RELEASE_REPO_NAME, SECTION_ORDER, -}; -use regex::Regex; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use ts_rs::TS; - -#[derive(Serialize, Deserialize, Debug, Clone, TS)] -#[ts(export)] -pub struct Tag { - name: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, TS)] -#[ts(export)] -pub enum Project { - FlightCore, - Northstar, -} - -/// Wrapper type needed for frontend -#[derive(Serialize, Deserialize, Debug, Clone, TS)] -#[ts(export)] -pub struct TagWrapper { - label: String, - value: Tag, -} - -#[derive(Debug, Deserialize)] -pub struct CommitInfo { - pub sha: String, - commit: Commit, - author: Option<CommitAuthor>, -} - -#[derive(Debug, Deserialize)] -struct Commit { - message: String, -} - -#[derive(Debug, Deserialize)] -struct CommitAuthor { - login: String, -} - -#[derive(Debug, Deserialize)] -struct Comparison { - commits: Vec<CommitInfo>, -} - -/// Get a list of tags on the FlightCore repo -#[tauri::command] -pub fn get_list_of_tags(project: Project) -> Result<Vec<TagWrapper>, String> { - // Set the repository name. - - // Create a `reqwest` client with a user agent. - let client = reqwest::blocking::Client::builder() - .user_agent(APP_USER_AGENT) - .build() - .unwrap(); - - // Switch repo to fetch from based on project - let repo_name = match project { - Project::FlightCore => FLIGHTCORE_REPO_NAME, - Project::Northstar => NORTHSTAR_RELEASE_REPO_NAME, - }; - - // Fetch the list of tags for the repository as a `Vec<Tag>`. - let tags_url = format!("https://api.github.com/repos/{}/tags", repo_name); - let tags: Vec<Tag> = client.get(tags_url).send().unwrap().json().unwrap(); - - // Map each `Tag` element to a `TagWrapper` element with the desired label and `Tag` value. - let tag_wrappers: Vec<TagWrapper> = tags - .into_iter() - .map(|tag| TagWrapper { - label: tag.name.clone(), - value: tag, - }) - .collect(); - - Ok(tag_wrappers) -} - -/// Use GitHub API to compare two tags of the same repo against each other and get the resulting changes -#[tauri::command] -pub fn compare_tags(project: Project, first_tag: Tag, second_tag: Tag) -> Result<String, String> { - match project { - Project::FlightCore => compare_tags_flightcore(first_tag, second_tag), - Project::Northstar => compare_tags_northstar(first_tag, second_tag), - } -} - -pub fn compare_tags_flightcore(first_tag: Tag, second_tag: Tag) -> Result<String, String> { - // Fetch the list of commits between the two tags. - - // Create a `reqwest` client with a user agent. - let client = reqwest::blocking::Client::builder() - .user_agent(APP_USER_AGENT) - .build() - .unwrap(); - - let repo = "R2NorthstarTools/FlightCore"; - - let mut full_patch_notes = "".to_string(); - - let mut patch_notes: Vec<String> = [].to_vec(); - println!("{}", repo); - // let repo = "R2Northstar/NorthstarLauncher"; - let comparison_url = format!( - "https://api.github.com/repos/{}/compare/{}...{}", - repo, first_tag.name, second_tag.name - ); - - let comparison: Comparison = client.get(comparison_url).send().unwrap().json().unwrap(); - let commits = comparison.commits; - - // Display the list of commits. - println!( - "Commits between {} and {}:", - first_tag.name, second_tag.name - ); - - // Iterate over all commits in the diff - for commit in commits { - println!( - " * {} : {}", - commit.sha, - commit.commit.message.split('\n').next().unwrap() - ); - patch_notes.push( - commit - .commit - .message - .split('\n') - .next() - .unwrap() - .to_string(), - ); - } - - full_patch_notes += &generate_flightcore_release_notes(patch_notes); - - Ok(full_patch_notes.to_string()) -} - -/// Generate release notes in the format used for FlightCore -fn generate_flightcore_release_notes(commits: Vec<String>) -> String { - let grouped_commits = group_commits_by_type(commits); - let mut release_notes = String::new(); - - // Go over commit types and generate notes - for commit_type in SECTION_ORDER { - if let Some(commit_list) = grouped_commits.get(commit_type) { - if !commit_list.is_empty() { - let section_title = match commit_type { - "feat" => "**Features:**", - "fix" => "**Bug Fixes:**", - "docs" => "**Documentation:**", - "style" => "**Code style changes:**", - "refactor" => "**Code Refactoring:**", - "build" => "**Build:**", - "ci" => "**Continuous integration changes:**", - "test" => "**Tests:**", - "chore" => "**Chores:**", - "i18n" => "**Translations:**", - _ => "**Other:**", - }; - - release_notes.push_str(&format!("{}\n", section_title)); - - for commit_message in commit_list { - release_notes.push_str(&format!("- {}\n", commit_message)); - } - - release_notes.push('\n'); - } - } - } - - let release_notes = release_notes.trim_end_matches('\n').to_string(); - release_notes -} - -/// Group semantic commit messages by type -/// Commmit messages that are not formatted accordingly are marked as "other" -fn group_commits_by_type(commits: Vec<String>) -> HashMap<String, Vec<String>> { - let mut grouped_commits: HashMap<String, Vec<String>> = HashMap::new(); - let mut other_commits: Vec<String> = vec![]; - - for commit in commits { - let commit_parts: Vec<&str> = commit.splitn(2, ':').collect(); - if commit_parts.len() == 2 { - let commit_type = commit_parts[0].to_lowercase(); - let commit_description = commit_parts[1].trim().to_string(); - - // Check if known commit type - if SECTION_ORDER.contains(&commit_type.as_str()) { - let commit_list = grouped_commits.entry(commit_type.to_string()).or_default(); - commit_list.push(commit_description); - } else { - // otherwise add to list of "other" - other_commits.push(commit.to_string()); - } - } else { - other_commits.push(commit.to_string()); - } - } - grouped_commits.insert("other".to_string(), other_commits); - - grouped_commits -} - -/// Compares two tags on Northstar repo and generates release notes over the diff in tags -/// over the 3 major repos (Northstar, NorthstarLauncher, NorthstarMods) -pub fn compare_tags_northstar(first_tag: Tag, second_tag: Tag) -> Result<String, String> { - // Fetch the list of commits between the two tags. - - // Create a `reqwest` client with a user agent. - let client = reqwest::blocking::Client::builder() - .user_agent(APP_USER_AGENT) - .build() - .unwrap(); - - let repos = [ - "R2Northstar/Northstar", - "R2Northstar/NorthstarLauncher", - "R2Northstar/NorthstarMods", - ]; - - let mut full_patch_notes = "".to_string(); - let mut authors_set = std::collections::HashSet::new(); - - for repo in repos { - full_patch_notes += &format!("{}\n\n", repo); - - let mut patch_notes: Vec<String> = [].to_vec(); - println!("{}", repo); - // let repo = "R2Northstar/NorthstarLauncher"; - let comparison_url = format!( - "https://api.github.com/repos/{}/compare/{}...{}", - repo, first_tag.name, second_tag.name - ); - - log::info!("Compare URL: {}", comparison_url.clone()); - let comparison: Comparison = client.get(&comparison_url).send().unwrap().json().unwrap(); - let commits = comparison.commits; - - // Display the list of commits. - println!( - "Commits between {} and {}:", - first_tag.name, second_tag.name - ); - - // - for commit in commits { - println!( - " * {} : {}", - commit.sha, - turn_pr_number_into_link(commit.commit.message.split('\n').next().unwrap(), repo) - ); - patch_notes.push(turn_pr_number_into_link( - commit.commit.message.split('\n').next().unwrap(), - repo, - )); - - // Store authors in set - if commit.author.is_some() { - authors_set.insert(commit.author.unwrap().login); - } - } - - full_patch_notes += &patch_notes.join("\n"); - full_patch_notes += "\n\n\n"; - } - - // Convert the set to a sorted vector. - let mut sorted_vec: Vec<String> = authors_set.into_iter().collect(); - sorted_vec.sort_by_key(|a| a.to_lowercase()); - - // Define a string to prepend to each element. - let prefix = "@"; - - // Create a new list with the prefix prepended to each element. - let prefixed_list: Vec<String> = sorted_vec.iter().map(|s| prefix.to_owned() + s).collect(); - - full_patch_notes += "**Contributors:**\n"; - full_patch_notes += &prefixed_list.join(" "); - - Ok(full_patch_notes.to_string()) -} - -/// Takes the commit title and repo slug and formats it as -/// `[commit title(SHORTENED_REPO#NUMBER)](LINK)` -fn turn_pr_number_into_link(input: &str, repo: &str) -> String { - // Extract `Mods/Launcher` from repo title - let last_line = repo - .split('/') - .next_back() - .unwrap() - .trim_start_matches("Northstar"); - // Extract PR number - let re = Regex::new(r"#(\d+)").unwrap(); - - // Generate pull request link - let pull_link = format!("https://github.com/{}/pull/", repo); - re.replace_all(input, format!("[{}#$1]({}$1)", last_line, pull_link)) - .to_string() -} diff --git a/src-tauri/src/github/pull_requests.rs b/src-tauri/src/github/pull_requests.rs deleted file mode 100644 index de733feb..00000000 --- a/src-tauri/src/github/pull_requests.rs +++ /dev/null @@ -1,398 +0,0 @@ -use crate::constants::{APP_USER_AGENT, NORTHSTAR_LAUNCHER_REPO_NAME, NORTHSTAR_MODS_REPO_NAME}; -use crate::repair_and_verify::check_is_valid_game_path; -use crate::GameInstall; -use anyhow::anyhow; -use serde::{Deserialize, Serialize}; -use std::fs::File; -use std::io; -use std::io::prelude::*; -use std::path::Path; -use ts_rs::TS; - -#[derive(Serialize, Deserialize, Debug, Clone, TS)] -#[ts(export)] -struct Repo { - full_name: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone, TS)] -#[ts(export)] -struct CommitHead { - sha: String, - #[serde(rename = "ref")] - gh_ref: String, - repo: Repo, -} - -#[derive(Serialize, Deserialize, Debug, Clone, TS)] -#[ts(export)] -pub struct PullsApiResponseElement { - number: u64, - title: String, - url: String, - head: CommitHead, - html_url: String, - labels: Vec<String>, -} - -// GitHub API response JSON elements as structs -#[derive(Debug, Deserialize, Clone)] -struct WorkflowRun { - id: u64, - head_sha: String, -} -#[derive(Debug, Deserialize, Clone)] -struct ActionsRunsResponse { - workflow_runs: Vec<WorkflowRun>, -} - -#[derive(Debug, Deserialize, Clone)] -struct Artifact { - id: u64, - name: String, - workflow_run: WorkflowRun, -} - -#[derive(Debug, Deserialize, Clone)] -struct ArtifactsResponse { - artifacts: Vec<Artifact>, -} - -#[derive(Serialize, Deserialize, Debug, Clone, TS)] -#[ts(export)] -pub enum PullRequestType { - Mods, - Launcher, -} - -/// Parse pull requests from specified URL -pub async fn get_pull_requests( - repo: PullRequestType, -) -> Result<Vec<PullsApiResponseElement>, anyhow::Error> { - let repo = match repo { - PullRequestType::Mods => NORTHSTAR_MODS_REPO_NAME, - PullRequestType::Launcher => NORTHSTAR_LAUNCHER_REPO_NAME, - }; - - // Grab list of PRs - let octocrab = octocrab::instance(); - let page = octocrab - .pulls("R2Northstar", repo) - .list() - .state(octocrab::params::State::Open) - .per_page(50) // Only grab 50 PRs - .page(1u32) - .send() - .await?; - - // Iterate over pull request elements and insert into struct - let mut all_pull_requests: Vec<PullsApiResponseElement> = vec![]; - for item in page.items { - let repo = Repo { - full_name: item - .head - .repo - .ok_or(anyhow!("repo not found"))? - .full_name - .ok_or(anyhow!("full_name not found"))?, - }; - - let head = CommitHead { - sha: item.head.sha, - gh_ref: item.head.ref_field, - repo, - }; - - // Get labels and their names and put the into vector - let label_names: Vec<String> = item - .labels - .unwrap_or_else(Vec::new) - .into_iter() - .map(|label| label.name) - .collect(); - - // TODO there's probably a way to automatically serialize into the struct but I don't know yet how to - let elem = PullsApiResponseElement { - number: item.number, - title: item.title.ok_or(anyhow!("title not found"))?, - url: item.url, - head, - html_url: item - .html_url - .ok_or(anyhow!("html_url not found"))? - .to_string(), - labels: label_names, - }; - - all_pull_requests.push(elem); - } - - Ok(all_pull_requests) -} - -/// Gets either launcher or mods PRs -#[tauri::command] -pub async fn get_pull_requests_wrapper( - install_type: PullRequestType, -) -> Result<Vec<PullsApiResponseElement>, String> { - match get_pull_requests(install_type).await { - Ok(res) => Ok(res), - Err(err) => Err(err.to_string()), - } -} - -pub async fn check_github_api(url: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> { - let client = reqwest::Client::new(); - let res = client - .get(url) - .header(reqwest::header::USER_AGENT, APP_USER_AGENT) - .send() - .await - .unwrap() - .text() - .await - .unwrap(); - - let json: serde_json::Value = serde_json::from_str(&res).expect("JSON was not well-formatted"); - - Ok(json) -} - -/// Downloads a file from given URL into an array in memory -pub async fn download_zip_into_memory(download_url: String) -> Result<Vec<u8>, anyhow::Error> { - let client = reqwest::Client::builder() - .user_agent(APP_USER_AGENT) - .build()?; - - let response = client.get(download_url).send().await?; - - if !response.status().is_success() { - return Err(anyhow!("Request unsuccessful: {}", response.status())); - } - - let bytes = response.bytes().await?; - Ok(bytes.to_vec()) -} - -/// Gets GitHub download link of a mods PR -fn get_mods_download_link(pull_request: PullsApiResponseElement) -> Result<String, anyhow::Error> { - // {pr object} -> number == pr_number - // -> head -> ref - // -> repo -> full_name - - // Use repo and branch name to get download link - let download_url = format!( - "https://github.com/{}/archive/refs/heads/{}.zip", - pull_request.head.repo.full_name, // repo name - pull_request.head.gh_ref, // branch name - ); - - Ok(download_url) -} - -/// Gets `nightly.link` artifact download link of a launcher commit -#[tauri::command] -pub async fn get_launcher_download_link(commit_sha: String) -> Result<String, String> { - // Iterate over the first 10 pages of - for i in 1..=10 { - // Crossreference with runs API - let runs_response: ActionsRunsResponse = match check_github_api(&format!( - "https://api.github.com/repos/R2Northstar/NorthstarLauncher/actions/runs?page={}", - i - )) - .await - { - Ok(result) => serde_json::from_value(result).unwrap(), - Err(err) => return Err(format!("{}", err)), - }; - - // Cross-reference commit sha against workflow runs - for workflow_run in &runs_response.workflow_runs { - // If head commit sha of CI run matches the one passed to this function, grab CI output - if workflow_run.head_sha == commit_sha { - // Check artifacts - let api_url = format!("https://api.github.com/repos/R2Northstar/NorthstarLauncher/actions/runs/{}/artifacts", workflow_run.id); - let artifacts_response: ArtifactsResponse = serde_json::from_value( - check_github_api(&api_url).await.expect("Failed request"), - ) - .unwrap(); - - let multiple_artifacts = artifacts_response.artifacts.len() > 1; - - // Iterate over artifacts - for artifact in artifacts_response.artifacts { - if multiple_artifacts && !artifact.name.starts_with("NorthstarLauncher-MSVC") { - continue; - } - - // Make sure artifact and CI run commit head sha match - if artifact.workflow_run.head_sha == workflow_run.head_sha { - // Download artifact - return Ok(format!("https://nightly.link/R2Northstar/NorthstarLauncher/actions/artifacts/{}.zip", artifact.id)); - } - } - } - } - } - - Err(format!( - "Couldn't grab download link for \"{}\". Corresponding PR might be too old and therefore no CI build has been detected. Maybe ask author to update?", - commit_sha - )) -} - -/// Adds a batch file that allows for launching Northstar with mods PR profile -fn add_batch_file(game_install_path: &str) { - let batch_path = format!("{}/r2ns-launch-mod-pr-version.bat", game_install_path); - let path = Path::new(&batch_path); - let display = path.display(); - - // Open a file in write-only mode, returns `io::Result<File>` - let mut file = match File::create(path) { - Err(why) => panic!("couldn't create {}: {}", display, why), - Ok(file) => file, - }; - - // Write the string to `file`, returns `io::Result<()>` - let batch_file_content = - "NorthstarLauncher.exe -profile=R2Northstar-PR-test-managed-folder\r\n"; - - match file.write_all(batch_file_content.as_bytes()) { - Err(why) => panic!("couldn't write to {}: {}", display, why), - Ok(_) => log::info!("successfully wrote to {}", display), - } -} - -/// Downloads selected launcher PR and extracts it into game install path -#[tauri::command] -pub async fn apply_launcher_pr( - pull_request: PullsApiResponseElement, - game_install: GameInstall, -) -> Result<(), String> { - // Exit early if wrong game path - check_is_valid_game_path(&game_install.game_path)?; - - // get download link - let download_url = match get_launcher_download_link(pull_request.head.sha.clone()).await { - Ok(res) => res, - Err(err) => { - return Err(format!( - "Couldn't grab download link for PR \"{}\". {}", - pull_request.number, err - )) - } - }; - - let archive = match download_zip_into_memory(download_url).await { - Ok(archive) => archive, - Err(err) => return Err(err.to_string()), - }; - - let extract_directory = format!( - "{}/___flightcore-temp/download-dir/launcher-pr-{}", - game_install.game_path, pull_request.number - ); - match std::fs::create_dir_all(extract_directory.clone()) { - Ok(_) => (), - Err(err) => { - return Err(format!( - "Failed creating temporary download directory: {}", - err - )) - } - }; - - let target_dir = std::path::PathBuf::from(extract_directory.clone()); // Doesn't need to exist - match zip_extract::extract(io::Cursor::new(archive), &target_dir, true) { - Ok(()) => (), - Err(err) => { - return Err(format!("Failed unzip: {}", err)); - } - }; - - // Copy only necessary files from temp dir - // Copy: - // - NorthstarLauncher.exe - // - Northstar.dll - let files_to_copy = vec!["NorthstarLauncher.exe", "Northstar.dll"]; - for file_name in files_to_copy { - let source_file_path = format!("{}/{}", extract_directory, file_name); - let destination_file_path = format!("{}/{}", game_install.game_path, file_name); - match std::fs::copy(source_file_path, destination_file_path) { - Ok(_result) => (), - Err(err) => { - return Err(format!( - "Failed to copy necessary file {} from temp dir: {}", - file_name, err - )) - } - }; - } - - // delete extract directory - match std::fs::remove_dir_all(&extract_directory) { - Ok(()) => (), - Err(err) => { - return Err(format!( - "Failed to delete temporary download directory: {}", - err - )) - } - } - - log::info!("All done with installing launcher PR"); - Ok(()) -} - -/// Downloads selected mods PR and extracts it into profile in game install path -#[tauri::command] -pub async fn apply_mods_pr( - pull_request: PullsApiResponseElement, - game_install: GameInstall, -) -> Result<(), String> { - // Exit early if wrong game path - check_is_valid_game_path(&game_install.game_path)?; - - let download_url = match get_mods_download_link(pull_request) { - Ok(url) => url, - Err(err) => return Err(err.to_string()), - }; - - let archive = match download_zip_into_memory(download_url).await { - Ok(archive) => archive, - Err(err) => return Err(err.to_string()), - }; - - let profile_folder = format!( - "{}/R2Northstar-PR-test-managed-folder", - game_install.game_path - ); - - // Delete previously managed folder - if std::fs::remove_dir_all(profile_folder.clone()).is_err() { - if std::path::Path::new(&profile_folder).exists() { - log::error!("Failed removing previous dir"); - } else { - log::warn!("Failed removing folder that doesn't exist. Probably cause first run"); - } - }; - - // Create profile folder - match std::fs::create_dir_all(profile_folder.clone()) { - Ok(()) => (), - Err(err) => return Err(err.to_string()), - } - - let target_dir = std::path::PathBuf::from(format!("{}/mods", profile_folder)); // Doesn't need to exist - match zip_extract::extract(io::Cursor::new(archive), &target_dir, true) { - Ok(()) => (), - Err(err) => { - return Err(format!("Failed unzip: {}", err)); - } - }; - // Add batch file to launch right profile - add_batch_file(&game_install.game_path); - - log::info!("All done with installing mods PR"); - Ok(()) -} diff --git a/src-tauri/src/github/release_notes.rs b/src-tauri/src/github/release_notes.rs deleted file mode 100644 index 4adfb24b..00000000 --- a/src-tauri/src/github/release_notes.rs +++ /dev/null @@ -1,244 +0,0 @@ -use rand::prelude::SliceRandom; -use serde::{Deserialize, Serialize}; -use std::vec::Vec; -use ts_rs::TS; - -#[derive(Serialize, Deserialize, Debug, Clone, TS)] -#[ts(export)] -pub struct ReleaseInfo { - pub name: String, - pub published_at: String, - pub body: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone, TS)] -#[ts(export)] -pub struct FlightCoreVersion { - tag_name: String, - published_at: String, -} - -/// Gets newest FlighCore version from GitHub -#[tauri::command] -pub async fn get_newest_flightcore_version() -> Result<FlightCoreVersion, String> { - // Get newest version number from GitHub API - log::info!("Checking GitHub API"); - let octocrab = octocrab::instance(); - let page = octocrab - .repos("R2NorthstarTools", "FlightCore") - .releases() - .list() - // Optional Parameters - .per_page(1) - .page(1u32) - // Send the request - .send() - .await - .map_err(|err| err.to_string())?; - - // Get newest element - let latest_release_item = &page.items[0]; - - let flightcore_version = FlightCoreVersion { - tag_name: latest_release_item.tag_name.clone(), - published_at: latest_release_item.published_at.unwrap().to_rfc3339(), - }; - log::info!("Done checking GitHub API"); - - Ok(flightcore_version) -} - -/// Checks if installed FlightCore version is up-to-date -/// false -> FlightCore install is up-to-date -/// true -> FlightCore install is outdated -#[tauri::command] -pub async fn check_is_flightcore_outdated() -> Result<bool, String> { - let newest_flightcore_release = get_newest_flightcore_version().await?; - // Parse version number excluding leading `v` - let newest_version = semver::Version::parse(&newest_flightcore_release.tag_name[1..]).unwrap(); - - // Get version of installed FlightCore - let current_version = env!("CARGO_PKG_VERSION"); - let current_version = semver::Version::parse(current_version).unwrap(); - - #[cfg(debug_assertions)] - let is_outdated = current_version < newest_version; - #[cfg(not(debug_assertions))] - let is_outdated = current_version != newest_version; - - // If outdated, check how new the update is - if is_outdated { - // Time to wait (2h) h * m * s - let threshold_seconds = 2 * 60 * 60; - - // Get current time - let current_time = chrono::Utc::now(); - - // Get latest release time from GitHub API response - let result = chrono::DateTime::parse_from_rfc3339(&newest_flightcore_release.published_at) - .unwrap() - .with_timezone(&chrono::Utc); - - // Check if current time is outside of threshold - let diff = current_time - result; - if diff.num_seconds() < threshold_seconds { - // User would be outdated but the newest release is recent - // therefore we do not wanna show outdated warning. - return Ok(false); - } - return Ok(true); - } - - Ok(is_outdated) -} - -#[tauri::command] -pub async fn get_northstar_release_notes() -> Result<Vec<ReleaseInfo>, String> { - let octocrab = octocrab::instance(); - let page = octocrab - .repos("R2Northstar", "Northstar") - .releases() - .list() - // Optional Parameters - .per_page(25) - .page(1u32) - // Send the request - .send() - .await - .map_err(|err| err.to_string())?; - - // TODO there's probably a way to automatically serialize into the struct but I don't know yet how to - let mut release_info_vector: Vec<ReleaseInfo> = vec![]; - for item in page.items { - let release_info = ReleaseInfo { - name: item.name.ok_or(String::from("Release name not found"))?, - published_at: item - .published_at - .ok_or(String::from("Release date not found"))? - .to_rfc3339(), - body: item.body.ok_or(String::from("Release body not found"))?, - }; - release_info_vector.push(release_info); - } - - log::info!("Done checking GitHub API"); - - Ok(release_info_vector) -} - -/// Checks latest GitHub release and generates a announcement message for Discord based on it -#[tauri::command] -pub async fn generate_release_note_announcement() -> Result<String, String> { - let octocrab = octocrab::instance(); - let page = octocrab - .repos("R2Northstar", "Northstar") - .releases() - .list() - // Optional Parameters - .per_page(1) - .page(1u32) - // Send the request - .send() - .await - .unwrap(); - - // Get newest element - let latest_release_item = &page.items[0]; - - // Extract the URL to the GitHub release note - let github_release_link = latest_release_item.html_url.clone(); - - // Extract release version number - let current_ns_version = &latest_release_item.tag_name; - - // Extract changelog and format it - let changelog = remove_markdown_links::remove_markdown_links( - latest_release_item - .body - .as_ref() - .unwrap() - .split("**Contributors:**") - .next() - .unwrap() - .trim(), - ); - - // Strings to insert for different sections - // Hardcoded for now - let general_info = "REPLACE ME"; - let modders_info = "Mod compatibility should not be impacted"; - let server_hosters_info = "REPLACE ME"; - - let mut rng = rand::thread_rng(); - let attributes = vec![ - "adorable", - "amazing", - "beautiful", - "blithsome", - "brilliant", - "compassionate", - "dazzling", - "delightful", - "distinguished", - "elegant", - "enigmatic", - "enthusiastic", - "fashionable", - "fortuitous", - "friendly", - "generous", - "gleeful", - "gorgeous", - "handsome", - "lively", - "lovely", - "lucky", - "lustrous", - "marvelous", - "merry", - "mirthful", - "phantasmagorical", - "pretty", - "propitious", - "ravishing", - "sincere", - "sophisticated fellow", - "stupendous", - "vivacious", - "wonderful", - "zestful", - ]; - - let selected_attribute = attributes.choose(&mut rng).unwrap(); - - // Build announcement string - let return_string = format!( - r"Hello {selected_attribute} people <3 -**Northstar `{current_ns_version}` is out!** - -{general_info} - -__**Modders:**__ - -{modders_info} - -__**Server hosters:**__ - -{server_hosters_info} - -__**Changelog:**__ -``` -{changelog} -``` -{github_release_link} - -Checkout #installation on how to install/update Northstar -(the process is the same for both, using a Northstar installer like FlightCore, Viper, or VTOL is recommended over manual installation) - -If you do notice any bugs, please open an issue on Github or drop a message in the thread below -" - ); - - // Return built announcement message - Ok(return_string.to_string()) -} |