aboutsummaryrefslogtreecommitdiff
path: root/src-tauri/src/github
diff options
context:
space:
mode:
authorGeckoEidechse <gecko.eidechse+git@pm.me>2024-12-22 23:55:52 +0100
committerGeckoEidechse <gecko.eidechse+git@pm.me>2024-12-22 23:55:52 +0100
commitf1dee718da95836ffa5c0985c9e8f5643e0f3f6f (patch)
tree24967a28bcae1fc1e5b08da9f58bcc678ed52937 /src-tauri/src/github
parentcc5ae684221d3165479d7a68556a2bb6fa81cf3a (diff)
downloadFlightCore-f1dee718da95836ffa5c0985c9e8f5643e0f3f6f.tar.gz
FlightCore-f1dee718da95836ffa5c0985c9e8f5643e0f3f6f.zip
dev: Replace with sample Tauri 2.0 project
as a first step to convert FlightCore to Tauri 2.0
Diffstat (limited to 'src-tauri/src/github')
-rw-r--r--src-tauri/src/github/mod.rs312
-rw-r--r--src-tauri/src/github/pull_requests.rs398
-rw-r--r--src-tauri/src/github/release_notes.rs244
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())
-}