Skip to content

Instantly share code, notes, and snippets.

@stesie
Created February 1, 2025 12:35
Show Gist options
  • Save stesie/3f496a10ee4961c00ebbd2a2825e71c4 to your computer and use it in GitHub Desktop.
Save stesie/3f496a10ee4961c00ebbd2a2825e71c4 to your computer and use it in GitHub Desktop.
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