revamp booru

This commit is contained in:
Ashley Graves 2024-10-11 14:07:01 +02:00
parent 7c741f88d5
commit ff2e2ea587
8 changed files with 231 additions and 38 deletions

7
.gitignore vendored
View file

@ -72,8 +72,7 @@ web_modules/
# Yarn Integrity file # Yarn Integrity file
.yarn-integrity .yarn-integrity
# dotenv environment variable files and credentials # dotenv environment variable files
credentials.json
.env .env
.env.development.local .env.development.local
.env.test.local .env.test.local
@ -130,5 +129,9 @@ dist
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
# configs
credentials.json
emojis.json
# database # database
data.db data.db

View file

@ -0,0 +1,16 @@
{
"booru": {
"rating": {
"safe": "<:rating_safe:1293819920978804829>",
"general": "<:rating_general:1293819929199513610>",
"questionable": "<:rating_questionable:1293819907099725925>",
"explicit": "<:rating_explicit:1293819893795389491>",
"unknown": "<:rating_unknown:1293819936845594665>"
},
"score": {
"green_arrow_up": "<:green_arrow_up:1293819944399667222>",
"red_arrow_down": "<:red_arrow_down:1293819951764869181>",
"yellow_tilde": "<:yellow_tilde:1293819958643396608>"
}
}
}

View file

@ -3,7 +3,7 @@ const { generateImageUrl } = require('@imgproxy/imgproxy-node');
const { stringify } = require("node:querystring"); const { stringify } = require("node:querystring");
const { readFileSync } = require("node:fs"); const { readFileSync } = require("node:fs");
const { decode } = require("html-entities"); const { decode } = require("html-entities");
const { extname } = require("node:path"); const { extname, basename } = require("node:path");
const { knex } = require("../../db.js"); const { knex } = require("../../db.js");
const Booru = require("@himeka/booru"); const Booru = require("@himeka/booru");
@ -63,11 +63,11 @@ function notEmpty(str) {
return str.trim() !== '' return str.trim() !== ''
} }
const regexCutTags = /[\S\s]{1,75}[^,]{0,25}/; const regexCutTags = /[\S\s]{1,75}[^,]{0,125}/;
function formatTags(tags) { function formatTags(tags) {
const tagString = decode(tags.join(', ')); const tagString = decode(tags.join(', '));
if (tagString.length < 100) { if (tagString.length < 500) {
return tagString; return tagString;
} }
@ -75,12 +75,15 @@ function formatTags(tags) {
return `${escapeMarkdown(tagCutMatch[0] ?? '')}, ...`; return `${escapeMarkdown(tagCutMatch[0] ?? '')}, ...`;
} }
var credentials = JSON.parse(readFileSync("config/credentials.json"));
var emojis = JSON.parse(readFileSync("config/emojis.json"));
const ratingEmojis = { const ratingEmojis = {
s: '<:rating_safe:1293819920978804829>', s: emojis.booru.rating.safe,
g: '<:rating_general:1293819929199513610>', g: emojis.booru.rating.general,
q: '<:rating_questionable:1293819907099725925>', q: emojis.booru.rating.questionable,
e: '<:rating_explicit:1293819893795389491>', e: emojis.booru.rating.explicit,
u: '<:rating_unknown:1293819936845594665>', u: emojis.booru.rating.unknown
} }
function formatRating(rating) { function formatRating(rating) {
@ -89,11 +92,11 @@ function formatRating(rating) {
function formatScore(score) { function formatScore(score) {
if (score > 0) { if (score > 0) {
return `<:green_arrow_up:1293819944399667222> ${score}` return `${emojis.booru.score.green_arrow_up} ${score}`
} else if (score < 0) { } else if (score < 0) {
return `<:red_arrow_down:1293819951764869181> ${score}` return `${emojis.booru.score.red_arrow_down} ${score}`
} else { } else {
return `<:yellow_tilde:1293819958643396608> ${score}` return `${emojis.booru.score.yellow_tilde} ${score}`
} }
} }
@ -106,8 +109,6 @@ const blacklist = [
"ai_art" "ai_art"
]; ];
var credentials = JSON.parse(readFileSync("credentials.json"));
function proxy(url) { function proxy(url) {
if (!process.env.IMGPROXY_HOST) if (!process.env.IMGPROXY_HOST)
return url; return url;
@ -142,10 +143,12 @@ module.exports = {
if (!result) if (!result)
result = { blacklist: '' }; result = { blacklist: '' };
const userBlacklist = (result.blacklist ?? "").trim().split(" "); const userBlacklist = (result.blacklist ?? "").trim().split(" ").filter(notEmpty);
const searchTags = [rating, ...tags, ...[...blacklist, ...userBlacklist].map(i => "-" + i)]; const searchTags = [rating, ...tags, ...[...blacklist, ...userBlacklist].map(i => "-" + i)];
const startTime = process.hrtime.bigint(); const startTime = process.hrtime.bigint();
console.log(searchTags);
var post = (await Booru.search(booru, searchTags, { limit: 1, random: true, credentials: credentials[booru] ?? null }))[0]; var post = (await Booru.search(booru, searchTags, { limit: 1, random: true, credentials: credentials[booru] ?? null }))[0];
if (post == null) { if (post == null) {
await interaction.followUp("<:warning:1293874152150667315> Could not find any post matching tags."); await interaction.followUp("<:warning:1293874152150667315> Could not find any post matching tags.");
@ -155,12 +158,13 @@ module.exports = {
const endTime = process.hrtime.bigint(); const endTime = process.hrtime.bigint();
const timeTaken = endTime - startTime; const timeTaken = endTime - startTime;
const ext = extname((post['data']).file_name ?? post.fileUrl ?? '').toLowerCase(); const fileName = (post.rating != "g" ? "SPOILER_" : "") + basename(post.fileUrl);
const ext = extname(fileName).toLowerCase();
const leadingDescription = [ const leadingDescription = [
`**Score:** ${formatScore(post.score ?? 0)}`, `**Score:** ${formatScore(post.score ?? 0)}`,
`**Rating:** ${formatRating(post.rating)}`, `**Rating:** ${formatRating(post.rating)}`,
`[File URL](${post.fileUrl})`, `[File URL](<${post.fileUrl}>)`,
`\`${ext}\``, `\`${ext}\``,
].join(' | ') ].join(' | ')
@ -174,26 +178,24 @@ module.exports = {
timeTaken ? formatTime(timeTaken) : '', timeTaken ? formatTime(timeTaken) : '',
].filter(notEmpty).join(' · ') ].filter(notEmpty).join(' · ')
if (isEmbeddableFileType(ext)) { const embed = new EmbedBuilder()
const embed = new EmbedBuilder() .setColor("#cba6f7")
.setColor("#cba6f7") .setTitle(`Post #${post.id}`)
.setTitle(`Post #${post.id}`) .setURL(post.postView)
.setURL(post.postView) .setDescription(description)
.setDescription(description) .setFooter({
.setImage(post.fileUrl) text: footerText,
.setFooter({ iconURL: proxy(`https://${post.booru.domain}/favicon.ico`),
text: footerText, })
iconURL: proxy(`https://${post.booru.domain}/favicon.ico`),
})
await interaction.followUp({ content: "", embeds: [embed.data] }); await interaction.followUp({
} else { content: "",
await interaction.followUp( embeds: [embed.data],
'>>> ' + bold(`[Post #${post.id}](<${post.postView}>)`) + "\n" + files: [{
description + "\n" + attachment: post.fileUrl,
footerText name: fileName
); }]
} });
}, },
async autocomplete(interaction) { async autocomplete(interaction) {
const focusedValue = interaction.options.getFocused(); const focusedValue = interaction.options.getFocused();

View file

@ -0,0 +1,98 @@
const { InteractionContextType, ApplicationIntegrationType, SlashCommandBuilder } = require("discord.js");
const { format } = require("node:util");
const { knex } = require("../../db.js");
const configData = {
"yuri": {
"description": "Automated yuri posting",
"options": [{
"name": "enable",
"required": true,
"type": "Boolean",
"description": "Should the bot post yuri?"
}, {
"name": "channel",
"type": "Channel",
"description": "Where to post yuri",
}]
}
}
const data = new SlashCommandBuilder()
.setName("serverconfig")
.setDescription("Manage your server settings")
.setContexts([
InteractionContextType.Guild
])
.setIntegrationTypes([
ApplicationIntegrationType.GuildInstall
]);
for (const cfg in configData) {
const config = configData[cfg];
data.addSubcommand((builder) => {
builder
.setName(cfg)
.setDescription(config.description);
for (const opt in config.options) {
const option = config.options[opt];
builder[`add${option.type}Option`](optionBuilder =>
optionBuilder
.setName(option.name)
.setRequired(option.required ?? false)
.setDescription(option.description)
);
}
return builder;
});
}
module.exports = {
data,
async execute(interaction) {
var result = await knex.select("data").from("serverconfigs").where("id", interaction.guildId).first();
if (!result)
result = { data: '{}' };
const config = JSON.parse(result.data);
const cfg = interaction.options.getSubcommand(true);
config[cfg] = config[cfg] ?? {};
for (const option of configData[cfg].options) {
config[cfg][option.name] = interaction.options[`get${option.type}`](option.name);
}
const data = {
id: interaction.guildId,
data: JSON.stringify(config)
}
await knex.raw(format('%s ON CONFLICT (id) DO UPDATE SET %s',
knex("serverconfigs").insert(data).toString().toString(),
knex("serverconfigs").update(data).whereRaw(`'serverconfigs'.id = '${interaction.guildId}'`).toString().replace(/^update\s.*\sset\s/i, '')
));
interaction.reply({ content: "Settings updated!", ephemeral: true });
},
async autocomplete(interaction) {
const focusedOption = interaction.options.getFocused(true);
const command = interaction.options.getSubcommand(true);
console.log(command, focusedOption);
const id = "";
const choices = [];
for (const option in configData) {
if (focusedOption.name == "name" && option.startsWith(focusedOption.value))
choices.push(option);
else if (focusedOption.name == "value" && (option == interaction.options.getString("name") ?? ""))
choices.push(...buildChoices(option, interaction));
}
await interaction.respond(choices.map(choice => ({ name: choice, value: choice })))
},
};

View file

@ -0,0 +1,62 @@
const { InteractionContextType, ApplicationIntegrationType, SlashCommandBuilder } = require("discord.js");
const { knex } = require("../../db.js");
const configData = {}
const data = new SlashCommandBuilder()
.setName("userconfig")
.setDescription("Manage your user settings")
.setContexts([
InteractionContextType.Guild,
InteractionContextType.BotDM,
InteractionContextType.PrivateChannel
])
.setIntegrationTypes([
ApplicationIntegrationType.UserInstall
]);
for (const option in configData) {
const config = configData[option];
data.addSubcommand((builder) => {
builder
.setName(option)
.setDescription(config.description)
switch (config.type) {
case "bool":
builder.addBooleanOption(builder =>
builder.setName("value")
);
case "channel":
builder.addChannelOption(builder =>
builder.setName("channel")
);
default:
}
})
}
module.exports = {
data,
async execute(interaction) {
interaction.reply("Not implemented yet, sorry!");
},
async autocomplete(interaction) {
const focusedOption = interaction.options.getFocused(true);
const command = interaction.options.getSubcommand(true);
console.log(command, focusedOption);
const id = "";
const choices = [];
for (const option in configData) {
if (focusedOption.name == "name" && option.startsWith(focusedOption.value))
choices.push(option);
else if (focusedOption.name == "value" && (option == interaction.options.getString("name") ?? ""))
choices.push(...buildChoices(option, interaction));
}
await interaction.respond(choices.map(choice => ({ name: choice, value: choice })))
},
};

View file

@ -80,6 +80,18 @@ client.once(Events.ClientReady, async () => {
table.string("blacklist"); table.string("blacklist");
}); });
if (!(await knex.schema.hasTable("serverconfigs")))
await knex.schema.createTable("serverconfigs", function (table) {
table.string("id").primary();
table.string("data");
});
if (!(await knex.schema.hasTable("userconfigs")))
await knex.schema.createTable("userconfigs", function (table) {
table.string("id").primary();
table.string("data");
});
var user = client.user.toJSON(); var user = client.user.toJSON();
for (const prompt of fs.readdirSync(promptsDir)) { for (const prompt of fs.readdirSync(promptsDir)) {