diff --git a/lib/services/gmail_wrapper.dart b/lib/services/gmail_wrapper.dart new file mode 100644 index 0000000..0cfafd9 --- /dev/null +++ b/lib/services/gmail_wrapper.dart @@ -0,0 +1,82 @@ +// Version: 1.0.0 +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:googleapis/gmail/v1.dart'; +import 'package:googleapis_auth/google_auth.dart'; + +/// Gmail API を介した同期用メールリレー +/// +/// P2P 通信不要なノード識別システムを提供します。 +class GmailWrapper { + final String _gmailAddress; // BCC 用gmail アドレス(ノードキー) + final GAuthClient? _authClient; // OAuth 認証クライアント + final GmailService? _gmail; + + /// 新しいインスタンスを作成 + factory GmailWrapper({ + required String gmailAddress, + bool useOAuth = true, + }) { + if (useOAuth) { + // 正式な OAuth2 フロー(credentials.json など)を使う場合 + final client = GAuthClient.fromFile('credentials.json'); + return GmailWrapper._internal( + gmailAddress: gmailAddress, + authClient: client, + ); + } else { + // アプリパスワードなどの簡易認証は後実装 + throw UnsupportedError('OAuth 方式でのみサポートされています'); + } + } + + GmailWrapper._internal({ + required this._gmailAddress, + required GAuthClient? authClient, + }) : _authClient = authClient, + _gmail = authClient != null ? GmailService(authClient) : null; + + /// ノード ID(BCC アドレス)を取得 + String get gmailAddress => _gmailAddress; + + /// チャットメッセージをノードに配送する + Future sendMessage({ + required String fromNode, + required String message, + String? toNode, + }) async { + if (_gmail == null) return; + + try { + // シンプルなテキストの送信(HTML のため簡易 escaping) + final body = base64Encode(utf8.encode(message)); + + await _gmail.users.messages.send( + userId: 'me', + body: EmailMessage(fromNode, message, toNode), + ); + + print('[Gmail] メッセージ送信完了 (from=$fromNode to=$toNode)'); + } catch (e) { + print('[Gmail] 送信失敗:$e'); + rethrow; + } + } + + /// ノードリストを取得(自己認識用) + Future> getNodes() async { + // 現在接続可能なノードを返す(ハートビートの結果などから集約) + return []; // ここに母艦が管理するノードリストを読み込むロジック + } +} + +/// メール送信用ダミーモデル(後実装で本格的な GmailService の構築へ) +class EmailMessage { + final String from; + final String body; + final String? to; + + EmailMessage(this.from, this.body, this.to); + + // ...実際のメールオブジェクト構築は Googleapis ライブラリに委譲... +} diff --git a/lib/services/google/gmail_wrapper.dart b/lib/services/google/gmail_wrapper.dart new file mode 100644 index 0000000..54c67ee --- /dev/null +++ b/lib/services/google/gmail_wrapper.dart @@ -0,0 +1,121 @@ +// Version: 1.0.0 +import 'dart:async'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:googleapis_auth/google_auth.dart'; +import 'package:googleapis/gmail/v1.dart'; + +/// Gmail API を介した同期用メールリレー +/// +/// P2P 通信不要なノード識別システムを提供します。 +/// BCC の Gmail アドレスをノードの一意キーとして使用。 +class GmailWrapper { + final String _gmailAddress; // BCC 用 gmail アドレス(ノードキー) + final GAuthClient? _authClient; // OAuth 認証クライアント + final GmailService? _gmail; + + /// 新しいインスタンスを作成 + factory GmailWrapper({ + required String gmailAddress, + bool useOAuth = true, + }) async { + if (useOAuth) { + try { + final client = GAuthClient.fromFile('credentials.json'); + return GmailWrapper._internal( + gmailAddress: gmailAddress, + authClient: client, + ); + } catch (_) { + // credentials.json がない場合、後で認証フローを実行できるように null を保持 + print('[Gmail] credentials.json 未見。認証画面から開始してください'); + return GmailWrapper._internal( + gmailAddress: gmailAddress, + authClient: null, + ); + } + } else { + throw UnsupportedError('OAuth 方式でのみサポートされています'); + } + } + + GmailWrapper._internal({ + required this._gmailAddress, + GAuthClient? authClient, + }) : _authClient = authClient, + _gmail = authClient != null ? GmailService(authClient) : null; + + /// ノード ID(BCC アドレス)を取得 + String get gmailAddress => _gmailAddress; + + /// 認証状態を確認 + bool get isAuthorized => _authClient != null && _gmail != null; + + /// チャットメッセージをノードに配送する + Future sendMessage({ + required String fromNode, + required String message, + String? toNode, + }) async { + if (_gmail == null) return; + + try { + // シンプルなテキストの送信(HTML のため簡易 escaping) + final body = base64Encode(utf8.encode(message)); + + final msg = EmailMessage( + subject: '[SalesAssist1] チャット:$fromNode', + to: toNode, + cc: null, + bcc: _gmailAddress, // BCC でノード識別 + htmlBody: '

$message

', + ); + + final response = await _gmail.users.messages.send( + userId: 'me', + body: msg, + ).root; + + print('[Gmail] メッセージ送信完了 (from=$fromNode to=${toNode ?? 'BCC キー'})'); + } catch (e) { + print('[Gmail] 送信失敗:$e'); + rethrow; + } + } + + /// ノードリストを取得(自己認識用) + Future> getNodes() async { + // 現在接続可能なノードを返す(ハートビートの結果などから集約) + return []; // ここに母艦が管理するノードリストを読み込むロジック + } + + /// 認証フローを起動(初回実行時) + Future authenticate() async { + try { + final client = GAuthClient.fromFile('credentials.json'); + print('[Gmail] 認証済みクライアント使用'); + return client; + } catch (e) { + print('[Gmail] 認証フロー起動:$e'); + // GoogleSignIn で認証画面をポップアップさせる処理(後実装) + return null; + } + } +} + +/// メール送信用モデル(Googleapis ライブラリの仕様に従う) +class EmailMessage { + String? subject; + List? to; + String? cc; + String? bcc; + String? htmlBody; + + EmailMessage({ + this.subject, + this.to, + this.cc, + this.bcc, + this.htmlBody, + }); +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 2e32637..affcf9a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,10 @@ dependencies: # Google エコシステム連携 google_sign_in: ^6.1.0 http: ^1.1.0 - + googleapis_auth: ^1.4.0 + googleapis: ^12.0.0 + googleapis_auth: ^1.5.0 + dev_dependencies: flutter_test: sdk: flutter