Develop a nostr bot: A Simple Guide

Developing a bot for the Nostr is easier than you might think. This guide will show you how to do that in the python programming language. Use your power with care, create only useful bots.

Here's the Github repo where I took most of the code from: https://github.com/TheHubstr/track-extractor-bot

Prerequisites

You should be familiar with how nostr works, with identities and relays in nostr. You should know what python is and where to put it. For running the bot, you should be familiar with a cloud like GKS or AWS or a framework like cloud foundry. We will create a Dockerfile here for deployment. All of this will be quick & dirty style. Obviously you should restructure the code for production use.

Use case: say "Bitcoin is cool"

The bot should read all text notes on nostr, and if a note contains the word Bitcoin, it should reply to that note like "Thanks, Bitcoin is great".

Step 1: Install dependencies

Like always, you'll need some libs from the net. Install them in your venv or global on your liking:


pip install pynostr tornado cachetools
                    

You don't need the cachetools if you keep seen event-ids somehow else or use only one relay.

I don't show the use statements here, your IDE will tell you what to include.

Step 2: Basics

No program can live without logging, so set up a logger for your code:


logging.basicConfig(level=logging.DEBUG, stream=sys.stdout,
                    format="[%(asctime)s - %(levelname)s] %(message)s")
logging.getLogger().setLevel(logging.INFO)
log = logging.getLogger("Bot-Lesson")
                    

An event-id cache, your hex32 private key and some relays you want to connect to:


small_cache = TTLCache(maxsize=200, ttl=15)
hex_pk = 'my-hex32-nsec key'
relays = [
    "wss://nos.lol", "wss://relay.nostr.bg",
    "wss://nostr.einundzwanzig.space", "wss://relay.damus.io",
    "wss://nostr.mom/", "wss://nostr-pub.wellorder.net/",
    "wss://relay.nostr.jabber.ch", "wss://relay.pleb.to", ]
                    

We need any kind of cache because several relays may send the same events to us, identified by their event id. In the cache we keep the known events to not handle them more than once.

Step 3: Set up your relay connections

The nostr works with streaming web service connections, you connect to relays, subscribe events and post events. Some of that relays may be dead or only accept payed postings. So we catch relay metadata first and drop all the unusable ones. Than we start the RelayManager.


    log.info("The bot connects to relays now")
    relay_list = RelayList()
    relay_list.append_url_list(relays)
    relay_list.update_relay_information(timeout=0.9)
    relay_list.drop_empty_metadata()
    log.info(f"Using {len(relay_list.data)} relays...")

    # set up the relay manager
    relay_manager = RelayManager(error_threshold=3, timeout=0)
    relay_manager.add_relay_list(
        relay_list,
        close_on_eose=False,
        message_callback=check_message,
        message_callback_url=True,
    )
                    

You may read details about the classes RelayList and RelayManager in the pynostr Github.

Step 4: Create a subscription and start listening

Now you want to subscribe to some events. For text events it "kind 1", read more about the kinds in the NIP-01


    # filter on text notes and subscribe
    filters = FiltersList(
        [
            Filters(
                since=int(datetime.datetime.now().timestamp()),
                kinds=[EventKind.TEXT_NOTE],
            )
        ]
    )
    subscription_id = uuid.uuid1().hex
    relay_manager.add_subscription_on_all_relays(subscription_id, filters)
    relay_manager.run_sync()
                    

That was easy, right? Now your message_callback=check_message gets called for every event that arrives from the relays.

Step 5: Read the text events and create a reply

So far we've connected to the relays and have dropped our subscription for text events. Now we need to check for our use case and create a reply post.



@gen.coroutine
def check_message(message_json, url):
    global relay_manager
    if message_json[0] == RelayMessageType.EVENT:
        event: Event = Event.from_dict(message_json[2])
        if event.id not in small_cache:
            small_cache[event.id] = 1
            if 'Bitcoin' in event.content:
                new_event: Event = Event("Thanks, Bitcoin is great")
                new_event.add_tag("e", event.id)
                new_event.sign(hex_pk)
                relay_manager.publish_event(new_event)
                    

If you want the author to be notified, add the 'p' tag in new_event (look up details by yourself). Now, that's all. Now you may docker (Dockfile) your code in a python image and deploy it wherever it should run.

Final thoughts

  • This is quick & dirty and covers only the must-haves. For production use cases you may need more sophisticated programming.
  • Don't be bad, bots are dirty to most users.
  • Think about useful extensions where you connect a messenger like Telegram to your bot, communicate with clients or facilitate an llm.

If you have any comments or questions, please reach out by nostr: nostr:npub1cygus44llmuj4m7w5yfpgqk83x9lvrtr2qavn26ugsyduzv7jc0qnjw7h8

If you found this guide useful, consider sending some sats to lud16 - cygus44@hubstr.org - or zap my profile.

Thanks, stay brave and private 🧡