From 452eded8b2b60a467519849700633729d0063689 Mon Sep 17 00:00:00 2001 From: codecanna Date: Mon, 29 Dec 2025 18:36:12 -0700 Subject: [PATCH 1/9] Moved classes into /classes and interfaces.ts to root --- {models => classes}/E621Bot.ts | 0 {models => classes}/E621UrlBuilderPools.ts | 0 {models => classes}/E621UrlBuilderPosts.ts | 0 models/interfaces.ts => interfaces.ts | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {models => classes}/E621Bot.ts (100%) rename {models => classes}/E621UrlBuilderPools.ts (100%) rename {models => classes}/E621UrlBuilderPosts.ts (100%) rename models/interfaces.ts => interfaces.ts (100%) diff --git a/models/E621Bot.ts b/classes/E621Bot.ts similarity index 100% rename from models/E621Bot.ts rename to classes/E621Bot.ts diff --git a/models/E621UrlBuilderPools.ts b/classes/E621UrlBuilderPools.ts similarity index 100% rename from models/E621UrlBuilderPools.ts rename to classes/E621UrlBuilderPools.ts diff --git a/models/E621UrlBuilderPosts.ts b/classes/E621UrlBuilderPosts.ts similarity index 100% rename from models/E621UrlBuilderPosts.ts rename to classes/E621UrlBuilderPosts.ts diff --git a/models/interfaces.ts b/interfaces.ts similarity index 100% rename from models/interfaces.ts rename to interfaces.ts From 836393c10757dacba889c865961f8de60d3c9570 Mon Sep 17 00:00:00 2001 From: codecanna Date: Mon, 29 Dec 2025 19:02:02 -0700 Subject: [PATCH 2/9] moved classes around --- main.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.ts b/main.ts index 06c2cd6..8506b5d 100644 --- a/main.ts +++ b/main.ts @@ -1,6 +1,6 @@ -import { E621Bot } from "./models/E621Bot.ts"; -import { E621UrlBuilderPosts } from "./models/E621UrlBuilderPosts.ts"; -import { E621UrlBuilderPools } from "./models/E621UrlBuilderPools.ts"; +import { E621Bot } from "./classes/E621Bot.ts"; +import { E621UrlBuilderPosts } from "./classes/E621UrlBuilderPosts.ts"; +import { E621UrlBuilderPools } from "./classes/E621UrlBuilderPools.ts"; import { Post } from "./models/interfaces.ts"; import { Pool } from "./models/interfaces.ts"; import * as numbers from "./constants/numbers.ts"; From 0c1d445499dcba38768e2dc69fc7915293b600fd Mon Sep 17 00:00:00 2001 From: codecanna Date: Mon, 29 Dec 2025 19:02:35 -0700 Subject: [PATCH 3/9] added sqlite query to create blacklist_db table --- db/sql/create_blacklist_table.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 db/sql/create_blacklist_table.sql diff --git a/db/sql/create_blacklist_table.sql b/db/sql/create_blacklist_table.sql new file mode 100644 index 0000000..831f7fd --- /dev/null +++ b/db/sql/create_blacklist_table.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS blacklist_db ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + telegramId BIGINT NOT NULL, + timestamp BIGINT NOT NULL, + blacklisted_tags TEXT +); \ No newline at end of file From b4fc6e2741a1b65ebcbda38c6ed31134cfe8b355 Mon Sep 17 00:00:00 2001 From: codecanna Date: Mon, 29 Dec 2025 19:02:48 -0700 Subject: [PATCH 4/9] added migrations.ts with tests --- db/migration.ts | 15 +++++++++++++++ tests/migration_tests.ts | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 db/migration.ts create mode 100644 tests/migration_tests.ts diff --git a/db/migration.ts b/db/migration.ts new file mode 100644 index 0000000..211230c --- /dev/null +++ b/db/migration.ts @@ -0,0 +1,15 @@ +import { PathLike } from "node:fs"; +import { DatabaseSync } from "node:sqlite"; +import { SQL_BASEPATH } from "../constants/strings.ts"; + +export function createBlacklistDb(dbFile: PathLike) { + try { + const db = new DatabaseSync(dbFile); + const query = Deno.readTextFileSync(`${SQL_BASEPATH}/create_blacklist_table.sql`).trim(); + + db.prepare(query).run(); + db.close(); + } catch (err) { + console.error(`Failed to create blacklist db: ${err}`); + } +} \ No newline at end of file diff --git a/tests/migration_tests.ts b/tests/migration_tests.ts new file mode 100644 index 0000000..ce5163b --- /dev/null +++ b/tests/migration_tests.ts @@ -0,0 +1,19 @@ +import { assertEquals } from "@std/assert/equals"; +import { DatabaseSync } from "node:sqlite"; +import { createBlacklistDb } from "../db/migration.ts"; +import { assertNotEquals } from "@std/assert/not-equals"; +const testDbFile = "db/test_blacklist.db"; +Deno.test(function testCreateBlacklistDb() { + createBlacklistDb(testDbFile); + + // Get table + const db = new DatabaseSync(testDbFile); + const table = db.prepare( + `SELECT name FROM sqlite_master WHERE type ='table' AND name = 'blacklist_db';`, + ).get(); + db.close(); + + assertNotEquals(table, undefined); + assertEquals(table?.name, "blacklist_db"); + Deno.removeSync(testDbFile); +}); From 1624f20f6f4dfa4076ea840705563b8cd4d1f36f Mon Sep 17 00:00:00 2001 From: codecanna Date: Mon, 29 Dec 2025 19:03:06 -0700 Subject: [PATCH 5/9] Added a base url to access db/sql --- constants/strings.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/constants/strings.ts b/constants/strings.ts index ece931a..86d0d3b 100644 --- a/constants/strings.ts +++ b/constants/strings.ts @@ -51,3 +51,4 @@ export const fileTypes = { export const keywordsRegex = "(id|creator|active|inactive|category|order)"; export const BLACKLIST_PATH = "./blacklist.txt"; +export const SQL_BASEPATH = "db/sql"; From eef3af00975978c0b32893972c14ae50d6be1a39 Mon Sep 17 00:00:00 2001 From: codecanna Date: Tue, 30 Dec 2025 02:45:38 -0700 Subject: [PATCH 6/9] added db files to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7447f89..8b2226b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/bin \ No newline at end of file +/bin +*.db \ No newline at end of file From 326681c40bc23e25ad85ec6964a4170bffe664e9 Mon Sep 17 00:00:00 2001 From: codecanna Date: Tue, 30 Dec 2025 02:46:47 -0700 Subject: [PATCH 7/9] added allow-write --- deno.json | 9 +++++---- deno.lock | 8 ++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/deno.json b/deno.json index 15a22f6..3a51851 100644 --- a/deno.json +++ b/deno.json @@ -1,11 +1,12 @@ { "tasks": { - "dev": "deno run --allow-net --allow-env --allow-read --watch main.ts", - "test": "deno test --allow-net --allow-env --allow-read ./tests/*", - "bin": "deno compile --allow-net --allow-env --allow-read -o ./bin/E621Bot main.ts", - "bin-arm": "deno compile --allow-net --allow-env --allow-read --target aarch64-unknown-linux-gnu -o ./bin/E621BotArm main.ts" + "dev": "deno run --allow-write --allow-net --allow-env --allow-read --watch main.ts", + "test": "deno test --allow-write --allow-net --allow-env --allow-read ./tests/*", + "bin": "deno compile --allow-write --allow-net --allow-env --allow-read -o ./bin/E621Bot main.ts", + "bin-arm": "deno compile --allow-write --allow-net --allow-env --allow-read --target aarch64-unknown-linux-gnu -o ./bin/E621BotArm main.ts" }, "imports": { + "@grammyjs/conversations": "npm:@grammyjs/conversations@^2.1.1", "@std/assert": "jsr:@std/assert@^1.0.16", "grammy": "npm:grammy@^1.38.4" } diff --git a/deno.lock b/deno.lock index 1806691..56469a9 100644 --- a/deno.lock +++ b/deno.lock @@ -3,6 +3,7 @@ "specifiers": { "jsr:@std/assert@^1.0.16": "1.0.16", "jsr:@std/internal@^1.0.12": "1.0.12", + "npm:@grammyjs/conversations@^2.1.1": "2.1.1_grammy@1.38.4", "npm:grammy@^1.38.4": "1.38.4" }, "jsr": { @@ -17,6 +18,12 @@ } }, "npm": { + "@grammyjs/conversations@2.1.1_grammy@1.38.4": { + "integrity": "sha512-hoxqwSkaXDeU7mzXulpk3A4Cmd6UZO3HU4aPoITX5ekSHK7ZcUEmMl7RhKKkqw3z6zVbbAShQreJoVV5/dDSLA==", + "dependencies": [ + "grammy" + ] + }, "@grammyjs/types@3.22.2": { "integrity": "sha512-uu7DX2ezhnBPozL3bXHmwhLvaFsh59E4QyviNH4Cij7EdVekYrs6mCzeXsa2pDk30l3uXo7DBahlZLzTPtpYZg==" }, @@ -70,6 +77,7 @@ "workspace": { "dependencies": [ "jsr:@std/assert@^1.0.16", + "npm:@grammyjs/conversations@^2.1.1", "npm:grammy@^1.38.4" ] } From 60baf6dc4d5c82ac13b15eaa03c7a620760e056c Mon Sep 17 00:00:00 2001 From: codecanna Date: Tue, 30 Dec 2025 02:47:36 -0700 Subject: [PATCH 8/9] Added ability for user to choose their own blacklists. --- classes/E621Bot.ts | 38 ++++++---- classes/E621UrlBuilderPools.ts | 2 +- classes/E621UrlBuilderPosts.ts | 2 +- constants/strings.ts | 6 +- db/migration.ts | 20 ++--- db/sql/create_blacklist_table.sql | 7 +- db/utils/createDb.ts | 12 +++ handlers/edit_blacklist.ts | 58 +++++++++++++++ main.ts | 60 ++++++++++++--- models/user.ts | 117 ++++++++++++++++++++++++++++++ tests/migration_tests.ts | 18 +++-- tests/misc_tests.ts | 9 +-- tests/parse_inline_query_tests.ts | 8 +- tests/url_builder_tests.ts | 2 +- tests/user_tests.ts | 90 +++++++++++++++++++++++ types/Blacklist.ts | 5 ++ 16 files changed, 395 insertions(+), 59 deletions(-) create mode 100644 db/utils/createDb.ts create mode 100644 handlers/edit_blacklist.ts create mode 100644 models/user.ts create mode 100644 tests/user_tests.ts create mode 100644 types/Blacklist.ts diff --git a/classes/E621Bot.ts b/classes/E621Bot.ts index f89fc8d..32d232d 100644 --- a/classes/E621Bot.ts +++ b/classes/E621Bot.ts @@ -1,40 +1,47 @@ import { Bot, InlineQueryResultBuilder } from "grammy"; +import { + Conversation, + type ConversationFlavor, + conversations, + createConversation, +} from "@grammyjs/conversations"; import { InlineQueryResult } from "grammy/types"; import { E621UrlBuilderPosts } from "./E621UrlBuilderPosts.ts"; import { ONE_MEGABYTE } from "../constants/numbers.ts"; import { E621UrlBuilderPools } from "./E621UrlBuilderPools.ts"; import { poolSearch } from "../constants/urls.ts"; -import { Post } from "./interfaces.ts"; -import { Pool } from "./interfaces.ts"; +import { Post } from "../interfaces.ts"; +import { Pool } from "../interfaces.ts"; import * as strings from "../constants/strings.ts"; import * as urls from "../constants/urls.ts"; import * as numbers from "../constants/numbers.ts"; +import { edit_blacklist } from "../handlers/edit_blacklist.ts"; +import { Context } from "grammy"; + +type e621BotContext = Context & ConversationFlavor & Conversation; /** * E621Bot can get streams of images based on a users inline query */ -export class E621Bot extends Bot { +export class E621Bot extends Bot { telegramtelegramApiKey: string; e621ApiKey: string; - blacklist: string[]; - blacklistPath: string; hits: number; last_hit_time?: string; constructor( telegramApiKey: string, e621ApiKey: string, - blacklist: string[], - blacklistPath: string = "./blacklist.txt", hits: number = 0, last_hit_time?: string, ) { super(telegramApiKey); this.telegramtelegramApiKey = telegramApiKey; this.e621ApiKey = e621ApiKey; - this.blacklist = blacklist; - this.blacklistPath = blacklistPath; this.hits = hits; this.last_hit_time = last_hit_time; + + this.use(conversations()); + this.use(createConversation(edit_blacklist)); } /** @@ -227,9 +234,8 @@ export class E621Bot extends Bot { return urlBuilder; } - processPosts(posts: Post[]): InlineQueryResult[] { + processPosts(posts: Post[], blacklistedTags: string[]): InlineQueryResult[] { const inlineQueryResults: InlineQueryResult[] = []; - post_loop: for (const post in posts) { const tagMatrix: string[][] = []; @@ -239,9 +245,11 @@ export class E621Bot extends Bot { }); const tags = tagMatrix.flat(); + console.log(this.buildBlacklistRegex(blacklistedTags)); + // Check for blacklisted tags for (const tag in tags) { - if (this.buildBlacklistRegex()?.test(tags[tag])) { + if (this.buildBlacklistRegex(blacklistedTags).test(tags[tag])) { console.log("Blacklisted found skipping post!"); continue post_loop; // Skip this post if a blacklisted tag was found } @@ -342,8 +350,8 @@ export class E621Bot extends Bot { return bytes / ONE_MEGABYTE; // Divide number of bytes by the number of bytes equal to one megabytes } - buildBlacklistRegex(): RegExp | null { - if (this.blacklist.length === 0) return null; - return new RegExp("(" + this.blacklist.join("|") + ")"); + buildBlacklistRegex(blacklistedTags: string[]): RegExp { + if (blacklistedTags.length === 0) return new RegExp(""); + return new RegExp("(" + blacklistedTags.join("|") + ")"); } } diff --git a/classes/E621UrlBuilderPools.ts b/classes/E621UrlBuilderPools.ts index b01447a..15b4bc9 100644 --- a/classes/E621UrlBuilderPools.ts +++ b/classes/E621UrlBuilderPools.ts @@ -1,5 +1,5 @@ import { POOLS_PAGE_SIZE } from "../constants/numbers.ts"; -import { E621UrlBuilder } from "./interfaces.ts"; +import { E621UrlBuilder } from "../interfaces.ts"; import * as urls from "../constants/urls.ts"; export class E621UrlBuilderPools implements E621UrlBuilder { diff --git a/classes/E621UrlBuilderPosts.ts b/classes/E621UrlBuilderPosts.ts index 17d2017..6d591b5 100644 --- a/classes/E621UrlBuilderPosts.ts +++ b/classes/E621UrlBuilderPosts.ts @@ -1,6 +1,6 @@ import * as urls from "../constants/urls.ts"; import { API_PAGE_SIZE } from "../constants/numbers.ts"; -import { E621UrlBuilder } from "./interfaces.ts"; +import { E621UrlBuilder } from "../interfaces.ts"; /** * Build an e621 URL based on parameters passed to this class diff --git a/constants/strings.ts b/constants/strings.ts index 86d0d3b..0011ad9 100644 --- a/constants/strings.ts +++ b/constants/strings.ts @@ -49,6 +49,10 @@ export const fileTypes = { }; export const keywordsRegex = "(id|creator|active|inactive|category|order)"; - export const BLACKLIST_PATH = "./blacklist.txt"; +export const defaultBlacklist = "gore,scat,watersports,young,-rating:s,loli,shota"; + +// DB strings export const SQL_BASEPATH = "db/sql"; +export const DB_FILE = "db/prod_db/blacklist.db"; +export const TEST_DB_FILE = "db/test_db/blacklist_test.db"; diff --git a/db/migration.ts b/db/migration.ts index 211230c..af4487e 100644 --- a/db/migration.ts +++ b/db/migration.ts @@ -3,13 +3,15 @@ import { DatabaseSync } from "node:sqlite"; import { SQL_BASEPATH } from "../constants/strings.ts"; export function createBlacklistDb(dbFile: PathLike) { - try { - const db = new DatabaseSync(dbFile); - const query = Deno.readTextFileSync(`${SQL_BASEPATH}/create_blacklist_table.sql`).trim(); + try { + const db = new DatabaseSync(dbFile); + const query = Deno.readTextFileSync( + `${SQL_BASEPATH}/create_blacklist_table.sql`, + ).trim(); - db.prepare(query).run(); - db.close(); - } catch (err) { - console.error(`Failed to create blacklist db: ${err}`); - } -} \ No newline at end of file + db.prepare(query).run(); + db.close(); + } catch (err) { + console.error(`Failed to create blacklist db: ${err}`); + } +} diff --git a/db/sql/create_blacklist_table.sql b/db/sql/create_blacklist_table.sql index 831f7fd..f0f2438 100644 --- a/db/sql/create_blacklist_table.sql +++ b/db/sql/create_blacklist_table.sql @@ -1,6 +1,5 @@ -CREATE TABLE IF NOT EXISTS blacklist_db ( +CREATE TABLE IF NOT EXISTS user_db ( id INTEGER PRIMARY KEY AUTOINCREMENT, - telegramId BIGINT NOT NULL, - timestamp BIGINT NOT NULL, - blacklisted_tags TEXT + telegram_id BIGINT NOT NULL UNIQUE, + blacklist TEXT DEFAULT 'gore,scat,watersports,young,loli,shota' -- Default blacklist for every user on e621 ); \ No newline at end of file diff --git a/db/utils/createDb.ts b/db/utils/createDb.ts new file mode 100644 index 0000000..0b7ad71 --- /dev/null +++ b/db/utils/createDb.ts @@ -0,0 +1,12 @@ +import { PathLike } from "node:fs"; +import { createBlacklistDb } from "../migration.ts"; + +export function createDatabase(dbFile: PathLike): boolean { + try { + createBlacklistDb(dbFile); + return true; + } catch (err) { + console.error(`Failed to create Database: ${dbFile}: ${err}`); + return false; + } +} diff --git a/handlers/edit_blacklist.ts b/handlers/edit_blacklist.ts new file mode 100644 index 0000000..668d3f3 --- /dev/null +++ b/handlers/edit_blacklist.ts @@ -0,0 +1,58 @@ +import { Conversation } from "@grammyjs/conversations"; +import { Context } from "grammy"; +import { User } from "../types/Blacklist.ts"; +import { getUserByTelegramId, updateUser } from "../models/user.ts"; +import { DB_FILE } from "../constants/strings.ts"; + +/** + * Walk user through editing their blacklist + * @param conversation + * @param ctx + */ +export async function edit_blacklist(conversation: Conversation, ctx: Context) { + const user = getUserByTelegramId(ctx.from?.id!, DB_FILE); + if (user) { + await ctx.reply(user.blacklist.join("\n")); + } else { + await ctx.reply(`Failed to retrieve blacklist to copy.`); + throw new Error(`Failed to retrieve blacklist to copy.`); + } + + await ctx.reply( + `Copy and paste the above message then you can edit it and send it back, or you can send a list of space or line seperated VALID + + Here is a list of valid e621 tags for reference there are a LOT of them.`, + { parse_mode: "HTML" }, + ); + const blacklist = (await conversation.form.text()).split("\n"); + + console.log(blacklist); + + const newUserData: User = { + telegramId: ctx.from?.id!, + blacklist: blacklist, + }; + + try { + updateUser(newUserData, DB_FILE); + } catch (err) { + console.error( + `Failed to save blacklist for ${ctx.from?.first_name} id ${ctx.from?.id}: ${err}`, + ); + } + + const newBlacklist = getUserByTelegramId(ctx.from?.id!, DB_FILE); + + if (newBlacklist) { + await ctx.reply( + `Success! Your new blacklist is: +${newBlacklist.blacklist.join("\n")}`, + { parse_mode: "HTML" }, + ); + } else { + console.error(`Failed to retrieve updated blacklist after update`); + ctx.reply( + "Failed to retrieve updated blacklist try running /blacklist to see your changes", + ); + } +} diff --git a/main.ts b/main.ts index 8506b5d..bc87a29 100644 --- a/main.ts +++ b/main.ts @@ -1,23 +1,41 @@ import { E621Bot } from "./classes/E621Bot.ts"; import { E621UrlBuilderPosts } from "./classes/E621UrlBuilderPosts.ts"; import { E621UrlBuilderPools } from "./classes/E621UrlBuilderPools.ts"; -import { Post } from "./models/interfaces.ts"; -import { Pool } from "./models/interfaces.ts"; +import { Post } from "./interfaces.ts"; +import { Pool } from "./interfaces.ts"; import * as numbers from "./constants/numbers.ts"; import * as urls from "./constants/urls.ts"; import * as strings from "./constants/strings.ts"; +import { existsSync } from "node:fs"; +import { createDatabase } from "./db/utils/createDb.ts"; +import { + userExists as userExists, + getUserByTelegramId as getUserByTelegramId, + insertUser as insertUser, +} from "./models/user.ts"; if (import.meta.main) { try { - // Load the blacklist - const decoder = new TextDecoder("utf-8"); - const blackListBytes = await Deno.readFile(strings.BLACKLIST_PATH); - const blacklist = decoder.decode(blackListBytes).split("\n"); // Separate blacklist into an array + // Create the directory structure create it and the db it its not there. + if (!existsSync(strings.DB_FILE)) { + if (!existsSync("db/prod_db")) { + Deno.mkdir("db/prod_db", { recursive: true }); + } + console.log("Creating directory structure"); + Deno.mkdir("db/prod_db", { recursive: true }); + + console.log("Attempting to create blacklist database"); + if (!createDatabase(strings.DB_FILE)) { + throw new Error(`Failed to create blacklist DB`); + } + console.log("Database created!"); + } else { + console.log(`Database found at ${strings.DB_FILE}!`); + } const yiffBot = new E621Bot( Deno.env.get("TELEGRAM_BOT_KEY") || "", Deno.env.get("E621_API_KEY") || "", - blacklist, ); yiffBot.command("start", async (ctx) => @@ -39,6 +57,20 @@ if (import.meta.main) { ); }); + yiffBot.command("blacklist", async (ctx) => { + const user = getUserByTelegramId(ctx.from?.id!, strings.DB_FILE); + if (user) { + await ctx.reply( + `This is your current blacklist: \n ${user.blacklist.join("\n")}`, + {parse_mode: "HTML"} + ); + } + }); + + yiffBot.command("edit_blacklist", async (ctx) => { + await ctx.conversation.enter("edit_blacklist"); + }); + yiffBot.command("help", async (ctx) => { await ctx.reply(strings.helpString, { parse_mode: "HTML" }); }); @@ -152,6 +184,12 @@ if (import.meta.main) { * Handle general searches */ yiffBot.on("inline_query", async (ctx) => { + // Create new user if not exists + if (!userExists(ctx.from.id, strings.DB_FILE)) { + insertUser({ telegramId: ctx.from.id, blacklist: [] }, strings.DB_FILE); + } + const user = getUserByTelegramId(ctx.from.id, strings.DB_FILE); + // Parse the inline query and create a new URL builder object based on the query const urlBuilder = yiffBot.parseInlineQuery( ctx.inlineQuery.query, @@ -184,7 +222,7 @@ if (import.meta.main) { // Grab our data const request = await yiffBot.sendRequest(urlBuilder.buildUrl()); const requestJson = await request.json(); - const postsJson = requestJson.posts; // An array of 320 posts + const postsJson = requestJson.posts; // An array of 50 posts // console.log(postsJson[0]); @@ -207,7 +245,8 @@ if (import.meta.main) { }, ); - const inlineResults = yiffBot.processPosts(posts); + // Process and filter posts through blacklist + const inlineResults = yiffBot.processPosts(posts, user!.blacklist); console.log(`Number of Results Retrieved: ${inlineResults.length}`); let resultBatch; @@ -237,7 +276,8 @@ if (import.meta.main) { `E621Bot Error: ${err.message}:${err.ctx.chosenInlineResult}`, ); }); - yiffBot.start(); + await yiffBot.start(); + console.log("Bot Started!"); } catch (error) { console.error( `Encountered and error while trying to start the bot: ${error}`, diff --git a/models/user.ts b/models/user.ts new file mode 100644 index 0000000..b34faa6 --- /dev/null +++ b/models/user.ts @@ -0,0 +1,117 @@ +import { PathLike } from "node:fs"; +import { DatabaseSync } from "node:sqlite"; +import { User } from "../types/Blacklist.ts"; +import { defaultBlacklist } from "../constants/strings.ts"; + +/** + * Insert a new user + * @param user + * @param dbFile + * @returns StatementResultingChanges + */ +export function insertUser(user: User, dbFile: PathLike) { + try { + const db = new DatabaseSync(dbFile); + const queryResult = db.prepare( + `INSERT INTO user_db (telegram_id) VALUES (?);`, + ).run( + user.telegramId, + ); + db.close(); + return queryResult; + } catch (err) { + console.error(`Failed to insert user: ${err}`); + throw err; + } +} + +/** + * Update an existing user + * @param blacklist + * @param dbFile + * @returns StatementResultingChanges + */ +export function updateUser(user: User, dbFile: PathLike) { + try { + const db = new DatabaseSync(dbFile); + const queryResult = db.prepare( + `UPDATE OR FAIL user_db SET blacklist = ? WHERE telegram_id = ${user.telegramId};`, + ).run( + user.blacklist.join(","), + ); + db.close(); + return queryResult; + } catch (err) { + console.error(`Failed to update user: ${err}`); + throw err; + } +} + +/** + * Delete a user from the db + * @param telegramId Id attatched to blacklist + * @param dbFile Path to database file + * @returns StatementResultingChanges + */ +export function deleteUser(telegramId: number, dbFile: PathLike) { + try { + const db = new DatabaseSync(dbFile); + const queryResult = db.prepare( + `DELETE FROM user_db WHERE telegram_id = ${telegramId};`, + ).run(); + db.close(); + return queryResult; + } catch (err) { + console.error(`Failed to delete user data for ${telegramId}: ${err}`); + throw err; + } +} + +/** + * Grabs a user from the db by owner's telegram id + * @param telegramId + * @param dbFile + * @returns Blacklist + */ +export function getUserByTelegramId( + telegramId: number, + dbFile: PathLike, +): User | undefined { + try { + const db = new DatabaseSync(dbFile); + const queryResult = db.prepare( + `SELECT * FROM user_db WHERE telegram_id = ${telegramId};`, + ).get(); + db.close(); + + console.log(queryResult); + + if (queryResult) { + return { + id: Number(queryResult.id), + telegramId: Number(queryResult.telegram_id), + blacklist: String(queryResult.blacklist).split(",") || defaultBlacklist.split(","), + }; + } + } catch (err) { + console.error( + `Failed to retrieve user ${telegramId}: ${err}`, + ); + throw err; + } +} + +export function userExists(telegramId: number, dbFile: PathLike) { + try { + const blacklist = getUserByTelegramId(telegramId, dbFile); + if (blacklist) { + return true; + } else { + return false; + } + } catch (err) { + console.error( + `Failed to qery the database for user: ${telegramId}: ${err}`, + ); + } +} diff --git a/tests/migration_tests.ts b/tests/migration_tests.ts index ce5163b..dab9f54 100644 --- a/tests/migration_tests.ts +++ b/tests/migration_tests.ts @@ -2,18 +2,24 @@ import { assertEquals } from "@std/assert/equals"; import { DatabaseSync } from "node:sqlite"; import { createBlacklistDb } from "../db/migration.ts"; import { assertNotEquals } from "@std/assert/not-equals"; -const testDbFile = "db/test_blacklist.db"; +import { TEST_DB_FILE } from "../constants/strings.ts"; +import { existsSync } from "node:fs"; + +if (!existsSync("db/test_db")) { + Deno.mkdir("db/test_db", { recursive: true }); +} + Deno.test(function testCreateBlacklistDb() { - createBlacklistDb(testDbFile); + createBlacklistDb(TEST_DB_FILE); // Get table - const db = new DatabaseSync(testDbFile); + const db = new DatabaseSync(TEST_DB_FILE); const table = db.prepare( - `SELECT name FROM sqlite_master WHERE type ='table' AND name = 'blacklist_db';`, + `SELECT name FROM sqlite_master WHERE type ='table' AND name = 'user_db';`, ).get(); db.close(); assertNotEquals(table, undefined); - assertEquals(table?.name, "blacklist_db"); - Deno.removeSync(testDbFile); + assertEquals(table?.name, "user_db"); + Deno.removeSync(TEST_DB_FILE); }); diff --git a/tests/misc_tests.ts b/tests/misc_tests.ts index 792e498..4524732 100644 --- a/tests/misc_tests.ts +++ b/tests/misc_tests.ts @@ -1,6 +1,6 @@ import { assertEquals } from "@std/assert/equals"; -import { E621Bot } from "../models/E621Bot.ts"; -import { E621UrlBuilderPools } from "../models/E621UrlBuilderPools.ts"; +import { E621Bot } from "../classes/E621Bot.ts"; +import { E621UrlBuilderPools } from "../classes/E621UrlBuilderPools.ts"; import * as urls from "../constants/urls.ts"; // TODO: Write test for processPosts() @@ -17,7 +17,6 @@ Deno.test(async function sendRequestTest() { const testBot = new E621Bot( Deno.env.get("TELEGRAM_BOT_KEY") || "", Deno.env.get("E621_API_KEY") || "", - new Array(), ); const testResponse = await testBot.sendRequest(testUrl); await testResponse.body?.cancel(); // Cancel test request @@ -28,7 +27,6 @@ Deno.test(function calcMegabytesTest() { const testBot = new E621Bot( Deno.env.get("TELEGRAM_BOT_KEY") || "", Deno.env.get("E621_API_KEY") || "", - new Array(), ); const testValue = 1024; // bytes assertEquals(Math.ceil(testBot.calcMegabytes(testValue)), 1); @@ -43,10 +41,9 @@ Deno.test(function buildBlacklistRegexTest() { const testBot = new E621Bot( Deno.env.get("TELEGRAM_BOT_KEY") || "", Deno.env.get("E621_API_KEY") || "", - blacklist, ); assertEquals( - testBot.buildBlacklistRegex(), + testBot.buildBlacklistRegex(blacklist), /(feces|murder|waterworks)/, ); }); diff --git a/tests/parse_inline_query_tests.ts b/tests/parse_inline_query_tests.ts index 1f2e15d..b8557f4 100644 --- a/tests/parse_inline_query_tests.ts +++ b/tests/parse_inline_query_tests.ts @@ -1,7 +1,7 @@ import { assertEquals } from "@std/assert/equals"; -import { E621Bot } from "../models/E621Bot.ts"; -import { E621UrlBuilderPools } from "../models/E621UrlBuilderPools.ts"; -import { E621UrlBuilderPosts } from "../models/E621UrlBuilderPosts.ts"; +import { E621Bot } from "../classes/E621Bot.ts"; +import { E621UrlBuilderPools } from "../classes/E621UrlBuilderPools.ts"; +import { E621UrlBuilderPosts } from "../classes/E621UrlBuilderPosts.ts"; import * as urls from "../constants/urls.ts"; import * as numbers from "../constants/numbers.ts"; @@ -12,7 +12,6 @@ Deno.test(function parseInlineQueryTest() { const testBot = new E621Bot( Deno.env.get("TELEGRAM_BOT_KEY") || "", Deno.env.get("E621_API_KEY") || "", - new Array(), ); // Create our test queries @@ -213,7 +212,6 @@ Deno.test(function parseInlineQueryPoolsTest() { const testBot = new E621Bot( Deno.env.get("TELEGRAM_BOT_KEY") || "", Deno.env.get("E621_API_KEY") || "", - new Array(), ); // Define test queries for each case diff --git a/tests/url_builder_tests.ts b/tests/url_builder_tests.ts index 336812b..d0bb695 100644 --- a/tests/url_builder_tests.ts +++ b/tests/url_builder_tests.ts @@ -1,6 +1,6 @@ import { assertEquals } from "@std/assert/equals"; import { API_PAGE_SIZE } from "../constants/numbers.ts"; -import { E621UrlBuilderPosts } from "../models/E621UrlBuilderPosts.ts"; +import { E621UrlBuilderPosts } from "../classes/E621UrlBuilderPosts.ts"; Deno.test(function buildUrlPostsTest() { const testUrl = diff --git a/tests/user_tests.ts b/tests/user_tests.ts new file mode 100644 index 0000000..1aa5ad9 --- /dev/null +++ b/tests/user_tests.ts @@ -0,0 +1,90 @@ +import { existsSync } from "node:fs"; +import { TEST_DB_FILE } from "../constants/strings.ts"; +import { createBlacklistDb } from "../db/migration.ts"; +import { + userExists, + deleteUser, + getUserByTelegramId as getUserByTelegramId, + insertUser, + updateUser, +} from "../models/user.ts"; +import { User } from "../types/Blacklist.ts"; +import { assertEquals } from "@std/assert/equals"; +import { assertObjectMatch } from "@std/assert/object-match"; + +const testBlacklist: User = { + id: 1, + telegramId: 12345, + blacklist: [ + "gore", + "scat", + "watersports", + "young", + "loli", + "shota", + ], // Default tags +}; + +if (!existsSync("db/test_db")) { + Deno.mkdir("db/test_db", { recursive: true }); +} + +Deno.test(function testInsertBlacklist() { + createBlacklistDb(TEST_DB_FILE); + + const queryResult = insertUser(testBlacklist, TEST_DB_FILE); + assertEquals(queryResult?.changes, 1); + assertEquals(queryResult?.lastInsertRowid, 1); + Deno.removeSync(TEST_DB_FILE); +}); + +Deno.test(function testGetUserByTelegramId() { + createBlacklistDb(TEST_DB_FILE); + insertUser(testBlacklist, TEST_DB_FILE); + + const user = getUserByTelegramId(12345, TEST_DB_FILE); + + console.log(user); + + assertObjectMatch(user!, testBlacklist); + Deno.removeSync(TEST_DB_FILE); +}); + +Deno.test(function testUpdateUser() { + createBlacklistDb(TEST_DB_FILE); + insertUser(testBlacklist, TEST_DB_FILE); + + const updatedBlacklist = testBlacklist; + + updatedBlacklist.blacklist.push("test", "tags"); + + const queryResult = updateUser(updatedBlacklist, TEST_DB_FILE); + + assertEquals(queryResult?.changes, 1); + assertEquals(queryResult?.lastInsertRowid, 0); + Deno.removeSync(TEST_DB_FILE); +}); + +Deno.test(function testDeleteUser() { + createBlacklistDb(TEST_DB_FILE); + insertUser(testBlacklist, TEST_DB_FILE); + + const queryResult = deleteUser(testBlacklist.telegramId, TEST_DB_FILE); + assertEquals(queryResult?.changes, 1); + assertEquals(queryResult?.lastInsertRowid, 0); + Deno.removeSync(TEST_DB_FILE); +}); + +Deno.test(function testUserExist() { + createBlacklistDb(TEST_DB_FILE); + insertUser(testBlacklist, TEST_DB_FILE); + + const result1 = userExists(12345, TEST_DB_FILE); + assertEquals(result1, true); + + deleteUser(12345, TEST_DB_FILE); + + const result2 = userExists(12345, TEST_DB_FILE); + assertEquals(result2, false); + Deno.removeSync(TEST_DB_FILE); +}); diff --git a/types/Blacklist.ts b/types/Blacklist.ts new file mode 100644 index 0000000..e529c33 --- /dev/null +++ b/types/Blacklist.ts @@ -0,0 +1,5 @@ +export type User = { + id?: number; + telegramId: number; + blacklist: string[]; +}; From 4d00526843143c7dc425794daad77423ce5c32f7 Mon Sep 17 00:00:00 2001 From: codecanna Date: Tue, 30 Dec 2025 02:48:24 -0700 Subject: [PATCH 9/9] deno fmt --- constants/strings.ts | 3 ++- main.ts | 8 +++++--- models/user.ts | 3 ++- tests/user_tests.ts | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/constants/strings.ts b/constants/strings.ts index 0011ad9..1817ba7 100644 --- a/constants/strings.ts +++ b/constants/strings.ts @@ -50,7 +50,8 @@ export const fileTypes = { export const keywordsRegex = "(id|creator|active|inactive|category|order)"; export const BLACKLIST_PATH = "./blacklist.txt"; -export const defaultBlacklist = "gore,scat,watersports,young,-rating:s,loli,shota"; +export const defaultBlacklist = + "gore,scat,watersports,young,-rating:s,loli,shota"; // DB strings export const SQL_BASEPATH = "db/sql"; diff --git a/main.ts b/main.ts index bc87a29..147ea6a 100644 --- a/main.ts +++ b/main.ts @@ -9,9 +9,9 @@ import * as strings from "./constants/strings.ts"; import { existsSync } from "node:fs"; import { createDatabase } from "./db/utils/createDb.ts"; import { - userExists as userExists, getUserByTelegramId as getUserByTelegramId, insertUser as insertUser, + userExists as userExists, } from "./models/user.ts"; if (import.meta.main) { @@ -61,8 +61,10 @@ if (import.meta.main) { const user = getUserByTelegramId(ctx.from?.id!, strings.DB_FILE); if (user) { await ctx.reply( - `This is your current blacklist: \n ${user.blacklist.join("\n")}`, - {parse_mode: "HTML"} + `This is your current blacklist: \n ${ + user.blacklist.join("\n") + }`, + { parse_mode: "HTML" }, ); } }); diff --git a/models/user.ts b/models/user.ts index b34faa6..8b728a6 100644 --- a/models/user.ts +++ b/models/user.ts @@ -90,7 +90,8 @@ export function getUserByTelegramId( return { id: Number(queryResult.id), telegramId: Number(queryResult.telegram_id), - blacklist: String(queryResult.blacklist).split(",") || defaultBlacklist.split(","), + blacklist: String(queryResult.blacklist).split(",") || + defaultBlacklist.split(","), }; } } catch (err) { diff --git a/tests/user_tests.ts b/tests/user_tests.ts index 1aa5ad9..a72d0ca 100644 --- a/tests/user_tests.ts +++ b/tests/user_tests.ts @@ -2,11 +2,11 @@ import { existsSync } from "node:fs"; import { TEST_DB_FILE } from "../constants/strings.ts"; import { createBlacklistDb } from "../db/migration.ts"; import { - userExists, deleteUser, getUserByTelegramId as getUserByTelegramId, insertUser, updateUser, + userExists, } from "../models/user.ts"; import { User } from "../types/Blacklist.ts"; import { assertEquals } from "@std/assert/equals";