AVB21's avatar
AVB21
avb@nostrplebs.com
npub1sec6...p0l7
Historian, builder of tools, maker of chocolates and code. AVB_21 on X LN: avb@nostrplebs.com
AVB21's avatar
AVB 9 months ago
I block these '21 bitfluencers' from now on. If you're only active when there are conference tickets to be scored, the f the f off.
AVB21's avatar
AVB 9 months ago
Let's NiP05 scan the #spambots Better coders can finetune I suppose Assumptions and Setup Nostr Relay: You have a WebSocket connection to a Nostr relay (e.g., wss://relay.damus.io) to fetch events. NIP-05 Provider Data: You have a database or API with member public keys and their NIP-05 registration timestamps. Libraries: Uses websocket for Nostr relay communication, json for event parsing, and random for selecting replies. You may need additional libraries like requests for NIP-05 verification checks. Database: A simple SQLite database is assumed for storing member data, but you can adapt to your setup (e.g., MySQL, API). Bot Detection: Basic bot detection checks for identical or nonsensical replies using simple text comparison. You can enhance this with NLP libraries like nltk or textblob for advanced analysis. Script import websocket import json import time import random import sqlite3 from datetime import datetime, timedelta import requests import re from collections import Counter # Configuration RELAY_URL = "wss://relay.damus.io" # Replace with your relay DB_PATH = "nip05_members.db" # SQLite database for NIP-05 members TIME_WINDOW = 7 # Days to check for recent activity REPLY_SAMPLE_SIZE = 5 # Number of replies to analyze SIMILARITY_THRESHOLD = 0.9 # Jaccard similarity threshold for identical replies # Initialize database def init_db(): conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS members ( pubkey TEXT PRIMARY KEY, nip05_address TEXT, registration_timestamp INTEGER ) """) # Example: Populate with dummy data (replace with your actual data) cursor.execute("INSERT OR IGNORE INTO members VALUES (?, ?, ?)", ("b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9", "bob@example.com", int(time.time() - 86400 * 30))) conn.commit() return conn, cursor # Connect to Nostr relay def connect_to_relay(): ws = websocket.WebSocket() ws.connect(RELAY_URL) return ws # Fetch events from relay def fetch_events(ws, pubkey, since_timestamp, kind=1): subscription_id = f"scan_{pubkey[:8]}" filter = { "authors": [pubkey], "kinds": [kind], # kind=1 for posts/replies "since": since_timestamp } ws.send(json.dumps(["REQ", subscription_id, filter])) events = [] timeout = time.time() + 10 # 10-second timeout while time.time() < timeout: try: message = json.loads(ws.recv()) if message[0] == "EVENT" and message[1] == subscription_id: events.append(message[2]) except websocket.WebSocketTimeoutException: break ws.send(json.dumps(["CLOSE", subscription_id])) return events # Calculate Jaccard similarity between two texts def jaccard_similarity(text1, text2): if not text1 or not text2: return 0.0 set1 = set(re.findall(r'\w+', text1.lower())) set2 = set(re.findall(r'\w+', text2.lower())) intersection = len(set1 & set2) union = len(set1 | set2) return intersection / union if union > 0 else 0.0 # Check if text appears nonsensical (basic heuristic) def is_nonsense(text): # Basic check: too short, repetitive characters, or random strings if len(text) < 10: return True if re.match(r'^(.)\1{3,}$', text): # Repeated characters return True # Add more checks (e.g., entropy, common spam phrases) return False # Check NIP-05 registration status def check_nip05_status(pubkey, nip05_address): try: local_part, domain = nip05_address.split('@') url = f"https://{domain}/.well-known/nostr.json?name={local_part}" response = requests.get(url, timeout=5) if response.status_code == 200: data = response.json() if data.get("names", {}).get(local_part) == pubkey: return True return False except Exception as e: print(f"Error checking NIP-05 for {nip05_address}: {e}") return False # Main scanning function def scan_for_spam_bots(): conn, cursor = init_db() ws = connect_to_relay() # Get all members cursor.execute("SELECT pubkey, nip05_address, registration_timestamp FROM members") members = cursor.fetchall() # Calculate timestamp for 7 days ago since_timestamp = int((datetime.now() - timedelta(days=TIME_WINDOW)).timestamp()) log = [] for pubkey, nip05_address, reg_timestamp in members: print(f"Scanning {nip05_address} ({pubkey[:8]}...)") # Step 1: Check for at least one reply in the past 7 days events = fetch_events(ws, pubkey, since_timestamp, kind=1) replies = [e for e in events if "e" in e.get("tags", [])] # Events with 'e' tag are replies if len(replies) < 1: log.append({ "pubkey": pubkey, "nip05_address": nip05_address, "status": "No replies in past 7 days", "nip05_active": check_nip05_status(pubkey, nip05_address), "registration_date": datetime.fromtimestamp(reg_timestamp).isoformat() }) continue # Step 2: Analyze up to 5 random replies sample_replies = random.sample(replies, min(len(replies), REPLY_SAMPLE_SIZE)) suspicious = False reply_texts = [r["content"] for r in sample_replies] # Check for identical or similar replies for i, text1 in enumerate(reply_texts): for text2 in reply_texts[i+1:]: if jaccard_similarity(text1, text2) > SIMILARITY_THRESHOLD: suspicious = True break if suspicious: break # Check for nonsensical replies if not suspicious: suspicious = any(is_nonsense(text) for text in reply_texts) # Step 3: Check NIP-05 status (for logging only) nip05_active = check_nip05_status(pubkey, nip05_address) # Log results log.append({ "pubkey": pubkey, "nip05_address": nip05_address, "status": "Suspicious" if suspicious else "Clean", "reply_count": len(replies), "nip05_active": nip05_active, "registration_date": datetime.fromtimestamp(reg_timestamp).isoformat() }) ws.close() conn.close() # Save log to file with open("spam_scan_log.json", "w") as f: json.dump(log, f, indent=2) return log # Run the script if __name__ == "__main__": results = scan_for_spam_bots() print("Scan complete. Results saved to spam_scan_log.json") for result in results: print(result)
AVB21's avatar
AVB 9 months ago
About the Yoda Speak Spambots on nostr: Can't be that hard to reject or even disable the NiP05 'verification'nog these bots after they've replied nonsense to random new people a couple of times. But seen the dismal state of some of these NiP05 providers (who paste a JSON and call it quits) ... We need more thinking. Maybe I'll code one myself.
AVB21's avatar
AVB 9 months ago
How is that service called where we can follow some groupd of one sort of nostr people? I forgot the name. Nostr waves? Or nostr packs? Don't know anymore
AVB21's avatar
AVB 9 months ago
Anyone here who quit X completely and went only on Nostr? I'm still in doubt.
AVB21's avatar
AVB 9 months ago
GM Going to try to make this my home now. We need to.
AVB21's avatar
AVB 9 months ago
What's up with these reply bots that scramble the sentence you just typed and reply random garbage? I had a lot of these since I've added some wot relay.
AVB21's avatar
AVB 9 months ago
Literally every click you make in nostr web-implementations is an in-your-face bug. I wouldn't even let most of these things go out as an alpha, let alone in production ๐Ÿ˜‰
AVB21's avatar
AVB 9 months ago
Hey nostr.info: copy-pasting a realy name from your website is close to impossible. Is it that hard to let select copy/paste work? ๐Ÿ˜Ÿ
AVB21's avatar
AVB 9 months ago
Bitcoin made me more lonely. That's true for many people I'm sure I'm not the only one.
AVB21's avatar
AVB 9 months ago
If you miss someone. But on the other hand you know they're batshit crazy. ๐Ÿ˜ตโ€๐Ÿ’ซ
AVB21's avatar
AVB 9 months ago
trying to log in with primal (or any other nostr service) + nos2x is a very disappointing experience. Why do developers hardly foresee a "log in with extension" link or something? It's really rotten to stare at a blank screen, no errors, no user feedback... you "just have to know" linux-mentality.
AVB21's avatar
AVB 9 months ago
primal is broken for me today... what's going on?
AVB21's avatar
AVB 9 months ago
Wrote a lot past few months. It was a relief to finish it all Refining some tools I've built , and then on to the next adventure. In the meanwhile, creating some art.
AVB21's avatar
AVB 9 months ago
Some want you to go "seedless", they want to be your middleman, expensive hardware with a questionable supply chain and a "subscription" like model to refresh your equipment every so many times. Others... build open-source tools that can be replicated, run offline or online, and give you a way to generate seeds. and
AVB21's avatar
AVB 9 months ago
'Yell louder' is the new 'I want may payment to be included in the next block'
AVB21's avatar
AVB 9 months ago
I miss a lot of people. I miss genuine people the most. But even fake ones can be missed. At least better than spineless useless f's.
โ†‘