Initial commit
This commit is contained in:
parent
2fc7e76385
commit
4bbe3029d9
180
README.md
180
README.md
@ -1,2 +1,180 @@
|
||||
# discord-lnbits-bot
|
||||
# Discord LNbits Bot
|
||||
|
||||
This bot allows users to purchase a **Discord role** using **Bitcoin Lightning payments** through LNbits. Users request an invoice via a **slash command**, and the bot automatically assigns the role **once payment is confirmed**.
|
||||
|
||||
## Features
|
||||
|
||||
✅ Users request an invoice using `/support` (configurable).
|
||||
✅ The bot generates a **Lightning invoice** via LNbits.
|
||||
✅ Once paid, the bot assigns a **Discord role**.
|
||||
✅ Configurable check intervals & max attempts to verify payments.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Ubuntu 24.04**
|
||||
- **Python 3.10+**
|
||||
- **A Discord Bot Token** (from the [Developer Portal](https://discord.com/developers/applications))
|
||||
- **LNbits Wallet Invoice Key**
|
||||
- **A running LNbits instance** (e.g., [Sats.Love](https://sats.love/))
|
||||
|
||||
---
|
||||
|
||||
## 1️⃣ Setting Up Your Bot
|
||||
|
||||
### Generate a Proper Bot Invite Link
|
||||
|
||||
1. **Go to**: [Discord Developer Portal](https://discord.com/developers/applications)
|
||||
2. **Click New Application**
|
||||
3. Go to **OAuth2 > URL Generator**
|
||||
4. **Check these Scopes**:
|
||||
- ✅ **bot**
|
||||
- ✅ **applications.commands** (for slash commands)
|
||||
4. **Under Bot Permissions**, check:
|
||||
- ✅ **Manage Roles**
|
||||
- ✅ **Send Messages**
|
||||
- ✅ **Embed Links**
|
||||
- ✅ **Read Message History**
|
||||
5. **Copy & paste the generated link into your browser.**
|
||||
6. **Select your Discord server** and click **Authorize**.
|
||||
|
||||
---
|
||||
|
||||
### Enable Privileged Intents
|
||||
|
||||
1. **Go to** [Discord Developer Portal](https://discord.com/developers/applications).
|
||||
2. **Select your bot application.**
|
||||
3. **Navigate to** `Bot` in the left sidebar.
|
||||
4. **Scroll down to** `Privileged Gateway Intents`.
|
||||
5. **Enable** the following:
|
||||
- ✅ **Presence Intent** *(optional)*
|
||||
- ✅ **Server Members Intent** *(⚠️ Required for managing roles!)*
|
||||
- ✅ **Message Content Intent** *(optional, only if reading messages is needed)*
|
||||
6. **Click Save Changes.**
|
||||
|
||||
---
|
||||
|
||||
### Ensure Correct Role Permissions
|
||||
|
||||
1. **Go to Server Settings** → `Roles`.
|
||||
2. **Drag the bot’s role ABOVE** the role it needs to assign.
|
||||
3. **Ensure the bot has these permissions**:
|
||||
- ✅ **Manage Roles**
|
||||
- ✅ **Read Messages**
|
||||
- ✅ **Send Messages**
|
||||
|
||||
---
|
||||
|
||||
## 2️⃣ Installation Guide
|
||||
|
||||
### 1. Install System Dependencies
|
||||
|
||||
```bash
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
sudo apt install python3 python3-pip python3-venv -y
|
||||
```
|
||||
|
||||
### 2. Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://code.rustysats.com/saulteafarmer/discord-lnbits-bot
|
||||
cd discord-lnbits-bot
|
||||
```
|
||||
|
||||
### 3. Create & Activate Virtual Environment
|
||||
|
||||
```bash
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
### 4. Install Dependencies
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3️⃣ Find Your Discord Guild ID & Role ID
|
||||
|
||||
Before configuring the bot, retrieve your **Guild ID** and **Role ID**.
|
||||
|
||||
### 🔹 How to Find Your Guild ID (Server ID):
|
||||
|
||||
1. Open **Discord** → Go to **User Settings** (⚙️).
|
||||
2. Scroll down to **Advanced** → Enable **Developer Mode**.
|
||||
3. **Right-click your server name** (left sidebar) → Click **Copy ID**.
|
||||
4. **Save this ID** for later (`guild_id`).
|
||||
|
||||
### 🔹 How to Find Your Role ID:
|
||||
|
||||
1. Open your **Discord Server**.
|
||||
2. Go to **Server Settings** → **Roles**.
|
||||
3. **Right-click on the role** you want to assign → Click **Copy ID**.
|
||||
4. **Save this ID** for later (`role_id`).
|
||||
|
||||
---
|
||||
|
||||
## 4️⃣ Configure the Bot
|
||||
|
||||
Edit the `config.json` file inside the bot directory:
|
||||
|
||||
```bash
|
||||
sudo nano config.json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"discord_token": "YOUR_DISCORD_BOT_TOKEN",
|
||||
"guild_id": "YOUR_GUILD_ID",
|
||||
"role_id": "YOUR_ROLE_ID",
|
||||
"lnbits_url": "https://sats.love",
|
||||
"lnbits_api_key": "YOUR_INVOICE_READ_KEY",
|
||||
"price": 1000,
|
||||
"command_name": "support",
|
||||
"check_interval": 30,
|
||||
"max_checks": 20
|
||||
}
|
||||
```
|
||||
|
||||
### 🔹 What Each Setting Does:
|
||||
|
||||
- **`discord_token`** → Your bot token from Discord Developer Portal
|
||||
- **`guild_id`** → Your Discord server ID
|
||||
- **`role_id`** → The Discord role the bot will assign
|
||||
- **`lnbits_url`** → Base URL of your LNbits instance
|
||||
- **`lnbits_api_key`** → **Invoice-only key** from LNbits (⚠️ NOT an admin key)
|
||||
- **`price`** → Price in **satoshis** (e.g., `1000` = 1000 sats)
|
||||
- **`command_name`** → Name of the command (default: `support`)
|
||||
- **`check_interval`** → How often (in seconds) the bot checks for payments
|
||||
- **`max_checks`** → How many times the bot will check before stopping
|
||||
|
||||
---
|
||||
|
||||
## 5️⃣ Run the Bot
|
||||
|
||||
```bash
|
||||
python3 discord_lnbits_bot.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6️⃣ Running the Bot in the Background
|
||||
|
||||
```bash
|
||||
nohup python3 discord_lnbits_bot.py &
|
||||
```
|
||||
|
||||
🔹 **To stop the bot**:
|
||||
|
||||
```bash
|
||||
pkill -f discord_lnbits_bot.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This project is **open-source** and free to use. Contributions welcome!
|
11
config.json
Normal file
11
config.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"discord_token": "YOUR_DISCORD_BOT_TOKEN",
|
||||
"guild_id": "YOUR_GUILD_ID",
|
||||
"role_id": "YOUR_ROLE_ID",
|
||||
"lnbits_url": "https://sats.love",
|
||||
"lnbits_api_key": "YOUR_INVOICE_READ_KEY",
|
||||
"price": 1000,
|
||||
"command_name": "support",
|
||||
"check_interval": 30,
|
||||
"max_checks": 20
|
||||
}
|
137
discord_lnbits_bot.py
Normal file
137
discord_lnbits_bot.py
Normal file
@ -0,0 +1,137 @@
|
||||
import discord
|
||||
import asyncio
|
||||
import requests
|
||||
import json
|
||||
import io
|
||||
import qrcode
|
||||
from discord import File, Embed
|
||||
from discord.ext import commands, tasks
|
||||
|
||||
with open("config.json", "r") as f:
|
||||
config = json.load(f)
|
||||
|
||||
TOKEN = config["discord_token"]
|
||||
GUILD_ID = int(config["guild_id"])
|
||||
ROLE_ID = int(config["role_id"])
|
||||
LNBITS_URL = config["lnbits_url"]
|
||||
LNBITS_API_KEY = config["lnbits_api_key"]
|
||||
PRICE = config["price"]
|
||||
CHECK_INTERVAL = config["check_interval"]
|
||||
MAX_CHECKS = config["max_checks"]
|
||||
|
||||
intents = discord.Intents.default()
|
||||
intents.members = True
|
||||
intents.message_content = True
|
||||
|
||||
bot = commands.Bot(command_prefix="!", intents=intents)
|
||||
|
||||
pending_invoices = {}
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
"""Called when the bot successfully logs in."""
|
||||
print(f"✅ Logged in as {bot.user}")
|
||||
print("Bot is in the following guilds:")
|
||||
for g in bot.guilds:
|
||||
print(f" - {g.name} (ID: {g.id})")
|
||||
|
||||
synced_cmds = await bot.tree.sync()
|
||||
print("✅ Synced global commands:", synced_cmds)
|
||||
|
||||
print(f"🔄 Checking invoices every {CHECK_INTERVAL} seconds...")
|
||||
check_invoices.start()
|
||||
|
||||
@bot.tree.command(name="support", description="Pay a Lightning invoice to get your role!")
|
||||
async def support_command(interaction: discord.Interaction):
|
||||
user_id = interaction.user.id
|
||||
|
||||
invoice_data = {
|
||||
"out": False,
|
||||
"amount": PRICE,
|
||||
"memo": "Lightning Payment"
|
||||
}
|
||||
headers = {
|
||||
"X-Api-Key": LNBITS_API_KEY,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
resp = requests.post(f"{LNBITS_URL}/api/v1/payments", json=invoice_data, headers=headers)
|
||||
if resp.status_code == 201:
|
||||
invoice_json = resp.json()
|
||||
payment_request = invoice_json["payment_request"]
|
||||
payment_hash = invoice_json["payment_hash"]
|
||||
|
||||
pending_invoices[payment_hash] = (user_id, 0)
|
||||
|
||||
qr_buffer = io.BytesIO()
|
||||
qrcode.make(payment_request).save(qr_buffer, format="PNG")
|
||||
qr_buffer.seek(0)
|
||||
qr_file = File(fp=qr_buffer, filename="invoice_qr.png")
|
||||
|
||||
embed = Embed(
|
||||
title="Payment Invoice",
|
||||
description="Please pay the following Lightning invoice to complete your purchase."
|
||||
)
|
||||
embed.add_field(name="Invoice", value=f"```{payment_request}```", inline=False)
|
||||
embed.add_field(name="Amount", value=f"{PRICE} sats", inline=True)
|
||||
embed.add_field(name="Requesting User", value=f"{interaction.user.display_name}", inline=True)
|
||||
embed.set_image(url="attachment://invoice_qr.png")
|
||||
|
||||
await interaction.user.send(
|
||||
content=f"{interaction.user.display_name}, please pay **{PRICE} sats** using the Lightning Network.",
|
||||
embed=embed,
|
||||
file=qr_file
|
||||
)
|
||||
|
||||
await interaction.response.send_message("✅ I've sent you a payment invoice via DM!", ephemeral=True)
|
||||
|
||||
else:
|
||||
await interaction.response.send_message("❌ Failed to generate invoice. Try again later.", ephemeral=True)
|
||||
print(f"❌ LNbits Error: {resp.text}")
|
||||
|
||||
@tasks.loop(seconds=CHECK_INTERVAL)
|
||||
async def check_invoices():
|
||||
if not pending_invoices:
|
||||
return
|
||||
|
||||
guild = discord.utils.get(bot.guilds, id=GUILD_ID)
|
||||
if guild is None:
|
||||
print(f"⚠️ Bot not in server with ID {GUILD_ID}")
|
||||
return
|
||||
|
||||
headers = {
|
||||
"X-Api-Key": LNBITS_API_KEY,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
invoices_to_remove = []
|
||||
|
||||
for payment_hash, (user_id, attempts) in pending_invoices.items():
|
||||
if attempts >= MAX_CHECKS:
|
||||
invoices_to_remove.append(payment_hash)
|
||||
continue
|
||||
|
||||
status_resp = requests.get(f"{LNBITS_URL}/api/v1/payments/{payment_hash}", headers=headers)
|
||||
|
||||
if status_resp.status_code == 200:
|
||||
payment_status = status_resp.json()
|
||||
if payment_status.get("paid", False):
|
||||
member = guild.get_member(user_id)
|
||||
if member:
|
||||
role = guild.get_role(ROLE_ID)
|
||||
if role:
|
||||
await member.add_roles(role)
|
||||
await member.send("✅ **Thank you! Your role has been assigned.**")
|
||||
print(f"🎉 Role assigned to {member.name}")
|
||||
invoices_to_remove.append(payment_hash)
|
||||
else:
|
||||
print(f"❌ Role ID {ROLE_ID} not found!")
|
||||
else:
|
||||
print(f"❌ Member {user_id} not found!")
|
||||
|
||||
pending_invoices[payment_hash] = (user_id, attempts + 1)
|
||||
|
||||
for done_hash in invoices_to_remove:
|
||||
del pending_invoices[done_hash]
|
||||
|
||||
bot.run(TOKEN)
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
discord.py
|
||||
requests
|
||||
qrcode[pil]
|
||||
asyncio
|
Loading…
x
Reference in New Issue
Block a user