Init project
This commit is contained in:
18
.env.example
Normal file
18
.env.example
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 環境変数設定ファイル(例)
|
||||||
|
# このファイルをコピーして.envファイルを作成し、実際の値を設定してください
|
||||||
|
|
||||||
|
# メールサーバー設定
|
||||||
|
IMAP_SERVER=imap.gmail.com
|
||||||
|
IMAP_PORT=993
|
||||||
|
USE_SSL=true
|
||||||
|
MAILBOX=INBOX
|
||||||
|
|
||||||
|
# メール認証情報
|
||||||
|
EMAIL_USER=your-email@gmail.com
|
||||||
|
EMAIL_PASSWORD=your-app-password
|
||||||
|
|
||||||
|
# Discord Webhook URL
|
||||||
|
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL
|
||||||
|
|
||||||
|
# 監視設定
|
||||||
|
CHECK_INTERVAL=60
|
||||||
28
Dockerfile
Normal file
28
Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# 作業ディレクトリを設定
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# システムパッケージを更新
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Pythonの依存関係をコピーしてインストール
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# アプリケーションファイルをコピー
|
||||||
|
COPY app.py .
|
||||||
|
|
||||||
|
# 非rootユーザーを作成
|
||||||
|
RUN useradd --create-home --shell /bin/bash appuser
|
||||||
|
RUN chown -R appuser:appuser /app
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# ヘルスチェックを追加
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
|
CMD python -c "import sys; sys.exit(0)"
|
||||||
|
|
||||||
|
# アプリケーションを実行
|
||||||
|
CMD ["python", "app.py"]
|
||||||
315
README.md
Normal file
315
README.md
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
# Email to Discord Webhook Forwarder
|
||||||
|
|
||||||
|
メールサーバーを監視して、新しいメールが到着したときにDiscordのWebhookを使用して通知を送信するPythonアプリケーションです。
|
||||||
|
|
||||||
|
## 🚀 機能
|
||||||
|
|
||||||
|
- **メールサーバー監視**: IMAP/IMAPS プロトコルでメールサーバーを監視
|
||||||
|
- **Discord通知**: 新しいメールが到着したときにDiscordに通知
|
||||||
|
- **環境変数設定**: 設定は全て環境変数で管理
|
||||||
|
- **Dockerサポート**: コンテナとして簡単にデプロイ可能
|
||||||
|
- **SSL/TLS対応**: セキュアな接続をサポート
|
||||||
|
- **エラーハンドリング**: 堅牢なエラー処理とログ出力
|
||||||
|
|
||||||
|
## 📋 必要な環境変数
|
||||||
|
|
||||||
|
| 変数名 | 説明 | 必須 | デフォルト値 |
|
||||||
|
|--------|------|------|-------------|
|
||||||
|
| `EMAIL_USER` | メールアドレス | ✅ | - |
|
||||||
|
| `EMAIL_PASSWORD` | メールパスワード/アプリパスワード | ✅ | - |
|
||||||
|
| `DISCORD_WEBHOOK_URL` | Discord Webhook URL | ✅ | - |
|
||||||
|
| `IMAP_SERVER` | IMAPサーバーアドレス | ❌ | `imap.gmail.com` |
|
||||||
|
| `IMAP_PORT` | IMAPポート番号 | ❌ | `993` |
|
||||||
|
| `USE_SSL` | SSL/TLS使用の有無 | ❌ | `true` |
|
||||||
|
| `MAILBOX` | 監視するメールボックス | ❌ | `INBOX` |
|
||||||
|
| `CHECK_INTERVAL` | チェック間隔(秒) | ❌ | `60` |
|
||||||
|
|
||||||
|
## 🔧 セットアップ
|
||||||
|
|
||||||
|
### 1. Discord Webhook URLの取得
|
||||||
|
|
||||||
|
1. Discordでメッセージを送信したいチャンネルを選択
|
||||||
|
2. チャンネル設定 → 連携サービス → ウェブフック
|
||||||
|
3. 新しいウェブフックを作成してURLをコピー
|
||||||
|
|
||||||
|
### 2. Gmail用アプリパスワードの作成(Gmailを使用する場合)
|
||||||
|
|
||||||
|
1. Googleアカウントの2段階認証を有効にする
|
||||||
|
2. Googleアカウント設定 → セキュリティ → アプリパスワード
|
||||||
|
3. メール用のアプリパスワードを生成
|
||||||
|
|
||||||
|
### 3. 環境変数の設定
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env.exampleをコピーして.envファイルを作成
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# .envファイルを編集して実際の値を設定
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐳 Docker Composeでの実行
|
||||||
|
|
||||||
|
### 1. docker-compose.ymlの設定
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
- EMAIL_USER=your-email@gmail.com
|
||||||
|
- EMAIL_PASSWORD=your-app-password
|
||||||
|
- DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. コンテナの起動
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# コンテナをビルドして起動
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# ログの確認
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# コンテナの停止
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Linux用シェルスクリプトでの実行
|
||||||
|
|
||||||
|
Linux環境では便利なシェルスクリプトを使用できます:
|
||||||
|
|
||||||
|
### 1. スクリプトに実行権限を付与
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 初期セットアップ
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .envファイルを作成
|
||||||
|
./start.sh setup
|
||||||
|
|
||||||
|
# .envファイルを編集して実際の値を設定
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. よく使用するコマンド
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dockerイメージをビルド
|
||||||
|
./start.sh build
|
||||||
|
|
||||||
|
# コンテナを起動
|
||||||
|
./start.sh start
|
||||||
|
|
||||||
|
# リアルタイムでログを表示
|
||||||
|
./start.sh logs-f
|
||||||
|
|
||||||
|
# コンテナの状態を確認
|
||||||
|
./start.sh status
|
||||||
|
|
||||||
|
# コンテナを停止
|
||||||
|
./start.sh stop
|
||||||
|
|
||||||
|
# コンテナを再起動
|
||||||
|
./start.sh restart
|
||||||
|
|
||||||
|
# docker-compose で起動
|
||||||
|
./start.sh compose-up
|
||||||
|
|
||||||
|
# Python直接実行
|
||||||
|
./start.sh python
|
||||||
|
|
||||||
|
# ヘルプを表示
|
||||||
|
./start.sh help
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 利用可能なコマンド一覧
|
||||||
|
|
||||||
|
| コマンド | 説明 |
|
||||||
|
|----------|------|
|
||||||
|
| `setup` | 初期セットアップ(.envファイル作成) |
|
||||||
|
| `build` | Dockerイメージをビルド |
|
||||||
|
| `start` | コンテナを起動 |
|
||||||
|
| `stop` | コンテナを停止 |
|
||||||
|
| `restart` | コンテナを再起動 |
|
||||||
|
| `logs` | ログを表示 |
|
||||||
|
| `logs-f` | ログをリアルタイム表示 |
|
||||||
|
| `status` | コンテナの状態を確認 |
|
||||||
|
| `clean` | 停止済みコンテナとイメージを削除 |
|
||||||
|
| `compose-up` | docker-compose で起動 |
|
||||||
|
| `compose-down` | docker-compose で停止 |
|
||||||
|
| `python` | Python直接実行 |
|
||||||
|
| `help` | ヘルプを表示 |
|
||||||
|
|
||||||
|
## 🪟 Windows用PowerShellスクリプトでの実行
|
||||||
|
|
||||||
|
Windows環境では PowerShell スクリプトを使用できます:
|
||||||
|
|
||||||
|
### 1. 初期セットアップ
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# .envファイルを作成
|
||||||
|
.\start.ps1 setup
|
||||||
|
|
||||||
|
# .envファイルを編集して実際の値を設定
|
||||||
|
notepad .env
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. よく使用するコマンド
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Dockerイメージをビルド
|
||||||
|
.\start.ps1 build
|
||||||
|
|
||||||
|
# コンテナを起動
|
||||||
|
.\start.ps1 start
|
||||||
|
|
||||||
|
# リアルタイムでログを表示
|
||||||
|
.\start.ps1 logs-f
|
||||||
|
|
||||||
|
# コンテナの状態を確認
|
||||||
|
.\start.ps1 status
|
||||||
|
|
||||||
|
# コンテナを停止
|
||||||
|
.\start.ps1 stop
|
||||||
|
|
||||||
|
# ヘルプを表示
|
||||||
|
.\start.ps1 help
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔨 Dockerでの直接実行
|
||||||
|
|
||||||
|
### 1. イメージのビルド
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t email-to-discord .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. コンテナの実行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name email-monitor \
|
||||||
|
--restart unless-stopped \
|
||||||
|
-e EMAIL_USER=your-email@gmail.com \
|
||||||
|
-e EMAIL_PASSWORD=your-app-password \
|
||||||
|
-e DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL \
|
||||||
|
-e CHECK_INTERVAL=60 \
|
||||||
|
email-to-discord
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐍 Python直接実行
|
||||||
|
|
||||||
|
### 1. 依存関係のインストール
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 環境変数の設定
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Windowsの場合
|
||||||
|
set EMAIL_USER=your-email@gmail.com
|
||||||
|
set EMAIL_PASSWORD=your-app-password
|
||||||
|
set DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL
|
||||||
|
|
||||||
|
# Linux/macOSの場合
|
||||||
|
export EMAIL_USER=your-email@gmail.com
|
||||||
|
export EMAIL_PASSWORD=your-app-password
|
||||||
|
export DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. アプリケーションの実行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📧 対応メールプロバイダー
|
||||||
|
|
||||||
|
### Gmail
|
||||||
|
|
||||||
|
```env
|
||||||
|
IMAP_SERVER=imap.gmail.com
|
||||||
|
IMAP_PORT=993
|
||||||
|
USE_SSL=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Outlook/Hotmail
|
||||||
|
|
||||||
|
```env
|
||||||
|
IMAP_SERVER=outlook.office365.com
|
||||||
|
IMAP_PORT=993
|
||||||
|
USE_SSL=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Yahoo Mail
|
||||||
|
|
||||||
|
```env
|
||||||
|
IMAP_SERVER=imap.mail.yahoo.com
|
||||||
|
IMAP_PORT=993
|
||||||
|
USE_SSL=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### その他のプロバイダー
|
||||||
|
|
||||||
|
各プロバイダーのIMAP設定を確認して適切な値を設定してください。
|
||||||
|
|
||||||
|
## 📊 ログとモニタリング
|
||||||
|
|
||||||
|
アプリケーションは以下の情報をログ出力します:
|
||||||
|
|
||||||
|
- 起動/停止メッセージ
|
||||||
|
- メールサーバー接続状況
|
||||||
|
- 新しいメールの検出
|
||||||
|
- Discord送信の成功/失敗
|
||||||
|
- エラー情報
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dockerコンテナのログを確認
|
||||||
|
docker logs email-to-discord-monitor
|
||||||
|
|
||||||
|
# リアルタイムでログを監視
|
||||||
|
docker logs -f email-to-discord-monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 セキュリティ考慮事項
|
||||||
|
|
||||||
|
1. **アプリパスワードの使用**: 通常のパスワードではなくアプリ専用パスワードを使用
|
||||||
|
2. **環境変数での機密情報管理**: パスワードやWebhook URLは環境変数で管理
|
||||||
|
3. **SSL/TLS接続**: メールサーバーとの通信は暗号化
|
||||||
|
4. **非rootユーザー**: Dockerコンテナは非rootユーザーで実行
|
||||||
|
|
||||||
|
## 🛠️ トラブルシューティング
|
||||||
|
|
||||||
|
### メールサーバーに接続できない場合
|
||||||
|
|
||||||
|
1. IMAP設定が正しいか確認
|
||||||
|
2. アプリパスワードが正しく設定されているか確認
|
||||||
|
3. 2段階認証が有効になっているか確認(Gmail)
|
||||||
|
4. ファイアウォールの設定を確認
|
||||||
|
|
||||||
|
### Discord通知が送信されない場合
|
||||||
|
|
||||||
|
1. Webhook URLが正しいか確認
|
||||||
|
2. Discordサーバーの権限を確認
|
||||||
|
3. ネットワーク接続を確認
|
||||||
|
|
||||||
|
### ログの確認方法
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# アプリケーションのログレベルを変更(開発時)
|
||||||
|
# app.py内のlogging.basicConfig levelをDEBUGに変更
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 ライセンス
|
||||||
|
|
||||||
|
このプロジェクトはMITライセンスの下で公開されています。
|
||||||
|
|
||||||
|
## 🤝 コントリビューション
|
||||||
|
|
||||||
|
プルリクエストやイシューの報告を歓迎します。
|
||||||
|
|
||||||
|
## 📞 サポート
|
||||||
|
|
||||||
|
問題が発生した場合は、GitHubのIssueにて報告してください。
|
||||||
348
app.py
Normal file
348
app.py
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Email to Discord Webhook Forwarder
|
||||||
|
メールサーバーを監視してDiscordに転送するアプリケーション
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import imaplib
|
||||||
|
import email
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
import signal
|
||||||
|
import threading
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
# ログ設定
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.StreamHandler(sys.stdout)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EmailMessage:
|
||||||
|
"""メールメッセージのデータクラス"""
|
||||||
|
subject: str
|
||||||
|
sender: str
|
||||||
|
date: str
|
||||||
|
body: str
|
||||||
|
uid: str
|
||||||
|
|
||||||
|
class EmailMonitor:
|
||||||
|
"""メールサーバー監視クラス"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.running = False
|
||||||
|
self.mail_client = None
|
||||||
|
self.last_processed_uid = None
|
||||||
|
|
||||||
|
# 環境変数から設定を取得
|
||||||
|
self.imap_server = os.getenv('IMAP_SERVER', 'imap.gmail.com')
|
||||||
|
self.imap_port = int(os.getenv('IMAP_PORT', '993'))
|
||||||
|
self.email_user = os.getenv('EMAIL_USER')
|
||||||
|
self.email_password = os.getenv('EMAIL_PASSWORD')
|
||||||
|
self.discord_webhook_url = os.getenv('DISCORD_WEBHOOK_URL')
|
||||||
|
self.check_interval = int(os.getenv('CHECK_INTERVAL', '60')) # 秒
|
||||||
|
self.mailbox = os.getenv('MAILBOX', 'INBOX')
|
||||||
|
self.use_ssl = os.getenv('USE_SSL', 'true').lower() == 'true'
|
||||||
|
|
||||||
|
# 設定の検証
|
||||||
|
self._validate_config()
|
||||||
|
|
||||||
|
def _validate_config(self):
|
||||||
|
"""設定の検証"""
|
||||||
|
required_vars = ['EMAIL_USER', 'EMAIL_PASSWORD', 'DISCORD_WEBHOOK_URL']
|
||||||
|
missing_vars = [var for var in required_vars if not os.getenv(var)]
|
||||||
|
|
||||||
|
if missing_vars:
|
||||||
|
logger.error(f"必要な環境変数が設定されていません: {', '.join(missing_vars)}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
logger.info("設定の検証が完了しました")
|
||||||
|
|
||||||
|
def connect_to_email(self) -> bool:
|
||||||
|
"""メールサーバーに接続"""
|
||||||
|
try:
|
||||||
|
if self.use_ssl:
|
||||||
|
self.mail_client = imaplib.IMAP4_SSL(self.imap_server, self.imap_port)
|
||||||
|
else:
|
||||||
|
self.mail_client = imaplib.IMAP4(self.imap_server, self.imap_port)
|
||||||
|
|
||||||
|
self.mail_client.login(self.email_user, self.email_password)
|
||||||
|
self.mail_client.select(self.mailbox)
|
||||||
|
logger.info(f"メールサーバーに接続しました: {self.imap_server}:{self.imap_port}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"メールサーバーへの接続に失敗しました: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def disconnect_from_email(self):
|
||||||
|
"""メールサーバーから切断"""
|
||||||
|
if self.mail_client:
|
||||||
|
try:
|
||||||
|
self.mail_client.close()
|
||||||
|
self.mail_client.logout()
|
||||||
|
logger.info("メールサーバーから切断しました")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"メールサーバーからの切断でエラーが発生しました: {str(e)}")
|
||||||
|
|
||||||
|
def get_new_emails(self) -> List[EmailMessage]:
|
||||||
|
"""新しいメールを取得"""
|
||||||
|
try:
|
||||||
|
# UNSEENフラグのメールを検索
|
||||||
|
typ, data = self.mail_client.search(None, 'UNSEEN')
|
||||||
|
|
||||||
|
if typ != 'OK':
|
||||||
|
logger.warning("メール検索に失敗しました")
|
||||||
|
return []
|
||||||
|
|
||||||
|
email_ids = data[0].split()
|
||||||
|
new_emails = []
|
||||||
|
|
||||||
|
for email_id in email_ids:
|
||||||
|
try:
|
||||||
|
# メールを取得
|
||||||
|
typ, msg_data = self.mail_client.fetch(email_id, '(RFC822)')
|
||||||
|
|
||||||
|
if typ != 'OK':
|
||||||
|
continue
|
||||||
|
|
||||||
|
email_body = msg_data[0][1]
|
||||||
|
email_message = email.message_from_bytes(email_body)
|
||||||
|
|
||||||
|
# メールデータを解析
|
||||||
|
parsed_email = self._parse_email(email_message, email_id.decode())
|
||||||
|
if parsed_email:
|
||||||
|
new_emails.append(parsed_email)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"メール解析エラー (ID: {email_id}): {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
return new_emails
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"新しいメールの取得に失敗しました: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _parse_email(self, email_message, uid: str) -> Optional[EmailMessage]:
|
||||||
|
"""メールメッセージを解析"""
|
||||||
|
try:
|
||||||
|
# ヘッダー情報を取得
|
||||||
|
subject = self._decode_header(email_message.get('Subject', ''))
|
||||||
|
sender = self._decode_header(email_message.get('From', ''))
|
||||||
|
date = email_message.get('Date', '')
|
||||||
|
|
||||||
|
# メール本文を取得
|
||||||
|
body = self._get_email_body(email_message)
|
||||||
|
|
||||||
|
return EmailMessage(
|
||||||
|
subject=subject,
|
||||||
|
sender=sender,
|
||||||
|
date=date,
|
||||||
|
body=body,
|
||||||
|
uid=uid
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"メール解析エラー: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _decode_header(self, header: str) -> str:
|
||||||
|
"""メールヘッダーをデコード"""
|
||||||
|
if not header:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
try:
|
||||||
|
decoded_header = email.header.decode_header(header)
|
||||||
|
result = ''
|
||||||
|
|
||||||
|
for text, encoding in decoded_header:
|
||||||
|
if isinstance(text, bytes):
|
||||||
|
if encoding:
|
||||||
|
text = text.decode(encoding, errors='ignore')
|
||||||
|
else:
|
||||||
|
text = text.decode('utf-8', errors='ignore')
|
||||||
|
result += text
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"ヘッダーデコードエラー: {str(e)}")
|
||||||
|
return str(header)
|
||||||
|
|
||||||
|
def _get_email_body(self, email_message) -> str:
|
||||||
|
"""メール本文を取得"""
|
||||||
|
body = ''
|
||||||
|
|
||||||
|
try:
|
||||||
|
if email_message.is_multipart():
|
||||||
|
for part in email_message.walk():
|
||||||
|
content_type = part.get_content_type()
|
||||||
|
content_disposition = str(part.get('Content-Disposition'))
|
||||||
|
|
||||||
|
if content_type == 'text/plain' and 'attachment' not in content_disposition:
|
||||||
|
charset = part.get_content_charset() or 'utf-8'
|
||||||
|
body_bytes = part.get_payload(decode=True)
|
||||||
|
if body_bytes:
|
||||||
|
body = body_bytes.decode(charset, errors='ignore')
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
charset = email_message.get_content_charset() or 'utf-8'
|
||||||
|
body_bytes = email_message.get_payload(decode=True)
|
||||||
|
if body_bytes:
|
||||||
|
body = body_bytes.decode(charset, errors='ignore')
|
||||||
|
|
||||||
|
# 本文が長い場合は切り詰める(Discord制限対策)
|
||||||
|
if len(body) > 1900:
|
||||||
|
body = body[:1900] + '\n...(本文が切り詰められました)'
|
||||||
|
|
||||||
|
return body
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"メール本文取得エラー: {str(e)}")
|
||||||
|
return '本文の取得に失敗しました'
|
||||||
|
|
||||||
|
def send_to_discord(self, email_msg: EmailMessage) -> bool:
|
||||||
|
"""DiscordにWebhookでメールを送信"""
|
||||||
|
try:
|
||||||
|
# Discord Embed形式でメッセージを構築
|
||||||
|
embed = {
|
||||||
|
"title": "📧 新しいメール",
|
||||||
|
"color": 0x3498db,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "件名",
|
||||||
|
"value": email_msg.subject or "件名なし",
|
||||||
|
"inline": False
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "送信者",
|
||||||
|
"value": email_msg.sender or "送信者不明",
|
||||||
|
"inline": True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "日時",
|
||||||
|
"value": email_msg.date or "日時不明",
|
||||||
|
"inline": True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "本文",
|
||||||
|
"value": email_msg.body[:1000] if email_msg.body else "本文なし",
|
||||||
|
"inline": False
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timestamp": datetime.utcnow().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
webhook_data = {
|
||||||
|
"username": "Email Monitor",
|
||||||
|
"avatar_url": "https://cdn-icons-png.flaticon.com/512/732/732200.png",
|
||||||
|
"embeds": [embed]
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
self.discord_webhook_url,
|
||||||
|
json=webhook_data,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 204:
|
||||||
|
logger.info(f"Discordにメールを送信しました: {email_msg.subject}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error(f"Discord送信エラー: {response.status_code} - {response.text}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Discord送信エラー: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def start_monitoring(self):
|
||||||
|
"""メール監視を開始"""
|
||||||
|
logger.info("メール監視を開始します...")
|
||||||
|
self.running = True
|
||||||
|
|
||||||
|
while self.running:
|
||||||
|
try:
|
||||||
|
# メールサーバーに接続
|
||||||
|
if not self.connect_to_email():
|
||||||
|
logger.warning(f"{self.check_interval}秒後に再試行します...")
|
||||||
|
time.sleep(self.check_interval)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 新しいメールをチェック
|
||||||
|
new_emails = self.get_new_emails()
|
||||||
|
|
||||||
|
if new_emails:
|
||||||
|
logger.info(f"{len(new_emails)}件の新しいメールが見つかりました")
|
||||||
|
|
||||||
|
for email_msg in new_emails:
|
||||||
|
if self.send_to_discord(email_msg):
|
||||||
|
logger.info(f"処理完了: {email_msg.subject}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"Discord送信失敗: {email_msg.subject}")
|
||||||
|
else:
|
||||||
|
logger.debug("新しいメールはありません")
|
||||||
|
|
||||||
|
# メールサーバーから切断
|
||||||
|
self.disconnect_from_email()
|
||||||
|
|
||||||
|
# 次のチェックまで待機
|
||||||
|
logger.debug(f"{self.check_interval}秒後に次のチェックを実行します")
|
||||||
|
time.sleep(self.check_interval)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("キーボード割り込みを受信しました")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"監視ループでエラーが発生しました: {str(e)}")
|
||||||
|
self.disconnect_from_email()
|
||||||
|
time.sleep(self.check_interval)
|
||||||
|
|
||||||
|
logger.info("メール監視を停止しました")
|
||||||
|
|
||||||
|
def stop_monitoring(self):
|
||||||
|
"""メール監視を停止"""
|
||||||
|
self.running = False
|
||||||
|
self.disconnect_from_email()
|
||||||
|
|
||||||
|
def signal_handler(signum, frame, monitor):
|
||||||
|
"""シグナルハンドラー"""
|
||||||
|
logger.info(f"シグナル {signum} を受信しました。アプリケーションを終了します...")
|
||||||
|
monitor.stop_monitoring()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""メイン関数"""
|
||||||
|
logger.info("Email to Discord Webhook Forwarder を開始します")
|
||||||
|
logger.info(f"チェック間隔: {os.getenv('CHECK_INTERVAL', '60')}秒")
|
||||||
|
logger.info(f"メールボックス: {os.getenv('MAILBOX', 'INBOX')}")
|
||||||
|
logger.info(f"IMAPサーバー: {os.getenv('IMAP_SERVER', 'imap.gmail.com')}")
|
||||||
|
|
||||||
|
# メール監視インスタンスを作成
|
||||||
|
monitor = EmailMonitor()
|
||||||
|
|
||||||
|
# シグナルハンドラーを設定
|
||||||
|
signal.signal(signal.SIGINT, lambda s, f: signal_handler(s, f, monitor))
|
||||||
|
signal.signal(signal.SIGTERM, lambda s, f: signal_handler(s, f, monitor))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 監視開始
|
||||||
|
monitor.start_monitoring()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"アプリケーションエラー: {str(e)}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
38
docker-compose.yml
Normal file
38
docker-compose.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
email-to-discord:
|
||||||
|
build: .
|
||||||
|
container_name: email-to-discord-monitor
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
# メールサーバー設定
|
||||||
|
- IMAP_SERVER=imap.gmail.com
|
||||||
|
- IMAP_PORT=993
|
||||||
|
- USE_SSL=true
|
||||||
|
- MAILBOX=INBOX
|
||||||
|
|
||||||
|
# メール認証情報(実際の値に変更してください)
|
||||||
|
- EMAIL_USER=your-email@gmail.com
|
||||||
|
- EMAIL_PASSWORD=your-app-password
|
||||||
|
|
||||||
|
# Discord Webhook URL(実際のURLに変更してください)
|
||||||
|
- DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL
|
||||||
|
|
||||||
|
# 監視設定
|
||||||
|
- CHECK_INTERVAL=60 # チェック間隔(秒)
|
||||||
|
|
||||||
|
# ログの設定
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# リソース制限
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 256M
|
||||||
|
reservations:
|
||||||
|
memory: 128M
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
requests==2.31.0
|
||||||
|
python-dotenv==1.0.0
|
||||||
343
start.ps1
Normal file
343
start.ps1
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
# Email to Discord Monitor - Windows起動スクリプト
|
||||||
|
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]$Action = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
# 色付きメッセージ用の関数
|
||||||
|
function Write-ColoredText {
|
||||||
|
param(
|
||||||
|
[string]$Text,
|
||||||
|
[string]$Color = "White"
|
||||||
|
)
|
||||||
|
Write-Host $Text -ForegroundColor $Color
|
||||||
|
}
|
||||||
|
|
||||||
|
function Log-Info {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-ColoredText "[INFO] $Message" "Green"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Log-Warn {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-ColoredText "[WARN] $Message" "Yellow"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Log-Error {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-ColoredText "[ERROR] $Message" "Red"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Log-Blue {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-ColoredText "[INFO] $Message" "Blue"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ヘルプメッセージ
|
||||||
|
function Show-Help {
|
||||||
|
Write-Host "Email to Discord Monitor - Windows起動スクリプト"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "使用方法:"
|
||||||
|
Write-Host " .\start.ps1 [オプション]"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "オプション:"
|
||||||
|
Write-Host " build Dockerイメージをビルド"
|
||||||
|
Write-Host " start コンテナを起動"
|
||||||
|
Write-Host " stop コンテナを停止"
|
||||||
|
Write-Host " restart コンテナを再起動"
|
||||||
|
Write-Host " logs ログを表示"
|
||||||
|
Write-Host " logs-f ログをリアルタイムで表示"
|
||||||
|
Write-Host " status コンテナの状態を確認"
|
||||||
|
Write-Host " clean 停止済みコンテナとイメージを削除"
|
||||||
|
Write-Host " setup 初期セットアップ(.envファイル作成)"
|
||||||
|
Write-Host " compose-up docker-compose で起動"
|
||||||
|
Write-Host " compose-down docker-compose で停止"
|
||||||
|
Write-Host " python Python直接実行"
|
||||||
|
Write-Host " help このヘルプを表示"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "例:"
|
||||||
|
Write-Host " .\start.ps1 setup # 初期セットアップ"
|
||||||
|
Write-Host " .\start.ps1 build # イメージをビルド"
|
||||||
|
Write-Host " .\start.ps1 start # コンテナを起動"
|
||||||
|
Write-Host " .\start.ps1 logs-f # ログをリアルタイム表示"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 必要なツールの確認
|
||||||
|
function Test-Requirements {
|
||||||
|
$missingTools = @()
|
||||||
|
|
||||||
|
if (!(Get-Command docker -ErrorAction SilentlyContinue)) {
|
||||||
|
$missingTools += "docker"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(Get-Command docker-compose -ErrorAction SilentlyContinue)) {
|
||||||
|
$missingTools += "docker-compose"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($missingTools.Count -gt 0) {
|
||||||
|
Log-Error "以下のツールがインストールされていません: $($missingTools -join ', ')"
|
||||||
|
Log-Info "Dockerのインストール: https://docs.docker.com/desktop/windows/"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# .envファイルのセットアップ
|
||||||
|
function Setup-Environment {
|
||||||
|
Log-Info "初期セットアップを開始します..."
|
||||||
|
|
||||||
|
if (Test-Path ".env") {
|
||||||
|
Log-Warn ".envファイルが既に存在します"
|
||||||
|
$response = Read-Host "上書きしますか? (y/N)"
|
||||||
|
if ($response -ne "y" -and $response -ne "Y") {
|
||||||
|
Log-Info "セットアップをキャンセルしました"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(Test-Path ".env.example")) {
|
||||||
|
Log-Error ".env.exampleファイルが見つかりません"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Copy-Item ".env.example" ".env"
|
||||||
|
Log-Info ".envファイルを作成しました"
|
||||||
|
|
||||||
|
Log-Blue "以下の設定を.envファイルに入力してください:"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "1. EMAIL_USER=your-email@gmail.com"
|
||||||
|
Write-Host "2. EMAIL_PASSWORD=your-app-password"
|
||||||
|
Write-Host "3. DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL"
|
||||||
|
Write-Host ""
|
||||||
|
Log-Warn "設定完了後、'.\start.ps1 build' でイメージをビルドしてください"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Dockerイメージをビルド
|
||||||
|
function Build-Image {
|
||||||
|
Log-Info "Dockerイメージをビルドしています..."
|
||||||
|
docker build -t email-to-discord .
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Log-Info "ビルドが完了しました"
|
||||||
|
} else {
|
||||||
|
Log-Error "ビルドに失敗しました"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# コンテナを起動
|
||||||
|
function Start-Container {
|
||||||
|
Log-Info "コンテナを起動しています..."
|
||||||
|
|
||||||
|
# 既存のコンテナを確認・削除
|
||||||
|
$existingContainer = docker ps -a --format "table {{.Names}}" | Select-String "^email-monitor$"
|
||||||
|
if ($existingContainer) {
|
||||||
|
Log-Warn "既存のコンテナを削除します..."
|
||||||
|
docker rm -f email-monitor 2>$null
|
||||||
|
}
|
||||||
|
|
||||||
|
# .envファイルの確認
|
||||||
|
if (!(Test-Path ".env")) {
|
||||||
|
Log-Error ".envファイルが見つかりません"
|
||||||
|
Log-Info "'.\start.ps1 setup' を実行して設定を行ってください"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# コンテナを起動
|
||||||
|
docker run -d `
|
||||||
|
--name email-monitor `
|
||||||
|
--restart unless-stopped `
|
||||||
|
--env-file .env `
|
||||||
|
email-to-discord
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Log-Info "コンテナが起動しました"
|
||||||
|
Log-Blue "ログを確認するには: .\start.ps1 logs-f"
|
||||||
|
} else {
|
||||||
|
Log-Error "コンテナの起動に失敗しました"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# コンテナを停止
|
||||||
|
function Stop-Container {
|
||||||
|
Log-Info "コンテナを停止しています..."
|
||||||
|
docker stop email-monitor 2>$null
|
||||||
|
docker rm email-monitor 2>$null
|
||||||
|
Log-Info "コンテナを停止しました"
|
||||||
|
}
|
||||||
|
|
||||||
|
# コンテナを再起動
|
||||||
|
function Restart-Container {
|
||||||
|
Log-Info "コンテナを再起動しています..."
|
||||||
|
Stop-Container
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
Start-Container
|
||||||
|
}
|
||||||
|
|
||||||
|
# ログを表示
|
||||||
|
function Show-Logs {
|
||||||
|
param([bool]$Follow = $false)
|
||||||
|
|
||||||
|
if ($Follow) {
|
||||||
|
Log-Info "ログをリアルタイムで表示します (Ctrl+C で終了)"
|
||||||
|
docker logs -f email-monitor
|
||||||
|
} else {
|
||||||
|
Log-Info "ログを表示します"
|
||||||
|
docker logs email-monitor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# コンテナの状態を確認
|
||||||
|
function Get-Status {
|
||||||
|
Log-Info "コンテナの状態を確認しています..."
|
||||||
|
|
||||||
|
$runningContainer = docker ps --format "table {{.Names}}" | Select-String "email-monitor"
|
||||||
|
if ($runningContainer) {
|
||||||
|
Log-Info "コンテナは実行中です"
|
||||||
|
docker ps --format "table {{.Names}}`t{{.Status}}`t{{.CreatedAt}}" | Select-String "email-monitor"
|
||||||
|
} else {
|
||||||
|
Log-Warn "コンテナは実行されていません"
|
||||||
|
$stoppedContainer = docker ps -a --format "table {{.Names}}" | Select-String "email-monitor"
|
||||||
|
if ($stoppedContainer) {
|
||||||
|
Log-Info "停止済みコンテナが存在します"
|
||||||
|
docker ps -a --format "table {{.Names}}`t{{.Status}}`t{{.CreatedAt}}" | Select-String "email-monitor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# クリーンアップ
|
||||||
|
function Invoke-Cleanup {
|
||||||
|
Log-Info "クリーンアップを実行しています..."
|
||||||
|
|
||||||
|
# コンテナを停止・削除
|
||||||
|
docker stop email-monitor 2>$null
|
||||||
|
docker rm email-monitor 2>$null
|
||||||
|
|
||||||
|
# イメージを削除
|
||||||
|
docker rmi email-to-discord 2>$null
|
||||||
|
|
||||||
|
# 未使用のリソースを削除
|
||||||
|
docker system prune -f
|
||||||
|
|
||||||
|
Log-Info "クリーンアップが完了しました"
|
||||||
|
}
|
||||||
|
|
||||||
|
# docker-compose で起動
|
||||||
|
function Start-Compose {
|
||||||
|
Log-Info "docker-compose でサービスを起動しています..."
|
||||||
|
docker-compose up -d
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Log-Info "サービスが起動しました"
|
||||||
|
Log-Blue "ログを確認するには: .\start.ps1 compose-logs"
|
||||||
|
} else {
|
||||||
|
Log-Error "サービスの起動に失敗しました"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# docker-compose で停止
|
||||||
|
function Stop-Compose {
|
||||||
|
Log-Info "docker-compose でサービスを停止しています..."
|
||||||
|
docker-compose down
|
||||||
|
Log-Info "サービスを停止しました"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Python直接実行
|
||||||
|
function Start-Python {
|
||||||
|
Log-Info "Python環境で直接実行します..."
|
||||||
|
|
||||||
|
# Python3の確認
|
||||||
|
if (!(Get-Command python -ErrorAction SilentlyContinue)) {
|
||||||
|
Log-Error "Pythonがインストールされていません"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# pipの確認
|
||||||
|
if (!(Get-Command pip -ErrorAction SilentlyContinue)) {
|
||||||
|
Log-Error "pipがインストールされていません"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 依存関係のインストール
|
||||||
|
Log-Info "依存関係をインストールしています..."
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# .envファイルの確認
|
||||||
|
if (!(Test-Path ".env")) {
|
||||||
|
Log-Error ".envファイルが見つかりません"
|
||||||
|
Log-Info "'.\start.ps1 setup' を実行して設定を行ってください"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 環境変数を読み込んで実行
|
||||||
|
Log-Info "アプリケーションを起動しています..."
|
||||||
|
|
||||||
|
# .envファイルから環境変数を読み込み
|
||||||
|
Get-Content ".env" | ForEach-Object {
|
||||||
|
if ($_ -match "^([^=]+)=(.*)$") {
|
||||||
|
[Environment]::SetEnvironmentVariable($matches[1], $matches[2], "Process")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
python app.py
|
||||||
|
}
|
||||||
|
|
||||||
|
# メイン処理
|
||||||
|
switch ($Action.ToLower()) {
|
||||||
|
"build" {
|
||||||
|
Test-Requirements
|
||||||
|
Build-Image
|
||||||
|
}
|
||||||
|
"start" {
|
||||||
|
Test-Requirements
|
||||||
|
Start-Container
|
||||||
|
}
|
||||||
|
"stop" {
|
||||||
|
Stop-Container
|
||||||
|
}
|
||||||
|
"restart" {
|
||||||
|
Test-Requirements
|
||||||
|
Restart-Container
|
||||||
|
}
|
||||||
|
"logs" {
|
||||||
|
Show-Logs
|
||||||
|
}
|
||||||
|
"logs-f" {
|
||||||
|
Show-Logs -Follow $true
|
||||||
|
}
|
||||||
|
"status" {
|
||||||
|
Get-Status
|
||||||
|
}
|
||||||
|
"clean" {
|
||||||
|
Invoke-Cleanup
|
||||||
|
}
|
||||||
|
"setup" {
|
||||||
|
Setup-Environment
|
||||||
|
}
|
||||||
|
"compose-up" {
|
||||||
|
Test-Requirements
|
||||||
|
Start-Compose
|
||||||
|
}
|
||||||
|
"compose-down" {
|
||||||
|
Stop-Compose
|
||||||
|
}
|
||||||
|
"compose-logs" {
|
||||||
|
docker-compose logs -f
|
||||||
|
}
|
||||||
|
"python" {
|
||||||
|
Start-Python
|
||||||
|
}
|
||||||
|
"help" {
|
||||||
|
Show-Help
|
||||||
|
}
|
||||||
|
"" {
|
||||||
|
Log-Error "引数が指定されていません"
|
||||||
|
Write-Host ""
|
||||||
|
Show-Help
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
Log-Error "不明なオプション: $Action"
|
||||||
|
Write-Host ""
|
||||||
|
Show-Help
|
||||||
|
}
|
||||||
|
}
|
||||||
344
start.sh
Normal file
344
start.sh
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Email to Discord Monitor - Linux起動スクリプト
|
||||||
|
|
||||||
|
set -e # エラー時に終了
|
||||||
|
|
||||||
|
# 色付きメッセージ用の定数
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# ログ関数
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_blue() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ヘルプメッセージ
|
||||||
|
show_help() {
|
||||||
|
echo "Email to Discord Monitor - 起動スクリプト"
|
||||||
|
echo ""
|
||||||
|
echo "使用方法:"
|
||||||
|
echo " $0 [オプション]"
|
||||||
|
echo ""
|
||||||
|
echo "オプション:"
|
||||||
|
echo " build Dockerイメージをビルド"
|
||||||
|
echo " start コンテナを起動"
|
||||||
|
echo " stop コンテナを停止"
|
||||||
|
echo " restart コンテナを再起動"
|
||||||
|
echo " logs ログを表示"
|
||||||
|
echo " logs-f ログをリアルタイムで表示"
|
||||||
|
echo " status コンテナの状態を確認"
|
||||||
|
echo " clean 停止済みコンテナとイメージを削除"
|
||||||
|
echo " setup 初期セットアップ(.envファイル作成)"
|
||||||
|
echo " compose-up docker-compose で起動"
|
||||||
|
echo " compose-down docker-compose で停止"
|
||||||
|
echo " python Python直接実行"
|
||||||
|
echo " help このヘルプを表示"
|
||||||
|
echo ""
|
||||||
|
echo "例:"
|
||||||
|
echo " $0 setup # 初期セットアップ"
|
||||||
|
echo " $0 build # イメージをビルド"
|
||||||
|
echo " $0 start # コンテナを起動"
|
||||||
|
echo " $0 logs-f # ログをリアルタイム表示"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 必要なツールの確認
|
||||||
|
check_requirements() {
|
||||||
|
local missing_tools=()
|
||||||
|
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
missing_tools+=("docker")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
|
||||||
|
missing_tools+=("docker-compose")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#missing_tools[@]} -ne 0 ]; then
|
||||||
|
log_error "以下のツールがインストールされていません: ${missing_tools[*]}"
|
||||||
|
log_info "Dockerのインストール: https://docs.docker.com/get-docker/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# .envファイルのセットアップ
|
||||||
|
setup_env() {
|
||||||
|
log_info "初期セットアップを開始します..."
|
||||||
|
|
||||||
|
if [ -f ".env" ]; then
|
||||||
|
log_warn ".envファイルが既に存在します"
|
||||||
|
read -p "上書きしますか? (y/N): " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
log_info "セットアップをキャンセルしました"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f ".env.example" ]; then
|
||||||
|
log_error ".env.exampleファイルが見つかりません"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp .env.example .env
|
||||||
|
log_info ".envファイルを作成しました"
|
||||||
|
|
||||||
|
log_blue "以下の設定を.envファイルに入力してください:"
|
||||||
|
echo ""
|
||||||
|
echo "1. EMAIL_USER=your-email@gmail.com"
|
||||||
|
echo "2. EMAIL_PASSWORD=your-app-password"
|
||||||
|
echo "3. DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK_URL"
|
||||||
|
echo ""
|
||||||
|
log_warn "設定完了後、'$0 build' でイメージをビルドしてください"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Dockerイメージをビルド
|
||||||
|
build_image() {
|
||||||
|
log_info "Dockerイメージをビルドしています..."
|
||||||
|
docker build -t email-to-discord .
|
||||||
|
log_info "ビルドが完了しました"
|
||||||
|
}
|
||||||
|
|
||||||
|
# コンテナを起動
|
||||||
|
start_container() {
|
||||||
|
log_info "コンテナを起動しています..."
|
||||||
|
|
||||||
|
# 既存のコンテナを確認
|
||||||
|
if docker ps -a --format "table {{.Names}}" | grep -q "^email-monitor$"; then
|
||||||
|
log_warn "既存のコンテナを削除します..."
|
||||||
|
docker rm -f email-monitor 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# .envファイルの確認
|
||||||
|
if [ ! -f ".env" ]; then
|
||||||
|
log_error ".envファイルが見つかりません"
|
||||||
|
log_info "'$0 setup' を実行して設定を行ってください"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 環境変数を読み込み
|
||||||
|
source .env
|
||||||
|
|
||||||
|
# 必須変数の確認
|
||||||
|
if [ -z "$EMAIL_USER" ] || [ -z "$EMAIL_PASSWORD" ] || [ -z "$DISCORD_WEBHOOK_URL" ]; then
|
||||||
|
log_error "必須の環境変数が設定されていません"
|
||||||
|
log_info ".envファイルを確認してください"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# コンテナを起動
|
||||||
|
docker run -d \
|
||||||
|
--name email-monitor \
|
||||||
|
--restart unless-stopped \
|
||||||
|
--env-file .env \
|
||||||
|
email-to-discord
|
||||||
|
|
||||||
|
log_info "コンテナが起動しました"
|
||||||
|
log_blue "ログを確認するには: $0 logs-f"
|
||||||
|
}
|
||||||
|
|
||||||
|
# コンテナを停止
|
||||||
|
stop_container() {
|
||||||
|
log_info "コンテナを停止しています..."
|
||||||
|
docker stop email-monitor 2>/dev/null || log_warn "コンテナが実行されていません"
|
||||||
|
docker rm email-monitor 2>/dev/null || log_warn "削除するコンテナがありません"
|
||||||
|
log_info "コンテナを停止しました"
|
||||||
|
}
|
||||||
|
|
||||||
|
# コンテナを再起動
|
||||||
|
restart_container() {
|
||||||
|
log_info "コンテナを再起動しています..."
|
||||||
|
stop_container
|
||||||
|
sleep 2
|
||||||
|
start_container
|
||||||
|
}
|
||||||
|
|
||||||
|
# ログを表示
|
||||||
|
show_logs() {
|
||||||
|
if [ "$1" = "-f" ]; then
|
||||||
|
log_info "ログをリアルタイムで表示します (Ctrl+C で終了)"
|
||||||
|
docker logs -f email-monitor
|
||||||
|
else
|
||||||
|
log_info "ログを表示します"
|
||||||
|
docker logs email-monitor
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# コンテナの状態を確認
|
||||||
|
check_status() {
|
||||||
|
log_info "コンテナの状態を確認しています..."
|
||||||
|
|
||||||
|
if docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -q "email-monitor"; then
|
||||||
|
log_info "コンテナは実行中です"
|
||||||
|
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.CreatedAt}}" | grep email-monitor
|
||||||
|
else
|
||||||
|
log_warn "コンテナは実行されていません"
|
||||||
|
if docker ps -a --format "table {{.Names}}" | grep -q "email-monitor"; then
|
||||||
|
log_info "停止済みコンテナが存在します"
|
||||||
|
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.CreatedAt}}" | grep email-monitor
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# クリーンアップ
|
||||||
|
cleanup() {
|
||||||
|
log_info "クリーンアップを実行しています..."
|
||||||
|
|
||||||
|
# コンテナを停止・削除
|
||||||
|
docker stop email-monitor 2>/dev/null || true
|
||||||
|
docker rm email-monitor 2>/dev/null || true
|
||||||
|
|
||||||
|
# イメージを削除
|
||||||
|
docker rmi email-to-discord 2>/dev/null || log_warn "削除するイメージがありません"
|
||||||
|
|
||||||
|
# 未使用のリソースを削除
|
||||||
|
docker system prune -f
|
||||||
|
|
||||||
|
log_info "クリーンアップが完了しました"
|
||||||
|
}
|
||||||
|
|
||||||
|
# docker-compose で起動
|
||||||
|
compose_up() {
|
||||||
|
log_info "docker-compose でサービスを起動しています..."
|
||||||
|
|
||||||
|
if command -v docker-compose &> /dev/null; then
|
||||||
|
docker-compose up -d
|
||||||
|
else
|
||||||
|
docker compose up -d
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "サービスが起動しました"
|
||||||
|
log_blue "ログを確認するには: $0 compose-logs"
|
||||||
|
}
|
||||||
|
|
||||||
|
# docker-compose で停止
|
||||||
|
compose_down() {
|
||||||
|
log_info "docker-compose でサービスを停止しています..."
|
||||||
|
|
||||||
|
if command -v docker-compose &> /dev/null; then
|
||||||
|
docker-compose down
|
||||||
|
else
|
||||||
|
docker compose down
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "サービスを停止しました"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Python直接実行
|
||||||
|
run_python() {
|
||||||
|
log_info "Python環境で直接実行します..."
|
||||||
|
|
||||||
|
# Python3の確認
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
log_error "Python3がインストールされていません"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# pipの確認
|
||||||
|
if ! command -v pip3 &> /dev/null; then
|
||||||
|
log_error "pip3がインストールされていません"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 依存関係のインストール
|
||||||
|
log_info "依存関係をインストールしています..."
|
||||||
|
pip3 install -r requirements.txt
|
||||||
|
|
||||||
|
# .envファイルの確認
|
||||||
|
if [ ! -f ".env" ]; then
|
||||||
|
log_error ".envファイルが見つかりません"
|
||||||
|
log_info "'$0 setup' を実行して設定を行ってください"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 環境変数を読み込んで実行
|
||||||
|
log_info "アプリケーションを起動しています..."
|
||||||
|
export $(cat .env | xargs)
|
||||||
|
python3 app.py
|
||||||
|
}
|
||||||
|
|
||||||
|
# メイン処理
|
||||||
|
main() {
|
||||||
|
case "$1" in
|
||||||
|
"build")
|
||||||
|
check_requirements
|
||||||
|
build_image
|
||||||
|
;;
|
||||||
|
"start")
|
||||||
|
check_requirements
|
||||||
|
start_container
|
||||||
|
;;
|
||||||
|
"stop")
|
||||||
|
stop_container
|
||||||
|
;;
|
||||||
|
"restart")
|
||||||
|
check_requirements
|
||||||
|
restart_container
|
||||||
|
;;
|
||||||
|
"logs")
|
||||||
|
show_logs
|
||||||
|
;;
|
||||||
|
"logs-f")
|
||||||
|
show_logs -f
|
||||||
|
;;
|
||||||
|
"status")
|
||||||
|
check_status
|
||||||
|
;;
|
||||||
|
"clean")
|
||||||
|
cleanup
|
||||||
|
;;
|
||||||
|
"setup")
|
||||||
|
setup_env
|
||||||
|
;;
|
||||||
|
"compose-up")
|
||||||
|
check_requirements
|
||||||
|
compose_up
|
||||||
|
;;
|
||||||
|
"compose-down")
|
||||||
|
compose_down
|
||||||
|
;;
|
||||||
|
"compose-logs")
|
||||||
|
if command -v docker-compose &> /dev/null; then
|
||||||
|
docker-compose logs -f
|
||||||
|
else
|
||||||
|
docker compose logs -f
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"python")
|
||||||
|
run_python
|
||||||
|
;;
|
||||||
|
"help"|"--help"|"-h")
|
||||||
|
show_help
|
||||||
|
;;
|
||||||
|
"")
|
||||||
|
log_error "引数が指定されていません"
|
||||||
|
echo ""
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "不明なオプション: $1"
|
||||||
|
echo ""
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# スクリプト実行
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user