|
|
|
|
|
require('dotenv').config(); |
|
|
|
|
|
const config = { |
|
|
|
|
|
hfModel: "Qwen/Qwen2.5-72B-Instruct", |
|
|
hfToken: process.env.HF_TOKEN, |
|
|
moroccanSites: [ |
|
|
'site:anapec.org', |
|
|
'site:alwadifa-maroc.com', |
|
|
'site:marocannonces.com', |
|
|
'site:rekrute.com', |
|
|
'site:linkedin.com/jobs/morocco', |
|
|
'site:welovers.ma' |
|
|
], |
|
|
puppeteerArgs: [ |
|
|
'--no-sandbox', |
|
|
'--disable-setuid-sandbox', |
|
|
'--disable-dev-shm-usage', |
|
|
'--disable-gpu' |
|
|
] |
|
|
}; |
|
|
|
|
|
|
|
|
const { HfInference } = require("@huggingface/inference"); |
|
|
const hf = new HfInference(config.hfToken); |
|
|
|
|
|
async function analyzeMessage(userMessage) { |
|
|
const systemPrompt = ` |
|
|
You are an expert Moroccan Job Recruiter Agent. |
|
|
User Message: "${userMessage}" |
|
|
|
|
|
Task: Analyze the intent. |
|
|
1. If user wants a job, extract: job_title (in French/English), city. |
|
|
2. If greeting, respond in Moroccan Darija. |
|
|
|
|
|
Output Format: You must reply with a valid JSON Object ONLY. Do not write any other text. |
|
|
Schema: |
|
|
{ |
|
|
"intent": "job_search" or "chat", |
|
|
"keywords": "job title keywords", |
|
|
"city": "city name", |
|
|
"reply": "Short response in Darija/Arabic to send to user" |
|
|
} |
|
|
`; |
|
|
|
|
|
try { |
|
|
const response = await hf.chatCompletion({ |
|
|
model: config.hfModel, |
|
|
messages: [ |
|
|
{ role: "system", content: "You are a strict JSON output machine." }, |
|
|
{ role: "user", content: systemPrompt } |
|
|
], |
|
|
max_tokens: 500, |
|
|
temperature: 0.3 |
|
|
}); |
|
|
|
|
|
const content = response.choices[0].message.content; |
|
|
|
|
|
|
|
|
const jsonMatch = content.match(/\{[\s\S]*\}/); |
|
|
if (jsonMatch) { |
|
|
return JSON.parse(jsonMatch[0]); |
|
|
} |
|
|
throw new Error("Failed to parse JSON"); |
|
|
|
|
|
} catch (error) { |
|
|
console.error("AI Error:", error); |
|
|
return { |
|
|
intent: "chat", |
|
|
reply: "سمح ليا، مافهمتش مزيان. عاود كتب ليا شنو بغيتي بالضبط." |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const { search } = require('duck-duck-scrape'); |
|
|
|
|
|
async function findJobs(keywords, city) { |
|
|
const sites = config.moroccanSites.join(' OR '); |
|
|
|
|
|
const query = `(${sites}) ${keywords} ${city} "2024" OR "2025"`; |
|
|
|
|
|
console.log(`🔎 Searching HF: ${query}`); |
|
|
|
|
|
try { |
|
|
const results = await search(query, { |
|
|
safeSearch: "off", |
|
|
time: "w" |
|
|
}); |
|
|
|
|
|
return results.results.slice(0, 3).map(r => ({ |
|
|
title: r.title, |
|
|
link: r.url, |
|
|
source: r.domain |
|
|
})); |
|
|
} catch (e) { |
|
|
console.error("Search Error:", e); |
|
|
return []; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const { Client, LocalAuth } = require('whatsapp-web.js'); |
|
|
const qrcode = require('qrcode'); |
|
|
|
|
|
|
|
|
let qrStream = { data: null }; |
|
|
|
|
|
const client = new Client({ |
|
|
authStrategy: new LocalAuth({ dataPath: '/app/.wwebjs_auth' }), |
|
|
puppeteer: { |
|
|
headless: true, |
|
|
executablePath: '/usr/bin/chromium', |
|
|
args: config.puppeteerArgs |
|
|
} |
|
|
}); |
|
|
|
|
|
client.on('qr', (qr) => { |
|
|
qrcode.toDataURL(qr, (err, url) => { |
|
|
qrStream.data = url; |
|
|
console.log('⚡ New QR Generated'); |
|
|
}); |
|
|
}); |
|
|
|
|
|
client.on('ready', () => { |
|
|
console.log('✅ Connected to WhatsApp!'); |
|
|
qrStream.data = "CONNECTED"; |
|
|
}); |
|
|
|
|
|
client.on('message', async msg => { |
|
|
if(msg.from.includes('@g.us')) return; |
|
|
|
|
|
|
|
|
const analysis = await analyzeMessage(msg.body); |
|
|
|
|
|
|
|
|
if (analysis.reply) await msg.reply(analysis.reply); |
|
|
|
|
|
|
|
|
if (analysis.intent === 'job_search') { |
|
|
const jobs = await findJobs(analysis.keywords, analysis.city); |
|
|
|
|
|
if (jobs.length > 0) { |
|
|
let replyText = "لقيت ليك هاد العروض: 🔥\n\n"; |
|
|
jobs.forEach(j => { |
|
|
replyText += `📌 *${j.title}*\n🔗 ${j.link}\n\n`; |
|
|
}); |
|
|
replyText += "الله يسهل! 🤲"; |
|
|
await client.sendMessage(msg.from, replyText); |
|
|
} else { |
|
|
await client.sendMessage(msg.from, "ما لقيت والو جديد هاد السيمانة 😔"); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
client.initialize(); |
|
|
|
|
|
|
|
|
const express = require('express'); |
|
|
const app = express(); |
|
|
const port = 7860; |
|
|
|
|
|
app.get('/', (req, res) => { |
|
|
if (qrStream.data === "CONNECTED") { |
|
|
res.send('<h1 style="color:green;text-align:center;margin-top:20%;">✅ Bot is Connected & Running!</h1>'); |
|
|
} else if (qrStream.data) { |
|
|
res.send(` |
|
|
<div style="text-align:center;margin-top:50px;"> |
|
|
<h1>Scan this QR Code</h1> |
|
|
<img src="${qrStream.data}" style="width:300px;"/> |
|
|
<p>Refresh page if code expired.</p> |
|
|
</div> |
|
|
`); |
|
|
} else { |
|
|
res.send('<h1 style="text-align:center;">Initializing... Wait 10 seconds and Refresh 🔄</h1>'); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.listen(port, () => console.log(`Server running on port ${port}`)); |
|
|
|