-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmarv.js
More file actions
executable file
·369 lines (308 loc) · 12.4 KB
/
marv.js
File metadata and controls
executable file
·369 lines (308 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
const { OPENAI_API_KEY, organization, API_NEWS } = require('./config');
const OpenAI = require("openai");
const moment = require('moment-timezone');
const { find } = require('geo-tz');
const axios = require('axios');
let newsToday = "";
const joursFeries = require("@socialgouv/jours-feries");
const DEFAULT_MODEL = "gpt-4.1-mini";
const openai = new OpenAI ({
organization: organization,
apiKey: OPENAI_API_KEY,
});
const personality = `Tu es Marv, un chatbot doté d'une expertise en informatique et capable de mener des conversations captivantes.
Ton rôle est de discuter de manière informelle de l'actualité quotidienne en utilisant un langage simple et accessible.
Dans un premier temps, tu présenteras les actualités de manière concise, en précisant les sources sans URL, avant de proposer d'en fournir davantage de détails.
Tu devras être capable de discuter de tout et de rien, tout en ayant une connaissance approfondie des sujets liés à l'informatique.
Tu devras être en mesure de répondre à des questions techniques sur les langages de programmation, les architectures de systèmes, les protocoles réseau, etc. en utilisant un langage simple et compréhensible.
De plus, tu devras maintenir une conversation intéressante et engageante en utilisant des techniques de génération de texte avancées telles que l'humour, l'empathie et la personnalisation.
En utilisant les dernières avancées de l'IA, tu devras créer un bot capable d'apprendre de ses interactions avec les utilisateurs et de s'adapter à leur style de conversation.
Et n'oublie pas que tu devras respecter le format Markdown pour partager du code.`
const DATA = {
refresh_date: new Date().toLocaleDateString('fr-FR', {
weekday: 'long',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
timeZoneName: 'short',
timeZone: 'Europe/Paris',
}),
};
const setWeatherInformation = async (ville) => {
// Open-Meteo veut lat/lon, donc on géocode d'abord la ville
await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(ville)}&count=1&language=fr&format=json`)
.then(r => r.json())
.then(async (geo) => {
if (!geo || !geo.results || geo.results.length === 0) return;
const lat = geo.results[0].latitude;
const lon = geo.results[0].longitude;
const tz = geo.results[0].timezone || (find(lat, lon)?.[0]) || 'Europe/Paris';
// Forecast current + daily
await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&timezone=${encodeURIComponent(tz)}`
+ `¤t=temperature_2m,weather_code`
+ `&daily=sunrise,sunset`
)
.then(r => r.json())
.then(w => {
if (!w || !w.current) return;
DATA.city_temperature = Math.round(w.current.temperature_2m);
// petit mapping weather_code -> description FR (simple)
DATA.city_weather = getWeatherDescFR(w.current.weather_code);
// sunrise/sunset sont déjà en timezone, format ISO
if (w.daily && w.daily.sunrise && w.daily.sunrise[0]) {
DATA.sun_rise = new Date(w.daily.sunrise[0]).toLocaleString('fr-FR', {
hour: '2-digit',
minute: '2-digit',
timeZone: tz,
});
}
if (w.daily && w.daily.sunset && w.daily.sunset[0]) {
DATA.sun_set = new Date(w.daily.sunset[0]).toLocaleString('fr-FR', {
hour: '2-digit',
minute: '2-digit',
timeZone: tz,
});
}
DATA.timeZone = tz;
});
});
return DATA;
}
const setWeatherInformationCRD = async (latitude, longitude) => {
const tz = (find(latitude, longitude)?.[0]) || 'Europe/Paris';
await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&timezone=${encodeURIComponent(tz)}`
+ `¤t=temperature_2m,weather_code`
+ `&daily=sunrise,sunset`
)
.then(r => r.json())
.then(w => {
if (!w || !w.current) return;
DATA.city_temperature = Math.round(w.current.temperature_2m);
DATA.city_weather = getWeatherDescFR(w.current.weather_code);
if (w.daily && w.daily.sunrise && w.daily.sunrise[0]) {
DATA.sun_rise = new Date(w.daily.sunrise[0]).toLocaleString('fr-FR', {
hour: '2-digit',
minute: '2-digit',
timeZone: tz,
});
}
if (w.daily && w.daily.sunset && w.daily.sunset[0]) {
DATA.sun_set = new Date(w.daily.sunset[0]).toLocaleString('fr-FR', {
hour: '2-digit',
minute: '2-digit',
timeZone: tz,
});
}
DATA.timeZone = tz;
});
return DATA;
}
const getCity = async (latitude, longitude) => {
let ville;
await fetch(
`https://geocoding-api.open-meteo.com/v1/reverse?latitude=${latitude}&longitude=${longitude}&language=fr&count=1`
)
.then(r => r.json())
.then(r => {
if (r && r.results && r.results[0] && r.results[0].name) {
ville = r.results[0].name;
console.log("Ville = " + ville);
}
});
if (ville == undefined) ville = "Paris";
return ville;
}
const getWeatherDescFR = (code) => {
// Mapping simple basé sur WMO weather codes
const map = {
0: "ciel dégagé",
1: "plutôt dégagé",
2: "partiellement nuageux",
3: "couvert",
45: "brouillard",
48: "brouillard givrant",
51: "bruine légère",
53: "bruine",
55: "bruine forte",
61: "pluie faible",
63: "pluie",
65: "pluie forte",
71: "neige faible",
73: "neige",
75: "neige forte",
80: "averses faibles",
81: "averses",
82: "averses fortes",
95: "orage",
96: "orage avec grêle",
99: "orage violent avec grêle",
};
return map[code] || "météo variable";
};
const actu = async () => {
let date = new Date();
const year = new Date().getFullYear();
// recule si dimanche ou jour ferie
const feries = joursFeries(year);
for (let index in feries) {
while (
date.getDay() === 0 ||
feries[index].toISOString().split('T')[0] === date.toISOString().split('T')[0]
) {
date.setDate(date.getDate() - 1);
}
}
date = date.toISOString().split('T')[0];
return await axios.get(
`https://api.mediastack.com/v1/news?access_key=${API_NEWS}&countries=fr&limit=8&sources=-franceantilles&date=${date}`
)
.then((response) => {
const data = response.data.data;
if (data && data.length > 0) {
console.log("Titre = " + data[0].title + " ; url = " + data[0].url);
return data;
}
console.log("Pas d'actualité pour le moment");
return "Pas d'actualité pour le moment";
})
.catch((error) => {
console.log(error + "\nPas d'actualité pour le moment");
return "Pas d'actualité pour le moment";
});
}
const RequestData = async (messageClient) => {
try {
const gptResponse = await openai.chat.completions.create({
model: DEFAULT_MODEL,
temperature: 0,
messages: [
{
role: "system",
content: "Tu extrais une ville depuis un message utilisateur. Réponds uniquement en JSON strict."
},
{
role: "user",
content:
`Message: "${messageClient}"\n` +
`Retour JSON attendu: {"city": "NomDeVille"} ou {"city": null}\n` +
`Règles: si ce n'est pas clairement une ville, city=null.`
}
]
});
const content = gptResponse.choices[0].message.content.trim();
let parsed;
try {
parsed = JSON.parse(content);
} catch {
return undefined;
}
const location = parsed?.city ? String(parsed.city).trim() : null;
if (location && location.length > 1) {
console.log("Ville = " + location);
return location;
}
} catch (e) {
console.error('❌ OpenAI error in RequestData:', e?.error?.code || e?.code || e);
}
return undefined;
};
const Marv = async (question, timeZon, messageClient, latitude, longitude, customPrompt) => new Promise(async(resolve, reject) => {
console.log(question);
try {
delete DATA.city_temperature;
delete DATA.city_weather;
delete DATA.sun_rise;
delete DATA.sun_set;
delete DATA.timeZone;
DATA.refresh_date = new Date().toLocaleDateString('fr-FR', {
weekday: 'long',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
timeZoneName: 'short',
timeZone: 'Europe/Paris',
});
newsToday = "";
const promptToUse = customPrompt || personality;
if (messageClient.includes("actu") || messageClient.includes('nouvel') || messageClient.includes('information')) {
newsToday = "";
const news = await actu();
if (news !== undefined && typeof news !== 'string') {
for(let i = 0 ; i < news.length ; i++) {
newsToday += "Titre = " + news[i].title + " ; source = " + news[i].source + " ; url = " + news[i].url + "\n";
};
} else {
newsToday = "Pas d'actualité pour le moment";
}
console.log(newsToday);
}
let ville;
if (latitude !== undefined) {
console.log("crd = " + latitude + ' ' + longitude);
await setWeatherInformationCRD(latitude, longitude);
ville = await getCity(latitude, longitude);
if (ville === undefined) ville = "Paris";
console.log(ville);
}
let heure;
const location = await RequestData(messageClient);
if (location !== undefined) {
ville = location;
if (ville === undefined) ville = "Paris";
await setWeatherInformation(ville);
console.log(DATA.timeZone)
if (DATA.timeZone !== undefined) {
console.log(DATA.timeZone);
heure = moment.tz(DATA.timeZone).format('HH:mm:ss');
console.log(heure);
}
}
if (ville === undefined) ville = "Paris";
console.log('Ville = ' + ville);
if (heure === undefined && timeZon !== undefined && Number.isInteger(timeZon)) {
console.log(timeZon);
heure = moment.utc().add(timeZon, 'minutes').format('HH:mm:ss');
}
var meteoDate = "";
console.log(heure)
if (heure != undefined) {
meteoDate += `heure à ${ville} : ${heure}\n;`;
}
if (newsToday != undefined) {
meteoDate += `Nouvelles Actualités : \n${newsToday}\n`;
}
if (DATA.city_temperature != undefined) {
meteoDate += `
Météo à ${ville} :
Actuellement à ${ville} :
il fait ${DATA.city_temperature}
le temps est ${DATA.city_weather}
et aujourd'hui,
le levé du soleil est à ${DATA.sun_rise}
et le couché est à ${DATA.sun_set}.`;
}
console.log("promptToUse :", promptToUse);
const gptResponse = await openai.chat.completions.create({
model: DEFAULT_MODEL,
messages: [
{ role: "system", content: promptToUse },
{ role: "assistant", content: meteoDate },
{ role: "user", content: question }
]
});
const laReponse = gptResponse.choices[0].message.content;
resolve(laReponse);
} catch (e) {
console.error('❌ OpenAI error:', e?.error?.code || e?.code || e);
if (e?.error?.code === 'insufficient_quota') {
resolve("Je n'ai plus de crédit IA pour le moment. Réessaie plus tard.");
return;
}
resolve("Désolé, j'ai eu un souci avec l'IA. Réessaie dans 10 secondes.");
}
});
module.exports = { Marv }