iInitial commit - transferwee - Download/upload file via wetransfer.com Err tccr.it 70 hgit clone https://github.com/iamleot/transferwee URL:https://github.com/iamleot/transferwee tccr.it 70 1Log /r/transferwee/log.gph tccr.it 70 1Files /r/transferwee/files.gph tccr.it 70 1Refs /r/transferwee/refs.gph tccr.it 70 1README /r/transferwee/file/README.md.gph tccr.it 70 i--- Err tccr.it 70 1commit 552965b43fb08c01ea2607112146482d50ca9519 /r/transferwee/commit/552965b43fb08c01ea2607112146482d50ca9519.gph tccr.it 70 hAuthor: Leonardo Taccari URL:mailto:iamleot@gmail.com tccr.it 70 iDate: Tue, 8 May 2018 00:24:06 +0200 Err tccr.it 70 i Err tccr.it 70 iInitial commit Err tccr.it 70 i Err tccr.it 70 iDiffstat: Err tccr.it 70 i A transferwee.py | 381 +++++++++++++++++++++++++++++++ Err tccr.it 70 i Err tccr.it 70 i1 file changed, 381 insertions(+), 0 deletions(-) Err tccr.it 70 i--- Err tccr.it 70 1diff --git a/transferwee.py b/transferwee.py /r/transferwee/file/transferwee.py.gph tccr.it 70 i@@ -0,0 +1,381 @@ Err tccr.it 70 i+#!/usr/pkg/bin/python3.6 Err tccr.it 70 i+ Err tccr.it 70 i+# Err tccr.it 70 i+# Copyright (c) 2018 Leonardo Taccari Err tccr.it 70 i+# All rights reserved. Err tccr.it 70 i+# Err tccr.it 70 i+# Redistribution and use in source and binary forms, with or without Err tccr.it 70 i+# modification, are permitted provided that the following conditions Err tccr.it 70 i+# are met: Err tccr.it 70 i+# Err tccr.it 70 i+# 1. Redistributions of source code must retain the above copyright Err tccr.it 70 i+# notice, this list of conditions and the following disclaimer. Err tccr.it 70 i+# 2. Redistributions in binary form must reproduce the above copyright Err tccr.it 70 i+# notice, this list of conditions and the following disclaimer in the Err tccr.it 70 i+# documentation and/or other materials provided with the distribution. Err tccr.it 70 i+# Err tccr.it 70 i+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS Err tccr.it 70 i+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED Err tccr.it 70 i+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR Err tccr.it 70 i+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS Err tccr.it 70 i+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR Err tccr.it 70 i+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF Err tccr.it 70 i+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS Err tccr.it 70 i+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN Err tccr.it 70 i+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) Err tccr.it 70 i+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE Err tccr.it 70 i+# POSSIBILITY OF SUCH DAMAGE. Err tccr.it 70 i+# Err tccr.it 70 i+ Err tccr.it 70 i+ Err tccr.it 70 i+from typing import List Err tccr.it 70 i+import os.path Err tccr.it 70 i+import urllib.parse Err tccr.it 70 i+import zlib Err tccr.it 70 i+ Err tccr.it 70 i+import requests Err tccr.it 70 i+ Err tccr.it 70 i+ Err tccr.it 70 i+WETRANSFER_API_URL = 'https://wetransfer.com/api/v4/transfers' Err tccr.it 70 i+WETRANSFER_DOWNLOAD_URL = WETRANSFER_API_URL + '/{transfer_id}/download' Err tccr.it 70 i+WETRANSFER_UPLOAD_EMAIL_URL = WETRANSFER_API_URL + '/email' Err tccr.it 70 i+WETRANSFER_UPLOAD_LINK_URL = WETRANSFER_API_URL + '/link' Err tccr.it 70 i+WETRANSFER_FILES_URL = WETRANSFER_API_URL + '/{transfer_id}/files' Err tccr.it 70 i+WETRANSFER_PART_PUT_URL = WETRANSFER_FILES_URL + '/{file_id}/part-put-url' Err tccr.it 70 i+WETRANSFER_FINALIZE_MPP_URL = WETRANSFER_FILES_URL + '/{file_id}/finalize-mpp' Err tccr.it 70 i+WETRANSFER_FINALIZE_URL = WETRANSFER_API_URL + '/{transfer_id}/finalize' Err tccr.it 70 i+ Err tccr.it 70 i+WETRANSFER_DEFAULT_CHUNK_SIZE = 5242880 Err tccr.it 70 i+ Err tccr.it 70 i+ Err tccr.it 70 i+def download_url(url: str) -> str: Err tccr.it 70 i+ """Given a wetransfer.com download URL download return the downloadable URL. Err tccr.it 70 i+ Err tccr.it 70 i+ The URL should be of the form `https://we.tl/' or Err tccr.it 70 i+ `https://wetransfer.com/downloads/'. If it is a short URL (i.e. `we.tl') the Err tccr.it 70 i+ redirect is followed in order to retrieve the corresponding Err tccr.it 70 i+ `wetransfer.com/downloads/' URL. Err tccr.it 70 i+ Err tccr.it 70 i+ Return the download URL (AKA `direct_link') as a str. Err tccr.it 70 i+ """ Err tccr.it 70 i+ # Follow the redirect if we have a short URL Err tccr.it 70 i+ if url.startswith('https://we.tl/'): Err tccr.it 70 i+ r = requests.head(url, allow_redirects=True) Err tccr.it 70 i+ url = r.url Err tccr.it 70 i+ Err tccr.it 70 i+ recipient_id = None Err tccr.it 70 i+ params = url.replace('https://wetransfer.com/downloads/', '').split('/') Err tccr.it 70 i+ Err tccr.it 70 i+ if len(params) == 2: Err tccr.it 70 i+ transfer_id, security_hash = params Err tccr.it 70 i+ elif len(params) == 3: Err tccr.it 70 i+ transfer_id, recipient_id, security_hash = params Err tccr.it 70 i+ Err tccr.it 70 i+ j = { Err tccr.it 70 i+ "security_hash": security_hash, Err tccr.it 70 i+ } Err tccr.it 70 i+ if recipient_id: Err tccr.it 70 i+ j["recipient_id"] = recipient_id Err tccr.it 70 i+ r = requests.post(WETRANSFER_DOWNLOAD_URL.format(transfer_id=transfer_id), json=j) Err tccr.it 70 i+ Err tccr.it 70 i+ j = r.json() Err tccr.it 70 i+ return j.get('direct_link') Err tccr.it 70 i+ Err tccr.it 70 i+ Err tccr.it 70 i+def download(url: str) -> None: Err tccr.it 70 i+ """Given a `we.tl/' or `wetransfer.com/downloads/' download it. Err tccr.it 70 i+ Err tccr.it 70 i+ First a direct link is retrieved, the filename will be extracted to it and Err tccr.it 70 i+ it will be fetched and stored on the current working directory. Err tccr.it 70 i+ """ Err tccr.it 70 i+ dl_url = download_url(url) Err tccr.it 70 i+ file = urllib.parse.urlparse(dl_url).path.split('/')[-1] Err tccr.it 70 i+ Err tccr.it 70 i+ r = requests.get(dl_url, stream=True) Err tccr.it 70 i+ with open(file, 'wb') as f: Err tccr.it 70 i+ for chunk in r.iter_content(chunk_size=1024): Err tccr.it 70 i+ f.write(chunk) Err tccr.it 70 i+ Err tccr.it 70 i+ Err tccr.it 70 i+def _prepare_email_upload(filenames: List[str], message: str, Err tccr.it 70 i+ sender: str, recipients: List[str]) -> str: Err tccr.it 70 i+ """Given a list of filenames, message a sender and recipients prepare for Err tccr.it 70 i+ the email upload. Err tccr.it 70 i+ Err tccr.it 70 i+ Return the parsed JSON response. Err tccr.it 70 i+ Err tccr.it 70 i+ A successfull response is of the following form: Err tccr.it 70 i+ Err tccr.it 70 i+ { Err tccr.it 70 i+ "id": transfer_id, Err tccr.it 70 i+ "security_hash": security_hash, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ "message": message, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ "files": [ Err tccr.it 70 i+ { Err tccr.it 70 i+ "id": file_id, Err tccr.it 70 i+ "name": filename, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ "size": 0, Err tccr.it 70 i+ "chunk_size": 5242880 Err tccr.it 70 i+ } Err tccr.it 70 i+ ], Err tccr.it 70 i+ "recipients": [ Err tccr.it 70 i+ ..., Err tccr.it 70 i+ "email": recipient_email, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ ] Err tccr.it 70 i+ } Err tccr.it 70 i+ """ Err tccr.it 70 i+ j = { Err tccr.it 70 i+ "filenames": filenames, Err tccr.it 70 i+ "from": sender, Err tccr.it 70 i+ "message": message, Err tccr.it 70 i+ "recipients": recipients, Err tccr.it 70 i+ "ui_language": "en", Err tccr.it 70 i+ } Err tccr.it 70 i+ Err tccr.it 70 i+ r = requests.post(WETRANSFER_UPLOAD_EMAIL_URL, json=j) Err tccr.it 70 i+ return r.json() Err tccr.it 70 i+ Err tccr.it 70 i+ Err tccr.it 70 i+def _prepare_link_upload(filenames: List[str], message: str) -> str: Err tccr.it 70 i+ """Given a list of filenames and a message prepare for the link upload. Err tccr.it 70 i+ Err tccr.it 70 i+ Return the parsed JSON response. Err tccr.it 70 i+ Err tccr.it 70 i+ A successfull response is of the following form: Err tccr.it 70 i+ Err tccr.it 70 i+ { Err tccr.it 70 i+ "id": transfer_id, Err tccr.it 70 i+ "security_hash": security_hash, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ "message": message, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ "files": [ Err tccr.it 70 i+ { Err tccr.it 70 i+ "id": file_id, Err tccr.it 70 i+ "name": filename, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ "size": 0, Err tccr.it 70 i+ "chunk_size": 5242880 Err tccr.it 70 i+ } Err tccr.it 70 i+ ], Err tccr.it 70 i+ "recipients": [ Err tccr.it 70 i+ ... Err tccr.it 70 i+ ] Err tccr.it 70 i+ } Err tccr.it 70 i+ """ Err tccr.it 70 i+ j = { Err tccr.it 70 i+ "filenames": filenames, Err tccr.it 70 i+ "message": message, Err tccr.it 70 i+ "ui_language": "en", Err tccr.it 70 i+ } Err tccr.it 70 i+ Err tccr.it 70 i+ r = requests.post(WETRANSFER_UPLOAD_LINK_URL, json=j) Err tccr.it 70 i+ return r.json() Err tccr.it 70 i+ Err tccr.it 70 i+ Err tccr.it 70 i+def _prepare_file_upload(transfer_id: str, file: str) -> str: Err tccr.it 70 i+ """Given a transfer_id and file prepare it for the upload. Err tccr.it 70 i+ Err tccr.it 70 i+ Return the parsed JSON response. Err tccr.it 70 i+ Err tccr.it 70 i+ A successfull response is of the following form: Err tccr.it 70 i+ Err tccr.it 70 i+ { Err tccr.it 70 i+ "id": file_id, Err tccr.it 70 i+ "name": filename, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ "size": filesize, Err tccr.it 70 i+ "chunk_size": 5242880 Err tccr.it 70 i+ } Err tccr.it 70 i+ """ Err tccr.it 70 i+ filename = os.path.basename(file) Err tccr.it 70 i+ filesize = os.path.getsize(file) Err tccr.it 70 i+ Err tccr.it 70 i+ j = { Err tccr.it 70 i+ "name": filename, Err tccr.it 70 i+ "size": filesize Err tccr.it 70 i+ } Err tccr.it 70 i+ Err tccr.it 70 i+ r = requests.post(WETRANSFER_FILES_URL.format(transfer_id=transfer_id), json=j) Err tccr.it 70 i+ return r.json() Err tccr.it 70 i+ Err tccr.it 70 i+ Err tccr.it 70 i+def _upload_chunks(transfer_id: str, file_id: str, file: str, Err tccr.it 70 i+ default_chunk_size: int = WETRANSFER_DEFAULT_CHUNK_SIZE) -> str: Err tccr.it 70 i+ """Given a transfer_id, file_id and file upload it. Err tccr.it 70 i+ Err tccr.it 70 i+ Return the parsed JSON response. Err tccr.it 70 i+ Err tccr.it 70 i+ A successfull response is of the following form: Err tccr.it 70 i+ Err tccr.it 70 i+ { Err tccr.it 70 i+ "id": file_id, Err tccr.it 70 i+ "name": filename, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ "size": filesize, Err tccr.it 70 i+ "chunk_size": 5242880 Err tccr.it 70 i+ } Err tccr.it 70 i+ """ Err tccr.it 70 i+ f = open(file, 'rb') Err tccr.it 70 i+ Err tccr.it 70 i+ chunk_number = 0 Err tccr.it 70 i+ while True: Err tccr.it 70 i+ chunk = f.read(default_chunk_size) Err tccr.it 70 i+ chunk_size = len(chunk) Err tccr.it 70 i+ if chunk_size == 0: Err tccr.it 70 i+ break Err tccr.it 70 i+ chunk_number += 1 Err tccr.it 70 i+ Err tccr.it 70 i+ j = { Err tccr.it 70 i+ "chunk_crc": zlib.crc32(chunk), Err tccr.it 70 i+ "chunk_number": chunk_number, Err tccr.it 70 i+ "chunk_size": chunk_size, Err tccr.it 70 i+ "retries": 0 Err tccr.it 70 i+ } Err tccr.it 70 i+ Err tccr.it 70 i+ r = requests.post( Err tccr.it 70 i+ WETRANSFER_PART_PUT_URL.format(transfer_id=transfer_id, file_id=file_id), Err tccr.it 70 i+ json=j Err tccr.it 70 i+ ) Err tccr.it 70 i+ url = r.json().get('url') Err tccr.it 70 i+ r = requests.options(url, Err tccr.it 70 i+ headers={ Err tccr.it 70 i+ 'Origin': 'https://wetransfer.com', Err tccr.it 70 i+ 'Access-Control-Request-Method': 'PUT', Err tccr.it 70 i+ } Err tccr.it 70 i+ ) Err tccr.it 70 i+ r = requests.put(url, data=chunk) Err tccr.it 70 i+ Err tccr.it 70 i+ j = { Err tccr.it 70 i+ 'chunk_count': chunk_number Err tccr.it 70 i+ } Err tccr.it 70 i+ r = requests.put( Err tccr.it 70 i+ WETRANSFER_FINALIZE_MPP_URL.format(transfer_id=transfer_id, file_id=file_id), Err tccr.it 70 i+ json=j Err tccr.it 70 i+ ) Err tccr.it 70 i+ Err tccr.it 70 i+ return r.json() Err tccr.it 70 i+ Err tccr.it 70 i+ Err tccr.it 70 i+def _finalize_upload(transfer_id: str) -> str: Err tccr.it 70 i+ """Given a transfer_id finalize the upload. Err tccr.it 70 i+ Err tccr.it 70 i+ Return the parsed JSON response. Err tccr.it 70 i+ Err tccr.it 70 i+ A successfull response is of the following form: Err tccr.it 70 i+ Err tccr.it 70 i+ { Err tccr.it 70 i+ "id": transfer_id, Err tccr.it 70 i+ "security_hash": security_hash, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ "shortened_url": shortened_url, Err tccr.it 70 i+ "message": message, Err tccr.it 70 i+ "expires_at": expires_at, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ "files": [ Err tccr.it 70 i+ { Err tccr.it 70 i+ "id": file_id, Err tccr.it 70 i+ "name": filename, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ "size": filesize, Err tccr.it 70 i+ "chunk_size", 5242880 Err tccr.it 70 i+ }, Err tccr.it 70 i+ ..., Err tccr.it 70 i+ ], Err tccr.it 70 i+ "recipients": [ Err tccr.it 70 i+ ..., Err tccr.it 70 i+ ] Err tccr.it 70 i+ } Err tccr.it 70 i+ """ Err tccr.it 70 i+ r = requests.put(WETRANSFER_FINALIZE_URL.format(transfer_id=transfer_id)) Err tccr.it 70 i+ Err tccr.it 70 i+ return r.json() Err tccr.it 70 i+ Err tccr.it 70 i+ Err tccr.it 70 i+def upload(files: List[str], message: str = '', sender: str = None, Err tccr.it 70 i+ recipients: List[str] = []) -> str: Err tccr.it 70 i+ """Given a list of files upload them and return the corresponding URL. Err tccr.it 70 i+ Err tccr.it 70 i+ Also accepts optional parameters: Err tccr.it 70 i+ - `message': message used as a description of the transfer Err tccr.it 70 i+ - `sender': email address used to receive an ACK if the upload is Err tccr.it 70 i+ successfull. For every download by the recipients an email will Err tccr.it 70 i+ be also sent Err tccr.it 70 i+ - `recipients': list of email addresses of recipients. When the upload Err tccr.it 70 i+ succeed every recipients will receive an email with a link Err tccr.it 70 i+ Err tccr.it 70 i+ If both sender and recipient parameters are passed the email upload will be Err tccr.it 70 i+ used. Otherwise, the link upload will be used. Err tccr.it 70 i+ Err tccr.it 70 i+ Return the short URL of the transfer on success. Err tccr.it 70 i+ """ Err tccr.it 70 i+ Err tccr.it 70 i+ # Check that all files exists Err tccr.it 70 i+ for f in files: Err tccr.it 70 i+ if not os.path.exists(f): Err tccr.it 70 i+ return None Err tccr.it 70 i+ Err tccr.it 70 i+ # Check that there are no duplicates filenames (despite different dirname()) Err tccr.it 70 i+ filenames = [ os.path.basename(f) for f in files ] Err tccr.it 70 i+ if len(files) != len({ *filenames }): Err tccr.it 70 i+ return None Err tccr.it 70 i+ Err tccr.it 70 i+ transfer_id = None Err tccr.it 70 i+ if sender and recipients: Err tccr.it 70 i+ # email upload Err tccr.it 70 i+ transfer_id = \ Err tccr.it 70 i+ _prepare_email_upload(filenames, message, sender, recipients)['id'] Err tccr.it 70 i+ else: Err tccr.it 70 i+ # link upload Err tccr.it 70 i+ transfer_id = _prepare_link_upload(filenames, message)['id'] Err tccr.it 70 i+ Err tccr.it 70 i+ for f in files: Err tccr.it 70 i+ file_id = _prepare_file_upload(transfer_id, os.path.basename(f))['id'] Err tccr.it 70 i+ _upload_chunks(transfer_id, file_id, f) Err tccr.it 70 i+ Err tccr.it 70 i+ return _finalize_upload(transfer_id)['shortened_url'] Err tccr.it 70 i+ Err tccr.it 70 i+ Err tccr.it 70 i+if __name__ == '__main__': Err tccr.it 70 i+ import argparse Err tccr.it 70 i+ Err tccr.it 70 i+ ap = argparse.ArgumentParser( Err tccr.it 70 i+ prog='transferwee', Err tccr.it 70 i+ description='Download/upload files via wetransfer.com' Err tccr.it 70 i+ ) Err tccr.it 70 i+ sp = ap.add_subparsers(dest='action', help='action') Err tccr.it 70 i+ Err tccr.it 70 i+ # download subcommand Err tccr.it 70 i+ dp = sp.add_parser('download', help='download files') Err tccr.it 70 i+ dp.add_argument('-g', action='store_true', help='only print the direct link (without downloading it)') Err tccr.it 70 i+ dp.add_argument('url', nargs='+', type=str, metavar='url', help='URL (we.tl/... or wetransfer.com/downloads/...)') Err tccr.it 70 i+ Err tccr.it 70 i+ # upload subcommand Err tccr.it 70 i+ up = sp.add_parser('upload', help='upload files') Err tccr.it 70 i+ up.add_argument('-m', type=str, default='', metavar='message', help='message description for the transfer') Err tccr.it 70 i+ up.add_argument('-f', type=str, metavar='from', help='sender email') Err tccr.it 70 i+ up.add_argument('-t', nargs='+', type=str, metavar='to', help='recipient emails') Err tccr.it 70 i+ up.add_argument('files', nargs='+', type=str, metavar='file', help='files to upload') Err tccr.it 70 i+ Err tccr.it 70 i+ args = ap.parse_args() Err tccr.it 70 i+ Err tccr.it 70 i+ if args.action == 'download': Err tccr.it 70 i+ if args.g: Err tccr.it 70 i+ for u in args.url: Err tccr.it 70 i+ print(download_url(u)) Err tccr.it 70 i+ else: Err tccr.it 70 i+ for u in args.url: Err tccr.it 70 i+ download(u) Err tccr.it 70 i+ exit(0) Err tccr.it 70 i+ Err tccr.it 70 i+ if args.action == 'upload': Err tccr.it 70 i+ print(upload(args.files, args.m, args.f, args.t)) Err tccr.it 70 i+ exit(0) Err tccr.it 70 i+ Err tccr.it 70 i+ # No action selected, print help message Err tccr.it 70 i+ ap.print_help() Err tccr.it 70 i+ exit(1) Err tccr.it 70 .