-
-
Save stesie/3f496a10ee4961c00ebbd2a2825e71c4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const TelegramBot = require('node-telegram-bot-api'); | |
const { Telegraf } = require('telegraf'); | |
const { createClient } = require('@supabase/supabase-js'); | |
const { OpenAI } = require('openai'); | |
const multer = require('multer'); | |
const axios = require('axios'); | |
const fs = require('fs'); | |
const pg = require('pg'); | |
class GasMeterBot { | |
constructor() { | |
// Telegram Bot Configuration | |
this.botToken = process.env.TELEGRAM_BOT_TOKEN; | |
this.bot = new Telegraf(this.botToken); | |
// OpenAI Configuration | |
this.openai = new OpenAI({ | |
apiKey: process.env.OPENAI_API_KEY | |
}); | |
// PostgreSQL Configuration | |
this.pool = new pg.Pool({ | |
connectionString: process.env.DATABASE_URL, | |
ssl: { | |
rejectUnauthorized: false | |
} | |
}); | |
// Multer for handling file uploads | |
this.upload = multer({ | |
dest: 'uploads/', | |
limits: { fileSize: 5 * 1024 * 1024 } // 5MB file size limit | |
}); | |
this.initializeBot(); | |
} | |
initializeBot() { | |
// Start command handler | |
this.bot.command('start', (ctx) => { | |
ctx.reply('Welcome! Send me a photo of your gas meter, and I\'ll read and store the reading.'); | |
}); | |
// Photo message handler | |
this.bot.on('photo', async (ctx) => { | |
try { | |
// Get the largest photo file | |
const photo = ctx.message.photo[ctx.message.photo.length - 1]; | |
const fileId = photo.file_id; | |
// Download the file | |
const fileLink = await ctx.telegram.getFile(fileId); | |
const filePath = await this.downloadFile(fileLink.file_path); | |
// Process the image with OpenAI | |
const meterReading = await this.processMeterReading(filePath); | |
// Validate meter reading format | |
if (!this.validateMeterReading(meterReading)) { | |
throw new Error('Invalid meter reading format'); | |
} | |
// Store in database | |
await this.storeMeterReading(ctx.from.id, meterReading); | |
// Reply to user | |
ctx.reply(`Meter reading recorded: ${meterReading}`); | |
// Clean up uploaded file | |
fs.unlinkSync(filePath); | |
} catch (error) { | |
console.error('Error processing meter reading:', error); | |
ctx.reply('Sorry, I couldn\'t process the meter reading. Please try again.'); | |
} | |
}); | |
// Start the bot | |
this.bot.launch(); | |
} | |
async downloadFile(filePath) { | |
const fileUrl = `https://api.telegram.org/file/bot${this.botToken}/${filePath}`; | |
const localFilePath = `uploads/${Date.now()}_meter.jpg`; | |
const response = await axios({ | |
method: 'get', | |
url: fileUrl, | |
responseType: 'stream' | |
}); | |
const writer = fs.createWriteStream(localFilePath); | |
response.data.pipe(writer); | |
return new Promise((resolve, reject) => { | |
writer.on('finish', () => resolve(localFilePath)); | |
writer.on('error', reject); | |
}); | |
} | |
async processMeterReading(filePath) { | |
const response = await this.openai.chat.completions.create({ | |
model: "gpt-4-vision-preview", | |
messages: [ | |
{ | |
role: "user", | |
content: [ | |
{ | |
type: "text", | |
text: "Read the number on this gas meter. Ensure it's a 5-digit number before the comma and 3 decimal places." | |
}, | |
{ | |
type: "image_url", | |
image_url: { | |
url: `data:image/jpeg;base64,${fs.readFileSync(filePath, {encoding: 'base64'})}` | |
} | |
} | |
] | |
} | |
], | |
max_tokens: 300 | |
}); | |
// Extract the number from the response | |
const reading = response.choices[0].message.content; | |
return reading.trim(); | |
} | |
validateMeterReading(reading) { | |
// Regex to match 5 digits before comma, 3 decimal places | |
const meterReadingRegex = /^\d{5}\.\d{3}$/; | |
return meterReadingRegex.test(reading); | |
} | |
async storeMeterReading(userId, reading) { | |
const client = await this.pool.connect(); | |
try { | |
await client.query( | |
'INSERT INTO gas_meter_readings (user_id, reading, timestamp) VALUES ($1, $2, NOW())', | |
[userId, parseFloat(reading)] | |
); | |
} finally { | |
client.release(); | |
} | |
} | |
// Database setup method (run once) | |
async setupDatabase() { | |
const client = await this.pool.connect(); | |
try { | |
await client.query(` | |
CREATE TABLE IF NOT EXISTS gas_meter_readings ( | |
id SERIAL PRIMARY KEY, | |
user_id BIGINT NOT NULL, | |
reading NUMERIC(8,3) NOT NULL, | |
timestamp TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP | |
) | |
`); | |
} finally { | |
client.release(); | |
} | |
} | |
} | |
// Export for use | |
module.exports = GasMeterBot; | |
// Example instantiation | |
// const gasMeterBot = new GasMeterBot(); | |
// gasMeterBot.setupDatabase(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment