從osu找到spotify的同一首歌 以及 從spotify找到osu的同一首歌
一切始於我們又又又換專題主題的那個晚上,本來unity要準備開始了,但組員的一番提議,我們從C#鬼轉到Rust的程式開發
耗時6個月製作,做出來的東西我還蠻滿意的 至少我覺得專題展覽可以過拉
在你點開應用後,亦即同意我們程式的運作方式,本團隊不負任何因你使用我們程式引發任何問題的責任,是你自己點開的。
以什麼語言製作的?為什麼?以及製作過程依參數格式化輸出
我們以Rust製作
因為Rust有媲美C++的效率,又比C++安全很多 (我恨你空指標)
在漫長的6個月中,我與我們的組員都十分配合,該寫程式的,該做報告的以及每天打遊戲王的
至少 我們全都做完了。對吧
他是如何動的?
graph TD
A[開始] --> B{輸入歌名或是網址}
B -->|歌名| C[在Spotify與Osu查詢這首歌]
B -->|網址| D{連結是否為Spotify或Osu}
D -->|否| E[顯示錯誤]
D -->|是| F{判斷是Spotify還是Osu連結}
F -->|Spotify| G{是否輸入正確連結}
F -->|Osu| H{是否輸入正確連結}
C --> I[顯示Spotify與Osu的搜尋結果]
G -->|是| J[在Spotify查詢這首歌]
G -->|否| K[顯示錯誤]
J --> L[顯示Spotify搜尋結果]
H -->|是| M[在Osu查詢這首歌]
H -->|否| N[顯示錯誤]
M --> O[顯示Osu搜尋結果]
我們在這期間都做了些什麼
gantt
title 開發歷程
dateFormat MM-DD
axisFormat %y-%m-%d
section 主題確認
組內討論:crit,active, discuss, 2024-03-02,2d
section 可行性研究
Rust程式語言學習:active, done,after discuss,120d
section APP製作
程式撰寫:active, done,after discuss,220d
APP版面設計:active, done,after discuss,30d
APP版面重設:active, done,2024-5-15,30d
section 程式測試與修正
程式檢測:active, down,2024-5-1,100d
程式檢測:active, down,2024-8-30,20d
程式修正:active, down,2024-5-20,100d
程式修正:active, down,2024-9-10,30d
section 專題製作(報告,海報方面)
海報設計:crit, uxt,2024-9-30,35d
簡報製作:crit, uxt,2024-9-30,35d
裡面是什麼動得很厲害
就如上面流程圖(他目前還不存在)所展示的,我們是透過Spotify與OSU所提供的API來做最基本的運作
API的連線(OSU)
pub async fn get_osu_token(client: &Client, debug_mode: bool) -> Result<String, OsuError> {
if debug_mode {
debug!("開始獲取 Osu token");
}
let config = read_config(debug_mode).map_err(|e| {
error!("讀取配置文件時出錯: {}", e);
OsuError::ConfigError(format!("Error reading config: {}", e))
})?;
let client_id = &config.osu.client_id;
let client_secret = &config.osu.client_secret;
if debug_mode {
debug!("成功讀取 Osu client_id 和 client_secret");
}
let url = "https://osu.ppy.sh/oauth/token";
let params = [
("client_id", client_id),
("client_secret", client_secret),
("grant_type", &"client_credentials".to_string()),
("scope", &"public".to_string()),
];
if debug_mode {
debug!("準備發送 Osu token 請求");
}
let response = client.post(url).form(¶ms).send().await.map_err(|e| {
error!("發送 Osu token 請求時出錯: {}", e);
OsuError::RequestError(e)
})?;
let token_response: TokenResponse = response.json().await.map_err(|e| {
error!("解析 Osu token 回應時出錯: {}", e);
OsuError::RequestError(e)
})?;
if debug_mode {
debug!("成功獲取 Osu token");
}
Ok(token_response.access_token)
}輸入框的例外處理(連結錯誤)
pub fn is_valid_spotify_url(url: &str) -> Result<SpotifyUrlStatus, SpotifyError> {
lazy_static! {
static ref SPOTIFY_URL_REGEX: Regex = Regex::new(
r"^https?://open\.spotify\.com/(track|album|playlist)/[a-zA-Z0-9]+(?:\?.*)?$"
)
.unwrap();
}
if let Ok(parsed_url) = url::Url::parse(url) {
match parsed_url.domain() {
Some("open.spotify.com") => {
if SPOTIFY_URL_REGEX.is_match(url) {
Ok(SpotifyUrlStatus::Valid)
} else {
Ok(SpotifyUrlStatus::Incomplete)
}
}
Some(_) => {
if url.contains("/track/") || url.contains("/album/") || url.contains("/playlist/")
{
Ok(SpotifyUrlStatus::Invalid)
} else {
Ok(SpotifyUrlStatus::NotSpotify)
}
}
None => Ok(SpotifyUrlStatus::NotSpotify),
}
} else {
Ok(SpotifyUrlStatus::NotSpotify)
}
}接下來確認輸入框的東西都沒問題的話,開始把從個別資料庫中的信息取出(如:歌名、作者名...等),之後等待兩邊的資料回傳。
回傳的結構體樣式(spotify)
pub struct Track {
pub name: String,
pub artists: Vec<Artist>,
pub external_urls: HashMap<String, String>,
pub album: Album,
pub is_liked: Option<bool>,
#[serde(skip)]
pub index: usize,
}將資料放進結構體當中
pub async fn search_track(
client: &Client,
query: &str,
token: &str,
limit: u32,
offset: u32,
debug_mode: bool,
) -> Result<(Vec<TrackWithCover>, u32), SpotifyError> {
let url = format!(
"{}/search?q={}&type=track&limit={}&offset={}",
SPOTIFY_API_BASE_URL, query, limit, offset
);
let response = client
.get(&url)
.bearer_auth(token)
.send()
.await
.map_err(|e| SpotifyError::RequestError(e))?;
if debug_mode {
info!("Spotify API 請求詳情:");
info!(" URL: {}", url);
info!("收到回應狀態碼: {}", response.status());
}
let response_text = response
.text()
.await
.map_err(|e| SpotifyError::RequestError(e))?;
if debug_mode {
info!("Spotify API 回應 JSON: {}", response_text);
}
let search_result: SearchResult =
serde_json::from_str(&response_text).map_err(|e| SpotifyError::JsonError(e))?;
match search_result.tracks {
Some(tracks) => {
let total_tracks = tracks.total;
let total_pages = (total_tracks + limit - 1) / limit;
if debug_mode {
info!("找到 {} 首曲目,共 {} 頁", tracks.total, total_pages);
}
let track_infos: Vec<TrackWithCover> = tracks
.items
.into_iter()
.enumerate()
.map(|(index, track)| {
let cover_url = track.album.images.first().map(|img| img.url.clone());
let artists_names = track
.artists
.iter()
.map(|artist| artist.name.clone())
.collect::<Vec<String>>()
.join(", ");
if debug_mode {
if let Some(url) = &cover_url {
info!(
"處理曲目 {}: \"{}\" by {}",
index, track.name, artists_names
);
info!(" 專輯封面 URL: {}", url);
} else {
error!(
"處理曲目 {} 時出錯: \"{}\" by {} - 缺少封面 URL",
index, track.name, artists_names
);
}
}
TrackWithCover {
name: track.name,
artists: track.artists,
external_urls: track.external_urls,
album_name: track.album.name,
cover_url,
index: index + (offset as usize),
}
})
.collect();
if debug_mode {
info!("成功處理 {} 首曲目", track_infos.len());
}
Ok((track_infos, total_pages))
}
None => Err(SpotifyError::ApiError("搜索結果中沒有找到曲目".to_string())),
}
}