184 lines
4.9 KiB
JavaScript
184 lines
4.9 KiB
JavaScript
|
|
// common-message-file.js
|
||
|
|
// 共通テキストファイル経由の通信システム
|
||
|
|
|
||
|
|
const fs = require('fs');
|
||
|
|
const path = require('path');
|
||
|
|
const EventEmitter = require('events');
|
||
|
|
|
||
|
|
class FileBasedMessenger extends EventEmitter {
|
||
|
|
constructor(config = {}) {
|
||
|
|
super();
|
||
|
|
this.config = {
|
||
|
|
messageFile: config.messageFile || './shared-messages.txt',
|
||
|
|
lockFile: config.lockFile || './shared-messages.lock',
|
||
|
|
pollInterval: config.pollInterval || 500,
|
||
|
|
...config
|
||
|
|
};
|
||
|
|
|
||
|
|
this.lastReadPosition = 0;
|
||
|
|
this.isWatching = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// メッセージを書き込む
|
||
|
|
async sendMessage(from, to, message) {
|
||
|
|
const timestamp = new Date().toISOString();
|
||
|
|
const entry = JSON.stringify({
|
||
|
|
timestamp,
|
||
|
|
from,
|
||
|
|
to,
|
||
|
|
message
|
||
|
|
}) + '\n';
|
||
|
|
|
||
|
|
// ロックを取得
|
||
|
|
await this.acquireLock();
|
||
|
|
|
||
|
|
try {
|
||
|
|
// ファイルに追記
|
||
|
|
fs.appendFileSync(this.config.messageFile, entry);
|
||
|
|
console.log(`📤 Sent: ${from} → ${to}: ${message}`);
|
||
|
|
} finally {
|
||
|
|
// ロック解放
|
||
|
|
this.releaseLock();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// メッセージを監視
|
||
|
|
startWatching(myName) {
|
||
|
|
this.isWatching = true;
|
||
|
|
console.log(`👁️ Watching messages for: ${myName}`);
|
||
|
|
|
||
|
|
// 初期位置を設定
|
||
|
|
if (fs.existsSync(this.config.messageFile)) {
|
||
|
|
const stats = fs.statSync(this.config.messageFile);
|
||
|
|
this.lastReadPosition = stats.size;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 定期的にチェック
|
||
|
|
const checkMessages = () => {
|
||
|
|
if (!this.isWatching) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (!fs.existsSync(this.config.messageFile)) {
|
||
|
|
setTimeout(checkMessages, this.config.pollInterval);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const stats = fs.statSync(this.config.messageFile);
|
||
|
|
if (stats.size > this.lastReadPosition) {
|
||
|
|
// 新しいメッセージがある
|
||
|
|
const buffer = Buffer.alloc(stats.size - this.lastReadPosition);
|
||
|
|
const fd = fs.openSync(this.config.messageFile, 'r');
|
||
|
|
fs.readSync(fd, buffer, 0, buffer.length, this.lastReadPosition);
|
||
|
|
fs.closeSync(fd);
|
||
|
|
|
||
|
|
const newLines = buffer.toString().trim().split('\n');
|
||
|
|
|
||
|
|
for (const line of newLines) {
|
||
|
|
if (line) {
|
||
|
|
try {
|
||
|
|
const msg = JSON.parse(line);
|
||
|
|
// 自分宛のメッセージ
|
||
|
|
if (msg.to === myName || msg.to === '*') {
|
||
|
|
this.emit('message', msg);
|
||
|
|
console.log(`📨 Received: ${msg.from} → ${msg.to}: ${msg.message}`);
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
console.error('Parse error:', e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
this.lastReadPosition = stats.size;
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
console.error('Watch error:', err);
|
||
|
|
}
|
||
|
|
|
||
|
|
setTimeout(checkMessages, this.config.pollInterval);
|
||
|
|
};
|
||
|
|
|
||
|
|
checkMessages();
|
||
|
|
}
|
||
|
|
|
||
|
|
// 監視停止
|
||
|
|
stopWatching() {
|
||
|
|
this.isWatching = false;
|
||
|
|
console.log('🛑 Stopped watching');
|
||
|
|
}
|
||
|
|
|
||
|
|
// 簡易ロック機構
|
||
|
|
async acquireLock(maxWait = 5000) {
|
||
|
|
const startTime = Date.now();
|
||
|
|
|
||
|
|
while (fs.existsSync(this.config.lockFile)) {
|
||
|
|
if (Date.now() - startTime > maxWait) {
|
||
|
|
throw new Error('Lock timeout');
|
||
|
|
}
|
||
|
|
await this.sleep(50);
|
||
|
|
}
|
||
|
|
|
||
|
|
fs.writeFileSync(this.config.lockFile, process.pid.toString());
|
||
|
|
}
|
||
|
|
|
||
|
|
releaseLock() {
|
||
|
|
if (fs.existsSync(this.config.lockFile)) {
|
||
|
|
fs.unlinkSync(this.config.lockFile);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sleep(ms) {
|
||
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||
|
|
}
|
||
|
|
|
||
|
|
// メッセージ履歴をクリア
|
||
|
|
clearMessages() {
|
||
|
|
if (fs.existsSync(this.config.messageFile)) {
|
||
|
|
fs.unlinkSync(this.config.messageFile);
|
||
|
|
}
|
||
|
|
console.log('🗑️ Message history cleared');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// CLIとして使用
|
||
|
|
if (require.main === module) {
|
||
|
|
const messenger = new FileBasedMessenger();
|
||
|
|
const myName = process.argv[2];
|
||
|
|
const command = process.argv[3];
|
||
|
|
|
||
|
|
if (!myName || !command) {
|
||
|
|
console.log(`
|
||
|
|
使い方:
|
||
|
|
node common-message-file.js <名前> watch # メッセージを監視
|
||
|
|
node common-message-file.js <名前> send <宛先> <内容> # メッセージ送信
|
||
|
|
node common-message-file.js <名前> clear # 履歴クリア
|
||
|
|
`);
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
switch (command) {
|
||
|
|
case 'watch':
|
||
|
|
messenger.on('message', (msg) => {
|
||
|
|
// 自動返信(デモ用)
|
||
|
|
if (msg.message.includes('?')) {
|
||
|
|
setTimeout(() => {
|
||
|
|
messenger.sendMessage(myName, msg.from, 'はい、了解しました!');
|
||
|
|
}, 1000);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
messenger.startWatching(myName);
|
||
|
|
console.log('Press Ctrl+C to stop...');
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'send':
|
||
|
|
const to = process.argv[4];
|
||
|
|
const message = process.argv.slice(5).join(' ');
|
||
|
|
messenger.sendMessage(myName, to, message);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'clear':
|
||
|
|
messenger.clearMessages();
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = FileBasedMessenger;
|