Clean Commit Overwrite Old Files
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 machinepainting
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,65 @@
|
||||
# ComfyUI DropSend Node
|
||||
|
||||
This custom node package adds automatic Dropbox upload capabilities to your ComfyUI workflows.
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- **📤 Dropbox AutoUploader Node**
|
||||
- Monitors a specified folder (e.g., ComfyUI's `output/`) in real time
|
||||
- Automatically uploads new images to your Dropbox account
|
||||
|
||||
- **🛠️ Dropbox Setup Node**
|
||||
- Accepts your App Key, App Secret, and Authorization Code
|
||||
- Automatically generates a refresh token and saves credentials to a `.env` file
|
||||
|
||||
- **🔐 Helpers**
|
||||
- `generate_base64.py`: Easily generate your Base64 App credentials
|
||||
- `get_refresh_token.py`: Standalone CLI for manually retrieving a refresh token
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Installation
|
||||
|
||||
Place this folder inside your `ComfyUI/custom_nodes/` directory:
|
||||
|
||||
```bash
|
||||
ComfyUI/custom_nodes/ComfyUI_DropSendNode/
|
||||
```
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Setup Instructions
|
||||
|
||||
1. Go to the [Dropbox App Console](https://www.dropbox.com/developers/apps) and create a new app.
|
||||
2. Enable `App Folder` access and note your **App Key** and **App Secret**.
|
||||
3. Generate an **Authorization Code** via:
|
||||
```
|
||||
https://www.dropbox.com/oauth2/authorize?client_id=APPKEY&response_type=code&token_access_type=offline
|
||||
```
|
||||
4. Paste these into the **🛠️ Dropbox Setup** node and run it once.
|
||||
5. Then use the **📤 Dropbox AutoUploader** node in your workflow to monitor and upload.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Example Workflow
|
||||
- Connect your image-saving pipeline to the default output folder
|
||||
- Add the **📤 AutoUploader** node pointing to that folder
|
||||
- Files will upload as soon as they’re saved
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tested On
|
||||
- Python 3.10 / 3.11
|
||||
- ComfyUI latest release (2024–2025)
|
||||
- Dropbox API with refresh token support
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
MIT
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# ComfyUI_DropSendNode/__init__.py
|
||||
# Register Dropbox Setup + Run-only nodes
|
||||
|
||||
from .run_dropbox_node import NODE_CLASS_MAPPINGS as RUN_CLASS, NODE_DISPLAY_NAME_MAPPINGS as RUN_DISPLAY
|
||||
from .setup_dropbox_node import NODE_CLASS_MAPPINGS as SETUP_CLASS, NODE_DISPLAY_NAME_MAPPINGS as SETUP_DISPLAY
|
||||
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_CLASS_MAPPINGS.update(RUN_CLASS)
|
||||
NODE_CLASS_MAPPINGS.update(SETUP_CLASS)
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(RUN_DISPLAY)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(SETUP_DISPLAY)
|
||||
@@ -0,0 +1,60 @@
|
||||
# ComfyUI_DropSendNode/dropbox_upload.py
|
||||
|
||||
import os
|
||||
import dropbox
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
def get_token():
|
||||
# ✅ 1. Prioritize direct access_token from general environment variables
|
||||
access_token = os.getenv("DROPBOX_ACCESS_TOKEN")
|
||||
if access_token:
|
||||
return access_token
|
||||
|
||||
# ✅ 2. Try refresh_token flow from general environment variables
|
||||
refresh_token = os.getenv("DROPBOX_REFRESH_TOKEN")
|
||||
client_id = os.getenv("DROPBOX_APP_KEY")
|
||||
client_secret = os.getenv("DROPBOX_APP_SECRET")
|
||||
|
||||
# ✅ 3. If missing, check for RunPod-style prefixed secrets
|
||||
refresh_token = refresh_token or os.getenv("RUNPOD_SECRET_DROPBOX_REFRESH_TOKEN")
|
||||
access_token = os.getenv("RUNPOD_SECRET_DROPBOX_ACCESS_TOKEN")
|
||||
if access_token:
|
||||
return access_token
|
||||
|
||||
# 🔁 4. Final fallback: load from .env for local users
|
||||
if not all([refresh_token, client_id, client_secret]):
|
||||
env_path = os.path.join(os.path.dirname(__file__), ".env")
|
||||
if os.path.exists(env_path):
|
||||
load_dotenv(env_path, override=True)
|
||||
refresh_token = refresh_token or os.getenv("DROPBOX_REFRESH_TOKEN") or os.getenv("RUNPOD_SECRET_DROPBOX_REFRESH_TOKEN")
|
||||
client_id = client_id or os.getenv("DROPBOX_APP_KEY")
|
||||
client_secret = client_secret or os.getenv("DROPBOX_APP_SECRET")
|
||||
|
||||
if not all([refresh_token, client_id, client_secret]):
|
||||
raise Exception("❌ Missing Dropbox credentials. Define them as environment variables or in a .env file.")
|
||||
|
||||
response = requests.post(
|
||||
"https://api.dropbox.com/oauth2/token",
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
data={
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": refresh_token,
|
||||
"client_id": client_id,
|
||||
"client_secret": client_secret,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()["access_token"]
|
||||
|
||||
def upload_to_dropbox(local_file_path, dropbox_folder="/Apps/ComfyUI_Output_Files"):
|
||||
access_token = get_token()
|
||||
dbx = dropbox.Dropbox(access_token)
|
||||
|
||||
file_name = os.path.basename(local_file_path)
|
||||
dropbox_path = f"{dropbox_folder}/{file_name}"
|
||||
|
||||
with open(local_file_path, "rb") as f:
|
||||
dbx.files_upload(f.read(), dropbox_path, mode=dropbox.files.WriteMode.overwrite)
|
||||
|
||||
print(f"📦 Uploaded to Dropbox: {dropbox_path}")
|
||||
@@ -0,0 +1,10 @@
|
||||
# ComfyUI_DropSendNode/generate_base64.py
|
||||
|
||||
import base64
|
||||
|
||||
app_key = input("Enter your App Key: ")
|
||||
app_secret = input("Enter your App Secret: ")
|
||||
|
||||
credentials = f"{app_key}:{app_secret}"
|
||||
encoded = base64.b64encode(credentials.encode()).decode()
|
||||
print(f"\nBasic {encoded}")
|
||||
@@ -0,0 +1,27 @@
|
||||
# ComfyUI_DropSendNode/get_refresh_token.py
|
||||
|
||||
import base64
|
||||
import requests
|
||||
|
||||
APP_KEY = input("Enter your APP KEY: ")
|
||||
APP_SECRET = input("Enter your APP SECRET: ")
|
||||
AUTH_CODE = input("Paste the Dropbox authorization code: ")
|
||||
|
||||
auth_header = base64.b64encode(f"{APP_KEY}:{APP_SECRET}".encode()).decode()
|
||||
res = requests.post(
|
||||
"https://api.dropbox.com/oauth2/token",
|
||||
data={
|
||||
"code": AUTH_CODE,
|
||||
"grant_type": "authorization_code",
|
||||
"redirect_uri": "https://localhost"
|
||||
},
|
||||
headers={
|
||||
"Authorization": f"Basic {auth_header}",
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
)
|
||||
|
||||
if res.ok:
|
||||
print("\nYour refresh token is:\n", res.json().get("refresh_token"))
|
||||
else:
|
||||
print("Error:", res.text)
|
||||
@@ -0,0 +1,48 @@
|
||||
# ComfyUI_DropSendNode/monitor_output.py
|
||||
|
||||
import time
|
||||
import os
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from .dropbox_upload import upload_to_dropbox
|
||||
|
||||
uploaded_files = set()
|
||||
|
||||
def start_monitoring(folder, dropbox_dest_folder):
|
||||
class Handler(FileSystemEventHandler):
|
||||
def on_created(self, event):
|
||||
if event.is_directory:
|
||||
return
|
||||
file_path = event.src_path
|
||||
|
||||
# Wait for file to finish writing
|
||||
while True:
|
||||
try:
|
||||
with open(file_path, 'rb') as f:
|
||||
f.read()
|
||||
break
|
||||
except Exception:
|
||||
time.sleep(0.5)
|
||||
|
||||
if file_path not in uploaded_files:
|
||||
try:
|
||||
upload_to_dropbox(file_path, dropbox_dest_folder)
|
||||
uploaded_files.add(file_path)
|
||||
print(f"📦✅ Uploaded: {file_path}")
|
||||
except Exception as e:
|
||||
print(f"📦❌ Upload failed: {e}")
|
||||
|
||||
# Ensure the folder exists
|
||||
if not os.path.exists(folder):
|
||||
os.makedirs(folder)
|
||||
|
||||
observer = Observer()
|
||||
observer.schedule(Handler(), folder, recursive=False)
|
||||
observer.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
observer.stop()
|
||||
observer.join()
|
||||
@@ -0,0 +1,3 @@
|
||||
requests
|
||||
python-dotenv
|
||||
watchdog
|
||||
@@ -0,0 +1,64 @@
|
||||
# ComfyUI_DropSendNode/run_dropbox_node.py
|
||||
|
||||
import os
|
||||
import threading
|
||||
from dotenv import load_dotenv, dotenv_values
|
||||
from .monitor_output import start_monitoring
|
||||
|
||||
class DropSendRunNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
node_dir = os.path.dirname(__file__)
|
||||
env_path = os.path.join(node_dir, ".env")
|
||||
|
||||
default_watch = os.path.join(os.getcwd(), "output")
|
||||
default_dest = "/Apps/ComfyUI_Output_Files"
|
||||
|
||||
# if .env exists, pull last-used folder
|
||||
if os.path.exists(env_path):
|
||||
cfg = dotenv_values(env_path)
|
||||
default_dest = cfg.get("DROPBOX_FOLDER", default_dest)
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"watch_folder": ("STRING", {"default": default_watch}),
|
||||
"dropbox_dest_folder": ("STRING", {"default": default_dest}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "start"
|
||||
|
||||
def start(self, watch_folder, dropbox_dest_folder):
|
||||
# persist new destination into .env alongside secrets
|
||||
node_dir = os.path.dirname(__file__)
|
||||
env_path = os.path.join(node_dir, ".env")
|
||||
|
||||
existing = {}
|
||||
if os.path.exists(env_path):
|
||||
existing = dotenv_values(env_path)
|
||||
|
||||
existing["DROPBOX_FOLDER"] = dropbox_dest_folder
|
||||
with open(env_path, "w") as f:
|
||||
for k, v in existing.items():
|
||||
f.write(f"{k}={v}\n")
|
||||
|
||||
load_dotenv(env_path, override=True)
|
||||
|
||||
# start (or restart) the folder watcher
|
||||
thread = threading.Thread(
|
||||
target=start_monitoring,
|
||||
args=(watch_folder, dropbox_dest_folder),
|
||||
daemon=True
|
||||
)
|
||||
thread.start()
|
||||
|
||||
return (
|
||||
f"📦✅ .env updated: DROPBOX_FOLDER={dropbox_dest_folder}",
|
||||
f"🔎📁 Monitoring Folder: {watch_folder} → {dropbox_dest_folder}"
|
||||
)
|
||||
|
||||
# Required mappings for ComfyUI
|
||||
NODE_CLASS_MAPPINGS = {"DropSendRunNode": DropSendRunNode}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"DropSendRunNode": "📦📤 Dropbox AutoUploader Run"}
|
||||
@@ -0,0 +1,72 @@
|
||||
# ComfyUI_DropSendNode/setup_dropbox_node.py
|
||||
|
||||
import os
|
||||
import requests
|
||||
from dotenv import load_dotenv, dotenv_values
|
||||
|
||||
class DropboxSetupNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"app_key": ("STRING", {"default": ""}),
|
||||
"app_secret": ("STRING", {"default": ""}),
|
||||
"auth_code": ("STRING", {"default": ""}),
|
||||
"dropbox_dest_folder": ("STRING", {"default": "/Apps/ComfyUI_Output_Files"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "setup"
|
||||
|
||||
def setup(self, app_key, app_secret, auth_code, dropbox_dest_folder):
|
||||
node_dir = os.path.dirname(__file__)
|
||||
env_path = os.path.join(node_dir, ".env")
|
||||
|
||||
# 1. Skip setup if generic environment variables already exist
|
||||
general_env_set = all([
|
||||
os.getenv("DROPBOX_APP_KEY"),
|
||||
os.getenv("DROPBOX_APP_SECRET"),
|
||||
os.getenv("DROPBOX_REFRESH_TOKEN")
|
||||
])
|
||||
if general_env_set:
|
||||
return ("⚠️ Dropbox credentials already set in system environment variables. Skipping .env creation.",)
|
||||
|
||||
# 2. Skip setup if RunPod secrets are defined
|
||||
runpod_env_set = all([
|
||||
os.getenv("RUNPOD_SECRET_DROPBOX_ACCESS_TOKEN"),
|
||||
os.getenv("RUNPOD_SECRET_DROPBOX_REFRESH_TOKEN")
|
||||
])
|
||||
if runpod_env_set:
|
||||
return ("⚠️ Detected RunPod secrets (RUNPOD_SECRET_DROPBOX_ACCESS_TOKEN, RUNPOD_SECRET_DROPBOX_REFRESH_TOKEN). Skipping .env creation.",)
|
||||
|
||||
# 3. Run auth_code flow if no env config is present
|
||||
if not all([app_key, app_secret, auth_code]):
|
||||
return ("❌ Missing App Key, Secret, or Authorization Code.",)
|
||||
|
||||
data = {
|
||||
"code": auth_code,
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": app_key,
|
||||
"client_secret": app_secret
|
||||
}
|
||||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
|
||||
resp = requests.post("https://api.dropbox.com/oauth2/token", headers=headers, data=data)
|
||||
resp.raise_for_status()
|
||||
refresh_token = resp.json().get("refresh_token")
|
||||
|
||||
# Write .env with fallback credentials
|
||||
with open(env_path, "w") as f:
|
||||
f.write(f"DROPBOX_APP_KEY={app_key}\n")
|
||||
f.write(f"DROPBOX_APP_SECRET={app_secret}\n")
|
||||
f.write(f"DROPBOX_REFRESH_TOKEN={refresh_token}\n")
|
||||
f.write(f"DROPBOX_FOLDER={dropbox_dest_folder}\n")
|
||||
|
||||
load_dotenv(env_path, override=True)
|
||||
return ("✅ .env file created with Dropbox credentials.",)
|
||||
|
||||
# Required mappings for ComfyUI
|
||||
NODE_CLASS_MAPPINGS = {"DropboxSetupNode": DropboxSetupNode}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"DropboxSetupNode": "📦⚙️ Dropbox AutoUploader Setup"}
|
||||
Reference in New Issue
Block a user