Add ai functionality
This commit is contained in:
parent
da66cf9001
commit
dd4da06753
19 changed files with 1118 additions and 52 deletions
|
|
@ -12,35 +12,41 @@ const registerModuleAdmin = (
|
|||
client = matrixClient;
|
||||
|
||||
callbackStore.messageCallbacks.push({
|
||||
startCondition: `${config.app.triggerPrefix}shutdown`,
|
||||
startConditions: [`${config.app.triggerPrefix}shutdown`],
|
||||
allowedRoles: ["ADMIN"],
|
||||
callbackFunc: onShutdown,
|
||||
});
|
||||
callbackStore.messageCallbacks.push({
|
||||
startCondition: `${config.app.triggerPrefix}loaddata`,
|
||||
startConditions: [`${config.app.triggerPrefix}loaddata`],
|
||||
allowedRoles: ["MODERATOR", "ADMIN"],
|
||||
callbackFunc: onLoadData,
|
||||
});
|
||||
callbackStore.messageCallbacks.push({
|
||||
startCondition: `${config.app.triggerPrefix}savedata`,
|
||||
startConditions: [`${config.app.triggerPrefix}savedata`],
|
||||
allowedRoles: ["MODERATOR", "ADMIN"],
|
||||
callbackFunc: onSaveData,
|
||||
});
|
||||
};
|
||||
|
||||
const onShutdown = (text: string) => {
|
||||
const onShutdown = (text: string, roomId: string) => {
|
||||
if (!text.includes("nosave")) {
|
||||
client.sendTextMessage(roomId, "Saving data...");
|
||||
save();
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
const sendPromise = client.sendTextMessage(roomId, "Shutting down...");
|
||||
sendPromise.finally(() => {
|
||||
process.exit(0);
|
||||
});
|
||||
};
|
||||
|
||||
const onLoadData = () => {
|
||||
const onLoadData = (_text: string, roomId: string) => {
|
||||
client.sendTextMessage(roomId, "Loading data...");
|
||||
load();
|
||||
};
|
||||
|
||||
const onSaveData = () => {
|
||||
const onSaveData = (_text: string, roomId: string) => {
|
||||
client.sendTextMessage(roomId, "Saving data...");
|
||||
save();
|
||||
};
|
||||
|
||||
|
|
|
|||
140
src/modules/ai/ai.ts
Normal file
140
src/modules/ai/ai.ts
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import { MatrixClient, MsgType } from "matrix-js-sdk";
|
||||
import type { ICallbackStore } from "../types.js";
|
||||
import { config } from "../../config.js";
|
||||
import { getImageNanoBanana, getTextGemini } from "../../services/ai/ai.js";
|
||||
import type { IAnimal } from "./types.js";
|
||||
|
||||
let client: MatrixClient;
|
||||
|
||||
const registerModuleAI = (
|
||||
matrixClient: MatrixClient,
|
||||
callbackStore: ICallbackStore,
|
||||
) => {
|
||||
client = matrixClient;
|
||||
|
||||
callbackStore.messageCallbacks.push({
|
||||
startConditions: [`${config.app.triggerPrefix}ai `],
|
||||
callbackFunc: onAI,
|
||||
});
|
||||
callbackStore.messageCallbacks.push({
|
||||
startConditions: [
|
||||
`satek when `,
|
||||
`satek ked `,
|
||||
`gabor when `,
|
||||
`gabor ked `,
|
||||
`martin when `,
|
||||
`martin ked `,
|
||||
`madys when `,
|
||||
`madys ked `,
|
||||
`mandak when `,
|
||||
`mandak ked `,
|
||||
`mando when `,
|
||||
`mando ked `,
|
||||
`mandik when `,
|
||||
`mandik ked `,
|
||||
`madik when `,
|
||||
`madik ked `,
|
||||
`janys when `,
|
||||
`janys ked `,
|
||||
`jano when `,
|
||||
`jano ked `,
|
||||
],
|
||||
callbackFunc: onImageGen,
|
||||
});
|
||||
};
|
||||
|
||||
const getAnimal = (name: string): IAnimal | undefined => {
|
||||
const animals: IAnimal[] = [
|
||||
{
|
||||
name: "satek",
|
||||
animal: "black cat",
|
||||
},
|
||||
{
|
||||
name: "gabor",
|
||||
animal: "hedgehog",
|
||||
},
|
||||
{
|
||||
name: "martin",
|
||||
animal: "hedgehog",
|
||||
},
|
||||
{
|
||||
name: "madys",
|
||||
animal: "beaver",
|
||||
},
|
||||
{
|
||||
name: "mandak",
|
||||
animal: "beaver",
|
||||
},
|
||||
{
|
||||
name: "mando",
|
||||
animal: "beaver",
|
||||
},
|
||||
{
|
||||
name: "mandik",
|
||||
animal: "beaver",
|
||||
},
|
||||
{
|
||||
name: "madik",
|
||||
animal: "beaver",
|
||||
},
|
||||
{
|
||||
name: "janys",
|
||||
animal: "lion",
|
||||
},
|
||||
{
|
||||
name: "jano",
|
||||
animal: "lion",
|
||||
},
|
||||
];
|
||||
|
||||
const foundAnimals = animals
|
||||
.map((animal) => {
|
||||
if (name.includes(animal.name)) {
|
||||
return animal;
|
||||
}
|
||||
})
|
||||
.filter((animal) => animal);
|
||||
|
||||
return foundAnimals.at(0);
|
||||
};
|
||||
|
||||
const onAI = async (text: string, roomId: string) => {
|
||||
if (text.trim().length < 5) {
|
||||
return;
|
||||
}
|
||||
|
||||
const responseAI = await getTextGemini(text.replace("!ai ", ""));
|
||||
client.sendTextMessage(roomId, responseAI);
|
||||
};
|
||||
|
||||
const onImageGen = async (text: string, roomId: string) => {
|
||||
const animal = getAnimal(text.trim().split(/\s+/)[0] ?? "");
|
||||
if (!animal) {
|
||||
return;
|
||||
}
|
||||
|
||||
const textMod = text.replace(animal.name, animal.animal);
|
||||
const buffer = await getImageNanoBanana(textMod.trim());
|
||||
if (!buffer || buffer.length < 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageName = `photo-${animal.name}.png`;
|
||||
|
||||
const uploadResult = await client.uploadContent(buffer, {
|
||||
type: "image/png",
|
||||
name: imageName,
|
||||
});
|
||||
|
||||
await client.sendMessage(roomId, {
|
||||
msgtype: MsgType.Image,
|
||||
body: imageName,
|
||||
url: uploadResult.content_uri,
|
||||
info: {
|
||||
mimetype: "image/png",
|
||||
size: buffer.length,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export { getAnimal, registerModuleAI, onImageGen };
|
||||
2
src/modules/ai/index.ts
Normal file
2
src/modules/ai/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./ai.js";
|
||||
export * from "./types.js";
|
||||
6
src/modules/ai/types.ts
Normal file
6
src/modules/ai/types.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
interface IAnimal {
|
||||
name: string;
|
||||
animal: string;
|
||||
}
|
||||
|
||||
export { type IAnimal };
|
||||
|
|
@ -4,22 +4,26 @@ import { config } from "../../config.js";
|
|||
|
||||
let client: MatrixClient;
|
||||
|
||||
const registerModuleTest = (
|
||||
const registerModuleBase = (
|
||||
matrixClient: MatrixClient,
|
||||
callbackStore: ICallbackStore,
|
||||
) => {
|
||||
client = matrixClient;
|
||||
|
||||
callbackStore.messageCallbacks.push({
|
||||
startCondition: `${config.app.triggerPrefix}ping`,
|
||||
startConditions: [`${config.app.triggerPrefix}ping`],
|
||||
callbackFunc: onPing,
|
||||
});
|
||||
callbackStore.messageCallbacks.push({
|
||||
startCondition: `${config.app.triggerPrefix}say `,
|
||||
startConditions: [`${config.app.triggerPrefix}say `],
|
||||
callbackFunc: onSay,
|
||||
});
|
||||
callbackStore.messageCallbacks.push({
|
||||
startCondition: `${config.app.triggerPrefix}help`,
|
||||
startConditions: [`${config.app.triggerPrefix}bowling `],
|
||||
callbackFunc: onBowling,
|
||||
});
|
||||
callbackStore.messageCallbacks.push({
|
||||
startConditions: [`${config.app.triggerPrefix}help`],
|
||||
callbackFunc: onHelp,
|
||||
});
|
||||
};
|
||||
|
|
@ -34,6 +38,12 @@ const onSay = (text: string, roomId: string) => {
|
|||
client.sendTextMessage(roomId, text.replace(trigger, ""));
|
||||
};
|
||||
|
||||
const onBowling = (text: string, _roomId: string) => {
|
||||
const trigger = `${config.app.triggerPrefix}bowling `;
|
||||
|
||||
client.sendTextMessage(config.app.bowlingRoomId, text.replace(trigger, ""));
|
||||
};
|
||||
|
||||
const onHelp = (_text: string, roomId: string) => {
|
||||
client.sendHtmlMessage(
|
||||
roomId,
|
||||
|
|
@ -42,9 +52,11 @@ const onHelp = (_text: string, roomId: string) => {
|
|||
<ul>
|
||||
<li><b>!ping</b> - Pong!</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>!rank</b> - Prints your rank and experience</li>
|
||||
<li><b>!level</b> - Prints your level and experience</li>
|
||||
<li><b>!leaderboard</b> - Prints total user ranking</li>
|
||||
<li><b>!ai {text}</b> - Say something to Gemini 3</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
<h3>Role: Moderator</h3>
|
||||
|
|
@ -60,4 +72,4 @@ const onHelp = (_text: string, roomId: string) => {
|
|||
);
|
||||
};
|
||||
|
||||
export { registerModuleTest };
|
||||
export { registerModuleBase };
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { MatrixClient } from "matrix-js-sdk";
|
||||
import type { TRole } from "../store/types.js";
|
||||
import { getRank, getUserById } from "../helpers.js";
|
||||
import { getLevel, getUserById } from "../helpers.js";
|
||||
import { config } from "../config.js";
|
||||
import { state } from "../store/store.js";
|
||||
|
||||
|
|
@ -23,19 +23,19 @@ const onAnyMessage = (
|
|||
return onAnyMessage(client, _text, roomId, sender);
|
||||
}
|
||||
|
||||
const rankBefore = getRank(user.experience);
|
||||
const levelBefore = getLevel(user.experience);
|
||||
|
||||
if (date > user.lastMessageTimestamp + config.app.experience.timeout) {
|
||||
user.experience += config.app.experience.gain;
|
||||
}
|
||||
user.lastMessageTimestamp = date;
|
||||
|
||||
const rankAfter = getRank(user.experience);
|
||||
if (rankAfter.rank > rankBefore.rank) {
|
||||
const levelAfter = getLevel(user.experience);
|
||||
if (levelAfter.level > levelBefore.level) {
|
||||
client.sendHtmlMessage(
|
||||
roomId,
|
||||
"",
|
||||
`${sender} - You are now rank <b>${rankAfter.rank}</b>`,
|
||||
`${sender} - You are now level <b>${levelAfter.level}</b>`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import {
|
|||
RoomEvent,
|
||||
type IContent,
|
||||
} from "matrix-js-sdk";
|
||||
import { registerModuleTest } from "./base/base.js";
|
||||
import type { ICallback, ICallbackStore } from "./types.js";
|
||||
import { registerModuleBase } from "./base/base.js";
|
||||
import { registerModuleAdmin } from "./admin/admin.js";
|
||||
import { registerModuleUser } from "./user/user.js";
|
||||
import { registerModuleAI } from "./ai/ai.js";
|
||||
import { checkRoles, getUserById } from "../helpers.js";
|
||||
import { onAnyMessage, onMissingRole } from "./global.js";
|
||||
import { config } from "../config.js";
|
||||
|
|
@ -27,13 +28,20 @@ const checkMessageCallback = (
|
|||
return false;
|
||||
}
|
||||
|
||||
if (callback.startCondition && !text.startsWith(callback.startCondition)) {
|
||||
if (
|
||||
callback.startConditions &&
|
||||
!callback.startConditions.some((condition) =>
|
||||
text.startsWith(condition),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
callback.includesCondition &&
|
||||
!text.includes(callback.includesCondition)
|
||||
callback.includesConditions &&
|
||||
!callback.includesConditions.some((condition) =>
|
||||
text.includes(condition),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -94,9 +102,10 @@ const registerModules = (client: MatrixClient) => {
|
|||
});
|
||||
});
|
||||
|
||||
registerModuleTest(client, callbacks);
|
||||
registerModuleBase(client, callbacks);
|
||||
registerModuleAdmin(client, callbacks);
|
||||
registerModuleUser(client, callbacks);
|
||||
registerModuleAI(client, callbacks);
|
||||
};
|
||||
|
||||
export { registerModules };
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ interface ICallbackStore {
|
|||
}
|
||||
|
||||
interface ICallback {
|
||||
startCondition?: string;
|
||||
includesCondition?: string;
|
||||
startConditions?: string[];
|
||||
includesConditions?: string[];
|
||||
allowedRoles?: TRole[];
|
||||
allowedRooms?: string;
|
||||
callbackFunc: (text: string, roomId: string, sender: string) => void;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { MatrixClient } from "matrix-js-sdk";
|
||||
import type { ICallbackStore } from "../types.js";
|
||||
import { config } from "../../config.js";
|
||||
import { getRank, getUserById, getUserName } from "../../helpers.js";
|
||||
import { getLevel, getUserById, getUserName } from "../../helpers.js";
|
||||
import { state } from "../../store/store.js";
|
||||
import type { IUser } from "../../store/types.js";
|
||||
let client: MatrixClient;
|
||||
|
|
@ -13,31 +13,35 @@ const registerModuleUser = (
|
|||
client = matrixClient;
|
||||
|
||||
callbackStore.messageCallbacks.push({
|
||||
startCondition: `${config.app.triggerPrefix}rank`,
|
||||
callbackFunc: onRank,
|
||||
startConditions: [`${config.app.triggerPrefix}level`],
|
||||
callbackFunc: onLevel,
|
||||
});
|
||||
callbackStore.messageCallbacks.push({
|
||||
startCondition: `${config.app.triggerPrefix}leaderboard`,
|
||||
startConditions: [`${config.app.triggerPrefix}leaderboard`],
|
||||
callbackFunc: onLeaderboard,
|
||||
});
|
||||
};
|
||||
|
||||
const onRank = (_text: string, roomId: string, sender: string) => {
|
||||
const rank = getRank(getUserById(sender).experience);
|
||||
const onLevel = (_text: string, roomId: string, sender: string) => {
|
||||
const level = getLevel(getUserById(sender).experience);
|
||||
|
||||
client.sendHtmlMessage(
|
||||
roomId,
|
||||
"",
|
||||
`<h3>Your Rank: ${rank.rank}</h3>
|
||||
<i>Next rank progress: ${rank.experienceInRank}/${rank.expToNextRank}exp</i>`,
|
||||
`<h3>Your Level: <b>${level.level}</b></h3>
|
||||
</br>
|
||||
<i>Next level progress: ${level.experienceInLevel}/${level.expToNextLevel}xp</i>`,
|
||||
);
|
||||
};
|
||||
|
||||
const onLeaderboard = (_text: string, roomId: string) => {
|
||||
const mapUsersToLeaderboard = (user: IUser): string => {
|
||||
const rank = getRank(user.experience);
|
||||
const level = getLevel(user.experience);
|
||||
const userName = getUserName(user);
|
||||
const userNameMod =
|
||||
userName.charAt(0).toUpperCase() + userName.slice(1);
|
||||
|
||||
return `<li>${getUserName(user)}: rank ${rank.rank} (${rank.experienceInRank}/${rank.expToNextRank}exp)</li>`;
|
||||
return `<li>${userNameMod}: level <b>${level.level}</b> (${level.experienceInLevel}/${level.expToNextLevel}xp)</li>`;
|
||||
};
|
||||
|
||||
const users = state.users.sort(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue