update_discord_message implemented

This commit is contained in:
CJ_Clippy 2024-08-07 15:43:17 -08:00
parent d08fcc0867
commit 54572dbebe
8 changed files with 175 additions and 161 deletions

View File

@ -6,7 +6,7 @@ import { configs } from './config.ts'
export const bot = createProxyCache( export const bot = createProxyCache(
createBot({ createBot({
token: configs.token, token: configs.token,
intents: Intents.Guilds intents: Intents.Guilds | Intents.GuildMessages
}), }),
{ {
desiredProps: { desiredProps: {

View File

@ -69,7 +69,7 @@ createCommand({
{ name: 'Filesize', value: '0 bytes', inline: true}, { name: 'Filesize', value: '0 bytes', inline: true},
{ name: 'URL', value: url, inline: false } { name: 'URL', value: url, inline: false }
]) ])
.setColor('#33eb23') .setColor('#808080')
const response: InteractionCallbackData = { embeds } const response: InteractionCallbackData = { embeds }
const message = await interaction.edit(response) const message = await interaction.edit(response)

View File

@ -1,18 +1,35 @@
if (!process.env.WORKER_CONNECTION_STRING) throw new Error("WORKER_CONNECTION_STRING was missing from env");
if (!process.env.POSTGREST_URL) throw new Error('Missing POSTGREST_URL env var'); if (!process.env.POSTGREST_URL) throw new Error('Missing POSTGREST_URL env var');
if (!process.env.DISCORD_TOKEN) throw new Error('Missing DISCORD_TOKEN env var'); if (!process.env.DISCORD_TOKEN) throw new Error('Missing DISCORD_TOKEN env var');
if (!process.env.DISCORD_CHANNEL_ID) throw new Error("DISCORD_CHANNEL_ID was missing from env");
if (!process.env.DISCORD_GUILD_ID) throw new Error("DISCORD_GUILD_ID was missing from env");
if (!process.env.AUTOMATION_USER_JWT) throw new Error('Missing AUTOMATION_USER_JWT env var'); if (!process.env.AUTOMATION_USER_JWT) throw new Error('Missing AUTOMATION_USER_JWT env var');
const token = process.env.DISCORD_TOKEN! const token = process.env.DISCORD_TOKEN!
const postgrestUrl = process.env.POSTGREST_URL! const postgrestUrl = process.env.POSTGREST_URL!
const discordChannelId = process.env.DISCORD_CHANNEL_ID!
const discordGuildId = process.env.DISCORD_GUILD_ID!
const automationUserJwt = process.env.AUTOMATION_USER_JWT! const automationUserJwt = process.env.AUTOMATION_USER_JWT!
const connectionString = process.env.WORKER_CONNECTION_STRING!
console.log(`hello i am configs and configs.connectionString=${connectionString}`)
export const configs: Config = {
token,
postgrestUrl,
automationUserJwt,
}
export interface Config { export interface Config {
token: string; token: string;
postgrestUrl: string; postgrestUrl: string;
automationUserJwt: string; automationUserJwt: string;
discordGuildId: string;
discordChannelId: string;
connectionString: string;
} }
export const configs: Config = {
token,
postgrestUrl,
automationUserJwt,
discordGuildId,
discordChannelId,
connectionString,
}

View File

@ -1,14 +1,12 @@
import 'dotenv/config'
// import loadCommands from './loadCommands.js' import updateDiscordMessage from './tasks/update_discord_message.js'
// import deployCommands from './deployCommands.js' import { type WorkerUtils, type RunnerOptions, run } from 'graphile-worker'
// import loadEvents from './loadEvents.js'
// import updateDiscordMessage from './tasks/update_discord_message.js'
import { type WorkerUtils } from 'graphile-worker'
import { bot } from './bot.ts' import { bot } from './bot.ts'
import type { Interaction } from '@discordeno/bot' import type { Interaction } from '@discordeno/bot'
import { importDirectory } from './utils/loader.ts' import { importDirectory } from './utils/loader.ts'
import { join, dirname } from 'node:path' import { join, dirname } from 'node:path'
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { configs } from './config.ts'
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
@ -18,43 +16,36 @@ export interface ExecuteArguments {
workerUtils: WorkerUtils; workerUtils: WorkerUtils;
} }
if (!process.env.AUTOMATION_USER_JWT) throw new Error(`AUTOMATION_USER_JWT was missing from env`);
if (!process.env.DISCORD_TOKEN) throw new Error("DISCORD_TOKEN was missing from env");
if (!process.env.DISCORD_CHANNEL_ID) throw new Error("DISCORD_CHANNEL_ID was missing from env");
if (!process.env.DISCORD_GUILD_ID) throw new Error("DISCORD_GUILD_ID was missing from env");
if (!process.env.WORKER_CONNECTION_STRING) throw new Error("WORKER_CONNECTION_STRING was missing from env");
const preset: GraphileConfig.Preset = { async function setupGraphileWorker() {
worker: { const preset: GraphileConfig.Preset = {
connectionString: process.env.WORKER_CONNECTION_STRING, worker: {
concurrentJobs: 3, connectionString: configs.connectionString,
fileExtensions: [".js", ".ts"] concurrentJobs: 3,
}, fileExtensions: [".js", ".ts"],
}; taskDirectory: join(__dirname, 'tasks')
},
// async function setupGraphileWorker() { };
// const runnerOptions: RunnerOptions = { console.log('worker preset as follows')
// preset, console.log(preset)
// taskList: { const runnerOptions: RunnerOptions = {
// 'updateDiscordMessage': updateDiscordMessage preset
// } // concurrency: 3,
// } // connectionString: configs.connectionString,
// taskDirectory: join(__dirname, 'tasks'),
// taskList: {
// 'update_discord_message': updateDiscordMessage
// }
}
// const runner = await run(runnerOptions) const runner = await run(runnerOptions)
// if (!runner) throw new Error('failed to initialize graphile worker'); if (!runner) throw new Error('failed to initialize graphile worker');
// await runner.promise await runner.promise
// } }
// async function setupWorkerUtils() {
// const workerUtils = await makeWorkerUtils({
// preset
// });
// await workerUtils.migrate()
// return workerUtils
// }
async function main() { async function setupBot() {
bot.logger.info('Starting @futureporn/bot.') bot.logger.info('Starting @futureporn/bot.')
@ -64,16 +55,15 @@ async function main() {
bot.logger.info('Loading events...') bot.logger.info('Loading events...')
await importDirectory(join(__dirname, './events')) await importDirectory(join(__dirname, './events'))
// const commands = await loadCommands()
// if (!commands) throw new Error('there were no commands available to be loaded.');
// await deployCommands(commands.map((c) => c.data.toJSON()))
// console.log(`${commands.length} commands deployed: ${commands.map((c) => c.data.name).join(', ')}`)
// const workerUtils = await setupWorkerUtils()
// setupGraphileWorker()
await bot.start() await bot.start()
} }
async function main() {
await setupBot()
await setupGraphileWorker()
}
main().catch((e) => { main().catch((e) => {
console.error("error during main() function") console.error("error during main() function")
console.error(e) console.error(e)

View File

@ -3,9 +3,19 @@ import type { RecordingState } from '@futureporn/types'
import { type Task, type Helpers } from 'graphile-worker' import { type Task, type Helpers } from 'graphile-worker'
import { add } from 'date-fns' import { add } from 'date-fns'
import prettyBytes from 'pretty-bytes' import prettyBytes from 'pretty-bytes'
import { EmbedsBuilder, type Component } from '@discordeno/bot' import {
EmbedsBuilder,
ButtonStyles,
type ActionRow,
MessageComponentTypes,
type ButtonComponent,
type InputTextComponent,
type EditMessage,
type Message,
type Embed
} from '@discordeno/bot'
import { bot } from '../bot.ts'
import { configs } from '../config.ts'
interface Payload { interface Payload {
record_id: number; record_id: number;
@ -19,11 +29,6 @@ function assertPayload(payload: any): asserts payload is Payload {
} }
if (!process.env.AUTOMATION_USER_JWT) throw new Error(`AUTOMATION_USER_JWT was missing from env`);
if (!process.env.DISCORD_TOKEN) throw new Error("DISCORD_TOKEN was missing from env");
if (!process.env.DISCORD_CHANNEL_ID) throw new Error("DISCORD_CHANNEL_ID was missing from env");
if (!process.env.DISCORD_GUILD_ID) throw new Error("DISCORD_GUILD_ID was missing from env");
async function editDiscordMessage({ helpers, recordingState, discordMessageId, url, fileSize, recordId }: { recordId: number, fileSize: number, url: string, helpers: Helpers, recordingState: RecordingState, discordMessageId: string }) { async function editDiscordMessage({ helpers, recordingState, discordMessageId, url, fileSize, recordId }: { recordId: number, fileSize: number, url: string, helpers: Helpers, recordingState: RecordingState, discordMessageId: string }) {
@ -34,46 +39,52 @@ async function editDiscordMessage({ helpers, recordingState, discordMessageId, u
// const { captureJobId } = job.data // const { captureJobId } = job.data
helpers.logger.info(`editDiscordMessage has begun with discordMessageId=${discordMessageId}, state=${recordingState}`) helpers.logger.info(`editDiscordMessage has begun with discordMessageId=${discordMessageId}, state=${recordingState}`)
// create a discord.js client
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
});
// Log in to Discord with your client's token
client.login(process.env.DISCORD_TOKEN);
const guild = await client.guilds.fetch(process.env.DISCORD_GUILD_ID!) as Guild // const guild = await bot.cache.guilds.get(BigInt(configs.discordGuildId))
if (!guild) throw new Error('guild was undefined'); // const channel = guild?.channels.get(BigInt(configs.discordChannelId))
helpers.logger.info('here is the guild as follows') // // const channel = await bot.cache.channels.get()
helpers.logger.info(guild.toString()) // console.log('channel as follows')
helpers.logger.info(`fetching discord channel id=${process.env.DISCORD_CHANNEL_ID} from discord guild`) // console.log(channel)
const channel = await client.channels.fetch(process.env.DISCORD_CHANNEL_ID!) as TextChannel
if (!channel) throw new Error(`discord channel was undefined`); const channelId = BigInt(configs.discordChannelId)
const updatedMessage: EditMessage = {
embeds: getStatusEmbed({ recordingState, fileSize, recordId, url }),
}
bot.helpers.editMessage(channelId, discordMessageId, updatedMessage)
const message = await channel.messages.fetch(discordMessageId) // channel.
helpers.logger.info(`discordMessageId=${discordMessageId}`)
helpers.logger.info(message as any) // const guild = await client.guilds.fetch(process.env.DISCORD_GUILD_ID!) as Guild
// if (!guild) throw new Error('guild was undefined');
// helpers.logger.info('here is the guild as follows')
// helpers.logger.info(guild.toString())
// helpers.logger.info(`fetching discord channel id=${process.env.DISCORD_CHANNEL_ID} from discord guild`)
// const channel = await client.channels.fetch(process.env.DISCORD_CHANNEL_ID!) as TextChannel
// if (!channel) throw new Error(`discord channel was undefined`);
const statusEmbed = getStatusEmbed({ recordId, recordingState, fileSize, url }) // const message = await channel.messages.fetch(discordMessageId)
const buttonRow = getButtonRow(recordingState) // helpers.logger.info(`discordMessageId=${discordMessageId}`)
// helpers.logger.info(message as any)
// const statusEmbed = getStatusEmbed({ recordId, recordingState, fileSize, url })
// const buttonRow = getButtonRow(recordingState)
// const embed = new EmbedBuilder().setTitle('Attachments'); // // const embed = new EmbedBuilder().setTitle('Attachments');
const updatedMessage = { // const updatedMessage = {
embeds: [ // embeds: [
statusEmbed // statusEmbed
], // ],
components: [ // components: [
buttonRow // buttonRow
] // ]
}; // };
message.edit(updatedMessage) // message.edit(updatedMessage)
} }
@ -121,14 +132,15 @@ export const updateDiscordMessage: Task = async function (payload, helpers: Help
} }
} }
function getStatusEmbed({ function getStatusEmbed({
recordingState, recordId, fileSize, url recordingState, recordId, fileSize, url
}: { fileSize: number, recordingState: RecordingState, recordId: number, url: string }) { }: { fileSize: number, recordingState: RecordingState, recordId: number, url: string }) {
const embeds = new EmbedsBuilder() const embeds = new EmbedsBuilder()
.setTitle(`Record ${recordId}`) .setTitle(`Record ${recordId}`)
.setFields([ .setFields([
{ name: 'Status', value: 'Pending', inline: true }, { name: 'Status', value: recordingState.charAt(0).toUpperCase()+recordingState.slice(1), inline: true },
{ name: 'Filesize', value: `${fileSize} bytes (${prettyBytes(fileSize)})`, inline: true }, { name: 'Filesize', value: prettyBytes(fileSize), inline: true },
{ name: 'URL', value: url, inline: false }, { name: 'URL', value: url, inline: false },
]) ])
if (recordingState === 'pending') { if (recordingState === 'pending') {
@ -157,72 +169,59 @@ function getStatusEmbed({
function getButtonRow(state: RecordingState) { function getButtonRow(state: RecordingState): ActionRow {
const components: ButtonComponent[] = []
const button = new Component()
.setType("BUTTON") if (state === 'pending' || state === 'recording') {
const stopButton: ButtonComponent = {
// // Button with raw types type: MessageComponentTypes.Button,
// const button2 = new Component() customId: 'stop',
// .setType(2) label: 'Cancel',
// .setStyle(4) style: ButtonStyles.Danger
// .setLabel("DO NOT CLICK") }
// .setCustomId("12345") components.push(stopButton)
// .toJSON();
// const actionRow = new Component()
// .setType("ACTION_ROW")
// .setComponents(button, button2)
// .toJSON();
// return actionRow
// Message to send
// const messageOptions = { content: "hello", components: [actionRow] };
// await client.helpers.sendMessage(channelId, messageOptions); // You can also use the Message Structure
if (state === 'pending') {
button
.setCustomId('stop')
.setLabel('Cancel')
.setEmoji('❌')
.setStyle('DANGER')
} else if (state === 'recording') {
button
.setCustomId('stop')
.setLabel('Stop Recording')
.setEmoji('🛑')
.setStyle('DANGER')
} else if (state === 'aborted') { } else if (state === 'aborted') {
button const retryButton: ButtonComponent = {
.setCustomId('retry') type: MessageComponentTypes.Button,
.setLabel('Retry Recording') customId: 'retry',
.setEmoji('🔄') label: 'Retry Recording',
.setStyle('SUCCESS') emoji: {
name: 'retry'
},
style: ButtonStyles.Secondary
}
components.push(retryButton)
} else if (state === 'ended') { } else if (state === 'ended') {
button const downloadButton: ButtonComponent = {
.setCustomId('download') type: MessageComponentTypes.Button,
.setLabel('Download Recording') customId: 'download',
.setEmoji('📥') label: 'Download Recording',
.setStyle('PRIMARY') emoji: {
id: BigInt('1253191939461873756')
},
style: ButtonStyles.Success
}
components.push(downloadButton)
} else { } else {
button const unknownButton: ButtonComponent = {
.setCustomId('unknown') type: MessageComponentTypes.Button,
.setLabel('Unknown State') customId: 'unknown',
.setEmoji('🤔') label: 'Unknown State',
.setStyle('SECONDARY') emoji: {
name: 'thinking'
},
style: ButtonStyles.Primary
}
components.push(unknownButton)
}
const actionRow: ActionRow = {
type: MessageComponentTypes.ActionRow,
components: components as [ButtonComponent]
} }
const actionRow = new Component return actionRow
return new ActionRowBuilder<MessageActionRowComponentBuilder>()
.addComponents([
new ButtonBuilder()
.setCustomId(id)
.setLabel(label)
.setEmoji(emoji)
.setStyle(style),
]);
} }

View File

@ -131,6 +131,7 @@ export default class Record {
parallelUploads3.on("httpUploadProgress", (progress) => { parallelUploads3.on("httpUploadProgress", (progress) => {
if (progress?.loaded) { if (progress?.loaded) {
if (this.onProgress) this.onProgress(this.counter);
console.log(`uploaded ${progress.loaded} bytes (${prettyBytes(progress.loaded)})`); console.log(`uploaded ${progress.loaded} bytes (${prettyBytes(progress.loaded)})`);
} else { } else {
console.log(`httpUploadProgress ${JSON.stringify(progress, null, 2)}`) console.log(`httpUploadProgress ${JSON.stringify(progress, null, 2)}`)
@ -158,12 +159,9 @@ export default class Record {
// streams setup // streams setup
this.uploadStream.on('data', (data) => { this.uploadStream.on('data', (data) => {
this.counter += data.length this.counter += data.length
if (this.counter % (1 * 1024 * 1024) <= 1024) {
console.log(`Received ${this.counter} bytes (${prettyBytes(this.counter)})`);
if (this.onProgress) this.onProgress(this.counter)
}
}) })
this.uploadStream.on('close', () => { this.uploadStream.on('close', () => {
console.log('[!!!] upload stream has closed') console.log('[!!!] upload stream has closed')

View File

@ -67,7 +67,16 @@ function checkIfAborted(record: RawRecordingRecord): boolean {
return (record.is_aborted) return (record.is_aborted)
} }
async function updateDatabaseRecord({recordId, recordingState, fileSize}: { recordId: number, recordingState: RecordingState, fileSize: number }): Promise<RawRecordingRecord> { async function updateDatabaseRecord({
recordId,
recordingState,
fileSize
}: {
recordId: number,
recordingState: RecordingState,
fileSize: number
}): Promise<RawRecordingRecord> {
console.log(`updating database record with recordId=${recordId}, recordingState=${recordingState}, fileSize=${fileSize}`)
const payload: any = { const payload: any = {
file_size: fileSize file_size: fileSize
} }
@ -83,7 +92,8 @@ async function updateDatabaseRecord({recordId, recordingState, fileSize}: { reco
body: JSON.stringify(payload) body: JSON.stringify(payload)
}) })
if (!res.ok) { if (!res.ok) {
throw new Error(`failed to updateDatabaseRecord. status=${res.status}, statusText=${res.statusText}`); const body = await res.text()
throw new Error(`failed to updateDatabaseRecord. status=${res.status}, statusText=${res.statusText}, body=${body}`);
} }
const body = await res.json() as RawRecordingRecord[]; const body = await res.json() as RawRecordingRecord[];
if (!body[0]) throw new Error(`failed to get a record that matched recordId=${recordId}`) if (!body[0]) throw new Error(`failed to get a record that matched recordId=${recordId}`)

View File

@ -5,7 +5,7 @@ CREATE FUNCTION public.tg__update_discord_message() RETURNS trigger
AS $$ AS $$
begin begin
PERFORM graphile_worker.add_job('update_discord_message', json_build_object( PERFORM graphile_worker.add_job('update_discord_message', json_build_object(
'record_id', NEW.record_id 'record_id', NEW.id
), max_attempts := 3); ), max_attempts := 3);
return NEW; return NEW;
end; end;