From 2ac0d2de6f1699dd6165ebb63d45e1a6ef0f4a02 Mon Sep 17 00:00:00 2001 From: Flummi Date: Tue, 9 Jan 2024 13:14:43 +0100 Subject: [PATCH] current status --- .gitignore | 3 + config.example.json | 10 ++ src/inc/lib.mjs | 34 +++++++ src/index.mjs | 235 ++++++++++++++++++++++++++++++++------------ 4 files changed, 217 insertions(+), 65 deletions(-) create mode 100644 .gitignore create mode 100644 config.example.json create mode 100644 src/inc/lib.mjs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..232ad0a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +conversations/ +config.json \ No newline at end of file diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..e4d158b --- /dev/null +++ b/config.example.json @@ -0,0 +1,10 @@ +{ + "api": { + "host": "localhost", + "port": 5001, + "bearer": "" + }, + "initiative": [], + "stops": ["###"], + "clients": [] +} diff --git a/src/inc/lib.mjs b/src/inc/lib.mjs new file mode 100644 index 0000000..d976ae0 --- /dev/null +++ b/src/inc/lib.mjs @@ -0,0 +1,34 @@ +import { exec as _exec } from 'node:child_process'; +import fetch from 'node-fetch'; + +export default new class { + exec(cmd) { + return new Promise((resolve, reject) => { + _exec(cmd, { maxBuffer: 5e3 * 1024 }, (err, stdout, stderr) => { + if(err) + return reject(err); + if(stderr) + console.error(stderr); + resolve({ stdout: stdout }); + }); + }); + }; + + rand(max = 1) { + return ~~(Math.random() * (max - 1) + 1); + }; + + async getPlayerlist(world, clanid) { + const res = await (await fetch(`https://${world}.freewar.de/freewar/dump_players.php`)).text(); + return res.split("\n").map(p => { + const player = p.split("\t"); + return { + id: +player[0], + name: player[1], + xp: +player[2], + rasse: player[3], + clanid: +player[4] + }; + }).filter(p => p.clanid === clanid) + }; +}; diff --git a/src/index.mjs b/src/index.mjs index 4474be8..8f40c25 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -2,100 +2,205 @@ import fs from 'node:fs/promises'; import cuffeo from 'cuffeo'; import fetch from 'node-fetch'; import oger from './inc/oger.mjs'; +import lib from './inc/lib.mjs'; import config from '../config.json' assert { type: 'json' }; -import initials from './inc/initials.json' assert { type: 'json' }; +import initials from '../data/initials.json' assert { type: 'json' }; const conversations = new Map(); const bot = await new cuffeo(config.clients); let last = false; +let playerlist = false; bot.on('message', async e => { + if(typeof e.message === 'undefined' || e.message.length <= 2) + return; + console.log(e.user.nick + ": " + e.message); const constr = `${e.network}::${e.channelid}`; - if(e.message?.match(/^!perf/i)) { - return await e.reply(JSON.stringify(await (await fetch(`http://${config.api.host}:${config.api.port}/api/extra/perf`)).json())); + if(e.message.match(/^!claninfo/i)) { + playerlist = await lib.getPlayerlist("welt13", 4760); + + return await e.reply( + playerlist.map(p => p.name).join(", ") + ); } - if(e.message?.match(/^!model/i)) { + if(e.message.match(/^!gens/i)) { + const perf = await (await fetch(`http://${config.api.host}:${config.api.port}/api/extra/perf`)).json(); + return await e.reply(`total_gens: ${perf.total_gens}`); + } + + if(e.message.match(/^!model/i)) { return await e.reply((await (await fetch(`http://${config.api.host}:${config.api.port}/api/v1/model`)).json()).result); } - if(e.message?.match(/^!channelid/i)) { + if(e.message.match(/^!messages/i)) { + try { + const cons = (await fs.readdir("./data/conversations")).filter(f => f.endsWith(".json")); + let messages = 0; + for(const con of cons) { + messages += JSON.parse(await fs.readFile(`./data/conversations/${con}`, 'utf8')).length; + } + + let output = []; + output.push(`Nachrichten insgesamt: ${messages}`); + + if(cons.includes(constr+'.json')) { + output.push(`Davon in diesem Kanal: ${JSON.parse(await fs.readFile(`./data/conversations/${constr}.json`, 'utf8')).length}`); + } + + return await e.reply(output.join(' | ')); + } catch(err) { + console.error(err); + return; + } + } + + if(e.message.match(/^!gpustat/i)) { + const gpustat = JSON.parse((await lib.exec('gpustat -a --json')).stdout); + const gpu = gpustat.gpus[0]; + + const output = [ + `${gpu['temperature.gpu']}°C, ${gpu['fan.speed']} rpm`, + `${gpu['memory.used']} / ${gpu['memory.total']} MB`, + `${gpu['power.draw']} / ${gpu['enforced.power.limit']} W` + ].join(' | '); + return await e.reply(`${gpustat.hostname}: ${gpu.name}\n${output}`); + } + + if(e.message.match(/^!channelid/i)) { return await e.reply(e.channelid); } - if(!conversations.has(constr)) { // get conv from json - try { - const tmpcon = JSON.parse(await fs.readFile(`./src/conversations/${constr}.json`, 'utf8')); + if(e.message.match(/^!reset/i)) { + conversations.set(constr, []); + await fs.writeFile(`./data/conversations/${constr}.json`, JSON.stringify([])); + return await e.reply("Konversation wurde erfolgreich zurückgesetzt."); + } + + let conv = conversations.has(constr) ? conversations.get(constr) : []; + + if(!conv.length) { + try { // get conv from json + const tmpcon = JSON.parse(await fs.readFile(`./data/conversations/${constr}.json`, 'utf8')); if(tmpcon) { - conversations.set(constr, tmpcon); + conv = tmpcon; console.log(`Konversation von ${constr} geladen. ${tmpcon.length} Einträge.`); } - } catch(err) {} + } catch(err) { + console.error(err); + conversations.set(constr, conv); + } } - if(e.message?.match(/ra+i+ne+r/i)) { + conv.push({ + role: 'user', + content: `${e.user.nick} schreibt: ${e.message.trim()}` + }); + await fs.writeFile(`./data/conversations/${constr}.json`, JSON.stringify(conv)); + conversations.set(constr, conv); - if(last) - return await e.reply(`ich antworte bereits ${last}!`); + if(!e.message?.match(/ra+i+ne+r/i)) { + if(!config.initiative.includes(constr)) + return; - const actcon = []; + let probability = 20; // default, 5% - if(conversations.has(constr)) - actcon.push(...conversations.get(constr)); - else { - actcon.push({ - "role": "system", - "content": initials[e.channelid] ? initials[e.channelid].lore : initials['default'].lore - }); - actcon.push({ - "role": "system", - "content": "eingehende Chatnachrichten sind immer folgendermaßen aufgebaut: \"%USERNAME% schreibt: %COMMAND%\". Du schreibst das aber nicht." - }); + if(e.message.match(/.*\?$/)) { + probability = 5; // 20% } + if(lib.rand(probability) !== 1) { + return; + } + } + + + // Rainer matched + if(last) + return await e.reply(`ich antworte bereits ${last}!`); + + const actcon = [{ + "role": "system", + "content": initials[ initials[constr] ? constr : 'default' ].lore + }, { + "role": "system", + "content": "eingehende Chatnachrichten sind immer folgendermaßen aufgebaut: \"%USERNAME% schreibt: %COMMAND%\". Du schreibst das aber nicht." + }, { + "role": "system", + "content": `Aktuelles Datum und Zeit: ${(new Date()).toLocaleString()}` + }]; + + if(constr === 'Discord::1143415824539992206') { + if(!playerlist) + playerlist = await lib.getPlayerlist('welt13', 4760); + actcon.push({ - role: 'user', - content: `${e.user.nick} schreibt: ${e.message.trim()}` + "role": "user", + "content": "Unser Clan besteht aus folgenden Mitgliedern:\n" + playerlist.map(p => `${p.name}: ${p.xp} XP, Rasse: ${p.rasse}`).join("\n") }); - - const opts = { - method: 'POST', - port: 5001, - headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${config.api.bearer}` - }, - body: JSON.stringify({ - mode: "chat", - instruction_template: "Alpaca", - stream: false, - max_tokens: 1024, - stop: ['###', ' <|endoftext|>', '---'], - messages: actcon - }) - }; - - last = e.user.nick; - - const res = await (await fetch(`http://${config.api.host}:${config.api.port}/v1/chat/completions`, opts)).json(); - - if(res.choices[0]?.message) { - if(res.choices[0].message.length <= 2) - return await e.reply('ich habe dazu nichts zu sagen, du Birne.'); - - res.choices[0].message.content = res.choices[0].message.content.split(/(###|---)/)[0]; - - actcon.push(res.choices[0].message); - conversations.set(constr, actcon); - - console.log("Rainer: " + res.choices[0].message.content.trim()); - last = false; - - await fs.writeFile(`./src/conversations/${constr}.json`, JSON.stringify(conversations.get(constr))); - return await e.reply(res.choices[0].message.content.trim()); - } - last = false; } + + actcon.push(...conv, { + role: 'user', + content: `${e.user.nick} schreibt: ${e.message.trim()}` + }); + + const opts = { + method: 'POST', + port: 5001, + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${config.api.bearer}` + }, + body: JSON.stringify({ + mode: "chat", + instruction_template: "Alpaca", + stream: false, + max_tokens: 768, + stop: config.stops, + messages: actcon + }) + }; + + last = e.user.nick; + + let res; + + try { + res = await (await fetch(`http://${config.api.host}:${config.api.port}/v1/chat/completions`, opts)).json(); + } catch(err) { + last = false; + return await e.reply('Backend ist gerade offline. Sorreh.'); + } + + if(res?.choices?.[0]?.message) { + if(res.choices[0].message.length <= 2) + return await e.reply('ich habe dazu nichts zu sagen, du Birne.'); + + res.choices[0].message.content = res.choices[0].message.content.split(/(###|---|ASSISTANT)/)[0]; + res.choices[0].message.content = res.choices[0].message.content.replace(/^Rainer( schreibt| antwortet)?: /, ''); + + conv.push(res.choices[0].message); + conversations.set(constr, conv); + await fs.writeFile(`./data/conversations/${constr}.json`, JSON.stringify(conv)); + + console.log("Rainer: " + res.choices[0].message.content.trim()); + + try { + if(initials[constr] && initials[constr].langmod === 'oger') + await e.reply(oger(res.choices[0].message.content.trim())); + else + await e.reply(res.choices[0].message.content.trim()); + } catch(err) { + await e.reply('ups, kann ich nicht schreiben, wäre zu viel gewesen.'); + } + } + else { + await e.reply('Hilfe, Fehler!'); + } + + last = false; + return; });