Compare commits
10 Commits
4f9243ff59
...
v2.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d61c3fea6 | |||
| a4bd8b9333 | |||
| ffaae146ac | |||
| b9ddeb5303 | |||
| 0e01f803b2 | |||
| e683449f17 | |||
| e47a28efae | |||
| c605e00e95 | |||
| ea3eb899e7 | |||
| 2fa007761c |
@@ -1,8 +0,0 @@
|
||||
# Example environment variables for TestPostsBot
|
||||
REDDIT_CLIENT_ID=vzdZ6l330b_l7UWPoZ7tcQ
|
||||
REDDIT_CLIENT_SECRET=Zh7gVG1glv8BNxlGFBHU1UAJnDjo9w
|
||||
REDDIT_USERNAME=MinecraftHelpModBot
|
||||
REDDIT_PASSWORD=t%FdW9$&CAdhQo6@Zz9v
|
||||
REDDIT_USER_AGENT=TestPostsBot/0.1 on {REDDIT_USERNAME}
|
||||
SUBREDDIT=MinecraftHelpModCopy
|
||||
WIKI_PAGE=testpostsbot_config
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
modtestposts_config.yaml
|
||||
.env
|
||||
prod.env
|
||||
|
||||
+61
-2
@@ -1,6 +1,65 @@
|
||||
# Changelog - TestPostsBot V2
|
||||
# Changelog - TestPostsBot
|
||||
|
||||
## Overview
|
||||
## Version 2.1.0 - Update Checker System
|
||||
|
||||
### New Features
|
||||
|
||||
#### 1. Update Checker System
|
||||
- Bot now periodically checks for new versions from a centralized update server
|
||||
- Automatically sends modmail notifications to subreddit modteam when updates are available
|
||||
- Includes changelog URL in notification for easy reference
|
||||
- 24-hour cooldown prevents spam (max 1 notification per day)
|
||||
- Runs as a background daemon thread alongside main bot operations
|
||||
- Tracks last update notification timestamp to file for persistence across restarts
|
||||
|
||||
#### 2. Version Management
|
||||
- Bot version is now centralized at the top of `bot.py` in a dedicated section
|
||||
- `BOT_VERSION` and `BOT_NAME` constants used throughout the bot
|
||||
- Easy single-location reference for version updates
|
||||
- Version automatically included in update checker calls and user agent string
|
||||
|
||||
#### 3. Infrastructure
|
||||
- Created separate `update_server/` project for version management service
|
||||
- Flask-based REST API serving version information via `/api/version/<bot_name>`
|
||||
- `versions.json` file stores version details for all bots
|
||||
- Docker support with Dockerfile for containerized deployment
|
||||
- Includes comprehensive README with deployment options
|
||||
|
||||
### New Files
|
||||
- **update_checker.py** - Background module that checks for and notifies about updates
|
||||
|
||||
### Modified Files
|
||||
|
||||
#### bot.py
|
||||
- Added version constants section at top for easy reference
|
||||
- Added import for `update_checker` module
|
||||
- Added `start_update_checker()` call in `main()` function
|
||||
- User agent now uses version constants: `{BOT_NAME}/{BOT_VERSION}`
|
||||
|
||||
#### requirements.txt
|
||||
- Added `requests` library for HTTP requests to update server
|
||||
|
||||
#### docker-compose.yml
|
||||
|
||||
- Volume mounts for persistent `versions.json` and log files
|
||||
|
||||
### Configuration
|
||||
Update checker timing is configurable in `update_checker.py`:
|
||||
- `UPDATE_CHECK_INTERVAL = 60` seconds (check frequency)
|
||||
- `UPDATE_COOLDOWN = 86400` seconds (24 hours between notifications)
|
||||
|
||||
### How It Works
|
||||
1. On startup, bot creates background thread running update checker
|
||||
2. Every hour, thread polls `https://updts.slfhstd.uk/api/version/TestPostsBot`
|
||||
3. If version differs from current `BOT_VERSION`, and 24 hours have passed since last notification, sends modmail
|
||||
4. Modmail includes current version, available version, and changelog URL
|
||||
5. Last notification timestamp stored in `DB/.last_update_check.txt`
|
||||
|
||||
---
|
||||
|
||||
## Version 2.0.0 - Trigger-Based Redesign
|
||||
|
||||
### Overview
|
||||
Complete refactor of TestPostsBot to be a continuously running, trigger-based posting bot that responds to moderator commands via chat messages.
|
||||
|
||||
## Major Features Added
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1773261500.1906805
|
||||
@@ -0,0 +1,103 @@
|
||||
o9q8xhp
|
||||
o9q9ibz
|
||||
o9q9jvz
|
||||
o9q9pcn
|
||||
o9qawn5
|
||||
o9qayrb
|
||||
o9qb2tq
|
||||
o9qdm6v
|
||||
o9qdpsi
|
||||
o9qdzec
|
||||
o9qef38
|
||||
o9qff1w
|
||||
o9qfgmz
|
||||
o9qfyl7
|
||||
o9qgkgs
|
||||
o9qh4nu
|
||||
o9qh5uk
|
||||
o9qhh0b
|
||||
o9qiha2
|
||||
o9qikd8
|
||||
o9qijyj
|
||||
o9qj7de
|
||||
o9qja83
|
||||
o9qjbc5
|
||||
o9qkdi6
|
||||
o9qkfdj
|
||||
o9qkjp9
|
||||
o9qm2qu
|
||||
o9qm3gf
|
||||
o9qm94g
|
||||
o9qmr6t
|
||||
o9qmt9p
|
||||
o9qmylo
|
||||
o9qnrak
|
||||
o9qnsm6
|
||||
o9qo16b
|
||||
o9qot2n
|
||||
o9qou0k
|
||||
o9qp1vq
|
||||
o9qpkrh
|
||||
o9qpm3p
|
||||
o9qpuks
|
||||
o9qqe50
|
||||
o9qqfrd
|
||||
o9qr1ee
|
||||
o9qrb06
|
||||
o9qrcv4
|
||||
o9qs54o
|
||||
o9qs6i6
|
||||
o9qsbua
|
||||
o9qst9s
|
||||
o9qvs45
|
||||
o9qwlty
|
||||
o9qxih7
|
||||
o9qxjm9
|
||||
o9qzqpn
|
||||
o9qzsej
|
||||
o9qzzma
|
||||
o9r00bc
|
||||
o9r093d
|
||||
o9r111o
|
||||
o9r6uje
|
||||
o9r6w0g
|
||||
o9r7s7b
|
||||
o9r8j1q
|
||||
o9r8k85
|
||||
o9r8qtp
|
||||
o9r9gzr
|
||||
o9r9iud
|
||||
o9r9vwe
|
||||
o9r9xlc
|
||||
o9raamy
|
||||
o9raaid
|
||||
o9racr4
|
||||
o9raepg
|
||||
o9rbepq
|
||||
o9rd2ve
|
||||
o9rd34l
|
||||
o9rd3dx
|
||||
o9rd3q8
|
||||
o9rd44n
|
||||
o9rd5ai
|
||||
o9rd5ef
|
||||
o9rd5n2
|
||||
o9rd5i6
|
||||
o9rd5vt
|
||||
o9rd60r
|
||||
o9rd5rs
|
||||
o9rdgs4
|
||||
o9rdqyg
|
||||
5k3eari
|
||||
5k3j00u
|
||||
5k3nm4r
|
||||
5k3p083
|
||||
5k3pd9q
|
||||
5k3pqsx
|
||||
o9vy9h2
|
||||
o9wez6a
|
||||
o9wf1nr
|
||||
o9wf3as
|
||||
o9x1j7v
|
||||
o9x1lp2
|
||||
o9x1n77
|
||||
@@ -1,9 +1,17 @@
|
||||
# Dockerfile for Reddit Test Posts Bot
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Create a non-root user for running the bot
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY bot.py .
|
||||
COPY config.py .
|
||||
COPY update_checker.py .
|
||||
# Create DB directory
|
||||
RUN mkdir -p /app/DB
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
CMD ["python", "bot.py"]
|
||||
|
||||
@@ -79,10 +79,35 @@ docker run \
|
||||
### Docker Compose
|
||||
|
||||
```bash
|
||||
# Edit docker-compose.yml with your credentials
|
||||
# Edit prod.env with your credentials, then:
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
#### Security: Running as Non-Root User
|
||||
|
||||
By default, the container runs as a non-root user (UID 1000, GID 1000) for improved security. You can customize the user and group IDs by setting environment variables before running:
|
||||
|
||||
```bash
|
||||
# Use specific user and group IDs
|
||||
USER_ID=1001 GROUP_ID=1001 docker-compose up
|
||||
|
||||
# Use default (1000:1000)
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
The user and group IDs can also be specified in a `.env` file:
|
||||
|
||||
```env
|
||||
USER_ID=1001
|
||||
GROUP_ID=1001
|
||||
REDDIT_CLIENT_ID=your_client_id
|
||||
REDDIT_CLIENT_SECRET=your_client_secret
|
||||
REDDIT_USERNAME=bot_username
|
||||
REDDIT_PASSWORD=bot_password
|
||||
SUBREDDIT=your_subreddit
|
||||
WIKI_PAGE=testpostsbot_config
|
||||
```
|
||||
|
||||
### Standalone
|
||||
|
||||
```bash
|
||||
@@ -96,3 +121,10 @@ python bot.py
|
||||
- The config is fetched fresh for each trigger, so you can update the wiki while the bot is running.
|
||||
- Only the first matching trigger per message is processed.
|
||||
- All processed messages are tracked in `DB/chat_wiki_requests.txt` to avoid duplicate processing.
|
||||
|
||||
## Security
|
||||
|
||||
- **Non-Root Execution:** The Docker container runs as a non-root user (UID 1000, GID 1000) by default to minimize security risks. This can be customized via `USER_ID` and `GROUP_ID` environment variables.
|
||||
- **Credentials:** Store Reddit API credentials in environment variables or `.env` files, never hardcode them.
|
||||
- **Moderator-Only Commands:** All bot triggers and commands require the sender to be a moderator of the target subreddit.
|
||||
- **DB Directory:** Processed message IDs are stored in a local `DB/` directory to prevent duplicate processing and maintain stateful operation.
|
||||
|
||||
@@ -6,12 +6,18 @@ import time
|
||||
import os
|
||||
import threading
|
||||
from config import fetch_config_from_wiki, validate_config_from_wiki, get_trigger_posts
|
||||
from update_checker import start_update_checker
|
||||
|
||||
# ==================== VERSION ====================
|
||||
BOT_VERSION = "2.1.0"
|
||||
BOT_NAME = "TestPostsBot"
|
||||
# ==================================================
|
||||
|
||||
REDDIT_CLIENT_ID = os.environ.get('REDDIT_CLIENT_ID')
|
||||
REDDIT_CLIENT_SECRET = os.environ.get('REDDIT_CLIENT_SECRET')
|
||||
REDDIT_USERNAME = os.environ.get('REDDIT_USERNAME')
|
||||
REDDIT_PASSWORD = os.environ.get('REDDIT_PASSWORD')
|
||||
REDDIT_USER_AGENT = os.environ.get('REDDIT_USER_AGENT', f'TestPostsBot/0.1 on {REDDIT_USERNAME}')
|
||||
REDDIT_USER_AGENT = os.environ.get('REDDIT_USER_AGENT', f'{BOT_NAME}/{BOT_VERSION} on {REDDIT_USERNAME}')
|
||||
|
||||
SUBREDDIT = os.environ.get('SUBREDDIT')
|
||||
WIKI_PAGE = os.environ.get('WIKI_PAGE', 'testpostsbot_config')
|
||||
@@ -180,6 +186,10 @@ def main():
|
||||
chat_thread.start()
|
||||
print("[STARTUP] Chat message watcher started.")
|
||||
|
||||
# Start update checker in background thread
|
||||
start_update_checker(reddit, SUBREDDIT, BOT_NAME, BOT_VERSION)
|
||||
print("[STARTUP] Update checker started.")
|
||||
|
||||
# Keep main thread alive
|
||||
try:
|
||||
while True:
|
||||
|
||||
+3
-1
@@ -2,5 +2,7 @@ services:
|
||||
testpostsbot:
|
||||
image: slfhstd.uk/slfhstd/testpostsbot:dev
|
||||
env_file:
|
||||
- .env
|
||||
- prod.env
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./DB:/app/DB
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
praw
|
||||
PyYAML
|
||||
requests
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import requests
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
UPDATE_CHECK_INTERVAL = 60 # Check every minute
|
||||
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')
|
||||
|
||||
|
||||
def get_latest_version(bot_name):
|
||||
"""Fetch latest version info from update server."""
|
||||
try:
|
||||
response = requests.get(f'https://updts.slfhstd.uk/api/version/{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."""
|
||||
|
||||
# Build payload for the compose API
|
||||
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()
|
||||
Reference in New Issue
Block a user