@@ -12,6 +12,7 @@ const cors = require('cors');
1212const path = require ( 'path' ) ;
1313const fs = require ( 'fs' ) ;
1414const { DateTime } = require ( 'luxon' ) ;
15+ const FormData = require ( 'form-data' ) ;
1516require ( 'dotenv' ) . config ( ) ;
1617
1718// ============================================
@@ -37,6 +38,7 @@ const CONFIG = {
3738 } ,
3839 GOOGLE_AI_API_KEY : process . env . GOOGLE_AI_API_KEY ,
3940 CALENDARIFIC_API_KEY : process . env . CALENDARIFIC_API_KEY || 'demo' ,
41+ IMGBB_API_KEY : process . env . IMGBB_API_KEY || 'your_imgbb_key_here' , // Add IMGBB_API_KEY to .env
4042 TIMEZONE : process . env . TIMEZONE || 'Asia/Kolkata' ,
4143 SCHEDULE_TIME : process . env . SCHEDULE_TIME || '0 0 * * *' ,
4244 DASHBOARD_PORT : process . env . DASHBOARD_PORT ? Number ( process . env . DASHBOARD_PORT ) : 3000 ,
@@ -351,6 +353,48 @@ function todayDateString() {
351353 return nowInZone ( ) . toISODate ( ) ;
352354}
353355
356+ // ============================================
357+ // IMAGE UTILITIES
358+ // ============================================
359+
360+ // Function to upload image to imgbb
361+ async function uploadToImgbb ( imageBuffer , filename ) {
362+ if ( ! CONFIG . IMGBB_API_KEY || CONFIG . IMGBB_API_KEY === 'your_imgbb_key_here' ) {
363+ throw new Error ( 'Imgbb API key not configured' ) ;
364+ }
365+
366+ const form = new FormData ( ) ;
367+ form . append ( 'image' , imageBuffer , { filename } ) ;
368+
369+ try {
370+ const response = await axios . post (
371+ `https://api.imgbb.com/1/upload?key=${ CONFIG . IMGBB_API_KEY } ` ,
372+ form ,
373+ { headers : form . getHeaders ( ) , timeout : 30000 }
374+ ) ;
375+
376+ if ( response . data ?. success ) {
377+ return response . data . data . url ;
378+ } else {
379+ throw new Error ( 'Imgbb upload failed' ) ;
380+ }
381+ } catch ( error ) {
382+ log ( `Imgbb upload failed: ${ error . message } ` , 'ERROR' ) ;
383+ throw error ;
384+ }
385+ }
386+
387+ // Function to validate image URL
388+ async function validateImageUrl ( url ) {
389+ try {
390+ const response = await axios . head ( url , { timeout : 10000 } ) ;
391+ const contentType = response . headers [ 'content-type' ] ;
392+ return response . status === 200 && contentType && contentType . startsWith ( 'image/' ) ;
393+ } catch ( error ) {
394+ return false ;
395+ }
396+ }
397+
354398// ============================================
355399// AI MESSAGE GENERATION (Gemini) - fallback template if no key
356400// ============================================
@@ -443,7 +487,7 @@ Return ONLY the enhanced message. `;
443487 const response = await axios . post (
444488 `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${ CONFIG . GOOGLE_AI_API_KEY } ` ,
445489 {
446- contents : [ { parts : [ { text : prompt } ] } ,
490+ contents : [ { parts : [ { text : prompt } ] } ] ,
447491 generationConfig : { temperature : 0.7 , maxOutputTokens : 250 }
448492 } ,
449493 { headers : { 'Content-Type' : 'application/json' } , timeout : 15000 }
@@ -521,7 +565,7 @@ async function sendHolidayAnnouncement(holiday, message, imageUrl, webhookUrl, r
521565
522566async function sendCustomAnnouncement ( data ) {
523567 try {
524- const { title, message, roles, imageUrl, webhookChannel, useAI, serverInfo } = data ;
568+ const { title, message, roles, imageUrl, imageFile , webhookChannel, useAI, serverInfo } = data ;
525569 const channelKey = webhookChannel || 'ANNOUNCEMENTS' ;
526570
527571 // Get server-specific webhook or fallback to default
@@ -562,10 +606,22 @@ async function sendCustomAnnouncement(data) {
562606 } ,
563607 timestamp : new Date ( ) . toISOString ( )
564608 } ;
565- // Validate image URL
566- if ( imageUrl ) {
567- if ( imageUrl . startsWith ( 'data:image' ) || ! imageUrl . startsWith ( 'https://' ) ) {
568- return { success : false , error : 'Invalid image URL. Only public HTTPS URLs are supported.' } ;
609+
610+ // Handle image
611+ if ( imageFile && imageFile . buffer && imageFile . originalname ) {
612+ // Upload file to imgbb
613+ try {
614+ const uploadedUrl = await uploadToImgbb ( imageFile . buffer , imageFile . originalname ) ;
615+ embed . image = { url : uploadedUrl } ;
616+ log ( 'Image uploaded to imgbb successfully' , 'SUCCESS' ) ;
617+ } catch ( error ) {
618+ return { success : false , error : 'Failed to upload image' } ;
619+ }
620+ } else if ( imageUrl ) {
621+ // Validate image URL
622+ const isValid = await validateImageUrl ( imageUrl ) ;
623+ if ( ! isValid ) {
624+ return { success : false , error : 'Invalid or inaccessible image URL' } ;
569625 }
570626 embed . image = { url : imageUrl } ;
571627 }
@@ -783,6 +839,10 @@ app.use(cors());
783839app . use ( express . json ( ) ) ;
784840app . use ( express . static ( 'public' ) ) ;
785841
842+ // Multer for file uploads
843+ const multer = require ( 'multer' ) ;
844+ const upload = multer ( { storage : multer . memoryStorage ( ) } ) ;
845+
786846app . get ( '/api/status' , ( req , res ) => {
787847 res . json ( {
788848 ...botStatus ,
@@ -827,9 +887,17 @@ app.post('/api/trigger/holiday', async (req, res) => {
827887 }
828888} ) ;
829889
830- app . post ( '/api/announcement/send' , async ( req , res ) => {
890+ // Updated to handle file uploads
891+ app . post ( '/api/announcement/send' , upload . single ( 'imageFile' ) , async ( req , res ) => {
831892 try {
832- const result = await sendCustomAnnouncement ( req . body ) ;
893+ const data = { ...req . body } ;
894+ if ( req . file ) {
895+ data . imageFile = {
896+ buffer : req . file . buffer ,
897+ originalname : req . file . originalname
898+ } ;
899+ }
900+ const result = await sendCustomAnnouncement ( data ) ;
833901 res . json ( result ) ;
834902 } catch ( error ) {
835903 res . json ( { success : false , error : error . message } ) ;
@@ -1066,7 +1134,7 @@ app.post('/api/servers/:serverId/webhooks/create', async (req, res) => {
10661134} ) ;
10671135
10681136// Send custom announcement to specific server
1069- app . post ( '/api/servers/:serverId/announcement/send' , async ( req , res ) => {
1137+ app . post ( '/api/servers/:serverId/announcement/send' , upload . single ( 'imageFile' ) , async ( req , res ) => {
10701138 try {
10711139 const { serverId } = req . params ;
10721140 const serverConfig = getServerConfig ( serverId ) ;
@@ -1088,6 +1156,13 @@ app.post('/api/servers/:serverId/announcement/send', async (req, res) => {
10881156 }
10891157 } ;
10901158
1159+ if ( req . file ) {
1160+ announcementData . imageFile = {
1161+ buffer : req . file . buffer ,
1162+ originalname : req . file . originalname
1163+ } ;
1164+ }
1165+
10911166 const result = await sendCustomAnnouncement ( announcementData ) ;
10921167 res . json ( result ) ;
10931168 } catch ( error ) {
@@ -1308,6 +1383,12 @@ app.post('/api/generate-image', async (req, res) => {
13081383 const [ width , height ] = ( size || '1024x1024' ) . split ( 'x' ) ;
13091384 const unsplashUrl = `https://source.unsplash.com/${ width } x${ height } /?${ encodeURIComponent ( keywords ) } ` ;
13101385
1386+ // Validate the generated URL
1387+ const isValid = await validateImageUrl ( unsplashUrl ) ;
1388+ if ( ! isValid ) {
1389+ throw new Error ( 'Generated URL is not accessible' ) ;
1390+ }
1391+
13111392 log ( `Using Unsplash image for: ${ keywords } ` , 'SUCCESS' ) ;
13121393 return res . json ( { success : true , imageUrl : unsplashUrl } ) ;
13131394 } catch ( err ) {
0 commit comments