diff --git a/mapillary_sequences_userscript/README.md b/mapillary_sequences_userscript/README.md new file mode 100644 index 00000000..ed9d48ec --- /dev/null +++ b/mapillary_sequences_userscript/README.md @@ -0,0 +1,23 @@ +# Export en masse de photos de Mapillary vers panoramax + +* avoir un token développeur mapillary +* se rendre sur la page mapillary d'un utilisateur dont on souhaite obtenir les identifiants de séquences de photos pour exporter les contenus des séquences photos, exemple: https://www.mapillary.com/app/user/gendy54 +* copier secret_variables_examples.sh et remplir avec un token de développeur Mapillary (ça demande d'avoir toujours un compte Mapillary fonctionnel ou de demander à quelqu'un qui en a un) +* cp secret_variables_examples.sh secret_variables.sh +* editor secret_variables.sh +* Le pseudo d'utilisateur mapillary et son numéro d'ID sont trouvables en allant sur leur page, dans la requete graphql latestActivity. +* Recopiez la requete en tant que cURL et collez là en remplacement de celle dans curl_land.sh +* Notez l'ID de l'utilisateur. +* Lancez la récupération des identifiants de séquence. +* `bash get_user.sh boblennon 10123456` +* Vous obtenez un script bash selon le nom d'utilisateur demandé (script_bash_get_sequences_for_user_boblennon.sh ) à lancer dans le dossier de mapillary_download.py du dépot https://github.com/Stefal/mapillary_download.git . +* Pour ce faire, clonez le dépot mapillary_download et déplacez dedans les scripts bash générés par cet exportateur. +* Si vous avez les libs (suivez les instructions d'installation de mapillary_download) vous devriez pouvoir récupérer vos séquences de photos mapillary. Et si votre connexion internet plante vous pourrez relancer ce script, ça n'ira pas retélécharger les photos déjà trouvées. +* Vous pourrez profiter de vos photos dans le dossier data, elles sont rangées par utilisateur et par séquence. + +# Envoi vers Panoramax en masse +* Pour envoyer en masse les séquences téléchargées, utilisez le script batch_geovisio_avec_token.sh , il faut le copier dans un dossier qui contient plusieurs séquences et avoir installé geovisio_cli avec python pip. +* bash batch_geovisio_avec_token.sh +* Si vous avez bien rempli le fichier secrets_variables.sh ça devrait envoyer vers panoramax, sur l'instance osm fr. +* Au premier lancement du script il faudra vous identifier avec votre compte Panoramax, qui sur l'instance osm fr est relié à un compte osm. +* Enjaillez! diff --git a/mapillary_sequences_userscript/curl_land.sh b/mapillary_sequences_userscript/curl_land.sh new file mode 100644 index 00000000..4bd78675 --- /dev/null +++ b/mapillary_sequences_userscript/curl_land.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# se rendre sur la page de l'utilisateur concerné avec firefox +# trouver la requête graphql qui fait le fetch latest activity +# copier la valeur curl de la requete dans les devtool firefox +# remplacer la valeur 10000 qui limite le nombre de séquences par disons 10000 +# lancer la commande : bash curl_land.sh > out.json +source secrets_variables.sh + + + + + + + +curl 'https://graph.mapillary.com/graphql?doc=query%20getLatestActivity(%24id%3A%20ID!%2C%20%24first%3A%20Int%2C%20%24after%3A%20ID%2C%20%24hide_after%3A%20Int)%20%7B%0A%20%20%20%20%20%20fetch__User(id%3A%20%24id)%20%7B%0A%20%20%20%20%20%20%20%20id%0A%20%20%20%20%20%20%20%20feed(first%3A%20%24first%2C%20after%3A%20%24after%2C%20hide_failed_sequences_after_days%3A%20%24hide_after)%20%7B%0A%20%20%20%20%20%20%20%20%20%20page_info%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20start_cursor%0A%20%20%20%20%20%20%20%20%20%20%20%20end_cursor%0A%20%20%20%20%20%20%20%20%20%20%20%20has_next_page%0A%20%20%20%20%20%20%20%20%20%20%20%20has_previous_page%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20nodes%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20cluster_id%20type%20created_at_seconds%20captured_at_seconds%20thumb_url%20item_count%20image_id%20status%20initial_processing_status%20anonymization_status%20tiler_status%20error_code%20timezone%0A%20%20%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D&query=query%20getLatestActivity(%24id%3A%20ID!%2C%20%24first%3A%20Int%2C%20%24after%3A%20ID%2C%20%24hide_after%3A%20Int)%20%7B%0A%20%20fetch__User(id%3A%20%24id)%20%7B%0A%20%20%20%20id%0A%20%20%20%20feed(%0A%20%20%20%20%20%20first%3A%20%24first%0A%20%20%20%20%20%20after%3A%20%24after%0A%20%20%20%20%20%20hide_failed_sequences_after_days%3A%20%24hide_after%0A%20%20%20%20)%20%7B%0A%20%20%20%20%20%20page_info%20%7B%0A%20%20%20%20%20%20%20%20start_cursor%0A%20%20%20%20%20%20%20%20end_cursor%0A%20%20%20%20%20%20%20%20has_next_page%0A%20%20%20%20%20%20%20%20has_previous_page%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20nodes%20%7B%0A%20%20%20%20%20%20%20%20cluster_id%0A%20%20%20%20%20%20%20%20type%0A%20%20%20%20%20%20%20%20created_at_seconds%0A%20%20%20%20%20%20%20%20captured_at_seconds%0A%20%20%20%20%20%20%20%20thumb_url%0A%20%20%20%20%20%20%20%20item_count%0A%20%20%20%20%20%20%20%20image_id%0A%20%20%20%20%20%20%20%20status%0A%20%20%20%20%20%20%20%20initial_processing_status%0A%20%20%20%20%20%20%20%20anonymization_status%0A%20%20%20%20%20%20%20%20tiler_status%0A%20%20%20%20%20%20%20%20error_code%0A%20%20%20%20%20%20%20%20timezone%0A%20%20%20%20%20%20%20%20__typename%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D%0A%20%20%20%20__typename%0A%20%20%7D%0A%20%20__typename%0A%7D&operationName=getLatestActivity&variables=%7B%22id%22%3A%22100515188859784%22%2C%22first%22%3A200%2C%22after%22%3Anull%2C%22hide_after%22%3A14%7D' --compressed -H 'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0' -H 'Accept: */*' -H 'Accept-Language: fr,en-US;q=0.7,en;q=0.3' -H 'Accept-Encoding: gzip, deflate, br, zstd' -H 'Referer: https://www.mapillary.com/' -H 'content-type: application/json' -H 'authorization: OAuth EEEEEEEEEEEEEEEEEEEEEEEEEE' -H 'Origin: https://www.mapillary.com' -H 'Connection: keep-alive' -H 'Sec-Fetch-Dest: empty' -H 'Sec-Fetch-Mode: cors' -H 'Sec-Fetch-Site: same-site' -H 'Sec-GPC: 1' -H 'Priority: u=4' -H 'TE: trailers' diff --git a/mapillary_sequences_userscript/get_sequences_of_username.py b/mapillary_sequences_userscript/get_sequences_of_username.py new file mode 100644 index 00000000..6b4e171c --- /dev/null +++ b/mapillary_sequences_userscript/get_sequences_of_username.py @@ -0,0 +1,87 @@ +import json +import requests +# lit un json listant les id de photo de chaque séquence et va +# chercher la séquence par API. + +import argparse + +def parse_args(argv =None): + parser = argparse.ArgumentParser() + parser.add_argument('--username', type=str, help='Username to get the sequences id of') + parser.add_argument('--dev_token', type=str, help='Your mapillary developer token') + parser.add_argument('--max_sequence', type=str, help='Username to get the sequences id of') + + global args + args = parser.parse_args(argv) + print(args) + + + +# Initialisation de la liste pour stocker les réponses +responses = [] +sequences = [] + +def get_image_data_from_sequences(): + username = args.username + input_file = "out_"+username+".json" + + + # Chargement du fichier JSON d'entrée + with open(input_file, "r") as file: + input_data = json.load(file) + + # Itération sur les noeuds pour collectionner les image_ids + nodelist = input_data["data"]["fetch__User"]["feed"]["nodes"] + print( 'séquences : ', len(nodelist)) + image_ids = [node["image_id"] for node in nodelist] + print(image_ids) + + dev_token = args.dev_token + + # Préparation de la tête d'autorisation pour toutes les futures requêtes + header = {"Access-Token": dev_token} + + ii=0 + limit_requests = 1000000000 +# limit_requests = 5 # pour tester + # Boucle sur chaque image_id pour interroger l'API Mapillary + for image_id in image_ids: + ii+=1 + if limit_requests >= ii and image_id: + params = {"id": image_id, "fields": "id,sequence"} + request_url = "https://graph.mapillary.com/" + str(image_id)+"?access_token="+dev_token+"&fields=id,sequence" + response = requests.get(request_url) + + # Analyse de la réponse + parsed_response = {} + if response.ok and response.status_code == 200: + raw_response = response.json() + + parsed_response["id"] = raw_response["id"] + parsed_response["sequence"] = raw_response["sequence"] + sequences.append(parsed_response["sequence"]) + + print("séquence trouvée: "+str(ii)+"/"+args.max_sequence+" : "+raw_response["sequence"]) + else: + print(response) + + responses.append(parsed_response) + +def persist_files(): + # Sauvegarde des nouveaux résultats dans le fichier output.json + output_file = "sequences_"+args.username+".json" + + with open(output_file, "w") as file: + json.dump(responses, file) + + sequence_filename = "sequences_"+args.username+".txt" + with open(sequence_filename, "w") as file: + json.dump(sequences, file) + print('fichier sauvegardé: '+sequence_filename) + + +parse_args() +get_image_data_from_sequences() +persist_files() + +# si la requete donne moins du max de noeuds on prévoit d'en relancer une nouvelle pour avoir la suite diff --git a/mapillary_sequences_userscript/get_user.sh b/mapillary_sequences_userscript/get_user.sh new file mode 100644 index 00000000..3e599171 --- /dev/null +++ b/mapillary_sequences_userscript/get_user.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# lancement de la récupération des identifiants de séquences +# exemple: +# bash get_user.sh binerf 102718865306727 + +source secrets_variables.sh + +export username=$1 +export num_user=$2 +echo "télécharger la séquence pour l'utilisateur $username, $num_user" +bash curl_land.sh "$username" "$num_user" > "out_${username}.json" + +echo "séquences récupérées:" +num_sequences=$(grep -o -w 'image_id' "out_${username}.json" | wc -l) +# +if (( num_sequences > 0 )) +then + echo "Séquences trouvées: (${num_sequences}). Noice." + python3 get_sequences_of_username.py --username="$username" --max_sequence="$num_sequences" --dev_token="$MAPILLARY_DEV_TOKEN" + python3 text_array_to_download_script.py --username="$username" --dev_token="$MAPILLARY_DEV_TOKEN" +## +else + echo "Aucune séquence trouvée (${num_sequences}) ! Pas d'autres actions à entreprendre." + cat "out_${username}.json" +fi + + diff --git a/mapillary_sequences_userscript/requirements.txt b/mapillary_sequences_userscript/requirements.txt new file mode 100644 index 00000000..c55c24c5 --- /dev/null +++ b/mapillary_sequences_userscript/requirements.txt @@ -0,0 +1,303 @@ +addok==1.1.2 +addok-csv==1.1.0 +addok-fr==1.0.1 +addok-france==1.1.3 +ansible==7.7.0 +ansible-core==2.14.9 +anyio==4.3.0 +apache-libcloud==3.4.1 +appdirs==1.4.4 +appendfilename==2022.1.4.1 +apsw==3.42.0.1 +argcomplete==2.0.0 +argon2-cffi==23.1.0 +argon2-cffi-bindings==21.2.0 +args==0.1.0 +arrow==1.3.0 +asttokens==2.2.1 +async-lru==2.0.4 +async-timeout==4.0.2 +attrs==23.2.0 +Babel==2.10.3 +backcall==0.2.0 +beautifulsoup4==4.12.2 +beniget==0.4.1 +bleach==6.1.0 +blinker==1.6.2 +Brlapi==0.8.4 +Brotli==1.0.9 +certifi==2022.9.24 +cffi==1.16.0 +chardet==5.1.0 +charset-normalizer==3.3.2 +click==8.1.6 +clint==0.5.1 +cloud-init==24.1.3 +colorama==0.4.6 +comm==0.2.2 +command-not-found==0.3 +configobj==5.0.8 +contourpy==1.0.7 +cryptography==38.0.4 +css-parser==1.0.9 +cssselect==1.2.0 +cups-of-caffeine==2.9.12 +cupshelpers==1.0 +cycler==0.11.0 +date2name==2024.3.10.1 +dbus-python==1.3.2 +debugpy==1.8.1 +decorator==5.1.1 +defer==1.0.6 +defusedxml==0.7.1 +distlib==0.3.8 +distro==1.8.0 +distro-info==1.5+ubuntu0.23.10.1 +dnspython==2.4.1 +docker==5.0.3 +docker-compose==1.29.2 +dockerpty==0.4.1 +docopt==0.6.2 +editdistance==0.6.2 +ewmh==0.1.6 +executing==1.2.0 +fail2ban==1.0.2 +falcon==3.1.1 +fastjsonschema==2.19.1 +feedparser==6.0.10 +filelock==3.14.0 +filetags==2024.2.12.1 +fonttools==4.38.0 +fqdn==1.5.1 +fs==2.4.16 +fuse-python==1.0.5 +fuzzywuzzy==0.18.0 +gast==0.5.2 +geckodriver==0.0.1 +geopic-tag-reader==1.0.5 +geovisio_cli==0.3.9 +gnureadline==8.1.2 +gpg==1.18.0 +graphql-core==3.2.3 +graphqlclient==0.2.4 +guessfilename==2023.2.5.2 +h11==0.14.0 +h3==3.7.7 +hashids==1.3.1 +haversine==2.8.1 +hiredis==2.2.2 +html2text==2020.1.16 +html5-parser==0.4.11 +html5lib==1.1 +httpcore==1.0.5 +httpie==3.2.2 +httplib2==0.20.4 +httpx==0.27.0 +idna==3.3 +ifaddr==0.2.0 +importlib-metadata==4.12.0 +iniconfig==2.0.0 +ipykernel==6.29.4 +ipython==8.14.0 +ipywidgets==8.1.2 +isoduration==20.11.0 +jaraco.classes==3.2.1 +jedi==0.18.2 +jeepney==0.8.0 +Jinja2==3.1.2 +jmespath==1.0.1 +jq==1.7.0 +json5==0.9.25 +jsonpatch==1.32 +jsonpointer==2.0 +jsonschema==4.22.0 +jsonschema-specifications==2023.12.1 +jupyter==1.0.0 +jupyter-console==6.6.3 +jupyter-events==0.10.0 +jupyter-lsp==2.2.5 +jupyter_client==8.6.1 +jupyter_core==5.7.2 +jupyter_server==2.14.0 +jupyter_server_terminals==0.5.3 +jupyterlab==4.1.8 +jupyterlab_pygments==0.3.0 +jupyterlab_server==2.27.1 +jupyterlab_widgets==3.0.10 +keyring==24.2.0 +kiwisolver==0.0.0 +language-selector==0.1 +launchpadlib==1.11.0 +lazr.restfulclient==0.14.5 +lazr.uri==1.0.6 +Levenshtein==0.25.0 +lockfile==0.12.2 +louis==3.26.0 +lxml==4.9.3 +lz4==4.0.2+dfsg +Mako==1.2.4.dev0 +Markdown==3.4.4 +markdown-it-py==3.0.0 +MarkupSafe==2.1.3 +matplotlib==3.6.3 +matplotlib-inline==0.1.6 +mdurl==0.1.2 +mechanize==0.4.8 +mistune==3.0.2 +more-itertools==10.1.0 +move2archive==2023.7.15.1 +mpmath==0.0.0 +msgpack==1.0.3 +multidict==6.0.5 +mutagen==1.47.0 +nbclient==0.10.0 +nbconvert==7.16.4 +nbformat==5.10.4 +nest-asyncio==1.6.0 +netaddr==0.8.0 +netifaces==0.11.0 +ngram==4.0.3 +notebook==7.1.3 +notebook_shim==0.2.4 +ntlm-auth==1.4.0 +numpy==1.24.2 +oauthlib==3.2.2 +olefile==0.46 +outcome==1.3.0.post0 +overrides==7.7.0 +packaging==23.1 +pandas==2.2.1 +pandocfilters==1.5.1 +parso==0.8.3 +passlib==1.7.4 +pexpect==4.8.0 +pickleshare==0.7.5 +Pillow==10.0.0 +platformdirs==4.2.1 +pluggy==1.4.0 +ply==3.11 +progressist==0.1.0 +prometheus_client==0.20.0 +prompt-toolkit==3.0.39 +psutil==5.9.4 +ptyprocess==0.7.0 +pure-eval==0.0.0 +py7zr==0.11.3+dfsg +pycairo==1.24.0 +pychm==0.8.6 +pycparser==2.21 +pycryptodomex==3.11.0 +pycups==2.0.1 +PyExifTool==0.5.6 +pyexiv2==2.8.3 +Pygments==2.15.1 +PyGObject==3.46.0 +pyinotify==0.9.6 +PyJWT==2.7.0 +pykerberos==1.1.14 +pylibacl==0.7.0 +pyparsing==3.1.0 +pypdf==4.1.0 +PyPDF2==3.0.1 +pypng==0.20220715.0 +PyQt5==5.15.9 +PyQt5-sip==12.12.2 +PyQt6==6.5.2 +PyQt6-sip==13.5.2 +PyQt6-WebEngine==6.5.0 +pyreadline==2.1 +pyreadline3==3.4.1 +pyrsistent==0.18.1 +pyserial==3.5 +PySocks==1.7.1 +pytest==8.1.1 +python-apt==2.6.0+ubuntu1 +python-dateutil==2.8.2 +python-debian==0.1.49+ubuntu2 +python-dotenv==1.0.1 +python-geohash==0.8.5 +python-json-logger==2.0.7 +python-Levenshtein==0.25.0 +python-xlib==0.33 +pythran==0.11.0 +pytz==2023.3 +pywinrm==0.3.0 +pyxattr==0.8.1 +pyxdg==0.28 +PyYAML==6.0.1 +pyzmq==26.0.3 +qrcode==7.4.2 +qtconsole==5.5.2 +QtPy==2.4.1 +rapidfuzz==3.6.2 +redis==4.5.4 +referencing==0.35.1 +regex==2022.10.31 +repoze.lru==0.7 +requests==2.31.0 +requests-kerberos==0.12.0 +requests-ntlm==1.1.0 +requests-toolbelt==1.0.0 +resolvelib==1.0.1 +rfc3339-validator==0.1.4 +rfc3986-validator==0.1.1 +rich==13.7.1 +Routes==2.5.1 +rpds-py==0.18.1 +scipy==1.10.1 +screen-resolution-extra==0.0.0 +SecretStorage==3.3.3 +selenium==4.18.1 +selinux @ file:///build/libselinux-YCoZMU/libselinux-3.5/src +Send2Trash==1.8.3 +sgmllib3k==1.0.0 +simplejson==3.19.1 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +soupsieve==2.4.1 +ssh-import-id==5.11 +stack-data==0.6.2 +sympy==1.12 +systemd-python==235 +terminado==0.18.1 +terminator==2.1.3 +texttable==1.6.7 +timezonefinder==6.5.0 +tinycss2==1.3.0 +tomli==2.0.1 +tomli_w==1.0.0 +torbrowser-launcher==0.3.6 +tornado==6.3.2 +traitlets==5.14.3 +trio==0.25.0 +trio-websocket==0.11.1 +typer==0.9.0 +types-python-dateutil==2.9.0.20240316 +typing_extensions==4.10.0 +tzdata==2024.1 +ubuntu-drivers-common==0.0.0 +ubuntu-pro-client==8001 +ufoLib2==0.14.0 +ufw==0.36.2 +unattended-upgrades==0.1 +Unidecode==1.3.6 +uri-template==1.3.0 +urllib3==2.2.1 +virtualenv==20.26.1 +wadllib==1.3.6 +wcwidth==0.2.5 +webcolors==1.13 +webdriver-manager==4.0.1 +webencodings==0.5.1 +WebOb==1.8.6 +websocket-client==1.8.0 +websockets==12.0 +widgetsnbextension==4.0.10 +wsproto==1.2.0 +xdg==5 +xkit==0.0.0 +xmltodict==0.13.0 +yt-dlp==2024.3.10 +zeroconf==0.76.0 +zipp==1.0.0 diff --git a/mapillary_sequences_userscript/secrets_variables_example.sh b/mapillary_sequences_userscript/secrets_variables_example.sh new file mode 100644 index 00000000..f7dd09f2 --- /dev/null +++ b/mapillary_sequences_userscript/secrets_variables_example.sh @@ -0,0 +1,9 @@ +# scripts common shared variables +# this is to copy as a file named "secrets_variables.sh" which is ignored in this git repo + +export OAUTH_TOKEN="MLYabcdeeeeeeeeeeeeee_replace_it" +export MAPILLARY_DEV_TOKEN="MLY|blahblah_replace_it" +export USER_ID_MAPILLARY="123456789_replace_it" +export GEOVISIO_TOKEN="_replace_it" +# you can keep it +export SEQUENCES_COUNT="10000" diff --git a/mapillary_sequences_userscript/text_array_to_download_script.py b/mapillary_sequences_userscript/text_array_to_download_script.py index 972ac8f7..348e309f 100644 --- a/mapillary_sequences_userscript/text_array_to_download_script.py +++ b/mapillary_sequences_userscript/text_array_to_download_script.py @@ -1,8 +1,11 @@ +import os + +input_file = 'input_file' import argparse def parse_args(argv =None): parser = argparse.ArgumentParser() -# parser.add_argument('--access_token', type=str, help='Your mapillary access token') + parser.add_argument('--dev_token', type=str, help='Your mapillary access token') parser.add_argument('--username', type=str, help='Username to get the sequences id of') global args @@ -11,14 +14,28 @@ def parse_args(argv =None): if __name__ == '__main__': + print("Construction du script bash de récupération des images de chaque séquences pour Mapillary_download (https://github.com/Stefal/mapillary_download.git)") + parse_args() username=args.username input_file = f"sequences_{username}.txt" + + if not args.dev_token: + print(f"Erreur : Le token de développeur de mapillary manque, vérifiez le fichier de variables secretes. Arrêt du script.") + exit(1) + + if not os.path.exists(input_file) or not os.path.isfile(input_file): + print(f"Erreur : Le fichier '{input_file}' n'a pas été trouvé. Arrêt du script.") + exit(1) + else: + print(f"Fichier '{input_file}' trouvé.") + + output_file = f"script_bash_get_sequences_for_user_{username}.sh" - access_token = "--access_token=\"mly_token_A_REMPLACERRRRRRRRRRRR\" " - format_string = "python download.py {} --sequence_id={}\n" + access_token = "--access_token="+args.dev_token+" " + format_string = "/usr/bin/python3 mapillary_download.py {} --sequence_id={}\n" with open(output_file, "w") as output: @@ -26,9 +43,11 @@ if __name__ == '__main__': content = input_handle.read() sequences = eval(content) for seq in sequences: - full_cmd = f"python download.py {access_token} --sequence_id={seq} --username={username}\n" + full_cmd = f"/usr/bin/python3 mapillary_download.py {access_token} --sequence_id={seq} --username={username}\n" output.write(full_cmd) - print(f"Script Bash généré : {output_file}") - print(output_file) + + print(f"\n Script Bash généré avec succès.") + print(f"Lancez le pour récupérer les photos de l'utilisateur {username}: \n bash {output_file}") + diff --git a/mapillary_sequences_userscript/text_curl.sh b/mapillary_sequences_userscript/text_curl.sh new file mode 100644 index 00000000..e69de29b