friendship ended with S3, now posix file system is my best friend
This commit is contained in:
parent
4eb781732b
commit
ef03b23fa9
7 changed files with 99 additions and 53 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -10,5 +10,5 @@ yarn-error.log*
|
||||||
/.pnp
|
/.pnp
|
||||||
src/**/*.js
|
src/**/*.js
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
/shitposts
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|
@ -14,8 +14,8 @@ export abstract class ContextCommand<T extends User | Message> extends ICommand
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Command extends ICommand {
|
export abstract class Command extends ICommand {
|
||||||
abstract run(interaction: ChatInputCommandInteraction, config: Config, s3: S3Client): Promise<void>;
|
abstract run(interaction: ChatInputCommandInteraction, config: Config): Promise<void>;
|
||||||
autoComplete(interaction: AutocompleteInteraction, config: Config, option: AutocompleteFocusedOption, s3: S3Client): Promise<void> {
|
autoComplete(interaction: AutocompleteInteraction, config: Config, option: AutocompleteFocusedOption): Promise<void> {
|
||||||
throw new Error("Autocompletion called on command that does not have #autoComplete implemented.");
|
throw new Error("Autocompletion called on command that does not have #autoComplete implemented.");
|
||||||
}
|
}
|
||||||
abstract slashCommand: SharedSlashCommand
|
abstract slashCommand: SharedSlashCommand
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,14 @@ import {
|
||||||
Message
|
Message
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import { ContextCommand } from "../command.ts";
|
import { ContextCommand } from "../command.ts";
|
||||||
import {PutObjectCommand, type S3Client} from "@aws-sdk/client-s3";
|
|
||||||
import type {Config} from "../config.ts";
|
import type {Config} from "../config.ts";
|
||||||
import {BUCKETNAME} from "./shitpost.ts";
|
import {BUCKETNAME} from "./shitpost.ts";
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import {fileURLToPath} from "url";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
export default class Mock extends ContextCommand<Message> {
|
export default class Mock extends ContextCommand<Message> {
|
||||||
targetType: ApplicationCommandType.Message = ApplicationCommandType.Message;
|
targetType: ApplicationCommandType.Message = ApplicationCommandType.Message;
|
||||||
|
|
@ -16,26 +21,40 @@ export default class Mock extends ContextCommand<Message> {
|
||||||
.setName('AddToShitposts')
|
.setName('AddToShitposts')
|
||||||
.setType(ApplicationCommandType.Message)
|
.setType(ApplicationCommandType.Message)
|
||||||
async run(interaction: ContextMenuCommandInteraction, target: Message, config:Config): Promise<void> {
|
async run(interaction: ContextMenuCommandInteraction, target: Message, config:Config): Promise<void> {
|
||||||
await interaction.deferReply()
|
await interaction.deferReply();
|
||||||
await interaction.followUp({content: "uploading..."})
|
await interaction.followUp({content: "uploading..."});
|
||||||
for (const [_, attachment] of target.attachments) {
|
|
||||||
|
|
||||||
const response = await fetch(attachment.proxyURL);
|
const downloadFolderPath = path.join(__dirname, '..', '..', 'shitposts');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.mkdir(downloadFolderPath, { recursive: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating download folder:", error);
|
||||||
|
await interaction.editReply({ content: "the fucking posix file system failed me (download foler couldnt be made)" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [_, attachment] of target.attachments) {
|
||||||
|
const response = await fetch(attachment.url);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
await interaction.reply({ content: "discord shat itself??????" });
|
await interaction.editReply({ content: "discord shat itself while fetching an attachment!?" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await response.arrayBuffer();
|
const buffer = await response.arrayBuffer();
|
||||||
|
const fileName = attachment.name || `attachment_${attachment.id}`;
|
||||||
|
const filePath = path.join(downloadFolderPath, fileName);
|
||||||
|
|
||||||
const command = new PutObjectCommand({
|
try {
|
||||||
Bucket: BUCKETNAME,
|
await fs.writeFile(filePath, Buffer.from(buffer));
|
||||||
Key: attachment.name,
|
console.log(`Downloaded: ${fileName}`);
|
||||||
Body: Buffer.from(buffer),
|
} catch (error) {
|
||||||
});
|
console.error(`Error downloading ${fileName}:`, error);
|
||||||
await config.s3.send(command)
|
await interaction.editReply({ content: `Failed to download ${fileName}.` });
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await interaction.editReply({content: "shits have been posted"})
|
await interaction.editReply({content: "shits have been posted!"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -29,7 +29,7 @@ function keepV(url: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class PingCommand extends Command {
|
export default class PingCommand extends Command {
|
||||||
async run(interaction: ChatInputCommandInteraction, config: Config, s3: S3Client): Promise<void> {
|
async run(interaction: ChatInputCommandInteraction, config: Config): Promise<void> {
|
||||||
await interaction.deferReply()
|
await interaction.deferReply()
|
||||||
const user = interaction.options.getString("user") ?? config.listenbrainzAccount;
|
const user = interaction.options.getString("user") ?? config.listenbrainzAccount;
|
||||||
const usesonglink = interaction.options.getBoolean("usesonglink") ?? true
|
const usesonglink = interaction.options.getBoolean("usesonglink") ?? true
|
||||||
|
|
|
||||||
|
|
@ -6,48 +6,84 @@ import {
|
||||||
SlashCommandBuilder
|
SlashCommandBuilder
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import {config, type Config} from "../config.ts";
|
import {config, type Config} from "../config.ts";
|
||||||
import {ListObjectsV2Command, type S3Client} from "@aws-sdk/client-s3";
|
|
||||||
import {inspect} from "node:util";
|
import {inspect} from "node:util";
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import {fileURLToPath} from "url";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
export const BUCKETNAME = "shitposts" as const;
|
export const BUCKETNAME = "shitposts" as const;
|
||||||
|
export const DOWNLOAD_FOLDER_PATH = path.join(__dirname, '..', '..', 'shitposts');
|
||||||
|
async function getFilesInFolder(folderPath: string): Promise<{ name: string, value: string }[]> {
|
||||||
|
try {
|
||||||
|
const files = await fs.readdir(folderPath);
|
||||||
|
const fileList: { name: string, value: string }[] = [];
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(folderPath, file);
|
||||||
|
const stats = await fs.stat(filePath);
|
||||||
|
|
||||||
|
if (stats.isFile()) {
|
||||||
|
fileList.push({
|
||||||
|
name: file,
|
||||||
|
value: file
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileList;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error reading directory ${folderPath}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
export default class ShitPostCommand extends Command {
|
export default class ShitPostCommand extends Command {
|
||||||
|
|
||||||
|
|
||||||
async run(interaction: ChatInputCommandInteraction, config: Config, s3: S3Client) {
|
async run(interaction: ChatInputCommandInteraction, config: Config) {
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
const shitpost = interaction.options.getString("shitpost")!;
|
const fileName = interaction.options.getString('shitpost', true);
|
||||||
|
|
||||||
const shitpostUrl = "https://sp.amy.rip/" + encodeURIComponent(shitpost);
|
const filePath = path.join(DOWNLOAD_FOLDER_PATH, fileName);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(shitpostUrl);
|
await fs.access(filePath);
|
||||||
|
const attachment = new AttachmentBuilder(filePath, { name: fileName });
|
||||||
|
await interaction.editReply({
|
||||||
|
files: [attachment]
|
||||||
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
} catch (error: any) {
|
||||||
await interaction.followUp({content: "S3 bucket shat itself??????"});
|
if (error.code === 'ENOENT') {
|
||||||
return;
|
console.error(`file not found ${filePath}`, error);
|
||||||
|
await interaction.editReply({
|
||||||
|
content: `\`${fileName}\`. wasnt found, aka something shat itself`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error(`Error sending file ${fileName}:`, error);
|
||||||
|
await interaction.editReply({
|
||||||
|
content: `buh, shitpost (\`${fileName}\`) wasnt posted.`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await response.arrayBuffer();
|
|
||||||
const attachment = new AttachmentBuilder(Buffer.from(buffer), {name: shitpost});
|
|
||||||
|
|
||||||
await interaction.followUp({files: [attachment]});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
await interaction.followUp({content: "fileproccessing shat itself"});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async autoComplete(interaction: AutocompleteInteraction, config: Config, option: AutocompleteFocusedOption, s3: S3Client): Promise<void> {
|
|
||||||
|
|
||||||
await interaction.respond((await s3.send(new ListObjectsV2Command({Bucket: BUCKETNAME}))).Contents!
|
async autoComplete(interaction: AutocompleteInteraction, config: Config, option: AutocompleteFocusedOption): Promise<void> {
|
||||||
.filter((i): i is { Key: string } => !!i.Key)
|
const files = await getFilesInFolder(DOWNLOAD_FOLDER_PATH);
|
||||||
.map(key => ({name: key.Key, value: key.Key})))
|
|
||||||
|
|
||||||
|
const focusedValue = option.value.toLowerCase();
|
||||||
|
const filteredFiles = files.filter(choice => choice.name.toLowerCase().includes(focusedValue));
|
||||||
|
|
||||||
|
await interaction.respond(
|
||||||
|
filteredFiles.slice(0, 25)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
slashCommand = new SlashCommandBuilder()
|
slashCommand = new SlashCommandBuilder()
|
||||||
.setName("shitpost")
|
.setName("shitpost")
|
||||||
.setDescription("shitpost with S3!!!!!").setIntegrationTypes([
|
.setDescription("shitpost with the posix file system!!!!!!").setIntegrationTypes([
|
||||||
ApplicationIntegrationType.UserInstall
|
ApplicationIntegrationType.UserInstall
|
||||||
]).addStringOption(option => {
|
]).addStringOption(option => {
|
||||||
return option.setName("shitpost").setRequired(true).setDescription("the shitposts name")
|
return option.setName("shitpost").setRequired(true).setDescription("the shitposts name")
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ const configT = z.object({
|
||||||
R2AccessKeyId: z.string(),
|
R2AccessKeyId: z.string(),
|
||||||
R2SecretAccessKey: z.string(),
|
R2SecretAccessKey: z.string(),
|
||||||
});
|
});
|
||||||
export type RawConfig = z.infer<typeof configT>;
|
export type Config = z.infer<typeof configT>;
|
||||||
export type Config = RawConfig & {s3: S3Client}
|
export const config: Config = configT.parse(rawconfig);
|
||||||
export const config: RawConfig = configT.parse(rawconfig);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
16
src/index.ts
16
src/index.ts
|
|
@ -21,15 +21,7 @@ const client = new Client({
|
||||||
|
|
||||||
const allCommands: ICommand[] = []
|
const allCommands: ICommand[] = []
|
||||||
|
|
||||||
const S3 = new S3Client({
|
|
||||||
region: "auto",
|
|
||||||
endpoint: `https://${config.R2AccountID}.r2.cloudflarestorage.com`,
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: config.R2AccessKeyId,
|
|
||||||
secretAccessKey: config.R2SecretAccessKey,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const enrichedConfig = {...config, s3:S3} satisfies Config;
|
|
||||||
const commandDir = path.join(__dirname, "commands");
|
const commandDir = path.join(__dirname, "commands");
|
||||||
for (const file of fs.readdirSync(commandDir)) {
|
for (const file of fs.readdirSync(commandDir)) {
|
||||||
if (!file.endsWith('.ts')) continue
|
if (!file.endsWith('.ts')) continue
|
||||||
|
|
@ -81,7 +73,7 @@ client.on(Events.InteractionCreate, async (interaction) => {
|
||||||
if (command.targetType != (interaction.isUserContextMenuCommand() ? ApplicationCommandType.User : ApplicationCommandType.Message))
|
if (command.targetType != (interaction.isUserContextMenuCommand() ? ApplicationCommandType.User : ApplicationCommandType.Message))
|
||||||
console.error("Out of date discord definition of this context command")
|
console.error("Out of date discord definition of this context command")
|
||||||
try {
|
try {
|
||||||
await command.run(interaction, interaction.isUserContextMenuCommand() ? interaction.targetUser : interaction.targetMessage, enrichedConfig)
|
await command.run(interaction, interaction.isUserContextMenuCommand() ? interaction.targetUser : interaction.targetMessage, config)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("error during context command execution: " + commandName, e)
|
console.error("error during context command execution: " + commandName, e)
|
||||||
interaction.reply("something sharted itself")
|
interaction.reply("something sharted itself")
|
||||||
|
|
@ -99,7 +91,7 @@ client.on(Events.InteractionCreate, async (interaction) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await command.run(interaction, enrichedConfig, S3);
|
await command.run(interaction, config);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("error during command execution: " + commandName, e)
|
console.error("error during command execution: " + commandName, e)
|
||||||
interaction.reply("something sharted itself")
|
interaction.reply("something sharted itself")
|
||||||
|
|
@ -118,7 +110,7 @@ client.on(Events.InteractionCreate, async (interaction) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await command.autoComplete(interaction, enrichedConfig, interaction.options.getFocused(true), S3);
|
await command.autoComplete(interaction, config, interaction.options.getFocused(true));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("error during command execution: " + commandName, e)
|
console.error("error during command execution: " + commandName, e)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue