はじめに
この記事では、Last.fmで再生中の曲をDiscord Rich Presenceを利用して表示する方法を紹介します。
完成品
環境
- Rust 1.83.0
- MacOS Sequoia
リポジトリ
やってみよう
Last.fmのAPIを呼び出す
user.getrecenttracks
を呼び出し、指定したユーザーが現在再生している曲を取得します。
楽曲が現在再生しているかどうかは、recent_tracks[0]["@attr"]["nowplaying"] == "true"
で確認できます。
https://www.last.fm/api/show/user.getRecentTracks
use reqwest::Client;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
fn main() {
const API_KEY: &str = "api secret";
const USERNAME: &str = "username";
let client = Client::new();
let response = get_api(&client, USERNAME, API_KEY).unwrap();
let recent_tracks = &response["recenttracks"]["track"];
if recent_tracks[0]["@attr"].get("nowplaying").is_some()
{
let artist = recent_tracks[0]["artist"]["#text"].as_str().unwrap();
let track = recent_tracks[0]["name"].as_str().unwrap();
println!("Now playing: {} - {}", artist, track);
}
}
#[tokio::main]
async fn get_api(client: &Client, username: &str, apikey: &str) -> Result<serde_json::Value> {
let url = format!("http://ws.audioscrobbler.com/2.0");
let response = client
.get(url)
.query(&[
("method", "user.getrecenttracks"),
("user", username),
("api_key", apikey),
("format", "json"),
("limit", "1"),
])
.send()
.await?;
Ok(response.json().await?)
}
Now playing: ReoNa - Canaria
Discordに反映しよう!
デザインを考える
今後変更されるかもしれませんが、
stateを入力せずにdetailsを入力すると、App Nameの枠にdetailsが入力されます。
また、stateを入力せずにlarge_imageのaltを設定すると、detailsの枠にlarge_imageのaltが入力されます。
引用: https://discord.com/developers/docs/rich-presence/using-with-the-embedded-app-sdk
今回は、以下のようにしてみました。
- large_image: アルバムのジャケット
- large_text(ALT): アーティスト名
- details: 曲名
small_imageに、アーティストの写真を入れたかったのですが、Last.fm APIの仕様変更で取得できなくなってしまったようです。
少し調べただけですが、DeezerのAPIを利用するなど他の方法もあるようなので興味のある方は試してみるといいかもしれません!
コード
discord-rich-presence
ライブラリを利用して、Discordに情報を送信します。
fn update_presence(discord_client: &mut DiscordIpcClient, data: &serde_json::Value) {
discord_client
.set_activity(
activity::Activity::new()
.details(data[0]["name"].as_str().unwrap())
.activity_type(ActivityType::Listening)
.assets(
activity::Assets::new()
.large_image(data[0]["image"][3]["#text"].as_str().unwrap()) // サムネイル
.large_text(data[0]["artist"]["#text"].as_str().unwrap()), // ALTテキスト
)
.timestamps(
activity::Timestamps::new().start(
time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64,
),
),
)
.unwrap();
}
ソースコード
use discord_rich_presence::{activity, activity::ActivityType, DiscordIpc, DiscordIpcClient};
use reqwest::Client;
use std::thread::sleep;
use std::time;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
fn main() {
const API_KEY: &str = "";
const USERNAME: &str = "";
const CLIENT_ID: &str = "";
// Discordに接続
let mut discord_client = DiscordIpcClient::new(CLIENT_ID).unwrap();
discord_client.connect().unwrap();
let client = Client::new();
let mut old_track = serde_json::Value::Null;
loop {
let response = get_api(&client, USERNAME, API_KEY).unwrap();
let recent_tracks = &response["recenttracks"]["track"];
if recent_tracks[0]["@attr"].get("nowplaying").is_some() && &old_track != recent_tracks {
let artist = recent_tracks[0]["artist"]["#text"].as_str().unwrap();
let track = recent_tracks[0]["name"].as_str().unwrap();
println!("Now playing: {} - {}", artist, track);
update_presence(&mut discord_client, recent_tracks);
old_track = recent_tracks.clone();
}
sleep(time::Duration::from_secs(2));
}
}
fn update_presence(discord_client: &mut DiscordIpcClient, data: &serde_json::Value) {
discord_client
.set_activity(
activity::Activity::new()
.details(data[0]["name"].as_str().unwrap())
.activity_type(ActivityType::Listening)
.assets(
activity::Assets::new()
.large_image(data[0]["image"][3]["#text"].as_str().unwrap())
.large_text(data[0]["artist"]["#text"].as_str().unwrap()),
)
.timestamps(
activity::Timestamps::new().start(
time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64,
),
),
)
.unwrap();
}
#[tokio::main]
async fn get_api(client: &Client, username: &str, apikey: &str) -> Result<serde_json::Value> {
let url = format!("http://ws.audioscrobbler.com/2.0");
let response = client
.get(url)
.query(&[
("method", "user.getrecenttracks"),
("user", username),
("api_key", apikey),
("format", "json"),
("limit", "1"),
])
.send()
.await?;
Ok(response.json().await?)
}
参考