-
Notifications
You must be signed in to change notification settings - Fork 3
feat: remove automatic code detection and reformatting feature #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fc110e1
3772eab
c837976
d7714fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,133 @@ | ||||||||
| import { | ||||||||
| ActionRowBuilder, | ||||||||
| ButtonBuilder, | ||||||||
| ButtonStyle, | ||||||||
| EmbedBuilder, | ||||||||
| } from "discord.js"; | ||||||||
| import fetch from "node-fetch"; | ||||||||
| import type { CommandCallbackOpts } from "../../types/command.ts"; | ||||||||
|
|
||||||||
| export default { | ||||||||
| name: "crate", | ||||||||
| description: "Search Rust crates", | ||||||||
| usage: "crate <crate name>", | ||||||||
| aliases: ["crates", "cargo"], | ||||||||
| react: "🦀", | ||||||||
| async callback({ message, args }: CommandCallbackOpts) { | ||||||||
| try { | ||||||||
| const query = args.join(" "); | ||||||||
| if (!query) { | ||||||||
| return message.reply( | ||||||||
| "Please provide a search term, e.g. `;crate tokio`", | ||||||||
| ); | ||||||||
| } | ||||||||
|
|
||||||||
| const searchRes = await fetch( | ||||||||
| `https://crates.io/api/v1/crates?q=${encodeURIComponent(query)}&per_page=1`, | ||||||||
| { | ||||||||
| headers: { "User-Agent": "DiscordBot (contact@yourbotdomain.com)" }, | ||||||||
| }, | ||||||||
| ); | ||||||||
| const searchData = await searchRes.json(); | ||||||||
|
|
||||||||
| if (!searchData.crates || searchData.crates.length === 0) { | ||||||||
| return message.reply(`¯\\_(ツ)_/¯ No crates found for **${query}**`); | ||||||||
| } | ||||||||
|
|
||||||||
| const crate = searchData.crates[0]; | ||||||||
| const name = crate.name; | ||||||||
|
|
||||||||
| const description = crate.description | ||||||||
| ? crate.description.length > 800 | ||||||||
| ? crate.description.slice( | ||||||||
| 0, | ||||||||
| crate.description.lastIndexOf(" ", 800), | ||||||||
| ) + " ..." | ||||||||
| : crate.description | ||||||||
| : "No description available."; | ||||||||
|
|
||||||||
| const embed = new EmbedBuilder() | ||||||||
| .setTitle(name) | ||||||||
| .setURL(`https://crates.io/crates/${name}`) | ||||||||
| .setDescription(description) | ||||||||
| .addFields( | ||||||||
| { | ||||||||
| name: "Version", | ||||||||
| value: crate.max_version || "Unknown", | ||||||||
| inline: true, | ||||||||
| }, | ||||||||
| { | ||||||||
| name: "Author", | ||||||||
| value: "See Registry", | ||||||||
| inline: true, | ||||||||
| }, | ||||||||
| { | ||||||||
| name: "Publisher", | ||||||||
| value: "See Registry", | ||||||||
| inline: true, | ||||||||
| }, | ||||||||
| { | ||||||||
| name: "Recent Downloads", | ||||||||
| value: crate.recent_downloads?.toLocaleString() || "Unknown", | ||||||||
| inline: true, | ||||||||
| }, | ||||||||
| { | ||||||||
| name: "Repository", | ||||||||
| value: crate.repository | ||||||||
| ? `[${crate.repository | ||||||||
| .replace("https://github.com/", "") | ||||||||
| .replace(".git", "") | ||||||||
| .split("/") | ||||||||
| .slice(-2) | ||||||||
| .join("/")}](${crate.repository})` | ||||||||
| : "Unknown", | ||||||||
| inline: true, | ||||||||
| }, | ||||||||
| { | ||||||||
| name: "License", | ||||||||
| value: crate.license || "Other", | ||||||||
| inline: true, | ||||||||
| }, | ||||||||
| ) | ||||||||
| .setThumbnail( | ||||||||
| "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Rust_programming_language_black_logo.svg/1200px-Rust_programming_language_black_logo.svg.png", | ||||||||
| ) | ||||||||
| .setFooter({ text: "Source: crates.io" }); | ||||||||
|
|
||||||||
| const button = new ButtonBuilder() | ||||||||
| .setLabel("View on npm") | ||||||||
| .setLabel("View on crates.io") | ||||||||
| .setStyle(ButtonStyle.Link) | ||||||||
| .setURL(`https://crates.io/crates/${name}`); | ||||||||
|
|
||||||||
| const componentButtons = [button]; | ||||||||
| if (crate.homepage) { | ||||||||
| componentButtons.push( | ||||||||
| new ButtonBuilder() | ||||||||
| .setLabel("Visit Homepage") | ||||||||
| .setStyle(ButtonStyle.Link) | ||||||||
| .setURL(crate.homepage), | ||||||||
| ); | ||||||||
| } | ||||||||
|
|
||||||||
| const row = new ActionRowBuilder().addComponents(...componentButtons); | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Confidence: 5/5 Suggested Fix
Suggested change
Add the generic type parameter Prompt for AICopy this prompt to your AI IDE to fix this issue locally: |
||||||||
|
|
||||||||
| return message.reply({ | ||||||||
| embeds: [embed], | ||||||||
| components: [row.toJSON()], | ||||||||
| }); | ||||||||
| } catch (err) { | ||||||||
| console.error(err); | ||||||||
| return message.reply({ | ||||||||
| embeds: [ | ||||||||
| new EmbedBuilder() | ||||||||
| .setTitle("❌ Failed to fetch crate results") | ||||||||
| .setDescription( | ||||||||
| "An error occurred while fetching results from crates.io.", | ||||||||
| ) | ||||||||
| .setColor(0xd21872), | ||||||||
| ], | ||||||||
| }); | ||||||||
| } | ||||||||
| }, | ||||||||
| }; | ||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,141 @@ | ||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||
| ActionRowBuilder, | ||||||||||||||||||||||||
| ButtonBuilder, | ||||||||||||||||||||||||
| ButtonStyle, | ||||||||||||||||||||||||
| EmbedBuilder, | ||||||||||||||||||||||||
| } from "discord.js"; | ||||||||||||||||||||||||
| import fetch from "node-fetch"; | ||||||||||||||||||||||||
| import type { CommandCallbackOpts } from "../../types/command.ts"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export default { | ||||||||||||||||||||||||
| name: "gem", | ||||||||||||||||||||||||
| description: "Search Ruby gems", | ||||||||||||||||||||||||
| usage: "gem <gem name>", | ||||||||||||||||||||||||
| aliases: ["gems", "gempkg"], | ||||||||||||||||||||||||
| react: "💎", | ||||||||||||||||||||||||
| async callback({ message, args }: CommandCallbackOpts) { | ||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| const query = args.join(" "); | ||||||||||||||||||||||||
| if (!query) { | ||||||||||||||||||||||||
| return message.reply("Please provide a search term, e.g. `;gem rails`"); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const searchRes = await fetch( | ||||||||||||||||||||||||
| `https://rubygems.org/api/v1/search.json?query=${encodeURIComponent(query)}`, | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
Comment on lines
+23
to
+25
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fetch request lacks a timeout, which can cause the Discord bot to hang indefinitely if the RubyGems API is slow or unresponsive. This can lead to resource exhaustion and poor user experience. Confidence: 5/5 Suggested Fix
Suggested change
Add a 10-second timeout to prevent the request from hanging indefinitely. Wrap the fetch in a try-catch to handle Prompt for AICopy this prompt to your AI IDE to fix this issue locally: 📍 This suggestion applies to lines 23-25 |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (!searchRes.ok) { | ||||||||||||||||||||||||
| throw new Error("RubyGems API failed to respond properly."); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const searchData = await searchRes.json(); | ||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Confidence: 5/5 Suggested Fix
Suggested change
Add explicit error handling for JSON parsing failures to provide clearer error messages and prevent exposing internal error details. Prompt for AICopy this prompt to your AI IDE to fix this issue locally: |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (!searchData || searchData.length === 0) { | ||||||||||||||||||||||||
| return message.reply(`¯\\_(ツ)_/¯ No Ruby gems found for **${query}**`); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const gem = searchData[0]; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const description = gem.info | ||||||||||||||||||||||||
| ? gem.info.length > 800 | ||||||||||||||||||||||||
| ? gem.info.slice(0, gem.info.lastIndexOf(" ", 800)) + " ..." | ||||||||||||||||||||||||
| : gem.info | ||||||||||||||||||||||||
| : "No description available."; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const repoUrl = gem.source_code_uri || gem.homepage_uri || null; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const repoDisplay = | ||||||||||||||||||||||||
| repoUrl && repoUrl.includes("github.com") | ||||||||||||||||||||||||
| ? `[${repoUrl.replace("https://github.com/", "").split("/").slice(0, 2).join("/")}](${repoUrl})` | ||||||||||||||||||||||||
| : repoUrl | ||||||||||||||||||||||||
| ? `[View Source](${repoUrl})` | ||||||||||||||||||||||||
| : "Unknown"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const embed = new EmbedBuilder() | ||||||||||||||||||||||||
| .setTitle(gem.name) | ||||||||||||||||||||||||
| .setURL(`https://rubygems.org/gems/${gem.name}`) | ||||||||||||||||||||||||
| .setDescription(description) | ||||||||||||||||||||||||
| .addFields( | ||||||||||||||||||||||||
| { | ||||||||||||||||||||||||
| name: "Version", | ||||||||||||||||||||||||
| value: gem.version || "Unknown", | ||||||||||||||||||||||||
| inline: true, | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| { | ||||||||||||||||||||||||
| name: "Author", | ||||||||||||||||||||||||
| value: gem.authors || "Unknown", | ||||||||||||||||||||||||
| inline: true, | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| { | ||||||||||||||||||||||||
| name: "Publisher", | ||||||||||||||||||||||||
| value: gem.authors?.split(",")[0] || "Unknown", | ||||||||||||||||||||||||
| inline: true, | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| { | ||||||||||||||||||||||||
| name: "Total Downloads", | ||||||||||||||||||||||||
| value: gem.downloads?.toLocaleString() || "Unknown", | ||||||||||||||||||||||||
| inline: true, | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| { | ||||||||||||||||||||||||
| name: "Repository", | ||||||||||||||||||||||||
| value: repoDisplay, | ||||||||||||||||||||||||
| inline: true, | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| { | ||||||||||||||||||||||||
| name: "License", | ||||||||||||||||||||||||
| value: gem.licenses?.join(", ") || "Other", | ||||||||||||||||||||||||
| inline: true, | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| .setThumbnail( | ||||||||||||||||||||||||
| "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e6/RubyGems_Logo_2015.svg/1200px-RubyGems_Logo_2015.svg.png", | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| .setFooter({ text: "Source: rubygems.org" }) | ||||||||||||||||||||||||
| .setColor(0xe9573f); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const buttons = [ | ||||||||||||||||||||||||
| new ButtonBuilder() | ||||||||||||||||||||||||
| .setLabel("View on RubyGems") | ||||||||||||||||||||||||
| .setStyle(ButtonStyle.Link) | ||||||||||||||||||||||||
| .setURL(`https://rubygems.org/gems/${gem.name}`), | ||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (gem.homepage_uri) { | ||||||||||||||||||||||||
| buttons.push( | ||||||||||||||||||||||||
| new ButtonBuilder() | ||||||||||||||||||||||||
| .setLabel("Visit Homepage") | ||||||||||||||||||||||||
| .setStyle(ButtonStyle.Link) | ||||||||||||||||||||||||
| .setURL(gem.homepage_uri), | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (gem.documentation_uri) { | ||||||||||||||||||||||||
| buttons.push( | ||||||||||||||||||||||||
| new ButtonBuilder() | ||||||||||||||||||||||||
| .setLabel("Docs") | ||||||||||||||||||||||||
| .setStyle(ButtonStyle.Link) | ||||||||||||||||||||||||
| .setURL(gem.documentation_uri), | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const row = new ActionRowBuilder().addComponents(...buttons); | ||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Confidence: 5/5 Suggested Fix
Suggested change
Add the generic type parameter Prompt for AICopy this prompt to your AI IDE to fix this issue locally: |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return message.reply({ | ||||||||||||||||||||||||
| embeds: [embed], | ||||||||||||||||||||||||
| components: [row.toJSON()], | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||
| console.error(err); | ||||||||||||||||||||||||
| return message.reply({ | ||||||||||||||||||||||||
| embeds: [ | ||||||||||||||||||||||||
| new EmbedBuilder() | ||||||||||||||||||||||||
| .setTitle("❌ Failed to fetch RubyGems results") | ||||||||||||||||||||||||
| .setDescription( | ||||||||||||||||||||||||
| "An error occurred while fetching results from rubygems.org.", | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| .setColor(0xd21872), | ||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code doesn't validate the HTTP response status before attempting to parse JSON. If the crates.io API returns an error status (4xx, 5xx), calling
.json()on a non-JSON response will throw an exception that gets caught generically, hiding the actual API error from users.Confidence: 5/5
Suggested Fix
Add HTTP response status validation after the fetch call and before parsing JSON. This prevents attempting to parse error HTML/text responses as JSON and provides clearer error messages to users.
Prompt for AI
Copy this prompt to your AI IDE to fix this issue locally:
📍 This suggestion applies to lines 25-31