Initial commit

This commit is contained in:
2026-01-11 23:23:53 +01:00
commit 62d8ae1034
2 changed files with 206 additions and 0 deletions

201
discord.mjs Normal file
View File

@@ -0,0 +1,201 @@
// ────────────────────────────────────────────────────────────────────────────────
// bot.js Discord bot rewritten for Node 25+ and Ollama (via run_prompt2)
// ────────────────────────────────────────────────────────────────────────────────
import { Client, GatewayIntentBits, Partials, PermissionsBitField } from 'discord.js';
import * as SECRET from '/srv/Projekt/dicsordbot/secret.mjs';
/* -------------------------------------------------------------------------- */
/* 1⃣ Utility run_prompt2 (Ollama helper) the same you gave me */
/* -------------------------------------------------------------------------- */
const MODEL = 'gpt-oss:20b';
/**
* Query the local Ollama server.
*
* @param {string} system_prompt Systemmessage that sets the persona.
* @param {string} model Model name.
* @param {Array<{role:string,content:string}>} pieces The chat messages.
* @param {object} options Optional Ollama options.
* @returns {Promise<{status:number, response?:object, error?:string}>}
*/
export async function run_prompt2(system_prompt, model, pieces, options = { num_ctx: 16384 }) {
const payload = {
model,
messages: [
{ role: 'system', content: system_prompt },
...pieces.map(entry => ({ role: 'user', content: entry }))
],
stream: false,
options,
};
const response = await fetch('http://localhost:11434/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (response.ok) {
return { status: response.status, response: await response.json() };
} else {
return { status: response.status, error: await response.text() };
}
}
/* -------------------------------------------------------------------------- */
/* 2⃣ Discord bot configuration intents & partials */
/* -------------------------------------------------------------------------- */
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMembers,
],
partials: [Partials.Message, Partials.Channel], // safety
});
/* -------------------------------------------------------------------------- */
/* 3⃣ Secrets pulled from secret.mjs */
/* -------------------------------------------------------------------------- */
const { API_TOKEN, PERMISSIONS, SERVER_ID, CHANNEL_ID } = SECRET;
/* -------------------------------------------------------------------------- */
/* 4⃣ Allowlist only these user IDs may invoke the bot */
/* -------------------------------------------------------------------------- */
const ALLOW_LIST = new Set([
267085394548490241n,
310828422362431490n,
450834412049924097n,
277582984129806337n,
622871531881627648n,
]);
/* -------------------------------------------------------------------------- */
/* 5⃣ Helpers */
/* -------------------------------------------------------------------------- */
function splitDiscordMessage(text) {
const MAX = 1900; // buffer
const chunks = [];
let start = 0;
while (start < text.length) {
let end = Math.min(start + MAX, text.length);
const nl = text.lastIndexOf('\n', end);
const sp = text.lastIndexOf(' ', end);
if (nl > start) end = nl + 1;
else if (sp > start) end = sp + 1;
chunks.push(text.slice(start, end));
start = end;
}
return chunks;
}
/* -------------------------------------------------------------------------- */
/* 6⃣ Core message handler */
/* -------------------------------------------------------------------------- */
async function handleMessage(msg) {
if (msg.author.id === client.user.id) return; // ignore self
// Only process messages from the target guild & channel
if (msg.guild?.id !== SERVER_ID) return;
//if (msg.channel.id !== CHANNEL_ID) return;
// Allowlist check
/*
if (!ALLOW_LIST.has(BigInt(msg.author.id))) {
await msg.reply(
`Currently only people in the allow list may use this bot. If you want to participate, just let <@310828422362431490> know! <:astral_yak:1098185039805681755>`
);
return;
}
*/
const mention = `<@${client.user.id}>`;
if (!msg.content.includes(mention)) return; // not a mention
/* 5a Build the system prompt (Silicon Duck persona) */
const systemPrompt = `You are Silicon Duck, a helpful, cheerful, and generally excited creative Discord bot in the server 'Mikael Lövqvist's shenanigans'. You do NOT retain any history across messages. Your tag is <@${client.user.id}>, do not tag yourself in replies. The current channel is '${msg.channel.name}'. The user is ${msg.author.username} with tag <@${msg.author.id}>. You should not produce code examples unless explicitly asked. Do not make markdown tables, discord does not support those. You are running the model ${MODEL} using Ollama and OpenWebUI. Dial back emoji use.`;
/* 5b Check for special commands that use PERMISSIONS */
if (msg.content.startsWith('!create ')) {
// !create <channelname>
const name = msg.content.slice('!create '.length).trim();
if (!name) return msg.reply('Usage: `!create <channel-name>`');
try {
const newCh = await msg.guild.channels.create({
name,
type: 0, // TextChannel
permissionOverwrites: [
{
id: msg.guild.id, // @everyone
allow: Number(BigInt(PERMISSIONS)), // use the provided bitmask
},
],
});
await msg.reply(`✅ Created ${newCh.name} with the requested permissions.`);
} catch (err) {
console.error('Channelcreate error', err);
await msg.reply('❌ Could not create the channel.');
}
return; // finished command
}
/* 5c Build the system prompt (Silicon Duck persona) */
const userMessage = msg.content;
/* 5d Query Ollama via run_prompt2 */
let runResponse;
try {
const reply = await run_prompt2(systemPrompt, MODEL, [userMessage], { num_ctx: 16384 });
if (reply.status !== 200) {
console.error('Ollama error', reply.error);
await msg.reply('Sorry, I ran into an error while thinking.');
return;
}
runResponse = reply.response;
} catch (e) {
console.error('Error calling Ollama:', e);
await msg.reply('Sorry, I couldnt reach the AI service.');
return;
}
/* 5e Extract the answer text */
const answer = runResponse?.message?.content ?? '';
if (!answer) {
await msg.reply('Sorry, I didnt receive a reply from the AI.');
return;
}
/* 5f Send the answer back to Discord (chunked) */
const chunks = splitDiscordMessage(answer);
for (const chunk of chunks) await msg.channel.send(chunk);
console.log(`Answered to ${msg.author.username} (${msg.author.id}) in ${chunks.length} chunk(s).`);
}
/* -------------------------------------------------------------------------- */
/* 7⃣ Hook up Discord events */
/* -------------------------------------------------------------------------- */
client.once('clientReady', () => {
console.log(`✅ Logged in as ${client.user.tag}`);
});
client.on('messageCreate', handleMessage);
/* -------------------------------------------------------------------------- */
/* 8⃣ Start the bot */
/* -------------------------------------------------------------------------- */
client.login(API_TOKEN).catch(err => {
console.error('❌ Failed to login', err);
process.exit(1);
});

5
package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"discord.js": "^14.25.1"
}
}