Before you proceed to make your own version of this project, I'd like to inform you that there already is a well-established foss and community for this called lanyard.
What I've essentially did here is:
- Create a bot and put it in a private guild
- Read my user's presence
- Feed it to the client through websockets
Creating the bot
Head over to the developer portal and create a new application. Make a new bot, with presence intent enabled as here:
Then put it in a private server your account is in. You can filter presences of course, but it's cleaner to just keep it in a channel that only your account is in.
Reading your presence
Now for this part, I've used serenity.rs.
Made a client and event handler with presence intent, and every presence update, created a simple json payload for the stuff I need. Which in serenity would look something like:
#[async_trait]
impl EventHandler for Handler {
async fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
async fn presence_update(&self, _ctx: Context, new_presence: Presence) {
println!("Presence Update");
if new_presence.user.id.get() != PRESENCE_TRACKED_ID {
return;
}
let mut payload = json!({
"status": new_presence.status.name(),
"activities": new_presence.activities
});
for activity in new_presence.activities {
println!("{}", activity.name.to_string());
let mut activity_payload = json!({
"name": activity.name.to_string()
});
match activity.application_id {
Some(id) => {
activity_payload["id"] = json!(id.to_string());
},
None => {}
}
match activity.assets {
Some(assets) => {
if let Some(large_text) = assets.large_text {
activity_payload["large_text"] = json!(large_text);
}
if let Some(small_text) = assets.small_text {
activity_payload["small_text"] = json!(small_text);
}
if let Some(large_image) = assets.large_image {
activity_payload["large_image"] = json!(large_image);
}
if let Some(small_image) = assets.small_image {
activity_payload["small_image"] = json!(small_image);
}
},
None => {}
}
payload["activities"].as_array_mut().unwrap().push(activity_payload);
}
self.presence_sender.send(payload.to_string()).unwrap();
}
}
Then I've pushed to a channel that broadcasts into websockets.
Websockets
Your implementation will differ here, but the idea is the same.
On the client side, create a WebSocket that onmessage updates the frontend dynamically.
let socket = new WebSocket("/presence");
socket.onopen = function(e) {
socket.send("OK");
}
const activities_dom = document.querySelector(".discord_activities");
socket.onmessage = function(e) {
// Update document
let data = JSON.parse(e.data);
if (data.heartbeat) {
return;
}
let status = document.getElementById("discord-status");
if (data.status === "online") {
status.style.color = "green";
...
Do note that WebSockets have timeouts, so you're going to have to ping/pong your connection even if nothing updates.
I simply send {"heartbeat":1} to the client every 5 seconds.
Then on the server side, create a WebSocket connection by updating the http version to 1.1 and setting the upgrade headers. I'm using nginx so for the presence route:
location /discord {
proxy_pass http://127.0.0.1:8080/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
And send the payload to the client.
~Q3 2025~