Skip to content

Rebuild bot on raw Discord REST/Gateway and modular media fetchers#4

Open
SyntaxGhost404 wants to merge 1 commit into
experimentalfrom
codex/rewrite-codebase-following-discord-api-guidelines
Open

Rebuild bot on raw Discord REST/Gateway and modular media fetchers#4
SyntaxGhost404 wants to merge 1 commit into
experimentalfrom
codex/rewrite-codebase-following-discord-api-guidelines

Conversation

@SyntaxGhost404

Copy link
Copy Markdown
Owner

Motivation

  • Replace the discord.js client wrapper with a direct implementation of the Discord REST API and Gateway for tighter control and a smaller dependency surface.
  • Move to a highly modular command architecture so each command is its own file and the system auto-registers application commands on startup.
  • Preserve the full set of NSFW media sources and delivery behaviors (Purrbot, N-SFW/ABD, Waifu.im, Waifu.pics toggle, NekoBot, Nekos API, Sex.com, Porngifs, Porngifs.tv) while ensuring URL buffering/attachment fallbacks for problematic CDNs and expired certs.
  • Ensure no binary/compiled artifacts are committed and add .gitignore rules to prevent binary PR failures.

Description

  • Replaced the old implementation with a raw-API architecture under src/ including src/api/discordRest.js, src/core/gateway.js, and src/core/interactionHandler.js to handle REST requests, Gateway identify/heartbeat/dispatch, and interaction lifecycles.
  • Introduced modular command definitions and loader: src/commands/mediaDefinitions.js, a createMediaCommand factory in src/core/commandFactory.js, and a dynamic src/core/commandLoader.js that exposes a command map for runtime use and automatic global registration via DiscordRest.registerGlobalCommands.
  • Moved all media fetch logic into src/api/mediaFetchers.js preserving and consolidating every NSFW endpoint and special handling (buffering ABD images with rejectUnauthorized: false, DNS/SNI bypass for Porngifs, Nekobot authorization header, Waifu.im key usage, Sex.com scraping logic, Porngifs.tv extraction, etc.).
  • Project housekeeping and packaging changes include a new package.json (minimal dependencies: axios and ws), .env.example, expanded .gitignore, simplified src/index.js startup (loads commands, registers global commands, connects to Gateway), and archival of the previous source tree to /workspace/Discord-NSFW-Bot-archive.

Testing

  • Ran npm install to install the new dependency set and the install completed successfully.
  • Ran npm run check (which runs node --check on src/index.js and all src/*.js files) and it completed without syntax errors.
  • Executed a command-loader smoke test via node -e "const { loadCommands } = require('./src/core/commandLoader'); const cmds = loadCommands(); console.log(cmds.size, [...cmds.keys()].sort().join(','));" which listed the expected commands and returned the loaded command count.
  • Performed a repository status check to confirm changed/created files and no binary artifacts are present (clean for PR submission).

Codex Task

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8649cc5302

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/api/discordRest.js
registerGlobalCommands(applicationId, commands) { return this.request('PUT', `/applications/${applicationId}/commands`, commands); }
createInteractionResponse(interactionId, token, payload) { return this.request('POST', `/interactions/${interactionId}/${token}/callback`, payload); }
async editOriginalInteractionResponse(applicationId, token, payload) {
if (!payload.attachments) return this.request('PATCH', `/webhooks/${applicationId}/${token}/messages/@original`, payload);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear stale files on URL-only refresh edits

When a refresh follows a result that uploaded a buffered file (for example ABD or Porngifs), later URL-only or error edits take this JSON branch and omit the attachments field. Discord webhook PATCH keeps existing files unless attachments is supplied, so the old explicit nsfw.gif remains attached under the new embed/error instead of being replaced. Send attachments: [] for edits that should clear prior uploads, or normalize that here before PATCHing.

Useful? React with 👍 / 👎.

Comment thread src/core/gateway.js
send(op, d) { this.ws.send(JSON.stringify({ op, d })); }
identify() { this.send(2, { token: this.token, intents: 513, properties: { os: process.platform, browser: 'raw-discord-api', device: 'raw-discord-api' } }); }
heartbeat() { this.lastHeartbeatSent = Date.now(); this.send(1, this.seq); }
handle(packet) { if (packet.s) this.seq = packet.s; if (packet.op === 10) { this.heartbeatMs = packet.d.heartbeat_interval; this.heartbeat(); this.heartbeatTimer = setInterval(() => this.heartbeat(), this.heartbeatMs); this.identify(); return; } if (packet.op === 11) { this.lastHeartbeatAck = Date.now(); this.latency = this.lastHeartbeatSent ? this.lastHeartbeatAck - this.lastHeartbeatSent : null; return; } if (packet.op === 0) { if (packet.t === 'READY') { this.sessionId = packet.d.session_id; logInfo(`Gateway ready as ${packet.d.user.username}`); } this.onDispatch(packet.t, packet.d, this.latency); } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle gateway reconnect and invalid-session opcodes

This handler only reacts to Hello, Heartbeat ACK, and Dispatch packets, so Discord Reconnect (op 7) and Invalid Session (op 9) packets fall through. In those gateway states the session may no longer deliver INTERACTION_CREATE events, and op 9 in particular does not require the server to close the socket, leaving the bot connected but unusable until an external reconnect happens. Add explicit reconnect/resume or re-identify handling for these opcodes.

Useful? React with 👍 / 👎.

d('selfie','Delivers a random NSFW selfie Image/GIF','🔞 ▸ NSFW Selfie Image',[{id:'abd',endpoint:nsfw('selfie')},{id:'waifuim',tag:'selfies'}]),
d('smothering','Delivers a random NSFW smothering Image/GIF','🔞 ▸ NSFW Smothering Image',[{id:'abd',endpoint:nsfw('smothering')}]),
d('socks','Delivers a random NSFW socks Image/GIF','🔞 ▸ NSFW Socks Image',[{id:'abd',endpoint:nsfw('socks')}]),
d('solo','Delivers a random NSFW solo GIF','🔞 ▸ NSFW Solo Image',[{id:'purrbot',endpoint:purr('solo')},{id:'purrbot',endpoint:purr('solo_male')},{id:'nekosv4',endpoint:nekos('masturbating')}]),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the solo gender selector

The new generic solo definition registers no options, even though the command still has distinct male and female Purrbot endpoints. After the startup command overwrite, users can no longer invoke /solo gender:male or /solo gender:female, and refreshes cannot preserve that choice; the command always randomizes across all sources instead. Add a dedicated option/handler or extend the factory so the previous gender-specific behavior remains available.

Useful? React with 👍 / 👎.

d('cum','Delivers a random NSFW cum GIF','🔞 ▸ NSFW Cum Image',[{id:'purrbot',endpoint:purr('cum')}]),
d('ecchi','Delivers a random NSFW ecchi Image/GIF','🔞 ▸ NSFW Ecchi Image',[{id:'abd',endpoint:nsfw('ecchi')},{id:'waifuim',tag:'ecchi'}]),
d('ero','Delivers a random NSFW ero Image','🔞 ▸ NSFW Ero Image',[{id:'waifuim',tag:'ero'}]),
d('feet','Delivers a random NSFW feet Image/GIF','🔞 ▸ NSFW Feet Image',[{id:'abd',endpoint:nsfw('feet')},{id:'nekobot',type:'feet'}]),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore the feet style option

The feet command used to expose a style option that selected anime ABD results or real NekoBot results, but this definition puts both providers in one pool and leaves hasStyle false. Once startup overwrites the global command, /feet style:Anime and /feet style:Real disappear and refreshes cannot preserve that choice. Split the providers into anime/real arrays and enable the style option here.

Useful? React with 👍 / 👎.

Comment thread src/commands/gif.js
const { fetchBySource } = require('../api/mediaFetchers');
const { embed, errorEmbed, components } = require('../core/messages');
const niches = ['Amateur','Anal','Asian','Big Tits','Blonde','Blowjob','Brunette','Creampie','Cumshot','Hardcore','Latina','Lesbian','MILF','Masturbation','Threesome','Ass','BBW','BDSM','Double Penetration','Ebony','Female Ejaculation','Fisting','Footjob','Gangbang','Hairy','Handjob','Hentai','Lingerie','Public Sex','Pussy','Toys'];
module.exports = { data: { name: 'gif', description: 'Delivers a random NSFW GIF', nsfw: true, integration_types: [0,1], contexts: [0,1,2], options: [{ type: ApplicationCommandOptionType.STRING, name: 'category', description: 'Optional GIF category', required: false, choices: niches.slice(0,25).map(n => ({ name:n, value:n })) }] }, async execute(ctx, saved) { const category = saved || ctx.options?.category || niches[Math.floor(Math.random()*niches.length)]; const source = Math.random() < 0.34 ? {id:'porngifs'} : Math.random() < 0.5 ? {id:'porngifstv'} : {id:'sexcom', niche: category}; const image = await fetchBySource(source); if (!image || image.error) return ctx.edit({ embeds: [errorEmbed(image?.error === 'TIMEOUT' ? 'API Timeout: The request took longer than 15 seconds. Please try again later.' : 'Failed to fetch GIF.')], components: [] }); return ctx.edit({ embeds: [embed(`🔞 ▸ NSFW GIF${category ? ` (${category})` : ''}`, image.buffer ? 'attachment://nsfw.gif' : image.url, ctx.user, image.source)], components: components(`refresh_gif_${category.replaceAll(' ','_')}`, image.url), attachments: image.buffer ? [{ id: '0', filename: 'nsfw.gif', data: image.buffer }] : undefined }); } };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve buffered GIF file extensions

When /gif selects Porngifs.tv, fetchPorngifsTv can return a buffered .webp, but this edit always uploads it as nsfw.gif. Discord infers attachment handling from the uploaded filename, so WebP animations can be misclassified or fail to render inline; the previous implementation derived the extension from imageData.url before creating the attachment. Keep the source extension for buffered GIF results instead of forcing every upload to .gif.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant