Compare commits

..

No commits in common. "5ca047cb3a5e83815651626a512e93352dc6c119bcf75020cf2f3e44cd171f81" and "29832dfce3c02a0f143b9c7ffa0affd81dbb8ab608ead6e45ef55f88244b2081" have entirely different histories.

14 changed files with 169 additions and 202 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "aslobot-matrix", "name": "aslobot-matrix",
"version": "0.7.0", "version": "0.6.0",
"description": "", "description": "",
"license": "ISC", "license": "ISC",
"author": "", "author": "",

View file

@ -14,12 +14,7 @@
"gain": 5, "gain": 5,
"startingRequirement": 50, "startingRequirement": 50,
"multiplier": 1.25, "multiplier": 1.25,
"timeout": 300000 "timeout": 60000
},
"money": {
"gain": 2,
"startingValue": 10,
"timeout": 3600000
}, },
"ai": { "ai": {
"api": { "api": {

View file

@ -4,31 +4,14 @@ import { type IUser, type TRole } from "./store/types.js";
import type { ILevel } from "./types.js"; import type { ILevel } from "./types.js";
const getUserById = (userId: string): IUser => { const getUserById = (userId: string): IUser => {
const user = state.users.find((user) => user.id === userId); return (
if (!user) { state.users.find((user) => user.id === userId) ?? {
return {
id: ":", id: ":",
role: "NONE", role: "NONE",
experience: 0, experience: 0,
money: 0,
aiCost: 0,
lastMessageTimestamp: 0, lastMessageTimestamp: 0,
lastExperienceGainTimestamp: 0,
lastMoneyGainTimestamp: 0,
};
} }
);
if (!user.experience) {
user.experience = 0;
}
if (!user.money) {
user.money = 0;
}
if (!user.aiCost) {
user.aiCost = 0;
}
return user;
}; };
const checkRoles = (roles: TRole[], userId: string) => { const checkRoles = (roles: TRole[], userId: string) => {

View file

@ -3,9 +3,6 @@ import type { ICallbackStore } from "../types.js";
import { config } from "../../config.js"; import { config } from "../../config.js";
import { getTextGemini, getImageGemini } from "../../services/ai/ai.js"; import { getTextGemini, getImageGemini } from "../../services/ai/ai.js";
import { alts } from "./alts.js"; import { alts } from "./alts.js";
import type { IAdminInstructions } from "./types.js";
import { getUserById } from "../../helpers.js";
import { prices } from "./prices.js";
let client: MatrixClient; let client: MatrixClient;
@ -18,58 +15,40 @@ const registerModuleAI = (
callbackStore.messageCallbacks.push({ callbackStore.messageCallbacks.push({
startConditions: [`${config.app.triggerPrefix}ai `], startConditions: [`${config.app.triggerPrefix}ai `],
callbackFunc: onAI, callbackFunc: onAI,
allowedRoles: ["USER", "MODERATOR", "ADMIN"],
}); });
callbackStore.messageCallbacks.push({ callbackStore.messageCallbacks.push({
startConditions: [`${config.app.triggerPrefix}img `], startConditions: [
...alts.map((alt) => `${alt.key} when`),
...alts.map((alt) => `${alt.key} ked`),
`!img`,
],
callbackFunc: onImageGen, callbackFunc: onImageGen,
allowedRoles: ["USER", "MODERATOR", "ADMIN"],
}); });
}; };
const onAI = async (text: string, roomId: string, sender: string) => { const onAI = async (text: string, roomId: string) => {
const user = getUserById(sender);
if (text.trim().length < 5) { if (text.trim().length < 5) {
return; return;
} }
let textMod = text.replace("!ai", "").trim().toLowerCase(); const responseAI = await getTextGemini(text.replace("!ai ", ""));
let instructions = { client.sendTextMessage(roomId, responseAI);
prefferedLanguages: ["english", "slovak"],
adminText: "Be concise, try to keep text as short as possible",
alts: alts,
} as IAdminInstructions;
textMod = `Admin Instructions:\n${instructions}\nPrompt:\n${textMod}`;
const responseAI = await getTextGemini(textMod);
user.aiCost += responseAI.tokens * prices.text;
client.sendTextMessage(roomId, responseAI.text);
}; };
const onImageGen = async (text: string, roomId: string, sender: string) => { const onImageGen = async (text: string, roomId: string) => {
const user = getUserById(sender);
let textMod = text.replace("!img", "").trim().toLowerCase(); let textMod = text.replace("!img", "").trim().toLowerCase();
alts.forEach((alt) => { alts.forEach((alt) => {
alt.keys.forEach((key) => { textMod = textMod.replaceAll(alt.key, alt.alt);
textMod = textMod.replaceAll(key, alt.alt);
});
}); });
const responseAI = await getImageGemini(textMod); const buffer = await getImageGemini(textMod);
if (!buffer || buffer.length < 10) {
user.aiCost += responseAI.tokens * prices.image;
if (!responseAI.image || responseAI.image.length < 10) {
return; return;
} }
const imageName = `photo-img-gen.png`; const imageName = `photo-img-gen.png`;
const uploadResult = await client.uploadContent(responseAI.image, { const uploadResult = await client.uploadContent(buffer, {
type: "image/png", type: "image/png",
name: imageName, name: imageName,
}); });
@ -80,7 +59,7 @@ const onImageGen = async (text: string, roomId: string, sender: string) => {
url: uploadResult.content_uri, url: uploadResult.content_uri,
info: { info: {
mimetype: "image/png", mimetype: "image/png",
size: responseAI.image.length, size: buffer.length,
}, },
}); });
}; };

View file

@ -2,73 +2,164 @@ import type { IAIAlt } from "./types.js";
const alts: IAIAlt[] = [ const alts: IAIAlt[] = [
{ {
keys: [ key: "satek",
"satek",
"sateg",
"sadeg",
"coty",
"satko",
"sadko",
"vlado",
"vladko",
"vladimir",
"macak",
"macag",
],
alt: "black cat", alt: "black cat",
}, },
{ {
keys: ["macica", "macico"], key: "sateg",
alt: "black cat",
},
{
key: "sadeg",
alt: "black cat",
},
{
key: "satko",
alt: "black cat",
},
{
key: "sadko",
alt: "black cat",
},
{
key: "vlado",
alt: "black cat",
},
{
key: "vladko",
alt: "black cat",
},
{
key: "vladimir",
alt: "black cat",
},
{
key: "macag",
alt: "black cat",
},
{
key: "vladko",
alt: "black cat",
},
{
key: "macica",
alt: "white cat", alt: "white cat",
}, },
{ {
keys: ["gabor", "gaber", "martin", "marting"], key: "macico",
alt: "white cat",
},
{
key: "gabor",
alt: "hedgehog", alt: "hedgehog",
}, },
{ {
keys: [ key: "martin",
"madys", alt: "hedgehog",
"mandak", },
"mandag", {
"mando", key: "madys",
"mandik",
"madik",
"madeg",
"madek",
],
alt: "beaver", alt: "beaver",
}, },
{ {
keys: ["janys", "jano", "janeg", "janek", "aslanek", "aslan", "aslo"], key: "mandak",
alt: "beaver",
},
{
key: "mando",
alt: "beaver",
},
{
key: "mandik",
alt: "beaver",
},
{
key: "madik",
alt: "beaver",
},
{
key: "madeg",
alt: "beaver",
},
{
key: "janys",
alt: "lion", alt: "lion",
}, },
{ {
keys: ["marek", "mareg", "macek", "maceg", "rod"], key: "jano",
alt: "lion",
},
{
key: "janeg",
alt: "lion",
},
{
key: "aslan",
alt: "lion",
},
{
key: "aslo",
alt: "lion",
},
{
key: "marek",
alt: "purple snake", alt: "purple snake",
}, },
{ {
keys: ["lara", "kostur", "kosturik"], key: "mareg",
alt: "purple snake",
},
{
key: "maceg",
alt: "purple snake",
},
{
key: "rod",
alt: "purple snake",
},
{
key: "lara",
alt: "ginger dog", alt: "ginger dog",
}, },
{ {
keys: ["tato"], key: "kostur",
alt: "green troll", alt: "ginger dog",
}, },
{ {
keys: ["mama", "monika", "monik"], key: "kosturik",
alt: "god's peasant", alt: "ginger dog",
}, },
{ {
keys: ["puk", "pucik", "pucig"], key: "tato",
alt: "fat rat", alt: "troll",
}, },
{ {
keys: ["becky", "beky", "beki"], key: "mama",
alt: "35 year old pink haired giantess", alt: "angel",
}, },
{ {
keys: ["veronika", "veronik", "wewe", "wonka"], key: "monik",
alt: "lgbt princess with curly hair", alt: "angel",
},
{
key: "puk",
alt: "rat",
},
{
key: "becky",
alt: "giantess",
},
{
key: "beky",
alt: "giantess",
},
{
key: "beki",
alt: "giantess",
},
{
key: "veronik",
alt: "lgbt princess",
}, },
]; ];

View file

@ -1,6 +0,0 @@
const prices = {
text: 0.000002,
image: 0.00003,
};
export { prices };

View file

@ -1,12 +1,6 @@
interface IAIAlt { interface IAIAlt {
keys: string[]; key: string;
alt: string; alt: string;
} }
interface IAdminInstructions { export { type IAIAlt };
prefferedLanguages: string[];
adminText: string;
alts: IAIAlt[];
}
export { type IAIAlt, type IAdminInstructions };

View file

@ -61,21 +61,17 @@ const onHelp = (_text: string, roomId: string) => {
client.sendHtmlMessage( client.sendHtmlMessage(
roomId, roomId,
"", "",
`<h3>Role: Any</h3> `<h3>Role: User</h3>
<ul> <ul>
<li><b>!ping</b> - Pong!</li> <li><b>!ping</b> - Pong!</li>
<li><b>!info</b> - Prints information about this bot</li> <li><b>!info</b> - Prints information about this bot</li>
<li><b>!say {text}</b> - Repeats your message</li> <li><b>!say {text}</b> - Repeats your message</li>
<li><b>!bowling {text}</b> - Repeats your message in bowling</li> <li><b>!bowling {text}</b> - Repeats your message in bowling</li>
<li><b>!help</b> - Prints this help message</li> <li><b>!help</b> - Prints this help message</li>
<li><b>!me</b> - Prints your role, level, experience, money and ai cost</li> <li><b>!level</b> - Prints your level and experience</li>
<li><b>!leaderboard</b> - Prints total user ranking</li> <li><b>!leaderboard</b> - Prints total user ranking</li>
<li><b>!aileaderboard</b> - Prints total user ai cost</li>
</ul>
<h3>Role: User</h3>
<ul>
<li><b>!ai {text}</b> - Say something to Gemini 3</li> <li><b>!ai {text}</b> - Say something to Gemini 3</li>
<li><b>!img {text}</b> - Generate an image</li> <li><b>!img {text}</b> - Generate an image (also works with "{name} when/ked {text}")</li>
</ul> </ul>
<hr/> <hr/>
<h3>Role: Moderator</h3> <h3>Role: Moderator</h3>

View file

@ -18,28 +18,15 @@ const onAnyMessage = (
id: sender, id: sender,
role: "USER", role: "USER",
experience: 0, experience: 0,
money: 10,
aiCost: 0,
lastMessageTimestamp: date, lastMessageTimestamp: date,
lastExperienceGainTimestamp: date,
lastMoneyGainTimestamp: date,
}); });
return onAnyMessage(client, _text, roomId, sender); return onAnyMessage(client, _text, roomId, sender);
} }
const levelBefore = getLevel(user.experience); const levelBefore = getLevel(user.experience);
if ( if (date > user.lastMessageTimestamp + config.app.experience.timeout) {
date >
user.lastExperienceGainTimestamp + config.app.experience.timeout
) {
user.experience += config.app.experience.gain; user.experience += config.app.experience.gain;
user.lastExperienceGainTimestamp = date;
}
if (date > user.lastMoneyGainTimestamp + config.app.money.timeout) {
user.money += config.app.money.gain;
user.lastMoneyGainTimestamp = date;
} }
user.lastMessageTimestamp = date; user.lastMessageTimestamp = date;

View file

@ -13,31 +13,24 @@ const registerModuleUser = (
client = matrixClient; client = matrixClient;
callbackStore.messageCallbacks.push({ callbackStore.messageCallbacks.push({
startConditions: [`${config.app.triggerPrefix}me`], startConditions: [`${config.app.triggerPrefix}level`],
callbackFunc: onMe, callbackFunc: onLevel,
}); });
callbackStore.messageCallbacks.push({ callbackStore.messageCallbacks.push({
startConditions: [`${config.app.triggerPrefix}leaderboard`], startConditions: [`${config.app.triggerPrefix}leaderboard`],
callbackFunc: onLeaderboard, callbackFunc: onLeaderboard,
}); });
callbackStore.messageCallbacks.push({
startConditions: [`${config.app.triggerPrefix}aileaderboard`],
callbackFunc: onAILeaderboard,
});
}; };
const onMe = (_text: string, roomId: string, sender: string) => { const onLevel = (_text: string, roomId: string, sender: string) => {
const user = getUserById(sender); const level = getLevel(getUserById(sender).experience);
const level = getLevel(user.experience);
client.sendHtmlMessage( client.sendHtmlMessage(
roomId, roomId,
"", "",
`<p>Your Role: <b>${user.role}</b></p></br> `<h3>Your Level: ${level.level}</h3>
<p>Your Money: <b>${user.money}</b></p></br> </br>
<p>Your AI Cost: <b>${user.aiCost}$</b></p></br> <i>Next level progress: ${level.experienceInLevel}/${level.expToNextLevel}xp</i>`,
<p>Your Level: <b>${level.level}</b></p></br>
<i>Next level progress: <b>${level.experienceInLevel}/${level.expToNextLevel}xp</b></i>`,
); );
}; };
@ -65,28 +58,4 @@ const onLeaderboard = (_text: string, roomId: string) => {
); );
}; };
const onAILeaderboard = (_text: string, roomId: string) => {
const mapUsersToLeaderboard = (user: IUser): string => {
const cost = user.aiCost;
const userName = getUserName(user);
const userNameMod =
userName.charAt(0).toUpperCase() + userName.slice(1);
return `<li>${userNameMod}: cost <b>${cost}</b></li>`;
};
const users = state.users.sort(
(userA, userB) => userB.aiCost - userA.aiCost,
);
client.sendHtmlMessage(
roomId,
"",
`<h3>AI Cost Leaderboard</h3>
<ul>
${users.map(mapUsersToLeaderboard).join("")}
</ul>`,
);
};
export { registerModuleUser }; export { registerModuleUser };

View file

@ -1,24 +1,22 @@
import { GoogleGenAI } from "@google/genai"; import { GoogleGenAI } from "@google/genai";
import { config } from "../../config.js"; import { config } from "../../config.js";
import type { AIResponseImage, AIResponseText } from "./types.js";
const googleAI = new GoogleGenAI({ const googleAI = new GoogleGenAI({
apiKey: config.app.ai.api.key, apiKey: config.app.ai.api.key,
}); });
const getTextGemini = async (input: string): Promise<AIResponseText> => { const getTextGemini = async (input: string): Promise<string> => {
const response = await googleAI.models.generateContent({ const response = await googleAI.models.generateContent({
model: "gemini-3-flash-preview", model: "gemini-3-flash-preview",
contents: input, contents: input,
}); });
return { return response.text ?? "AI Error";
text: response.text ?? "AI Error",
tokens: response.usageMetadata?.totalTokenCount ?? 0,
};
}; };
const getImageGemini = async (input: string): Promise<AIResponseImage> => { const getImageGemini = async (
input: string,
): Promise<Buffer<ArrayBuffer> | undefined> => {
const response = await googleAI.models.generateContent({ const response = await googleAI.models.generateContent({
model: "gemini-2.5-flash-image", model: "gemini-2.5-flash-image",
contents: input, contents: input,
@ -40,10 +38,7 @@ const getImageGemini = async (input: string): Promise<AIResponseImage> => {
} }
}); });
return { return buffer;
image: buffer,
tokens: response.usageMetadata?.totalTokenCount ?? 0,
};
}; };
export { getTextGemini, getImageGemini }; export { getTextGemini, getImageGemini };

View file

@ -1,2 +1 @@
export * from "./ai.js"; export * from "./ai.js";
export * from "./types.js";

View file

@ -1,11 +0,0 @@
interface AIResponseText {
text: string;
tokens: number;
}
interface AIResponseImage {
image: Buffer<ArrayBuffer> | undefined;
tokens: number;
}
export { type AIResponseText, type AIResponseImage };

View file

@ -6,11 +6,7 @@ interface IUser {
id: string; id: string;
role: TRole; role: TRole;
experience: number; experience: number;
money: number;
aiCost: number;
lastMessageTimestamp: number; lastMessageTimestamp: number;
lastExperienceGainTimestamp: number;
lastMoneyGainTimestamp: number;
} }
type TRole = "NONE" | "USER" | "MODERATOR" | "ADMIN"; type TRole = "NONE" | "USER" | "MODERATOR" | "ADMIN";