Rewrite: Replace discord.js implementation with raw Discord API/Gateway client and media layer#3
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 308e2412bd
ℹ️ 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".
| async function fetchAss(id = null) { return guarded('obutts.ru', async () => { const ep = id !== null && !Number.isNaN(Number(id)) ? `http://api.obutts.ru/butts/get/${id}/` : 'http://api.obutts.ru/butts/0/1/random/'; const d = await getJson(ep); if (!d?.length) return null; return { id: d[0].id, url: `http://media.obutts.ru/${d[0].preview}` }; }); } | ||
| async function fetchPurrbot(endpoint) { return guarded('purrbot.site', async () => { const d = await getJson(endpoint); return d && !d.error && d.link ? { url: d.link } : null; }); } | ||
| async function fetchWaifu(endpoint) { if (process.env.WAIFU_PICS === 'false') return null; return guarded('waifu.pics', async () => { const d = await getJson(endpoint); return d?.url ? { url: d.url } : null; }); } | ||
| async function fetchABD(endpoint) { return guarded('n-sfw.com', async () => { const d = await getJson(endpoint); const target = d?.url_japan || d?.url_usa; if (!target) return null; return { url: target }; }); } |
There was a problem hiding this comment.
Upload ABD media instead of embedding its CDN URL
For every command whose pool selects abd (including ABD-only commands such as /breeding, /legs, /milf, etc.), this now returns only the url_japan/url_usa CDN URL. The previous provider fetched that media into a buffer because Discord's media proxy could not reliably embed the n-sfw S3 URL, and the command path uploaded that buffer as an attachment; with this rewrite runMedia only sets image: data.url, so these commands can acknowledge successfully while displaying a broken image. Please preserve the buffer/upload path for ABD responses.
Useful? React with 👍 / 👎.
| async function fetchNekosV4(endpoint) { return guarded('nekosapi.com', async () => { const d = await getJson(endpoint); return Array.isArray(d) && d[0]?.url ? { url: d[0].url, source: 'nekosv4' } : null; }); } | ||
| async function fetchNekoBot(type) { return guarded('nekobot.xyz', async () => { const d = await getJson(`https://nekobot.xyz/api/image?type=${encodeURIComponent(type)}`, { headers: { Authorization: '015445535454455354D6' } }); return d?.success && d?.message ? { url: d.message, source: 'nekobot' } : null; }); } | ||
| async function fetchSexcom(niche) { return guarded('sex.com', async () => { const page = Math.floor(Math.random() * 10) + 1; const url = new URL('https://www.sex.com/portal/api/gifs/search'); Object.entries({ 'sexual-orientation': 'straight', order: 'likeCount', search: niche, page, limit: 40 }).forEach(([k,v]) => url.searchParams.set(k, v)); const d = await getJson(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }); const arr = d?.data || []; if (!arr.length) return null; const item = arr[Math.floor(Math.random() * arr.length)]; let path = item.uri; if (!path) return null; if (path.endsWith('.webp')) path = path.slice(0, -5) + '.gif'; return { id: item.id, url: `https://imagex1.sx.cdn.live${path}` }; }); } | ||
| async function fetchPorngifs() { return guarded('porngifs.com', async () => { const id = Math.floor(Math.random() * 39239) + 1; return { id: String(id), url: `https://cdn.porngifs.com/img/${id}`, source: 'porngifs' }; }); } |
There was a problem hiding this comment.
Verify Porngifs URLs before reporting success
When /gif chooses the Porngifs.com branch, this function now immediately returns a random cdn.porngifs.com/img/<id> URL without fetching it, checking the status/size, or using the DNS/SNI workaround the old provider used before it returned a buffer. Any missing, blocked, or over-limit random ID is therefore treated as a successful fetch and sent to Discord as an embed, leaving users with a broken GIF instead of retrying or showing an error.
Useful? React with 👍 / 👎.
| if (sessionId && resumeGatewayUrl) { | ||
| ws = new WebSocket(`${resumeGatewayUrl}?v=10&encoding=json`); | ||
| ws.on('open', () => send(6, { token: process.env.BOT_TOKEN, session_id: sessionId, seq })); | ||
| ws.on('message', raw => { const p = JSON.parse(raw.toString()); if (p.s !== null && p.s !== undefined) seq = p.s; if (p.op === 10) { clearInterval(heartbeatTimer); heartbeatTimer = setInterval(heartbeat, p.d.heartbeat_interval); heartbeat(); } if (p.op === 0 && p.t === 'INTERACTION_CREATE') handleInteraction(p.d); }); |
There was a problem hiding this comment.
Handle invalid Gateway resumes
When this resume connection receives an Invalid Session (op === 9) or Reconnect (op === 7) payload, it currently ignores it because the compact handler only processes Hello and dispatch events. After a session timeout or invalid sequence, Discord sends Invalid Session and expects the client to disconnect and identify again; leaving this socket open without identifying means the bot stops receiving interactions until the process is restarted.
Useful? React with 👍 / 👎.
| async function runGif(interaction, update=false){ await d.defer(interaction, update); const n=Math.floor(Math.random()*4); let data, link; if(n===0){ data=await media.fetchSexcom(pick(NICHES)); link=data?.id?`https://www.sex.com/pin/${data.id}/`:data?.url; } else if(n===1){ data=await media.fetchPorngifs(); link=data?.url; } else if(n===2){ data=await media.fetchNekoBot('pgif'); link=data?.url; } else { data=await media.fetchPorngifsTv(); link=data?.url; } if(!data||data.error) return d.edit(interaction,{embeds:[{title:'❌ ▸ Error',description:'Failed to fetch image.',color:0xff0000}],components:[]}); return d.edit(interaction,{embeds:[d.embed({title:'🔞 ▸ NSFW GIF',image:data.url,user:interaction.member?.user||interaction.user})],components:d.buttons('refresh_gif',link||data.url)}); } | ||
| async function runSolo(interaction, update=false, saved=null){ await d.defer(interaction, update); const type=saved||d.getOption(interaction,'type'); const sources= type==='male' ? [source('purrbot',{endpoint:'https://purrbot.site/api/img/nsfw/solo_male/gif'})] : type==='female' ? [source('purrbot',{endpoint:'https://purrbot.site/api/img/nsfw/solo/gif'}),source('abd',{endpoint:'https://api.n-sfw.com/nsfw/masturbation'}),source('nekosv4',{endpoint:'https://api.nekosapi.com/v4/images/random?limit=1&rating=explicit,suggestive&tags=masturbating'})] : [source('purrbot',{endpoint:'https://purrbot.site/api/img/nsfw/solo/gif'}),source('abd',{endpoint:'https://api.n-sfw.com/nsfw/masturbation'}),source('purrbot',{endpoint:'https://purrbot.site/api/img/nsfw/solo_male/gif'}),source('nekosv4',{endpoint:'https://api.nekosapi.com/v4/images/random?limit=1&rating=explicit,suggestive&tags=masturbating'})]; const data=await fetchSource(pick(sources)); if(!data||data.error) return d.edit(interaction,{embeds:[{title:'❌ ▸ Error',description:'Failed to fetch image.',color:0xff0000}],components:[]}); return d.edit(interaction,{embeds:[d.embed({title:'🔞 ▸ NSFW Solo Image',image:data.url,user:interaction.member?.user||interaction.user})],components:d.buttons(`refresh_solo_${type||'random'}`,data.url)}); } | ||
| async function runThreesome(interaction, update=false, saved=null){ await d.defer(interaction, update); const chosen=saved||d.getOption(interaction,'type'); const type=chosen||pick(['fff','ffm','mmf','nekosv4']); const src=type==='nekosv4'?source('nekosv4',{endpoint:'https://api.nekosapi.com/v4/images/random?limit=1&rating=explicit,suggestive&tags=threesome'}):source('purrbot',{endpoint:`https://purrbot.site/api/img/nsfw/threesome_${type}/gif`}); const data=await fetchSource(src); if(!data||data.error) return d.edit(interaction,{embeds:[{title:'❌ ▸ Error',description:'Failed to fetch image.',color:0xff0000}],components:[]}); return d.edit(interaction,{embeds:[d.embed({title:'🔞 ▸ NSFW Threesome Image',image:data.url,user:interaction.member?.user||interaction.user})],components:d.buttons(`refresh_threesome_${chosen||'random'}`,data.url)}); } | ||
| async function runUtility(name, interaction){ if(name==='ping') return d.callback(interaction,d.CALLBACK.CHANNEL_MESSAGE_WITH_SOURCE,{embeds:[{title:'🏓 ▸ Pong',description:`Gateway and REST bot is online. Uptime: ${Math.round(process.uptime())}s`,color:d.randomColor()}]}); if(name==='invite'){ const url=`https://discord.com/oauth2/authorize?client_id=${process.env.CLIENT_ID}&scope=bot%20applications.commands&permissions=2147485696`; return d.callback(interaction,d.CALLBACK.CHANNEL_MESSAGE_WITH_SOURCE,{embeds:[{title:'🔗 ▸ Invite',description:`[Add this bot to your server](${url})`,color:d.randomColor()}]}); } if(name==='help') return d.callback(interaction,d.CALLBACK.CHANNEL_MESSAGE_WITH_SOURCE,{embeds:[{title:'📖 ▸ Command Directory',description:Object.keys({...COMMANDS,gif:1,solo:1,threesome:1}).sort().map(c=>`\`/${c}\``).join(' • '),color:d.randomColor()}]}); } |
There was a problem hiding this comment.
Restore the invite's required bot permissions
The generated invite now requests permission value 2147485696, which only grants SEND_MESSAGES and USE_APPLICATION_COMMANDS and omits the permissions the old invite explicitly requested for this bot's responses, especially EMBED_LINKS and ATTACH_FILES. In servers or channels where those permissions are not already inherited from @everyone, users who add the bot through /invite can run commands but the bot's embed/media responses will fail or be stripped.
Useful? React with 👍 / 👎.
| if (packet.t === 'INTERACTION_CREATE') await handleInteraction(packet.d); | ||
| }); | ||
| ws.on('close', (code, reason) => { clearInterval(heartbeatTimer); log.warn(`Gateway closed (${code}) ${reason}`); setTimeout(start, 5000); }); | ||
| ws.on('error', err => log.error(`Gateway error: ${err.message}`)); |
There was a problem hiding this comment.
This treats heartbeat ACKs as a no-op and never tracks whether an ACK arrived before the next heartbeat. If the gateway TCP connection becomes zombied without a close event, Discord expects clients to terminate and resume; this implementation will keep sending heartbeats on the dead socket and stop receiving interactions until an external restart.
Useful? React with 👍 / 👎.
| const user = interaction.member?.user || interaction.user; | ||
| const name = interaction.data?.name || interaction.data?.custom_id; | ||
| log.command(name, user, interaction.guild_id || 'DM'); | ||
| if (interaction.guild_id && interaction.channel && interaction.channel.nsfw === false && !['help','invite','ping'].includes(interaction.data?.name)) { |
There was a problem hiding this comment.
Enforce NSFW parents for thread interactions
This check only rejects channels whose partial payload explicitly has nsfw === false; Discord thread payloads do not set nsfw and inherit that setting from the parent channel. A refresh/component interaction in a guild thread under a non-NSFW parent therefore bypasses the guard and can fetch NSFW media in a SFW location unless the parent channel is fetched or thread parents are otherwise denied.
Useful? React with 👍 / 👎.
Motivation
discord.js-based bot with a lightweight implementation that speaks directly to Discord REST v10 and the Gateway to reduce dependencies and run a custom raw client.src/media.jslayer to handle multiple NSFW providers and improve control over timeouts and buffering.src/entrypoint, adding a command registration script, and trimming legacy files and large archived studies from the active tree.Description
discord.js-based application (manycommands/,events/,utils/,index.js, handler code, docs and other legacy artifacts) and added a newsrc/implementation that implements a raw Gateway client insrc/index.jsand a small REST helper insrc/discord.js.src/media.jsthat centralizes HTTP helpers and provider-specific fetch functions with timeouts and guarded error handling, and addedsrc/commands.jsto map slash command definitions to media operations and refresh handlers.src/logger.js, addedsrc/register-commands.jsto register commands via raw REST, updatedpackage.json(new scripts:start,register,check) and trimmed dependencies towsonly, with correspondingpackage-lock.jsonupdates.README.mdto reflect the rewrite, addedarchive/README.md, updated.gitignoreto include.env,*.log, and.DS_Store, and removed numerous legacy docs, studies, and the oldLICENSEfile from the active branch.Testing
Codex Task