feat: add URL title preview plugin

Event-driven plugin that auto-fetches page titles for URLs posted in
channel messages. HEAD-then-GET via SOCKS5 pool, og:title priority,
cooldown dedup, !-suppression, binary/host filtering. 52 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
user
2026-02-17 21:57:00 +01:00
parent 7606280358
commit 8cabe0f8e8
4 changed files with 825 additions and 1 deletions

View File

@@ -50,6 +50,7 @@ channels = ["#test"] # Channels to join on connect
plugins_dir = "plugins" # Plugin directory path
rate_limit = 2.0 # Max messages per second (default: 2.0)
rate_burst = 5 # Burst capacity (default: 5)
paste_threshold = 4 # Max lines before overflow to FlaskPaste (default: 4)
admins = [] # Hostmask patterns (fnmatch), IRCOPs auto-detected
timezone = "UTC" # Timezone for calendar reminders (IANA tz name)
@@ -880,3 +881,46 @@ url = "https://paste.mymx.me" # or set FLASKPASTE_URL env var
Auth: place client cert/key at `secrets/flaskpaste/derp.crt` and `derp.key`
for mTLS (bypasses PoW). Without them, PoW challenges are solved per request.
### URL Title Preview (urltitle)
Automatic URL title preview for channel messages. When a user posts a URL,
the bot fetches the page title and description and displays a one-line
preview. No commands -- event-driven only.
```
<alice> check out https://example.com/article
<derp> ↳ Article Title -- Description of the article...
```
Behavior:
- Automatically previews HTTP(S) URLs posted in channel messages
- Skips private messages, bot's own messages, and command messages (`!prefix`)
- URLs prefixed with `!` are suppressed: `!https://example.com` produces no preview
- HEAD-then-GET fetch strategy (checks Content-Type before downloading body)
- Skips non-HTML content types (images, PDFs, JSON, etc.)
- Skips binary file extensions (`.png`, `.jpg`, `.pdf`, `.zip`, etc.)
- Skips FlaskPaste URLs and configured ignore hosts
- Dedup: same URL only previewed once per cooldown window (5 min default)
- Max 3 URLs previewed per message (configurable)
- Title from `og:title` takes priority over `<title>` tag
- Description from `og:description` takes priority over `<meta name="description">`
- Title truncated at 200 chars, description at 150 chars
Output format:
```
↳ Page Title -- Description truncated to 150 chars...
↳ Page Title
```
Configuration (optional):
```toml
[urltitle]
cooldown = 300 # seconds before same URL previewed again
timeout = 10 # HTTP fetch timeout
max_urls = 3 # max URLs to preview per message
ignore_hosts = [] # additional hostnames to skip
```