import sys import json import os from PyQt6.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QTextEdit, QMessageBox, QSpacerItem, QSizePolicy, QLabel, QFileDialog, QDialog, QFormLayout, QTableWidget, QTableWidgetItem, QHeaderView, QComboBox ) from PyQt6.QtGui import QIcon from PyQt6.QtCore import Qt, QDateTime, QUrl from PyQt6.QtSvgWidgets import QSvgWidget from PyQt6.QtGui import QDesktopServices class URLSearch: def __init__(self, url, search_engine_url): self.url = url self.search_engine_url = search_engine_url self.filters_dir = 'filters/' self.available_filters = self.load_filters() def load_filters(self): filters = {} for file_name in os.listdir(self.filters_dir): if file_name.endswith('.filter.py'): domain = '.'.join(file_name.split('.')[:-2]) # Extracting domain.tld from domain.tld.filter.py filters[domain] = os.path.join(self.filters_dir, file_name) print("Loaded filters:", filters) # Debug print return filters def extract_terms(self): parsed_url = QUrl(self.url) host_parts = parsed_url.host().split('.') domain = '.'.join(host_parts[-2:]) # Extract full domain including TLD print("Extracted domain:", domain) # Debug print if domain in self.available_filters: filter_file = self.available_filters[domain] terms = self.apply_filter(filter_file) if terms: return terms return None def apply_filter(self, filter_file): try: with open(filter_file, 'r') as file: filter_code = file.read() local_vars = {'url': self.url, 'terms': [], 'QUrl': QUrl} exec(filter_code, {}, local_vars) return local_vars['terms'] except Exception as e: print(f"Error applying filter: {e}") return None def search(self): terms = self.extract_terms() if terms: search_query = '+'.join(terms) search_url = f"{self.search_engine_url}{search_query}" QDesktopServices.openUrl(QUrl(search_url)) else: print("No valid search term extracted, search disabled.") class URLManager(QWidget): def __init__(self): super().__init__() self.settings = self.load_settings() self.translations = {} self.load_translations(f'lang/translations_{self.settings["language"].lower()[:2]}.json') self.setWindowTitle(self.translations['title']) self.setGeometry(100, 100, 600, 600) self.setWindowIcon(QIcon('assets/logo.png')) self.layout = QVBoxLayout() # URL input and settings button layout self.url_layout = QHBoxLayout() self.url_input = QLineEdit() self.url_input.setPlaceholderText(self.translations['url_placeholder']) self.url_layout.addWidget(self.url_input) self.settings_button = QPushButton() self.settings_button.setFixedSize(16, 16) self.settings_button.clicked.connect(self.show_settings_dialog) self.url_layout.addWidget(self.settings_button) self.info_button = QPushButton() self.info_button.setFixedSize(16, 16) self.info_button.clicked.connect(self.show_info_dialog) self.url_layout.addWidget(self.info_button) self.layout.addLayout(self.url_layout) self.update_icon_color() self.description_input = QTextEdit() self.description_input.setPlaceholderText(self.translations['description_placeholder']) self.description_input.setMaximumHeight(100) self.layout.addWidget(self.description_input) self.add_button = QPushButton(self.translations['save_url_button']) self.add_button.clicked.connect(self.add_url) self.layout.addWidget(self.add_button) self.layout.addSpacerItem(QSpacerItem(0, 10, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)) self.search_layout = QHBoxLayout() self.search_input = QLineEdit() self.search_input.setPlaceholderText(self.translations['search_placeholder']) self.search_input.textChanged.connect(self.filter_urls) self.search_layout.addWidget(self.search_input) self.layout.addLayout(self.search_layout) self.search_input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) self.url_list = QTableWidget() self.url_list.setColumnCount(3) self.url_list.setHorizontalHeaderLabels(['URL', self.translations['column_date'], self.translations['column_actions']]) self.url_list.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) self.url_list.setColumnWidth(1, 150) self.url_list.setColumnWidth(2, 100) self.layout.addWidget(self.url_list) self.setLayout(self.layout) self.urls = [] self.load_urls() self.search_engines = { "Bing": "https://www.bing.com/search?q=", "Brave": "https://search.brave.com/search?q=", "DuckDuckGo": "https://duckduckgo.com/?q=", "Ecosia": "https://www.ecosia.org/search?q=", "Google": "https://www.google.com/search?q=", "Startpage": "https://www.startpage.com/do/search?q=", "Swisscows": "https://swisscows.com/web?query=" } def load_translations(self, file_path): if os.path.exists(file_path): with open(file_path, 'r') as file: data = json.load(file) self.translations = data['translations'] def update_icon_color(self): palette = self.palette() if palette.color(palette.ColorRole.Window).value() < 128: settings_icon_path = 'assets/cogwheel_dark.svg' info_icon_path = 'assets/info_dark.svg' self.action_icons = { "search": 'assets/search_dark.svg', "delete": 'assets/delete_dark.svg' } else: settings_icon_path = 'assets/cogwheel_light.svg' info_icon_path = 'assets/info_light.svg' self.action_icons = { "search": 'assets/search_light.svg', "delete": 'assets/delete_light.svg' } self.settings_button.setIcon(QIcon(settings_icon_path)) self.info_button.setIcon(QIcon(info_icon_path)) def add_url(self): url = self.url_input.text() description = self.description_input.toPlainText() if url: date_added = QDateTime.currentDateTime().toString(Qt.DateFormat.ISODate) url_data = {'url': url, 'description': description, 'date': date_added} self.urls.append(url_data) self.save_url(url_data) self.update_url_list() self.url_input.clear() self.description_input.clear() def save_url(self, url_data): url_path = os.path.join(self.settings['data_directory'], 'urls.json') if os.path.exists(url_path): with open(url_path, 'r') as file: url_list = json.load(file) else: url_list = [] url_list.append(url_data) with open(url_path, 'w') as file: json.dump(url_list, file) def update_url(self): selected_item = self.url_list.currentItem() if selected_item: row = selected_item.row() new_url = self.url_input.text() new_description = self.description_input.toPlainText() self.urls[row]['url'] = new_url self.urls[row]['description'] = new_description self.save_urls() self.update_url_list() self.url_input.clear() self.description_input.clear() def delete_url(self, row): reply = QMessageBox.question(self, self.translations['delete_confirmation_title'], self.translations['delete_confirmation'], QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No) if reply == QMessageBox.StandardButton.Yes: del self.urls[row] self.save_urls() self.update_url_list() def filter_urls(self): search_term = self.search_input.text().strip().lower() if not search_term: self.update_url_list() return filtered_urls = [url for url in self.urls if search_term in url['url'].lower() or search_term in url['description'].lower()] self.update_url_list(filtered_urls) def update_url_list(self, urls=None): self.url_list.setRowCount(0) urls_to_display = urls if urls else self.urls for row, url in enumerate(urls_to_display): self.url_list.insertRow(row) self.url_list.setItem(row, 0, QTableWidgetItem(url['url'])) formatted_date = self.format_date(url['date']) self.url_list.setItem(row, 1, QTableWidgetItem(formatted_date)) actions_layout = QHBoxLayout() search_button = QPushButton() search_button.setIcon(QIcon(self.action_icons["search"])) search_button.setFixedSize(18, 18) search_button.setStyleSheet("border: none; padding: 0px;") search_button.clicked.connect(lambda _, url=url['url']: self.search_url(url)) actions_layout.addWidget(search_button) delete_button = QPushButton() delete_button.setIcon(QIcon(self.action_icons["delete"])) delete_button.setFixedSize(18, 18) delete_button.setStyleSheet("border: none; padding: 0px;") delete_button.clicked.connect(lambda _, row=row: self.delete_url(row)) actions_layout.addWidget(delete_button) actions_widget = QWidget() actions_widget.setLayout(actions_layout) self.url_list.setCellWidget(row, 2, actions_widget) vertical_header = self.url_list.verticalHeader() vertical_header.setVisible(False) def format_date(self, date_str): date = QDateTime.fromString(date_str, Qt.DateFormat.ISODate) if self.settings['date_format'] == 'Nerdy': return date.toString("yyyy-MM-ddTHH:mm:ss") elif self.settings['date_format'] == 'Normal': return date.toString("dd/MM/yy @ HH:mm") elif self.settings['date_format'] == 'Murica!': return date.toString("MM/dd/yyyy hh:mm AP") else: return date_str def search_url(self, url): search_engine_url = self.search_engines[self.settings['search_engine']] url_search = URLSearch(url, search_engine_url) url_search.search() def load_settings(self): settings_path = 'data/settings.json' if os.path.exists(settings_path): with open(settings_path, 'r') as file: settings = json.load(file) self.data_directory = settings['data_directory'] self.search_engine = settings['search_engine'] self.date_format = settings.get('date_format', 'Nerdy') self.language = settings.get('language', 'English') return settings else: default_settings = { 'data_directory': 'data/', 'search_engine': 'Google', 'date_format': 'Nerdy', 'language': 'English' } self.save_settings(default_settings) return default_settings def save_settings(self, settings=None): settings_path = 'data/settings.json' os.makedirs(os.path.dirname(settings_path), exist_ok=True) if settings is None: settings = { 'data_directory': self.data_directory, 'search_engine': self.search_engine, 'date_format': self.date_format, 'language': self.language } with open(settings_path, 'w') as file: json.dump(settings, file) def load_urls(self): url_path = os.path.join(self.settings['data_directory'], 'urls.json') if os.path.exists(url_path): with open(url_path, 'r') as file: self.urls = json.load(file) self.update_url_list() def save_urls(self): url_path = os.path.join(self.settings['data_directory'], 'urls.json') with open(url_path, 'w') as file: json.dump(self.urls, file) def show_info_dialog(self): dialog = QDialog(self) dialog.setWindowTitle(self.translations['about_title']) layout = QVBoxLayout() svg_widget = QSvgWidget('assets/logo.svg') svg_widget.setFixedSize(256, 256) layout.addWidget(svg_widget, alignment=Qt.AlignmentFlag.AlignCenter) written_by_label = QLabel(self.translations['developed_by']) layout.addWidget(written_by_label, alignment=Qt.AlignmentFlag.AlignCenter) link_label = QLabel() link_label.setText(f'{self.translations["repository_link_name"]}') link_label.setOpenExternalLinks(True) layout.addWidget(link_label, alignment=Qt.AlignmentFlag.AlignCenter) dialog.setLayout(layout) dialog.exec() def show_settings_dialog(self): dialog = SettingsDialog(self) if dialog.exec() == QDialog.DialogCode.Accepted: self.settings = self.load_settings() self.update_url_list() class SettingsDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle(parent.translations['settings_title']) self.setFixedWidth(360) self.parent = parent layout = QVBoxLayout() directory_label = QLabel(parent.translations['data_directory_label']) layout.addWidget(directory_label) directory_layout = QHBoxLayout() self.directory_input = QLineEdit(self.parent.settings['data_directory']) self.browse_button = QPushButton(self.parent.translations['browse_button']) self.browse_button.clicked.connect(self.browse_directory) directory_layout.addWidget(self.directory_input) directory_layout.addWidget(self.browse_button) layout.addLayout(directory_layout) layout.addSpacerItem(QSpacerItem(0, 4, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)) form_layout = QFormLayout() # Populate Search Engine Combobox self.search_engine_combobox = QComboBox() search_engines = sorted(self.parent.search_engines.keys()) self.search_engine_combobox.addItems(search_engines) self.search_engine_combobox.setCurrentText(self.parent.settings['search_engine']) form_layout.addRow(self.parent.translations['search_engine_label'], self.search_engine_combobox) # Populate Date Format Combobox self.date_format_combobox = QComboBox() self.date_format_combobox.addItems(["Nerdy", "Normal", "Murica!"]) self.date_format_combobox.setCurrentText(self.parent.settings['date_format']) form_layout.addRow(self.parent.translations['date_format_label'], self.date_format_combobox) # Populate Language Combobox self.language_combobox = QComboBox() self.populate_language_combobox() current_language_code = self.parent.settings['language'] self.set_current_language(current_language_code) form_layout.addRow(self.parent.translations['language_label'], self.language_combobox) layout.addLayout(form_layout) self.save_button = QPushButton(self.parent.translations['save_button']) self.save_button.clicked.connect(self.save_settings) layout.addWidget(self.save_button) self.setLayout(layout) def browse_directory(self): directory = QFileDialog.getExistingDirectory(self, "Select Directory") if directory: self.directory_input.setText(directory) def populate_language_combobox(self): lang_dir = 'lang/' language_files = [f for f in os.listdir(lang_dir) if f.startswith('translations_') and f.endswith('.json')] self.language_map = {} for file_name in language_files: file_path = os.path.join(lang_dir, file_name) with open(file_path, 'r') as file: data = json.load(file) language_name = data.get('language', 'Unknown') language_code = file_name[len('translations_'):-len('.json')] display_text = f"{language_name} ({language_code})" self.language_combobox.addItem(display_text) self.language_map[language_code] = display_text def set_current_language(self, language_code): if language_code in self.language_map: display_text = self.language_map[language_code] index = self.language_combobox.findText(display_text) if index != -1: self.language_combobox.setCurrentIndex(index) def save_settings(self): selected_language = self.language_combobox.currentText() language_code = selected_language.split('(')[-1].strip(' )') # Extract language code from "(en)" format old_language = self.parent.settings['language'] self.parent.settings['data_directory'] = self.directory_input.text() self.parent.settings['search_engine'] = self.search_engine_combobox.currentText() self.parent.settings['date_format'] = self.date_format_combobox.currentText() self.parent.settings['language'] = language_code self.parent.save_settings(self.parent.settings) if old_language != language_code: QMessageBox.information(self, self.parent.translations['restart_required_title'], self.parent.translations['restart_required_message']) self.accept() if __name__ == '__main__': app = QApplication(sys.argv) window = URLManager() window.show() sys.exit(app.exec())