# 機能設計書 51-マークダウン対応

## 概要

本ドキュメントは、Chatwootにおけるメッセージのマークダウンレンダリング機能について記述する。本機能は、エージェントが入力したマークダウン形式のテキストを、各配信チャネルに適したフォーマットに変換して送信する機能である。

### 本機能の処理概要

**業務上の目的・背景**：カスタマーサポートにおいて、エージェントはテキストに書式（太字、斜体、リスト等）を適用して、より分かりやすいメッセージを顧客に送信したいというニーズがある。しかし、WhatsApp、Telegram、Instagram、LINE、メール、SMSなど各チャネルは独自のマークダウン構文やHTMLサポート状況を持つため、統一された入力形式から各チャネルに適したフォーマットへの変換が必要である。

**機能の利用シーン**：エージェントがダッシュボードの会話詳細画面でメッセージを作成する際、マークダウン記法（例：`**太字**`、`_斜体_`、`- リスト項目`）を使用して入力する。メッセージ送信時に、接続先チャネルに応じて適切なフォーマットに自動変換される。

**主要な処理内容**：
1. メッセージコンテンツの受け取りとチャネルタイプの判定
2. チャネルタイプに応じたレンダラーの選択
3. CommonMarkerライブラリによるマークダウン構文解析
4. チャネル固有のフォーマットへの変換出力
5. 複数改行の保持処理

**関連システム・外部連携**：CommonMarkerライブラリ（GitHubフレーバーマークダウンパーサー）を使用してマークダウンの構文解析を行う。

**権限による制御**：本機能はメッセージ送信処理の一部として実行されるため、メッセージ送信権限を持つユーザー（エージェント、管理者）が利用可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 11 | 会話詳細画面 | 主画面 | メッセージ送信時にマークダウン変換処理が実行される |
| 116 | メッセージ画面（Widget） | 参照画面 | ウィジェットからのメッセージもマークダウン変換対象 |

## 機能種別

データ変換処理 / マークダウンレンダリング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| content | String | Yes | マークダウン形式のメッセージコンテンツ | 空でないこと |
| channel_type | String | Yes | チャネルタイプ（例：Channel::Whatsapp） | CHANNEL_RENDERERSに定義されたチャネルタイプ |
| channel | Object | No | チャネルオブジェクト（Twilio SMS用） | - |

### 入力データソース

- 会話画面からのメッセージ入力
- APIからのメッセージ送信リクエスト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| rendered_content | String | チャネル固有フォーマットに変換されたメッセージ |

### 出力先

- 外部チャネルAPI（WhatsApp、Telegram等）へ送信されるメッセージ本文

## 処理フロー

### 処理シーケンス

```
1. MarkdownRendererService初期化
   └─ コンテンツ、チャネルタイプ、チャネルオブジェクトを受け取る
2. renderメソッド実行
   └─ コンテンツが空の場合はそのまま返却
3. 有効チャネルタイプの判定
   └─ Twilio SMSでWhatsAppの場合はChannel::Whatsappとして扱う
4. レンダラーメソッドの選択
   └─ CHANNEL_RENDERERSから対応メソッドを取得
5. チャネル固有レンダリング実行
   └─ 各レンダラーがCommonMarkerでパースし変換
6. 複数改行の復元
   └─ 保持用プレースホルダーを元の改行に戻す
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{コンテンツが空?}
    B -->|Yes| C[コンテンツをそのまま返却]
    B -->|No| D[有効チャネルタイプ判定]
    D --> E{Twilio SMS & WhatsApp?}
    E -->|Yes| F[Channel::Whatsappとして扱う]
    E -->|No| G[元のチャネルタイプを使用]
    F --> H[レンダラーメソッド取得]
    G --> H
    H --> I{レンダラーあり?}
    I -->|No| J[コンテンツをそのまま返却]
    I -->|Yes| K[チャネル固有レンダリング実行]
    K --> L[終了]
    C --> L
    J --> L
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-51-01 | チャネル別レンダリング | 各チャネルは独自のマークダウン構文を持つため、チャネルタイプに応じて適切なレンダラーを使用する | メッセージ送信時 |
| BR-51-02 | Twilio SMS/WhatsApp判定 | Twilio SMSチャネルでWhatsApp番号の場合はWhatsAppレンダラーを使用する | Twilio SMSチャネル利用時 |
| BR-51-03 | 改行保持 | 2つ以上の連続改行は保持される | 全チャネル |
| BR-51-04 | 未対応チャネル | 対応するレンダラーがないチャネルは元のコンテンツをそのまま返却する | 未定義チャネル |

### チャネル別対応表

| チャネル | レンダラー | 太字 | 斜体 | 取り消し線 | リンク | コードブロック |
|----------|------------|------|------|-----------|--------|---------------|
| Email | HTML | `<strong>` | `<em>` | `<del>` | HTML形式 | HTML形式 |
| WebWidget | HTML | `<strong>` | `<em>` | `<del>` | HTML形式 | HTML形式 |
| Telegram | HTML | `<strong>` | `<em>` | `<del>` | `<a href>` | `<pre>` |
| WhatsApp | 専用記法 | `*text*` | `_text_` | - | URLのみ | バッククォート |
| Instagram | 専用記法 | `*text*` | `_text_` | - | URLのみ | プレーン |
| LINE | 専用記法 | `*text*` | `_text_` | - | URLのみ | バッククォート |
| Twitter | PlainText | なし | なし | なし | URLのみ | なし |
| SMS | PlainText | なし | なし | なし | URLのみ | なし |

## データベース操作仕様

### 操作別データベース影響一覧

本機能はメッセージ送信時のコンテンツ変換処理のため、直接のデータベース操作は行わない。

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | データベース操作なし |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | パース失敗 | 不正なマークダウン構文 | 元のコンテンツをそのまま返却 |

### リトライ仕様

パース処理はリトライ不要。失敗時は元のコンテンツを使用する。

## トランザクション仕様

本機能はステートレスな変換処理のため、トランザクション管理は不要。

## パフォーマンス要件

- CommonMarkerライブラリはCで実装されており高速
- 単一メッセージの変換は数ミリ秒以内で完了

## セキュリティ考慮事項

- HTMLレンダリング時はXSS攻撃を防ぐため、信頼できる入力のみを受け付ける
- CommonMarkerのデフォルト設定により、危険なHTMLタグは除去される

## 備考

- CommonMarker gemを使用（GitHubフレーバーマークダウン対応）
- 取り消し線は `~~text~~`（ダブルチルダ）形式をサポート

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: メインサービスクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | markdown_renderer_service.rb | `app/services/messages/markdown_renderer_service.rb` | チャネル別レンダラーのマッピングとレンダリングフロー |

**主要処理フロー**:
1. **2-13行目**: CHANNEL_RENDERERSでチャネルタイプとレンダラーメソッドのマッピング定義
2. **15-19行目**: コンストラクタでcontent、channel_type、channelを受け取る
3. **21-26行目**: renderメソッドで空チェック後、適切なレンダラーを呼び出し
4. **30-37行目**: Twilio SMS経由のWhatsApp判定ロジック
5. **99-111行目**: 複数改行を保持するプレースホルダー処理

#### Step 2: 基底レンダラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | base_markdown_renderer.rb | `app/services/messages/markdown_renderers/base_markdown_renderer.rb` | CommonMarker::Rendererを継承した基底クラスの動作 |

**主要処理フロー**:
- **3-4行目**: documentメソッドで子要素を出力
- **6-9行目**: paragraphメソッドで段落を処理
- **11-17行目**: text、softbreak、linebreakの基本処理
- **23-27行目**: strikethroughで取り消し線をHTMLタグとして出力
- **29-37行目**: method_missingで未定義ノードタイプを処理

#### Step 3: チャネル固有レンダラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | whats_app_renderer.rb | `app/services/messages/markdown_renderers/whats_app_renderer.rb` | WhatsApp固有のマークダウン記法への変換 |
| 3-2 | telegram_renderer.rb | `app/services/messages/markdown_renderers/telegram_renderer.rb` | TelegramのHTML形式への変換 |
| 3-3 | instagram_renderer.rb | `app/services/messages/markdown_renderers/instagram_renderer.rb` | Instagram固有の記法への変換 |
| 3-4 | line_renderer.rb | `app/services/messages/markdown_renderers/line_renderer.rb` | LINE固有の記法への変換 |
| 3-5 | plain_text_renderer.rb | `app/services/messages/markdown_renderers/plain_text_renderer.rb` | プレーンテキストへの変換（SMS、Twitter用） |

**WhatsAppレンダラーの主要処理**:
- **7-9行目**: 太字を`*text*`形式に変換
- **11-13行目**: 斜体を`_text_`形式に変換
- **30-38行目**: リストアイテムの番号付け処理

#### Step 4: 呼び出し元を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | message_content_presenter.rb | `app/presenters/message_content_presenter.rb` | outgoing_contentでMarkdownRendererServiceを呼び出す |

**主要処理フロー**:
- **10-15行目**: メッセージ送信時にMarkdownRendererServiceを初期化して変換実行

### プログラム呼び出し階層図

```
MessageContentPresenter
    │
    └─ Messages::MarkdownRendererService
           │
           ├─ Messages::MarkdownRenderers::BaseMarkdownRenderer (Email, WebWidget)
           │
           ├─ Messages::MarkdownRenderers::TelegramRenderer
           │
           ├─ Messages::MarkdownRenderers::WhatsAppRenderer
           │
           ├─ Messages::MarkdownRenderers::InstagramRenderer (Instagram, FacebookPage)
           │
           ├─ Messages::MarkdownRenderers::LineRenderer
           │
           └─ Messages::MarkdownRenderers::PlainTextRenderer (Twitter, SMS, TwilioSms)
```

### データフロー図

```
[入力]                    [処理]                           [出力]

マークダウン         MarkdownRendererService           チャネル固有
テキスト       ───▶  + CommonMarker パース    ───▶    フォーマット
                     + チャネル別レンダラー           テキスト

例: **太字**         WhatsAppRenderer               *太字*
    **太字**         TelegramRenderer               <strong>太字</strong>
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| markdown_renderer_service.rb | `app/services/messages/markdown_renderer_service.rb` | サービス | メインのレンダリングサービス |
| base_markdown_renderer.rb | `app/services/messages/markdown_renderers/base_markdown_renderer.rb` | レンダラー | 基底レンダラークラス |
| whats_app_renderer.rb | `app/services/messages/markdown_renderers/whats_app_renderer.rb` | レンダラー | WhatsApp用レンダラー |
| telegram_renderer.rb | `app/services/messages/markdown_renderers/telegram_renderer.rb` | レンダラー | Telegram用レンダラー |
| instagram_renderer.rb | `app/services/messages/markdown_renderers/instagram_renderer.rb` | レンダラー | Instagram用レンダラー |
| line_renderer.rb | `app/services/messages/markdown_renderers/line_renderer.rb` | レンダラー | LINE用レンダラー |
| plain_text_renderer.rb | `app/services/messages/markdown_renderers/plain_text_renderer.rb` | レンダラー | プレーンテキスト用レンダラー |
| message_content_presenter.rb | `app/presenters/message_content_presenter.rb` | プレゼンター | メッセージ出力時の変換呼び出し元 |
