refactor!: rewrite bot core; add AI (Ollama, Gemini), DB, new commands

This commit is contained in:
2026-01-12 15:32:50 +03:00
parent 9d74ad9861
commit df9471a7e4
137 changed files with 11341 additions and 2025 deletions
+12
View File
@@ -0,0 +1,12 @@
import "dotenv/config";
import {drizzle, LibSQLDatabase} from "drizzle-orm/libsql";
import {Environment} from "../common/environment";
export class DatabaseManager {
static db: LibSQLDatabase;
static init() {
DatabaseManager.db = drizzle(Environment.DB_PATH);
}
}
+97
View File
@@ -0,0 +1,97 @@
import * as fs from "fs";
import {Environment} from "../common/environment";
export let muted: Set<number> = new Set<number>();
type DataJsonFile = {
admins: number[]
muted: number[]
}
export let jsonFile: DataJsonFile;
type AnswersJsonFile = {
test: string[]
prefix: string[]
better: string[]
who: string[]
kick: string[]
invite: string[]
day: number[]
}
export const testAnswers: string[] = [];
export const prefixAnswers: string[] = [];
export const betterAnswers: string[] = [];
export const whoAnswers: string[] = [];
export const kickAnswers: string[] = [];
export const inviteAnswers: string[] = [];
export const dayAnswers: number[] = [];
export async function addMute(id: number): Promise<boolean> {
if (muted.has(id)) return Promise.resolve(false);
muted.add(id);
await saveData();
return Promise.resolve(true);
}
export async function removeMute(id: number): Promise<boolean> {
if (!muted.has(id)) return Promise.resolve(false);
muted.delete(id);
await saveData();
return Promise.resolve(true);
}
export async function readData(): Promise<void> {
try {
jsonFile = JSON.parse(fs.readFileSync(`${Environment.DATA_PATH}/data.json`).toString());
const admins = jsonFile.admins || [];
admins.unshift(Environment.CREATOR_ID);
Environment.setAdmins(new Set<number>(admins));
muted = new Set<number>(jsonFile.muted || []);
return Promise.resolve();
} catch (e) {
console.error(e);
return Promise.reject(e);
}
}
export async function saveData(): Promise<void> {
const adminIds: number[] = [];
Environment.ADMIN_IDS.forEach(id => adminIds.push(id));
jsonFile.admins = adminIds;
const mutedList: number[] = [];
muted.forEach(id => mutedList.push(id));
jsonFile.muted = mutedList;
try {
fs.writeFileSync(`${Environment.DATA_PATH}/data.json`, JSON.stringify(jsonFile));
return readData();
} catch (e) {
return Promise.reject(e);
}
}
export async function retrieveAnswers(): Promise<void> {
try {
const json: AnswersJsonFile = JSON.parse(fs.readFileSync(`${Environment.DATA_PATH}/answers.json`).toString());
json.test.forEach(e => testAnswers.push(e));
json.prefix.forEach(e => prefixAnswers.push(e));
json.better.forEach(e => betterAnswers.push(e));
json.who.forEach(e => whoAnswers.push(e));
json.kick.forEach(e => kickAnswers.push(e));
json.invite.forEach(e => inviteAnswers.push(e));
json.day.forEach(e => dayAnswers.push(e));
return Promise.resolve();
} catch (e) {
console.error(e);
return Promise.reject(e);
}
}
+113
View File
@@ -0,0 +1,113 @@
import {messagesTable} from "./schema";
import {DatabaseManager} from "./database-manager";
import {StoredMessage} from "../model/stored-message";
import {and, eq} from "drizzle-orm";
import {inArray} from "drizzle-orm/sql/expressions/conditions";
import {Message} from "typescript-telegram-bot-api";
import {Dao} from "../base/dao";
import {buildExcludedSet} from "../util/utils";
export class MessageDao extends Dao<StoredMessage> {
private tag: string = "MessageDao";
override async getAll(): Promise<StoredMessage[]> {
const then = Date.now();
const messages = await DatabaseManager.db.select().from(messagesTable);
const now = Date.now();
const diff = now - then;
console.log(`${this.tag}: getAll()`, `took ${diff}ms; size: ${messages.length}`);
return this.mapFrom(messages);
}
override async getById(params: { chatId: number, id: number }): Promise<StoredMessage | null> {
const then = Date.now();
const messages =
await DatabaseManager.db.select()
.from(messagesTable)
.where(
and(
eq(messagesTable.chatId, params.chatId),
eq(messagesTable.id, params.id)
)
);
const now = Date.now();
const diff = now - then;
console.log(`${this.tag}: getById(${params.chatId}, ${params.id})`, `took ${diff}ms; size: ${messages.length}`);
const m = messages[0];
if (!m) return null;
return this.mapFrom([m])[0];
}
override async getByIds(params: { chatId: number, ids: number[] }): Promise<StoredMessage[]> {
const then = Date.now();
const messages =
await DatabaseManager.db.select()
.from(messagesTable)
.where(
and(
eq(messagesTable.chatId, params.chatId),
inArray(messagesTable.id, params.ids)
)
);
const now = Date.now();
const diff = now - then;
console.log(`${this.tag}: getByIds(${params.chatId}, ${params.ids})`, `took ${diff}ms; size: ${messages.length}`);
return this.mapFrom(messages);
}
async insert(values: typeof messagesTable.$inferInsert[]): Promise<true> {
const then = Date.now();
const r = await DatabaseManager.db
.insert(messagesTable)
.values(values)
.onConflictDoUpdate({
target: messagesTable.id,
set: buildExcludedSet(messagesTable, ["id"])
});
const now = Date.now();
const diff = now - then;
console.log(`${this.tag}: insert(size: ${values.length})`, `took ${diff}ms'; inserted: ${r.rowsAffected}`);
return true;
}
mapTo(messages: Message[]): typeof messagesTable.$inferInsert[] {
return messages.map(msg => {
return {
chatId: msg.chat.id,
id: msg.message_id,
replyToMessageId: msg.reply_to_message?.message_id,
fromId: msg.from.id,
text: msg.text,
date: msg.date,
firstName: msg.from.first_name,
lastName: msg.from.last_name,
};
});
}
mapFrom(messages: typeof messagesTable.$inferInsert[]): StoredMessage[] {
return messages.map(m => {
return {
firstName: m.firstName,
lastName: m.lastName,
chatId: m.chatId,
messageId: m.id,
replyToMessageId: m.replyToMessageId,
fromId: m.fromId,
text: m.text,
date: m.date
};
});
}
}
+25
View File
@@ -0,0 +1,25 @@
import {int, sqliteTable, text} from "drizzle-orm/sqlite-core";
export const messagesTable = sqliteTable("messages", {
id: int().primaryKey().unique().notNull(),
chatId: int().notNull(),
replyToMessageId: int(),
fromId: int().notNull(),
text: text().notNull(),
date: int().notNull(),
firstName: text().notNull(),
lastName: text(),
});
export type MessageInsert = typeof messagesTable.$inferInsert;
export const usersTable = sqliteTable("users", {
id: int().primaryKey().unique().notNull(),
isBot: int().notNull(),
firstName: text().notNull(),
lastName: text(),
userName: text(),
isPremium: int(),
});
export type UserInsert = typeof usersTable.$inferInsert;
+105
View File
@@ -0,0 +1,105 @@
import {StoredUser} from "../model/stored-user";
import {Dao} from "../base/dao";
import {DatabaseManager} from "./database-manager";
import {UserInsert, usersTable} from "./schema";
import {eq} from "drizzle-orm";
import {inArray} from "drizzle-orm/sql/expressions/conditions";
import {User} from "typescript-telegram-bot-api";
import {boolToInt, buildExcludedSet} from "../util/utils";
export class UserDao extends Dao<StoredUser> {
private tag: string = "UserDao";
override async getAll(): Promise<StoredUser[]> {
const then = Date.now();
const users = await DatabaseManager.db.select().from(usersTable);
const now = Date.now();
const diff = now - then;
console.log(`${this.tag}: getAll()`, `took ${diff}ms; size: ${users.length}`);
return this.mapFrom(users);
}
override async getById(params: { id: number }): Promise<StoredUser | null> {
const then = Date.now();
const users =
await DatabaseManager.db.select()
.from(usersTable)
.where(
eq(usersTable.id, params.id)
);
const now = Date.now();
const diff = now - then;
console.log(`${this.tag}: getById(${params.id})`, `took ${diff}ms; size: ${users.length}`);
const u = users[0];
if (!u) return null;
return this.mapFrom([u])[0];
}
override async getByIds(params: { ids: number[] }): Promise<StoredUser[]> {
const then = Date.now();
const users =
await DatabaseManager.db.select()
.from(usersTable)
.where(
inArray(usersTable.id, params.ids)
);
const now = Date.now();
const diff = now - then;
console.log(`${this.tag}: getByIds(${params.ids})`, `took ${diff}ms; size: ${users.length}`);
return this.mapFrom(users);
}
override async insert(values: UserInsert[] | UserInsert): Promise<true> {
const rows = Array.isArray(values) ? values : [values];
const then = Date.now();
const r = await DatabaseManager.db
.insert(usersTable)
.values(rows)
.onConflictDoUpdate({
target: usersTable.id,
set: buildExcludedSet(usersTable, ["id"])
});
const now = Date.now();
const diff = now - then;
console.log(`${this.tag}: insert(size: ${rows.length})`, `took ${diff}ms; inserted: ${r.rowsAffected}`);
return true;
}
mapTo(users: User[]): UserInsert[] {
return users.map(u => {
return {
id: u.id,
isBot: boolToInt(u.is_bot),
firstName: u.first_name,
lastName: u.last_name,
userName: u.username,
isPremium: boolToInt(u.is_premium)
};
});
}
mapFrom(users: UserInsert[]): StoredUser[] {
return users.map(u => {
return {
id: u.id,
isBot: u.isBot === 1,
firstName: u.firstName,
lastName: u.lastName,
userName: u.userName,
isPremium: u.isPremium === 1
};
});
}
}