first commit
This commit is contained in:
commit
7f20ec0336
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.env
|
||||||
|
node_modules
|
||||||
|
package-lock.json
|
130
README.md
Normal file
130
README.md
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
# URL Shortener Bot with Backend API
|
||||||
|
|
||||||
|
This repository contains two components: a **Discord bot** for shortening URLs and a **backend API** for handling the URL shortening and redirection. Both parts work together to provide a seamless experience for shortening URLs and sharing them directly from Discord.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Discord Slash Command** for URL shortening with domain selection.
|
||||||
|
- **Express.js Backend API** to handle the actual shortening process and redirection.
|
||||||
|
- **MongoDB Integration** for storing and retrieving shortened URLs.
|
||||||
|
- **API Key Validation** for secure access to the backend.
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### 1. `short.js` - Discord URL Shortener Bot
|
||||||
|
|
||||||
|
This is a Discord bot built using `discord.js` that provides a command to shorten URLs directly from Discord. Users can choose from multiple domains, or the bot will use a default domain.
|
||||||
|
|
||||||
|
#### Setup Instructions for `short.js`
|
||||||
|
|
||||||
|
1. **Install dependencies**:
|
||||||
|
```bash
|
||||||
|
npm install discord.js unirest dotenv
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Environment Variables**:
|
||||||
|
Create a `.env` file in the project root and add:
|
||||||
|
```
|
||||||
|
BOT_TOKEN=your_discord_bot_token
|
||||||
|
DISCORD_CLIENT_ID=your_discord_client_id
|
||||||
|
API_KEY=your_url_shortening_api_key
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Run the bot**:
|
||||||
|
```bash
|
||||||
|
node short.js
|
||||||
|
```
|
||||||
|
|
||||||
|
#### How It Works
|
||||||
|
|
||||||
|
- Users enter a URL and optionally choose a domain using the `/shortenurl` slash command.
|
||||||
|
- The bot validates the URL and sends a request to the backend API to generate the shortened link.
|
||||||
|
- The response is sent back as a Discord embed message with the shortened URL.
|
||||||
|
|
||||||
|
### 2. `short-backend-api.js` - URL Shortener Backend
|
||||||
|
|
||||||
|
This is a Node.js backend API using `Express.js` and `MongoDB` for URL shortening. It receives requests from the Discord bot or any client and provides a short URL in response.
|
||||||
|
|
||||||
|
#### Setup Instructions for `short-backend-api.js`
|
||||||
|
|
||||||
|
1. **Install dependencies**:
|
||||||
|
```bash
|
||||||
|
npm install express mongoose shortid dotenv
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **MongoDB Setup**:
|
||||||
|
Make sure MongoDB is running and accessible. By default, it connects to `mongodb://127.0.0.1:27017/shorturl`.
|
||||||
|
|
||||||
|
3. **Environment Variables**:
|
||||||
|
Create a `.env` file in the project root with:
|
||||||
|
```
|
||||||
|
API_KEY=your_secure_api_key
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Run the API**:
|
||||||
|
```bash
|
||||||
|
node short-backend-api.js
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Routes
|
||||||
|
|
||||||
|
- **POST `/api/shorturl`**: Shortens a URL.
|
||||||
|
- **Request**:
|
||||||
|
- Body: `{ "longUrl": "https://example.com", "domain": "s.shells.lol" }`
|
||||||
|
- Header: `x-api-key: your_api_key`
|
||||||
|
- **Response**: `{ "shortUrl": "https://s.shells.lol/abcd1234" }`
|
||||||
|
- **GET `/:shortId`**: Redirects to the original long URL.
|
||||||
|
|
||||||
|
#### How It Works
|
||||||
|
|
||||||
|
- The API checks if the provided API key is valid.
|
||||||
|
- The `longUrl` is checked against the database. If it has already been shortened, the existing short URL is returned.
|
||||||
|
- If it's a new URL, it generates a unique `shortId` using `shortid` and stores it in the database.
|
||||||
|
- The `shortId` can then be used to redirect users to the original URL when they visit `https://yourdomain.com/:shortId`.
|
||||||
|
|
||||||
|
## Models
|
||||||
|
|
||||||
|
### `Url` Model
|
||||||
|
|
||||||
|
This model represents the schema for storing URLs in MongoDB. It consists of the following fields:
|
||||||
|
|
||||||
|
- **longUrl**: The original URL.
|
||||||
|
- **shortId**: The shortened identifier for the URL.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const urlSchema = new mongoose.Schema({
|
||||||
|
longUrl: { type: String, required: true },
|
||||||
|
shortId: { type: String, required: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Url', urlSchema);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Full Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/) v16.6.0 or higher
|
||||||
|
- [MongoDB](https://www.mongodb.com/) running locally or remotely
|
||||||
|
- A valid Discord bot token and a URL shortening API key
|
||||||
|
|
||||||
|
### Steps
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/url-shortener-bot.git
|
||||||
|
cd url-shortener-bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Follow the setup instructions for both `short.js` and `short-backend-api.js`.
|
||||||
|
|
||||||
|
3. Ensure the bot and backend API are both running:
|
||||||
|
```bash
|
||||||
|
# In one terminal, run the backend API
|
||||||
|
node short-backend-api.js
|
||||||
|
|
||||||
|
# In another terminal, run the Discord bot
|
||||||
|
node short.js
|
||||||
|
```
|
15
models/Url.js
Normal file
15
models/Url.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const urlSchema = new mongoose.Schema({
|
||||||
|
longUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
shortId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Url', urlSchema);
|
20
package.json
Normal file
20
package.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "short",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "short.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.7.7",
|
||||||
|
"discord.js": "^14.16.3",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"unirest": "^0.6.0",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"mongoose": "^7.0.4",
|
||||||
|
"shortid": "^2.2.16"
|
||||||
|
}
|
||||||
|
}
|
94
short-backend-api.js
Normal file
94
short-backend-api.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
const shortid = require('shortid');
|
||||||
|
const Url = require('./models/Url');
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = 9043;
|
||||||
|
|
||||||
|
// Middleware to parse JSON
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// MongoDB connection
|
||||||
|
mongoose.connect('mongodb://127.0.0.1:27017/shorturl');
|
||||||
|
|
||||||
|
const db = mongoose.connection;
|
||||||
|
db.on('error', console.error.bind(console, 'connection error:'));
|
||||||
|
db.once('open', () => {
|
||||||
|
console.log('Connected to MongoDB');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Supported domains
|
||||||
|
const supportedDomains = [
|
||||||
|
's.shells.lol',
|
||||||
|
's.hehe.rest',
|
||||||
|
's.dcord.rest', // default domain
|
||||||
|
's.nodejs.lol',
|
||||||
|
's.dht.rest',
|
||||||
|
's.tcp.quest'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Middleware to check API key
|
||||||
|
const validateApiKey = (req, res, next) => {
|
||||||
|
const apiKey = req.headers['x-api-key'];
|
||||||
|
if (apiKey && apiKey === process.env.API_KEY) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.status(403).json({ error: 'Forbidden' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Route to create a short URL
|
||||||
|
app.post('/api/shorturl', validateApiKey, async (req, res) => {
|
||||||
|
const { longUrl, domain } = req.body;
|
||||||
|
|
||||||
|
if (!longUrl) {
|
||||||
|
return res.status(400).json({ error: 'Invalid URL' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate domain, default to 's.dcord.rest' if not provided or invalid
|
||||||
|
const selectedDomain = supportedDomains.includes(domain) ? domain : 's.dcord.rest';
|
||||||
|
|
||||||
|
try {
|
||||||
|
let url = await Url.findOne({ longUrl });
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
return res.json({ shortUrl: `https://${selectedDomain}/${url.shortId}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const shortId = shortid.generate();
|
||||||
|
url = new Url({
|
||||||
|
longUrl,
|
||||||
|
shortId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await url.save();
|
||||||
|
res.json({ shortUrl: `https://${selectedDomain}/${shortId}` });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({ error: 'Server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route to handle redirect
|
||||||
|
app.get('/:shortId', async (req, res) => {
|
||||||
|
const { shortId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = await Url.findOne({ shortId });
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
return res.redirect(301, url.longUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(404).json({ error: 'URL not found' });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({ error: 'Server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Server running on port ${port}`);
|
||||||
|
});
|
100
short.js
Normal file
100
short.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
const { Client, GatewayIntentBits, REST, Routes, SlashCommandBuilder, EmbedBuilder } = require('discord.js');
|
||||||
|
const unirest = require('unirest');
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||||
|
const rest = new REST({ version: '10' }).setToken(process.env.BOT_TOKEN);
|
||||||
|
const DiscordClientId = process.env.DISCORD_CLIENT_ID; // Your Discord Client ID
|
||||||
|
|
||||||
|
// URL validation function
|
||||||
|
function isValidURL(url) {
|
||||||
|
const regex = /^(https?:\/\/)?([a-zA-Z0-9\-\.]+)\.([a-z]{2,})(\/\S*)?$/;
|
||||||
|
return regex.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.once('ready', async () => {
|
||||||
|
console.log(`Logged in as ${client.user.tag}`);
|
||||||
|
|
||||||
|
// Define your slash commands using SlashCommandBuilder
|
||||||
|
const command = new SlashCommandBuilder()
|
||||||
|
.setName('shortenurl')
|
||||||
|
.setDescription('Shorten a URL')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('url')
|
||||||
|
.setDescription('Enter the URL to shorten')
|
||||||
|
.setRequired(true))
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('domain')
|
||||||
|
.setDescription('Choose the domain for the short URL')
|
||||||
|
.setRequired(false)
|
||||||
|
.addChoices(
|
||||||
|
{ name: 's.shells.lol', value: 's.shells.lol' },
|
||||||
|
{ name: 's.hehe.rest', value: 's.hehe.rest' },
|
||||||
|
{ name: 's.dcord.rest', value: 's.dcord.rest' }, // default domain
|
||||||
|
{ name: 's.nodejs.lol', value: 's.nodejs.lol' },
|
||||||
|
{ name: 's.dht.rest', value: 's.dht.rest' },
|
||||||
|
{ name: 's.tcp.quest', value: 's.tcp.quest' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.toJSON(); // Convert to JSON
|
||||||
|
|
||||||
|
// Add extras to the command for integration
|
||||||
|
const extras = {
|
||||||
|
"integration_types": [0, 1], // 0 for guild, 1 for user
|
||||||
|
"contexts": [0, 1, 2], // 0 for guild, 1 for app DMs, 2 for GDMs and other DMs
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(extras).forEach(key => command[key] = extras[key]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Register the command with Discord
|
||||||
|
await rest.put(Routes.applicationCommands(DiscordClientId), { body: [command] });
|
||||||
|
console.log('Commands registered successfully.');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error registering commands:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('interactionCreate', async interaction => {
|
||||||
|
if (!interaction.isCommand() || interaction.commandName !== 'shortenurl') return;
|
||||||
|
|
||||||
|
const domain = interaction.options.getString('domain') || 's.dcord.rest';
|
||||||
|
const url = interaction.options.getString('url');
|
||||||
|
|
||||||
|
// Validate the URL format before proceeding
|
||||||
|
if (!isValidURL(url)) {
|
||||||
|
return interaction.reply({ content: 'Please provide a valid URL.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Make API call to the selected domain to create a short URL
|
||||||
|
unirest.post(`https://${domain}/api/shorturl`)
|
||||||
|
.headers({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-api-key': process.env.API_KEY // Use the API key from environment variables
|
||||||
|
})
|
||||||
|
.send({ longUrl: url, domain })
|
||||||
|
.end((response) => {
|
||||||
|
const data = response.body;
|
||||||
|
|
||||||
|
if (data.shortUrl) {
|
||||||
|
// Render the short URL directly without Markdown brackets for proper embedding
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor("#FF0000")
|
||||||
|
.setTitle("🔗 URL Shortened!")
|
||||||
|
.setDescription(`${data.shortUrl}`)
|
||||||
|
.setTimestamp()
|
||||||
|
.setFooter({ text: `Requested by ${interaction.user.tag}`, iconURL: `${interaction.user.displayAvatarURL()}` });
|
||||||
|
|
||||||
|
interaction.reply({ embeds: [embed] });
|
||||||
|
} else {
|
||||||
|
interaction.reply({ content: `Failed to shorten URL: ${data.error || 'Unknown error'}`, ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating shortened URL:', error);
|
||||||
|
interaction.reply({ content: 'There was an error trying to shorten the URL. Please try again later.', ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.login(process.env.BOT_TOKEN);
|
Loading…
Reference in New Issue
Block a user