My journey with Bluesky started with a spark of curiosity. After exploring
the platform, I found myself diving deeper into how their system works,
eventually leading me to a fun and practical project: building a bot that
shares your current song with Bluesky using MPD (Music Player Daemon).
This project has been a great opportunity to get hands-on experience with
Bluesky’s API and automate something useful for myself.
While the initial goal was simply to create a basic bot template that
posts something to Bluesky, I decided to focus specifically on building
a bot that automatically shares your current song—if you’re using Linux
and MPD. But I don’t want to stop here. I plan to further improve it with
Last.fm scrobbling, and maybe even integrate with YouTube music streaming.
However, those ideas are still a work in progress, and I need to figure
out how best to implement them.
In this post, I’ll break down what this bot does, step by step, and
provide code snippets along the way to show how each piece works.
Breaking Down the Bot: From Concept to Execution
1. Getting Started with Bluesky
Before diving into the code, we need to interact with the Bluesky
platform. Bluesky offers a simple API, which we can use to post updates.
To do this, I used the @atproto/api
package to interact with the Bluesky
API.
What we’re doing:
- Logging into Bluesky using your username and password (stored
securely with
dotenv
). - Posting a text update to Bluesky, which will be our song info.
Here’s how we log in and post to Bluesky:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| const api = require('@atproto/api');
const dotenv = require('dotenv');
dotenv.config();
const agent = new api.BskyAgent({
service: 'https://bsky.social',
});
async function postToBluesky() {
try {
// Login to Bluesky with credentials stored in the environment variables
await agent.login({
identifier: process.env.BLUESKY_USERNAME,
password: process.env.BLUESKY_PASSWORD,
});
// The bot will post this text (current song info)
const songText = await logCurrentSong();
// Post the song info as a status update on Bluesky
const response = await agent.post({
text: songText,
});
console.log("Successfully posted to Bluesky:", response);
} catch (error) {
console.error('Error posting to Bluesky:', error);
}
}
postToBluesky();
|
Next, we need to interact with MPD (Music Player Daemon), which is
a server-side application that controls music playback. We’ll use the
mpd-api
library to fetch the currently playing song from MPD.
Here’s a simple function to retrieve the current song’s title, artist, and
album:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| const mpdapi = require('mpd-api');
async function logCurrentSong() {
try {
const config = {
host: '127.0.0.1', // Localhost
port: 6600, // Default MPD port
};
// Connect to MPD and get the status
const client = await mpdapi.connect(config);
const status = await client.api.status.get();
const currentSong = await client.api.status.currentsong();
// If a song is playing, return its info; otherwise, return a default message
if (status.state === 'play' && currentSong) {
return `Now playing: ${currentSong.title} by ${currentSong.artist} from the album ${currentSong.album}`;
} else {
return "No song currently playing.";
}
} catch (error) {
console.error('Error fetching song info:', error);
return "Error retrieving song info.";
}
}
|
In this code:
- We connect to MPD (running locally on
127.0.0.1
at port 6600
). - We fetch the current song details if MPD is playing, and we return
a string with the song title, artist, and album.
- If no song is playing, we return a message saying so.
3. Putting It All Together
Now that we have the logic to retrieve the song info from MPD and post to
Bluesky, it’s time to combine them into a full working bot.
Here’s what happens:
- The bot first logs in to Bluesky using your credentials.
- It then gets the current song from MPD.
- Finally, it posts the song details to Bluesky as a status update.
Here’s the full code for the bot:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
| "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const api_1 = require("@atproto/api");
const dotenv = __importStar(require("dotenv"));
const mpdapi = __importStar(require("mpd-api"));
// Load environment variables
dotenv.config();
// Create a Bluesky Agent
const agent = new api_1.BskyAgent({
service: 'https://bsky.social',
});
// Function to connect to MPD and log the current song
async function logCurrentSong() {
try {
// Define connection configuration
const config = {
host: "127.0.0.1", // MPD server host
port: 6600, // MPD server port
// password: 'yourpassword', // Uncomment if MPD requires a password
};
// Connect to MPD using the config object
const client = await mpdapi.connect(config);
console.log("Connected to MPD");
// Get the playback state and currently playing song
const status = await client.api.status.get();
const currentSong = await client.api.status.currentsong();
// Check if MPD is currently playing something
if (status.state === "play" && currentSong) {
const songInfo = {
title: currentSong.title || "Unknown Title",
artist: currentSong.artist || "Unknown Artist",
album: currentSong.album || "Unknown Album",
};
const songText = `Now playing: ${songInfo.title} by ${songInfo.artist} from the album ${songInfo.album}`;
console.log(songText);
return songText;
}
else {
console.log("No song currently playing.");
return "No song currently playing.";
}
// Disconnect from MPD
await client.disconnect();
}
catch (error) {
console.error("Error connecting to MPD or retrieving song information:", error);
return "Error retrieving song info.";
}
}
// Function to post current song info to Bluesky
async function postToBluesky() {
try {
// Login to Bluesky
await agent.login({
identifier: process.env.BLUESKY_USERNAME, // Explicitly cast to string
password: process.env.BLUESKY_PASSWORD, // Explicitly cast to string
});
// Get the current song info
const currentSongText = await logCurrentSong();
// Post the current song info to Bluesky
const response = await agent.post({
text: currentSongText,
});
console.log("Successfully posted to Bluesky:", response);
}
catch (error) {
console.error("Error posting to Bluesky:", error);
}
}
// Run the Bluesky post function
postToBluesky();
|
Running the Bot Locally
To run the bot locally:
- Install the necessary dependencies with
npm install
. - Create a
.env
file to store your Bluesky credentials:1
2
| BLUESKY_USERNAME=your_username
BLUESKY_PASSWORD=your_password
|
- Run the bot using
node index.js
.
When you run the bot, it will:
- Log into Bluesky.
- Retrieve the current song from MPD.
- Post the song info to Bluesky as a status update.
You should see the current song in your Bluesky feed!
Next Steps: Expanding the Bot
This bot is a great starting point, but there’s a lot of room for improvement:
- Last.fm Scrobbling: Integrate Last.fm API to scrobble the songs you listen to.
- YouTube Integration: If you want to share music from YouTube, you’ll need to figure out how to extract the currently playing video or song.
- Scheduled Posts: You could set up a scheduled task to post updates periodically instead of only when triggered manually.
Check Out the Full Code on GitHub
If you want to explore the full project, check out the repository on GitHub:
https://github.com/nishimi-ya/mpd-bsky-bot