はじめに
ポートフォリオって、基本的に「過去の成果物の展示」だと理解して、作ったもの、使った技術、経歴……全部過去形。悪くはないけど、ライブ感がない。
見に来てくれた人に「この人、今も現役で動いてるかな」って感じてもらいたかった。
ポートフォリオに 生きてる感 を出したかったんです。
そこで目をつけたのがDiscordのプレゼンス情報。オンライン状態・再生中の音楽・プレイ中のゲームなど、リアルタイムの自分がそこにあります。LanyardというAPIを使えば、それをそのままポートフォリオに引っ張ってこれます。
自分のポートフォリオはこちらです。
ポートフォリオ自体はNext.js(途中Page Router → App Router)+ TypeScriptで構築していて、Discordステータス以外にもいくつか機能を入れています。
その他の機能(クリックで展開)
| 機能 | 概要 |
|---|---|
| 天気連動エフェクト | 現在地の天気に応じて背景の雨・雪・雷が変わる(桜の時期など) |
| WakaTimeコーディング統計 | 直近のコーディング時間をリアルタイム表示 |
| Notionベースの/nowページ | 「今何に取り組んでいるか」をNotionデータベースから自動取得 |
| 訪問者計上 | アクセス元の国や人数などの計上 |
| イースターエッグ | KONAMI COMMANDなど |
この記事では、Discordステータスのリアルタイム表示に絞って紹介します。
動いているイメージ
折りたたみ状態
展開状態
Lanyardとは
Lanyardは、DiscordユーザーのプレゼンスデータをREST APIやWebSocketで取得できるサービスです。
似たサービスとの違い
Discordのステータスを外部から取得する仕組みとして、個人が自作したAPIを公開しているケースもあります(参考)。LanyardはオープンソースでGitHub上に3k+ Starsがあり、WebSocketによるリアルタイム更新・Spotify連携・KVストアなど機能が豊富で、コミュニティも活発です。安定性・機能面で選ぶならLanyardが無難だと思います。
セットアップ手順
- Lanyardの公式Discordサーバーに参加する
- 以上 簡単すぎるでしょう
サーバーに入った瞬間から、自分のプレゼンスがAPIで取れるようになります。
データの取得方法
| 方式 | エンドポイント | 用途 |
|---|---|---|
| REST API | GET https://api.lanyard.rest/v1/users/{discord_user_id} |
単発取得 |
| WebSocket | wss://api.lanyard.rest/socket |
リアルタイム |
今回はリアルタイムで反映させたいのでWebSocketを使います。
レスポンス例
REST APIを叩くと、こんなJSONが返ってきます。
https://api.lanyard.rest/v1/users/{Discord_ID}
{
"success": true,
"data": {
"active_on_discord_mobile": false,
"active_on_discord_desktop": true,
"listening_to_spotify": true,
// Lanyard KV
"kv": {
"location": "Los Angeles, CA"
},
// Below is a custom crafted "spotify" object, which will be null if listening_to_spotify is false
"spotify": {
"track_id": "3kdlVcMVsSkbsUy8eQcBjI",
"timestamps": {
"start": 1615529820677,
"end": 1615530068733
},
"song": "Let Go",
"artist": "Ark Patrol; Veronika Redd",
"album_art_url": "https://i.scdn.co/image/ab67616d0000b27364840995fe43bb2ec73a241d",
"album": "Let Go"
},
"discord_user": {
"username": "Phineas",
"public_flags": 131584,
"id": "94490510688792576",
"discriminator": "0001",
"avatar": "a_7484f82375f47a487f41650f36d30318"
},
"discord_status": "online",
// activities contains the plain Discord activities array that gets sent down with presences
"activities": [
{
"type": 2,
"timestamps": {
"start": 1615529820677,
"end": 1615530068733
},
"sync_id": "3kdlVcMVsSkbsUy8eQcBjI",
"state": "Ark Patrol; Veronika Redd",
"session_id": "140ecdfb976bdbf29d4452d492e551c7",
"party": {
"id": "spotify:94490510688792576"
},
"name": "Spotify",
"id": "spotify:1",
"flags": 48,
"details": "Let Go",
"created_at": 1615529838051,
"assets": {
"large_text": "Let Go",
"large_image": "spotify:ab67616d0000b27364840995fe43bb2ec73a241d"
}
},
{
"type": 0,
"timestamps": {
"start": 1615438153941
},
"state": "Workspace: lanyard",
"name": "Visual Studio Code",
"id": "66b84f5317e9de6c",
"details": "Editing README.md",
"created_at": 1615529838050,
"assets": {
"small_text": "Visual Studio Code",
"small_image": "565945770067623946",
"large_text": "Editing a MARKDOWN file",
"large_image": "565945077491433494"
},
"application_id": "383226320970055681"
}
]
}
}
↑ 参考:https://github.com/Phineas/lanyard
ここからdataの中身を使ってUIを組み立てていきます。
特に重要なのは以下の2つです。
| フィールド | 役割 |
|:-----------|:-----|
| `platform` | アクティビティの発信元デバイス。PS5なら `"ps5"` が入る |
| `active_on_discord_embedded` | PS5のような組み込みデバイスが Discord に接続しているか |
上記はDiscordの「設定 → 接続」から確認できます。Spotify、PlayStation、YouTube、Twitchなど、Discordが公式にサポートしている外部サービスの一覧です。ここで接続したサービスのアクティビティが自動的にプレゼンスに反映されてLanyard経由で取得できるようになります。
上記一覧に載っていないアプリ(Chrome・VSCode・Slackなど)でも、Discordの「設定 → 登録済みのゲーム」から手動で登録すれば、activities配列にアクティビティとして載せることができます。つまり、公式連携 + 手動登録を組み合わせることで、ほぼあらゆるアプリの使用状況をポートフォリオに表示可能です。
実装
1. 環境変数
NEXT_PUBLIC_DISCORD_ID=DiscordユーザーID
DiscordのユーザーIDは、設定 → 詳細設定 → 開発者モードをONにしたあと、自分のアイコンを右クリック → 「IDをコピー」で取得できます。
2. カスタマイズ型定義
Lanyardから返ってくるデータの型です。
interface LanyardData {
discord_status: "online" | "idle" | "dnd" | "offline";
discord_user: {
avatar: string;
display_name: string;
username: string;
id: string;
};
activities: {
name: string;
type: number; // 0=Playing, 1=Streaming, 2=Listening, 3=Watching, 4=Custom, 5=Competing
state?: string;
details?: string;
platform?: string; // "ps5" など — コンソール識別用
assets?: {
large_image?: string;
large_text?: string;
small_image?: string;
small_text?: string;
};
}[];
active_on_discord_desktop: boolean;
active_on_discord_mobile: boolean;
active_on_discord_embedded: boolean; // PS5 などコンソールの接続判定
listening_to_spotify: boolean;
spotify: {
song: string;
artist: string;
album: string;
album_art_url: string;
track_id: string;
} | null;
}
3. WebSocket接続
LanyardのWebSocketプロトコルはシンプルです。
| op | 方向 | 内容 |
|---|---|---|
| 0 | 受信 | イベント(INIT_STATE / PRESENCE_UPDATE) |
| 1 | 受信 | Hello(heartbeat_interval が含まれる) |
| 2 | 送信 | Initialize(購読するユーザー ID を指定) |
| 3 | 送信 | Heartbeat |
useEffect(() => {
let ws: WebSocket;
let heartbeat: NodeJS.Timeout;
function connect() {
ws = new WebSocket("wss://api.lanyard.rest/socket");
ws.onopen = () => {
ws.send(JSON.stringify({
op: 2,
d: { subscribe_to_id: DISCORD_ID },
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.op === 1) {
// Hello — heartbeat 開始
heartbeat = setInterval(() => {
ws.send(JSON.stringify({ op: 3 }));
}, msg.d.heartbeat_interval);
}
if (msg.op === 0) {
// プレゼンス更新
if (msg.t === "INIT_STATE" || msg.t === "PRESENCE_UPDATE") {
setData(msg.d);
}
}
};
ws.onclose = () => {
clearInterval(heartbeat);
setTimeout(connect, 5000); // 切断時は 5 秒後に再接続
};
}
connect();
return () => {
clearInterval(heartbeat);
ws?.close();
};
}, []);
接続が切れても5秒で自動再接続します。
Lanyardを使う上で共通の部分はここまでです。ここから先はデータをどう加工・表示するかの話になります。
ここから先は自分のポートフォリオ向けのカスタマイズです。
Lanyardのデータをどう見せるかは完全に自由なので、あくまで一例として参考にしてください。
4. アクティビティの動詞マッピング
Discordのアクティビティにはtypeフィールドで種類が分かりますが、そのまま使うと不自然になるケースがあります。
たとえばYouTubeはtype: 0(Playing)なので、そのままだと 「Playing YouTube」 になる。いや、見てるんだよと。
const activityVerb: Record<number, string> = {
0: "Playing", 1: "Streaming", 2: "Listening to", 3: "Watching", 5: "Competing in",
};
// アプリ名で動詞を上書き
const appVerb: Record<string, string> = {
"YouTube": "Watching", "Google Chrome": "Browsing", "Code": "Coding in",
"ChatGPT": "Chatting with", "Notion": "Writing in", "Slack": "Chatting on",
// ...自分の使うアプリに合わせて自由に追加
};
// 表示名の上書き(LanyardはVS Codeを"Code"として返す)
const appDisplayName: Record<string, string> = { "Code": "VSCode" };
function getActivityVerb(type: number, name: string) {
return appVerb[name] ?? activityVerb[type] ?? "Using";
}
ハマりどころ:Discordに表示されるアプリ名はPCとPS5で微妙に異なることがあります。REST APIのレスポンスを実際に叩いて確認するのが確実です。
5. PS5アクティビティの検出
PS5とDiscordを連携していると、Lanyardのデータにコンソール情報が載ってきます。
// 電源が入っているだけ → activities空、embeddedフラグだけ立つ
{ "active_on_discord_embedded": true, "activities": [] }
// ゲーム起動中 → platform: "ps5" 付きでアクティビティが載る
{ "active_on_discord_embedded": true,
"activities": [{ "name": "ドラゴンクエストVII", "platform": "ps5", "type": 0 }] }
platformフィールドでPS5をPC側と分離します。
const ps5Activity = data.activities.find((a) => a.platform === "ps5");
const pcActivities = data.activities.filter((a) => a.type !== 4 && a.platform !== "ps5");
6. UI設計
折りたたみ + クリックで展開するカードUIにしています。
折りたたみ: Spotify再生中 → PCアクティビティ → PS5接続 → ステータス名の優先順位で1行表示
展開カード:
| セクション | 内容 |
|---|---|
| ステータス |
Online / Idle / DND / Offline
|
| Spotify | アルバムアート + 曲名 + アーティスト名 |
| アクティビティ | PC側の動詞付きリスト表示 |
| PlayStation 5 | ゲーム名 or Online
|
※自分のポートフォリオで開閉アニメーションにはFramer MotionのAnimatePresenceを使っています。
Discordへのアクティビティ登録
Lanyardがアクティビティを返すには、そもそもDiscord側でアプリが認識されている必要があります。
PCアプリの登録
ChromeやVS Codeなどは自動で検出されないことがあります。その場合は手動で登録します。
- Discord設定 → 登録済みのゲーム → 「追加する!」
- 起動中のアプリ一覧からChrome・Slack・VS Codeなどを選択
- 右側の目のアイコンでオーバーレイ表示のON/OFFを切り替え可能
ポイント
Discordはゲーム以外のアプリも「ゲーム」として登録できます。ここに登録したアプリが起動中の時、Lanyardのactivities配列にデータが載ってきます。
PS5との連携
PS5のゲームをDiscordに表示させるための設定です。
これでactive_on_discord_embedded: trueによりPS5の接続を検知でき、ゲーム起動中ならplatform: "ps5"付きでゲーム名も取得できます。
Spotify
Spotifyは特別な登録は不要です。DiscordアカウントとSpotifyアカウントを連携するだけで、再生中の曲がLanyardのspotifyフィールドに自動的に載ります。
Discord設定 → 接続 → Spotifyを追加
全部繋ぐとこうなる
SpotifyはLanyardのspotifyフィールドで常時取得、PS5はactive_on_discord_embeddedフラグで常時検知されるので、これらは特別な工夫なしにリアルタイムで表示できます。一方、PCアプリ(Chrome・VSCodeなど)はアクティブなものしかLanyardに載らないため、独自にクライアント側で直近10分以内に使ったアプリを蓄積する仕組みを入れています。
これにより、Chromeで調べ物 → VSCodeに切り替えても両方表示され、さらにSpotifyのBGMやPS5の待機状態も合わさって、「今まさにPC前で何をしているか」の全体像がポートフォリオに反映されます。
おわりに
Lanyardのおかげで、認証もサーバーも不要でDiscordのプレゼンスをポートフォリオに組み込めました。
この記事のポイント:
- Lanyard WebSocketでリアルタイム更新
-
platformフィールドでPS5のアクティビティを識別 - active_on_discord_embeddedでコンソール接続を検知
- アプリ名ごとの動詞マッピング+表示名上書きで自然な文言に
「この人、今スペースマリーン2やってるんだ」とか「Spotify聴いてるな」とか、ポートフォリオを見に来た人がちょっとニヤッとしてくれたら嬉しいです。








