# 機能設計書 123-Twitter_X

## 概要

本ドキュメントは、ChatwootのTwitter/X連携機能の設計仕様を記載する。この機能は、Twitter（現X）を通じた顧客とのコミュニケーションをChatwootで一元管理するためのチャネル統合機能である。

### 本機能の処理概要

Twitter/X連携は、Twitter APIを通じてダイレクトメッセージ（DM）およびツイート（メンション・リプライ）を受信し、Chatwootの会話として管理する。エージェントはChatwootから直接DMやツイートへの返信を送信できる。

**業務上の目的・背景**：Twitter/Xは多くの企業が顧客サポートやブランドコミュニケーションに活用するプラットフォームである。本機能により、Twitter経由の問い合わせをChatwootの統合インターフェースで管理でき、SNS上での顧客対応を効率化する。

**機能の利用シーン**：
- 顧客からのダイレクトメッセージを受信・返信する場面
- ブランドアカウントへのメンションツイートに対応する場面
- ツイートスレッドでの会話を追跡する場面

**主要な処理内容**：
1. Twitterプロフィールとの認証・接続設定
2. Webhookを通じたDM・ツイートの受信処理
3. Twitter APIを使用したDM・ツイート送信
4. 連絡先（Twitterユーザー）の自動作成・更新
5. ツイートモードとDMモードの使い分け

**関連システム・外部連携**：
- Twitter API v1.1 / v2
- Twitter Account Activity API（Webhook）
- Twitty gem（Rubyクライアント）

**権限による制御**：インボックスの作成・管理にはAdministrator権限が必要。メッセージの送受信はインボックスにアサインされたエージェントが実行可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 49 | チャネル設定画面 | 主画面 | Twitterチャネルの設定・認可フロー |
| 11 | 会話詳細画面 | 主画面 | DM/ツイートの送受信 |

## 機能種別

データ連携 / メッセージング

## 入力仕様

### 入力パラメータ（チャネル作成時）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| profile_id | String | Yes | TwitterプロフィールID | 一意性（account_idとの組み合わせ） |
| twitter_access_token | String | Yes | アクセストークン | 必須 |
| twitter_access_token_secret | String | Yes | アクセストークンシークレット | 必須 |
| account_id | Integer | Yes | Chatwootアカウント | 必須 |
| tweets_enabled | Boolean | No | ツイート統合の有効化 | デフォルト: true |

### 入力パラメータ（メッセージ送信時）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| content | String | Yes | メッセージ本文 | 280文字制限（ツイート時） |
| in_reply_to | Integer | No | 返信先メッセージID | - |

### 入力データソース

- Twitter OAuth認可フローからトークン取得
- Chatwoot会話画面からのメッセージ入力

## 出力仕様

### 出力データ（Webhook受信時）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| message | Message | 作成されたメッセージレコード |
| conversation | Conversation | 関連する会話 |
| contact | Contact | 送信者の連絡先 |

### 出力データ（メッセージ送信時）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id_str | String | Twitter側のツイートID（source_idとして保存） |

### 出力先

- Chatwootデータベース（messages, conversations, contacts）
- Twitter API（送信メッセージ）

## 処理フロー

### 処理シーケンス（メッセージ受信）

```
1. Twitter Webhookからイベント受信
   └─ TwitterEventsJobで処理
2. イベントタイプの判定
   └─ direct_message_events / tweet_create_events
3. DMイベントの場合
   └─ DirectMessageParserServiceで処理
   └─ 連絡先の作成または取得
   └─ 会話の作成または取得
   └─ メッセージの作成
4. ツイートイベントの場合
   └─ TweetParserServiceで処理
   └─ 会話タイプを'tweet'に設定
```

### 処理シーケンス（メッセージ送信）

```
1. Chatwootでメッセージ作成
   └─ Message#after_create_commitコールバック
2. SendOnTwitterServiceの実行
   └─ 会話タイプの判定（tweet/dm）
3. ツイートの場合
   └─ send_tweet_replyで返信ツイート送信
   └─ @メンション付きで送信
4. DMの場合
   └─ send_direct_messageでDM送信
5. レスポンス処理
   └─ id_strをsource_idとして保存
```

### フローチャート

```mermaid
flowchart TD
    A[メッセージ作成] --> B{チャネル検証}
    B -->|NG| C[処理終了]
    B -->|OK| D{outgoingメッセージ?}
    D -->|No| C
    D -->|Yes| E{private note?}
    E -->|Yes| C
    E -->|No| F{会話タイプ判定}
    F -->|tweet| G[send_tweet_reply]
    F -->|dm| H[send_direct_message]
    G --> I{送信成功?}
    H --> J{送信成功?}
    I -->|Yes| K[source_id保存]
    I -->|No| L[エラーログ出力]
    J --> M[処理完了]
    K --> M
    L --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | ツイート返信フォーマット | 返信時は@screen_nameを先頭に付与 | ツイート返信時 |
| BR-002 | 文字数制限 | ツイートは280文字以内 | ツイート送信時 |
| BR-003 | ツイート有効化設定 | tweets_enabled=falseの場合ツイート統合無効 | チャネル設定時 |
| BR-004 | 返信先特定 | in_reply_toまたは最後の受信メッセージに返信 | ツイート返信時 |

### 計算ロジック

- screen_name取得：返信先がoutgoingの場合はinbox.name、incomingの場合はsender.additional_attributes['screen_name']

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| チャネル作成 | channel_twitter_profiles | INSERT | Twitterチャネル情報保存 |
| メッセージ受信 | messages | INSERT | 受信メッセージ保存 |
| メッセージ送信 | messages | UPDATE | source_id更新 |
| 連絡先作成 | contacts | INSERT/UPDATE | 送信者情報保存 |
| 会話作成 | conversations | INSERT | 新規会話作成 |

### テーブル別操作詳細

#### channel_twitter_profiles

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | profile_id, twitter_access_token, twitter_access_token_secret, tweets_enabled, account_id | OAuth認可フローから取得 | 暗号化保存 |
| SELECT | twitter_access_token, twitter_access_token_secret | チャネルID指定 | API呼び出し時に使用 |

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | additional_attributes | {type: 'tweet'} or {type: 'dm'} | 会話タイプ識別 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | アクセストークン無効 | チャネル再認可 |
| 403 | Forbidden | API利用制限 | レート制限解除を待機 |
| 404 | Not Found | ユーザー・ツイート不存在 | エラーログ出力 |

### リトライ仕様

- 自動リトライなし
- エラーはRails.loggerに出力

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

- メッセージ受信時：メッセージ・会話・連絡先の作成は単一トランザクション
- メッセージ送信時：source_id更新は独立したトランザクション

## パフォーマンス要件

- Webhook処理はバックグラウンドジョブで実行
- Twitter APIのレート制限を考慮

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

- twitter_access_token、twitter_access_token_secretは暗号化して保存
- 環境変数でTWITTER_CONSUMER_KEY、TWITTER_CONSUMER_SECRETを管理

## 備考

- EDITABLE_ATTRS = [:tweets_enabled] でツイート統合の有効/無効を設定可能
- チャネル削除時にunsubscribeでWebhook登録解除

---

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

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

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | twitter_profile.rb | `app/models/channel/twitter_profile.rb` | チャネルモデルの構造、暗号化設定、Twittyクライアント |

**読解のコツ**:
- `tweets_enabled`フラグでツイート統合の有効/無効を制御
- `twitter_client`メソッドでTwitty::Facadeを生成

**主要処理フロー**:
- **23-26行目**: 暗号化設定
- **28行目**: テーブル名定義
- **30行目**: profile_idの一意性バリデーション
- **32行目**: 削除時にunsubscribe
- **34行目**: EDITABLE_ATTRS定義
- **40-46行目**: ContactInboxWithContactBuilderで連絡先作成
- **48-57行目**: twitter_clientメソッドでTwittyクライアント生成

#### Step 2: メッセージ送信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | send_on_twitter_service.rb | `app/services/twitter/send_on_twitter_service.rb` | DM/ツイート送信の分岐処理 |

**主要処理フロー**:
- **12-13行目**: perform_reply - 会話タイプで処理分岐
- **27-29行目**: conversation_type - additional_attributes['type']で判定
- **31-35行目**: screen_name - 返信先のスクリーンネーム取得
- **37-42行目**: send_direct_message - DM送信
- **44-50行目**: reply_to_message - 返信先メッセージ特定
- **52-63行目**: send_tweet_reply - ツイート返信送信、成功時source_id保存

#### Step 3: メッセージ受信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | direct_message_parser_service.rb | `app/services/twitter/direct_message_parser_service.rb` | DM受信処理 |
| 3-2 | tweet_parser_service.rb | `app/services/twitter/tweet_parser_service.rb` | ツイート受信処理 |

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

```
[メッセージ送信フロー]
Message#after_create_commit
    │
    └─ SendReplyJob.perform_later
           │
           └─ Twitter::SendOnTwitterService#perform
                  │
                  ├─ validate_target_channel
                  ├─ outgoing_message? チェック
                  ├─ invalid_message? チェック
                  │
                  └─ perform_reply
                         │
                         ├─ conversation_type判定
                         │
                         ├─ [tweet] send_tweet_reply
                         │      └─ twitter_client.send_tweet_reply
                         │
                         └─ [dm] send_direct_message
                                └─ twitter_client.send_direct_message

[メッセージ受信フロー]
Twitter Webhook
    │
    └─ TwitterEventsJob
           │
           ├─ [DM] DirectMessageParserService
           │      ├─ 連絡先作成/取得
           │      ├─ 会話作成/取得（type: 'dm'）
           │      └─ メッセージ作成
           │
           └─ [Tweet] TweetParserService
                  ├─ 連絡先作成/取得
                  ├─ 会話作成/取得（type: 'tweet'）
                  └─ メッセージ作成
```

### データフロー図

```
[送信フロー]
[Chatwoot]              [処理]                    [Twitter]

Message作成     ───▶ SendReplyJob
                      │
                      ▼
                SendOnTwitterService
                      │
                      ├─▶ [tweet] send_tweet_reply
                      │      └─ @screen_name + content
                      │
                      └─▶ [dm] send_direct_message
                             └─ recipient_id + content
                      │
                      ▼
                twitter_client.send_*      ───▶ Twitter API
                      │
                      ▼
                id_str受信                 ◀───  API Response
                      │
                      ▼
                source_id更新

[受信フロー]
[Twitter]               [処理]                    [Chatwoot]

Webhook Event   ───▶ TwitterEventsJob
                      │
                      ├─▶ DirectMessageParserService
                      │      └─ 連絡先/会話/メッセージ作成
                      │
                      └─▶ TweetParserService
                             └─ 連絡先/会話/メッセージ作成
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| twitter_profile.rb | `app/models/channel/twitter_profile.rb` | モデル | チャネル定義 |
| send_on_twitter_service.rb | `app/services/twitter/send_on_twitter_service.rb` | サービス | メッセージ送信処理 |
| direct_message_parser_service.rb | `app/services/twitter/direct_message_parser_service.rb` | サービス | DM受信処理 |
| tweet_parser_service.rb | `app/services/twitter/tweet_parser_service.rb` | サービス | ツイート受信処理 |
| webhook_subscribe_service.rb | `app/services/twitter/webhook_subscribe_service.rb` | サービス | Webhook購読管理 |
| webhooks_base_service.rb | `app/services/twitter/webhooks_base_service.rb` | サービス | Webhook基底クラス |
| twitter_events_job.rb | `app/jobs/webhooks/twitter_events_job.rb` | ジョブ | Webhook処理 |
| authorizations_controller.rb | `app/controllers/api/v1/accounts/twitter/authorizations_controller.rb` | コントローラー | OAuth認可 |
