This commit is contained in:
2026-03-11 23:24:42 +00:00
commit cf3e2c8c78
15 changed files with 1568 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
# Minecraft Update Bot - Environment Configuration
# Copy this file to .env and fill in your values
# Reddit App Credentials
# Create an app at https://www.reddit.com/prefs/apps (type: Script)
REDDIT_CLIENT_ID=YOUR_CLIENT_ID_HERE
REDDIT_CLIENT_SECRET=YOUR_CLIENT_SECRET_HERE
# Your Reddit Account
REDDIT_USERNAME=YOUR_USERNAME_HERE
REDDIT_PASSWORD=YOUR_PASSWORD_HERE
# Which subreddit to post to
SUBREDDIT=YOUR_SUBREDDIT_HERE
# Optional: customize the user agent
# REDDIT_USER_AGENT=MinecraftUpdateBot/1.0
# Optional: what types of releases to check for
# Options: release, snapshot, old_beta, old_alpha
# Multiple: RELEASE_TYPES=release,snapshot
RELEASE_TYPES=release
# Optional: how often to check for updates (in seconds)
# Default: 3600 (1 hour)
# For testing: 600 (10 minutes)
CHECK_INTERVAL=3600
# Wiki Configuration
# Post titles and bodies are loaded from the subreddit wiki page "minecraft_update_bot"
# Format: YAML with release_type sections containing title and body
# Example: See WIKI_CONFIG.md for complete documentation
+30
View File
@@ -0,0 +1,30 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
*.egg-info/
dist/
build/
# IDE
.vscode/
.idea/
*.swp
*.swo
# Project files
DB/
.env
.env.local
# Environment
venv/
env/
# OS
.DS_Store
Thumbs.db
+204
View File
@@ -0,0 +1,204 @@
# Running with Docker Compose
This project includes Docker and Docker Compose configuration for easy deployment.
## Prerequisites
- Docker installed
- Docker Compose installed
## Quick Start
### 1. Copy Environment File
```bash
copy .env.example .env
```
### 2. Edit .env
Open `.env` and fill in your Reddit credentials:
```env
REDDIT_CLIENT_ID=your_client_id
REDDIT_CLIENT_SECRET=your_client_secret
REDDIT_USERNAME=your_username
REDDIT_PASSWORD=your_password
SUBREDDIT=your_subreddit
```
### 3. (Optional) Set Up Wiki Configuration
Create a wiki page on your subreddit named `minecraft_update_bot` with your post templates in YAML format. This allows different posts for releases, snapshots, etc.
See [WIKI_CONFIG.md](WIKI_CONFIG.md) for detailed instructions and examples.
### 4. Start the Bot
```bash
docker-compose up -d
```
This will:
- Build the Docker image
- Start the bot in a container
- Persist data in a Docker volume named `minecraft-bot-db`
- Auto-restart the container if it crashes
### 5. View Logs
```bash
# Watch logs in real-time
docker-compose logs -f
# Or check status
docker-compose ps
```
### 5. Stop the Bot
```bash
docker-compose down
```
## Configuration
All configuration is done via the `.env` file. The following variables are available:
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `REDDIT_CLIENT_ID` | ✓ | - | Your Reddit app client ID |
| `REDDIT_CLIENT_SECRET` | ✓ | - | Your Reddit app secret |
| `REDDIT_USERNAME` | ✓ | - | Your Reddit username |
| `REDDIT_PASSWORD` | ✓ | - | Your Reddit password |
| `SUBREDDIT` | ✓ | - | Subreddit to post to |
| `RELEASE_TYPES` | | `release` | Comma-separated: `release,snapshot` |
| `CHECK_INTERVAL` | | `3600` | Seconds between checks |
| `REDDIT_USER_AGENT` | | `MinecraftUpdateBot/1.0` | API user agent |
### Examples
**Check for both releases and snapshots:**
```env
RELEASE_TYPES=release,snapshot
```
**Check every 10 minutes (for testing):**
```env
CHECK_INTERVAL=600
```
## Data Persistence
The bot's database (list of posted versions) is stored in a Docker volume called `minecraft-bot-db`. This persists between container restarts.
To view the data:
```bash
docker volume inspect minecraft-bot-db
```
To reset the database (post all versions again):
```bash
docker volume rm minecraft-bot-db
```
## Building
To rebuild the image after code changes:
```bash
docker-compose build --no-cache
docker-compose up -d
```
## Troubleshooting
### Container won't start
```bash
docker-compose logs
```
Check for configuration errors or missing credentials.
### Reddit authentication error
- Verify your credentials in `.env`
- Check that your Reddit app still exists at https://www.reddit.com/prefs/apps
- Make sure your account can post to the subreddit
### No posts being made
- Check the logs: `docker-compose logs -f`
- Verify the subreddit name in `.env`
- Check `docker exec minecraft-bot cat /app/DB/posted_versions.json` to see what's been posted
## Advanced Options
### Custom Post Template
Mount your config file to customize the post format:
Edit `docker-compose.yml`:
```yaml
volumes:
- minecraft-bot-db:/app/DB
- ./config.py:/app/config.py # Uncomment this line
```
Then edit `config.py` locally to customize `POST_TEMPLATE`.
### Resource Limits
Uncomment and adjust in `docker-compose.yml`:
```yaml
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
```
### Running Multiple Instances
Create separate directories with different `.env` files and run:
```bash
docker-compose -p instance1 up -d
docker-compose -p instance2 up -d
```
## Getting Reddit Credentials
1. Go to https://www.reddit.com/prefs/apps
2. Click "Create an app" or "Create another app"
3. Choose "Script" application type
4. Fill in the form:
- **Name:** MinecraftUpdateBot
- **Redirect URI:** http://localhost:8080
5. Copy Client ID and Client Secret from the app page
6. Use your Reddit username and password
## Docker Commands Reference
```bash
# Start the bot
docker-compose up -d
# Stop the bot
docker-compose down
# Restart the bot
docker-compose restart
# View logs
docker-compose logs -f
# Check status
docker-compose ps
# Execute a command in the container
docker exec minecraft-bot python test_setup.py
# Remove everything (including volumes)
docker-compose down -v
```
+16
View File
@@ -0,0 +1,16 @@
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Create DB directory
RUN mkdir -p DB
# Run the bot
CMD ["python", "main.py"]
+65
View File
@@ -0,0 +1,65 @@
# Quick Start Guide
## 1️⃣ Installation
```bash
cd d:\Git Repos\MinecraftUpdates
pip install -r requirements.txt
```
## 2️⃣ Reddit Setup
1. Go to https://www.reddit.com/prefs/apps
2. Click "Create an app" (type: Script)
3. Get your credentials: Client ID, Client Secret
## 3️⃣ Configure
Edit `config.py`:
```python
REDDIT_CLIENT_ID = "your_client_id"
REDDIT_CLIENT_SECRET = "your_client_secret"
REDDIT_USERNAME = "your_username"
REDDIT_PASSWORD = "your_password"
SUBREDDIT = "your_subreddit"
```
## 4️⃣ Test
```bash
python test_setup.py
```
Should show:
- ✓ Configuration checked
- ✓ Minecraft API working
- ✓ Reddit connection working
## 5️⃣ Run
```bash
python main.py
```
See posts appear in your subreddit automatically!
---
## Optional Config Changes
### Check for Snapshots Too
```python
RELEASE_TYPES = ["release", "snapshot"]
```
### Check More Frequently (testing)
```python
CHECK_INTERVAL = 600 # 10 minutes instead of 1 hour
```
### Customize Post Format
Edit the `POST_TEMPLATE` in `config.py` - it uses `{version}` and `{release_date}` placeholders.
---
## Files Overview
- `main.py` - The bot itself
- `config.py` - Settings (edit this!)
- `minecraft_checker.py` - Fetches Minecraft version data
- `test_setup.py` - Tests your configuration
- `DB/` - Stores tracking of posted versions (auto-created)
+212
View File
@@ -0,0 +1,212 @@
# Minecraft Update Bot 🎮
Automatically detects new Minecraft releases and posts them to a subreddit.
## Features
- ✅ Checks Minecraft launcher manifest for new releases
- ✅ Posts to Reddit whenever a new version is detected
- ✅ Tracks posted versions to avoid duplicates
- ✅ Runs continuously with configurable check interval
- ✅ Supports multiple release types (releases, snapshots, etc.)
- ✅ Docker & Docker Compose ready
-**Customizable post templates via subreddit wiki**
-**Different post formats for different release types**
-**Auto-update notifications** - Gets alerted when new bot versions are available
## Quick Start - Docker (Recommended)
```bash
# Copy and edit environment file
copy .env.example .env
# Edit .env with your Reddit credentials
# Start the bot
docker-compose up -d
# View logs
docker-compose logs -f
```
See [DOCKER.md](DOCKER.md) for detailed Docker setup instructions.
## Customizing Posts with Wiki Configuration
The bot loads post templates from your subreddit's wiki page `minecraft_update_bot`. This allows you to:
- Create different post formats for releases vs. snapshots
- Customize titles and bodies without restarting
- Support legacy version types with custom messages
**Quick Setup:**
1. Create a wiki page named `minecraft_update_bot` on your subreddit
2. Add YAML configuration with your templates (see example below)
3. Bot auto-loads every 30 minutes
**Simple Example:**
```yaml
release:
title: "Minecraft {version} Released!"
body: |
# Minecraft {version}
Available now! Get it at [minecraft.net](https://minecraft.net)
**Released:** {release_date}
snapshot:
title: "Minecraft {version} Snapshot"
body: |
# New Snapshot Available
Test {version} before the official release!
```
👉 See [WIKI_CONFIG.md](WIKI_CONFIG.md) for complete setup and examples.
## Manual Setup
### 1. Install Dependencies
```bash
pip install -r requirements.txt
```
### 2. Create Reddit Application
1. Go to [https://www.reddit.com/prefs/apps](https://www.reddit.com/prefs/apps)
2. Click "Create an app" or "Create another app"
3. Fill in the form:
- **Name:** MinecraftUpdateBot
- **App type:** Script
- **Redirect URI:** http://localhost:8080
4. Copy the credentials
### 3. Configure the Bot
Edit `config.py` and fill in your credentials:
```python
REDDIT_CLIENT_ID = "YOUR_CLIENT_ID" # From app page
REDDIT_CLIENT_SECRET = "YOUR_CLIENT_SECRET" # From app page
REDDIT_USERNAME = "YOUR_USERNAME" # Your Reddit username
REDDIT_PASSWORD = "YOUR_PASSWORD" # Your Reddit password
SUBREDDIT = "YOUR_SUBREDDIT" # Subreddit to post to
```
Or use environment variables (Docker-friendly):
```bash
set REDDIT_CLIENT_ID=your_client_id
set REDDIT_CLIENT_SECRET=your_client_secret
set REDDIT_USERNAME=your_username
set REDDIT_PASSWORD=your_password
set SUBREDDIT=your_subreddit
python main.py
```
### 4. Customize (Optional)
In `config.py`:
- **RELEASE_TYPES:** Change `["release"]` to include snapshots or other types
- Options: `"release"`, `"snapshot"`, `"old_beta"`, `"old_alpha"`
- **CHECK_INTERVAL:** How often to check for updates (in seconds)
- Default: 3600 (1 hour)
- **POST_TEMPLATE:** Customize the post format
Examples:
```python
# Check for both releases and snapshots
RELEASE_TYPES = ["release", "snapshot"]
# Check every 30 minutes
CHECK_INTERVAL = 1800
# Check every 10 minutes (for testing)
CHECK_INTERVAL = 600
```
### 5. Run the Bot
```bash
python main.py
```
You should see output like:
```
[BOT] Starting Minecraft Update Bot...
[BOT] ✓ Successfully connected to Reddit
[BOT] Started update checker (checking every 3600 seconds)
[BOT] ✓ Bot is running
```
## Automatic Update Notifications
The bot includes an update checker that periodically polls for new versions and notifies your subreddit's modteam via modmail when updates are available.
**How it works:**
- Checks `https://updts.slfhstd.uk` every hour for new versions
- Sends modmail to your subreddit's modteam if an update is found
- Limits notifications to once per 24 hours to avoid spam
- No configuration needed - it runs automatically!
**What you'll see:**
```
[UPDATE_CHECKER] Started for MinecraftUpdateBot v1.0
[UPDATE_CHECKER] Update found: 1.0 -> 1.1
[UPDATE_CHECKER] Sent update notification to r/your_subreddit
```
The modteam will receive a message with the new version number and changelog link.
## How It Works
1. **Initialization:** Bot connects to Reddit and loads posted versions from `DB/posted_versions.json`
2. **Check Loop:** Every CHECK_INTERVAL seconds, it:
- Fetches the latest Minecraft versions from Mojang's launcher manifest
- Checks if any are new (not in `DB/posted_versions.json`)
- Posts to subreddit if new versions found
- Saves the posted version IDs
3. **Background:** Runs in background thread, checking continuously
## Database
Posted versions are stored in `DB/posted_versions.json`:
```json
{
"posted": [
"1.20.1",
"1.21",
"1.21.1"
]
}
```
To reset (post all new versions again), delete or clear this file.
## Troubleshooting
### Connection Issues
- Check your credentials in `config.py` or environment variables
- Verify your Reddit app is configured correctly
- Make sure your Reddit account can post to the subreddit
### "No New Releases"
- The bot only posts once per version
- Check `DB/posted_versions.json` to see what's been posted
- Delete a version from the file to repost it
### Test with Snapshots
Change `RELEASE_TYPES = ["snapshot"]` and `CHECK_INTERVAL = 60` to test quickly.
## Files
- `main.py` - Main bot script
- `config.py` - Configuration
- `minecraft_checker.py` - Fetches version data from Mojang
- `test_setup.py` - Validates your setup
- `Dockerfile` - Docker image definition
- `docker-compose.yml` - Docker Compose configuration
- `DB/` - Database folder (created automatically)
- `requirements.txt` - Python dependencies
## License
MIT
+202
View File
@@ -0,0 +1,202 @@
# Wiki Configuration Setup
The Minecraft Update Bot uses a subreddit wiki page to store customizable post templates. This allows you to configure different post formats for different release types (releases, snapshots, etc.) without restarting the bot.
## Quick Setup
1. **Create the wiki page on your subreddit**
- Go to your subreddit settings
- Navigate to "Edit wiki page"
- Create a new page named `minecraft_update_bot`
2. **Add the configuration in YAML format**
Copy and paste the following into your wiki page:
```yaml
release:
title: "Minecraft {version} Released!"
body: |
# Minecraft {version} Released
A new version of Minecraft is now available!
**Version:** {version}
**Released:** {release_date}
Download it from [minecraft.net](https://minecraft.net) or use the Minecraft launcher.
snapshot:
title: "Minecraft {version} Snapshot Available"
body: |
# Minecraft {version} Snapshot
A new snapshot is available for testing!
**Version:** {version}
**Released:** {release_date}
Try it in the launcher with the development profiles.
default:
title: "Minecraft {version} ({type})"
body: |
# Minecraft {version}
A new {type} build has been released!
**Version:** {version}
**Released:** {release_date}
```
3. **Save the wiki page**
- The bot will automatically load this config on startup
- Changes to the wiki are refreshed every 30 minutes
## Configuration Format
The wiki page must be valid YAML with this structure:
```yaml
release_type:
title: "Post title with {placeholders}"
body: |
Multi-line post body
with {placeholders}
```
### Available Placeholders
In both title and body, you can use:
- `{version}` - The Minecraft version (e.g., "1.21")
- `{release_date}` - The formatted release date (e.g., "June 13, 2024")
- `{type}` - The release type (e.g., "release", "snapshot")
### Release Types
Create sections for each release type you want custom posts for:
- `release` - Final releases
- `snapshot` - Snapshots
- `old_beta` - Old beta versions
- `old_alpha` - Old alpha versions
- `default` - Fallback for any unconfigured type
## Examples
### Example 1: Simple Setup (Releases Only)
```yaml
release:
title: "Minecraft {version} is Out!"
body: |
{version} is available now!
Get it at minecraft.net
```
### Example 2: Different Templates per Type
```yaml
release:
title: "🎉 Minecraft {version} Released"
body: |
# Minecraft {version}
The full release is here!
**Download:** [minecraft.net](https://minecraft.net)
**Date:** {release_date}
snapshot:
title: "📸 Minecraft {version} Snapshot"
body: |
# Snapshot Available
Test drive {version} before the official release!
**Date:** {release_date}
old_beta:
title: "[LEGACY] Minecraft {version} Beta"
body: |
Archive: Minecraft {version} beta
Released: {release_date}
```
### Example 3: Minimal Setup with Default
```yaml
default:
title: "Minecraft {version}"
body: |
New {type}: {version}
{release_date}
```
## Features
- **Multiple Configurations:** Different posts for releases, snapshots, legacy versions, etc.
- **Auto-Refresh:** Wiki changes are loaded every 30 minutes automatically
- **Fallback:** If a release type isn't configured, it uses the `default` config
- **No Restart Required:** Changes take effect on the next check cycle
- **Flexible:** Use any text, formatting, and placeholders you want
## Troubleshooting
### Configuration Not Loading
1. Check the bot logs:
```bash
docker-compose logs minecraft-bot
```
2. Verify the wiki page name is exactly `minecraft_update_bot`
3. Ensure the YAML is valid (use a YAML validator if needed)
4. Check that the bot has permissions to read the wiki
### Posts Not Formatting Correctly
- Verify placeholder names are correct: `{version}`, `{release_date}`, `{type}`
- Check that the YAML syntax is correct (indentation matters!)
- Use the pipe `|` for multi-line bodies
### Reverting to Default
If the wiki page is empty or invalid, the bot will use embedded defaults:
```yaml
release:
title: "Minecraft {version} Released!"
body: "# Minecraft {version}\n\nA new version is available!\n\n**Version:** {version}\n**Released:** {release_date}\n\nGet it at [minecraft.net](https://minecraft.net)"
```
## Advanced: Testing Your Configuration
To test without posting to your subreddit:
1. View the bot logs:
```bash
docker-compose logs -f
```
2. Watch for the line:
```
[WIKI_CONFIG] ✓ Loaded X configuration(s)
```
3. This confirms your configs were loaded successfully
4. When a new version is posted, you'll see:
```
[BOT] ✓ Posted Minecraft X.X (release_type)
```
## Wiki Permissions
Make sure the bot account:
- Has **write** permission to edit the subreddit
- Can access the wiki pages
- Has the right to post to the subreddit
If you get permission errors, add the bot account as a moderator with wiki permissions.
+46
View File
@@ -0,0 +1,46 @@
import os
# Reddit Configuration
# Can be set via environment variables or directly here
REDDIT_CLIENT_ID = os.getenv("REDDIT_CLIENT_ID", "YOUR_CLIENT_ID")
REDDIT_CLIENT_SECRET = os.getenv("REDDIT_CLIENT_SECRET", "YOUR_CLIENT_SECRET")
REDDIT_USER_AGENT = os.getenv("REDDIT_USER_AGENT", "MinecraftUpdateBot/1.0")
REDDIT_USERNAME = os.getenv("REDDIT_USERNAME", "YOUR_USERNAME")
REDDIT_PASSWORD = os.getenv("REDDIT_PASSWORD", "YOUR_PASSWORD")
# Subreddit to post to
SUBREDDIT = os.getenv("SUBREDDIT", "YOUR_SUBREDDIT")
# Minecraft release types to check for
# Options: "release", "snapshot", "old_beta", "old_alpha"
# Can set via env as comma-separated: RELEASE_TYPES=release,snapshot
release_types_env = os.getenv("RELEASE_TYPES", "release")
RELEASE_TYPES = [t.strip() for t in release_types_env.split(",")]
# Post templates
# NOTE: Post titles and bodies are now fetched from the subreddit wiki page "minecraft_update_bot"
# See DOCKER.md or README.md for how to set up the wiki page with YAML configuration
#
# Wiki page format (YAML):
# release:
# title: "Minecraft {version} Released!"
# body: |
# # Minecraft {version} Released
# A new version is available!
# **Version:** {version}
# **Released:** {release_date}
#
# snapshot:
# title: "Minecraft {version} Snapshot"
# body: |
# # Minecraft {version} Snapshot
# Available for testing!
# **Version:** {version}
# **Released:** {release_date}
#
# Placeholders: {version}, {release_date}, {type}
# Check interval in seconds (default: 3600 = 1 hour)
# Can be set via environment variable: CHECK_INTERVAL=3600
CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL", "3600"))
+32
View File
@@ -0,0 +1,32 @@
services:
minecraft-bot:
build: .
image: minecraft-update-bot:latest
container_name: minecraft-bot
environment:
# Reddit Configuration
REDDIT_CLIENT_ID: ${REDDIT_CLIENT_ID}
REDDIT_CLIENT_SECRET: ${REDDIT_CLIENT_SECRET}
REDDIT_USER_AGENT: ${REDDIT_USER_AGENT:-MinecraftUpdateBot/1.0}
REDDIT_USERNAME: ${REDDIT_USERNAME}
REDDIT_PASSWORD: ${REDDIT_PASSWORD}
# Bot Configuration
SUBREDDIT: ${SUBREDDIT}
RELEASE_TYPES: ${RELEASE_TYPES:-release}
CHECK_INTERVAL: ${CHECK_INTERVAL:-3600}
volumes:
# Persist the database of posted versions
- minecraft-bot-db:/app/DB
# Optional: mount config.py for easy editing
# - ./config.py:/app/config.py
restart: unless-stopped
# Optional: resource limits
# deploy:
# resources:
# limits:
# cpus: '0.5'
# memory: 256M
volumes:
minecraft-bot-db:
driver: local
+194
View File
@@ -0,0 +1,194 @@
"""
Minecraft Update Bot - Detects new Minecraft releases and posts to Reddit
"""
import praw
import json
import time
import os
import threading
from datetime import datetime
import config
from minecraft_checker import get_latest_releases, parse_release_date
from wiki_config import WikiConfig
from update_checker import start_update_checker
# Bot version (increment when making changes)
BOT_VERSION = "1.0"
# Database file to track posted versions
DB_DIR = os.path.join(os.path.dirname(__file__), 'DB')
POSTED_VERSIONS_FILE = os.path.join(DB_DIR, 'posted_versions.json')
# Wiki configuration manager (initialized on startup)
wiki_config = WikiConfig()
def init_db():
"""Initialize the database directory and posted versions file."""
os.makedirs(DB_DIR, exist_ok=True)
if not os.path.exists(POSTED_VERSIONS_FILE):
with open(POSTED_VERSIONS_FILE, 'w') as f:
json.dump({"posted": []}, f, indent=2)
def load_posted_versions():
"""Load the list of already-posted version IDs."""
try:
with open(POSTED_VERSIONS_FILE, 'r') as f:
data = json.load(f)
return set(data.get("posted", []))
except:
return set()
def save_posted_version(version_id):
"""Save a version ID as posted."""
try:
with open(POSTED_VERSIONS_FILE, 'r') as f:
data = json.load(f)
except:
data = {"posted": []}
if version_id not in data.get("posted", []):
data.setdefault("posted", []).append(version_id)
with open(POSTED_VERSIONS_FILE, 'w') as f:
json.dump(data, f, indent=2)
def make_reddit_connection():
"""Create a Reddit API connection using PRAW."""
try:
reddit = praw.Reddit(
client_id=config.REDDIT_CLIENT_ID,
client_secret=config.REDDIT_CLIENT_SECRET,
user_agent=config.REDDIT_USER_AGENT,
username=config.REDDIT_USERNAME,
password=config.REDDIT_PASSWORD
)
# Test the connection
reddit.user.me()
print("[BOT] ✓ Successfully connected to Reddit")
return reddit
except Exception as e:
print(f"[BOT] ✗ Failed to connect to Reddit: {e}")
return None
def post_to_subreddit(reddit, version_info):
"""
Post a message about a new Minecraft release to the subreddit.
Args:
reddit: PRAW Reddit instance
version_info: Dict with keys: id, type, releaseTime
Returns:
True if successful, False otherwise
"""
try:
version_id = version_info["id"]
release_type = version_info["type"]
release_date = parse_release_date(version_info["releaseTime"])
# Get title and body from wiki config
title, body = wiki_config.format_post(release_type, version_id, release_date)
subreddit = reddit.subreddit(config.SUBREDDIT)
submission = subreddit.submit(title, selftext=body)
print(f"[BOT] ✓ Posted Minecraft {version_id} ({release_type})")
print(f" URL: {submission.url}")
return True
except Exception as e:
print(f"[BOT] ✗ Failed to post: {e}")
return False
def check_for_updates(reddit):
"""
Check for new Minecraft releases and post if any are new.
Args:
reddit: PRAW Reddit instance
"""
try:
posted_versions = load_posted_versions()
latest_releases = get_latest_releases(config.RELEASE_TYPES)
for version_info in latest_releases:
version_id = version_info["id"]
if version_id not in posted_versions:
print(f"[BOT] New release found: {version_id}")
if post_to_subreddit(reddit, version_info):
save_posted_version(version_id)
else:
print(f"[BOT] Version {version_id} already posted, skipping")
except Exception as e:
print(f"[BOT] ✗ Error checking for updates: {e}")
def bot_thread(reddit):
"""
Background thread that periodically checks for Minecraft updates.
Args:
reddit: PRAW Reddit instance
"""
print(f"[BOT] Started update checker (checking every {config.CHECK_INTERVAL} seconds)")
while True:
try:
check_for_updates(reddit)
except Exception as e:
print(f"[BOT] ✗ Error in bot thread: {e}")
time.sleep(config.CHECK_INTERVAL)
def start_bot():
"""Start the Minecraft Update Bot."""
print("[BOT] Starting Minecraft Update Bot...")
# Initialize database
init_db()
# Connect to Reddit
reddit = make_reddit_connection()
if not reddit:
print("[BOT] ✗ Cannot start bot without Reddit connection")
return
# Initialize wiki configuration
wiki_config.init(reddit, config.SUBREDDIT)
wiki_config.fetch_from_wiki()
# Start update checker
start_update_checker(reddit, config.SUBREDDIT, "MinecraftUpdateBot", BOT_VERSION)
# Do an initial check
check_for_updates(reddit)
# Start background thread
thread = threading.Thread(target=bot_thread, args=(reddit,), daemon=True)
thread.start()
print("[BOT] ✓ Bot is running")
# Keep main thread alive
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n[BOT] Shutting down...")
if __name__ == "__main__":
start_bot()
+98
View File
@@ -0,0 +1,98 @@
"""
Minecraft version checker - fetches latest releases from Mojang's launcher manifest
"""
import requests
import json
from datetime import datetime
MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"
TIMEOUT = 10
def get_minecraft_versions():
"""
Fetch all Minecraft versions from the official launcher manifest.
Returns dict with version info categorized by type.
"""
try:
response = requests.get(MANIFEST_URL, timeout=TIMEOUT)
if response.status_code == 200:
return response.json()
except Exception as e:
print(f"[MINECRAFT_CHECKER] Error fetching manifest: {e}")
return None
def get_latest_releases(release_types=None):
"""
Get latest releases of specified types.
Args:
release_types: List of types like ["release", "snapshot"]
If None, returns only the latest release
Returns:
List of dicts with version info
"""
if release_types is None:
release_types = ["release"]
manifest = get_minecraft_versions()
if not manifest:
return []
latest_versions = []
# Get the latest version of each type
for release_type in release_types:
if release_type in manifest["latest"]:
version_id = manifest["latest"][release_type]
# Find the full version info
for version in manifest["versions"]:
if version["id"] == version_id:
latest_versions.append(version)
break
return latest_versions
def get_all_versions_of_type(release_type):
"""
Get all versions of a specific type.
Args:
release_type: Type like "release", "snapshot", etc.
Returns:
List of version dicts
"""
manifest = get_minecraft_versions()
if not manifest:
return []
versions = []
for version in manifest["versions"]:
if version["type"] == release_type:
versions.append(version)
return versions
def parse_release_date(iso_date_str):
"""
Parse ISO format date string to readable format.
Args:
iso_date_str: Date in format "2024-06-13T11:27:32+00:00"
Returns:
Formatted date string like "June 13, 2024"
"""
try:
# Parse ISO format
dt = datetime.fromisoformat(iso_date_str.replace("+00:00", ""))
return dt.strftime("%B %d, %Y")
except:
return iso_date_str
+3
View File
@@ -0,0 +1,3 @@
praw==7.7.0
requests==2.31.0
PyYAML==6.0.1
+118
View File
@@ -0,0 +1,118 @@
"""
Test script to verify bot setup before running
"""
import sys
import config
from minecraft_checker import get_latest_releases, parse_release_date
def check_config():
"""Verify configuration is filled in."""
print("[TEST] Checking configuration...")
required = [
"REDDIT_CLIENT_ID",
"REDDIT_CLIENT_SECRET",
"REDDIT_USERNAME",
"REDDIT_PASSWORD",
"SUBREDDIT"
]
missing = []
for key in required:
value = getattr(config, key, None)
if not value or value.startswith("YOUR_"):
missing.append(key)
else:
print(f"{key}: configured")
if missing:
print(f"\n✗ Missing configuration:")
for key in missing:
print(f" - {key}")
return False
return True
def check_minecraft_api():
"""Test fetching from Minecraft API."""
print("\n[TEST] Checking Minecraft API...")
try:
latest = get_latest_releases()
if not latest:
print(" ✗ Failed to fetch latest releases")
return False
print(f" ✓ Connected to Minecraft launcher manifest")
for version in latest:
release_date = parse_release_date(version["releaseTime"])
print(f" - {version['id']} ({version['type']}) - {release_date}")
return True
except Exception as e:
print(f" ✗ Error: {e}")
return False
def check_reddit_connection():
"""Test Reddit API connection."""
print("\n[TEST] Checking Reddit connection...")
try:
import praw
except ImportError:
print(" ✗ praw not installed. Run: pip install -r requirements.txt")
return False
try:
reddit = praw.Reddit(
client_id=config.REDDIT_CLIENT_ID,
client_secret=config.REDDIT_CLIENT_SECRET,
user_agent=config.REDDIT_USER_AGENT,
username=config.REDDIT_USERNAME,
password=config.REDDIT_PASSWORD
)
user = reddit.user.me()
print(f" ✓ Connected as u/{user.name}")
# Check if we can access the subreddit
subreddit = reddit.subreddit(config.SUBREDDIT)
print(f" ✓ Can access r/{config.SUBREDDIT} (subscribers: {subreddit.subscribers:,})")
# Check if we can post
try:
subreddit.mod.modqueue()
print(f" ✓ Have moderator access to r/{config.SUBREDDIT}")
except:
print(f" ⚠ No moderator access (must be able to post normally)")
return True
except Exception as e:
print(f" ✗ Reddit connection failed: {e}")
return False
def main():
print("=" * 50)
print("Minecraft Update Bot - Setup Test")
print("=" * 50)
config_ok = check_config()
api_ok = check_minecraft_api()
reddit_ok = check_reddit_connection()
print("\n" + "=" * 50)
if config_ok and api_ok and reddit_ok:
print("✓ All checks passed! Ready to run: python main.py")
return 0
else:
print("✗ Some checks failed. See above for details.")
return 1
if __name__ == "__main__":
sys.exit(main())
+109
View File
@@ -0,0 +1,109 @@
"""
Update Checker - Periodically checks for bot updates from update server
"""
import requests
import threading
import time
import os
from datetime import datetime
UPDATE_CHECK_INTERVAL = 3600 # Check every hour
UPDATE_COOLDOWN = 86400 # Don't send mail more than once per 24 hours
LAST_UPDATE_FILE = os.path.join(os.path.dirname(__file__), 'DB', '.last_update_check.txt')
UPDATE_SERVER_URL = "https://updts.slfhstd.uk/api/version"
def get_latest_version(bot_name):
"""Fetch latest version info from update server."""
try:
response = requests.get(f'{UPDATE_SERVER_URL}/{bot_name}', timeout=10)
if response.status_code == 200:
return response.json()
except Exception as e:
print(f"[UPDATE_CHECKER] Error fetching version: {e}")
return None
def send_update_modmail(reddit, subreddit_name, bot_name, current_version, available_version, changelog_url):
"""Send modmail to subreddit modteam about available update."""
try:
subject = f"🤖 {bot_name} Update Available (v{available_version})"
message = f"""Hello,
An update is available for {bot_name}!
**Current Version:** {current_version}
**Available Version:** {available_version}
Changelog: {changelog_url}
Please visit the update server for installation instructions.
---
This is an automated message from the Update Checker."""
data = {
"subject": subject,
"text": message,
"to": f"/r/{subreddit_name}",
}
reddit.post("api/compose/", data=data)
print(f"[UPDATE_CHECKER] Sent update notification to r/{subreddit_name}")
return True
except Exception as e:
print(f"[UPDATE_CHECKER] Error sending modmail: {e}")
return False
def should_send_update_mail():
"""Check if enough time has passed since last update mail."""
if not os.path.exists(LAST_UPDATE_FILE):
return True
try:
with open(LAST_UPDATE_FILE, 'r') as f:
last_check = float(f.read().strip())
return (time.time() - last_check) >= UPDATE_COOLDOWN
except:
return True
def mark_update_mailed():
"""Record when update mail was sent."""
os.makedirs(os.path.dirname(LAST_UPDATE_FILE), exist_ok=True)
with open(LAST_UPDATE_FILE, 'w') as f:
f.write(str(time.time()))
def update_checker_thread(reddit, subreddit_name, bot_name, current_version):
"""Background thread that checks for updates periodically."""
print(f"[UPDATE_CHECKER] Started for {bot_name} v{current_version}")
while True:
try:
version_info = get_latest_version(bot_name)
if version_info:
available_version = version_info.get('version')
changelog_url = version_info.get('changelog_url', '#')
if available_version != current_version and should_send_update_mail():
print(f"[UPDATE_CHECKER] Update found: {current_version} -> {available_version}")
if send_update_modmail(reddit, subreddit_name, bot_name, current_version, available_version, changelog_url):
mark_update_mailed()
except Exception as e:
print(f"[UPDATE_CHECKER] Error in update checker thread: {e}")
time.sleep(UPDATE_CHECK_INTERVAL)
def start_update_checker(reddit, subreddit_name, bot_name, bot_version):
"""Start the update checker in a background thread."""
thread = threading.Thread(
target=update_checker_thread,
args=(reddit, subreddit_name, bot_name, bot_version),
daemon=True
)
thread.start()
+207
View File
@@ -0,0 +1,207 @@
"""
Wiki Configuration Manager - Fetches post templates and titles from subreddit wiki
"""
import time
import json
import re
import yaml
from typing import Dict, Optional, Tuple
# Cache TTL in seconds (refresh wiki config every 30 minutes)
WIKI_CACHE_TTL = 1800
WIKI_PAGE_NAME = "minecraft_update_bot"
class WikiConfig:
"""Manages post configurations loaded from subreddit wiki."""
def __init__(self):
self.configs: Dict[str, Dict[str, str]] = {}
self.default_config: Dict[str, str] = {"title": "", "body": ""}
self.last_updated = 0
self.reddit = None
self.subreddit_name = None
def init(self, reddit, subreddit_name: str):
"""Initialize the wiki config manager."""
self.reddit = reddit
self.subreddit_name = subreddit_name
def should_refresh(self) -> bool:
"""Check if cache has expired."""
return (time.time() - self.last_updated) >= WIKI_CACHE_TTL
def fetch_from_wiki(self) -> bool:
"""
Fetch post configurations from subreddit wiki.
Wiki page format (YAML):
```yaml
release:
title: "Minecraft {version} Released!"
body: |
# Minecraft {version} Released
A new version of Minecraft is available!
**Version:** {version}
**Date:** {release_date}
Get it now at [minecraft.net](https://minecraft.net)
snapshot:
title: "Minecraft {version} Snapshot Available"
body: |
# Minecraft {version} Snapshot
A new snapshot is available for testing!
**Version:** {version}
**Date:** {release_date}
Try it in the launcher!
default:
title: "Minecraft {version} ({type})"
body: |
New {type}: {version}
Released: {release_date}
```
Returns:
True if successful, False otherwise
"""
try:
subreddit = self.reddit.subreddit(self.subreddit_name)
wiki_page = subreddit.wiki[WIKI_PAGE_NAME]
content = wiki_page.content_md
self.configs = self._parse_yaml_content(content)
self.last_updated = time.time()
if self.configs:
print(f"[WIKI_CONFIG] ✓ Loaded {len(self.configs)} configuration(s)")
for release_type in self.configs:
print(f" - {release_type}")
return True
else:
print("[WIKI_CONFIG] ⚠ No configurations found in wiki")
return False
except Exception as e:
print(f"[WIKI_CONFIG] ✗ Error fetching wiki: {e}")
return False
def _parse_yaml_content(self, content: str) -> Dict[str, Dict[str, str]]:
"""
Parse YAML content into configuration dict.
Expected format:
```yaml
release:
title: "Minecraft {version} Released!"
body: |
Multi-line post body
with {placeholders}
snapshot:
title: "Minecraft {version} Snapshot"
body: |
Snapshot post body
```
Returns:
Dict mapping release_type -> {"title": str, "body": str}
"""
try:
data = yaml.safe_load(content)
if not isinstance(data, dict):
print("[WIKI_CONFIG] ✗ Wiki content is not valid YAML dictionary")
return {}
configs = {}
for release_type, config in data.items():
if isinstance(config, dict):
if "title" in config and "body" in config:
configs[release_type.lower()] = {
"title": str(config["title"]),
"body": str(config["body"])
}
else:
print(f"[WIKI_CONFIG] ⚠ Missing 'title' or 'body' for {release_type}")
return configs
except yaml.YAMLError as e:
print(f"[WIKI_CONFIG] ✗ YAML parsing error: {e}")
return {}
def get_config(self, release_type: str, refresh: bool = False) -> Tuple[str, str]:
"""
Get title and body template for a release type.
Args:
release_type: Type like "release" or "snapshot"
refresh: Force refresh from wiki
Returns:
Tuple of (title_template, body_template)
"""
# Refresh if needed
if refresh or self.should_refresh() or not self.configs:
self.fetch_from_wiki()
# Get config for this type, fall back to any available config
if release_type in self.configs:
config = self.configs[release_type]
return config["title"], config["body"]
# Try generic "default" config
if "default" in self.configs:
config = self.configs["default"]
return config["title"], config["body"]
# Fall back to hardcoded defaults
default_title = "Minecraft {version} Released!"
default_body = """# Minecraft {version}
A new version is available!
**Version:** {version}
**Released:** {release_date}
Get it at [minecraft.net](https://minecraft.net)"""
return default_title, default_body
def format_post(self, release_type: str, version: str, release_date: str) -> Tuple[str, str]:
"""
Get formatted post title and body for a version.
Args:
release_type: Type like "release" or "snapshot"
version: Version string like "1.21"
release_date: Formatted date string
Returns:
Tuple of (formatted_title, formatted_body)
"""
title_template, body_template = self.get_config(release_type)
# Format with available placeholders
format_dict = {
"version": version,
"release_date": release_date,
"type": release_type
}
try:
formatted_title = title_template.format(**format_dict)
formatted_body = body_template.format(**format_dict)
return formatted_title, formatted_body
except KeyError as e:
print(f"[WIKI_CONFIG] ✗ Unknown placeholder in template: {e}")
# Fall back to defaults
formatted_title = f"Minecraft {version} ({release_type})"
formatted_body = f"New {release_type}: {version}\nReleased: {release_date}"
return formatted_title, formatted_body