Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/bin
/bin
*.db
38 changes: 23 additions & 15 deletions models/E621Bot.ts → classes/E621Bot.ts
Original file line number Diff line number Diff line change
@@ -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<Context> & Conversation;

/**
* E621Bot can get streams of images based on a users inline query
*/
export class E621Bot extends Bot {
export class E621Bot extends Bot<e621BotContext> {
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));
}

/**
Expand Down Expand Up @@ -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[][] = [];
Expand All @@ -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
}
Expand Down Expand Up @@ -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("|") + ")");
}
}
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 7 additions & 1 deletion constants/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,11 @@ 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";
17 changes: 17 additions & 0 deletions db/migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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}`);
}
}
5 changes: 5 additions & 0 deletions db/sql/create_blacklist_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS user_db (
id INTEGER PRIMARY KEY AUTOINCREMENT,
telegram_id BIGINT NOT NULL UNIQUE,
blacklist TEXT DEFAULT 'gore,scat,watersports,young,loli,shota' -- Default blacklist for every user on e621
);
12 changes: 12 additions & 0 deletions db/utils/createDb.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
9 changes: 5 additions & 4 deletions deno.json
Original file line number Diff line number Diff line change
@@ -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"
}
Expand Down
8 changes: 8 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions handlers/edit_blacklist.ts
Original file line number Diff line number Diff line change
@@ -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 <b><u>VALID</u></b>

<a href='https://e621.net/tags'>Here</a> 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:
<b>${newBlacklist.blacklist.join("\n")}</b>`,
{ 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",
);
}
}
File renamed without changes.
68 changes: 55 additions & 13 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
import { E621Bot } from "./models/E621Bot.ts";
import { E621UrlBuilderPosts } from "./models/E621UrlBuilderPosts.ts";
import { E621UrlBuilderPools } from "./models/E621UrlBuilderPools.ts";
import { Post } from "./models/interfaces.ts";
import { Pool } from "./models/interfaces.ts";
import { E621Bot } from "./classes/E621Bot.ts";
import { E621UrlBuilderPosts } from "./classes/E621UrlBuilderPosts.ts";
import { E621UrlBuilderPools } from "./classes/E621UrlBuilderPools.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 {
getUserByTelegramId as getUserByTelegramId,
insertUser as insertUser,
userExists as userExists,
} 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) =>
Expand All @@ -39,6 +57,22 @@ 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 <b>${
user.blacklist.join("\n")
}</b>`,
{ 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" });
});
Expand Down Expand Up @@ -152,6 +186,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,
Expand Down Expand Up @@ -184,7 +224,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]);

Expand All @@ -207,7 +247,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;
Expand Down Expand Up @@ -237,7 +278,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}`,
Expand Down
Loading