💾 Archived View for tilde.team › ~jmmartin › articles › creating_dynamic_gemini.gmi captured on 2021-12-05 at 23:47:19. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2021-11-30)

-=-=-=-=-=-=-

Creating a Dynamic Gemini Page

Intro

I recently discovered the Gemini protocol and immediately liked the concept and the technology behind. Started to play around with it creating some pages. Then I begun wondering how to add dynamic content within the Gemini protocol.

The following was my first approach and I'm putting together this document hoping beginners will find it useful.

Description

The idea is to create a Gemini page that prompts to enter a search term, and then returns a number of news headings related to the words entered.

To accomplish this we are going to create a CGI script. First, we need to make sure that our Gemini server supports CGI scripts. In simple terms, any script or binary that can be run by the server can be used to output text that will be served as the server response to a request.

Wikipedia CGI entry

I chose Python as an easy language to implement this.

First steps

First, we need a source to get the news from. In my case, I used the Free News API, available through RapidApi. It's free and can be used easily

RapidAPI

Free News API documentation

The script

Since we need to make the script executable, we start the file with the command needed to run it when called from the server:

#!/usr/bin/python

Then we pull some imports we need in the code:

import os
import http.client
import json
from urllib.parse import unquote

We need the 'http.client' and 'json' to get the news from the API. 'unquote' allows us to parse the query string containing the search terms (more on this later).

Let's create a function to retrieve the news related to given search terms from the API:

def get_news(terms):
    conn = http.client.HTTPSConnection("free-news.p.rapidapi.com")
    headers = {
        'x-rapidapi-key': "YOUR_API_KEY",
        'x-rapidapi-host': "free-news.p.rapidapi.com"
        }
    q = "/v1/search?q=" + terms
    conn.request("GET", q, headers=headers)

    res = conn.getresponse()
    data = res.read()
    return data.decode("utf-8")

The input to the function is a string containing the search terms. Since we will get these from the query string, the terms would already be URL encoded, what it is fine for our purpose here. We create the headers according to the data provided by RapidAPI (don't forget to use a valid API key). Then we build the query string adding the terms we got. Then the function basically returns the response as a string.

Now we add a function to parse the incoming string into something more usable. Actually, the following function will take this string and write it as Gemini format.

def parse_news(data):
    jdata = json.loads(data)
    for article in jdata["articles"]:
        print("## " + article["title"])
        print("### " + article["published_date"][:10] + " - " + article["rights"] + "\n")
        print(article["summary"] + "...\n")
        print("=> " + article["link"] + "\n\n")

We simply parse the input string as JSON then walk the object writing the values in a valid Gemini format.

Now, for the main body of the script we have:

query = os.environ.get("QUERY_STRING") 

if query == None:
    print("10 Enter search term\r\n")
else:
    news = get_news(query)
    print("20 text/gemini\r\n")
    print("# News about: " + unquote(query) + "\n")
    parse_news(news)

This is probably the most interesting part. CGI scripts get their input from the environment. In this case, we get the `QUERY_STRING` sent by the user when requesting our resource. For our purposes, `QUERY_STRING` can have 2 different values:

So in the `if` condition, when the user request our page the first time we answer with `10 Enter search term\r\n`. The response codes 1x in Gemini mean request the user to enter a string using the prompt after the code. So this string will cause the client to show the user an input box using our description as the prompt.

When the user enters any text, the client will call our resource with this text as the query string. So in the 'else' part, we call the function to get the news based on the user input. Then, we send the success response code `2x', the page header (where we use 'unquote' to get printable words from the query string), and then we call the `parse_news` function to output the contents of the news headers retrieved.

Final thoughts

This is a proof of concept of how a Gemini site can deliver dynamic content easily and quickly. When I was doing this the first time, it took me some time to put all the pieces together and have it working (mainly because I hadn't write CGI scripts for ages). So I hope that this simple example will help the newcomers to figure out general concept.