A headless, framework-agnostic Discord activity tracker using the Lanyard API. Works seamlessly with React, Vue, Svelte, and vanilla JavaScript.
useDiscordActivity hook for React applicationsnpm install discord-lanyard-activity
yarn add discord-lanyard-activity
pnpm add discord-lanyard-activity
The following music services are automatically detected when you use Discord's native integrations:
For better activity tracking across websites, streaming platforms, and apps, we highly recommend using PreMID!
PreMID is a browser extension and desktop application that displays rich presence information for thousands of websites and services on Discord. It significantly enhances your Discord activity tracking beyond native integrations.
Install the Browser Extension
Install PreMID Desktop App
Install Presences (Service Integrations)
Start Using
When you use PreMID with this package:
Example: Watch Netflix â PreMID shows it on Discord â Lanyard tracks it â This package displays it on your website in real-time!
ðĄ Pro Tip: After installing PreMID, your Discord activity will become much richer with detailed information about what you're doing across the web!
import { DiscordActivityClient } from 'discord-lanyard-activity';
const client = new DiscordActivityClient({
userId: '743173584935190620',
onPresenceUpdate: (data) => {
console.log('User status:', data.discord_status);
console.log('Activities:', data.activities);
},
onConnect: () => console.log('Connected!'),
onError: (error) => console.error('Error:', error),
});
// Connect to WebSocket
client.connect();
// Subscribe to state changes
const unsubscribe = client.subscribe((state) => {
if (state.data) {
console.log('Current state:', state);
}
});
// Get current state
const currentState = client.getState();
// Manually reconnect
client.reconnect();
// Cleanup when done
client.disconnect();
// or
client.destroy(); // Also removes all listeners
import { useDiscordActivity } from 'discord-lanyard-activity/react';
function DiscordPresence() {
const { data, isLoading, error, reconnect } = useDiscordActivity({
userId: '743173584935190620',
});
if (isLoading) return <div>Loading Discord activity...</div>;
if (error) {
return (
<div>
<p>Error: {error.message}</p>
<button onClick={reconnect}>Retry</button>
</div>
);
}
if (!data) return null;
return (
<div>
<h2>{data.discord_user.username}</h2>
<p>Status: {data.discord_status}</p>
{data.listening_to_spotify && data.spotify && (
<div>
<h3>Listening to Spotify</h3>
<p>Song: {data.spotify.song}</p>
<p>Artist: {data.spotify.artist}</p>
<img src={data.spotify.album_art_url} alt="Album Art" />
</div>
)}
{data.activities.length > 0 && (
<div>
<h3>Current Activity</h3>
<p>{data.activities[0].name}</p>
{data.activities[0].details && <p>{data.activities[0].details}</p>}
</div>
)}
</div>
);
}
<script setup lang="ts">
import { useDiscordActivity } from 'discord-lanyard-activity/vue';
const { data, isLoading, error, reconnect } = useDiscordActivity({
userId: '743173584935190620',
});
</script>
<template>
<div v-if="isLoading">Loading Discord activity...</div>
<div v-else-if="error">
<p>Error: {{ error.message }}</p>
<button @click="reconnect">Retry</button>
</div>
<div v-else-if="data">
<h2>{{ data.discord_user.username }}</h2>
<p>Status: {{ data.discord_status }}</p>
<div v-if="data.listening_to_spotify && data.spotify">
<h3>Listening to Spotify</h3>
<p>Song: {{ data.spotify.song }}</p>
<p>Artist: {{ data.spotify.artist }}</p>
<img :src="data.spotify.album_art_url" alt="Album Art" />
</div>
<div v-if="data.activities.length > 0">
<h3>Current Activity</h3>
<p>{{ data.activities[0].name }}</p>
<p v-if="data.activities[0].details">{{ data.activities[0].details }}</p>
</div>
</div>
</template>
<script lang="ts">
import { useDiscordActivity } from 'discord-lanyard-activity/svelte';
const activity = useDiscordActivity({
userId: '743173584935190620',
});
</script>
{#if $activity.isLoading}
<div>Loading Discord activity...</div>
{:else if $activity.error}
<div>
<p>Error: {$activity.error.message}</p>
<button on:click={activity.reconnect}>Retry</button>
</div>
{:else if $activity.data}
<div>
<h2>{$activity.data.discord_user.username}</h2>
<p>Status: {$activity.data.discord_status}</p>
{#if $activity.data.listening_to_spotify && $activity.data.spotify}
<div>
<h3>Listening to Spotify</h3>
<p>Song: {$activity.data.spotify.song}</p>
<p>Artist: {$activity.data.spotify.artist}</p>
<img src={$activity.data.spotify.album_art_url} alt="Album Art" />
</div>
{/if}
{#if $activity.data.activities.length > 0}
<div>
<h3>Current Activity</h3>
<p>{$activity.data.activities[0].name}</p>
{#if $activity.data.activities[0].details}
<p>{$activity.data.activities[0].details}</p>
{/if}
</div>
{/if}
</div>
{/if}
DiscordActivityClientThe core client class that manages the WebSocket connection to Lanyard.
interface DiscordActivityOptions {
userId: string; // Discord user ID (required)
maxReconnectAttempts?: number; // Default: 5
autoReconnect?: boolean; // Default: true
websocketUrl?: string; // Default: "wss://api.lanyard.rest/socket"
onConnect?: () => void;
onDisconnect?: () => void;
onPresenceUpdate?: (data: LanyardData) => void;
onError?: (error: Error) => void;
}
connect() - Connect to Lanyard WebSocketdisconnect() - Disconnect from WebSocketreconnect() - Manually trigger a reconnectiongetState() - Get current state snapshotsubscribe(listener) - Subscribe to state changes, returns unsubscribe functiondestroy() - Disconnect and remove all listenersinterface DiscordActivityState {
data: LanyardData | null;
isConnected: boolean;
isLoading: boolean;
error: Error | null;
reconnectAttempts: number;
}
The package exports several utility functions for working with activity data:
import {
getActivityTypeLabel,
parseImageUrl,
detectMusicService,
calculateProgress,
formatDuration,
getAvatarUrl,
getDisplayName,
getMostRelevantActivity,
sortActivitiesByPriority,
} from 'discord-lanyard-activity';
// Get human-readable activity type
const label = getActivityTypeLabel(activity.type); // "Playing", "Listening to", etc.
// Parse Discord/Spotify image URLs
const imageUrl = parseImageUrl(activity.assets.large_image, activity);
// Detect music service from activity
const service = detectMusicService(activity); // { name, icon, color }
// Calculate progress for activities with timestamps
const progress = calculateProgress(activity.timestamps.start, activity.timestamps.end);
// Format milliseconds to MM:SS
const duration = formatDuration(180000); // "3:00"
// Get Discord avatar URL
const avatarUrl = getAvatarUrl(userId, avatar, discriminator);
// Get display name
const displayName = getDisplayName(globalName, username);
// Get the most relevant activity (prioritizes music > games > others)
const mainActivity = getMostRelevantActivity(activities);
enum ActivityType {
PLAYING = 0, // Playing a game
STREAMING = 1, // Streaming
LISTENING = 2, // Listening to music
WATCHING = 3, // Watching something
CUSTOM = 4, // Custom status
COMPETING = 5, // Competing in something
}
interface LanyardData {
spotify: SpotifyTrack | null;
listening_to_spotify: boolean;
discord_user: DiscordUser;
discord_status: "online" | "idle" | "dnd" | "offline";
activities: DiscordActivity[];
active_on_discord_mobile: boolean;
active_on_discord_desktop: boolean;
active_on_discord_web: boolean;
kv?: Record<string, string>;
}
interface DiscordActivity {
id: string;
name: string;
type: ActivityType;
url?: string;
created_at: number;
timestamps?: {
start?: number;
end?: number;
};
application_id?: string;
details?: string;
state?: string;
assets?: {
large_image?: string;
large_text?: string;
small_image?: string;
small_text?: string;
};
// ... more fields
}
The package automatically detects various music services:
Use detectMusicService(activity) to get service information including name, icon URL, and brand color.
Check out the /examples directory for complete working examples:
To use this package, you need to:
This package uses the Lanyard API by @Phineas.
MIT License - see LICENSE file for details
Contributions are welcome! Please feel free to submit a Pull Request.
If you encounter any issues or have questions, please open an issue on GitHub.