Gemini-Mention CGI Script in Python

I wanted to implement Gemini-Mention on my Gemini capsule but I didn't want to use the existing scripts out there (Go, Lua, or Bash). Since I am most comfortable in Python, I just created my own.

If you're not familiar with Gemini-Mention, it's an RFC that defines a way for a person writing a reply gemlog (a gemlog replying to another gemlog) to be able to notify the original gemlog owner about the reply. Aside from aggregators like Cosmos and backlinks provided by some search providers, there's no real way to know if someone is replying to your gemlog. Gemini-Mention is there to hopefully solve that problem, even if it's a manual way of doing so.

Seen on my capsule: gemini://gemini.smallweb.space/gemini-mention/mention (you'll need a Gemini browser, like LaGrange )

Disclaimer, I'm a hobby programmer so there's definitely a better way to do this, but it gets the job done for me. Code follows.

#!/usr/bin/env python3

##################
# gemini-mention
# by Gritty
#
# Requires: ignition
##################

import os
import sys
import re
import ignition
import fileinput
import json
from urllib.parse import unquote

ERRORPAGE = '''
# Gemini-Mention

ERROR: Sorry, there was an issue fetching the URL you listed.  Please ensure that:
* It is percent-encoded
* Is a proper gemini:// URL

=> mention?submit Submit another link
'''

INSTRUCTIONPAGE = '''
# Gemini-Mention

=> mention?submit Submit a link

TLDR: Use the link above to notify me of a reply to one of my gemlogs.

## What is gemini-mention?
Gemini-mention alerts a capsule owner of a gemlog reply.  This is voluntary and done for the courtesy of the gemlog owner to which they are replying.

It is defined by an RFC created by @Bacardi55:
=> https://codeberg.org/bacardi55/gemini-mentions-rfc Gemini Mention RFC (HTTPS)  

### Usage
Submit a percent encoded gemini URL to the following link:
> gemini://gemini.smallweb.space/gemini-mention/mention?<percent_encoded_url_of_reply>

This capsule will then scan that page for links back to this capsule and log them for the capsule owner.  In the future I (Gritty) plan to post these replies to the bottom of their respective gemlogs.

'''

RESULTSPAGE ='''
# Gemini-Mention

Matching backlinks / mentions:
'''

FOOTER = '''
=> ../index.gmi Home
=> ../gemlog/index.gmi Gemlogs (non-technical)
=> ../tech-gemlog/index.gmi Technical Gemlogs
'''

def process_json(srcUrl, mentions):
    # expects source url that mentions this site
    # expections list of urls mentioned by the source url
    # make sure json file exists with at least one dummy entry
    # i.e., { "test" : "value"}
    with open("mentions.json", "r+") as file:
       
        # load json file
        dataDict = json.load(file)
        
        # create or update entry
        dataDict[srcUrl] = mentions
        
        # write the data, truncating file first
        file.seek(0)
        file.truncate()
        json.dump(dataDict, file, indent = 2)

def printPage(text, header=True, footer=True):
    if header:
        print("20 text/gemini; charset=utf-8", end = "\r\n")
    print(text)
    if footer:
        print(FOOTER)

#### Main program starts here ####

qString = os.getenv('QUERY_STRING')

if qString:
    if qString == 'submit':
        # Prompt user for a URL that mentions a page on this capsule
        print("10 Enter a mention URL. \r\n")
    else:
        # Everything else should (hopefully) be a URL to parse
        URL = unquote(qString)
        response = ignition.request(URL) 
        if not response.success():
            printPage(ERRORPAGE)
        else:
            page = response.data()
            pattern = r"^(=>) *(gemini://gemini.smallweb.space/(gemlog|tech-gemlog)[\S]*) *(.*$)" 
            regex = re.compile(pattern, re.MULTILINE)
            matches = re.findall(regex, page)
            if matches == []:
                printPage("No matches to this capsule found")
            else:
                # process the matches
                mentionedUrls = []
                pageText = "Matched URLs\n"
                for item in matches:
                    if item[1] not in mentionedUrls:
                        pageText += f"* {item[1]} \n"
                        mentionedUrls.append(item[1])                
                process_json(URL, mentionedUrls)
                pageText += "URLs / mentions Recorded\n"
                pageText += "=>mention?submit Submit another"
                printPage(pageText)

else:
    # We have an empty query string, display instructions
    printPage(INSTRUCTIONPAGE)