sveltekit-lanyard
Version:
A Svelte 5 library for integrating Discord Lanyard API with type-safe WebSocket and REST support
484 lines (383 loc) • 12.1 kB
Markdown
# sveltekit-lanyard
A modern, type-safe Svelte 5 library for seamless Discord Lanyard API integration with real-time WebSocket and REST support.
[](https://www.npmjs.com/package/sveltekit-lanyard)
[](https://opensource.org/licenses/MIT)
## Features
- **Fully Type-Safe** - Complete TypeScript support with Discord API types
- **Fine-Grained Reactivity** - Built with Svelte 5 runes
- **Dual Connection Modes** - Choose between WebSocket (real-time) or REST (polling)
- **Automatic Polling** - Optional automatic data refreshing for REST connections
- **Auto-Reconnection** - Configurable automatic reconnection for WebSocket connections
- **Heartbeat Management** - Automatic WebSocket heartbeat handling to keep connections alive
- **Custom URLs** - Support for self-hosted Lanyard instances
- **SSR Compatible** - Works seamlessly with SvelteKit's server-side rendering
## Installation
```bash
# pnpm
pnpm add sveltekit-lanyard
# bun
bun add sveltekit-lanyard
```
## Quick Start
### REST Mode
```svelte
<script lang="ts">
import { useLanyard } from 'sveltekit-lanyard';
const lanyard = useLanyard({
connectionType: 'rest',
userId: '769702535124090904'
});
</script>
{#if lanyard.loading}
<p>Loading presence...</p>
{:else if lanyard.data}
<p>{lanyard.data.discord_user.username} is {lanyard.data.discord_status}</p>
{/if}
```
### WebSocket Mode
```svelte
<script lang="ts">
import { useLanyard } from 'sveltekit-lanyard';
const lanyard = useLanyard({
connectionType: 'ws',
subscriptionScope: {
subscribe_to_id: '769702535124090904'
}
});
</script>
{#if lanyard.connected && lanyard.data}
<p>{lanyard.data.discord_user.username} is {lanyard.data.discord_status}</p>
{/if}
```
## Usage
### REST API
The REST API is ideal for simple use cases where you want to fetch presence data on-demand or at intervals.
#### Basic Example
```svelte
<script lang="ts">
import { useLanyard } from 'sveltekit-lanyard';
const lanyard = useLanyard({
connectionType: 'rest',
userId: '94490510688792576'
});
</script>
<div class="presence-card">
{#if lanyard.loading}
<div class="loading">
<p>Loading presence data...</p>
</div>
{:else if lanyard.error}
<div class="error">
<p>Error: {lanyard.error.message}</p>
<button onclick={() => lanyard.refetch()}>Retry</button>
</div>
{:else if lanyard.data}
<div class="user-info">
<img
src="https://cdn.discordapp.com/avatars/{lanyard.data.discord_user.id}/{lanyard.data
.discord_user.avatar}.png"
alt="Avatar"
/>
<h2>{lanyard.data.discord_user.username}</h2>
<span class="status status-{lanyard.data.discord_status}">
{lanyard.data.discord_status}
</span>
</div>
<button onclick={() => lanyard.refetch()}>Refresh</button>
{/if}
</div>
```
#### Manual Refetch
```svelte
<script lang="ts">
import { useLanyard } from 'sveltekit-lanyard';
const lanyard = useLanyard({
connectionType: 'rest',
userId: '94490510688792576'
});
// Refetch every 30 seconds
$effect(() => {
const interval = setInterval(() => {
lanyard.refetch();
}, 30000);
return () => clearInterval(interval);
});
</script>
```
### WebSocket API
The WebSocket API provides real-time updates whenever a user's presence changes on Discord.
#### Single User Subscription
```svelte
<script lang="ts">
import { useLanyard } from 'sveltekit-lanyard';
const lanyard = useLanyard({
connectionType: 'ws',
subscriptionScope: {
subscribe_to_id: '94490510688792576'
}
});
</script>
<div class="presence-realtime">
{#if !lanyard.connected}
<p>Connecting to WebSocket...</p>
{:else if lanyard.error}
<p class="error">{lanyard.error}</p>
{:else if lanyard.data}
<div class="user">
<h2>{lanyard.data.discord_user.username}</h2>
<p>Status: {lanyard.data.discord_status}</p>
{#if lanyard.lastUpdate}
<p class="timestamp">
Last update: {new Date(lanyard.lastUpdate.d.timestamp).toLocaleString()}
</p>
{/if}
</div>
{/if}
</div>
```
#### Multiple Users Subscription
```svelte
<script lang="ts">
import { useLanyard } from 'sveltekit-lanyard';
const lanyard = useLanyard({
connectionType: 'ws',
subscriptionScope: {
subscribe_to_ids: ['94490510688792576', '162619778399240192', '261304190928461824']
}
});
</script>
{#if lanyard.connected && lanyard.data}
<div class="users-grid">
{#each Object.values(lanyard.data) as user}
<div class="user-card">
<h3>{user.discord_user.username}</h3>
<span class="status-{user.discord_status}">
{user.discord_status}
</span>
{#if user.listening_to_spotify}
<div class="spotify-mini">
🎵 Listening to {user.spotify.song}
</div>
{/if}
</div>
{/each}
</div>
{/if}
```
#### Subscribe to All Users (Advanced)
```svelte
<script lang="ts">
import { useLanyard } from 'sveltekit-lanyard';
const lanyard = useLanyard({
connectionType: 'ws',
subscriptionScope: {
subscribe_to_all: true
}
});
</script>
```
## API Reference
### `useLanyard(options)`
The main function to initialize a Lanyard connection.
#### Options
##### REST Mode Options
```typescript
{
connectionType: 'rest';
userId: string; // Discord user ID (Snowflake)
polling?: boolean; // Enable automatic polling (default: false)
pollingInterval?: number; // Polling interval in ms (default: 5000)
apiUrl?: string; // Custom REST API URL (default: 'https://api.lanyard.rest/v1')
}
```
**REST Options:**
- `connectionType` - Must be `'rest'` for REST mode
- `userId` - Discord user ID (Snowflake)
- `polling` - _Optional_. Enable automatic polling to refresh data at intervals (default: `false`)
- `pollingInterval` - _Optional_. Polling interval in milliseconds (default: `5000`, only applies when `polling` is `true`)
- `apiUrl` - _Optional_. Custom REST API base URL for self-hosted Lanyard instances (default: `'https://api.lanyard.rest/v1'`)
##### WebSocket Mode Options
```typescript
{
connectionType: 'ws';
subscriptionScope:
| { subscribe_to_id: string } // Single user
| { subscribe_to_ids: string[] } // Multiple users (array of user IDs)
| { subscribe_to_all: true } // All users
maxReconnectAttempts?: number; // Max reconnection attempts (default: 3)
reconnectDelay?: number; // Delay between reconnects in ms (default: 1000)
wsUrl?: string; // Custom WebSocket URL (default: 'wss://api.lanyard.rest/socket')
}
```
**WebSocket Options:**
- `connectionType` - Must be `'ws'` for WebSocket mode
- `subscriptionScope` - Subscription configuration:
- `subscribe_to_id` - Single user ID to subscribe to
- `subscribe_to_ids` - Array of user IDs to subscribe to
- `subscribe_to_all` - Subscribe to all users (requires special access)
- `maxReconnectAttempts` - _Optional_. Maximum number of automatic reconnection attempts (default: `3`)
- `reconnectDelay` - _Optional_. Delay in milliseconds between reconnection attempts (default: `1000`)
- `wsUrl` - _Optional_. Custom WebSocket URL for self-hosted Lanyard instances (default: `'wss://api.lanyard.rest/socket'`)
#### Return Value
##### REST Mode Return
```typescript
{
data: LanyardPresenceData | null;
error: { message: string; code: string } | null;
loading: boolean;
refetch: () => Promise<void>;
}
```
**Properties:**
- `data` - The user's presence data, or `null` if not loaded
- `error` - Error object if request failed, or `null`
- `loading` - `true` during initial load or hydration
- `refetch()` - Function to manually refresh the data
##### WebSocket Mode Return
```typescript
{
data: LanyardPresenceData | Record<string, LanyardPresenceData> | null;
error: string | null;
connected: boolean;
initState: InitStatePayload | null;
lastUpdate: PresenceUpdatePayload | null;
}
```
**Properties:**
- `data` - Presence data (single user object or multi-user record)
- `error` - Error message if connection failed, or `null`
- `connected` - `true` when WebSocket is connected
- `initState` - Initial state received from WebSocket on connection
- `lastUpdate` - The most recent presence update event
## Advanced Configuration
### Automatic Polling (REST)
Enable automatic polling to keep REST data fresh without manual intervention:
```svelte
<script lang="ts">
import { useLanyard } from 'sveltekit-lanyard';
const lanyard = useLanyard({
connectionType: 'rest',
userId: '94490510688792576',
polling: true, // Enable automatic polling
pollingInterval: 3000 // Poll every 3 seconds (default: 5000ms)
});
</script>
<div>
{#if lanyard.data}
<p>{lanyard.data.discord_user.username} is {lanyard.data.discord_status}</p>
<p class="info">Data automatically refreshes every 3 seconds</p>
{/if}
<!-- Manual refresh is still available -->
<button onclick={() => lanyard.refetch()}>Refresh Now</button>
</div>
```
### Automatic Reconnection (WebSocket)
WebSocket connections automatically attempt to reconnect when disconnected:
```svelte
<script lang="ts">
import { useLanyard } from 'sveltekit-lanyard';
const lanyard = useLanyard({
connectionType: 'ws',
subscriptionScope: {
subscribe_to_id: '94490510688792576'
},
maxReconnectAttempts: 5, // Try up to 5 times (default: 3)
reconnectDelay: 2000 // Wait 2 seconds between attempts (default: 1000ms)
});
</script>
<div>
{#if lanyard.connected}
<span class="badge badge-success">Connected</span>
{:else if lanyard.error}
<span class="badge badge-error">Connection Error</span>
<p>Attempting to reconnect...</p>
{:else}
<span class="badge badge-warning">Connecting...</span>
{/if}
</div>
```
**Default URLs:**
- REST API: `https://api.lanyard.rest/v1`
- WebSocket: `wss://api.lanyard.rest/socket`
## Type Definitions
### Core Types
```typescript
import type {
LanyardPresenceData,
LanyardRestApi,
LanyardWebSocketApi,
LanyardClient,
LanyardGeneric
} from 'sveltekit-lanyard';
```
### LanyardPresenceData
```typescript
type LanyardPresenceData = {
active_on_discord_mobile: boolean;
active_on_discord_desktop: boolean;
active_on_discord_embedded: boolean;
active_on_discord_web: boolean;
listening_to_spotify: boolean;
spotify?: {
track_id: string;
song: string;
artist: string;
album: string;
album_art_url: string;
timestamps: {
start: number;
end: number;
};
};
discord_user: {
id: string;
username: string;
avatar: string;
discriminator: string;
bot: boolean;
global_name: string | null;
avatar_decoration_data: any | null;
display_name: string | null;
public_flags: number;
};
discord_status: 'online' | 'dnd' | 'idle' | 'offline';
activities: Activity[];
kv: Record<string, string>;
};
```
### Activity
Discord activity object following Discord API types.
```typescript
type Activity = {
name: string;
type: number;
state?: string;
details?: string;
timestamps?: {
start?: number;
end?: number;
};
assets?: {
large_image?: string;
large_text?: string;
small_image?: string;
small_text?: string;
};
// ... and more Discord activity properties
};
```
## Examples
You can check out a few examples in /examples
## FAQ
### Q: What is Lanyard?
**A:** Lanyard is a service that exposes Discord presence data through a simple API. You need to join the [Lanyard Discord server](https://discord.gg/lanyard) to use it with your Discord account.
### Q: How do I get a Discord user ID?
**A:** Enable Developer Mode in Discord settings, then right-click a user and select "Copy ID".
### Q: How do I handle errors?
**A:** Both modes expose an `error` property. For REST, you can retry with `refetch()`. WebSocket automatically attempts to reconnect based on your `maxReconnectAttempts` setting.
## License
MIT © [Mufaro](https://mufaro.dev)
## Credits
- [Lanyard API](https://github.com/Phineas/lanyard) by Phineas
- Built with [SvelteKit](https://kit.svelte.dev/)
- Discord API types from [discord-api-types](https://github.com/discordjs/discord-api-types)