🖥️

Obsidianプラグイン開発に挑戦!LoL試合データを自動取得してノート生成する「LoL Match Tracker」を作ってみた

に公開

Obsidianプラグイン開発に挑戦!LoL試合データを自動取得してノート生成する「LoL Match Tracker」を作ってみた

🇬🇧 English version available on Medium:
Read it on Medium

📝 概要

初めてのObsidianプラグイン開発として、League of Legends(LoL)の試合データを自動取得してノートを生成する「LoL Match Tracker」を作成しました。Riot APIと連携し、試合の詳細なスタッツやフレンドとの試合記録を自動的にMarkdownノートとして保存できるプラグインです。

この記事では、プラグイン開発の背景から技術的な実装、多言語対応まで、初心者目線で丁寧に解説します。

🎯 解決したい課題

  • 手動での試合記録作成の手間 - 毎回手動でノートを作成するのは時間がかかり、継続が難しい
  • データの正確性 - 手動入力だと数値の間違いが発生しやすい
  • 友人との試合記録の管理 - 個人の振り返りと友人との試合記録を統合して管理したい
  • 多言語対応 - 日本語、英語、韓国語で利用できるツールが欲しい
  • Obsidianの活用 - ノート管理の強力な機能を最大限に活かしたい

🧠 アプローチ

League of Legendsをプレイしていて、試合後の振り返りを習慣化したいと思っていました。Obsidianのマークダウンベースの記録システムに魅力を感じ、「これでLoLのマッチを記録してフィードバックしたら面白そうだな」と思ったのがきっかけです。

設計方針

  1. 自動化優先 - Riot APIから試合データを自動取得し、手動入力を最小限に
  2. フレンド連携 - 友人との試合を検出し、専用のフィードバックノートを生成
  3. 多言語対応 - i18nシステムを実装し、3言語に対応
  4. 検索性 - フロントマターとタグで試合を簡単に検索・整理
  5. 拡張性 - 将来的な機能追加を見据えた設計

主要機能

  • 🎮 自動試合データ取得 - Riot APIから最新の試合データを自動取得
  • 📝 詳細な試合ノート生成 - KDA、CS、ダメージ、ゴールド、ビジョンスコア、ビルド情報を含むMarkdownノート
  • 👥 フレンド試合検出 - 登録したフレンドとの試合を自動検出
  • 💬 フレンド専用ノート - 各フレンドとの試合に対して専用のフィードバックノートを生成
  • 🌐 多言語対応 - 英語、韓国語、日本語に対応
  • 🏷️ スマートな整理 - フロントマターとタグで検索しやすく整理

💻 実装例

使用技術

- TypeScript(型安全性を確保)
- Obsidian Plugin API(Obsidianの機能を活用)
- Riot Games API(試合データの取得)
- esbuild(バンドリング)
- i18n(多言語対応)

アーキテクチャ

src/
├── main.ts              # プラグインのエントリーポイント
├── i18n.ts              # 多言語対応
└── (設定画面はmain.ts内に統合)

Riot APIとの連携

async getAccountByRiotId(name?: string, tagLine?: string): Promise<RiotAccount> {
    const summonerName = name || this.settings.summonerName;
    const summonerTag = tagLine || this.settings.tagLine;
    const url = `https://asia.api.riotgames.com/riot/account/v1/accounts/by-riot-id/${encodeURIComponent(summonerName)}/${encodeURIComponent(summonerTag)}`;
  
    const response = await fetch(url, {
        headers: {
            'X-Riot-Token': this.settings.riotApiKey
        }
    });
  
    if (!response.ok) {
        throw new Error(`アカウント取得失敗: ${response.status} ${response.statusText}`);
    }
  
    return await response.json();
}

多言語対応の実装

export class I18n {
    private currentLanguage: Language;
  
    constructor(language: Language = 'en') {
        this.currentLanguage = language;
    }
  
    t(key: string, params?: Record<string, string | number>): string {
        const keys = key.split('.');
        let value: any = translations[this.currentLanguage];
      
        for (const k of keys) {
            if (value && typeof value === 'object') {
                value = value[k];
            } else {
                return key;
            }
        }
      
        if (typeof value !== 'string') {
            return key;
        }
      
        // パラメータの置換
        if (params) {
            Object.keys(params).forEach(paramKey => {
                value = value.replace(`{${paramKey}}`, String(params[paramKey]));
            });
        }
      
        return value;
    }
}

翻訳キーの階層構造とパラメータ置換機能を実装することで、柔軟な多言語対応を実現しました。

ノート生成の実装

async createMatchNote(matchData: MatchData, puuid: string) {
    // 自分のデータを取得
    const myData = matchData.info.participants.find(p => p.puuid === puuid);
    if (!myData) {
        throw new Error('プレイヤーデータが見つかりません');
    }
  
    // 日付をフォーマット
    const date = new Date(matchData.info.gameCreation);
    const dateStr = date.toISOString().split('T')[0];
    const timeStr = date.toTimeString().split(' ')[0];
  
    // 勝敗
    const result = myData.win ? 'Win' : 'Loss';
  
    // マークダウンコンテンツを生成
    const content = `---
date: ${dateStr}
time: ${timeStr}
champion: ${myData.championName}
role: ${myData.teamPosition || 'UNKNOWN'}
result: ${result}
kda: ${myData.kills}/${myData.deaths}/${myData.assists}
game_mode: ${gameMode}
match_id: ${matchData.metadata.matchId}
tags: [lol, match, ${result.toLowerCase()}, ${myData.championName.toLowerCase()}]
---

# ${this.i18n.t('match.overview')}
// ... ノート内容の生成
`;
  
    // ファイルを作成
    await this.app.vault.create(finalPath, content);
}

生成されるノートの例

メイン試合ノート

---
date: 2025-01-11
time: 14:30:00
champion: Ahri
role: MIDDLE
result: Win
kda: 8/2/12
game_mode: Ranked (Solo/Duo)
match_id: JP1_1234567890
tags: [lol, match, win, ahri]
---

# 試合概要

- **勝敗**: 🏆 勝利
- **ゲーム時間**: 32分15秒
- **ゲームモード**: Ranked (Solo/Duo)

# パフォーマンス

## スタッツ

- **KDA**: 8 / 2 / 12 (10.00)
- **CS**: 245 (7.6/分)
- **与ダメージ**: 45,230
- **被ダメージ**: 18,450

# 振り返りメモ

## 良かった点
- 

## 改善点
- 

## 学んだこと
- 

フレンド試合ノート

---
date: 2025-01-11
time: 14:30:00
friend: タロウ
my_champion: Ahri
friend_champion: Lee Sin
result: Win
same_team: true
tags: [lol, friend, タロウ, win]
---

# タロウとの試合

## タロウへのフィードバック

### 良かった点
- 

### 改善できそうな点
- 

## 連携について

### うまくいった連携
- 

### 改善したい連携
- 

🧩 結果と学び

開発で学んだこと

1. Obsidianプラグイン開発の基礎

Obsidianプラグインは一般的なWebアプリケーションとは異なる制約があります:

  • ファイルシステムへの直接アクセス - app.vault.create()でファイルを作成するが、既存ファイルの上書きや重複処理に注意が必要
  • プラグイン間の相互作用 - 他のプラグイン(Dataviewなど)との互換性を考慮した設計が重要
  • ユーザーのワークフローへの配慮 - 既存のノート構造を壊さない、自然な統合が求められる

2. Obsidianエコシステムの理解

単なるデータ記録ツールではなく、「思考を促進するツール」として設計することの重要性を学びました:

  • フロントマターの活用 - 構造化されたメタデータでDataviewクエリやフィルタリングが可能
  • リンク構造の設計 - [[]]記法を使った関連性の表現で、試合間の繋がりを可視化
  • タグ戦略 - 検索とフィルタリングを意識したタグ設計

3. ユーザー体験の設計

プラグイン開発では技術的な実装以上に、ユーザーの実際の使用場面を想像することが重要でした:

  • 設定の複雑さとのバランス - 柔軟性を保ちつつ、初期設定を簡単にする工夫
  • エラー時の分かりやすさ - APIキーの設定ミスなど、よくある問題の対処法を明確に提示
  • 段階的な機能提供 - 基本機能から始めて、徐々に高度な機能を使えるUI設計

今後の改善予定

機能追加

  • Dataviewプラグインとの連携強化
  • チャンピオン別統計の実装
  • カスタムテンプレート機能
  • グラフビューでの関連性表示

技術的改善

  • エラーハンドリングの強化
  • パフォーマンス最適化
  • 単体テストとE2Eテストの実装
  • オフライン対応(キャッシュ機能)

まとめ

初めてのObsidianプラグイン開発でしたが、TypeScriptとObsidian Plugin APIを組み合わせることで、思い描いていた機能を実現できました。このプラグインにより、LoLの試合記録を効率的に管理できるようになり、友人との試合についても詳細に振り返ることができるようになりました。何より、Obsidianの柔軟なノートシステムと組み合わせることで、単なる記録ツールを超えた学習支援ツールとして活用できています。

🔗 関連リンク

Discussion