Rebuild bot on raw Discord REST/Gateway and modular media fetchers#4
Conversation
There was a problem hiding this comment.
💡 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".
| 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); |
There was a problem hiding this comment.
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 👍 / 👎.
| 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); } } |
There was a problem hiding this comment.
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')}]), |
There was a problem hiding this comment.
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'}]), |
There was a problem hiding this comment.
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 👍 / 👎.
| 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 }); } }; |
There was a problem hiding this comment.
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 👍 / 👎.
Motivation
discord.jsclient wrapper with a direct implementation of the Discord REST API and Gateway for tighter control and a smaller dependency surface..gitignorerules to prevent binary PR failures.Description
src/includingsrc/api/discordRest.js,src/core/gateway.js, andsrc/core/interactionHandler.jsto handle REST requests, Gateway identify/heartbeat/dispatch, and interaction lifecycles.src/commands/mediaDefinitions.js, acreateMediaCommandfactory insrc/core/commandFactory.js, and a dynamicsrc/core/commandLoader.jsthat exposes a command map for runtime use and automatic global registration viaDiscordRest.registerGlobalCommands.src/api/mediaFetchers.jspreserving and consolidating every NSFW endpoint and special handling (buffering ABD images withrejectUnauthorized: false, DNS/SNI bypass for Porngifs, Nekobot authorization header, Waifu.im key usage, Sex.com scraping logic, Porngifs.tv extraction, etc.).package.json(minimal dependencies:axiosandws),.env.example, expanded.gitignore, simplifiedsrc/index.jsstartup (loads commands, registers global commands, connects to Gateway), and archival of the previous source tree to/workspace/Discord-NSFW-Bot-archive.Testing
npm installto install the new dependency set and the install completed successfully.npm run check(which runsnode --checkonsrc/index.jsand allsrc/*.jsfiles) and it completed without syntax errors.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.Codex Task