fixing i2psnarks new form with pythonscript

Issues and ideas about I2PSnark
Titus
Posts: 4
Joined: 28 Dec 2021 16:17

fixing i2psnarks new form with pythonscript

Post by Titus »

Hi,

as i have voiced before using the IRC2P i was deeply annoyed by i2psnark changing the form of of there start download form to included an upload option which prevented me from using firefox custom search keyword funktion to quickly add magnet links. Now i finally managed to get a workaround out of chatgpt:

Code: Select all

#!/usr/bin/env python3

# i2pSnarkFeeding.py
# Developed with the assistance of OpenAI's GPT-3.5

import requests
import configparser
import sys
import os
import re
from urllib.parse import urlparse

def get_language():
    lang = os.getenv("LANG")
    if lang:
        lang = lang.split('.')[0] # Berücksichtige nur den Sprachcode
    return lang

def get_translation(lang):
    if lang == "es":
        return {
            "adding_success": "¡Enlace magnético o archivo torrent añadido correctamente a i2psnark!",
            "adding_error": "Error al agregar el enlace magnético o archivo torrent a i2psnark.",
            "usage": "Uso: python script.py [archivo.torrent | enlace_magnético | enlace_http_a_torrent]",
            "help": [
                "Opciones:",
                "  -h, --help                 Mostrar este mensaje de ayuda"
            ]
        }
    elif lang == "de":
        return {
            "adding_success": "Magnet-Link oder Torrent-Datei erfolgreich zu i2psnark hinzugefügt!",
            "adding_error": "Fehler beim Hinzufügen des Magnet-Links oder der Torrent-Datei zu i2psnark.",
            "usage": "Verwendung: python script.py [torrent_datei | magnet_link | http_link_zu_torrent]",
            "help": [
                "Optionen:",
                "  -h, --help                 Diese Hilfemeldung anzeigen"
            ]
        }
    else: # Englisch als Standardsprache
        return {
            "adding_success": "Magnet link or torrent file successfully added to i2psnark!",
            "adding_error": "Error adding the magnet link or torrent file to i2psnark.",
            "usage": "Usage: python script.py [torrent_file | magnet_link | http_link_to_torrent]",
            "help": [
                "Options:",
                "  -h, --help                 Display this help message"
            ]
        }

def get_nonce(snark_url):
    try:
        response = requests.get(snark_url, timeout=5)
        nonce = response.text.split('name="nonce" value="', 1)[1].split('"', 1)[0]
        return nonce
    except Exception as e:
        print("Error fetching nonce:", str(e))
        return None

def add_to_i2psnark(input_value, target_dir, snark_url, nonce, translations):
    try:
        url = snark_url + "/_post" # _post an die Snark-URL anhängen
        files = {'newFile': open(input_value, 'rb')} if os.path.isfile(input_value) else {'newFile': None}
        data = {
            'nonce': nonce,
            'p': '1',
            'sort': '-9',
            'action': 'Add',
            'nofilter_newURL': input_value if not files['newFile'] else '',
            'nofilter_newDir': target_dir,
            'foo': 'Add torrent'
        }
        response = requests.post(url, files=files, data=data)
        print("Response from server:", response.text) # Drucke die Serverantwort
        if "Torrent added successfully" in response.text:
            print(translations["adding_success"])
        else:
            print(translations["adding_error"])
    except Exception as e:
        print("Error adding the magnet link or torrent file to i2psnark:", str(e))

def display_help(translations):
    print(translations["usage"])
    for line in translations["help"]:
        print(line)
    sys.exit()

def main():
    # Sprache ermitteln
    lang = get_language()
    translations = get_translation(lang)

    # Konfigurationsdatei lesen
    config = configparser.ConfigParser()
    config.read('i2pSnarkFeeding.ini')
    target_dir = config['DEFAULT'].get('target_dir', '')

    # Nonce abrufen
    snark_url = config['DEFAULT'].get('snark_url', 'http://127.0.0.1:7657/i2psnark')
    nonce = get_nonce(snark_url)
    if nonce:
        # CLI-Argumente verarbeiten
        if len(sys.argv) == 1 or "-h" in sys.argv or "--help" in sys.argv:
            display_help(translations)
        else:
            input_value = sys.argv[1]
            if os.path.isfile(input_value):
                add_to_i2psnark(input_value, target_dir, snark_url, nonce, translations)
            elif re.match(r'^magnet:', input_value):
                add_to_i2psnark(input_value, target_dir, snark_url, nonce, translations)
            elif urlparse(input_value).scheme.startswith('http'):
                add_to_i2psnark(input_value, target_dir, snark_url, nonce, translations)
            else:
                print("Invalid input. Please provide either a torrent file, a magnet link, or a link to a torrent file.")

if __name__ == "__main__":
    main()

just save this file somewhere on a system with access to the router, create a ini file like this along

i2pSnarkFeeding.ini:

Code: Select all

[DEFAULT]
target_dir = /I/i2psnark/
snark_url = http://127.0.0.1:7657/i2psnark
make the i2pSnarkFeeding.py if you are using and tell your browser to handle magnet links using that file and if you wish your os to open .torrent files using it to.

This script should be silent so you have to check your i2psnark in your browser yourself. I thought of implementing some desktop notifications but the time i was done i had enough of gpt3.5s stupidity and was happy it worked, but if you wish feel free to improve opon it.

happy hacking

Mr. T
Titus
Posts: 4
Joined: 28 Dec 2021 16:17

Re: fixing i2psnarks new form with pythonscript

Post by Titus »

grate, now i destroyed my whole i2psnark by changing the data dir in its global config, i cant seam to fix the issue with data dir is not transmitted correctly, there added torrents always get to my /home/ drive which filles up and brakes everything. Maybe somebody here wich actually speaks python can fix that. Here is my latest try, but i give up for now (or till i get gpt 4 through some means)

Code: Select all

$ cat ~/bin/i2pSnarkFeeding.py 
#!/usr/bin/env python3

# i2pSnarkFeeding.py
# Developed with the assistance of OpenAI's GPT-3.5

import requests
import configparser
import sys
import os
import re
from urllib.parse import urlparse
import logging
from datetime import datetime

def setup_logging(logfile):
    logging.basicConfig(filename=logfile, level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')

def get_language():
    lang = os.getenv("LANG")
    if lang:
        lang = lang.split('.')[0] # Consider only the language code
    return lang

def get_translation(lang):
    if lang == "es":
        return {
            "adding_success": "¡Enlace magnético o archivo torrent añadido correctamente a i2psnark!",
            "usage": "Uso: python script.py [archivo.torrent | enlace_magnético | enlace_http_a_torrent]",
            "help": [
                "Opciones:",
                "  -h, --help                 Mostrar este mensaje de ayuda"
            ]
        }
    elif lang == "de":
        return {
            "adding_success": "Magnet-Link oder Torrent-Datei erfolgreich zu i2psnark hinzugefügt!",
            "usage": "Verwendung: python script.py [torrent_datei | magnet_link | http_link_zu_torrent]",
            "help": [
                "Optionen:",
                "  -h, --help                 Diese Hilfemeldung anzeigen"
            ]
        }
    else: # English as default language
        return {
            "adding_success": "Magnet link or torrent file successfully added to i2psnark!",
            "usage": "Usage: python script.py [torrent_file | magnet_link | http_link_to_torrent]",
            "help": [
                "Options:",
                "  -h, --help                 Display this help message"
            ]
        }

def get_nonce(snark_url):
    try:
        response = requests.get(snark_url, timeout=5)
        nonce = response.text.split('name="nonce" value="', 1)[1].split('"', 1)[0]
        return nonce
    except Exception as e:
        logging.error("Error fetching nonce: %s", str(e))
        return None

def log_error(message):
    logging.error(message)
    print(message)

def add_to_i2psnark(input_value, target_dir, snark_url, nonce, translations):
    try:
        url = snark_url + "/_post" # Append _post to the Snark URL
        files = {'newFile': open(input_value, 'rb')} if os.path.isfile(input_value) else {'newFile': None}
        data = {
            'nonce': nonce,
            'p': '1',
            'sort': '-9',
            'action': 'Add',
            'nofilter_newDir': target_dir,
               'nofilter_newURL': input_value, 
            'foo': 'Add torrent'

        }
        response = requests.post(url, files=files, data=data)
        logging.info("Server response: %s", response.text)
        if "Torrent added successfully" in response.text:
            print(translations["adding_success"])
        else:
            log_error("Error adding the magnet link or torrent file to i2psnark.")
    except Exception as e:
        log_error("Error adding the magnet link or torrent file to i2psnark: %s", str(e))

def display_help(translations):
    print(translations["usage"])
    for line in translations["help"]:
        print(line)
    sys.exit()

def main():
    # Set up logging
    config = configparser.ConfigParser()
    config.read('i2pSnarkFeeding.ini')
    logfile = config['DEFAULT'].get('logfile', 'i2pSnarkFeeding.log')
    setup_logging(logfile)

    # Language detection
    lang = get_language()
    translations = get_translation(lang)

    # Read configuration file
    target_dir = config['DEFAULT'].get('target_dir', '')

    # Get nonce
    snark_url = config['DEFAULT'].get('snark_url', 'http://127.0.0.1:7657/i2psnark')
    nonce = get_nonce(snark_url)
    if nonce:
        # Process command-line arguments
        if len(sys.argv) == 1 or "-h" in sys.argv or "--help" in sys.argv:
            display_help(translations)
        else:
            input_value = sys.argv[1]
            if os.path.isfile(input_value):
                add_to_i2psnark(input_value, target_dir, snark_url, nonce, translations)
            elif re.match(r'^magnet:', input_value):
                add_to_i2psnark(input_value, target_dir, snark_url, nonce, translations)
            elif urlparse(input_value).scheme.startswith('http'):
                add_to_i2psnark(input_value, target_dir, snark_url, nonce, translations)
            else:
                print("Invalid input. Please provide either a torrent file, a magnet link, or a link to a torrent file.")

if __name__ == "__main__":
    main()

:~/bin$ cat ~/bin/i2pSnarkFeeding
#!/bin/bash

# i2pSnarkFeedingWrapper.sh

# Logfile path
LOG_FILE="~/bin/i2pSnarkFeedingWrapper.log"

# Log command line parameters
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Command line parameters: $@" >> "$LOG_FILE"

# Call the Python script with the provided parameters
python3 ~/bin/i2pSnarkFeeding.py "$@" >> "$LOG_FILE" 2>&1
User avatar
Phithue7
Posts: 24
Joined: 15 Jun 2024 12:38

Re: fixing i2psnarks new form with pythonscript

Post by Phithue7 »

Inspired by the above idea, I tried it with Linux basic tools,
curl, grep, cut, head, tail and bash.

Put it into a shell bash script, the 1st and only parameter ($1) is the magnet or torrent url.
Adjust snarkurl if required.
If called in a terminal, you see the snark log messages afterwards
The script has no error handling and WILL break on any change on the website.

Code: Select all

magnet="$1"

# CONFIG
snarkurl="http://127.0.0.1:7657/i2psnark/"

# get nonce
nonce=$(curl --silent "${snarkurl}" | grep 'nonce' | grep 'value=' | cut -d'"' -f6 | head -1)
boundary="$(date +%s%s)"
postdata="$(printf -- '--%s\r\nContent-Disposition: form-data; name="nonce"\r\n\r\n%s\r\n--%s\r\nContent-Disposition: form-data; name="action"\r\n\r\nAdd\r\n--%s\r\nContent-Disposition: form-data; name="nofilter_newURL"\r\n\r\n%s\r\n--%s\r\nContent-Disposition: form-data; name="foo"\r\n\r\nAdd torrent\r\n--%s\r\nContent-Disposition: form-data; name="newFile"; filename=""\r\nContent-Type: application/octet-stream\r\n\r\n--%s\r\nContent-Disposition: form-data; name="nofilter_newDir"\r\n\r\n\r\n--%s--\r\n' "$boundary" "${nonce}" "$boundary" "$boundary" "${magnet}" "$boundary" "$boundary" "$boundary" "$boundary")" 

# do post
curl "${snarkurl}_post" \
--request POST \
--header "Content-Type: multipart/form-data; boundary=$boundary" \
--data-binary "$postdata"

# get messages
echo "=============================================================================="
curl --silent "${snarkurl}" | grep --after-context=2 'clear messages' | tail -1
echo "=============================================================================="
User avatar
Phithue7
Posts: 24
Joined: 15 Jun 2024 12:38

Re: fixing i2psnarks new form with pythonscript

Post by Phithue7 »

+ feedback for desktop users with notify-send:

Code: Select all

# get messages
message="$(curl --silent "${snarkurl}" | grep --after-context=2 'clear messages' | tail -1)"
echo "=============================================================================="
echo "${message}"
echo "=============================================================================="
notify-send --app-name="snark-helper" "${message}"
User avatar
Phithue7
Posts: 24
Joined: 15 Jun 2024 12:38

Re: fixing i2psnarks new form with pythonscript

Post by Phithue7 »

v1 published, waiting for further feedback.
Contains also a screenshots and a readme.

http://tracker2.postman.i2p/index.php?v ... l&id=79456
User avatar
i2plus
Posts: 5
Joined: 28 Dec 2023 00:25

Re: fixing i2psnarks new form with pythonscript

Post by i2plus »

I've added some basic error handling, an optional argument --enable-notifications, removed any html tags from the notification and cleaned up the code:

Code: Select all

#!/bin/sh
# Requirements (Linux): curl, grep, cut, head, printf, tail, date
# Optional requirements for desktop notification: notify-send
# I2PSnark (embedded or standalone)
# Initial idea by Titus
# Shell script conversion by Phithue7
# Error handling, notification toggle, cleanups by dr|z3d

# magnet url as 1st parameter, with '--enable-notifications' to enable desktop notifications
magnet="$1"

# Your I2PSnark url, ending with "/"
snark_url="http://127.0.0.1:7657/i2psnark/"

# Check if the argument is "--enable-notifications" to enable notifications or anything else to disable
if [[ "$1" == "--enable-notifications" ]]; then
    shift
    # Set enable_notifications to true
    enable_notifications=true
else
    # Set enable_notifications to false by default
    enable_notifications=false
fi

# check if magnet url is provided
if [[ -z "$magnet" ]]; then
    echo " ! Error: Please provide a magnet URL as the first parameter."
    exit 1
fi

# get nonce
nonce=$(curl --silent "${snark_url}" | grep -oP '(?<=nonce value=)"[^"]+' | head -1)

# boundary
boundary="$(date +%s%s)"

# post data
post_data=$(cat << EOF
--${boundary}\r
Content-Disposition: form-data; name="nonce"\r
\r
${nonce}\r
--${boundary}\r
Content-Disposition: form-data; name="action"\r
\r
Add\r
--${boundary}\r
Content-Disposition: form-data; name="nofilter_newURL"\r
\r
${magnet}\r
--${boundary}\r
Content-Disposition: form-data; name="foo"\r
\r
Add torrent\r
--${boundary}\r
Content-Disposition: form-data; name="newFile"; filename=""\r
Content-Type: application/octet-stream\r
\r
--${boundary}\r
Content-Disposition: form-data; name="nofilter_newDir"\r
\r
\r
--${boundary}--\r
EOF
)

# do post
post_response=$(curl --silent --write-out %{http_code} \
"${snark_url}_post" \
--request POST \
--header "Content-Type: multipart/form-data; boundary=${boundary}" \
--data-binary "${post_data}" \
| tail -1)

# check if post was successful
if [[ "${post_response}" -eq 200 ]]; then
    # get result / messages and strip html tags from the output
    message=$(curl --silent "${snark_url}" | grep --after-context=2 'clear messages' | tail -1 | sed 's/<[^>]*>//g')
    echo "=============================================================================="
    echo "${message}"
    echo "=============================================================================="
fi

# Check if notifications are enabled
if [ "$enable_notifications" = true ]; then
    notify-send --app-name="${snark_url}" "${message}"
fi

# handle 404 error
if [[ "${post_response}" -eq 404 ]]; then
    echo " ! Error: The I2PSnark URL could not be found."
else
    echo " ! Post request failed with status code: ${post_response}"
fi
User avatar
lgillis
Posts: 165
Joined: 20 Oct 2018 12:52
Contact:

Re: fixing i2psnarks new form with pythonscript

Post by lgillis »

s@#!/bin/sh@#!/bin/bash :: shellcheck [SC3010]: In POSIX sh, [[ ]] is undefined.
Luther H. Gillis · Private Investigator · Discreet & Confidential
User avatar
Phithue7
Posts: 24
Joined: 15 Jun 2024 12:38

Re: fixing i2psnarks new form with pythonscript

Post by Phithue7 »

Yes, shellcheck complains:
(warning): In POSIX sh, [[ ]] is undefined.
Also '%{http_code}' would like single quotes I think.

But these are minor thing - I can't get your version to run.

a) the "nonce" does not get fetched. did you consider negative values ?
<input type="hidden" name="nonce" value="-6103078905641412345" >
Putting an extra echo $nonce returns an empty value.

b) the form_data contains literal \r if you printf it for testing - I think this will not work.
Thus I like your version better, if it would work, because it's better readable.

c) putting in a valid nonce I still get
No summary specified.
! Post request failed with status code: 500

d) --enable-notifications as parameter will not work for firefox users, since they can forward per click only one parameter, the magnet url, to the script, not the additional option

I think the desktop notifications is the most important one on this helper script: 1. It gives you a feedback that the click is processed, 2. it informs you that a torrent is already running, when clicking a 2nd time on a magnet - in case you forget if you have it already running.

P.S. I use the "official" i2psnark, did you maybe test with your plus version ? Do we maybe need here another script version ?
User avatar
lgillis
Posts: 165
Joined: 20 Oct 2018 12:52
Contact:

Re: fixing i2psnarks new form with pythonscript

Post by lgillis »

Phithue7 wrote: 19 Aug 2024 16:09 But these are minor thing - I can't get your version to run.
If that's how you see it, then I hope they never involve you in developing critical software to protect other people's asses.
http://discuss.i2p/viewtopic.php?p=439#p439
Luther H. Gillis · Private Investigator · Discreet & Confidential
User avatar
i2plus
Posts: 5
Joined: 28 Dec 2023 00:25

Re: fixing i2psnarks new form with pythonscript

Post by i2plus »

Latest revision, based on feedback (untested).

Code: Select all

#!/bin/sh

# Requirements (Linux): curl, grep, cut, head, printf, tail, date
# Optional requirements for desktop notification: notify-send
# I2PSnark (embedded or standalone)
# Initial idea by Titus
# Shell script conversion by Phithue7
# Error handling, notification toggle, cleanups by dr|z3d

magnet="$1"
snark_url="http://127.0.0.1:7657/i2psnark/"

# Set enable_notifications to true by default
enable_notifications=true

# check if magnet url is provided
if [ -z "$magnet" ]; then
    echo " ! Error: Please provide a magnet URL as the only argument."
    exit 1
fi

# get nonce
nonce=$(curl --silent "${snark_url}" | grep -oP '(?<=<input type="hidden" name="nonce" value=")[^"]+' | head -1)

# boundary
boundary="$(date +%s%s)"

# post data
post_data=$(cat <<-EOF
--${boundary}
Content-Disposition: form-data; name="nonce"

${nonce}
--${boundary}
Content-Disposition: form-data; name="action"

Add
--${boundary}
Content-Disposition: form-data; name="nofilter_newURL"

${magnet}
--${boundary}
Content-Disposition: form-data; name="foo"

Add torrent
--${boundary}
Content-Disposition: form-data; name="newFile"; filename=""
Content-Type: application/octet-stream

--${boundary}
Content-Disposition: form-data; name="nofilter_newDir"

--${boundary}--

EOF
)

# do post
post_response=$(curl --silent --write-out '%{http_code}' \
    "${snark_url}_post" \
    --request POST \
    --header "Content-Type: multipart/form-data; boundary=${boundary}" \
    --data-binary "${post_data}" \
    | tail -1)

# check if post was successful
if [ "$post_response" -eq 200 ]; then
    # get result / messages and strip html tags from the output
    message=$(curl --silent "${snark_url}" | grep --after-context=2 'clear messages' | tail -1 | sed 's/<[^>]*>//g')
    echo "=============================================================================="
    echo "${message}"
    echo "=============================================================================="
fi

# Check if notifications are enabled
if [ "$enable_notifications" = true ]; then
    notify-send --app-name="${snark_url}" "${message}"
fi

# handle 404 error
if [ "$post_response" -eq 404 ]; then
    echo " ! Error: The I2PSnark URL could not be found."
else
    echo " ! Post request failed with status code: $post_response"
fi
Post Reply