Help creating an extension using Python

I tried my best following the instructions in (GitHub - pilotmoon/PopClip-Extensions: Documentation and source code for PopClip extensions.) but I failed.

Many links I copy have this form:

url?source=email-1c1e7d607b-1681956455395-digest.reader-7f0cf5620c9-271eeff82----12-96------------------4f7fb68_dfc_4708_971f_187ed2ac4-1

The string of characters after ‘?’ is used to identify me. I like my privacy and I like to check the contents of the linked page without being tracked.

I wanted to create a PopClip extension that would copy the selected URL, got rid of ? and all the characters after that and copied the clean URL to the clipboard. I’ve failed even in trying to achieve something this simple.

I’m adding a link to my extension here:

https://www.dropbox.com/home/Public/CleanURL.popclipext.zip

You guys can check the contents yourselves. Everything is pretty simple and short.

When I click on the extension everything seems to work fine. Popclip shows me the extension is installed and I can see the icon of the extension (I borrowed this particular icon from another extension just to do a first test). When I select a text, however, the icon for this extension, i.e. the extension itself, does not show up as one of the extensions available to process the selected text.

Any help you can provide would be greatly appreciated. I love Popclip and the more things I can do with it, the better.

HI @jfontana, welcome to the forum. I haven’t looked at the code – the dropbox link seems to be faulty — but based on the “requirements statement” I put together the following sippet which seems to do the trick in JS (I’m not proficient in python)

// #popclip
// name: Clean URL
// icon: iconify:healthicons:clean-hands
// language: javascript
// requirements: [url]
// after: copy-result
return popclip.input.data.urls[0].split('?')[0]

(The above block is an extension snippet — select it then click “Install Extension” in PopClip.)

Some notes:

  • icon - I picked a random one, you can change that!
  • requirements: [url] this makes it so the action only appears when the selected text contains a URL
  • after: copy-result causes whatever is returned from the JS code to be copied to the clipboard

Here’s a python skeleton equivalent, which you can just edit the code part of edit: equivalent that I asked ChatGPT to make for me (since I don’t know python):

# #popclip
# name: Clean URL
# icon: iconify:healthicons:clean-hands
# interpreter: python3
# requirements: [url]
# after: copy-result
import os
from urllib.parse import urlparse, urlunparse

def clean_url(url):
    parsed_url = urlparse(url)
    # Remove the query string by setting it to an empty string
    clean_parsed_url = parsed_url._replace(query='')
    return urlunparse(clean_parsed_url)

print(clean_url(os.environ['POPCLIP_TEXT']), end='')

Thanks a lot Nick! This works perfectly. And nice choice for the icon. I like it.

I have been a PopClip user for a long time but I was not aware of the ‘extension snippet’. What a wonderful addition for a wonderful app this is!

I wanted to do it in Python because this is the only language I feel slightly comfortable with. If I have one extension as a basis, I can keep creating new and possibly more complex ones to suit my needs. What I had tried, following the tutorials/documentation I found was the following:

# #popclip
# { name: Clean URL, icon: micona, interpreter: python3 }

# #popclip
# { name: Clean URL, icon: micona, interpreter: python3 }

import clipboard
import re

def clean_url(text):
    url_pattern = r"(https?://[^\s]+)\?.*"
    match = re.match(url_pattern, text)
    if match:
        return match.group(1)
    return text

def main():
    text = clipboard.get()
    cleaned_text = clean_url(text)
    clipboard.set(cleaned_text)

if __name__ == "__main__":
    main()

I see that, besides the specific Python code you used, there are other differences in the
Because I want to understand how this works, after you provided me with the solution, I tried with the following:

# #popclip
# name: TEST URL CLEAN
# icon: iconify:healthicons:clean-hands
# interpreter: python3
# requirements: [url]
# after: copy-result

import re
import pyperclip

url_pattern = r"(https?://[^\s]+)\?.*"

def clean_url(text):
    match = re.match(url_pattern, text)
    if match:
        return match.group(1)
    return text

def main():
    text = pyperclip.paste()
    cleaned_text = clean_url(text)
    pyperclip.copy(cleaned_text)

if __name__ == "__main__":
    main()

The reason I tried with this is because I am confident that the script works when executed from the terminal. What it does is to process whatever is currently in the clipboard and return the “cleaned” version applying the regular expression.

Now, even though yours worked, I’d like to understand why this other alternative does not work. One important difference (and I think here lies the explanation) is that this script does not assume that you are selecting anything with your cursor. It assumes that you have already copied the string to your clipboard. The other important difference is the part where the script returns the results to PopClip. In the JavaScript version this seems obvious because reference to popclip is part of the return statement. In the Python version, I imagine this is done via the final print statement.

Still, what I’m still curious about is how does PopClip does what is not specified in the script (whether it is Python or JavaScript). I don’t know JavaScript but the script is simple enough to figure out what it is doing. The first thing I’m curious about is how does the script in the extension get the string (the url) it has to process. In neither of the two scripts there is anything that indicates that a copy of the string is made. Is this part of the ‘internals’ of PopClip? I mean, independently of what option you are going to choose, PopClip copies whatever you select to the clipboard?

The second has to do with how PopClip receives the output of the script. I’ve asked chatGPT to help me understand what the print statement does and it says:

" The last line of the code prints the cleaned URL to the console. The clean_url() function takes a URL as input, removes the query string from it, and returns the cleaned URL as a string. The last line of the code calls the clean_url() function with the POPCLIP_TEXT environment variable, which contains the URL that was selected in the previous step by the user. The print() statement then prints the cleaned URL to the console, with the end parameter set to an empty string to avoid adding a newline character at the end of the printed string."

I’ve tried to replicate this in my code by adding the print statement as follows but it doesn’t seem to work. I realize that you work with JavaScript and that this might be a Python related problem that has nothing to do with how PopClip works but I’m taking this as an opportunity to learn so that I don’t have to bother other people in the future or I can myself be able to help others. If you can think of a reason why this doesn’t work with Python, this might be helpful in the future.

# #popclip
# name: TESTIN URL
# icon: TESTIN
# interpreter: python3
# requirements: [url]
# after: copy-result

import re
import pyperclip

url_pattern = r"(https?://[^\s]+)\?.*"

def clean_url(text):
    match = re.match(url_pattern, text)
    if match:
        return match.group(1)
    return text

def clean_url_clipboard():
    text = pyperclip.paste()
    cleaned_text = clean_url(text)
    pyperclip.copy(cleaned_text)

if __name__ == "__main__":
    clean_url_clipboard()


print(clean_url_clipboard(os.environ['POPCLIP_TEXT']), end='')

In shell scripts, the text to be processed is passed in the POPCLIP_TEXT environment variable. From Python, you can access this as a string with the expression os.environ['POPCLIP_TEXT'], as you can see in my example.

Note that because the extension specifies requirements requirements: [url], the POPCLIP_TEXT variable will contain only the matched URL.

The problem is not with the print statement itself, which is fine.

The clean_url_clipboard function in your example takes no parameters. Therefore passing a string to it will have no effect. You need to modify it to take its input as a function parameter instead of from at the clipboard.

In general, PopClip extensions should not be operating on the clipboard contents, unless they specifically are intended to do something with the clipboard instead of the selected text. You should directly process the text passed to it by PopClip, usually from the POPCLIP_TEXT variable, but there are also other variables you can use, as shown in the documentation.

OK. Thanks very much for your explanation. I did the following (just in case it could help someone else trying to use Python) and it worked perfectly well:

# #popclip
# name: TESTIN URL
# icon: TESTIN
# interpreter: python3
# requirements: [url]
# after: copy-result

import re
import os

url_pattern = r"(https?://[^\s]+)\?.*"
compiled_pattern = re.compile(url_pattern)

def clean_url(text):
    match = re.match(compiled_pattern, text)
    return match.group(1) if match else text

def clean_url_clipboard(text):
    cleaned_text = clean_url(text)
    return cleaned_text

if __name__ == "__main__":
    text = os.getenv('POPCLIP_TEXT')
    print(clean_url_clipboard(text), end='')
1 Like