diff --git a/.gitignore b/.gitignore index 819ac27..5748761 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ output output/* copypasta.exe *.build -*.dist \ No newline at end of file +*.dist +tmp/ \ No newline at end of file diff --git a/README.md b/README.md index b6f87e7..02c61b1 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ - you only needs to start CopyPasta once on your desktop, even if you close the browser window CopyPasta can still receive scans and notify you ! (You still need to restart it after each shutdown of your computer) - If you have closed the browser window, you can reopen it by clicking on the shortcut like if you wanted to start copypasta or even just send your things and click on the notification toast ;D +## new feature in building : PC-to-PC copypasta ! + ## Installation : - download "launcher.exe" from the releases section - start the file once downloaded (let it make its things, it takes some time to download everything :D ) diff --git a/client.py b/client.py index d6e5888..24ce628 100644 --- a/client.py +++ b/client.py @@ -1,44 +1,46 @@ import requests from util import * -def send_text_scan(text): - r = requests.post("http://127.0.0.1:21987/upload",json={"type" : "text", "content" : f"{text}"}) - print(r.text) +def send_text_scan(text:str,ip_addr:str) -> bool: + """ + sends the specified text + to the instance of copypasta running on the specified address + """ + r = requests.post(f"http://{ip_addr}:21987/upload",json={"type" : "text", "content" : f"{text}"}) + return r.status_code == 200 -def send_file(file_path): - files = {'files': open(file_path,'rb')} - r = requests.post("http://127.0.0.1:21987/upload", files = files) - print(r.text) +def send_file(file_path:str,ip_addr:str): + + """ + sends the specified file + to the instance of copypasta running on the specified address + """ + + files = {'files': open(file_path,'rb')} + + r = requests.post(f"http://{ip_addr}:21987/upload",files=files) + return r.status_code == 200 -def send_keystrokes(text): +def send_keystrokes(text:str): r = requests.post("http://127.0.0.1:21987/upload",json={"type" : "keystrokes", "content" : {"text" : f"{text}"}}) - print(r.text) + return r.status_code == 200 -def send_barcode(text): +def send_barcode(text:str): r = requests.post("http://127.0.0.1:21987/upload",json={"type" : "isbn", "content" : f"{text}"}) - print(r.text) + return r.status_code == 200 -def send_wifi(ssid, encryption, key): +def send_wifi(ssid:str, encryption:str, key:str): r = requests.post("http://127.0.0.1:21987/upload",json={"type" : "wifi", "content" : {"ssid" : f"{ssid}", "encryption" : f"{encryption}", "key" : f"{key}"}}) - print(r.text) + return r.status_code == 200 -def send_email(dest_addr,subject,content): +def send_email(dest_addr:str,subject:str,content:str): r = requests.post("http://127.0.0.1:21987/upload",json={"type" : "email", "content" : {"address" : f"{dest_addr}", "subject" : f"{subject}", "content" : f"{content}"}}) - print(r.text) + return r.status_code == 200 -def send_url(url): +def send_url(url:str): r = requests.post("http://127.0.0.1:21987/upload",json={"type" : "url", "content" : f"{url}"}) - print(r.text) - -#send_email("unrealsoft.dev@gmail.com","test","test of email body") -#send_barcode("test") -#send_file("static/qr.jpeg") -#send_text_scan("test of text scan") -#send_wifi("This_is_a_wifi_name","wpa","password") -#send_url("https://www.google.com") - -check_templates_update() \ No newline at end of file + return r.status_code == 200 diff --git a/copypasta.json b/copypasta.json index ca8d211..f155474 100644 --- a/copypasta.json +++ b/copypasta.json @@ -7,7 +7,7 @@ }, { "optionDest": "filenames", - "value": "G:/Mon Drive/dev/projects/CopyPasta/copypasta.py" + "value": "C:/Users/thaao/OneDrive - insa-toulouse.fr/dev/projects/copypasta/copypasta.py" }, { "optionDest": "onefile", @@ -19,7 +19,7 @@ }, { "optionDest": "icon_file", - "value": "G:/Mon Drive/dev/projects/CopyPasta/static/favicon.ico" + "value": "C:/Users/thaao/OneDrive - insa-toulouse.fr/dev/projects/copypasta/favicon.ico" }, { "optionDest": "ascii", @@ -69,9 +69,25 @@ "optionDest": "argv_emulation", "value": false }, + { + "optionDest": "hiddenimports", + "value": "engineio.async_gevent" + }, + { + "optionDest": "hiddenimports", + "value": "gevent" + }, + { + "optionDest": "hiddenimports", + "value": "engineio.async_drivers.aiohttp" + }, + { + "optionDest": "hiddenimports", + "value": "engineio.async_aiohttp" + }, { "optionDest": "datas", - "value": "G:/Mon Drive/dev/projects/CopyPasta/version;." + "value": "C:/Users/thaao/OneDrive - insa-toulouse.fr/dev/projects/copypasta/version;." } ], "nonPyinstallerOptions": { diff --git a/copypasta.py b/copypasta.py index 0937085..e851264 100644 --- a/copypasta.py +++ b/copypasta.py @@ -9,6 +9,7 @@ from io import BytesIO from shutil import copyfile from urllib.parse import quote as url_encode +import client as pc_client try: import win32clipboard except ImportError: @@ -22,10 +23,12 @@ from flask_cors import CORS, cross_origin from re import findall -#waitress wsgi server -from waitress import serve -from webtest.http import StopableWSGIServer +# socket io for real time speeeeeed +from flask_socketio import SocketIO +# necessary to compile -__(°-°)__- +from engineio.async_drivers import gevent + # to generate app secret key from random import choice @@ -41,10 +44,8 @@ app.secret_key = "".join([choice(printable) for _ in range(256)]) - -# init waitress wsgi server -webserv = StopableWSGIServer(app) - +# init socketio +socketio = SocketIO(app,async_mode="gevent") if getattr(sys, 'frozen', False): @@ -62,8 +63,8 @@ if not path.exists("static/"): emergency_redownload() - - + + def check_exe_name(): if path.basename(__file__).replace(".py",".exe") != "copypasta.exe": rename(path.basename(__file__).replace(".py",".exe"),"copypasta.exe") @@ -75,14 +76,7 @@ def check_exe_name(): # copypasta url - -if is_hosts_file_modified(): - - COPYPASTA_URL = "http://copypasta.me" if system() == "Windows" else "http://copypasta.me:21987" - -else: - - COPYPASTA_URL = "http://127.0.0.1:21987" +COPYPASTA_URL = "http://127.0.0.1:21987" #necessary to update images (stack overflow) @app.after_request @@ -95,6 +89,7 @@ def add_header(response): + #home @app.route("/") @cross_origin() @@ -102,14 +97,22 @@ def home(): if request.remote_addr == "127.0.0.1": - if not path.exists("static/history.xml"): init_history_file() - #render the html with the history - return render_template("index.html",copypasta_url=COPYPASTA_URL,server_version=get_server_version(),hist = get_history(),ip=get_private_ip(),hostname=socket.gethostname(),tab=path.exists("static/tab")) + return render_template("index.html", + copypasta_url=COPYPASTA_URL, + server_version=get_server_version(), + hist = get_history_json(), + ip=get_private_ip(), + hostname=socket.gethostname(), + tab=path.exists("static/tab"), + is_accepting_uploads = is_accepting_uploads() + ) + + else: return abort(403) @@ -192,75 +195,212 @@ def scan_preview(): else: return abort(403) + + + +# real time processes (socketio) +@socketio.on("[DELETE_FILE_FROM_HIST]") +def delete_file_from_hist(json): + + file_info = get_history_file_by_id(json["file_id"]) + + # this id doest not belongs to any file + if not file_info: + return + + # check if file haven't been deleted by another process (we never know ^^) + if path.exists(file_info["path"]): + remove(file_info["path"]) + delete_history_file_by_id(json["file_id"]) + + + # now tell the page to refresh its history content + socketio.emit("fill_history_tab",get_history_json()) + + + +#copy scan from history page +@socketio.on("[COPY_SCAN_FROM_HIST]") +def copy_scan_from_hist(json_data:dict): -#processes -@app.route("/process/") -def process(process_id): - if request.remote_addr == "127.0.0.1": + text = get_history_file_by_id(json_data["scan_id"]) + + if not text: + return jsonify({"Error":"this id does not belongs to any sacn"}) + + copy(text["text"]) + + socketio.emit("[NOTIFY_USER]",{"msg":"Scan copied !"}) - #delete a particular image from history table - if "[DELETE_FILE_FROM_HIST]" in process_id: +@socketio.on("[DELETE_SCAN_FROM_HIST]") +def delete_scan_from_hist(json_data:dict): + + + delete_history_file_by_id(json_data["scan_id"]) + + # now tell the page to refresh its history content + socketio.emit("fill_history_tab",get_history_json()) + + # and notify user ;) + socketio.emit("[NOTIFY_USER]",{"msg":"File deleted from history"}) - try: - file_id = request.args.get("file_id",type=int) - except ValueError: - return jsonify({"Error":"wrong file_id argument type/no argument passed"}) - - file_path = get_history_file_by_id(file_id) - - if not file_path: - - return jsonify({"Error":"this id does not belongs to any file"}) +#empty the history files +@socketio.on("[DEL_HISTORY]") +def del_history(json_data:dict): + - remove(file_path['path']) + init_history_file(force=True) + + # now tell the page to refresh its history content + socketio.emit("fill_history_tab",get_history_json()) + + + socketio.emit(["NOTIFY_USER"] ,{"msg" : "History deleted !"}) + + +#open new tab on scan received or not +@socketio.on("[CHANGE_TAB_SETTINGS]") +def change_tab_settings(json_data:dict): + + if path.exists("static/tab"): + remove("static/tab") + else: + open("static/tab","w") - delete_history_file_by_id(file_id) + socketio.emit("[NOTIFY_USER]",{"msg":"Settings changed !"}) - return redirect("/") - #open an image preview from image history table - if "[OPEN_IMAGE_SCAN_FROM_HIST]" in process_id: +# open files explorer into the the copypasta directory +socketio.on("[OPEN_FILES_EXPLORER]") +def open_files_explorer(json_data:dict): + + Process(target=startfile,args=(f"{APP_PATH}/static/files_hist",)).start() + + socketio.emit("[NOTIFY_USER]",{"msg":"Opening your files explorer..."}) + + +# open a file with default app +socketio.on("[OPEN_FILE]") +def open_file(json_data:dict): + - try: - image_id = request.args.get("image_id",type=int) - except ValueError: - return jsonify({"Error":"wrong image_id argument type/no argument passed"}) - return redirect(f"/image_preview?image_id={image_id}") + json_dict = get_history_file_by_id(json_data["file_id"]) + + if not json_dict: # id does not exists + socketio.emit("[NOTIFY_USER]",{"msg":"This file does not exists."}) + return + + Process(target=startfile,args=("{}/{}".format(APP_PATH,json_dict["path"]),)).start() + + socketio.emit("[NOTIFY_USER]",{"msg":"Opening your file in the default app..."}) + - #copy scan from history page - if "[COPY_SCAN_FROM_HIST]" in process_id: + +socketio.on("[COPY_WIFI_PW]") +def copy_wifi_pw(json_data:dict): - try: - scan_id = request.args.get("scan_id",type=int) - except ValueError: - return jsonify({"Error":"wrong scan_id argument type/no argument passed"}) - text = get_history_file_by_id(scan_id) + json_dict = get_history_file_by_id(json_data["scan_id"]) + + if not "content" in json_dict.keys(): + socketio.emit("[NOTIFY_USER]",{"msg":"this kind of scan cannot be copied to clipboard"}) + + return - if not text: - return jsonify({"Error":"this id does not belongs to any sacn"}) + copy(json_dict['password']) + + socketio.emit("[NOTIFY_USER]",{"msg":"Wifi password copied to clipboard !"}) + + + +# copy text scan content +socketio.on("[COPY_CONTENT]") +def copy_text_scan_content(json_data:dict): - copy(text["text"]) + + json_dict = get_history_file_by_id(json_data["scan_id"]) + + if not "content" in json_dict.keys(): + + socketio.emit("[NOTIFY_USER]",{"msg":"this kind of scan cannot be copied to clipboard"}) + return + + # finally, if nothing is wrong, copy the scan content + copy(json_dict["content"]) + socketio.emit("[NOTIFY_USER]",{"msg":"Scan copied to clipboard !"}) + + +@socketio.on("[SHUTDOWN_SERVER]") +def shutdown_server(json_data:dict): + + socketio.emit("[NOTIFY_USER]",{"msg":"CopyPasta server is now off. You may close this tab."}) + + socketio.stop() - return redirect("/") + exit(1) + - if "[DELETE_SCAN_FROM_HIST]" in process_id: + +#empty the scan temporary file +@socketio.on("[CLEAR_LAST_SCAN]") +def clear_last_scan(json_data:dict): + + # overwrite the temp scan file + with open("static/scan.Blue","w") as f: + f.close() + + socketio.emit("[CLEAR_LAST_SCAN]") + +#copy the scan temporary file to clipboard +@socketio.on("[COPY_LAST_SCAN]") +def copy_last_scan(json_data:dict): + + with open("static/scan.Blue","r") as f: + copy(f.read()) + f.close() + + notify_desktop("CopyPasta","scan copied to clipboard :D") + + +#set the current uploads accepting state +@socketio.on("[CHANGE_ACCEPTING_UPLOADS_STATE]") +def change_accepting_uploads(json_data:dict): + + change_accepting_uploads_state() + + if is_accepting_uploads(): + socketio.emit("[NOTIFY_USER]",{"msg":"CopyPasta is now accepting incoming files !"}) + + else: + socketio.emit("[NOTIFY_USER]",{"msg":"CopyPasta is now refusing incoming files !"}) + + + + +#processes +@app.route("/process/") +def process(process_id): + + if request.remote_addr == "127.0.0.1": + + #open an image preview from image history table + if "[OPEN_IMAGE_SCAN_FROM_HIST]" in process_id: try: - scan_id = request.args.get("scan_id",type=int) + image_id = request.args.get("image_id",type=int) except ValueError: - return jsonify({"Error":"wrong scan_id argument type/no argument passed"}) - - delete_history_file_by_id(scan_id) + return jsonify({"Error":"wrong image_id argument type/no argument passed"}) - return redirect("/") + return redirect(f"/image_preview?image_id={image_id}") + + #download the image received - if "[DOWNLOAD IMG]" in process_id: + if "[DOWNLOAD_IMG]" in process_id: try: @@ -280,26 +420,12 @@ def process(process_id): download_name=secure_filename(image_path.replace("static/files_hist/","")), as_attachment=True) - #empty the scan temporary file - if process_id == "[CLEAR SCAN]": - open("static/scan.Blue","w") - - #redirect to the usual scan preview - return redirect("/scan_preview") - - #copy the scan temporary file to clipboard - if process_id == "[COPY SCAN]": - with open("static/scan.Blue","r") as f: - copy(f.read()) - f.close() - - notify_desktop("CopyPasta","scan copied to clipboard :D") - #redirect to the usual scan preview - return redirect("/scan_preview") + + #copy an image to the clipboard with a win32 api - if "[COPY IMG]" in process_id: + if "[COPY_IMG]" in process_id: try: image_id = request.args.get("image_id",type=int) @@ -337,96 +463,12 @@ def process(process_id): except ImportError: return redirect(f"/image_preview?image_id={image_id}") - - - #empty the history files - if process_id == "[DEL HISTORY]": - init_history_file(force=True) - return redirect("/") - - if process_id == "[HOME]": #redirect to homepage return redirect("/") - - - if process_id == "[CHANGE TAB SETTINGS]": - if path.exists("static/tab"): - remove("static/tab") - else: - open("static/tab","w") - - return "OK" - - if process_id == "[OPEN FILES EXPLORER]": - - Process(target=startfile,args=(f"{APP_PATH}/static/files_hist",)).start() - return redirect("/") - - if process_id == "[OPEN FILE]": - - try: - file_id = request.args.get("file_id",type=int) - except ValueError: - return jsonify({"Error":"invalid url argument"}) - - - json_dict = get_history_file_by_id(file_id) - - if not json_dict: # id does not exists - return jsonify({"Error":"invalid url argument"}) - - Process(target=startfile,args=("{}/{}".format(APP_PATH,json_dict["path"]),)).start() - - return redirect("/") - - - if process_id == "[COPY WIFI PW]": - - try: - scan_id = request.args.get("scan_id",type=int) - except ValueError: - return jsonify({"Error":"invalid url argument"}) - - - json_dict = get_history_file_by_id(scan_id) - - if not json_dict: # id does not exists - return jsonify({"Error":"invalid url argument"}) - - - if not "content" in json_dict.keys(): - return jsonify({"this kind of scan cannot be copied to clipboard"}) - - - copy(json_dict['password']) - - return redirect("/") - - - if process_id == "[COPY CONTENT]": - - try: - scan_id = request.args.get("scan_id",type=int) - except ValueError: - return jsonify({"Error":"invalid url argument"}) - - - json_dict = get_history_file_by_id(scan_id) - - if not json_dict: # id does not exists - return jsonify({"Error":"invalid url argument"}) - - - if not "content" in json_dict.keys(): - return jsonify({"this kind of scan cannot be copied to clipboard"}) - - # finally, if nothing is wrong, copy the scan content - copy(json_dict["content"]) - return redirect("/") - if process_id == "[OPEN VIDEO]": + if process_id == "[OPEN_VIDEO]": try: video_id = request.args.get('video_id',type=int) @@ -447,9 +489,6 @@ def process(process_id): - - - #api url(s) @app.route("/api/") @@ -458,13 +497,7 @@ def api(api_req): if request.remote_addr == "127.0.0.1": - - - if api_req == "get_history": - - return get_history() - - elif api_req == "ping": + if api_req == "ping": return "pong" @@ -492,14 +525,9 @@ def api(api_req): f.close() notify_desktop("Network change detected !","Updating you qr code, you need to rescan it ;)") + return jsonify({"new_ip" : "updating qr code and private ip"}) - elif api_req == "shutdown_server": - webserv.shutdown() - - remove_copypasta_port_redirect() - - return jsonify({"success":"shutting down CopyPasta server..."}) elif api_req == "gen_otdl_url": @@ -523,6 +551,7 @@ def api(api_req): # return url to javascript for qr code generation and notification safe_arg = url_encode(url_encode(path.basename(file_path))) # twice because google charts api will transform it once before qr generation return f"http://{get_private_ip()}:21987/download/main_page?file={safe_arg}" + else: return jsonify({"Error" : "wrong api call"}) @@ -569,16 +598,23 @@ def delete_ot_dl(response): @app.route("/upload",methods=["POST"]) - def upload(): - if request.method == "POST": - notify_desktop("New scan Incoming !", "Click to open CopyPasta") - - r = request.get_json() + r = request.get_json(silent=True) time = date.today().strftime("%d/%m/%Y") + # check upload mode + if not is_accepting_uploads(): + socketio.emit("[NOTIFY_USER]",{"msg":"Someone is trying to upload to your computer !
If this is you, you must turn on uploads (the switch right under) ;)"}) + notify_desktop("An upload request has been detected !","Someone is trying to upload to your computer ! If it is you, don't forget to activate uploads from the web app :)") + return jsonify({"Error":"Upload mode is disapled."}),403 + + notify_desktop("New scan Incoming !", "Click to open CopyPasta") + + socketio.emit("[NOTIFY_USER]",{"msg":"New scan Incoming !"}) + socketio.emit("fill_history_tab",get_history_json()) + if r != None: @@ -731,12 +767,80 @@ def upload(): open_browser_if_settings_okay(f"{COPYPASTA_URL}/image_preview?image_id={get_history_file_last_id()}") return jsonify({"upload_status" : "true"}) + + else: return abort(403) +# new feature, pc-to-pc +@app.route("/client",methods=["GET","POST"]) + +def client(): + + # obviously only allowed from the current computer + if request.remote_addr == "127.0.0.1": + msg = "" + + if request.method == "POST": + + msg = "Document sent !" + + try: + scan_type = request.form.get("type",type=str) + ip_addr = request.form.get("pc_ip_addr",type=str) + except ValueError: + return render_template("send_client.html",msg="erreur, champs non remplis") + + + + if scan_type == "text": + try: + scan_ctt = request.form.get("text_content",type=str) + except ValueError: + return render_template("send_client.html",msg="erreur, champs non remplis") + + Process(target = pc_client.send_text_scan,args = (scan_ctt,ip_addr)).start() + + + + elif scan_type == "file": + files = request.files.getlist("files_input") + + #go and store each files + for file in files : + + # If the user does not select a file, the browser submits an + # empty file without a filename. + if file.filename == '': + flash('No selected file') + return jsonify({"upload_status" : "false"}) + + #store file in temporary folder and delete after request + elif file : + filename = secure_filename(file.filename) + + # create temp folder if it does not exists + + if not path.exists("tmp"): + mkdir("tmp") + + # save the file into it + file.save(f"tmp/{filename}") + + Process(target = pc_client.send_file, args = (f"tmp/{filename}",ip_addr)).start() + + + # clear temporary file in 10s, but we don't sleep on main thread ^^ + Process(target=clear_tmp,args=(filename,)).start() + + # finally, render the same page with a little message ;) + return render_template("send_client.html",msg=msg) + else: + return abort(403) + if __name__ == "__main__": freeze_support() @@ -754,6 +858,8 @@ def upload(): r = get("https://chart.googleapis.com/chart?cht=qr&chs=300x300&chl="+make_qr_url(),allow_redirects=True) + + #write it with open("static/qr.jpeg","wb") as f: f.write(r.content) @@ -762,14 +868,13 @@ def upload(): #check if the templates are up-to-date check_templates_update() - #open tab in web browser - + Process(target=open_link_process, args=(COPYPASTA_URL,)).start() if not is_server_already_running(): - #run waitress web server - webserv.create(app,host="0.0.0.0",port=21987) - webserv.run() + socketio.run(app,host="0.0.0.0",port=21987) + + diff --git a/launcher.json b/launcher.json index 7927344..8f786dd 100644 --- a/launcher.json +++ b/launcher.json @@ -7,7 +7,7 @@ }, { "optionDest": "filenames", - "value": "G:/Mon Drive/dev/projects/CopyPasta/launcher.py" + "value": "C:/Users/thaao/OneDrive - insa-toulouse.fr/dev/projects/copypasta/launcher.py" }, { "optionDest": "onefile", @@ -19,7 +19,7 @@ }, { "optionDest": "icon_file", - "value": "G:/Mon Drive/dev/projects/CopyPasta/static/favicon.ico" + "value": "C:/Users/thaao/OneDrive - insa-toulouse.fr/dev/projects/copypasta/favicon.ico" }, { "optionDest": "ascii", @@ -31,7 +31,7 @@ }, { "optionDest": "splash", - "value": "G:/Mon Drive/dev/projects/CopyPasta/static/favicon.ico" + "value": "C:/Users/thaao/OneDrive - insa-toulouse.fr/dev/projects/copypasta/favicon.ico" }, { "optionDest": "strip", diff --git a/launcher.py b/launcher.py index 9387db6..141e57a 100644 --- a/launcher.py +++ b/launcher.py @@ -7,7 +7,7 @@ from zipfile import ZipFile from os import path, chdir, remove,mkdir,environ import sys -from util import add_copypasta_port_redirect, add_copypasta_to_hosts_file, create_shortcut, is_hosts_file_modified, notify_desktop +from util import create_shortcut, notify_desktop # to fix pyinstaller error import pywintypes @@ -98,15 +98,6 @@ def update_main_executable(version: str) -> None: # folder does not exists pass - - if not is_hosts_file_modified(): - - try: - add_copypasta_to_hosts_file() - except: - # launcher probably started without admin privileges, nothing to worry about - pass - def is_installed() -> None: return path.exists(APP_PATH) @@ -163,9 +154,7 @@ def move_launcher(): # create shortcuts and move launcher.exe to C:/Programs Files/CopyPasta/copypasta move_launcher() - - add_copypasta_port_redirect() - + chdir(APP_PATH) #now that we have the lastest, we can start the app :D diff --git a/pad.xml b/pad.xml new file mode 100644 index 0000000..a543004 --- /dev/null +++ b/pad.xml @@ -0,0 +1,31 @@ + +UnrealSoftwares +https://copypastaofficial.github.io +unrealsoft.dev@gmail.com +CopyPasta +1.7.1 +2020 +01 +01 +0 +Windows 7 x64,Windows 8 x64, Windows 10 x64, Windows 11 x64 +all languages +Security updates and new features +28 550 000 +tools,productivity,phone,pictures,OCR,open source +CopyPasta is a software linked to an android app that can basically double your productivity ! +main features : +scan text from camera or pictures and send it directly to your computer +save the text detected to your phone's clipboard +send pictures,videos,pdf... and all kind of files to your computer and to your phone +Email addresses and URLs are automatically detected from the text sent and displayed on your computer in a way that you can directly check the website or send the mail with your favorite mail client. +share wifi IDs to your computer so you just have to click on copy password and go ! +scan QR codes and barcodes +send text that will directly fill the input field where your cursor is focused on. (like address, password, text, URLs, large serial numbers etc...) +you only needs to start CopyPasta once on your desktop, even if you close the browser window CopyPasta can still receive scans and notify you ! (You still need to restart it after each shutdown of your computer) +If you have closed the browser window, you can reopen it by clicking on the shortcut like if you wanted to start copypasta or even just send your things and click on the notification toast ;D +Audio & Multimedia|Business|Communications|Desktop|Development|Education|Games +raw.githubusercontent.com/ThaaoBlues/CopyPasta/main/copypasta_desktop_screenshots/copypasta-1.png +https://github.com/CopyPastaOfficial/CopyPasta/releases/latest/download/launcher.exe +https://raw.githubusercontent.com/thaaoblues/CopyPasta/main/pad.xml + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index afb1c79..7ab2a7d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ win10toast-click flask_cors pyautogui pillow -waitress -webtest beautifulsoup4 +flask-socketio +gevent +requests diff --git a/templates/img_preview.html b/templates/img_preview.html index 3cbaf4e..3cd0392 100644 --- a/templates/img_preview.html +++ b/templates/img_preview.html @@ -22,10 +22,10 @@ @@ -51,24 +56,25 @@
Scan this QR Code with the app :
NAME : {{hostname}}
IP : {{ip}}
+
UPLOAD CODE : {{upload_code}}

{%if tab!=True%}
- +
{%else%}
- +
{%endif%}

Server version : {{server_version}} -
Server shutdown
+
Server shutdown
@@ -79,7 +85,16 @@

Last files/data sent :


- + +
+ +
+ + +
+
@@ -99,19 +114,13 @@

Last files/data sent :

- - {% with messages = get_flashed_messages() %} {% if messages %} - - {% endif %} {% endwith %} + - + + + + \ No newline at end of file diff --git a/templates/send_client.html b/templates/send_client.html new file mode 100644 index 0000000..0d30de9 --- /dev/null +++ b/templates/send_client.html @@ -0,0 +1,151 @@ + + + + + + + + CopyPasta/Client + + + + + + + + + + +
+

Don't forget that CopyPasta must be running on the other pc :)

+

{{msg}}

+
What do you want to send ?
+ +
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test.py b/test.py index 434f952..e69de29 100644 --- a/test.py +++ b/test.py @@ -1,8 +0,0 @@ -# keep main window hidden -from tkinter import Tk -from tkinter.filedialog import askopenfile - - -Tk().withdraw() -# open file dialog -file_path = askopenfile(mode = "r") \ No newline at end of file diff --git a/util.py b/util.py index fcbad99..f4585bd 100644 --- a/util.py +++ b/util.py @@ -3,25 +3,17 @@ from random import randint from json import * from bs4 import BeautifulSoup -from requests import get +from requests import get as http_get from os import mkdir, path, remove from xml.etree import ElementTree from xml.sax.saxutils import escape from multiprocessing import Process from webbrowser import open as open_tab from ast import literal_eval -import requests -from subprocess import Popen, run from functools import partial from win10toast_click import ToastNotifier -from win32com.client import Dispatch -from platform import system from time import sleep - - - -TEMPLATES_FILES = ["favicon.ico","index.html","scan_preview.html","img_preview.html","video_preview.html","download_page.html"] - +from subprocess import Popen def notify_desktop(title,text): # initialize @@ -65,21 +57,33 @@ def make_qr_url(): def check_templates_update(): + + + + # first, get a list of all templates available on github + + json_data = http_get("https://api.github.com/repos/copypastaofficial/copypasta/contents/templates").json() + + + # check templates integrity - for ele in TEMPLATES_FILES: - if not path.exists(f"templates/{ele}"): + for ele in json_data: + if not path.exists(ele["path"]): download_templates() return # check 10 startup rule - with open("static/update.Blue","r") as f: - n = int(f.read()) - if n == 10: + with open("static/config.json","r") as f: + config = loads(f.read()) + + if config["open_since_last_check"] >= 10 and not config["disable_updates"]: download_templates() + else: - with open("static/update.Blue","w") as f: - f.write(str(n+1)) + with open("static/config.json","w") as f: + config["open_since_last_check"] += 1 + f.write(dumps(config)) def emergency_redownload(): @@ -98,16 +102,13 @@ def emergency_redownload(): f = open("static/qr.jpeg","w") f.close() - - with open("static/update.Blue","w") as f: - f.write("1") - f.close() - mkdir("static/files_hist") init_history_file() + init_config_file() + download_templates() @@ -115,7 +116,7 @@ def emergency_redownload(): def is_server_already_running(): try: - response = requests.get("http://127.0.0.1:21987/api/ping").text + response = http_get("http://127.0.0.1:21987/api/ping").text except: response = "not pong lol" @@ -124,26 +125,57 @@ def is_server_already_running(): def download_templates(): - + + + # first, get a list of all templates available on github + + json_data = http_get("https://api.github.com/repos/copypastaofficial/copypasta/contents/templates").json() + + + """ + example : + [ + { + "name": "download_page.html", + "path": "templates/download_page.html", + "sha": "34d0f64b305c1798ec0b7aa9cc5cd192e3077368", + "size": 3089, + "url": "https://api.github.com/repos/ThaaoBlues/CopyPasta/contents/templates/download_page.html?ref=main", + "html_url": "https://github.com/ThaaoBlues/CopyPasta/blob/main/templates/download_page.html", + "git_url": "https://api.github.com/repos/ThaaoBlues/CopyPasta/git/blobs/34d0f64b305c1798ec0b7aa9cc5cd192e3077368", + "download_url": "https://raw.githubusercontent.com/ThaaoBlues/CopyPasta/main/templates/download_page.html", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/ThaaoBlues/CopyPasta/contents/templates/download_page.html?ref=main", + "git": "https://api.github.com/repos/ThaaoBlues/CopyPasta/git/blobs/34d0f64b305c1798ec0b7aa9cc5cd192e3077368", + "html": "https://github.com/ThaaoBlues/CopyPasta/blob/main/templates/download_page.html" + } + } + + ] + + """ + #get the templates - for ele in TEMPLATES_FILES: + for ele in json_data: + + r = http_get(ele["download_url"],allow_redirects=True) - r = get(f"https://raw.githubusercontent.com/copypastaofficial/copypasta/master/templates/{ele}",allow_redirects=True) - with open(f"templates/{ele}","wb") as f: + with open(ele["path"],"wb") as f: f.write(r.content) - with open("static/update.Blue","w") as f: - f.write("1") + # reset config + init_config_file() def update_main_executable(version): - if not literal_eval(get("https://api.github.com/repos/CopyPastaOfficial/CopyPasta/tags").text)[0]['name'] == version: + if not literal_eval(http_get("https://api.github.com/repos/CopyPastaOfficial/CopyPasta/tags").text)[0]['name'] == version: with open("copypasta(1).exe","wb") as f: - f.write(get("https://github.com/CopyPastaOfficial/CopyPasta/releases/latest/download/copypasta.exe").content) + f.write(http_get("https://github.com/CopyPastaOfficial/CopyPasta/releases/latest/download/copypasta.exe").content) f.close() Popen("copypasta(1).exe") @@ -176,29 +208,21 @@ def init_history_file(force=False): f.close() -def get_history(): +def get_history_json()->dict: # using lists and join() to speed up - history = ["{\"history\" : ["] + history = {"history":[]} for element in ElementTree.parse("static/history.xml").getroot(): - history.append(element.text) - history.append(",") - + history["history"].append(loads(element.text)) - if len(history) > 1: - history.pop() - history.append("]}") - else: - history.append("]}") - - return "".join(history) + return history def get_history_file_last_id(): return len(ElementTree.parse("static/history.xml").getroot()) -1 -def get_history_file_by_id(file_id): +def get_history_file_by_id(file_id) -> dict: if file_id < len(ElementTree.parse("static/history.xml").getroot()): @@ -245,21 +269,6 @@ def open_browser_if_settings_okay(url): if path.exists("static/tab"): Process(target=open_link_process,args=(url,)).start() - - - -def create_shortcut(path, target='', wDir='', icon=''): - - shell = Dispatch('WScript.Shell') - shortcut = shell.CreateShortCut(path) - shortcut.Targetpath = target - shortcut.WorkingDirectory = wDir - if icon == '': - pass - else: - shortcut.IconLocation = icon - shortcut.save() - def is_online(): @@ -268,33 +277,6 @@ def is_online(): return True except OSError: return False - - -def is_hosts_file_modified(): - - hosts_file_path = "C:\Windows\System32\Drivers\etc\hosts" if system() == "Windows" else "/etc/hosts" - - with open(hosts_file_path,"r") as f: - - return True if "copypasta.me" in f.read() else False - - -def add_copypasta_to_hosts_file(): - - hosts_file_path = "C:\Windows\System32\Drivers\etc\hosts" if system() == "Windows" else "/etc/hosts" - - with open(hosts_file_path,"a") as f: - - f.write("\n127.0.0.1:21987\tcopypasta") - - f.close() - - if system() == "Windows": - - # flush dns cache - run("ipconfig /flushdns",shell=True) - - add_copypasta_port_redirect() def get_server_version(): @@ -305,32 +287,6 @@ def get_server_version(): with open("version","r") as f: return f.read() - - - -def add_copypasta_port_redirect(): - - if system() == "Windows": - - # put port redirect from 127.0.0.1:21987 to 127.0.0.1:80 - try: - run("netsh interface portproxy add v4tov4 listenport=80 listenaddress=127.0.0.1 connectport=21987 connectaddress=127.0.0.1") - except: - # feature that may crash sometimes, not essential - pass - - -def remove_copypasta_port_redirect(): - - if system() == "Windows": - - # re-put port redirect from 127.0.0.1:80 to 127.0.0.1:80 - try: - run("netsh interface portproxy add v4tov4 listenport=80 listenaddress=127.0.0.1 connectport=80 connectaddress=127.0.0.1") - except: - # feature that may crash sometimes, not essential - pass - def is_image(file_type:str): @@ -343,7 +299,7 @@ def identify_product(isbn:str): # edible product ? - r = get(f"http://world.openfoodfacts.org/api/v0/product/{isbn}") + r = http_get(f"http://world.openfoodfacts.org/api/v0/product/{isbn}") r = loads(r.text) if "product" in r.keys(): @@ -353,7 +309,7 @@ def identify_product(isbn:str): # book ? - r = get(f"https://www.isbnsearcher.com/books/{isbn}",headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36 Edg/103.0.1264.37"}) + r = http_get(f"https://www.isbnsearcher.com/books/{isbn}",headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36 Edg/103.0.1264.37"}) if r.status_code == 200: r = BeautifulSoup(r.text,"html.parser") @@ -368,4 +324,54 @@ def delete_ot_dl_proc(APP_PATH,file:str): sleep(30) # delete file after download as it is only a one timer - remove(path.join(APP_PATH,"static","ot_upload",file)) \ No newline at end of file + remove(path.join(APP_PATH,"static","ot_upload",file)) + + +def clear_tmp(filename:str): + # wait the complete transfert + sleep(10) + #remove the temporary file + remove(f"tmp/{filename}") + +def init_config_file(): + """ + creates and init the config file + """ + + with open("static/config.json","w") as f: + f.write(dumps( + { + "accepting_uploads":True, + "disable_updates":False, + "open_since_last_check":1 + } + )) + +def is_accepting_uploads() -> bool: + + """ + check if user is accepting incoming uploads + """ + + with open("static/config.json","r") as f: + + config = loads(f.read()) + + return config["accepting_uploads"] + +def change_accepting_uploads_state(): + """ + + """ + + config = {} + + with open("static/config.json","r") as f: + + config = loads(f.read()) + + with open("static/config.json","w") as f: + + config["accepting_uploads"] = not config["accepting_uploads"] + + f.write(dumps(config)) \ No newline at end of file diff --git a/version b/version index 081af9a..abb1658 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.7.1 \ No newline at end of file +1.9.0 \ No newline at end of file