initial
This commit is contained in:
@@ -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
@@ -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
|
||||
@@ -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
@@ -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"]
|
||||
@@ -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)
|
||||
@@ -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
@@ -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.
|
||||
@@ -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"))
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
praw==7.7.0
|
||||
requests==2.31.0
|
||||
PyYAML==6.0.1
|
||||
+118
@@ -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())
|
||||
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user