CtrlK
BlogDocsLog inGet started
Tessl Logo

web-monitor

Use this skill when the user wants to watch a webpage for changes, monitor a URL, detect content updates, or get notified when a site changes.

71

Quality

86%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

SKILL.md
Quality
Evals
Security

Web Monitor

Overview

Monitors any URL for content changes and sends alerts via webhook, Discord, Telegram, or email. Uses content hashing for efficient change detection and CSS selectors to target specific page elements.

Quick Start

import requests, hashlib, json
from pathlib import Path

def check_url(url: str, state_file: str = "monitor_state.json") -> bool:
    """Returns True if content changed since last check."""
    state = json.loads(Path(state_file).read_text()) if Path(state_file).exists() else {}
    content = requests.get(url, timeout=10).text
    new_hash = hashlib.sha256(content.encode()).hexdigest()
    changed = state.get(url) != new_hash
    state[url] = new_hash
    Path(state_file).write_text(json.dumps(state))
    return changed

Targeted Element Monitoring (CSS Selectors)

from bs4 import BeautifulSoup
import requests, hashlib

def monitor_element(url: str, selector: str, state: dict) -> tuple[bool, str]:
    """Watch a specific element on a page."""
    soup = BeautifulSoup(requests.get(url, timeout=10).text, "html.parser")
    el = soup.select_one(selector)
    text = el.get_text(strip=True) if el else ""
    h = hashlib.sha256(text.encode()).hexdigest()
    changed = state.get(f"{url}#{selector}") != h
    state[f"{url}#{selector}"] = h
    return changed, text

Content Diffing

import difflib

def compute_diff(old: str, new: str) -> str:
    """Return a unified diff string between old and new content."""
    return "\n".join(difflib.unified_diff(
        old.splitlines(), new.splitlines(),
        fromfile="previous", tofile="current", lineterm=""
    ))

Notifications

Discord Webhook

import requests

def notify_discord(webhook_url: str, url: str, diff: str) -> None:
    content = f"**Change detected on:** {url}\n```diff\n{diff[:1800]}\n```"
    requests.post(webhook_url, json={"content": content}, timeout=5)

Telegram

def notify_telegram(bot_token: str, chat_id: str, url: str, diff: str) -> None:
    msg = f"Change on {url}:\n{diff[:3000]}"
    requests.post(
        f"https://api.telegram.org/bot{bot_token}/sendMessage",
        json={"chat_id": chat_id, "text": msg},
        timeout=5,
    )

Generic Webhook

def notify_webhook(endpoint: str, payload: dict) -> None:
    requests.post(endpoint, json=payload, timeout=5)

Full Monitor Loop

import time

def run_monitor(urls: list[str], interval_seconds: int = 300,
                discord_webhook: str | None = None) -> None:
    state = {}
    while True:
        for url in urls:
            try:
                changed, text = monitor_element(url, "body", state)
                if changed:
                    print(f"[CHANGED] {url}")
                    if discord_webhook:
                        notify_discord(discord_webhook, url, text[:500])
            except Exception as e:
                print(f"[ERROR] {url}: {e}")
        time.sleep(interval_seconds)

JavaScript-Rendered Pages (Playwright)

from playwright.sync_api import sync_playwright

def get_rendered_content(url: str, selector: str = "body") -> str:
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        page.goto(url, wait_until="networkidle")
        text = page.locator(selector).inner_text()
        browser.close()
    return text

Quick Reference

TaskToolNotes
Hash whole pagehashlib.sha256Fast, no parsing
Watch one elementbeautifulsoup4 + CSS selectorPrecise targeting
JS-rendered pagesplaywrightSlower, handles SPAs
Diff outputdifflib.unified_diffHuman-readable changes
Discord alertrequests.post(webhook)Free, instant
Rate limitingtime.sleep(interval)Respect robots.txt
Repository
ProwlrBot/prowlr-marketplace
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.