Rocket.Chat

コミュニケーションオープンソースセルフホストBot開発APIWebhookDocker

コミュニケーションツール

Rocket.Chat

概要

Rocket.Chatは、オープンソースの企業向けチームコラボレーションプラットフォームです。Slack風のモダンなUIを持ちながら、完全なセルフホスト環境での運用が可能で、データ主権を重視する企業に最適です。豊富なAPI、Webhook統合機能、Apps-Engine によるカスタムアプリ開発、Docker対応により高度なカスタマイズが可能です。

詳細

Rocket.Chat(ロケットチャット)は、2015年にリリースされたオープンソースのチームコラボレーションプラットフォームです。JavaScript(Meteor フレームワーク)で開発され、セルフホストできるSlackの代替として企業・開発チームでの採用が拡大しています。特にセキュリティとプライバシーを重視する組織での利用に適しています。

2024-2025年には、Node.js 20.x・Meteor 3.0への大幅アップグレード、MongoDB 7.0サポート追加、E2EE(エンドツーエンド暗号化)の強化、Apps-Engine による新しいボット・アプリ開発フレームワークの導入などが行われました。従来のBots統合機能は非推奨となり、Apps-Engineベースの開発が推奨されています。

Rocket.Chat APIは REST API、Realtime API、Livechat APIを提供し、包括的なWebhook機能とカスタムスクリプト実行により外部システムとの高度な連携が可能です。Docker & Docker Composeによる簡単なデプロイメント、豊富なプラグインエコシステム、MongoDB統合により、スケーラブルで拡張性の高いチャットプラットフォームを構築できます。

メリット・デメリット

メリット

  • 完全オープンソース: ソースコード公開、自由なカスタマイズ
  • セルフホスト対応: 完全な自社環境運用、データ主権確保
  • 豊富なAPI: REST API、Realtime API、Livechat API
  • Apps-Engine: 強力なアプリ・ボット開発フレームワーク
  • E2EE対応: エンドツーエンド暗号化によるセキュリティ
  • Webhook機能: 包括的なWebhook統合とカスタムスクリプト
  • Docker対応: 簡単デプロイメントとコンテナ化
  • コスト効率: セルフホスト環境での無制限利用

デメリット

  • 運用コスト: サーバー構築・維持管理の必要性
  • 技術要求: セットアップとカスタマイズの技術的要求
  • UI/UX: Slackと比較した使い勝手の課題
  • プラグインエコシステム: サードパーティ統合の限定性
  • モバイル体験: ネイティブアプリの機能制限
  • 学習コスト: Apps-Engine開発の学習コスト

主要リンク

書き方の例

Rocket.Chat Apps-Engine アプリ開発

// apps/sample-app/SampleApp.ts - Apps-Engine アプリのメイン
import {
    IConfigurationExtend,
    IEnvironmentRead,
    ILogger,
    IRead,
    IModify,
    IPersistence,
    IHttp,
} from '@rocket.chat/apps-engine/definition/accessors';
import { App } from '@rocket.chat/apps-engine/definition/App';
import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';
import {
    IMessage,
    IPreMessageSentPrevent,
    IPostMessageSent,
} from '@rocket.chat/apps-engine/definition/messages';
import { ISlashCommand, SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands';

export class SampleApp extends App implements IPreMessageSentPrevent, IPostMessageSent {
    constructor(info: IAppInfo, logger: ILogger) {
        super(info, logger);
    }

    // アプリ初期化時の設定
    public async initialize(configurationExtend: IConfigurationExtend, environmentRead: IEnvironmentRead): Promise<void> {
        // スラッシュコマンドの登録
        await configurationExtend.slashCommands.provideSlashCommand(new SampleSlashCommand());
        
        // API エンドポイントの登録
        await configurationExtend.api.provideApi({
            visibility: ApiVisibility.PUBLIC,
            security: ApiSecurity.UNSECURE,
            endpoints: [new SampleEndpoint(this)],
        });
    }

    // メッセージ送信前の処理
    public async executePreMessageSentPrevent(
        message: IMessage,
        read: IRead,
        http: IHttp,
        persistence: IPersistence,
    ): Promise<boolean> {
        // 特定のキーワードをブロック
        if (message.text && message.text.includes('blocked-word')) {
            return true; // メッセージをブロック
        }
        return false; // メッセージを許可
    }

    // メッセージ送信後の処理
    public async executePostMessageSent(
        message: IMessage,
        read: IRead,
        http: IHttp,
        persistence: IPersistence,
        modify: IModify,
    ): Promise<void> {
        // 自動応答の実装
        if (message.text && message.text.includes('help')) {
            const messageBuilder = modify.getCreator().startMessage()
                .setSender(await read.getUserReader().getById('rocket.cat'))
                .setRoom(message.room)
                .setText('🚀 ヘルプ情報をお送りします!');
            
            await modify.getCreator().finish(messageBuilder);
        }

        // 外部API連携
        if (message.text && message.text.includes('weather')) {
            await this.sendWeatherInfo(message, modify, http, read);
        }
    }

    private async sendWeatherInfo(
        message: IMessage,
        modify: IModify,
        http: IHttp,
        read: IRead,
    ): Promise<void> {
        try {
            // 外部weather APIの呼び出し
            const response = await http.get('https://api.openweathermap.org/data/2.5/weather', {
                params: {
                    q: 'Tokyo',
                    appid: 'your-api-key',
                    units: 'metric',
                },
            });

            if (response.statusCode === 200 && response.data) {
                const weather = response.data;
                const messageBuilder = modify.getCreator().startMessage()
                    .setSender(await read.getUserReader().getById('rocket.cat'))
                    .setRoom(message.room)
                    .setText(`🌤️ 東京の天気: ${weather.weather[0].description}, 気温: ${weather.main.temp}°C`);

                await modify.getCreator().finish(messageBuilder);
            }
        } catch (error) {
            console.error('Weather API error:', error);
        }
    }
}

// スラッシュコマンドの実装
class SampleSlashCommand implements ISlashCommand {
    public command = 'sample';
    public i18nParamsExample = 'sample_params';
    public i18nDescription = 'sample_description';
    public providesPreview = false;

    public async executor(context: SlashCommandContext, read: IRead, modify: IModify): Promise<void> {
        const messageBuilder = modify.getCreator().startMessage()
            .setSender(context.getSender())
            .setRoom(context.getRoom())
            .setText('🚀 Sample command executed!');

        await modify.getCreator().finish(messageBuilder);
    }
}

Webhook統合スクリプト

// Incoming Webhook スクリプト - GitHub統合例
class Script {
    process_incoming_request({ request }) {
        console.log('GitHub Webhook received:', request.content);
        
        const eventType = request.headers['x-github-event'];
        
        switch (eventType) {
            case 'push':
                return this.handlePushEvent(request.content);
            case 'pull_request':
                return this.handlePullRequestEvent(request.content);
            case 'issues':
                return this.handleIssueEvent(request.content);
            default:
                return this.handleUnknownEvent(eventType);
        }
    }

    handlePushEvent(payload) {
        const commits = payload.commits;
        const repository = payload.repository;
        const pusher = payload.pusher;

        let commitMessages = commits.map(commit => {
            const shortId = commit.id.substring(0, 7);
            return `• [${shortId}](${commit.url}) ${commit.message}`;
        }).join('\n');

        return {
            content: {
                username: 'GitHub',
                icon_url: 'https://github.com/fluidicon.png',
                text: `🚀 **Push to ${repository.full_name}**`,
                attachments: [{
                    color: '#28a745',
                    title: `${commits.length} commits pushed by ${pusher.name}`,
                    text: commitMessages,
                    title_link: payload.compare,
                    fields: [
                        {
                            title: 'Repository',
                            value: `[${repository.full_name}](${repository.html_url})`,
                            short: true
                        },
                        {
                            title: 'Branch',
                            value: payload.ref.split('/').pop(),
                            short: true
                        }
                    ]
                }]
            }
        };
    }

    handlePullRequestEvent(payload) {
        const pr = payload.pull_request;
        const action = payload.action;
        
        const colors = {
            opened: '#28a745',
            closed: pr.merged ? '#6f42c1' : '#d73a49',
            reopened: '#28a745'
        };

        const emoji = {
            opened: '📝',
            closed: pr.merged ? '🎉' : '❌',
            reopened: '🔄'
        };

        return {
            content: {
                username: 'GitHub',
                icon_url: payload.sender.avatar_url,
                text: `${emoji[action]} **Pull Request ${action}**`,
                attachments: [{
                    color: colors[action] || '#0366d6',
                    title: `#${pr.number} - ${pr.title}`,
                    title_link: pr.html_url,
                    text: pr.body,
                    author_name: pr.user.login,
                    author_link: pr.user.html_url,
                    author_icon: pr.user.avatar_url,
                    fields: [
                        {
                            title: 'Repository',
                            value: `[${payload.repository.full_name}](${payload.repository.html_url})`,
                            short: true
                        },
                        {
                            title: 'Branch',
                            value: `${pr.head.ref}${pr.base.ref}`,
                            short: true
                        }
                    ]
                }]
            }
        };
    }

    handleIssueEvent(payload) {
        const issue = payload.issue;
        const action = payload.action;

        return {
            content: {
                username: 'GitHub',
                icon_url: payload.sender.avatar_url,
                text: `🐛 **Issue ${action}**`,
                attachments: [{
                    color: action === 'closed' ? '#28a745' : '#d73a49',
                    title: `#${issue.number} - ${issue.title}`,
                    title_link: issue.html_url,
                    text: issue.body,
                    author_name: issue.user.login,
                    author_link: issue.user.html_url,
                    author_icon: issue.user.avatar_url
                }]
            }
        };
    }

    handleUnknownEvent(eventType) {
        return {
            content: {
                text: `⚠️ Unknown GitHub event: ${eventType}`
            }
        };
    }
}

Outgoing Webhook スクリプト

// Outgoing Webhook スクリプト - 外部サービス連携
class Script {
    prepare_outgoing_request({ request }) {
        const message = request.data.text;
        const command = message.split(' ')[0];

        switch (command) {
            case '/weather':
                return this.prepareWeatherRequest(request);
            case '/translate':
                return this.prepareTranslateRequest(request);
            case '/jira':
                return this.prepareJiraRequest(request);
            case '/deploy':
                return this.prepareDeployRequest(request);
            default:
                return this.prepareHelpResponse();
        }
    }

    prepareWeatherRequest(request) {
        const city = request.data.text.split(' ')[1] || 'Tokyo';
        
        return {
            url: 'https://api.openweathermap.org/data/2.5/weather',
            method: 'GET',
            params: {
                q: city,
                appid: 'your-weather-api-key',
                units: 'metric'
            }
        };
    }

    prepareTranslateRequest(request) {
        const text = request.data.text.replace('/translate', '').trim();
        
        return {
            url: 'https://api.mymemory.translated.net/get',
            method: 'GET',
            params: {
                q: text,
                langpair: 'ja|en'
            }
        };
    }

    prepareJiraRequest(request) {
        const issueKey = request.data.text.split(' ')[1];
        
        return {
            url: `https://your-domain.atlassian.net/rest/api/2/issue/${issueKey}`,
            method: 'GET',
            headers: {
                'Authorization': `Bearer ${process.env.JIRA_TOKEN}`,
                'Content-Type': 'application/json'
            }
        };
    }

    prepareDeployRequest(request) {
        const environment = request.data.text.split(' ')[1] || 'staging';
        
        return {
            url: 'https://api.your-deploy-service.com/deploy',
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${process.env.DEPLOY_TOKEN}`,
                'Content-Type': 'application/json'
            },
            data: {
                environment: environment,
                branch: 'main',
                user: request.data.user_name
            }
        };
    }

    process_outgoing_response({ request, response }) {
        const originalMessage = request.data.text;
        const command = originalMessage.split(' ')[0];

        switch (command) {
            case '/weather':
                return this.processWeatherResponse(response);
            case '/translate':
                return this.processTranslateResponse(response);
            case '/jira':
                return this.processJiraResponse(response);
            case '/deploy':
                return this.processDeployResponse(response);
            default:
                return { content: { text: 'Unknown command response' } };
        }
    }

    processWeatherResponse(response) {
        if (response.status_code === 200) {
            const weather = response.content;
            return {
                content: {
                    text: `🌤️ **${weather.name}の天気**`,
                    attachments: [{
                        color: '#87CEEB',
                        fields: [
                            {
                                title: '天気',
                                value: weather.weather[0].description,
                                short: true
                            },
                            {
                                title: '気温',
                                value: `${weather.main.temp}°C`,
                                short: true
                            },
                            {
                                title: '湿度',
                                value: `${weather.main.humidity}%`,
                                short: true
                            },
                            {
                                title: '風速',
                                value: `${weather.wind.speed} m/s`,
                                short: true
                            }
                        ]
                    }]
                }
            };
        } else {
            return {
                content: {
                    text: '❌ 天気情報の取得に失敗しました'
                }
            };
        }
    }

    processTranslateResponse(response) {
        if (response.status_code === 200) {
            const translation = response.content.responseData.translatedText;
            return {
                content: {
                    text: `🔤 **翻訳結果**: ${translation}`
                }
            };
        } else {
            return {
                content: {
                    text: '❌ 翻訳に失敗しました'
                }
            };
        }
    }

    processJiraResponse(response) {
        if (response.status_code === 200) {
            const issue = response.content;
            return {
                content: {
                    text: `🎫 **JIRA Issue: ${issue.key}**`,
                    attachments: [{
                        color: '#0052CC',
                        title: issue.fields.summary,
                        title_link: `https://your-domain.atlassian.net/browse/${issue.key}`,
                        text: issue.fields.description,
                        fields: [
                            {
                                title: 'Status',
                                value: issue.fields.status.name,
                                short: true
                            },
                            {
                                title: 'Assignee',
                                value: issue.fields.assignee?.displayName || 'Unassigned',
                                short: true
                            }
                        ]
                    }]
                }
            };
        } else {
            return {
                content: {
                    text: '❌ JIRA Issue の取得に失敗しました'
                }
            };
        }
    }

    processDeployResponse(response) {
        if (response.status_code === 200) {
            const deploy = response.content;
            return {
                content: {
                    text: `🚀 **デプロイ開始**`,
                    attachments: [{
                        color: '#28a745',
                        fields: [
                            {
                                title: 'Environment',
                                value: deploy.environment,
                                short: true
                            },
                            {
                                title: 'Deploy ID',
                                value: deploy.id,
                                short: true
                            },
                            {
                                title: 'Status',
                                value: 'In Progress',
                                short: true
                            }
                        ]
                    }]
                }
            };
        } else {
            return {
                content: {
                    text: '❌ デプロイの開始に失敗しました'
                }
            };
        }
    }
}

REST API を使用したBot開発

// Node.js Rocket.Chat Bot implementation
const axios = require('axios');

class RocketChatBot {
    constructor(serverUrl, username, password) {
        this.serverUrl = serverUrl;
        this.username = username;
        this.password = password;
        this.userId = null;
        this.authToken = null;
    }

    // ログイン認証
    async login() {
        try {
            const response = await axios.post(`${this.serverUrl}/api/v1/login`, {
                username: this.username,
                password: this.password
            });

            this.userId = response.data.data.userId;
            this.authToken = response.data.data.authToken;

            console.log('✅ Bot login successful');
            return true;
        } catch (error) {
            console.error('❌ Login failed:', error.response?.data || error.message);
            return false;
        }
    }

    // メッセージ送信
    async sendMessage(roomId, message, attachments = []) {
        try {
            const response = await axios.post(
                `${this.serverUrl}/api/v1/chat.postMessage`,
                {
                    roomId: roomId,
                    text: message,
                    attachments: attachments
                },
                {
                    headers: {
                        'X-Auth-Token': this.authToken,
                        'X-User-Id': this.userId
                    }
                }
            );

            return response.data;
        } catch (error) {
            console.error('Message send error:', error.response?.data || error.message);
            throw error;
        }
    }

    // チャンネル一覧取得
    async getChannels() {
        try {
            const response = await axios.get(
                `${this.serverUrl}/api/v1/channels.list`,
                {
                    headers: {
                        'X-Auth-Token': this.authToken,
                        'X-User-Id': this.userId
                    }
                }
            );

            return response.data.channels;
        } catch (error) {
            console.error('Get channels error:', error.response?.data || error.message);
            throw error;
        }
    }

    // メッセージ履歴取得
    async getMessages(roomId, count = 50) {
        try {
            const response = await axios.get(
                `${this.serverUrl}/api/v1/channels.history`,
                {
                    params: {
                        roomId: roomId,
                        count: count
                    },
                    headers: {
                        'X-Auth-Token': this.authToken,
                        'X-User-Id': this.userId
                    }
                }
            );

            return response.data.messages;
        } catch (error) {
            console.error('Get messages error:', error.response?.data || error.message);
            throw error;
        }
    }

    // ファイルアップロード
    async uploadFile(roomId, filePath, description = '') {
        const FormData = require('form-data');
        const fs = require('fs');
        
        const form = new FormData();
        form.append('file', fs.createReadStream(filePath));
        form.append('description', description);

        try {
            const response = await axios.post(
                `${this.serverUrl}/api/v1/rooms.upload/${roomId}`,
                form,
                {
                    headers: {
                        ...form.getHeaders(),
                        'X-Auth-Token': this.authToken,
                        'X-User-Id': this.userId
                    }
                }
            );

            return response.data;
        } catch (error) {
            console.error('File upload error:', error.response?.data || error.message);
            throw error;
        }
    }

    // リアルタイムメッセージ監視(WebSocket)
    startMessageListener() {
        const WebSocket = require('ws');
        const ws = new WebSocket(`${this.serverUrl.replace('http', 'ws')}/websocket`);

        ws.on('open', () => {
            console.log('WebSocket connected');
            
            // 認証
            ws.send(JSON.stringify({
                msg: 'connect',
                version: '1',
                support: ['1']
            }));

            ws.send(JSON.stringify({
                msg: 'method',
                method: 'login',
                params: [{
                    resume: this.authToken
                }],
                id: '1'
            }));

            // メッセージストリーム購読
            ws.send(JSON.stringify({
                msg: 'sub',
                id: 'messages',
                name: 'stream-notify-user',
                params: [`${this.userId}/message`, false]
            }));
        });

        ws.on('message', (data) => {
            try {
                const message = JSON.parse(data);
                if (message.msg === 'changed' && message.collection === 'stream-notify-user') {
                    this.handleIncomingMessage(message.fields.args[1]);
                }
            } catch (error) {
                console.error('WebSocket message parse error:', error);
            }
        });

        return ws;
    }

    async handleIncomingMessage(messageData) {
        // Botのメッセージは無視
        if (messageData.u._id === this.userId) return;

        const message = messageData.msg;
        const roomId = messageData.rid;
        const user = messageData.u;

        console.log(`📥 Message from ${user.username}: ${message}`);

        // コマンド処理
        if (message.startsWith('!')) {
            await this.processCommand(message, roomId, user);
        }

        // キーワード監視
        if (message.toLowerCase().includes('help')) {
            await this.sendHelpMessage(roomId);
        }
    }

    async processCommand(message, roomId, user) {
        const [command, ...args] = message.slice(1).split(' ');

        switch (command.toLowerCase()) {
            case 'status':
                await this.sendSystemStatus(roomId);
                break;
            case 'weather':
                await this.sendWeatherInfo(roomId, args[0] || 'Tokyo');
                break;
            case 'joke':
                await this.sendRandomJoke(roomId);
                break;
            default:
                await this.sendMessage(roomId, `❓ Unknown command: ${command}`);
        }
    }

    async sendSystemStatus(roomId) {
        const os = require('os');
        
        const statusMessage = {
            roomId: roomId,
            text: '📊 **System Status**',
            attachments: [{
                color: '#28a745',
                fields: [
                    {
                        title: 'Uptime',
                        value: `${Math.floor(os.uptime() / 3600)}h ${Math.floor((os.uptime() % 3600) / 60)}m`,
                        short: true
                    },
                    {
                        title: 'Memory Usage',
                        value: `${Math.round((os.totalmem() - os.freemem()) / os.totalmem() * 100)}%`,
                        short: true
                    },
                    {
                        title: 'Platform',
                        value: `${os.platform()} ${os.arch()}`,
                        short: true
                    },
                    {
                        title: 'Node Version',
                        value: process.version,
                        short: true
                    }
                ]
            }]
        };

        await this.sendMessage(statusMessage.roomId, statusMessage.text, statusMessage.attachments);
    }

    async sendHelpMessage(roomId) {
        const helpMessage = `
🤖 **Bot Commands**

\`!status\` - システム状態を表示
\`!weather [city]\` - 天気情報を取得
\`!joke\` - ランダムジョークを表示
\`help\` - このヘルプを表示

**その他の機能:**
• 自動応答
• ファイル監視
• 外部API連携
        `;

        await this.sendMessage(roomId, helpMessage);
    }
}

// Bot使用例
async function startBot() {
    const bot = new RocketChatBot(
        'https://your-rocketchat.com',
        'bot-username',
        'bot-password'
    );

    if (await bot.login()) {
        // WebSocket接続開始
        bot.startMessageListener();

        // 定期的なタスク実行
        setInterval(async () => {
            const channels = await bot.getChannels();
            const generalChannel = channels.find(c => c.name === 'general');
            
            if (generalChannel) {
                await bot.sendMessage(
                    generalChannel._id,
                    '⏰ 定期報告: システムは正常に動作しています'
                );
            }
        }, 3600000); // 1時間ごと
    }
}

startBot().catch(console.error);

Docker Compose 設定

# docker-compose.yml
version: '3.8'

services:
  rocketchat:
    image: registry.rocket.chat/rocketchat/rocket.chat:${RELEASE:-latest}
    container_name: rocketchat
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.rocketchat.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.rocketchat.tls=true"
      - "traefik.http.routers.rocketchat.entrypoints=https"
      - "traefik.http.routers.rocketchat.tls.certresolver=le"
    environment:
      MONGO_URL: "mongodb://mongodb:27017/rocketchat"
      MONGO_OPLOG_URL: "mongodb://mongodb:27017/local"
      ROOT_URL: "https://${DOMAIN}"
      PORT: 3000
      DEPLOY_METHOD: docker
      DEPLOY_PLATFORM: ${DEPLOY_PLATFORM:-}
      REG_TOKEN: ${REG_TOKEN:-}
    depends_on:
      - mongodb
    expose:
      - 3000
    volumes:
      - rocketchat_uploads:/app/uploads

  mongodb:
    image: docker.io/bitnami/mongodb:${MONGODB_VERSION:-5.0}
    container_name: mongodb
    restart: unless-stopped
    volumes:
      - mongodb_data:/bitnami/mongodb
    environment:
      MONGODB_REPLICA_SET_MODE: primary
      MONGODB_REPLICA_SET_NAME: rs0
      MONGODB_PORT_NUMBER: 27017
      MONGODB_INITIAL_PRIMARY_HOST: mongodb
      MONGODB_INITIAL_PRIMARY_PORT_NUMBER: 27017
      MONGODB_ADVERTISED_HOSTNAME: mongodb
      MONGODB_ENABLE_JOURNAL: true
      ALLOW_EMPTY_PASSWORD: yes

  # Traefik reverse proxy
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    command:
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.https.address=:443"
      - "--certificatesresolvers.le.acme.tlschallenge=true"
      - "--certificatesresolvers.le.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "traefik_letsencrypt:/letsencrypt"

volumes:
  mongodb_data:
  rocketchat_uploads:
  traefik_letsencrypt:

networks:
  default:
    name: rocketchat_network

環境変数設定

# .env ファイル
# Basic settings
DOMAIN=your-domain.com
[email protected]
RELEASE=6.0.0
MONGODB_VERSION=5.0

# Rocket.Chat settings
ROOT_URL=https://your-domain.com
PORT=3000
MONGO_URL=mongodb://mongodb:27017/rocketchat
MONGO_OPLOG_URL=mongodb://mongodb:27017/local

# Optional deployment settings
DEPLOY_METHOD=docker
DEPLOY_PLATFORM=
REG_TOKEN=

# API settings
ROCKETCHAT_URL=https://your-domain.com
ROCKETCHAT_USERNAME=admin
ROCKETCHAT_PASSWORD=your-admin-password

# Bot settings
BOT_USERNAME=automation-bot
BOT_PASSWORD=bot-password

# External API keys
WEATHER_API_KEY=your-weather-api-key
JIRA_TOKEN=your-jira-token
GITHUB_TOKEN=your-github-token

# Webhook URLs
SLACK_WEBHOOK_URL=your-slack-webhook-url
TEAMS_WEBHOOK_URL=your-teams-webhook-url

システムメトリクス監視

// monitoring.js - システム監視スクリプト
const { RocketChatBot } = require('./rocketchat-bot');
const os = require('os');

class SystemMonitor {
    constructor(bot, alertChannelId) {
        this.bot = bot;
        this.alertChannelId = alertChannelId;
        this.thresholds = {
            cpu: 80,
            memory: 85,
            disk: 90
        };
    }

    async startMonitoring() {
        setInterval(async () => {
            await this.checkSystemHealth();
        }, 300000); // 5分ごと

        console.log('🔍 System monitoring started');
    }

    async checkSystemHealth() {
        const metrics = await this.getSystemMetrics();
        
        if (this.shouldAlert(metrics)) {
            await this.sendAlert(metrics);
        }

        // 定期レポート(1日1回)
        const now = new Date();
        if (now.getHours() === 9 && now.getMinutes() < 5) {
            await this.sendDailyReport(metrics);
        }
    }

    async getSystemMetrics() {
        const cpuUsage = await this.getCpuUsage();
        const memoryUsage = this.getMemoryUsage();
        const diskUsage = await this.getDiskUsage();

        return {
            cpu: cpuUsage,
            memory: memoryUsage,
            disk: diskUsage,
            uptime: os.uptime(),
            timestamp: new Date()
        };
    }

    async getCpuUsage() {
        const cpus = os.cpus();
        let totalIdle = 0;
        let totalTick = 0;

        cpus.forEach(cpu => {
            for (let type in cpu.times) {
                totalTick += cpu.times[type];
            }
            totalIdle += cpu.times.idle;
        });

        return Math.round(100 - (totalIdle / totalTick) * 100);
    }

    getMemoryUsage() {
        const total = os.totalmem();
        const free = os.freemem();
        return Math.round(((total - free) / total) * 100);
    }

    async getDiskUsage() {
        const { exec } = require('child_process');
        return new Promise((resolve) => {
            exec("df -h / | awk 'NR==2{print $5}' | sed 's/%//'", (error, stdout) => {
                resolve(error ? 0 : parseInt(stdout.trim()));
            });
        });
    }

    shouldAlert(metrics) {
        return metrics.cpu > this.thresholds.cpu ||
               metrics.memory > this.thresholds.memory ||
               metrics.disk > this.thresholds.disk;
    }

    async sendAlert(metrics) {
        const alertMessage = {
            text: '🚨 **System Alert**',
            attachments: [{
                color: '#d73a49',
                title: 'High resource usage detected',
                fields: [
                    {
                        title: 'CPU Usage',
                        value: `${metrics.cpu}%`,
                        short: true
                    },
                    {
                        title: 'Memory Usage',
                        value: `${metrics.memory}%`,
                        short: true
                    },
                    {
                        title: 'Disk Usage',
                        value: `${metrics.disk}%`,
                        short: true
                    },
                    {
                        title: 'Uptime',
                        value: `${Math.floor(metrics.uptime / 3600)}h ${Math.floor((metrics.uptime % 3600) / 60)}m`,
                        short: true
                    }
                ],
                timestamp: metrics.timestamp.toISOString()
            }]
        };

        await this.bot.sendMessage(
            this.alertChannelId,
            alertMessage.text,
            alertMessage.attachments
        );
    }
}

// 使用例
async function startMonitoring() {
    const bot = new RocketChatBot(
        process.env.ROCKETCHAT_URL,
        process.env.BOT_USERNAME,
        process.env.BOT_PASSWORD
    );

    if (await bot.login()) {
        const channels = await bot.getChannels();
        const alertChannel = channels.find(c => c.name === 'alerts');
        
        if (alertChannel) {
            const monitor = new SystemMonitor(bot, alertChannel._id);
            await monitor.startMonitoring();
        }
    }
}

startMonitoring().catch(console.error);