Compare commits

10 Commits

Author SHA1 Message Date
slfhstd 5d61c3fea6 changelog 2026-03-11 21:06:04 +00:00
slfhstd a4bd8b9333 2.1 2026-03-11 21:01:49 +00:00
slfhstd ffaae146ac updated bot version 2026-03-11 20:51:11 +00:00
slfhstd b9ddeb5303 2026-03-11 18:25:51 +00:00
slfhstd 0e01f803b2 2026-03-11 18:21:06 +00:00
slfhstd e683449f17 2026-03-11 18:16:11 +00:00
slfhstd e47a28efae Further updates to fix permissions issues 2026-03-11 18:09:41 +00:00
slfhstd c605e00e95 docker build updates to improve security 2026-03-11 17:42:51 +00:00
slfhstd ea3eb899e7 updates to correct env file 2026-03-11 17:34:09 +00:00
slfhstd 2fa007761c gitignore 2026-03-11 17:33:29 +00:00
11 changed files with 327 additions and 14 deletions
-8
View File
@@ -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
View File
@@ -1,2 +1,2 @@
modtestposts_config.yaml
.env
prod.env
+61 -2
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
1773261500.1906805
+103
View File
@@ -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
+8
View File
@@ -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"]
+33 -1
View File
@@ -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.
+11 -1
View File
@@ -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
View File
@@ -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
View File
@@ -1,2 +1,3 @@
praw
PyYAML
requests
+105
View File
@@ -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()