Initial: Link analyzer bot for #remora channel
This commit is contained in:
10
.env.example
Normal file
10
.env.example
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Discord Bot Token (from https://discord.com/developers/applications)
|
||||||
|
DISCORD_BOT_TOKEN=your_token_here
|
||||||
|
|
||||||
|
# Tududi API
|
||||||
|
TUDUDI_API_URL=https://todo.dilain.com/api/v1
|
||||||
|
TUDUDI_API_KEY=tt_your_key_here
|
||||||
|
|
||||||
|
# OpenClaw Gateway (optional, for future AI analysis)
|
||||||
|
OPENCLAW_GATEWAY=http://127.0.0.1:18789
|
||||||
|
OPENCLAW_GATEWAY_TOKEN=your_gateway_token
|
||||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
.DS_Store
|
||||||
|
tracker.json
|
||||||
|
venv/
|
||||||
116
README.md
Normal file
116
README.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# Link Analyzer Bot - #remora
|
||||||
|
|
||||||
|
Analyzes links posted in #remora channel in real-time. Fetches content, creates summaries, and adds to Tududi inbox.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Install dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install discord.py requests
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Get Discord Bot Token
|
||||||
|
|
||||||
|
If you don't have a bot token:
|
||||||
|
1. Go to https://discord.com/developers/applications
|
||||||
|
2. Click "New Application"
|
||||||
|
3. Name it "Link Analyzer" (or whatever)
|
||||||
|
4. Go to "Bot" tab → "Add Bot"
|
||||||
|
5. Copy the token
|
||||||
|
6. Under OAuth2 → Scopes, select: `bot`
|
||||||
|
7. Under Permissions, select: `Read Messages/View Channels`, `Send Messages`, `Read Message History`
|
||||||
|
8. Go to the generated URL and add bot to your server
|
||||||
|
|
||||||
|
### 3. Set environment variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export DISCORD_BOT_TOKEN="your_token_here"
|
||||||
|
export TUDUDI_API_URL="https://todo.dilain.com/api/v1"
|
||||||
|
export TUDUDI_API_KEY="tt_5e3ac7fc2bf5ae5162ebac5d1d66dcc2ff9d9d0ab343b9d3d4c5a7c439ef67f5"
|
||||||
|
export OPENCLAW_GATEWAY="http://127.0.0.1:18789"
|
||||||
|
export OPENCLAW_GATEWAY_TOKEN="your_gateway_token"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Run bot
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 bot.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Should see:
|
||||||
|
```
|
||||||
|
✅ Bot logged in as LinkAnalyzer#1234
|
||||||
|
📍 Watching channel #remora (1467557082583535729)
|
||||||
|
```
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
1. **Real-time monitoring** - Listens to all messages in #remora
|
||||||
|
2. **Link detection** - Extracts URLs from messages
|
||||||
|
3. **Content fetching** - Downloads and analyzes page content
|
||||||
|
4. **Type detection** - GitHub, Reddit, YouTube, TikTok, etc.
|
||||||
|
5. **Summary response** - Replies with format: `📌 **Type**: Title`
|
||||||
|
6. **Tududi integration** - Adds to inbox: `📌 Type: Title\n🔗 URL`
|
||||||
|
7. **History tracking** - Saves to `tracker.json` (all processed links)
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `bot.py` - Main Discord bot
|
||||||
|
- `tracker.json` - History of all processed links
|
||||||
|
- `analyze-links.sh` - Old cron version (deprecated)
|
||||||
|
|
||||||
|
## Tracking
|
||||||
|
|
||||||
|
All links are saved in `tracker.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"url": "https://...",
|
||||||
|
"title": "Article Title",
|
||||||
|
"type": "GitHub",
|
||||||
|
"author": "username",
|
||||||
|
"message_id": 123456,
|
||||||
|
"date": "2026-02-09T18:05:00",
|
||||||
|
"tududi": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running as service
|
||||||
|
|
||||||
|
To run permanently (e.g., on a VPS):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using screen
|
||||||
|
screen -S link-bot
|
||||||
|
python3 bot.py
|
||||||
|
|
||||||
|
# Or systemd service
|
||||||
|
# TODO: Add systemd unit file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**Bot not seeing messages:**
|
||||||
|
- Check DISCORD_BOT_TOKEN is correct
|
||||||
|
- Verify bot has "Read Message History" permission
|
||||||
|
- Make sure bot is in the server
|
||||||
|
|
||||||
|
**Can't fetch links:**
|
||||||
|
- Some sites block scrapers → error logged in response
|
||||||
|
- Timeouts after 5 seconds
|
||||||
|
|
||||||
|
**Tududi not getting items:**
|
||||||
|
- Check TUDUDI_API_KEY is set
|
||||||
|
- Verify API endpoint is reachable
|
||||||
|
|
||||||
|
## Future enhancements
|
||||||
|
|
||||||
|
- [ ] Summarization with AI (use Haiku analysis)
|
||||||
|
- [ ] Tag suggestions based on content
|
||||||
|
- [ ] React with 👀 when processing
|
||||||
|
- [ ] Edit summary if analysis completes
|
||||||
|
- [ ] Support for media attachments
|
||||||
66
analyze-links.sh
Executable file
66
analyze-links.sh
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Scan #remora for new links, analyze, and add to Tududi inbox
|
||||||
|
# Run via cron every 5 minutes
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CHANNEL_ID="1467557082583535729"
|
||||||
|
TRACKER_FILE="$(dirname "$0")/tracker.json"
|
||||||
|
GATEWAY_URL="${OPENCLAW_GATEWAY:-http://127.0.0.1:18789}"
|
||||||
|
GATEWAY_TOKEN="${OPENCLAW_GATEWAY_TOKEN:-}"
|
||||||
|
|
||||||
|
if [ -z "$GATEWAY_TOKEN" ]; then
|
||||||
|
echo "⚠️ OPENCLAW_GATEWAY_TOKEN not set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read last check timestamp
|
||||||
|
last_check=$(jq -r '.last_check' "$TRACKER_FILE")
|
||||||
|
processed_ids=$(jq -r '.processed_message_ids[]' "$TRACKER_FILE" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
# Fetch recent messages from #remora
|
||||||
|
echo "📡 Scanning #remora for new links..."
|
||||||
|
messages=$(curl -s \
|
||||||
|
-H "Authorization: Bearer ${DISCORD_BOT_TOKEN:-}" \
|
||||||
|
"https://discord.com/api/v10/channels/$CHANNEL_ID/messages?limit=50" 2>/dev/null || echo "[]")
|
||||||
|
|
||||||
|
# Extract URLs and process new ones
|
||||||
|
echo "$messages" | jq -r '.[] | select(.content | test("http")) | "\(.id)|\(.content)|\(.author.username)"' | while IFS='|' read -r msg_id content author; do
|
||||||
|
# Skip if already processed
|
||||||
|
if echo "$processed_ids" | grep -q "$msg_id"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🔗 Found message from $author: $content"
|
||||||
|
|
||||||
|
# Extract URL (simple regex)
|
||||||
|
url=$(echo "$content" | grep -oP 'https?://[^\s]+' | head -1)
|
||||||
|
|
||||||
|
if [ -z "$url" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " URL: $url"
|
||||||
|
|
||||||
|
# Fetch and analyze
|
||||||
|
title=$(curl -s -I "$url" 2>/dev/null | grep -i "title" | cut -d' ' -f2- || echo "Unknown")
|
||||||
|
|
||||||
|
# Send to Tududi inbox with summary
|
||||||
|
if [ -n "$title" ]; then
|
||||||
|
echo " ➕ Adding to Tududi inbox: $title"
|
||||||
|
curl -s -X POST "https://todo.dilain.com/api/v1/inbox" \
|
||||||
|
-H "Authorization: Bearer ${TUDUDI_API_KEY:-}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"content\":\"📌 $title\n🔗 $url\"}" > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update tracker
|
||||||
|
jq ".processed_message_ids += [\"$msg_id\"]" "$TRACKER_FILE" > "$TRACKER_FILE.tmp"
|
||||||
|
mv "$TRACKER_FILE.tmp" "$TRACKER_FILE"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Update last check
|
||||||
|
jq ".last_check = \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"" "$TRACKER_FILE" > "$TRACKER_FILE.tmp"
|
||||||
|
mv "$TRACKER_FILE.tmp" "$TRACKER_FILE"
|
||||||
|
|
||||||
|
echo "✅ Scan complete"
|
||||||
190
bot.py
Normal file
190
bot.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Discord bot for #remora channel - analyzes links in real-time
|
||||||
|
Posts summaries, adds to Tududi inbox, maintains JSON history
|
||||||
|
"""
|
||||||
|
|
||||||
|
import discord
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Load .env file
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Config
|
||||||
|
CHANNEL_ID = 1467557082583535729
|
||||||
|
TRACKER_FILE = Path(__file__).parent / "tracker.json"
|
||||||
|
TUDUDI_API_URL = os.getenv("TUDUDI_API_URL", "https://todo.dilain.com/api/v1")
|
||||||
|
TUDUDI_API_KEY = os.getenv("TUDUDI_API_KEY")
|
||||||
|
GATEWAY_URL = os.getenv("OPENCLAW_GATEWAY", "http://127.0.0.1:18789")
|
||||||
|
GATEWAY_TOKEN = os.getenv("OPENCLAW_GATEWAY_TOKEN")
|
||||||
|
|
||||||
|
# Load or init tracker
|
||||||
|
def load_tracker():
|
||||||
|
if TRACKER_FILE.exists():
|
||||||
|
with open(TRACKER_FILE) as f:
|
||||||
|
return json.load(f)
|
||||||
|
return {
|
||||||
|
"channel_id": CHANNEL_ID,
|
||||||
|
"processed_message_ids": [],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
|
|
||||||
|
def save_tracker(data):
|
||||||
|
with open(TRACKER_FILE, "w") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
# Detect links in text
|
||||||
|
def extract_urls(text):
|
||||||
|
url_pattern = r'https?://[^\s<>"{}|\\^`\[\]]+'
|
||||||
|
return re.findall(url_pattern, text)
|
||||||
|
|
||||||
|
# Fetch and analyze URL
|
||||||
|
def analyze_url(url):
|
||||||
|
"""Fetch URL and create summary"""
|
||||||
|
try:
|
||||||
|
print(f" 📥 Fetching: {url}")
|
||||||
|
response = requests.get(url, timeout=5, headers={
|
||||||
|
'User-Agent': 'Mozilla/5.0'
|
||||||
|
})
|
||||||
|
content = response.text[:2000] # First 2k chars
|
||||||
|
|
||||||
|
# Extract title
|
||||||
|
title_match = re.search(r'<title[^>]*>([^<]+)</title>', content, re.IGNORECASE)
|
||||||
|
title = title_match.group(1).strip() if title_match else url.split('/')[-1]
|
||||||
|
|
||||||
|
# Simple content type detection
|
||||||
|
link_type = "webpage"
|
||||||
|
if "github.com" in url:
|
||||||
|
link_type = "GitHub"
|
||||||
|
elif "reddit.com" in url:
|
||||||
|
link_type = "Reddit"
|
||||||
|
elif "youtube.com" in url or "youtu.be" in url:
|
||||||
|
link_type = "YouTube"
|
||||||
|
elif "tiktok.com" in url:
|
||||||
|
link_type = "TikTok"
|
||||||
|
elif "twitter.com" in url or "x.com" in url:
|
||||||
|
link_type = "Twitter/X"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"title": title,
|
||||||
|
"type": link_type,
|
||||||
|
"status": "ok"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Error fetching: {e}")
|
||||||
|
return {
|
||||||
|
"title": "Couldn't fetch",
|
||||||
|
"type": "unknown",
|
||||||
|
"status": "error",
|
||||||
|
"error": str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send to Tududi inbox
|
||||||
|
def add_to_tududi(title, url, link_type):
|
||||||
|
"""Add to Tududi inbox with summary"""
|
||||||
|
try:
|
||||||
|
if not TUDUDI_API_KEY:
|
||||||
|
print(" ⚠️ TUDUDI_API_KEY not set")
|
||||||
|
return False
|
||||||
|
|
||||||
|
content = f"📌 {link_type}: {title}\n🔗 {url}"
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
f"{TUDUDI_API_URL}/inbox",
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {TUDUDI_API_KEY}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
json={"content": content},
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f" ✅ Added to Tududi: {title}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f" ⚠️ Tududi error: {response.status_code}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ Tududi error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Discord bot
|
||||||
|
intents = discord.Intents.default()
|
||||||
|
intents.message_content = True
|
||||||
|
|
||||||
|
class LinkAnalyzerBot(discord.Client):
|
||||||
|
async def on_ready(self):
|
||||||
|
print(f"✅ Bot logged in as {self.user}")
|
||||||
|
print(f"📍 Watching channel #remora ({CHANNEL_ID})")
|
||||||
|
|
||||||
|
async def on_message(self, message):
|
||||||
|
# Ignore bot's own messages
|
||||||
|
if message.author == self.user:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Only process #remora channel
|
||||||
|
if message.channel.id != CHANNEL_ID:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check for URLs
|
||||||
|
urls = extract_urls(message.content)
|
||||||
|
if not urls:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Skip if already processed
|
||||||
|
tracker = load_tracker()
|
||||||
|
if message.id in tracker["processed_message_ids"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"🔗 New link from {message.author}: {message.content}")
|
||||||
|
|
||||||
|
# Process each URL
|
||||||
|
for url in urls:
|
||||||
|
print(f" Processing: {url}")
|
||||||
|
|
||||||
|
# Analyze
|
||||||
|
analysis = analyze_url(url)
|
||||||
|
|
||||||
|
# Add to Tududi
|
||||||
|
add_to_tududi(analysis["title"], url, analysis["type"])
|
||||||
|
|
||||||
|
# Prepare response
|
||||||
|
summary = f"📌 **{analysis['type']}**: {analysis['title']}"
|
||||||
|
if analysis["status"] == "error":
|
||||||
|
summary += f"\n⚠️ {analysis['error']}"
|
||||||
|
|
||||||
|
# Post summary in channel
|
||||||
|
await message.reply(summary, mention_author=False)
|
||||||
|
|
||||||
|
# Add to tracker
|
||||||
|
tracker["links"].append({
|
||||||
|
"url": url,
|
||||||
|
"title": analysis["title"],
|
||||||
|
"type": analysis["type"],
|
||||||
|
"author": str(message.author),
|
||||||
|
"message_id": message.id,
|
||||||
|
"date": datetime.now().isoformat(),
|
||||||
|
"tududi": True
|
||||||
|
})
|
||||||
|
|
||||||
|
# Update processed IDs
|
||||||
|
tracker["processed_message_ids"].append(message.id)
|
||||||
|
save_tracker(tracker)
|
||||||
|
|
||||||
|
# Main
|
||||||
|
if __name__ == "__main__":
|
||||||
|
token = os.getenv("DISCORD_BOT_TOKEN")
|
||||||
|
if not token:
|
||||||
|
print("❌ DISCORD_BOT_TOKEN not set!")
|
||||||
|
print("Set it: export DISCORD_BOT_TOKEN='your_token'")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
bot = LinkAnalyzerBot(intents=intents)
|
||||||
|
bot.run(token)
|
||||||
Reference in New Issue
Block a user