10
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

N高グループ・N中等部Advent Calendar 2024

Day 19

Last.fmで再生中の曲情報をDiscordに表示しよう!

Last updated at Posted at 2024-12-18

はじめに

この記事では、Last.fmで再生中の曲をDiscord Rich Presenceを利用して表示する方法を紹介します。

完成品

image.png

環境

  • 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?)
}
Output
Now playing: ReoNa - Canaria

Discordに反映しよう!

デザインを考える

今後変更されるかもしれませんが、
stateを入力せずにdetailsを入力すると、App Nameの枠にdetailsが入力されます。
また、stateを入力せずにlarge_imageのaltを設定すると、detailsの枠にlarge_imageのaltが入力されます。

RPCの説明
引用: 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?)
}

参考

10
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?