diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/Book1 copy.xlsx b/Book1 copy.xlsx new file mode 100644 index 0000000..6ac1568 Binary files /dev/null and b/Book1 copy.xlsx differ diff --git a/Book1 copy_updated.xlsx b/Book1 copy_updated.xlsx new file mode 100644 index 0000000..d605350 Binary files /dev/null and b/Book1 copy_updated.xlsx differ diff --git a/Book1.xlsx b/Book1.xlsx new file mode 100644 index 0000000..5f70b28 Binary files /dev/null and b/Book1.xlsx differ diff --git a/Book1_updated.xlsx b/Book1_updated.xlsx new file mode 100644 index 0000000..649d474 Binary files /dev/null and b/Book1_updated.xlsx differ diff --git a/Solventum_2.code-workspace b/Solventum_2.code-workspace new file mode 100644 index 0000000..362d7c2 --- /dev/null +++ b/Solventum_2.code-workspace @@ -0,0 +1,7 @@ +{ + "folders": [ + { + "path": "." + } + ] +} \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..c5d3242 --- /dev/null +++ b/config.json @@ -0,0 +1 @@ +{"last_dir": "/Users/vadymsamoilenko/Documents/GIT/Script"} \ No newline at end of file diff --git a/cookies.json b/cookies.json new file mode 100644 index 0000000..2c426d8 --- /dev/null +++ b/cookies.json @@ -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 + } + ] \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..a5323d6 --- /dev/null +++ b/main.py @@ -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() \ No newline at end of file diff --git a/main_backup.py b/main_backup.py new file mode 100644 index 0000000..8914dd7 --- /dev/null +++ b/main_backup.py @@ -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() \ No newline at end of file diff --git a/zshrc b/zshrc new file mode 100644 index 0000000..6765014 --- /dev/null +++ b/zshrc @@ -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"