Initial commit
This commit is contained in:
parent
c79c6f4e6b
commit
173dedbf41
11 changed files with 885 additions and 0 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
BIN
Book1 copy.xlsx
Normal file
BIN
Book1 copy.xlsx
Normal file
Binary file not shown.
BIN
Book1 copy_updated.xlsx
Normal file
BIN
Book1 copy_updated.xlsx
Normal file
Binary file not shown.
BIN
Book1.xlsx
Normal file
BIN
Book1.xlsx
Normal file
Binary file not shown.
BIN
Book1_updated.xlsx
Normal file
BIN
Book1_updated.xlsx
Normal file
Binary file not shown.
7
Solventum_2.code-workspace
Normal file
7
Solventum_2.code-workspace
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
1
config.json
Normal file
1
config.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"last_dir": "/Users/vadymsamoilenko/Documents/GIT/Script"}
|
||||
297
cookies.json
Normal file
297
cookies.json
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
[
|
||||
{
|
||||
"domain": ".adobe.com",
|
||||
"expirationDate": 1773416846.396066,
|
||||
"hostOnly": false,
|
||||
"httpOnly": false,
|
||||
"name": "AMCV_9E1005A551ED61CA0A490D45%40AdobeOrg",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": false,
|
||||
"session": false,
|
||||
"storeId": "0",
|
||||
"value": "1176715910%7CMCMID%7C17349451639425099143064126811155089188%7CMCAAMLH-1739461646%7C6%7CMCAAMB-1739461646%7CRKhpRz8krg2tLO6pguXWp5olkAcUniQYPHaMWWgdJ3xzPWQmdj0y%7CMCOPTOUT-1738864046s%7CNONE%7CMCAID%7CNONE%7CvVersion%7C5.4.0",
|
||||
"id": 1
|
||||
},
|
||||
{
|
||||
"domain": ".adobe.com",
|
||||
"hostOnly": false,
|
||||
"httpOnly": false,
|
||||
"name": "AMCVS_9E1005A551ED61CA0A490D45%40AdobeOrg",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": false,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "1",
|
||||
"id": 2
|
||||
},
|
||||
{
|
||||
"domain": ".adobe.com",
|
||||
"expirationDate": 1770323817.206102,
|
||||
"hostOnly": false,
|
||||
"httpOnly": true,
|
||||
"name": "aux_sid",
|
||||
"path": "/",
|
||||
"sameSite": "no_restriction",
|
||||
"secure": true,
|
||||
"session": false,
|
||||
"storeId": "0",
|
||||
"value": "ARveyFS2d5-rYByIEKNVQNpPLmxTkVev8z6gJDDB0ZK3yAi7FR2pZ99q-CmiaFoeNAv305oPWfwjN1EM7ZknomKJpsNtCzz_5gS6ynKMEFQIThpefdkgPIIi2ID43fyqB_hp7llLxEKha_MiNJAf7xQI9jC30p-tLpN4nG3Xyw3R_BuZMphR333c6cgH39UeUcmioemcKJjqBVKahqun1U5IUHzlhnwqwu5vJ04x7FF7Q-qW5py22Yv9vC5QZplCPcjrFQgMlRUzUuA2sJ1tmA0j0aAD9bigzuMf32MYE8bra1H07PxPUMcV5LsKhe8cWP_0WzhD05p2dVAQI5Pp3O1E8J-i6RCf3uX_ehbqCpDPaJMzj_F4av6yFchvCjgsPOFhakXWxKnvuJWd_TeOJ0kgF5K2tMGEUKL6V4m7wCriM3zXHfG83cZMATeCcLoQtB8BvcP-dMOJvRKvVha3bN0oM7cauMSBiA5OQdm2rprMqJxX6VrGlh7t1Ozr7IvszMt91SO8EH8U3jCzkYL32ttvAkHGDjHfz-PZNWMMVkAGpgJO4JYPZphtZJllB54NH_1WO8iX5vAJ2T-qOpWQwmbLYqKQbcRt6Sb7AxvpEuRuFURHSxRfZQrGOKUIkzstiAI-LHOZOAL7Fhoa-NB9r0lqWY9cVas2kGjqUvEoIAXFJaLW1pGrXu3XpiPZcFWYTNyg1MvL4nkFm4i4VyLlQcFJnXosGSU3JmTch4o71cm4QwHvCEuzMF1mGkIr3ruhZkrXUrXZVhtbJu6XjNcebNUxCF69ecy-lUbVZOw7WOcwBY4",
|
||||
"id": 3
|
||||
},
|
||||
{
|
||||
"domain": ".adobe.com",
|
||||
"expirationDate": 1738858648,
|
||||
"hostOnly": false,
|
||||
"httpOnly": false,
|
||||
"name": "gpv",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": false,
|
||||
"session": false,
|
||||
"storeId": "0",
|
||||
"value": "Account:IMS:Process:Complete",
|
||||
"id": 4
|
||||
},
|
||||
{
|
||||
"domain": ".adobe.com",
|
||||
"expirationDate": 1770392846,
|
||||
"hostOnly": false,
|
||||
"httpOnly": false,
|
||||
"name": "OptanonConsent",
|
||||
"path": "/",
|
||||
"sameSite": "no_restriction",
|
||||
"secure": true,
|
||||
"session": false,
|
||||
"storeId": "0",
|
||||
"value": "isGpcEnabled=0&datestamp=Thu+Feb+06+2025+15%3A47%3A26+GMT%2B0000+(Greenwich+Mean+Time)&version=202311.1.0&browserGpcFlag=0&isIABGlobal=false&hosts=&consentId=e40ce7eb-a167-4f1e-a646-2783e554a9c1&interactionCount=1&landingPath=NotLandingPage&groups=C0001%3A1%2CC0002%3A0%2CC0003%3A0%2CC0004%3A0&AwaitingReconsent=false",
|
||||
"id": 5
|
||||
},
|
||||
{
|
||||
"domain": ".adobe.com",
|
||||
"hostOnly": false,
|
||||
"httpOnly": false,
|
||||
"name": "s_cc",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": false,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "true",
|
||||
"id": 6
|
||||
},
|
||||
{
|
||||
"domain": ".adobe.com",
|
||||
"expirationDate": 1773416848.218817,
|
||||
"hostOnly": false,
|
||||
"httpOnly": false,
|
||||
"name": "s_ecid",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": false,
|
||||
"session": false,
|
||||
"storeId": "0",
|
||||
"value": "MCMID%7C17349451639425099143064126811155089188",
|
||||
"id": 7
|
||||
},
|
||||
{
|
||||
"domain": ".adobe.com",
|
||||
"expirationDate": 1773416845.222818,
|
||||
"hostOnly": false,
|
||||
"httpOnly": false,
|
||||
"name": "s_fid",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": false,
|
||||
"session": false,
|
||||
"storeId": "0",
|
||||
"value": "2959E3DDB076BFB0-013B63E267B41648",
|
||||
"id": 8
|
||||
},
|
||||
{
|
||||
"domain": ".adobe.com",
|
||||
"hostOnly": false,
|
||||
"httpOnly": true,
|
||||
"name": "s_ims",
|
||||
"path": "/",
|
||||
"sameSite": "no_restriction",
|
||||
"secure": true,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "https://ims-na1.adobelogin.com/ims/session/v1/ZTQxMzZlZWQtZmIyNC00YjVhLTk0YjItZDZjZmNlZWJlZWJmLS02RDk2MUU5MTY3NjVENzREMEE0OTVFRTNAZWZhNTNkZmY2NDQ5MzZiZTQ5NWZiMi5l",
|
||||
"id": 9
|
||||
},
|
||||
{
|
||||
"domain": ".adobe.com",
|
||||
"hostOnly": false,
|
||||
"httpOnly": false,
|
||||
"name": "s_ppv",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": false,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "[%22auth.services.adobe.com/en_US/deeplink.html%22%2C100%2C0%2C1199%2C2544%2C1199%2C2560%2C1440%2C2%2C%22P%22]",
|
||||
"id": 10
|
||||
},
|
||||
{
|
||||
"domain": "mmmspinco.brand-portal.adobe.com",
|
||||
"hostOnly": true,
|
||||
"httpOnly": false,
|
||||
"name": "ApplicationGatewayAffinity",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": false,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "773965041c0c59ebea497d34b646d89f",
|
||||
"id": 11
|
||||
},
|
||||
{
|
||||
"domain": "mmmspinco.brand-portal.adobe.com",
|
||||
"hostOnly": true,
|
||||
"httpOnly": false,
|
||||
"name": "ApplicationGatewayAffinityCORS",
|
||||
"path": "/",
|
||||
"sameSite": "no_restriction",
|
||||
"secure": true,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "773965041c0c59ebea497d34b646d89f",
|
||||
"id": 12
|
||||
},
|
||||
{
|
||||
"domain": "mmmspinco.brand-portal.adobe.com",
|
||||
"expirationDate": 1773326870.932259,
|
||||
"hostOnly": true,
|
||||
"httpOnly": false,
|
||||
"name": "bp-system-notifications",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": false,
|
||||
"session": false,
|
||||
"storeId": "0",
|
||||
"value": "uid-e4ea986f4acffba136080287c5412be6e039ae8d",
|
||||
"id": 13
|
||||
},
|
||||
{
|
||||
"domain": "mmmspinco.brand-portal.adobe.com",
|
||||
"expirationDate": 1739461692,
|
||||
"hostOnly": true,
|
||||
"httpOnly": false,
|
||||
"name": "cq-assets-files",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": false,
|
||||
"session": false,
|
||||
"storeId": "0",
|
||||
"value": "card",
|
||||
"id": 14
|
||||
},
|
||||
{
|
||||
"domain": "mmmspinco.brand-portal.adobe.com",
|
||||
"expirationDate": 1739371666.692624,
|
||||
"hostOnly": true,
|
||||
"httpOnly": false,
|
||||
"name": "cq-authoring-mode",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": true,
|
||||
"session": false,
|
||||
"storeId": "0",
|
||||
"value": "TOUCH",
|
||||
"id": 15
|
||||
},
|
||||
{
|
||||
"domain": "mmmspinco.brand-portal.adobe.com",
|
||||
"hostOnly": true,
|
||||
"httpOnly": true,
|
||||
"name": "JSESSIONID",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": true,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "gzkyrt1sverd1v2izhudp0zqc",
|
||||
"id": 16
|
||||
},
|
||||
{
|
||||
"domain": "mmmspinco.brand-portal.adobe.com",
|
||||
"hostOnly": true,
|
||||
"httpOnly": true,
|
||||
"name": "login-token",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": true,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "b373b976-515c-44af-a48f-f88f93e9f8c7%3ad07ac9e0-74e4-4a85-a003-b9a3398c999a_18d4b4f70b20d06d108d03bbdf0c7333%3acrx.default",
|
||||
"id": 17
|
||||
},
|
||||
{
|
||||
"domain": "mmmspinco.brand-portal.adobe.com",
|
||||
"hostOnly": true,
|
||||
"httpOnly": true,
|
||||
"name": "MC_AoD2",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": true,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "\"{9e5029df170e469714ac05a728d40da7fbb935fa88f0b39f13b7357f913b50db5fac90a34db3ca79403bca01d8ba5c820537af63204ce1f5ed5109354e312786f6e435a5746586c5d2f98dfdbb5938a0226ba3bd1d0e281fb6a774040ff4b8a72934968c0f699390f02dad020914edb4921b5e80653550aec6366fe401e47c06}\"",
|
||||
"id": 18
|
||||
},
|
||||
{
|
||||
"domain": "mmmspinco.brand-portal.adobe.com",
|
||||
"hostOnly": true,
|
||||
"httpOnly": true,
|
||||
"name": "oauth-authid",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": true,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "ims",
|
||||
"id": 19
|
||||
},
|
||||
{
|
||||
"domain": "mmmspinco.brand-portal.adobe.com",
|
||||
"hostOnly": true,
|
||||
"httpOnly": true,
|
||||
"name": "oauth-configid",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": true,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "ims",
|
||||
"id": 20
|
||||
},
|
||||
{
|
||||
"domain": "mmmspinco.brand-portal.adobe.com",
|
||||
"hostOnly": true,
|
||||
"httpOnly": false,
|
||||
"name": "renderid",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": true,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "rend0",
|
||||
"id": 21
|
||||
},
|
||||
{
|
||||
"domain": "mmmspinco.brand-portal.adobe.com",
|
||||
"hostOnly": true,
|
||||
"httpOnly": false,
|
||||
"name": "shell.omnisearch.results.layoutId",
|
||||
"path": "/",
|
||||
"sameSite": "unspecified",
|
||||
"secure": false,
|
||||
"session": true,
|
||||
"storeId": "0",
|
||||
"value": "card",
|
||||
"id": 22
|
||||
}
|
||||
]
|
||||
376
main.py
Normal file
376
main.py
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import openpyxl
|
||||
from kivy.app import App
|
||||
from kivy.properties import ObjectProperty
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.button import Button
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.popup import Popup
|
||||
from kivy.uix.progressbar import ProgressBar
|
||||
from kivy.uix.filechooser import FileChooserListView
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.common.exceptions import NoSuchElementException, TimeoutException, StaleElementReferenceException
|
||||
from colorama import Fore, Style, init
|
||||
|
||||
init(autoreset=True)
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
filename=f'app_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
|
||||
)
|
||||
|
||||
CONFIG_FILE = 'config.json'
|
||||
CHROME_PROFILE_PATH = "/Users/vadymsamoilenko/Library/Application Support/Google/Chrome/Profile 1"
|
||||
PORTAL_URL = "https://mmmspinco.brand-portal.adobe.com"
|
||||
WAIT_TIMEOUT = 30
|
||||
LONG_TIMEOUT = 60
|
||||
MAX_RETRIES = 3
|
||||
SEARCH_DELAY = 3
|
||||
|
||||
def get_last_used_directory():
|
||||
if os.path.exists(CONFIG_FILE):
|
||||
with open(CONFIG_FILE, 'r') as file:
|
||||
config = json.load(file)
|
||||
return config.get('last_dir', '')
|
||||
return ''
|
||||
|
||||
def save_last_used_directory(directory):
|
||||
with open(CONFIG_FILE, 'w') as file:
|
||||
json.dump({'last_dir': directory}, file)
|
||||
|
||||
def normalize_string(s):
|
||||
return re.sub(r'[-_\s]+', '_', s.lower().strip())
|
||||
|
||||
def get_search_key(filename):
|
||||
if not filename:
|
||||
return ""
|
||||
base = os.path.splitext(filename)[0]
|
||||
return base.replace("-", "_")
|
||||
|
||||
class FileSearchApp(App):
|
||||
file_path = ObjectProperty(None)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setup_logging()
|
||||
self.driver: webdriver.Chrome | None = None
|
||||
self.continue_button = None
|
||||
self.progress = None
|
||||
self.status_label = None
|
||||
self.label = None
|
||||
|
||||
def setup_logging(self):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
def build(self):
|
||||
self.file_path = ""
|
||||
self.extensions = [".psd", ".indd", ".tif", ".tiff"]
|
||||
|
||||
layout = BoxLayout(orientation='vertical')
|
||||
self.label = Label(text="Select an Excel file with Asset IDs")
|
||||
layout.add_widget(self.label)
|
||||
|
||||
# Создаем кнопки с использованием on_press вместо bind
|
||||
choose_button = Button(text="Choose File", on_press=self.select_file)
|
||||
layout.add_widget(choose_button)
|
||||
|
||||
start_button = Button(text="Open Login Page", on_press=self.open_login_page)
|
||||
layout.add_widget(start_button)
|
||||
|
||||
self.continue_button = Button(
|
||||
text="Continue After Login",
|
||||
disabled=True,
|
||||
on_press=self.start_search
|
||||
)
|
||||
layout.add_widget(self.continue_button)
|
||||
|
||||
self.progress = ProgressBar(max=100)
|
||||
layout.add_widget(self.progress)
|
||||
|
||||
self.status_label = Label(text="Ready")
|
||||
layout.add_widget(self.status_label)
|
||||
|
||||
return layout
|
||||
|
||||
def select_file(self, instance):
|
||||
last_dir = get_last_used_directory() or '.'
|
||||
content = BoxLayout(orientation='vertical')
|
||||
filechooser = FileChooserListView(path=last_dir, filters=['*.xlsx'])
|
||||
|
||||
def on_selection(instance, selection, touch=None):
|
||||
if selection:
|
||||
self.on_file_select(selection, popup)
|
||||
|
||||
filechooser.bind(selection=on_selection)
|
||||
content.add_widget(filechooser)
|
||||
|
||||
popup = Popup(
|
||||
title="Choose Excel File",
|
||||
content=content,
|
||||
size_hint=(0.9, 0.9)
|
||||
)
|
||||
popup.open()
|
||||
|
||||
def on_file_select(self, selection, popup):
|
||||
if selection:
|
||||
self.file_path = selection[0]
|
||||
self.label.text = f"Selected File: {self.file_path}"
|
||||
self.logger.info(f"Selected file: {self.file_path}")
|
||||
save_last_used_directory(os.path.dirname(self.file_path))
|
||||
popup.dismiss()
|
||||
else:
|
||||
self.logger.warning("No file selected.")
|
||||
|
||||
def wait_for_element(self, by, value, timeout=WAIT_TIMEOUT, retries=3):
|
||||
if self.driver is None:
|
||||
raise ValueError("WebDriver is not initialized")
|
||||
|
||||
for attempt in range(retries):
|
||||
try:
|
||||
element = WebDriverWait(self.driver, timeout).until(
|
||||
EC.presence_of_element_located((by, value))
|
||||
)
|
||||
return element
|
||||
except TimeoutException:
|
||||
if attempt == retries - 1:
|
||||
raise
|
||||
time.sleep(2)
|
||||
return None
|
||||
|
||||
def wait_for_search_results(self, timeout=LONG_TIMEOUT):
|
||||
if self.driver is None:
|
||||
return False
|
||||
|
||||
try:
|
||||
WebDriverWait(self.driver, timeout).until(
|
||||
EC.presence_of_element_located((By.ID, "granite-omnisearch-result"))
|
||||
)
|
||||
time.sleep(SEARCH_DELAY)
|
||||
return True
|
||||
except TimeoutException:
|
||||
return False
|
||||
|
||||
def open_login_page(self, instance):
|
||||
if not self.file_path:
|
||||
popup = Popup(
|
||||
title='Warning',
|
||||
content=Label(text='Select a file before starting!'),
|
||||
size_hint=(0.8, 0.3)
|
||||
)
|
||||
popup.open()
|
||||
return
|
||||
|
||||
try:
|
||||
options = Options()
|
||||
options.add_argument(f"user-data-dir={CHROME_PROFILE_PATH}")
|
||||
self.driver = webdriver.Chrome(options=options)
|
||||
|
||||
if self.driver is not None:
|
||||
self.driver.get(f"{PORTAL_URL}/mediaportal.html/content/dam/mac/mmmspinco")
|
||||
time.sleep(5)
|
||||
|
||||
if self.wait_for_element(By.XPATH, "/html/body/coral-shell/coral-shell-content/div[1]/a/img"):
|
||||
self.continue_button.disabled = False
|
||||
self.logger.info("Login successful, ready to continue.")
|
||||
login_popup = Popup(
|
||||
title='Login',
|
||||
content=Label(text='Please log in and complete 2FA.\nClick "Continue After Login" once logged in.'),
|
||||
size_hint=(0.8, 0.3)
|
||||
)
|
||||
login_popup.open()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error opening login page: {e}")
|
||||
if self.driver:
|
||||
self.driver.quit()
|
||||
|
||||
def find_matching_link(self, file_name, psd_links):
|
||||
try:
|
||||
normalized_search_key = normalize_string(file_name)
|
||||
self.logger.info(f"Looking for links matching: {normalized_search_key}")
|
||||
|
||||
best_match = None
|
||||
best_ratio = 0
|
||||
|
||||
for link in psd_links if isinstance(psd_links, list) else [psd_links]:
|
||||
try:
|
||||
href = link.get_attribute("href")
|
||||
if href:
|
||||
link_filename = os.path.splitext(href.split('/')[-1])[0]
|
||||
normalized_link = normalize_string(link_filename)
|
||||
|
||||
if normalized_search_key in normalized_link or normalized_link in normalized_search_key:
|
||||
ratio = len(set(normalized_search_key.split('_')) & set(normalized_link.split('_'))) / \
|
||||
len(set(normalized_search_key.split('_')) | set(normalized_link.split('_')))
|
||||
|
||||
if ratio > best_ratio:
|
||||
best_ratio = ratio
|
||||
best_match = href
|
||||
self.logger.info(f"Found better match: {href} (ratio: {ratio})")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error processing link: {e}")
|
||||
continue
|
||||
|
||||
return best_match if best_match and best_ratio >= 0.5 else None
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error finding PSD links: {e}")
|
||||
return None
|
||||
|
||||
def process_row(self, row, total_rows):
|
||||
if self.driver is None:
|
||||
raise ValueError("WebDriver is not initialized")
|
||||
|
||||
if not row or not hasattr(row[0], 'value'):
|
||||
return None, None
|
||||
|
||||
asset_id = row[0].value
|
||||
file_name = row[2].value if len(row) >= 3 else None
|
||||
found_link = None
|
||||
|
||||
if not asset_id or not file_name:
|
||||
return row[0].row if hasattr(row[0], 'row') else None, None
|
||||
|
||||
self.status_label.text = f"Processing {asset_id}"
|
||||
self.logger.info(f"Processing Asset ID: {asset_id} with filename: {file_name}")
|
||||
|
||||
for attempt in range(MAX_RETRIES):
|
||||
try:
|
||||
self.driver.get(f"{PORTAL_URL}/aem/search.html")
|
||||
time.sleep(SEARCH_DELAY)
|
||||
|
||||
search_box = WebDriverWait(self.driver, WAIT_TIMEOUT).until(
|
||||
EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='fulltext']"))
|
||||
)
|
||||
search_box.clear()
|
||||
search_box.send_keys(asset_id)
|
||||
search_box.send_keys(Keys.RETURN)
|
||||
|
||||
WebDriverWait(self.driver, LONG_TIMEOUT).until(
|
||||
EC.presence_of_element_located((By.ID, "granite-omnisearch-result"))
|
||||
)
|
||||
time.sleep(SEARCH_DELAY)
|
||||
|
||||
asset_element = WebDriverWait(self.driver, WAIT_TIMEOUT).until(
|
||||
EC.presence_of_element_located((
|
||||
By.XPATH,
|
||||
f"//coral-card-subtitle[contains(@class, 'foundation-collection-item-subtitle') and text()='{file_name}']"
|
||||
))
|
||||
)
|
||||
|
||||
if asset_element:
|
||||
asset_card = asset_element.find_element(By.XPATH, "./ancestor::coral-card")
|
||||
self.driver.execute_script("arguments[0].scrollIntoView(true);", asset_card)
|
||||
time.sleep(1)
|
||||
|
||||
self.driver.execute_script("""
|
||||
var element = arguments[0];
|
||||
var rect = element.getBoundingClientRect();
|
||||
var event = new MouseEvent('mouseover', {
|
||||
'view': window,
|
||||
'bubbles': true,
|
||||
'cancelable': true,
|
||||
'clientX': rect.left + rect.width/2,
|
||||
'clientY': rect.top + rect.height/2
|
||||
});
|
||||
element.dispatchEvent(event);
|
||||
""", asset_card)
|
||||
time.sleep(1)
|
||||
|
||||
properties_button = WebDriverWait(self.driver, 10).until(
|
||||
EC.element_to_be_clickable((By.CSS_SELECTOR, "button[title='Properties']"))
|
||||
)
|
||||
self.driver.execute_script("arguments[0].click();", properties_button)
|
||||
time.sleep(2)
|
||||
|
||||
psd_links = WebDriverWait(self.driver, LONG_TIMEOUT).until(
|
||||
EC.presence_of_all_elements_located((
|
||||
By.XPATH,
|
||||
"//div[contains(@class, 'references-referencing')]//a[contains(@data-asset-path, '.psd')]"
|
||||
))
|
||||
)
|
||||
|
||||
if psd_links:
|
||||
found_link = self.find_matching_link(file_name, psd_links)
|
||||
if found_link:
|
||||
break
|
||||
else:
|
||||
self.logger.warning(f"Asset card not found for {asset_id}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error processing {asset_id} (attempt {attempt + 1}): {e}")
|
||||
if attempt == MAX_RETRIES - 1:
|
||||
break
|
||||
time.sleep(2)
|
||||
|
||||
if hasattr(row[0], 'row'):
|
||||
progress = (row[0].row / total_rows) * 100 if total_rows > 0 else 0
|
||||
self.progress.value = progress
|
||||
|
||||
return row[0].row if hasattr(row[0], 'row') else None, found_link
|
||||
|
||||
def start_search(self, instance):
|
||||
if not self.file_path or self.continue_button.disabled:
|
||||
popup = Popup(
|
||||
title='Warning',
|
||||
content=Label(text='Ensure you are logged in before starting!'),
|
||||
size_hint=(0.8, 0.3)
|
||||
)
|
||||
popup.open()
|
||||
return
|
||||
|
||||
try:
|
||||
wb = openpyxl.load_workbook(self.file_path)
|
||||
if wb is None:
|
||||
raise ValueError("Failed to load workbook")
|
||||
|
||||
sheet = wb.active
|
||||
if sheet is None:
|
||||
raise ValueError("Failed to get active sheet")
|
||||
|
||||
total_rows = sheet.max_row - 1 if sheet.max_row > 1 else 1
|
||||
|
||||
for row in sheet.iter_rows(min_row=2, max_row=sheet.max_row):
|
||||
try:
|
||||
row_num, found_link = self.process_row(row, total_rows)
|
||||
if row_num is not None:
|
||||
sheet.cell(row=row_num, column=2, value=found_link if found_link else "Links not found")
|
||||
save_path = self.file_path.replace(".xlsx", "_updated.xlsx")
|
||||
wb.save(save_path)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error processing row: {e}")
|
||||
continue
|
||||
|
||||
self.logger.info("Processing completed")
|
||||
success_popup = Popup(
|
||||
title='Success',
|
||||
content=Label(text=f"Processing completed. File saved."),
|
||||
size_hint=(0.8, 0.3)
|
||||
)
|
||||
success_popup.open()
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error during search: {e}")
|
||||
error_popup = Popup(
|
||||
title='Error',
|
||||
content=Label(text=f"An error occurred: {str(e)}"),
|
||||
size_hint=(0.8, 0.3)
|
||||
)
|
||||
error_popup.open()
|
||||
finally:
|
||||
if self.driver:
|
||||
self.driver.quit()
|
||||
|
||||
if __name__ == '__main__':
|
||||
FileSearchApp().run()
|
||||
201
main_backup.py
Normal file
201
main_backup.py
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
import json
|
||||
import os
|
||||
import time
|
||||
import openpyxl
|
||||
from kivy.app import App
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.button import Button
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.popup import Popup
|
||||
from kivy.uix.filechooser import FileChooserListView
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.common.exceptions import NoSuchElementException, TimeoutException
|
||||
|
||||
# Файл конфигурации для сохранения последнего пути
|
||||
CONFIG_FILE = 'config.json'
|
||||
|
||||
def get_last_used_directory():
|
||||
"""Возвращает последний использованный каталог из файла конфигурации."""
|
||||
if os.path.exists(CONFIG_FILE):
|
||||
with open(CONFIG_FILE, 'r') as file:
|
||||
config = json.load(file)
|
||||
return config.get('last_dir', '')
|
||||
return ''
|
||||
|
||||
def save_last_used_directory(directory):
|
||||
"""Сохраняет последний использованный каталог в файл конфигурации."""
|
||||
with open(CONFIG_FILE, 'w') as file:
|
||||
json.dump({'last_dir': directory}, file)
|
||||
|
||||
def get_search_key(filename):
|
||||
"""
|
||||
Преобразует имя файла: удаляет расширение и заменяет дефисы на подчёркивания.
|
||||
Например, "3M-Protemp4-Banner-900x450-BG.jpg" превращается в "3M_Protemp4_Banner_900x450_BG".
|
||||
"""
|
||||
if not filename:
|
||||
return ""
|
||||
base = os.path.splitext(filename)[0]
|
||||
return base.replace("-", "_")
|
||||
|
||||
class FileSearchApp(App):
|
||||
def build(self):
|
||||
self.file_path = ""
|
||||
self.extensions = [".psd", ".indd", ".tif", ".tiff"]
|
||||
|
||||
layout = BoxLayout(orientation='vertical')
|
||||
self.label = Label(text="Select an Excel file with Asset IDs")
|
||||
layout.add_widget(self.label)
|
||||
|
||||
choose_button = Button(text="Choose File")
|
||||
choose_button.bind(on_press=self.select_file)
|
||||
layout.add_widget(choose_button)
|
||||
|
||||
start_button = Button(text="Open Login Page")
|
||||
start_button.bind(on_press=self.open_login_page)
|
||||
layout.add_widget(start_button)
|
||||
|
||||
self.continue_button = Button(text="Continue After Login", disabled=True)
|
||||
self.continue_button.bind(on_press=self.start_search)
|
||||
layout.add_widget(self.continue_button)
|
||||
|
||||
return layout
|
||||
|
||||
def select_file(self, instance):
|
||||
last_dir = get_last_used_directory() or '.'
|
||||
filechooser = FileChooserListView(path=last_dir, filters=['*.xlsx'])
|
||||
popup = Popup(title="Choose Excel File", content=filechooser, size_hint=(0.9, 0.9))
|
||||
filechooser.bind(on_submit=lambda chooser, selection, touch: self.on_file_select(selection, popup))
|
||||
popup.open()
|
||||
|
||||
def on_file_select(self, selection, popup):
|
||||
if selection:
|
||||
self.file_path = selection[0]
|
||||
self.label.text = f"Selected File: {self.file_path}"
|
||||
print(f"Selected file: {self.file_path}")
|
||||
save_last_used_directory(os.path.dirname(self.file_path))
|
||||
popup.dismiss()
|
||||
else:
|
||||
print("No file selected.")
|
||||
|
||||
def open_login_page(self, instance):
|
||||
if not self.file_path:
|
||||
popup = Popup(title='Warning',
|
||||
content=Label(text='Select a file before starting!'),
|
||||
size_hint=(0.8, 0.3))
|
||||
popup.open()
|
||||
return
|
||||
options = Options()
|
||||
options.add_argument("user-data-dir=/Users/vadymsamoilenko/Library/Application Support/Google/Chrome/Profile 1")
|
||||
self.driver = webdriver.Chrome(options=options)
|
||||
try:
|
||||
self.driver.get("https://mmmspinco.brand-portal.adobe.com/mediaportal.html/content/dam/mac/mmmspinco")
|
||||
time.sleep(5)
|
||||
WebDriverWait(self.driver, 30).until(
|
||||
EC.presence_of_element_located((By.XPATH, "/html/body/coral-shell/coral-shell-content/div[1]/a/img"))
|
||||
)
|
||||
self.continue_button.disabled = False
|
||||
print("Login successful, ready to continue.")
|
||||
login_popup = Popup(title='Login',
|
||||
content=Label(text='Please log in and complete 2FA.\nClick "Continue After Login" once logged in.'),
|
||||
size_hint=(0.8, 0.3))
|
||||
login_popup.open()
|
||||
except Exception as e:
|
||||
print("Error opening login page:", e)
|
||||
self.driver.quit()
|
||||
|
||||
def start_search(self, instance):
|
||||
if not self.file_path or self.continue_button.disabled:
|
||||
popup = Popup(title='Warning',
|
||||
content=Label(text='Ensure you are logged in before starting!'),
|
||||
size_hint=(0.8, 0.3))
|
||||
popup.open()
|
||||
return
|
||||
try:
|
||||
wb = openpyxl.load_workbook(self.file_path)
|
||||
sheet = wb.active
|
||||
|
||||
# Предполагаем, что:
|
||||
# - Столбец 1: Asset ID
|
||||
# - Столбец 3: Имя файла (с расширением)
|
||||
for row in sheet.iter_rows(min_row=2, max_row=sheet.max_row):
|
||||
asset_id = row[0].value # Asset ID
|
||||
file_name = row[2].value if len(row) >= 3 else None # Имя файла
|
||||
found_link = None
|
||||
if asset_id and file_name:
|
||||
search_key = get_search_key(file_name)
|
||||
print(f"Processing Asset ID: {asset_id} with search key: {search_key}")
|
||||
|
||||
self.driver.get("https://mmmspinco.brand-portal.adobe.com/aem/search.html")
|
||||
search_box = WebDriverWait(self.driver, 10).until(
|
||||
EC.presence_of_element_located((By.NAME, "fulltext"))
|
||||
)
|
||||
# Очистка поля ввода через clear() и JavaScript
|
||||
time.sleep(3)
|
||||
search_box.clear()
|
||||
self.driver.execute_script("arguments[0].value = '';", search_box)
|
||||
|
||||
# Поиск по asset_id для первоначального отбора
|
||||
search_box.send_keys(asset_id)
|
||||
search_box.send_keys(Keys.RETURN)
|
||||
time.sleep(3)
|
||||
|
||||
try:
|
||||
properties_button = WebDriverWait(self.driver, 30).until(
|
||||
EC.element_to_be_clickable((By.XPATH, "//button/coral-icon[@icon='infoCircle']"))
|
||||
)
|
||||
properties_button.click()
|
||||
time.sleep(3)
|
||||
|
||||
# Получаем все PSD-ссылки из секции "Others"
|
||||
psd_link_elements = WebDriverWait(self.driver, 30).until(
|
||||
EC.presence_of_all_elements_located(
|
||||
(By.XPATH, "//div[contains(@class, 'references-referencing')]//a[@data-relation='others' and contains(@data-asset-path, '.psd')]")
|
||||
)
|
||||
)
|
||||
time.sleep(3)
|
||||
# Выводим отладочную информацию для каждого кандидата
|
||||
for elem in psd_link_elements:
|
||||
title_attr = elem.get_attribute("title")
|
||||
href = elem.get_attribute("href")
|
||||
print(f"Candidate element -> title: {title_attr}, href: {href}")
|
||||
# Выбираем элемент, где search_key встречается в title или href (без учета расширения)
|
||||
for elem in psd_link_elements:
|
||||
title_attr = elem.get_attribute("title")
|
||||
href = elem.get_attribute("href")
|
||||
search_key_lower = search_key.lower()
|
||||
if (search_key_lower in title_attr.lower() if title_attr else False) or \
|
||||
(search_key_lower in href.lower() if href else False):
|
||||
found_link = href
|
||||
print(f"Selected link matching search key: {found_link}")
|
||||
break
|
||||
except TimeoutException:
|
||||
print("Timeout while waiting for the PSD link element.")
|
||||
found_link = None
|
||||
except NoSuchElementException:
|
||||
print("PSD link element not found.")
|
||||
found_link = None
|
||||
except Exception as e:
|
||||
print(f"Unexpected error occurred: {e}")
|
||||
found_link = None
|
||||
|
||||
if not found_link:
|
||||
print("No suitable link found for asset:", asset_id)
|
||||
|
||||
sheet.cell(row=row[0].row, column=2, value=found_link if found_link else "Links not found")
|
||||
print(f"Processed asset {asset_id}, link: {found_link}")
|
||||
save_path = self.file_path.replace(".xlsx", "_updated.xlsx")
|
||||
wb.save(save_path)
|
||||
success_popup = Popup(title='Success',
|
||||
content=Label(text=f"File saved: {save_path}"),
|
||||
size_hint=(0.8, 0.3))
|
||||
success_popup.open()
|
||||
finally:
|
||||
self.driver.quit()
|
||||
|
||||
if __name__ == '__main__':
|
||||
FileSearchApp().run()
|
||||
3
zshrc
Normal file
3
zshrc
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export PATH="/opt/homebrew/opt/tcl-tk/bin:$PATH"
|
||||
export LDFLAGS="-L/opt/homebrew/opt/tcl-tk/lib"
|
||||
export CPPFLAGS="-I/opt/homebrew/opt/tcl-tk/include"
|
||||
Loading…
Add table
Reference in a new issue