├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── README.rst ├── requirements.txt ├── setup.cfg ├── setup.py └── transfersh_client ├── __init__.py └── app.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | transfersh_client.egg-info/ 4 | 5 | \.idea/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Arsen Losenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: upload 2 | .PHONY: deps 3 | .PHONY: clean 4 | .PHONY: install 5 | .PHONY: check 6 | 7 | build: 8 | python setup.py sdist bdist_wheel 9 | 10 | install: 11 | python setup.py install 12 | 13 | upload: 14 | twine upload dist/* 15 | 16 | check: 17 | twine check dist/* 18 | 19 | deps: 20 | pip install -r requirements.txt 21 | 22 | clean: 23 | rm -rf build dist *.egg-info 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # transfer.sh-client 2 | Python client for uploading files to transfer.sh (https://transfer.sh/) 3 | This command-line tool send file (or files, in case of directory download) to transfer.sh, and provides link to uploaded files, 4 | so it could be easily shared 5 | 6 | ## Latest release: 7 | 8 | https://pypi.org/project/transfersh-client/1.1.3/ 9 | 10 | ## Getting Started 11 | 12 | - Install python and pip (package manager): 13 | ```bash 14 | sudo apt-get update 15 | sudo apt-get install python3 python3-pip 16 | 17 | OR 18 | 19 | sudo apt-get install python python-pip 20 | ``` 21 | 22 | - Download package from pip: 23 | ```bash 24 | pip3 install --user transfersh_client 25 | ``` 26 | 27 | ## Usage 28 | 29 | - After installation, you can run this package directly in command line. Launching it without arguments starts it in interactive mode: 30 | ```bash 31 | transfer_files 32 | ``` 33 | 34 | ### Sample output: 35 | ```bash 36 | Github|⇒ transfer_files 37 | Enter path to file or directory: ./sysinfo 38 | Creating zipfile from files in... /home/path/to/directory/sysinfo 39 | Added file: cython_tut.cpython-34m.so 40 | Added file: cython_tut.pyx 41 | Added file: setup.py 42 | Added file: build 43 | Added file: fib.cpython-34m.so 44 | Added file: primes.c 45 | Added file: .idea 46 | Added file: fib.c 47 | Added file: parse_proc_files.py 48 | Added file: fib.pyx 49 | Added file: primes.pyx 50 | Added file: cython_tut.c 51 | Added file: primes.cpython-34m.so 52 | 53 | Sending zipfile: files_archive_09-02_18:34.zip (size of the file: 0.407897 MB) 54 | Link to download zipfile(will be saved till 2017-09-16): 55 | Could not save metadata 56 | 57 | Link copied to clipboard 58 | Remove archive? (y/n, yes/no):yes 59 | Removing file... /home/path/to/directory/sysinfo/files_archive_09-02_18:34.zip 60 | Removed. 61 | ``` 62 | - Besides that, you can start it with arguments: 63 | 64 | -i --interactive - keys that will start app with prompts (same as running it without arguments) 65 | 66 | -d --directory - enter path to directory (relative or absolute), which files will be sent in an archive 67 | 68 | -f --file - same as --directory, but enter path to file 69 | 70 | --ra --rm-archive - delete created archive, after it was sent 71 | 72 | --rf --rm-file - delete file after it was sent 73 | 74 | -h --help - display help message 75 | 76 | ### Sample output 77 | ```bash 78 | transfer.sh_client|dev⚡ ⇒ transfer_files -f test.txt --rf 79 | 80 | Sending file: /home/path/to/directory/transfer.sh_client/test.txt (size of the file: 0.000113 MB) 81 | Link to download file(will be saved till 2017-09-16): 82 | https://transfer.sh/CtaJs/test.txt 83 | Link copied to clipboard 84 | Removing file... /home/path/to/directory/transfer.sh_client/test.txt 85 | Removed. 86 | ``` 87 | 88 | ## Example of usage inside scripts 89 | 90 | ```python 91 | #!/usr/bin/env python3 92 | 93 | from transfersh_client.app import send_to_transfersh, create_zip, remove_file 94 | 95 | 96 | def send_files_from_dir(): 97 | directory = './' 98 | zip_file = create_zip(directory) # creates zip archive and returns it's absolute path 99 | send_to_transfersh(zip_file) # sends archive to transfer.sh 100 | remove_file(zip_file) # removes it 101 | 102 | 103 | if __name__ == '__main__': 104 | send_files_from_dir() 105 | ``` 106 | 107 | ## Contributing 108 | If you want to add some sort of a feature, fill free to fork this repo and submit your changes via a PR. 109 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Usage: 3 | ============ 4 | 5 | 6 | - After installation, you can run this package directly in command line. Launching it without arguments starts it in interactive mode: 7 | 8 | ================ 9 | Sample output: 10 | ================ 11 | :: 12 | 13 | Github|⇒ transfer_files 14 | Enter path to file or directory: ./sysinfo 15 | Creating zipfile from files in... /home/path/to/directory/sysinfo 16 | Added file: cython_tut.cpython-34m.so 17 | Added file: cython_tut.pyx 18 | Added file: setup.py 19 | Added file: build 20 | Added file: fib.cpython-34m.so 21 | Added file: primes.c 22 | Added file: .idea 23 | Added file: fib.c 24 | Added file: parse_proc_files.py 25 | Added file: fib.pyx 26 | Added file: primes.pyx 27 | Added file: cython_tut.c 28 | Added file: primes.cpython-34m.so 29 | 30 | Sending zipfile: files_archive_09-02_18:34.zip (size of the file: 0.407897 MB) 31 | Link to download zipfile(will be saved till 2017-09-16): 32 | Could not save metadata 33 | 34 | Link copied to clipboard 35 | Remove archive? (y/n, yes/no):yes 36 | Removing file... /home/path/to/directory/sysinfo/files_archive_09-02_18:34.zip 37 | Removed. 38 | 39 | 40 | 41 | - Besides that, you can start it with arguments: 42 | 43 | -i --interactive - keys that will start app with prompts (same as running it without arguments) 44 | 45 | -d --directory - enter path to directory (relative or absolute), which files will be sent in an archive 46 | 47 | -f --file - same as --directory, but enter path to file 48 | 49 | --ra --rm-archive - delete created archive, after it was sent 50 | 51 | --rf --rm-file - delete file after it was sent 52 | 53 | -h --help - display help message 54 | 55 | ============= 56 | Sample output 57 | ============= 58 | :: 59 | 60 | transfer.sh_client|dev⚡ ⇒ transfer_files -f test.txt --rf 61 | 62 | Sending file: /home/path/to/directory/transfer.sh_client/test.txt (size of the file: 0.000113 MB) 63 | Link to download file(will be saved till 2017-09-16): 64 | https://transfer.sh/CtaJs/test.txt 65 | Link copied to clipboard 66 | Removing file... /home/path/to/directory/transfer.sh_client/test.txt 67 | Removed. 68 | 69 | ================================ 70 | Example of usage inside scripts 71 | ================================ 72 | :: 73 | 74 | #!/usr/bin/env python3 75 | 76 | from transfersh_client.app import send_to_transfersh, create_zip, remove_file 77 | 78 | 79 | def send_files_from_dir(): 80 | directory = './' 81 | zip_file = create_zip(directory) # creates zip archive and returns it's absolute path 82 | send_to_transfersh(zip_file) # sends archive to transfer.sh 83 | remove_file(zip_file) # removes it 84 | 85 | 86 | if __name__ == '__main__': 87 | send_files_from_dir() 88 | 89 | 90 | ============ 91 | Download 92 | ============ 93 | :: 94 | 95 | pip3 install transfersh_client 96 | 97 | ================== 98 | Requirements 99 | ================== 100 | - pyperclip 101 | - requests 102 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.22.0 2 | wget==3.2 3 | twine==3.1.1 4 | pyperclip==1.7.0 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | [metadata] 5 | description_file = README.md 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from os.path import join, dirname 3 | 4 | 5 | def list_requirements(): 6 | deps = [] 7 | requirements_file = "requirements.txt" 8 | with open(requirements_file) as f: 9 | for line in f.readlines(): 10 | if "twine" in line: 11 | continue 12 | deps.append(line.strip()) 13 | return deps 14 | 15 | 16 | setup( 17 | name="transfersh_client", 18 | version="1.1.3", 19 | author="Arsen Losenko", 20 | author_email="arsenlosenko@protonmail.com", 21 | description="transfer.sh command-line client", 22 | url="https://github.com/arsenlosenko/transfer.sh_client.git", 23 | keywords="transfer.sh command-line client tool utility cli CLI", 24 | license="MIT", 25 | classifiers=[ 26 | 'Development Status :: 3 - Alpha', 27 | 'Intended Audience :: Developers', 28 | 'Topic :: Utilities', 29 | 'Environment :: Console', 30 | 'License :: OSI Approved :: MIT License', 31 | 'Programming Language :: Python :: 3.5' 32 | ], 33 | packages=find_packages(), 34 | long_description=open(join(dirname(__file__), "README.rst")).read(), 35 | long_description_content_type="text/x-rst", 36 | install_requires=list_requirements(), 37 | entry_points={ 38 | "console_scripts": 39 | ['transfer_files = transfersh_client.app:main'], 40 | }, 41 | ) 42 | -------------------------------------------------------------------------------- /transfersh_client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arsenlosenko/transfer.sh_client/46dfa9bf2246e68ed74a953cbaa0fe9768606c11/transfersh_client/__init__.py -------------------------------------------------------------------------------- /transfersh_client/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | author: Arsen Losenko 5 | email: arsenlosenko@protonmail.com 6 | github: arsenlosenko 7 | short description: command-line tool that uploads files to transfer.sh and returns download link 8 | """ 9 | 10 | import os 11 | import sys 12 | import zipfile 13 | import requests 14 | import datetime 15 | import pyperclip 16 | import optparse 17 | import wget 18 | 19 | 20 | def get_date_in_two_weeks(): 21 | """ 22 | get maximum date of storage for file 23 | :return: date in two weeks 24 | """ 25 | today = datetime.datetime.today() 26 | date_in_two_weeks = today + datetime.timedelta(days=14) 27 | return date_in_two_weeks.date() 28 | 29 | 30 | def get_size(file): 31 | """ 32 | get file size, in megabytes 33 | :param file: 34 | :return: size of file 35 | """ 36 | size_in_bytes = os.path.getsize(file) 37 | size_in_megabytes = size_in_bytes / 1000000 38 | return size_in_megabytes 39 | 40 | 41 | def parse_params(): 42 | usage = "%prog [-i/--file/--directory] /path/to/file [--rm-archive/--rm-file]\n" \ 43 | "Short description:\n" \ 44 | "transfersh-client is used to send files to transfer.sh and retrieve download link, " \ 45 | "so it could be shared fast and easily directly from command-line.\n" \ 46 | "It can send all files from entered directory (in an archive) or send one file.\n" \ 47 | "You can download up to 10 GB files to transfer.sh, they will be saved there for 14 days." 48 | parser = optparse.OptionParser(usage) 49 | parser.add_option('-i', '--interactive', 50 | dest="interactive_mode", 51 | action="store_true", 52 | help="run in interactive mode (with entering info in the prompt)") 53 | parser.add_option('-d', '--directory', 54 | dest="directory", 55 | action="store", 56 | type="string", 57 | help="enter absolute path to directory, and create archive of it") 58 | parser.add_option('-f', '--file', 59 | dest="file", 60 | action="store", 61 | type="string", 62 | help="absolute path to file which will be uploaded") 63 | parser.add_option('--rf', '--rm-file', 64 | dest="rm_file", 65 | action="store_true", 66 | help="remove files after sending") 67 | parser.add_option('--ra', '--rm-archive', 68 | dest="rm_arch", 69 | action="store_true", 70 | help="remove only created archive") 71 | (options, args) = parser.parse_args() 72 | 73 | return options, args, parser 74 | 75 | 76 | def check_params(): 77 | """ 78 | check if entered params are in correct usage (and prevent incorrect usage) 79 | :return: options in correct form 80 | """ 81 | params = parse_params() 82 | options = params[0] 83 | parser = params[2] 84 | 85 | # handle incorrect usage of options (a bit of spaghetti code) 86 | if (options.interactive_mode and options.file) \ 87 | or (options.interactive_mode and options.directory) \ 88 | or (options.interactive_mode and options.rm_arch) \ 89 | or (options.interactive_mode and options.rm_file): 90 | print("Interactive option is used separately from other options\n") 91 | parser.print_help() 92 | sys.exit() 93 | elif (options.file and options.rm_arch) \ 94 | or (options.directory and options.rm_file): 95 | print("Use either --ra or --rf accordingly:\n") 96 | parser.print_help() 97 | sys.exit() 98 | elif (options.file and options.directory) \ 99 | or (options.rm_arch and options.rm_file): 100 | print("Use either -f or -d options\n") 101 | parser.print_help() 102 | sys.exit() 103 | else: 104 | return options 105 | 106 | 107 | def check_absolute_path(path): 108 | """ 109 | check if entered directory is absolute, if not, format it to absolute 110 | :param path: path that was entered by user 111 | :return: absolute path 112 | """ 113 | current_dir = os.getcwd() 114 | if os.path.isabs(path) is False: 115 | if str(path).startswith("./"): 116 | return current_dir + path[1:] 117 | else: 118 | return current_dir + "/" + path 119 | else: 120 | return path 121 | 122 | 123 | def handle_params(): 124 | """ 125 | Function retrieves options from check_params() and runs functions accordingly to their state 126 | (which flags were used) 127 | :return: None 128 | """ 129 | options = check_params() 130 | if options.interactive_mode or len(sys.argv) == 1: 131 | interactive_mode_run() 132 | 133 | # check if file or directory exists, and weather to remove created archive or file 134 | if options.file is not None: 135 | try: 136 | os.path.isfile(options.file) 137 | except FileNotFoundError: 138 | print("Please enter valid absolute path to file") 139 | sys.exit() 140 | else: 141 | options.file = check_absolute_path(options.file) 142 | if options.rm_file: 143 | send_to_transfersh(options.file) 144 | remove_file(options.file) 145 | else: 146 | send_to_transfersh(options.file) 147 | 148 | if options.directory is not None: 149 | try: 150 | os.path.isdir(options.directory) 151 | except NotADirectoryError: 152 | print("Please enter valid absolute path to directory") 153 | sys.exit() 154 | else: 155 | options.directory = check_absolute_path(options.directory) 156 | if options.rm_arch: 157 | zip_file = create_zip(options.directory) 158 | send_to_transfersh(zip_file) 159 | remove_file(zip_file) 160 | else: 161 | zip_file = create_zip(options.directory) 162 | send_to_transfersh(zip_file) 163 | 164 | 165 | def interactive_mode_run(): 166 | """ 167 | running mode which asks user's data via prompt 168 | :return: None 169 | """ 170 | path = input("Enter path to file or directory:\n") 171 | path = check_absolute_path(path) 172 | 173 | if os.path.isfile(path): 174 | send_to_transfersh(path) 175 | confirm = input("Remove file? (y/n, yes/no):") 176 | confirm_removal(confirm, path) 177 | elif os.path.isdir(path): 178 | create_zip(path) 179 | send_to_transfersh(path) 180 | confirm = input("Remove archive? (y/n, yes/no):") 181 | confirm_removal(confirm, path) 182 | else: 183 | print("Please enter a valid absolute path to file/directory") 184 | sys.exit() 185 | 186 | 187 | def create_zip(file_dir): 188 | """ 189 | create zipfile from files in entered directory 190 | :param file_dir: absolute path to directory 191 | :return: absolute path to created zipfile 192 | """ 193 | os.chdir(file_dir) 194 | zip_name = 'files_archive_{}.zip'.format(str(datetime.datetime.now())[5:16].replace(' ', "_")) 195 | files = os.listdir() 196 | print("Creating zipfile from files in...", file_dir) 197 | with zipfile.ZipFile(zip_name, 'w') as zip: 198 | for f in files: 199 | zip.write(f) 200 | print("Added file: ", f) 201 | 202 | zip_path = file_dir + "/" + zip_name 203 | 204 | # double check if path is absolute 205 | if os.path.isabs(zip_path): 206 | return zip_path 207 | else: 208 | return os.getcwd() + "/" + zip_name 209 | 210 | 211 | def remove_file(file): 212 | print("Removing file...", file) 213 | os.remove(file) 214 | print("Removed.") 215 | 216 | 217 | def confirm_removal(confirm, filename): 218 | """ 219 | function used in interactive mode, asks weather to remove file, or not 220 | :param confirm: 221 | :param filename: absolute path to file 222 | :return: None 223 | """ 224 | if confirm == 'y' or confirm == 'yes': 225 | remove_file(filename) 226 | elif confirm == 'n' or confirm == 'no': 227 | print("File will stay there") 228 | else: 229 | print("Please etner a valid answer (y/n, yes/no)") 230 | confirm_removal() 231 | 232 | 233 | def send_to_transfersh(file, clipboard=True): 234 | """ 235 | send file to transfersh, retrieve download link, and copy it to clipboard 236 | :param file: absolute path to file 237 | :param copy_to_clipboard: boolean value specifing if the download_link should be copied to clipboard 238 | :return: download_link 239 | """ 240 | size_of_file = get_size(file) 241 | final_date = get_date_in_two_weeks() 242 | file_name = os.path.basename(file) 243 | 244 | print("\nSending file: {} (size of the file: {} MB)".format(file_name, size_of_file)) 245 | url = 'https://transfer.sh/' 246 | file = {'{}'.format(file): open(file, 'rb')} 247 | response = requests.post(url, files=file) 248 | download_link = response.content.decode('utf-8') 249 | print("Link to download file(will be saved till {}):\n{}".format(final_date, download_link)) 250 | 251 | if clipboard: 252 | copy_to_clipboard(download_link) 253 | return download_link 254 | 255 | 256 | def download_from_transfersh(download_link, path='.'): 257 | """ 258 | download file from transfersh 259 | :param download_link: link to uploaded file 260 | :param path: directory or file path for file to be downloaded 261 | :return: path where the file is downloaded 262 | """ 263 | return wget.download(download_link,out=path) 264 | 265 | 266 | def copy_to_clipboard(link): 267 | """ 268 | copy dowload link to clipboard 269 | :param link: dowload link for file 270 | :return: None 271 | """ 272 | pyperclip.copy(link) 273 | print("Link copied to clipboard") 274 | 275 | 276 | def main(): 277 | try: 278 | handle_params() 279 | except KeyboardInterrupt: 280 | print('Execution stopped') 281 | sys.exit() 282 | 283 | 284 | if __name__ == '__main__': 285 | main() 286 | --------------------------------------------------------------------------------