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",
"version": "0.7.0",
"version": "0.6.0",
"description": "",
"license": "ISC",
"author": "",

View file

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

View file

@ -4,31 +4,14 @@ import { type IUser, type TRole } from "./store/types.js";
import type { ILevel } from "./types.js";
const getUserById = (userId: string): IUser => {
const user = state.users.find((user) => user.id === userId);
if (!user) {
return {
return (
state.users.find((user) => user.id === userId) ?? {
id: ":",
role: "NONE",
experience: 0,
money: 0,
aiCost: 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) => {

View file

@ -3,9 +3,6 @@ import type { ICallbackStore } from "../types.js";
import { config } from "../../config.js";
import { getTextGemini, getImageGemini } from "../../services/ai/ai.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;
@ -18,58 +15,40 @@ const registerModuleAI = (
callbackStore.messageCallbacks.push({
startConditions: [`${config.app.triggerPrefix}ai `],
callbackFunc: onAI,
allowedRoles: ["USER", "MODERATOR", "ADMIN"],
});
callbackStore.messageCallbacks.push({
startConditions: [`${config.app.triggerPrefix}img `],
startConditions: [
...alts.map((alt) => `${alt.key} when`),
...alts.map((alt) => `${alt.key} ked`),
`!img`,
],
callbackFunc: onImageGen,
allowedRoles: ["USER", "MODERATOR", "ADMIN"],
});
};
const onAI = async (text: string, roomId: string, sender: string) => {
const user = getUserById(sender);
const onAI = async (text: string, roomId: string) => {
if (text.trim().length < 5) {
return;
}
let textMod = text.replace("!ai", "").trim().toLowerCase();
let instructions = {
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 responseAI = await getTextGemini(text.replace("!ai ", ""));
client.sendTextMessage(roomId, responseAI);
};
const onImageGen = async (text: string, roomId: string, sender: string) => {
const user = getUserById(sender);
const onImageGen = async (text: string, roomId: string) => {
let textMod = text.replace("!img", "").trim().toLowerCase();
alts.forEach((alt) => {
alt.keys.forEach((key) => {
textMod = textMod.replaceAll(key, alt.alt);
});
textMod = textMod.replaceAll(alt.key, alt.alt);
});
const responseAI = await getImageGemini(textMod);
user.aiCost += responseAI.tokens * prices.image;
if (!responseAI.image || responseAI.image.length < 10) {
const buffer = await getImageGemini(textMod);
if (!buffer || buffer.length < 10) {
return;
}
const imageName = `photo-img-gen.png`;
const uploadResult = await client.uploadContent(responseAI.image, {
const uploadResult = await client.uploadContent(buffer, {
type: "image/png",
name: imageName,
});
@ -80,7 +59,7 @@ const onImageGen = async (text: string, roomId: string, sender: string) => {
url: uploadResult.content_uri,
info: {
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[] = [
{
keys: [
"satek",
"sateg",
"sadeg",
"coty",
"satko",
"sadko",
"vlado",
"vladko",
"vladimir",
"macak",
"macag",
],
key: "satek",
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",
},
{
keys: ["gabor", "gaber", "martin", "marting"],
key: "macico",
alt: "white cat",
},
{
key: "gabor",
alt: "hedgehog",
},
{
keys: [
"madys",
"mandak",
"mandag",
"mando",
"mandik",
"madik",
"madeg",
"madek",
],
key: "martin",
alt: "hedgehog",
},
{
key: "madys",
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",
},
{
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",
},
{
keys: ["lara", "kostur", "kosturik"],
key: "mareg",
alt: "purple snake",
},
{
key: "maceg",
alt: "purple snake",
},
{
key: "rod",
alt: "purple snake",
},
{
key: "lara",
alt: "ginger dog",
},
{
keys: ["tato"],
alt: "green troll",
key: "kostur",
alt: "ginger dog",
},
{
keys: ["mama", "monika", "monik"],
alt: "god's peasant",
key: "kosturik",
alt: "ginger dog",
},
{
keys: ["puk", "pucik", "pucig"],
alt: "fat rat",
key: "tato",
alt: "troll",
},
{
keys: ["becky", "beky", "beki"],
alt: "35 year old pink haired giantess",
key: "mama",
alt: "angel",
},
{
keys: ["veronika", "veronik", "wewe", "wonka"],
alt: "lgbt princess with curly hair",
key: "monik",
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 {
keys: string[];
key: string;
alt: string;
}
interface IAdminInstructions {
prefferedLanguages: string[];
adminText: string;
alts: IAIAlt[];
}
export { type IAIAlt, type IAdminInstructions };
export { type IAIAlt };

View file

@ -61,21 +61,17 @@ const onHelp = (_text: string, roomId: string) => {
client.sendHtmlMessage(
roomId,
"",
`<h3>Role: Any</h3>
`<h3>Role: User</h3>
<ul>
<li><b>!ping</b> - Pong!</li>
<li><b>!info</b> - Prints information about this bot</li>
<li><b>!say {text}</b> - Repeats your message</li>
<li><b>!bowling {text}</b> - Repeats your message in bowling</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>!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>!img {text}</b> - Generate an image</li>
<li><b>!img {text}</b> - Generate an image (also works with "{name} when/ked {text}")</li>
</ul>
<hr/>
<h3>Role: Moderator</h3>

View file

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

View file

@ -13,31 +13,24 @@ const registerModuleUser = (
client = matrixClient;
callbackStore.messageCallbacks.push({
startConditions: [`${config.app.triggerPrefix}me`],
callbackFunc: onMe,
startConditions: [`${config.app.triggerPrefix}level`],
callbackFunc: onLevel,
});
callbackStore.messageCallbacks.push({
startConditions: [`${config.app.triggerPrefix}leaderboard`],
callbackFunc: onLeaderboard,
});
callbackStore.messageCallbacks.push({
startConditions: [`${config.app.triggerPrefix}aileaderboard`],
callbackFunc: onAILeaderboard,
});
};
const onMe = (_text: string, roomId: string, sender: string) => {
const user = getUserById(sender);
const level = getLevel(user.experience);
const onLevel = (_text: string, roomId: string, sender: string) => {
const level = getLevel(getUserById(sender).experience);
client.sendHtmlMessage(
roomId,
"",
`<p>Your Role: <b>${user.role}</b></p></br>
<p>Your Money: <b>${user.money}</b></p></br>
<p>Your AI Cost: <b>${user.aiCost}$</b></p></br>
<p>Your Level: <b>${level.level}</b></p></br>
<i>Next level progress: <b>${level.experienceInLevel}/${level.expToNextLevel}xp</b></i>`,
`<h3>Your Level: ${level.level}</h3>
</br>
<i>Next level progress: ${level.experienceInLevel}/${level.expToNextLevel}xp</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 };

View file

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

View file

@ -1,2 +1 @@
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;
role: TRole;
experience: number;
money: number;
aiCost: number;
lastMessageTimestamp: number;
lastExperienceGainTimestamp: number;
lastMoneyGainTimestamp: number;
}
type TRole = "NONE" | "USER" | "MODERATOR" | "ADMIN";