This commit is contained in:
36
.github/workflows/test.yml
vendored
Normal file
36
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Python CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "master" ]
|
||||
pull_request:
|
||||
branches: [ "main", "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.14"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install flake8
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
|
||||
- name: Check syntax
|
||||
run: |
|
||||
python -m py_compile main.py
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
config.json
|
||||
__pycache__/
|
||||
16
License
Normal file
16
License
Normal file
@@ -0,0 +1,16 @@
|
||||
MIT No Attribution
|
||||
|
||||
Copyright (c) 2025
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
software and associated documentation files (the "Software"), to deal in the Software
|
||||
without restriction, including without limitation the rights to use, copy, modify,
|
||||
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
11
config.example.json
Normal file
11
config.example.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"bot_token": "YOUR BOT TOKEN",
|
||||
"prefix": ";",
|
||||
"command_name": "reddit",
|
||||
"comment_limit": 2,
|
||||
"delete_user_message_on_dismiss": true,
|
||||
"embed_color_submission": "blue",
|
||||
"embed_color_comments": "green",
|
||||
"uptime_kuma_url": "",
|
||||
"uptime_kuma_interval": 60
|
||||
}
|
||||
10
docker-compose.yml
Normal file
10
docker-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
services:
|
||||
bot:
|
||||
working_dir: /app
|
||||
command: bash -c "pip install -r requirements.txt && python main.py"
|
||||
container_name: reddit-fix-please
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PYTHONUNBUFFERED=1
|
||||
volumes:
|
||||
- .:/app
|
||||
146
main.py
Normal file
146
main.py
Normal file
@@ -0,0 +1,146 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
import redditwarp.SYNC
|
||||
import json
|
||||
import os
|
||||
import aiohttp
|
||||
import asyncio
|
||||
|
||||
# Load configuration
|
||||
config_path = os.path.join(os.path.dirname(__file__), "config.json")
|
||||
with open(config_path, "r") as f:
|
||||
config = json.load(f)
|
||||
|
||||
bot_token = config.get("bot_token")
|
||||
prefix = config.get("prefix", ";")
|
||||
command_name = config.get("command_name", "rediddy")
|
||||
comment_limit = config.get("comment_limit", 2)
|
||||
delete_user_msg = config.get("delete_user_message_on_dismiss", True)
|
||||
uptime_kuma_url = config.get("uptime_kuma_url", "")
|
||||
uptime_kuma_interval = config.get("uptime_kuma_interval", 60)
|
||||
|
||||
# Color mapping
|
||||
COLORS = {
|
||||
"blue": discord.Color.blue(),
|
||||
"green": discord.Color.green(),
|
||||
"red": discord.Color.red(),
|
||||
"gold": discord.Color.gold(),
|
||||
"purple": discord.Color.purple(),
|
||||
}
|
||||
sub_color = COLORS.get(
|
||||
config.get("embed_color_submission", "blue"), discord.Color.blue()
|
||||
)
|
||||
comment_color = COLORS.get(
|
||||
config.get("embed_color_comments", "green"), discord.Color.green()
|
||||
)
|
||||
|
||||
# Setup Discord bot intents
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
|
||||
bot = commands.Bot(command_prefix=prefix, intents=intents)
|
||||
reddit_client = redditwarp.SYNC.Client()
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f"Logged in as {bot.user} (ID: {bot.user.id})")
|
||||
print("------")
|
||||
if uptime_kuma_url:
|
||||
bot.loop.create_task(uptime_kuma_heartbeat())
|
||||
|
||||
|
||||
async def uptime_kuma_heartbeat():
|
||||
async with aiohttp.ClientSession() as session:
|
||||
while not bot.is_closed():
|
||||
try:
|
||||
async with session.get(uptime_kuma_url) as resp:
|
||||
if resp.status == 200:
|
||||
print("Uptime Kuma heartbeat sent.")
|
||||
else:
|
||||
print(f"Uptime Kuma error: {resp.status}")
|
||||
except Exception as e:
|
||||
print(f"Uptime Kuma connection failed: {e}")
|
||||
await asyncio.sleep(uptime_kuma_interval)
|
||||
|
||||
|
||||
class RediddyView(discord.ui.View):
|
||||
def __init__(self, author_msg: discord.Message, submission_url: str):
|
||||
super().__init__(timeout=60)
|
||||
self.author_msg = author_msg
|
||||
self.add_item(discord.ui.Button(label="Open Submission", url=submission_url))
|
||||
|
||||
@discord.ui.button(label="Dismiss", style=discord.ButtonStyle.danger)
|
||||
async def dismiss(
|
||||
self, interaction: discord.Interaction, button: discord.ui.Button
|
||||
):
|
||||
if delete_user_msg:
|
||||
try:
|
||||
await self.author_msg.delete()
|
||||
except discord.Forbidden:
|
||||
pass # May not have permission to delete user messages
|
||||
await interaction.message.delete()
|
||||
|
||||
|
||||
@bot.command(name=command_name)
|
||||
async def fetch_reddit_submission(ctx, subreddit_name: str, *, query: str):
|
||||
"""Searches Reddit and returns the top post and comments in embeds."""
|
||||
try:
|
||||
# Search for the subreddit name to get a valid subreddit object/ID
|
||||
sr_list = reddit_client.p.subreddit.search_names(subreddit_name)
|
||||
if not sr_list:
|
||||
await ctx.send(f"Subreddit '{subreddit_name}' not found.")
|
||||
return
|
||||
|
||||
target_sr = sr_list[0]
|
||||
|
||||
# Search for submissions within that subreddit
|
||||
submissions = reddit_client.p.submission.search(target_sr, query, 1, sort="top")
|
||||
|
||||
found = False
|
||||
for submission_brief in submissions:
|
||||
found = True
|
||||
# Get full submission details
|
||||
submission = reddit_client.p.submission.get(submission_brief.id)
|
||||
title = submission.d.get("title", "Reddit Post")
|
||||
selftext = submission.d.get("selftext", "No content.")
|
||||
permalink = f"https://www.reddit.com{submission.d.get('permalink')}"
|
||||
|
||||
# Submission Embed
|
||||
main_embed = discord.Embed(
|
||||
title=title,
|
||||
url=permalink,
|
||||
description=selftext[:2000] if selftext else "No content.",
|
||||
color=sub_color,
|
||||
)
|
||||
main_embed.set_author(name=f"r/{target_sr}")
|
||||
|
||||
embeds = [main_embed]
|
||||
|
||||
# Fetch comments based on limit
|
||||
try:
|
||||
comment_tree = reddit_client.p.comment_tree.fetch(
|
||||
submission.id, limit=comment_limit, sort="top"
|
||||
)
|
||||
for i, child in enumerate(comment_tree.children[:comment_limit]):
|
||||
comment_body = child.value.body
|
||||
comment_embed = discord.Embed(
|
||||
title=f"Top Comment #{i+1}",
|
||||
description=comment_body[:1000],
|
||||
color=comment_color,
|
||||
)
|
||||
embeds.append(comment_embed)
|
||||
except Exception as e:
|
||||
print(f"Error fetching comments: {e}")
|
||||
|
||||
view = RediddyView(ctx.message, permalink)
|
||||
await ctx.send(embeds=embeds, view=view)
|
||||
|
||||
if not found:
|
||||
await ctx.send(f"No results found for '{query}' in {target_sr}.")
|
||||
|
||||
except Exception as e:
|
||||
await ctx.send(f"An error occurred: {e}")
|
||||
|
||||
|
||||
bot.run(bot_token)
|
||||
63
readme.md
Normal file
63
readme.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Reddit Fix Please - Discord Bot
|
||||
|
||||
A configurable Discord bot that searches Reddit subreddits for specific queries and returns the top submission along with its top comments in clean embeds.
|
||||
|
||||
## Features
|
||||
|
||||
- **Reddit Search**: Finds the most relevant "top" submission for a given subreddit and query.
|
||||
- **Rich Embeds**: Displays the submission title, body, and top comments in easy-to-read Discord embeds.
|
||||
- **Interactive UI**:
|
||||
- **Open Submission**: Direct link button to the Reddit thread.
|
||||
- **Dismiss**: Cleans up the bot's response and (optionally) the user's command message.
|
||||
- **Fully Configurable**: Manage everything from tokens to UI colors via `config.json`.
|
||||
- **Uptime Kuma Integration**: Support for push monitor heartbeats to track bot status.
|
||||
|
||||
## Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.8+
|
||||
- A Discord Bot Token (from [Discord Developer Portal](https://discord.com/developers/applications)) (enable "Message Content Intent" for the bot)
|
||||
|
||||
### Installation
|
||||
|
||||
1. Install the required dependencies:
|
||||
```bash
|
||||
pip install discord.py redditwarp
|
||||
```
|
||||
|
||||
2. Copy `config.example.json` to `config.json`:
|
||||
```bash
|
||||
cp config.example.json config.json
|
||||
```
|
||||
|
||||
3. Open `config.json` and fill in your `bot_token`.
|
||||
|
||||
### Configuration Options
|
||||
|
||||
| Key | Description | Default |
|
||||
|-----|-------------|---------|
|
||||
| `bot_token` | Your Discord bot application token | 😬 |
|
||||
| `prefix` | The character(s) used to trigger the bot | `;` |
|
||||
| `command_name` | The name of the search command | `reddit` |
|
||||
| `comment_limit` | Number of top comments to display | `2` |
|
||||
| `delete_user_message_on_dismiss` | Whether the Dismiss button deletes the user's message | `true` |
|
||||
| `embed_color_submission` | Color of the main submission embed | `blue` |
|
||||
| `embed_color_comments` | Color of the comment embeds | `green` |
|
||||
| `uptime_kuma_url` | Uptime Kuma Push Monitor URL | `""` |
|
||||
| `uptime_kuma_interval` | Interval in seconds for heartbeats | `60` |
|
||||
|
||||
## Usage
|
||||
|
||||
Start the bot:
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
In Discord, use the configured command (default is `;reddit`):
|
||||
```text
|
||||
;reddit [subreddit] [query]
|
||||
```
|
||||
|
||||
**Example:**
|
||||
`;reddit valorantechsupport van-57`
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
redditwarp
|
||||
discord.py
|
||||
aiohttp
|
||||
Reference in New Issue
Block a user