notebook
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
DROPBOX_APP_KEY=4scysbfek6ddwwm
|
||||||
|
DROPBOX_APP_SECRET=gn9ph1q3oro2nq0
|
||||||
|
DROPBOX_APP_REFRESH_TOKEN=VShbST3VjUgAAAAAAAAAAXeZZzFLns6eE80-VJKIc5oq61PyXW6sCx9Dw5kM1w8c
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import dropbox
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
from dropbox import DropboxOAuth2FlowNoRedirect
|
||||||
|
|
||||||
|
load_dotenv(Path(__file__).parent / ".env")
|
||||||
|
|
||||||
|
APP_KEY = os.getenv("DROPBOX_APP_KEY", "")
|
||||||
|
APP_SECRET = os.getenv("DROPBOX_APP_SECRET", "")
|
||||||
|
|
||||||
|
auth_flow = DropboxOAuth2FlowNoRedirect(
|
||||||
|
APP_KEY,
|
||||||
|
APP_SECRET,
|
||||||
|
token_access_type='offline' # důležité — dá refresh token
|
||||||
|
)
|
||||||
|
|
||||||
|
authorize_url = auth_flow.start()
|
||||||
|
print(f"Otevři v prohlížeči:\n{authorize_url}")
|
||||||
|
|
||||||
|
auth_code = input("Vlož autorizační kód: ").strip()
|
||||||
|
oauth_result = auth_flow.finish(auth_code)
|
||||||
|
|
||||||
|
print(f"Refresh token: {oauth_result.refresh_token}")
|
||||||
|
# Tento token ulož — platí "navždy" (dokud app neodvoláš)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import dropbox
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
|
load_dotenv(Path(__file__).parent / ".env")
|
||||||
|
|
||||||
|
APP_KEY = os.getenv("DROPBOX_APP_KEY", "")
|
||||||
|
APP_SECRET = os.getenv("DROPBOX_APP_SECRET", "")
|
||||||
|
REFRESH_TOKEN = os.getenv("DROPBOX_APP_REFRESH_TOKEN", "")
|
||||||
|
|
||||||
|
dbx = dropbox.Dropbox(
|
||||||
|
app_key=APP_KEY,
|
||||||
|
app_secret=APP_SECRET,
|
||||||
|
oauth2_refresh_token=REFRESH_TOKEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
dropbox_path = "/!!!Days/Downloads Z230/AHOJVLADO.TXT"
|
||||||
|
content = b"AHOJ VLADO"
|
||||||
|
|
||||||
|
dbx.files_upload(content, dropbox_path, mode=dropbox.files.WriteMode.overwrite)
|
||||||
|
print(f"Nahráno: {dropbox_path}")
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# msgreceiver — build & deploy na Unraid
|
||||||
|
|
||||||
|
## Umístění na Unraidu
|
||||||
|
- Appdata: `/mnt/user/appdata/msgreceiver/` (síťově `\\tower\appdata\msgreceiver\`)
|
||||||
|
- Emaily: `/mnt/user/JNJEMAILS` (mount jako `/msgs` v kontejneru)
|
||||||
|
|
||||||
|
## Kopírování souborů z Windows
|
||||||
|
Všechny soubory z `U:\janssen\EmailsImport\DockerCustomApp\` nakopírovat do `\\tower\appdata\msgreceiver\`.
|
||||||
|
|
||||||
|
## Build & restart (SSH)
|
||||||
|
```bash
|
||||||
|
# Připojení: ssh root@192.168.1.76, heslo: 7309208104
|
||||||
|
# Nebo přes paramiko v Pythonu (viz EmailsImport skripty)
|
||||||
|
|
||||||
|
cd /mnt/user/appdata/msgreceiver
|
||||||
|
docker build -t msgreceiver .
|
||||||
|
docker stop msgreceiver
|
||||||
|
docker rm msgreceiver
|
||||||
|
docker run -d --name msgreceiver \
|
||||||
|
-p 8765:8765 \
|
||||||
|
-v /mnt/user/JNJEMAILS:/msgs \
|
||||||
|
--restart unless-stopped \
|
||||||
|
msgreceiver
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kontejner
|
||||||
|
- Port: 8765
|
||||||
|
- Restart policy: unless-stopped
|
||||||
|
- Endpointy: `/upload` (msg), `/upload-db` (db), `/upload-dropbox` (soubory do Dropboxu)
|
||||||
|
- Auth: Bearer token v app.py
|
||||||
|
- Dropbox credentials: v `.env` uvnitř image
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
COPY app.py .
|
||||||
|
COPY .env .
|
||||||
|
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8765"]
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
from fastapi import FastAPI, UploadFile, File, Header, HTTPException
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import dropbox
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv(Path(__file__).parent / ".env")
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
TOKEN = "13e1bb01-9fd5-44a8-8ce9-4ee27133d340"
|
||||||
|
SAVE_DIR = Path("/msgs")
|
||||||
|
DB_DIR = Path("/msgs/db")
|
||||||
|
|
||||||
|
SAVE_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
DB_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
DROPBOX_APP_KEY = os.getenv("DROPBOX_APP_KEY", "")
|
||||||
|
DROPBOX_APP_SECRET = os.getenv("DROPBOX_APP_SECRET", "")
|
||||||
|
DROPBOX_REFRESH_TOKEN = os.getenv("DROPBOX_APP_REFRESH_TOKEN", "")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/upload")
|
||||||
|
async def upload_msg(
|
||||||
|
file: UploadFile = File(...),
|
||||||
|
authorization: str = Header(None)
|
||||||
|
):
|
||||||
|
if authorization != f"Bearer {TOKEN}":
|
||||||
|
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||||
|
if not file.filename.endswith(".msg"):
|
||||||
|
raise HTTPException(status_code=400, detail="Only .msg files accepted")
|
||||||
|
dest = SAVE_DIR / file.filename
|
||||||
|
if dest.exists():
|
||||||
|
return {"status": "exists", "file": file.filename}
|
||||||
|
with dest.open("wb") as f:
|
||||||
|
shutil.copyfileobj(file.file, f)
|
||||||
|
return {"status": "saved", "file": file.filename}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/upload-db")
|
||||||
|
async def upload_db(
|
||||||
|
file: UploadFile = File(...),
|
||||||
|
authorization: str = Header(None)
|
||||||
|
):
|
||||||
|
if authorization != f"Bearer {TOKEN}":
|
||||||
|
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||||
|
if not file.filename.endswith(".db"):
|
||||||
|
raise HTTPException(status_code=400, detail="Only .db files accepted")
|
||||||
|
dest = DB_DIR / file.filename
|
||||||
|
with dest.open("wb") as f:
|
||||||
|
shutil.copyfileobj(file.file, f)
|
||||||
|
return {"status": "saved", "file": file.filename}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/upload-dropbox")
|
||||||
|
async def upload_dropbox(
|
||||||
|
file: UploadFile = File(...),
|
||||||
|
authorization: str = Header(None),
|
||||||
|
):
|
||||||
|
if authorization != f"Bearer {TOKEN}":
|
||||||
|
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||||
|
if not DROPBOX_REFRESH_TOKEN:
|
||||||
|
raise HTTPException(status_code=500, detail="Dropbox not configured")
|
||||||
|
|
||||||
|
content = await file.read()
|
||||||
|
dbx = dropbox.Dropbox(
|
||||||
|
app_key=DROPBOX_APP_KEY,
|
||||||
|
app_secret=DROPBOX_APP_SECRET,
|
||||||
|
oauth2_refresh_token=DROPBOX_REFRESH_TOKEN,
|
||||||
|
)
|
||||||
|
dropbox_path = f"/!!!Days/Downloads Z230/{file.filename}"
|
||||||
|
dbx.files_upload(content, dropbox_path, mode=dropbox.files.WriteMode.overwrite)
|
||||||
|
return {"status": "uploaded", "file": file.filename, "dropbox_path": dropbox_path}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
python-multipart
|
||||||
|
dropbox
|
||||||
|
python-dotenv
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
import win32com.client
|
||||||
|
import requests
|
||||||
|
import sqlite3
|
||||||
|
import urllib3
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import tempfile
|
||||||
|
import io
|
||||||
|
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
TOKEN = "13e1bb01-9fd5-44a8-8ce9-4ee27133d340"
|
||||||
|
UPLOAD_URL = "https://msgs.buzalka.cz/upload"
|
||||||
|
DB_PATH = r"C:\Users\vbuzalka\SQLITE\jnjemails.db"
|
||||||
|
PR_INTERNET_MESSAGE_ID = "http://schemas.microsoft.com/mapi/proptag/0x1035001E"
|
||||||
|
|
||||||
|
def init_db(conn):
|
||||||
|
conn.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
message_id TEXT NOT NULL,
|
||||||
|
subject TEXT,
|
||||||
|
sender TEXT,
|
||||||
|
received_at TEXT,
|
||||||
|
folder TEXT,
|
||||||
|
source TEXT,
|
||||||
|
uploaded_at TEXT DEFAULT (datetime('now'))
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_message_id ON messages(message_id)")
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
def is_uploaded(conn, message_id):
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT 1 FROM messages WHERE message_id = ? LIMIT 1", (message_id,)
|
||||||
|
).fetchone()
|
||||||
|
return row is not None
|
||||||
|
|
||||||
|
def save_to_db(conn, message_id, subject, sender, received_at, folder, source):
|
||||||
|
conn.execute("""
|
||||||
|
INSERT OR IGNORE INTO messages (message_id, subject, sender, received_at, folder, source)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
""", (message_id, subject, sender, received_at, folder, source))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
def upload_db(db_path):
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
filename = f"jnjemails_{timestamp}.db"
|
||||||
|
with open(db_path, "rb") as f:
|
||||||
|
resp = requests.post(
|
||||||
|
"https://msgs.buzalka.cz/upload-db",
|
||||||
|
headers={"Authorization": f"Bearer {TOKEN}"},
|
||||||
|
files={"file": (filename, f, "application/octet-stream")},
|
||||||
|
timeout=60
|
||||||
|
)
|
||||||
|
print(f" DB upload: {resp.json()}")
|
||||||
|
|
||||||
|
def upload_msg(msg_path, filename):
|
||||||
|
with open(msg_path, "rb") as f:
|
||||||
|
resp = requests.post(
|
||||||
|
UPLOAD_URL,
|
||||||
|
headers={"Authorization": f"Bearer {TOKEN}"},
|
||||||
|
files={"file": (filename, f, "application/octet-stream")},
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json()["status"]
|
||||||
|
|
||||||
|
def get_folder_resume_date(conn, folder_path):
|
||||||
|
row = conn.execute(
|
||||||
|
"SELECT MAX(received_at) FROM messages WHERE folder = ?",
|
||||||
|
(folder_path,)
|
||||||
|
).fetchone()
|
||||||
|
if not row or not row[0]:
|
||||||
|
return None
|
||||||
|
last_dt = datetime.fromisoformat(row[0])
|
||||||
|
return last_dt - timedelta(hours=1)
|
||||||
|
|
||||||
|
def process_folder(conn, folder, source, folder_path="", counter=None):
|
||||||
|
if counter is None:
|
||||||
|
counter = [0]
|
||||||
|
|
||||||
|
current_path = f"{folder_path}/{folder.Name}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
resume_dt = get_folder_resume_date(conn, current_path)
|
||||||
|
|
||||||
|
items = folder.Items
|
||||||
|
|
||||||
|
if resume_dt:
|
||||||
|
resume_str = resume_dt.strftime("%Y/%m/%d %H:%M:%S")
|
||||||
|
filter_str = f"@SQL=\"urn:schemas:httpmail:datereceived\" > '{resume_str}'"
|
||||||
|
items = folder.Items.Restrict(filter_str)
|
||||||
|
print(f"\n Složka: {current_path} | pokračuji od: {resume_str}")
|
||||||
|
else:
|
||||||
|
print(f"\n Složka: {current_path} | od začátku")
|
||||||
|
|
||||||
|
items.Sort("[ReceivedTime]", False)
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
skipped = 0
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
try:
|
||||||
|
if not item.MessageClass.upper().startswith("IPM.NOTE"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
mid = item.PropertyAccessor.GetProperty(PR_INTERNET_MESSAGE_ID)
|
||||||
|
except:
|
||||||
|
mid = None
|
||||||
|
|
||||||
|
if not mid:
|
||||||
|
mid = f"entryid:{item.EntryID}"
|
||||||
|
|
||||||
|
if is_uploaded(conn, mid):
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
safe_name = f"{item.EntryID[-20:]}.msg"
|
||||||
|
tmp_path = Path(tmp) / safe_name
|
||||||
|
item.SaveAs(str(tmp_path), 3)
|
||||||
|
status = upload_msg(tmp_path, safe_name)
|
||||||
|
|
||||||
|
received = item.ReceivedTime.isoformat() if item.ReceivedTime else None
|
||||||
|
save_to_db(conn, mid, item.Subject, item.SenderEmailAddress,
|
||||||
|
received, current_path, source)
|
||||||
|
|
||||||
|
counter[0] += 1
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
if counter[0] % 1000 == 0:
|
||||||
|
print(f" → celkem {counter[0]} emailů přeneseno, uploaduji DB...")
|
||||||
|
upload_db(DB_PATH)
|
||||||
|
|
||||||
|
print(f" {status.upper():6} | {item.Subject[:60]}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" CHYBA | {getattr(item, 'Subject', '?')[:40]} | {e}")
|
||||||
|
|
||||||
|
print(f" → složka hotova: přeneseno {count} | skip {skipped}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" CHYBA složka {current_path}: {e}")
|
||||||
|
|
||||||
|
for subfolder in folder.Folders:
|
||||||
|
process_folder(conn, subfolder, source, current_path, counter)
|
||||||
|
|
||||||
|
# --- MAIN ---
|
||||||
|
Path(DB_PATH).parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
init_db(conn)
|
||||||
|
|
||||||
|
outlook = win32com.client.Dispatch("Outlook.Application")
|
||||||
|
ns = outlook.GetNamespace("MAPI")
|
||||||
|
|
||||||
|
for i in range(1, ns.Folders.Count + 1):
|
||||||
|
root = ns.Folders.Item(i)
|
||||||
|
|
||||||
|
# if "Archive" in root.Name:
|
||||||
|
# print(f"\n=== {root.Name} — přeskočeno ===")
|
||||||
|
# continue
|
||||||
|
|
||||||
|
source = "mailbox"
|
||||||
|
print(f"\n=== {root.Name} ({source}) ===")
|
||||||
|
process_folder(conn, root, source)
|
||||||
|
|
||||||
|
# Finální DB upload po dokončení
|
||||||
|
print("\nFinální upload DB...")
|
||||||
|
upload_db(DB_PATH)
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
print("\nHotovo.")
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import requests
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
TOKEN = "13e1bb01-9fd5-44a8-8ce9-4ee27133d340"
|
||||||
|
UPLOAD_URL = "https://msgs.buzalka.cz/upload-dropbox"
|
||||||
|
SOURCE_DIR = Path(r"C:\Users\vbuzalka\OneDrive - JNJ\##JNJPrenos")
|
||||||
|
|
||||||
|
files = [f for f in SOURCE_DIR.iterdir() if f.is_file()]
|
||||||
|
|
||||||
|
if not files:
|
||||||
|
print("Žádné soubory k odeslání.")
|
||||||
|
else:
|
||||||
|
for f in files:
|
||||||
|
try:
|
||||||
|
with f.open("rb") as fh:
|
||||||
|
resp = requests.post(
|
||||||
|
UPLOAD_URL,
|
||||||
|
headers={"Authorization": f"Bearer {TOKEN}"},
|
||||||
|
files={"file": (f.name, fh, "application/octet-stream")},
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
result = resp.json()
|
||||||
|
print(f" {result['status'].upper():10} | {f.name}")
|
||||||
|
f.unlink()
|
||||||
|
except Exception as e:
|
||||||
|
print(f" CHYBA | {f.name} | {e}")
|
||||||
|
|
||||||
|
print("\nHotovo.")
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import time
|
||||||
|
import requests
|
||||||
|
from pathlib import Path
|
||||||
|
from watchdog.observers import Observer
|
||||||
|
from watchdog.events import FileSystemEventHandler
|
||||||
|
|
||||||
|
TOKEN = "13e1bb01-9fd5-44a8-8ce9-4ee27133d340"
|
||||||
|
UPLOAD_URL = "https://msgs.buzalka.cz/upload-dropbox"
|
||||||
|
SOURCE_DIR = Path(r"C:\Users\vbuzalka\OneDrive - JNJ\##JNJPrenos")
|
||||||
|
|
||||||
|
|
||||||
|
def upload_file(f: Path):
|
||||||
|
time.sleep(2)
|
||||||
|
if not f.exists() or not f.is_file():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
with f.open("rb") as fh:
|
||||||
|
resp = requests.post(
|
||||||
|
UPLOAD_URL,
|
||||||
|
headers={"Authorization": f"Bearer {TOKEN}"},
|
||||||
|
files={"file": (f.name, fh, "application/octet-stream")},
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
print(f" UPLOADED | {f.name}")
|
||||||
|
f.unlink()
|
||||||
|
except Exception as e:
|
||||||
|
print(f" CHYBA | {f.name} | {e}")
|
||||||
|
|
||||||
|
|
||||||
|
class NewFileHandler(FileSystemEventHandler):
|
||||||
|
def on_created(self, event):
|
||||||
|
if event.is_directory:
|
||||||
|
return
|
||||||
|
upload_file(Path(event.src_path))
|
||||||
|
|
||||||
|
def on_moved(self, event):
|
||||||
|
if event.is_directory:
|
||||||
|
return
|
||||||
|
upload_file(Path(event.dest_path))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Při startu odešli soubory, které už tam jsou
|
||||||
|
for f in SOURCE_DIR.iterdir():
|
||||||
|
if f.is_file():
|
||||||
|
upload_file(f)
|
||||||
|
|
||||||
|
observer = Observer()
|
||||||
|
observer.schedule(NewFileHandler(), str(SOURCE_DIR), recursive=False)
|
||||||
|
observer.start()
|
||||||
|
print(f"Hlídám: {SOURCE_DIR}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
observer.stop()
|
||||||
|
observer.join()
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
IMEDIDATA_USERNAME=vladimir.buzalka
|
IMEDIDATA_USERNAME=vladimir.buzalka
|
||||||
IMEDIDATA_PASSWORD=Mar2026Ax162q8+
|
IMEDIDATA_PASSWORD=Mar2026Ax162q8+
|
||||||
DOWNLOAD_DIR=./downloads
|
DOWNLOAD_DIR=./downloads
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user