| const express = require('express'); |
| const bodyParser = require('body-parser'); |
| const axios = require('axios'); |
| const cron = require('node-cron'); |
| const fs = require('fs'); |
| const path = require('path'); |
| const WebSocket = require('ws'); |
| const app = express(); |
| app.use(bodyParser.json()); |
|
|
| const configPath = path.join(__dirname, 'database.json'); |
| const usersPath = path.join(__dirname, 'users.json'); |
| const favoritesPath = path.join(__dirname, 'favorites.json'); |
| const lastSeenPath = path.join(__dirname, 'lastSeen.json'); |
|
|
| if (!fs.existsSync(configPath)) { |
| fs.writeFileSync(configPath, JSON.stringify({ |
| PAGE_ACCESS_TOKEN: 'EAAJ2oq2jZCIwBO1kFcU7DaPM7vfGZC16ncAbJzFZAuokeSK5dEdpQ1muBKWZBC6sUDrDrG9Mq4thZCcH6pqFYM1viCDOIjmZCEGHRokhKj3kIAt5uTToHoZBZAkJSozb5mODsk5xxE8NXG3ZAtv7lcQneeHkiUJmwdmPQsFeuslWZAwfZC3Hvz4ZCa5yCwiDIcEJX0we28820E5GKKGuJfCKXRPRVUVsJAZDZD', |
| VERIFY_TOKEN: 'ccprojects' |
| })); |
| } |
|
|
| if (!fs.existsSync(usersPath)) fs.writeFileSync(usersPath, JSON.stringify({})); |
| if (!fs.existsSync(favoritesPath)) fs.writeFileSync(favoritesPath, JSON.stringify({})); |
| if (!fs.existsSync(lastSeenPath)) fs.writeFileSync(lastSeenPath, JSON.stringify({})); |
|
|
| const config = JSON.parse(fs.readFileSync(configPath)); |
| const PAGE_ACCESS_TOKEN = config.PAGE_ACCESS_TOKEN; |
| const VERIFY_TOKEN = config.VERIFY_TOKEN; |
|
|
| const activeSessions = new Map(); |
| const lastSentCache = new Map(); |
| let sharedWebSocket = null; |
| let keepAliveInterval = null; |
| const PH_TIMEZONE = "Asia/Manila"; |
|
|
| function pad(n) { |
| return n < 10 ? "0" + n : n; |
| } |
|
|
| function getPHTime() { |
| return new Date(new Date().toLocaleString("en-US", { timeZone: PH_TIMEZONE })); |
| } |
|
|
| function getCountdown(target) { |
| const now = getPHTime(); |
| const msLeft = target - now; |
| if (msLeft <= 0) return "00h 00m 00s"; |
| const h = Math.floor(msLeft / 3.6e6); |
| const m = Math.floor((msLeft % 3.6e6) / 6e4); |
| const s = Math.floor((msLeft % 6e4) / 1000); |
| return `${pad(h)}h ${pad(m)}m ${pad(s)}s`; |
| } |
|
|
| function getNextRestocks() { |
| const now = getPHTime(); |
| const timers = {}; |
|
|
| const nextEgg = new Date(now); |
| nextEgg.setMinutes(now.getMinutes() < 30 ? 30 : 0); |
| if (now.getMinutes() >= 30) nextEgg.setHours(now.getHours() + 1); |
| nextEgg.setSeconds(0, 0); |
| timers.egg = getCountdown(nextEgg); |
|
|
| const next5 = new Date(now); |
| const nextM = Math.ceil((now.getMinutes() + (now.getSeconds() > 0 ? 1 : 0)) / 5) * 5; |
| next5.setMinutes(nextM === 60 ? 0 : nextM, 0, 0); |
| if (nextM === 60) next5.setHours(now.getHours() + 1); |
| timers.gear = timers.seed = getCountdown(next5); |
|
|
| const nextHoney = new Date(now); |
| nextHoney.setMinutes(now.getMinutes() < 30 ? 30 : 0); |
| if (now.getMinutes() >= 30) nextHoney.setHours(now.getHours() + 1); |
| nextHoney.setSeconds(0, 0); |
| timers.honey = getCountdown(nextHoney); |
|
|
| const next7 = new Date(now); |
| const totalHours = now.getHours() + now.getMinutes() / 60 + now.getSeconds() / 3600; |
| const next7h = Math.ceil(totalHours / 7) * 7; |
| next7.setHours(next7h, 0, 0, 0); |
| timers.cosmetics = getCountdown(next7); |
|
|
| return timers; |
| } |
|
|
| function formatValue(val) { |
| if (val >= 1_000_000) return `x${(val / 1_000_000).toFixed(1)}M`; |
| if (val >= 1_000) return `x${(val / 1_000).toFixed(1)}K`; |
| return `x${val}`; |
| } |
|
|
| function addEmoji(name) { |
| const emojis = { |
| "Common Egg": "๐ฅ", "Uncommon Egg": "๐ฃ", "Rare Egg": "๐ณ", "Legendary Egg": "๐ชบ", "Mythical Egg": "๐ฎ", |
| "Bug Egg": "๐ชฒ", "Cleaning Spray": "๐งด", "Friendship Pot": "๐ชด", "Watering Can": "๐ฟ", "Trowel": "๐ ๏ธ", |
| "Recall Wrench": "๐ง", "Basic Sprinkler": "๐ง", "Advanced Sprinkler": "๐ฆ", "Godly Sprinkler": "โฒ", |
| "Lightning Rod": "โก", "Master Sprinkler": "๐", "Favorite Tool": "โค๏ธ", "Harvest Tool": "๐พ", "Carrot": "๐ฅ", |
| "Strawberry": "๐", "Blueberry": "๐ซ", "Orange Tulip": "๐ท", "Tomato": "๐
", "Corn": "๐ฝ", "Daffodil": "๐ผ", |
| "Watermelon": "๐", "Pumpkin": "๐", "Apple": "๐", "Bamboo": "๐", "Coconut": "๐ฅฅ", "Cactus": "๐ต", |
| "Dragon Fruit": "๐", "Mango": "๐ฅญ", "Grape": "๐", "Mushroom": "๐", "Pepper": "๐ถ๏ธ", "Cacao": "๐ซ", |
| "Beanstalk": "๐ฑ", "Ember Lily": "๐ต๏ธ", "Sugar Apple": "๐" |
| }; |
| return `${emojis[name] || ""} ${name}`; |
| } |
|
|
| function getTimeAgo(date) { |
| const now = getPHTime(); |
| const diff = now - new Date(date); |
| const sec = Math.floor(diff / 1000); |
| const min = Math.floor(sec / 60); |
| const hour = Math.floor(min / 60); |
| const day = Math.floor(hour / 24); |
|
|
| if (sec < 60) return `${sec}s ago`; |
| if (min < 60) return `${min}m ago`; |
| if (hour < 24) return `${hour}h ago`; |
| return `${day}d ago`; |
| } |
|
|
| function cleanText(text) { |
| return text.trim().toLowerCase(); |
| } |
|
|
| function loadFavorites() { |
| try { |
| return JSON.parse(fs.readFileSync(favoritesPath)); |
| } catch { |
| return {}; |
| } |
| } |
|
|
| function saveFavorites(favorites) { |
| fs.writeFileSync(favoritesPath, JSON.stringify(favorites)); |
| } |
|
|
| function loadLastSeen() { |
| try { |
| return JSON.parse(fs.readFileSync(lastSeenPath)); |
| } catch { |
| return {}; |
| } |
| } |
|
|
| function saveLastSeen(lastSeen) { |
| fs.writeFileSync(lastSeenPath, JSON.stringify(lastSeen)); |
| } |
|
|
| function updateLastSeen(category, items) { |
| const lastSeen = loadLastSeen(); |
| if (!lastSeen[category]) lastSeen[category] = {}; |
| const now = new Date().toISOString(); |
|
|
| items.forEach(item => { |
| lastSeen[category][item.name] = now; |
| }); |
|
|
| saveLastSeen(lastSeen); |
| } |
|
|
| function ensureWebSocketConnection() { |
| if (sharedWebSocket && sharedWebSocket.readyState === WebSocket.OPEN) return; |
|
|
| sharedWebSocket = new WebSocket("wss://gagstock.gleeze.com"); |
|
|
| sharedWebSocket.on("open", () => { |
| keepAliveInterval = setInterval(() => { |
| if (sharedWebSocket.readyState === WebSocket.OPEN) { |
| sharedWebSocket.send("ping"); |
| } |
| }, 10000); |
| }); |
|
|
| sharedWebSocket.on("message", async (data) => { |
| try { |
| const payload = JSON.parse(data); |
| if (payload.status !== "success") return; |
|
|
| const stock = payload.data; |
| const stockData = { |
| gear: stock.gear, |
| seed: stock.seed, |
| egg: stock.egg, |
| cosmetics: stock.cosmetics, |
| event: stock.honey, |
| }; |
|
|
| updateLastSeen("gear", stockData.gear.items); |
| updateLastSeen("seed", stockData.seed.items); |
| updateLastSeen("egg", stockData.egg.items); |
| updateLastSeen("cosmetics", stockData.cosmetics.items); |
| updateLastSeen("event", stockData.event.items); |
|
|
| const favorites = loadFavorites(); |
|
|
| for (const [senderId] of activeSessions.entries()) { |
| const userFavorites = favorites[senderId] || []; |
| let sections = []; |
| let matchCount = 0; |
|
|
| const checkAndAdd = (label, section, useEmoji) => { |
| const items = section.items || []; |
| const matchedItems = userFavorites.length > 0 |
| ? items.filter(i => userFavorites.includes(cleanText(i.name))) |
| : items; |
| |
| if (userFavorites.length > 0 && matchedItems.length === 0) return false; |
| |
| matchCount += matchedItems.length; |
| const formattedItems = matchedItems.map(i => |
| `- ${useEmoji ? addEmoji(i.name) : i.name}: ${formatValue(i.quantity)}` |
| ).join("\n"); |
| |
| sections.push(`${label}:\n${formattedItems}\nโณ Restock In: ${section.countdown}`); |
| return true; |
| }; |
|
|
| checkAndAdd("๐ ๏ธ ๐๐ฒ๐ฎ๐ฟ", stockData.gear, true); |
| checkAndAdd("๐ฑ ๐ฆ๐ฒ๐ฒ๐ฑ๐", stockData.seed, true); |
| checkAndAdd("๐ฅ ๐๐ด๐ด๐", stockData.egg, true); |
| checkAndAdd("๐จ ๐๐ผ๐๐บ๐ฒ๐๐ถ๐ฐ๐", stockData.cosmetics, false); |
| checkAndAdd("๐ ๐๐๐ฒ๐ป๐", stockData.event, false); |
|
|
| if (userFavorites.length > 0 && matchCount === 0) continue; |
| if (sections.length === 0) continue; |
|
|
| const updatedAt = getPHTime().toLocaleString("en-PH", { |
| hour: "numeric", minute: "numeric", second: "numeric", |
| hour12: true, day: "2-digit", month: "short", year: "numeric" |
| }); |
|
|
| const weather = await axios.get("https://growagardenstock.com/api/stock/weather") |
| .then(res => res.data) |
| .catch(() => null); |
| |
| const weatherInfo = weather |
| ? `๐ค๏ธ ๐ช๐ฒ๐ฎ๐๐ต๐ฒ๐ฟ: ${weather.icon} ${weather.weatherType}\n๐ ${weather.description}\n๐ฏ ${weather.cropBonuses}\n` |
| : ""; |
|
|
| const title = userFavorites.length > 0 |
| ? `โฅ๏ธ ${matchCount} ๐๐ฎ๐๐ผ๐ฟ๐ถ๐๐ฒ ๐ถ๐๐ฒ๐บ${matchCount > 1 ? "s" : ""} ๐๐ผ๐๐ป๐ฑ!` |
| : "๐พ ๐๐ฟ๐ผ๐ ๐ ๐๐ฎ๐ฟ๐ฑ๐ฒ๐ป โ ๐ง๐ฟ๐ฎ๐ฐ๐ธ๐ฒ๐ฟ"; |
|
|
| const messageKey = JSON.stringify({ title, sections, weatherInfo, updatedAt }); |
| const lastSent = lastSentCache.get(senderId); |
| if (lastSent === messageKey) continue; |
|
|
| lastSentCache.set(senderId, messageKey); |
|
|
| await sendMessage(senderId, |
| `${title}\n\n${sections.join("\n\n")}\n\n${weatherInfo}๐
Updated at (PH): ${updatedAt}` |
| ); |
| } |
| } catch (error) { |
| console.error('Error processing WebSocket message:', error); |
| } |
| }); |
|
|
| sharedWebSocket.on("close", () => { |
| clearInterval(keepAliveInterval); |
| sharedWebSocket = null; |
| setTimeout(ensureWebSocketConnection, 3000); |
| }); |
|
|
| sharedWebSocket.on("error", () => { |
| sharedWebSocket?.close(); |
| }); |
| } |
|
|
| async function sendTyping(recipientId) { |
| try { |
| await axios.post(`https://graph.facebook.com/v12.0/me/messages?access_token=${PAGE_ACCESS_TOKEN}`, { |
| recipient: { id: recipientId }, |
| sender_action: 'typing_on' |
| }); |
| } catch (error) { |
| console.error('Error sending typing indicator:', error.message); |
| } |
| } |
|
|
| async function getStockData() { |
| try { |
| const [stockResponse, updateResponse] = await Promise.all([ |
| axios.get('https://kenlie.top/api/gag/stocks/'), |
| axios.get('https://jonell01-reuploadotherhruhh.hf.space/grow') |
| ]); |
| return { |
| stockData: stockResponse.data, |
| lastUpdated: updateResponse.data.lastUpdated |
| }; |
| } catch (error) { |
| console.error('Error fetching stock data:', error.message); |
| return null; |
| } |
| } |
|
|
| async function getWeatherData() { |
| try { |
| const response = await axios.get('https://kenlie.top/api/gag/weather/'); |
| return response.data; |
| } catch (error) { |
| console.error('Error fetching weather data:', error.message); |
| return null; |
| } |
| } |
|
|
| function formatStockMessage(data) { |
| if (!data || !data.stockData.status) return "โ ๏ธ Could not fetch stock data"; |
| |
| let message = `๐ ๐๐จ๐ฅ๐ฅ๐๐ก๐ง ๐ฆ๐ง๐ข๐๐ ๐จ๐ฃ๐๐๐ง๐๐ฆ ๐\n`; |
| message += `๐ LAST UPDATED: ${new Date(data.lastUpdated).toLocaleString()}\n\n`; |
| |
| const stockData = data.stockData.response; |
| const categories = { |
| gearStock: '๐ ๏ธ ๐๐๐๐ฅ', |
| eggStock: '๐ฅ ๐๐๐๐ฆ', |
| seedsStock: '๐ฑ ๐ฆ๐๐๐๐ฆ', |
| easterStock: '๐ฐ ๐๐๐ฆ๐ง๐๐ฅ', |
| nightStock: '๐ ๐ก๐๐๐๐ง', |
| honeyStock: '๐ฏ ๐๐ข๐ก๐๐ฌ', |
| cosmeticsStock: '๐ ๐๐ข๐ฆ๐ ๐๐ง๐๐๐ฆ ' |
| }; |
|
|
| for (const [categoryKey, categoryName] of Object.entries(categories)) { |
| if (stockData[categoryKey] && stockData[categoryKey].length > 0) { |
| message += `${categoryName}:\n`; |
| stockData[categoryKey].forEach(item => { |
| message += `โข ${item.name}: ${item.value}\n`; |
| }); |
| message += '\n'; |
| } |
| } |
| return message; |
| } |
|
|
| function formatWeatherMessage(weatherData) { |
| if (!weatherData || !weatherData.status) return "โ ๏ธ Could not fetch weather data"; |
| |
| const weatherEmojis = { |
| rain: '๐ง๏ธ ๐ฅ๐๐๐ก', |
| snow: 'โ๏ธ ๐ฆ๐ก๐ข๐ช', |
| thunderstorm: 'โก ๐ง๐๐จ๐ก๐๐๐ฅ๐ฆ๐ง๐ข๐ฅ๐ ', |
| bloodnight: '๐ฉธ ๐๐๐ข๐ข๐๐ก๐๐๐๐ง', |
| meteorshower: 'โ๏ธ ๐ ๐๐ง๐๐ข๐ฅ ๐ฆ๐๐ข๐ช๐๐ฅ', |
| disco: '๐ชฉ ๐๐๐ฆ๐๐ข', |
| jandelstorm: '๐ฏ๏ธ ๐๐๐ก๐๐๐๐ฆ๐ง๐ข๐ฅ๐ ', |
| blackhole: '๐ณ๏ธ ๐๐๐๐๐๐๐ข๐๐', |
| frost: 'โ๏ธ ๐๐ฅ๐ข๐ฆ๐ง ' |
| }; |
| |
| let message = 'โ
๐๐จ๐ฅ๐ฅ๐๐ก๐ง ๐ช๐๐๐ง๐๐๐ฅ ๐๐ข๐ก๐๐๐ง๐๐ข๐ก๐ฆ\n\n'; |
| const activeEvents = []; |
| |
| for (const [event, data] of Object.entries(weatherData.response)) { |
| if (data.active) { |
| activeEvents.push(`${weatherEmojis[event] || '๐'} ${event.toUpperCase()}`); |
| } |
| } |
| |
| if (activeEvents.length === 0) { |
| message += "โ๏ธ NO ACTIVE WEATHER EVENTS\n"; |
| } else { |
| message += "๐ข ๐๐๐ง๐๐ฉ๐ ๐ช๐๐๐ง๐๐๐ฅ ๐๐ฉ๐๐ก๐ง๐ฆ:\n"; |
| message += activeEvents.join('\n'); |
| } |
| |
| return message; |
| } |
|
|
| async function sendMessage(recipientId, messageText) { |
| try { |
| await axios.post(`https://graph.facebook.com/v12.0/me/messages?access_token=${PAGE_ACCESS_TOKEN}`, { |
| recipient: { id: recipientId }, |
| message: { text: messageText } |
| }); |
| } catch (error) { |
| console.error('Error sending message:', error.message); |
| } |
| } |
|
|
| function loadUserPreferences() { |
| try { |
| return JSON.parse(fs.readFileSync(usersPath)); |
| } catch { |
| return {}; |
| } |
| } |
|
|
| function saveUserPreferences(preferences) { |
| fs.writeFileSync(usersPath, JSON.stringify(preferences)); |
| } |
|
|
| function setupUserNotification(recipientId, intervalMinutes = 5) { |
| const preferences = loadUserPreferences(); |
| if (preferences[recipientId]?.intervalId) { |
| clearInterval(preferences[recipientId].intervalId); |
| } |
|
|
| const intervalId = setInterval(async () => { |
| const data = await getStockData(); |
| if (data) { |
| await sendMessage(recipientId, formatStockMessage(data)); |
| } |
| }, intervalMinutes * 60 * 1000); |
|
|
| preferences[recipientId] = { |
| notificationsOn: true, |
| interval: intervalMinutes, |
| intervalId: intervalId |
| }; |
| saveUserPreferences(preferences); |
| } |
|
|
| function turnOffNotifications(recipientId) { |
| const preferences = loadUserPreferences(); |
| if (preferences[recipientId]?.intervalId) { |
| clearInterval(preferences[recipientId].intervalId); |
| } |
| preferences[recipientId] = { |
| notificationsOn: false, |
| interval: null, |
| intervalId: null |
| }; |
| saveUserPreferences(preferences); |
| } |
|
|
| async function handleGagStockCommand(senderId, args) { |
| const subcmd = args[0]?.toLowerCase(); |
|
|
| if (subcmd === "fav") { |
| const action = args[1]?.toLowerCase(); |
| const input = args.slice(2).join(" ").split("|").map(i => cleanText(i)).filter(Boolean); |
| |
| if (!action || !["add", "remove"].includes(action) || input.length === 0) { |
| return await sendMessage(senderId, "๐ Usage: /gagstock fav add/remove Item1 | Item2"); |
| } |
|
|
| const favorites = loadFavorites(); |
| const userFavorites = favorites[senderId] || []; |
| const updated = new Set(userFavorites); |
|
|
| for (const name of input) { |
| if (action === "add") updated.add(name); |
| else if (action === "remove") updated.delete(name); |
| } |
|
|
| favorites[senderId] = Array.from(updated); |
| saveFavorites(favorites); |
| return await sendMessage(senderId, `โ
Favorite list updated: ${Array.from(updated).join(", ") || "(empty)"}`); |
| } |
|
|
| if (subcmd === "lastseen") { |
| const filters = args.slice(1).join(" ").split("|").map(c => c.trim().toLowerCase()).filter(Boolean); |
| const categories = filters.length > 0 ? filters : ["gear", "seed", "egg", "cosmetics", "event"]; |
| const lastSeen = loadLastSeen(); |
|
|
| let result = []; |
| for (const cat of categories) { |
| const entries = lastSeen[cat]; |
| if (!entries || Object.keys(entries).length === 0) continue; |
|
|
| const list = Object.entries(entries) |
| .sort((a, b) => new Date(b[1]) - new Date(a[1])) |
| .map(([name, date]) => `โข ${name}: ${getTimeAgo(date)}`); |
|
|
| result.push(`๐น ${cat.toUpperCase()} (${list.length})\n${list.join("\n")}`); |
| } |
|
|
| if (result.length === 0) { |
| return await sendMessage(senderId, "โ ๏ธ No last seen data found for the selected category."); |
| } |
|
|
| return await sendMessage(senderId, `๐ฆ ๐๐ฎ๐๐ ๐ฆ๐ฒ๐ฒ๐ป ๐๐๐ฒ๐บ๐\n\n${result.join("\n\n")}`); |
| } |
|
|
| if (subcmd === "off") { |
| if (!activeSessions.has(senderId)) { |
| return await sendMessage(senderId, "โ ๏ธ You don't have an active gagstock session."); |
| } |
| activeSessions.delete(senderId); |
| lastSentCache.delete(senderId); |
| return await sendMessage(senderId, "๐ Gagstock tracking stopped."); |
| } |
|
|
| if (subcmd !== "on") { |
| return await sendMessage(senderId, |
| "๐ Usage:\nโข /gagstock on\nโข /gagstock fav add Carrot | Watering Can\nโข /gagstock lastseen gear | seed\nโข /gagstock off" |
| ); |
| } |
|
|
| if (activeSessions.has(senderId)) { |
| return await sendMessage(senderId, "๐ก You're already tracking Gagstock. Use /gagstock off to stop."); |
| } |
|
|
| activeSessions.set(senderId, true); |
| await sendMessage(senderId, "โ
Gagstock tracking started via WebSocket!"); |
| ensureWebSocketConnection(); |
| } |
|
|
| app.get('/webhook', (req, res) => { |
| if (req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === VERIFY_TOKEN) { |
| res.status(200).send(req.query['hub.challenge']); |
| } else { |
| res.sendStatus(403); |
| } |
| }); |
|
|
| app.post('/webhook', async (req, res) => { |
| if (req.body.object === 'page') { |
| for (const entry of req.body.entry) { |
| for (const event of entry.messaging) { |
| if (event.message?.text) { |
| await handleMessage(event.sender.id, event.message.text); |
| } |
| } |
| } |
| res.status(200).send('EVENT_RECEIVED'); |
| } else { |
| res.sendStatus(404); |
| } |
| }); |
|
|
| async function handleMessage(senderId, messageText) { |
| const command = messageText.trim().toLowerCase(); |
| |
| await sendTyping(senderId); |
|
|
| if (command === '/help') { |
| const helpMessage = `๐ฑ ๐๐ฅ๐ข๐ช ๐ ๐๐๐ฅ๐๐๐ก ๐๐ข๐ง ๐๐๐๐ฃ ๐ฑ\n\n` + |
| `๐ /๐๐๐ผ๐ฐ๐ธ - Get current stock information\n` + |
| `โ
/๐๐ฒ๐ฎ๐๐ต๐ฒ๐ฟ - Get current weather conditions\n` + |
| `๐พ /๐ด๐ฎ๐ด๐๐๐ผ๐ฐ๐ธ - Real-time stock tracking\n` + |
| ` โข /๐ด๐ฎ๐ด๐๐๐ผ๐ฐ๐ธ ๐ผ๐ป - Start tracking\n` + |
| ` โข /๐ด๐ฎ๐ด๐๐๐ผ๐ฐ๐ธ ๐ผ๐ณ๐ณ - Stop tracking\n` + |
| ` โข /๐ด๐ฎ๐ด๐๐๐ผ๐ฐ๐ธ ๐ณ๐ฎ๐ ๐ฎ๐ฑ๐ฑ ๐ถ๐๐ฒ๐บ - Add favorite\n` + |
| ` โข /๐ด๐ฎ๐ด๐๐๐ผ๐ฐ๐ธ ๐น๐ฎ๐๐๐๐ฒ๐ฒ๐ป - View last seen items\n` + |
| `๐ /๐ป๐ผ๐๐ถ ๐ผ๐ป - Enable stock notifications\n` + |
| `๐ /๐ป๐ผ๐๐ถ ๐ผ๐ณ๐ณ - Disable notifications\n` + |
| `โฑ๏ธ /๐ป๐ผ๐๐ถ ๐ผ๐ป [๐บ๐ถ๐ป๐๐๐ฒ๐] - Set notification interval\n` + |
| `โน๏ธ /๐ต๐ฒ๐น๐ฝ - Show this help message`; |
| await sendMessage(senderId, helpMessage); |
| } |
| else if (command === '/stock') { |
| const data = await getStockData(); |
| await sendMessage(senderId, data ? formatStockMessage(data) : 'โ ๏ธ Could not fetch stock data'); |
| } |
| else if (command === '/weather') { |
| const weatherData = await getWeatherData(); |
| await sendMessage(senderId, weatherData ? formatWeatherMessage(weatherData) : 'โ ๏ธ Could not fetch weather data'); |
| } |
| else if (command.startsWith('/gagstock')) { |
| const args = command.split(' ').slice(1); |
| await handleGagStockCommand(senderId, args); |
| } |
| else if (command.startsWith('/noti')) { |
| const parts = command.split(' '); |
| if (parts[1] === 'on') { |
| const interval = parts[2] ? parseInt(parts[2]) : 5; |
| if (isNaN(interval) || interval < 1) { |
| await sendMessage(senderId, 'โ ๏ธ Please provide valid minutes (e.g., /noti on 5)'); |
| return; |
| } |
| setupUserNotification(senderId, interval); |
| await sendMessage(senderId, `๐ Notifications enabled! Updates every ${interval} minutes.`); |
| } |
| else if (parts[1] === 'off') { |
| turnOffNotifications(senderId); |
| await sendMessage(senderId, '๐ Notifications disabled!'); |
| } |
| else { |
| await sendMessage(senderId, 'โ ๏ธ Use /noti on, /noti off, or /noti on [minutes]'); |
| } |
| } |
| else { |
| await sendMessage(senderId, '๐ฟ Welcome! Grow a Garden Notifier Stocks User! Type /help for commands.'); |
| } |
| } |
|
|
| loadUserPreferences(); |
| const PORT = process.env.PORT || 7860; |
| app.listen(PORT, () => { |
| console.log(`Bot running on port ${PORT}`); |
| }); |