diff --git a/.gitignore b/.gitignore index a24b8ba..59ba49a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,13 @@ __pycache__/ windoweffect/__pycache__/ # scraper.app must be rebuilt because of the amount of files -macOS/SearchifyX/scraper.app +macOS/SearchifyX/scraper/ +macOS/build/ +macOS/dist/ +macOS/scraper.spec # Xcode -xcuserdata/ +xcuserdata # General .DS_Store diff --git a/fonts/OFL.txt b/fonts/OFL.txt deleted file mode 100644 index 76df3b5..0000000 --- a/fonts/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fonts/Poppins Medium.ttf b/fonts/Poppins Medium.ttf deleted file mode 100644 index e90e87e..0000000 Binary files a/fonts/Poppins Medium.ttf and /dev/null differ diff --git a/gui.pyw b/gui.pyw index f32f9fa..9b845c9 100644 --- a/gui.pyw +++ b/gui.pyw @@ -1,5 +1,4 @@ from tendo.singleton import SingleInstance - me = SingleInstance() import ctypes @@ -22,6 +21,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets, uic from PyQt5.QtCore import QObject, Qt, pyqtSignal from PyQt5.QtWidgets import QApplication, QMainWindow from win32api import GetMonitorInfo, MonitorFromPoint +import darkdetect root = tk.Tk() root.withdraw() diff --git a/img/quizizz.png b/img/quizizz.png deleted file mode 100644 index beb8c38..0000000 Binary files a/img/quizizz.png and /dev/null differ diff --git a/img/quizlet.png b/img/quizlet.png deleted file mode 100644 index 28e16d0..0000000 Binary files a/img/quizlet.png and /dev/null differ diff --git a/img/search.png b/img/search.png deleted file mode 100644 index 40159a1..0000000 Binary files a/img/search.png and /dev/null differ diff --git a/macOS/SearchifyX.xcodeproj/project.pbxproj b/macOS/SearchifyX.xcodeproj/project.pbxproj index 34a6117..663033f 100644 --- a/macOS/SearchifyX.xcodeproj/project.pbxproj +++ b/macOS/SearchifyX.xcodeproj/project.pbxproj @@ -8,11 +8,19 @@ /* Begin PBXBuildFile section */ 4214674528C1C5EF00E9D706 /* SearchifyXApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4214674428C1C5EF00E9D706 /* SearchifyXApp.swift */; }; - 4214674728C1C5EF00E9D706 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4214674628C1C5EF00E9D706 /* ContentView.swift */; }; 4214674928C1C5F200E9D706 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4214674828C1C5F200E9D706 /* Assets.xcassets */; }; 4214677028C1CFB600E9D706 /* Flashcard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4214676F28C1CFB500E9D706 /* Flashcard.swift */; }; 4214677528C1D99300E9D706 /* Scraper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4214677428C1D99300E9D706 /* Scraper.swift */; }; - 4214677928C2887F00E9D706 /* scraper.app in Resources */ = {isa = PBXBuildFile; fileRef = 4214677828C2887F00E9D706 /* scraper.app */; }; + 423A30E82926193900071A8A /* WebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423A30E72926193900071A8A /* WebViewModel.swift */; }; + 423A30EE29268E6100071A8A /* NotesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423A30ED29268E6100071A8A /* NotesView.swift */; }; + 423A30F029268E7700071A8A /* BrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423A30EF29268E7700071A8A /* BrowserView.swift */; }; + 423A30F229268FB000071A8A /* ScraperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423A30F129268FB000071A8A /* ScraperView.swift */; }; + 423A30F429268FED00071A8A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423A30F329268FED00071A8A /* ContentView.swift */; }; + 423A30F62926A9C400071A8A /* ViewLoadExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423A30F52926A9C400071A8A /* ViewLoadExt.swift */; }; + 423A30F92926B65100071A8A /* BrowserToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423A30F82926B65100071A8A /* BrowserToolbar.swift */; }; + 423A30FB2926B70F00071A8A /* ScraperToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423A30FA2926B70F00071A8A /* ScraperToolbar.swift */; }; + 423AC30D290116FA00319B15 /* NSWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423AC30C290116FA00319B15 /* NSWebView.swift */; }; + 42C95D932900657A008936E3 /* scraper in Resources */ = {isa = PBXBuildFile; fileRef = 42C95D922900657A008936E3 /* scraper */; }; 42E9576F28C80FA300BF7B3B /* FloatingPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42E9576E28C80FA300BF7B3B /* FloatingPanel.swift */; }; 42E9579B28C84C0E00BF7B3B /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = 42E9579A28C84C0E00BF7B3B /* KeyboardShortcuts */; }; 42E9579D28C84C6900BF7B3B /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42E9579C28C84C6900BF7B3B /* SettingsView.swift */; }; @@ -22,11 +30,19 @@ /* Begin PBXFileReference section */ 4214674128C1C5EF00E9D706 /* SearchifyX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SearchifyX.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4214674428C1C5EF00E9D706 /* SearchifyXApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchifyXApp.swift; sourceTree = ""; }; - 4214674628C1C5EF00E9D706 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 4214674828C1C5F200E9D706 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 4214676F28C1CFB500E9D706 /* Flashcard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Flashcard.swift; sourceTree = ""; }; 4214677428C1D99300E9D706 /* Scraper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scraper.swift; sourceTree = ""; }; - 4214677828C2887F00E9D706 /* scraper.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = scraper.app; sourceTree = ""; }; + 423A30E72926193900071A8A /* WebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewModel.swift; sourceTree = ""; }; + 423A30ED29268E6100071A8A /* NotesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesView.swift; sourceTree = ""; }; + 423A30EF29268E7700071A8A /* BrowserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserView.swift; sourceTree = ""; }; + 423A30F129268FB000071A8A /* ScraperView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScraperView.swift; sourceTree = ""; }; + 423A30F329268FED00071A8A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 423A30F52926A9C400071A8A /* ViewLoadExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewLoadExt.swift; sourceTree = ""; }; + 423A30F82926B65100071A8A /* BrowserToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserToolbar.swift; sourceTree = ""; }; + 423A30FA2926B70F00071A8A /* ScraperToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScraperToolbar.swift; sourceTree = ""; }; + 423AC30C290116FA00319B15 /* NSWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSWebView.swift; sourceTree = ""; }; + 42C95D922900657A008936E3 /* scraper */ = {isa = PBXFileReference; lastKnownFileType = folder; path = scraper; sourceTree = ""; }; 42E9576E28C80FA300BF7B3B /* FloatingPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPanel.swift; sourceTree = ""; }; 42E9579C28C84C6900BF7B3B /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 42E9579E28C84DAC00BF7B3B /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; @@ -63,19 +79,67 @@ 4214674328C1C5EF00E9D706 /* SearchifyX */ = { isa = PBXGroup; children = ( - 4214677828C2887F00E9D706 /* scraper.app */, + 423A30F72926B57F00071A8A /* Toolbars */, + 423A30EC2926198300071A8A /* Helpers */, + 423A30EB2926197000071A8A /* Models */, + 423A30EA2926195700071A8A /* Views */, + 423A30E92926193F00071A8A /* WebView */, + 42C95D922900657A008936E3 /* scraper */, 4214674428C1C5EF00E9D706 /* SearchifyXApp.swift */, - 4214674628C1C5EF00E9D706 /* ContentView.swift */, 4214674828C1C5F200E9D706 /* Assets.xcassets */, - 4214676F28C1CFB500E9D706 /* Flashcard.swift */, 4214677428C1D99300E9D706 /* Scraper.swift */, - 42E9576E28C80FA300BF7B3B /* FloatingPanel.swift */, - 42E9579C28C84C6900BF7B3B /* SettingsView.swift */, 42E9579E28C84DAC00BF7B3B /* Constants.swift */, + 423A30F329268FED00071A8A /* ContentView.swift */, ); path = SearchifyX; sourceTree = ""; }; + 423A30E92926193F00071A8A /* WebView */ = { + isa = PBXGroup; + children = ( + 423A30E72926193900071A8A /* WebViewModel.swift */, + 423AC30C290116FA00319B15 /* NSWebView.swift */, + ); + path = WebView; + sourceTree = ""; + }; + 423A30EA2926195700071A8A /* Views */ = { + isa = PBXGroup; + children = ( + 42E9579C28C84C6900BF7B3B /* SettingsView.swift */, + 423A30ED29268E6100071A8A /* NotesView.swift */, + 423A30EF29268E7700071A8A /* BrowserView.swift */, + 423A30F129268FB000071A8A /* ScraperView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 423A30EB2926197000071A8A /* Models */ = { + isa = PBXGroup; + children = ( + 4214676F28C1CFB500E9D706 /* Flashcard.swift */, + ); + path = Models; + sourceTree = ""; + }; + 423A30EC2926198300071A8A /* Helpers */ = { + isa = PBXGroup; + children = ( + 42E9576E28C80FA300BF7B3B /* FloatingPanel.swift */, + 423A30F52926A9C400071A8A /* ViewLoadExt.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + 423A30F72926B57F00071A8A /* Toolbars */ = { + isa = PBXGroup; + children = ( + 423A30F82926B65100071A8A /* BrowserToolbar.swift */, + 423A30FA2926B70F00071A8A /* ScraperToolbar.swift */, + ); + path = Toolbars; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -140,8 +204,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4214677928C2887F00E9D706 /* scraper.app in Resources */, 4214674928C1C5F200E9D706 /* Assets.xcassets in Resources */, + 42C95D932900657A008936E3 /* scraper in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -153,11 +217,19 @@ buildActionMask = 2147483647; files = ( 4214677528C1D99300E9D706 /* Scraper.swift in Sources */, + 423A30F62926A9C400071A8A /* ViewLoadExt.swift in Sources */, + 423AC30D290116FA00319B15 /* NSWebView.swift in Sources */, + 423A30EE29268E6100071A8A /* NotesView.swift in Sources */, + 423A30F229268FB000071A8A /* ScraperView.swift in Sources */, 4214677028C1CFB600E9D706 /* Flashcard.swift in Sources */, - 4214674728C1C5EF00E9D706 /* ContentView.swift in Sources */, + 423A30F029268E7700071A8A /* BrowserView.swift in Sources */, + 423A30E82926193900071A8A /* WebViewModel.swift in Sources */, 42E9579D28C84C6900BF7B3B /* SettingsView.swift in Sources */, 42E9579F28C84DAC00BF7B3B /* Constants.swift in Sources */, + 423A30F429268FED00071A8A /* ContentView.swift in Sources */, + 423A30FB2926B70F00071A8A /* ScraperToolbar.swift in Sources */, 4214674528C1C5EF00E9D706 /* SearchifyXApp.swift in Sources */, + 423A30F92926B65100071A8A /* BrowserToolbar.swift in Sources */, 42E9576F28C80FA300BF7B3B /* FloatingPanel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -289,7 +361,6 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = SearchifyX/scraper.app; DEVELOPMENT_TEAM = R5T6626VHW; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; @@ -300,7 +371,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.teamaurous.SearchifyX; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -318,7 +389,6 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = SearchifyX/scraper.app; DEVELOPMENT_TEAM = R5T6626VHW; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; @@ -329,7 +399,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.teamaurous.SearchifyX; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/macOS/SearchifyX.xcodeproj/xcshareddata/xcschemes/SearchifyX.xcscheme b/macOS/SearchifyX.xcodeproj/xcshareddata/xcschemes/SearchifyX.xcscheme new file mode 100644 index 0000000..ae44639 --- /dev/null +++ b/macOS/SearchifyX.xcodeproj/xcshareddata/xcschemes/SearchifyX.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macOS/SearchifyX/Constants.swift b/macOS/SearchifyX/Constants.swift index 9da3b11..9f484de 100644 --- a/macOS/SearchifyX/Constants.swift +++ b/macOS/SearchifyX/Constants.swift @@ -1,13 +1,16 @@ -// -// Constants.swift -// SearchifyX -// -// Created by Jose Molina on 9/6/22. -// - import Foundation import KeyboardShortcuts +import AppKit +import SwiftUI extension KeyboardShortcuts.Name { static let openSearchify = Self("openSearchify") + static let ocrAndSearch = Self("ocrAndSearch") + static let pasteAndSearch = Self("pasteAndSearch") +} + +class Variables { + static var hiddenWindow: NSWindow? + + static var wkModel = WebViewModel() } diff --git a/macOS/SearchifyX/ContentView.swift b/macOS/SearchifyX/ContentView.swift index 9e912c3..b41b793 100644 --- a/macOS/SearchifyX/ContentView.swift +++ b/macOS/SearchifyX/ContentView.swift @@ -2,162 +2,66 @@ // ContentView.swift // SearchifyX // -// Created by Jose Molina on 9/2/22. +// Created by Jose Molina on 11/17/22. // import SwiftUI -let scraper = Scraper() - struct ContentView: View { - @State var question: String = "" - @State var enableQuizizz: Bool = true - @State var enableQuizlet: Bool = true - @State var cards: [Flashcard] = [] - @State var selected: Flashcard.ID? - @State var searching: Bool = false - @State var engine: String = "google" - @State var showingPanel: Bool = false - - @AppStorage("runAfterOcr") var runAfterOcr: Bool = false - @AppStorage("runAfterPaste") var runAfterPaste: Bool = false - var isPanel: Bool + var question: String? - var body: some View { - VStack { - HStack { - if isPanel == false { - Button(action: { - let ep = FloatingPanel(contentRect: NSRect(x: 0, y: 0, width: 900, height: 450), backing: .buffered, defer: false) - - ep.title = "Hidden SearchifyX" - ep.contentView = NSHostingView(rootView: ContentView(isPanel: true)) - - ep.center() - ep.orderFront(nil) - ep.makeKey() - }, label: { - Image(systemName: "eye.slash") - }) - } - TextField("Search a question here", text: $question) - .lineLimit(nil) - Button( - action: { - searchQuestion() - }, - label: { + @State var selection: Int? + @EnvironmentObject var wkvm: WebViewModel + + var body: some View { + NavigationView() { + List() { + NavigationLink(destination: ScraperView(isPanel: isPanel, question: question), tag: 0, selection: $selection) { + HStack { Image(systemName: "magnifyingglass") Text("Search") } - ) - Button( - action: { - var clipboardItems: [String] = [] - for element in NSPasteboard.general.pasteboardItems! { - if let str = element.string(forType: NSPasteboard.PasteboardType(rawValue: "public.utf8-plain-text")) { - clipboardItems.append(str) - } - } - - question = clipboardItems[0] - - if runAfterPaste { - searchQuestion() - } - }, - label: { - Image(systemName: "doc.on.clipboard") + } + .navigationTitle("Search") + NavigationLink(destination: BrowserView(isPanel: isPanel).environmentObject(wkvm), tag: 1, selection: $selection) { + HStack { + Image(systemName: "globe.americas") + Text("Browser") } - ) - Button( - action: { - DispatchQueue.global(qos: .userInitiated).async { - question = scraper.ocr() - - if runAfterOcr { - searchQuestion() - } - } - }, - label: { - Image(systemName: "eye") + } + .navigationTitle("Browser") + NavigationLink(destination: NotesView(), tag: 2, selection: $selection) { + HStack { + Image(systemName: "note.text") + Text("Notes") } - ) - } - .disabled(searching != false) - .padding() - - ZStack { - Table(cards, selection: $selected) { - TableColumn("Question", value: \.question) - TableColumn("Answer", value: \.answer) - TableColumn("Similarity", value: \.similarity) - TableColumn("URL", value: \.url) } - - if searching { - ProgressView() + .navigationTitle("Notes") + NavigationLink(destination: SettingsView(), tag: 3, selection: $selection) { + HStack { + Image(systemName: "gear") + Text("Settings") + } } + .navigationTitle("Settings") } - - HStack { - Spacer() - Picker("Search Engine", selection: $engine) { - Text("Google").tag("google") - Text("Bing").tag("bing") - Text("Startpage").tag("startpage") - } - Button(action: { - var original: Flashcard? - - for item in cards { - if (item.id == selected) { - original = item - break - } + .onAppear() { + self.selection = 0 + } + .toolbar { + ToolbarItemGroup(placement: .navigation) { + if !isPanel { + Button(action: { + Scraper.makeHiddenWindow(question: nil) + }, label: { + Image(systemName: "eye.slash.fill") + }) } - - let url = URL(string: original!.url)! - NSWorkspace.shared.open(url) - - }, label: { - Text("Open question") - }) - .disabled(selected == nil) - Toggle(isOn: $enableQuizizz) { - Image("QuizizzIcon") - .resizable() - .frame(width: 16, height: 16) - Text("Quizizz") - } - Toggle(isOn: $enableQuizlet) { - Image("QuizletIcon") - .resizable() - .frame(width: 16, height: 16) - Text("Quizlet") } } - .padding() - } - } - - func searchQuestion() { - searching = true - - var sites: [String] = [] - - if enableQuizizz { - sites.append("quizizz") - } - if enableQuizlet { - sites.append("quizlet") - } - - DispatchQueue.global(qos: .userInitiated).async { - cards = scraper.search(query: question, sites: sites.joined(separator: ","), engine: engine) - searching = false + .frame() + .listStyle(.sidebar) } } } diff --git a/macOS/SearchifyX/Helpers/FloatingPanel.swift b/macOS/SearchifyX/Helpers/FloatingPanel.swift new file mode 100644 index 0000000..47a6c11 --- /dev/null +++ b/macOS/SearchifyX/Helpers/FloatingPanel.swift @@ -0,0 +1,27 @@ +import SwiftUI +import Cocoa + +class FloatingPanel: NSPanel { + init(contentRect: NSRect, backing: NSWindow.BackingStoreType, defer flag: Bool) { + super.init(contentRect: contentRect, styleMask: [.nonactivatingPanel, .titled, .resizable, .closable, .fullSizeContentView], backing: backing, defer: flag) + self.isFloatingPanel = true + self.level = .floating + self.collectionBehavior.insert(.fullScreenAuxiliary) + self.titleVisibility = .hidden + self.titlebarAppearsTransparent = true + self.isMovableByWindowBackground = true + self.isReleasedWhenClosed = false + + self.standardWindowButton(.closeButton)?.isHidden = false + self.standardWindowButton(.miniaturizeButton)?.isHidden = true + self.standardWindowButton(.zoomButton)?.isHidden = true + } + + override var canBecomeKey: Bool { + return true + } + + override var canBecomeMain: Bool { + return true + } +} diff --git a/macOS/SearchifyX/Helpers/ViewLoadExt.swift b/macOS/SearchifyX/Helpers/ViewLoadExt.swift new file mode 100644 index 0000000..2b5697e --- /dev/null +++ b/macOS/SearchifyX/Helpers/ViewLoadExt.swift @@ -0,0 +1,37 @@ +// +// ViewLoadExt.swift +// SearchifyX +// +// Created by Jose Molina on 11/17/22. +// + +import Foundation +import SwiftUI + +struct ViewDidLoadModifier: ViewModifier { + + @State private var didLoad = false + private let action: (() -> Void)? + + init(perform action: (() -> Void)? = nil) { + self.action = action + } + + func body(content: Content) -> some View { + content.onAppear { + if didLoad == false { + didLoad = true + action?() + } + } + } + +} + +extension View { + + func onLoad(perform action: (() -> Void)? = nil) -> some View { + modifier(ViewDidLoadModifier(perform: action)) + } + +} diff --git a/macOS/SearchifyX/Models/Flashcard.swift b/macOS/SearchifyX/Models/Flashcard.swift new file mode 100644 index 0000000..e6e180f --- /dev/null +++ b/macOS/SearchifyX/Models/Flashcard.swift @@ -0,0 +1,9 @@ +import Foundation + +struct Flashcard: Identifiable, Codable { + let question: String + let answer: String + let url: String + let similarity: String + let id = UUID() +} diff --git a/macOS/SearchifyX/Scraper.swift b/macOS/SearchifyX/Scraper.swift index 29d2008..aa4ed37 100644 --- a/macOS/SearchifyX/Scraper.swift +++ b/macOS/SearchifyX/Scraper.swift @@ -1,19 +1,18 @@ -// -// Scraper.swift -// SearchifyX -// -// Created by Jose Molina on 9/2/22. -// - import Foundation import AppKit import Vision +import SwiftUI +import UserNotifications class Scraper { - func search(query: String, sites: String, engine: String) -> [Flashcard] { + static func search(query: String, sites: String, engine: String) -> [Flashcard] { + if query.isEmpty { + return [] + } + let proc = Process() proc.executableURL = - Bundle.main.bundleURL.appendingPathComponent("Contents/Resources/scraper.app/Contents/MacOS/scraper") + Bundle.main.bundleURL.appendingPathComponent("Contents/Resources/scraper/scraper") let tempDir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) let tempFile = ProcessInfo().globallyUniqueString @@ -34,11 +33,23 @@ class Scraper { } catch { print("Error occurred trying to run the scraper: \(error)") + Scraper.alert(caption: "Unable to search", msg: "An error occurred while trying to scrape results") return [] } } - func ocr() -> String { + static func ocr() -> String { + var window: NSWindow? + + if Variables.hiddenWindow != nil { + DispatchQueue.main.async { + if Variables.hiddenWindow!.isKeyWindow { + window = NSApp.keyWindow + window?.orderOut(nil) + } + } + } + let proc = Process() let exec = URL(fileURLWithPath: "/usr/sbin/screencapture") proc.executableURL = exec @@ -48,6 +59,12 @@ class Scraper { try proc.run() proc.waitUntilExit() + if Variables.hiddenWindow != nil { + DispatchQueue.main.async { + window?.orderFront(nil) + } + } + var data: Data? for element in NSPasteboard.general.pasteboardItems! { @@ -69,7 +86,7 @@ class Scraper { } if recognizedStrings.isEmpty { - alert(caption: "Unable to run the OCR", msg: "No text was detected") + Scraper.alert(caption: "Unable to run the OCR", msg: "No text was detected") return "" } @@ -77,20 +94,37 @@ class Scraper { } catch { print("An error occurred trying to run the OCR: \(error)") - alert(caption: "Unable to run the OCR", msg: "An error occurred while trying to run or process the OCR") + Scraper.alert(caption: "Unable to run the OCR", msg: "An error occurred while trying to run or process the OCR") return "" } } - func alert(caption: String, msg: String) { - DispatchQueue.main.async { - let alert = NSAlert() - alert.messageText = caption - alert.informativeText = msg - alert.addButton(withTitle: "OK") - alert.alertStyle = .warning - alert.runModal() + static func alert(caption: String, msg: String) { + let content = UNMutableNotificationContent() + content.title = caption + content.body = msg + + let uuid = UUID().uuidString + let request = UNNotificationRequest(identifier: uuid, content: content, trigger: nil) + + let center = UNUserNotificationCenter.current() + center.requestAuthorization(options: [.alert, .sound]) { _, _ in} + center.add(request) + } + + static func getClipboard() -> String { + var clipboardItems: [String] = [] + for element in NSPasteboard.general.pasteboardItems! { + if let str = element.string(forType: NSPasteboard.PasteboardType(rawValue: "public.utf8-plain-text")) { + clipboardItems.append(str) + } } + + if (clipboardItems.isEmpty) { + return "" + } + + return clipboardItems[0] } static func convertPercent(from input: String) -> Float { @@ -100,4 +134,20 @@ class Scraper { return 0 } } + + static func makeHiddenWindow(question: String?) { + if Variables.hiddenWindow != nil { + Variables.hiddenWindow?.isReleasedWhenClosed = true + Variables.hiddenWindow?.close() + } + + Variables.hiddenWindow = FloatingPanel(contentRect: NSRect(x: 0, y: 0, width: 900, height: 450), backing: .buffered, defer: false) + + Variables.hiddenWindow?.title = "Hidden SearchifyX" + Variables.hiddenWindow?.contentView = NSHostingView(rootView: ContentView(isPanel: true, question: question)) + + Variables.hiddenWindow?.center() + Variables.hiddenWindow?.orderFront(nil) + Variables.hiddenWindow?.makeKey() + } } diff --git a/macOS/SearchifyX/SearchifyXApp.swift b/macOS/SearchifyX/SearchifyXApp.swift index acec8ac..7d5d143 100644 --- a/macOS/SearchifyX/SearchifyXApp.swift +++ b/macOS/SearchifyX/SearchifyXApp.swift @@ -1,10 +1,3 @@ -// -// SearchifyXApp.swift -// SearchifyX -// -// Created by Jose Molina on 9/2/22. -// - import SwiftUI import KeyboardShortcuts @@ -15,7 +8,9 @@ struct SearchifyXApp: App { var body: some Scene { WindowGroup { ContentView(isPanel: false) + .environmentObject(Variables.wkModel) } + .windowStyle(HiddenTitleBarWindowStyle()) Settings { SettingsView() } @@ -26,14 +21,36 @@ struct SearchifyXApp: App { final class AppState: ObservableObject { init() { KeyboardShortcuts.onKeyUp(for: .openSearchify) { [self] in - let ep = FloatingPanel(contentRect: NSRect(x: 0, y: 0, width: 900, height: 450), backing: .buffered, defer: false) - - ep.title = "Hidden SearchifyX" - ep.contentView = NSHostingView(rootView: ContentView(isPanel: true)) - - ep.center() - ep.orderFront(nil) - ep.makeKey() + Scraper.makeHiddenWindow(question: nil) + } + + KeyboardShortcuts.onKeyUp(for: .ocrAndSearch) { [self] in + if UserDefaults.standard.bool(forKey: "showOnNotificationCenter") { + searchAndSend(question: Scraper.ocr()) + } + else { + Scraper.makeHiddenWindow(question: Scraper.ocr()) + } + } + + KeyboardShortcuts.onKeyUp(for: .pasteAndSearch) { [self] in + if UserDefaults.standard.bool(forKey: "showOnNotificationCenter") { + searchAndSend(question: Scraper.getClipboard()) + } + else { + Scraper.makeHiddenWindow(question: Scraper.getClipboard()) + } + } + } + + func searchAndSend(question: String?) { + if (question == nil) { + return + } + + let card = Scraper.search(query: question!, sites: "quizlet,quizizz", engine: "google").first + if (card != nil) { + Scraper.alert(caption: card!.question, msg: card!.answer) } } } diff --git a/macOS/SearchifyX/Toolbars/BrowserToolbar.swift b/macOS/SearchifyX/Toolbars/BrowserToolbar.swift new file mode 100644 index 0000000..7e98b0f --- /dev/null +++ b/macOS/SearchifyX/Toolbars/BrowserToolbar.swift @@ -0,0 +1,39 @@ +// +// BrowserToolbar.swift +// SearchifyX +// +// Created by Jose Molina on 11/17/22. +// + +import SwiftUI + +struct BrowserToolbar: View { + var url: Binding + + var body: some View { + HStack { + Button(action: { + Variables.wkModel.instance.goBack() + }, label: { + Image(systemName: "chevron.left") + }) + .buttonStyle(.borderless) + Button(action: { + Variables.wkModel.instance.goBack() + }, label: { + Image(systemName: "chevron.right") + }) + .buttonStyle(.borderless) + TextField("Enter URL", text: url) + .frame(minWidth: 400, maxWidth: 400) + .textFieldStyle(.roundedBorder) + Button(action: { + Variables.wkModel.loadUrl() + }, label: { + Text("Go") + }) + .buttonStyle(.borderless) + Spacer() + } + } +} diff --git a/macOS/SearchifyX/Toolbars/ScraperToolbar.swift b/macOS/SearchifyX/Toolbars/ScraperToolbar.swift new file mode 100644 index 0000000..4cc83e0 --- /dev/null +++ b/macOS/SearchifyX/Toolbars/ScraperToolbar.swift @@ -0,0 +1,64 @@ +// +// ScraperToolbar.swift +// SearchifyX +// +// Created by Jose Molina on 11/17/22. +// + +import SwiftUI + +struct ScraperToolbar: View { + var question: Binding + var runAfterPaste: Binding + var searchQuestion: (String) -> Void + var runAfterOcr: Binding + var searching: Binding + + var body: some View { + HStack { + TextField("Search a question here", text: question) + .lineLimit(nil) + .frame(minWidth: 400) + .textFieldStyle(.roundedBorder) + Button( + action: { + searchQuestion(self.question.wrappedValue) + }, + label: { + Image(systemName: "magnifyingglass") + Text("Search") + } + ) + .buttonStyle(.borderless) + Button( + action: { + self.question.wrappedValue = Scraper.getClipboard() + + if runAfterPaste.wrappedValue { + searchQuestion(self.question.wrappedValue) + } + }, + label: { + Image(systemName: "doc.on.clipboard") + } + ) + .buttonStyle(.borderless) + Button( + action: { + DispatchQueue.global(qos: .userInitiated).async { + question.wrappedValue = Scraper.ocr() + + if runAfterOcr.wrappedValue { + searchQuestion(self.question.wrappedValue) + } + } + }, + label: { + Image(systemName: "eye") + } + ) + .buttonStyle(.borderless) + } + .disabled(searching.wrappedValue != false) + } +} diff --git a/macOS/SearchifyX/Views/BrowserView.swift b/macOS/SearchifyX/Views/BrowserView.swift new file mode 100644 index 0000000..f3371d4 --- /dev/null +++ b/macOS/SearchifyX/Views/BrowserView.swift @@ -0,0 +1,40 @@ +// +// BrowserView.swift +// SearchifyX +// +// Created by Jose Molina on 11/17/22. +// + +import SwiftUI + +struct BrowserView: View { + var isPanel: Bool + + @EnvironmentObject var wkvm: WebViewModel + + var body: some View { + VStack { + if (isPanel) { + BrowserToolbar(url: $wkvm.urlString) + .padding(.leading) + .padding(.trailing) + } + + NSWebView(webkit: Variables.wkModel.instance) + } + .toolbar { + ToolbarItemGroup(placement: .principal) { + BrowserToolbar(url: $wkvm.urlString) + } + } + .onLoad { + Variables.wkModel.loadUrl() + } + } +} + +struct BrowserView_Previews: PreviewProvider { + static var previews: some View { + BrowserView(isPanel: false) + } +} diff --git a/macOS/SearchifyX/Views/ContentView.swift b/macOS/SearchifyX/Views/ContentView.swift new file mode 100644 index 0000000..d35aec3 --- /dev/null +++ b/macOS/SearchifyX/Views/ContentView.swift @@ -0,0 +1,206 @@ +import SwiftUI + +struct ContentView: View { + @State var question: String = "" + @State var enableQuizizz: Bool = true + @State var enableQuizlet: Bool = true + @State var cards: [Flashcard] = [] + @State var selected: Flashcard.ID? + @State var searching: Bool = false + @State var engine: String = "google" + @State var showingPanel: Bool = false + @State var showBrowser: Bool = false + + @StateObject var wkmodel = WebViewModel() + + @AppStorage("runAfterOcr") var runAfterOcr: Bool = false + @AppStorage("runAfterPaste") var runAfterPaste: Bool = false + + var isPanel: Bool + var hasShown: Bool? + var skQuery: String? + + init(isPanel: Bool, question q: String?) { + self.isPanel = isPanel + if q != nil { + skQuery = q! + hasShown = true + } + else { + hasShown = false + } + } + + var body: some View { + VStack { + HStack { + if isPanel == false { + Button(action: { + Scraper.makeHiddenWindow(question: nil) + }, label: { + Image(systemName: "eye.slash") + }) + } + TextField("Search a question here", text: $question) + .lineLimit(nil) + Button( + action: { + searchQuestion(query: self.question) + }, + label: { + Image(systemName: "magnifyingglass") + Text("Search") + } + ) + Button( + action: { + question = Scraper.getClipboard() + + if runAfterPaste { + searchQuestion(query: self.question) + } + }, + label: { + Image(systemName: "doc.on.clipboard") + } + ) + Button( + action: { + showBrowser = !showBrowser + }, + label: { + Image(systemName: "globe.americas.fill") + } + ) + Button( + action: { + DispatchQueue.global(qos: .userInitiated).async { + question = Scraper.ocr() + + if runAfterOcr { + searchQuestion(query: self.question) + } + } + }, + label: { + Image(systemName: "eye") + } + ) + } + .disabled(searching != false) + .padding() + + ZStack { + if showBrowser { + VStack { + HStack { + Button(action: { + wkmodel.instance.goBack() + }, label: { + Image(systemName: "chevron.left") + }) + Button(action: { + wkmodel.instance.goBack() + }, label: { + Image(systemName: "chevron.right") + }) + TextField("Enter URL", text: $wkmodel.urlString) + Button(action: { + wkmodel.loadUrl() + }, label: { + Text("Go") + }) + Spacer() + } + .padding(.trailing) + .padding(.leading) + NSWebView(webkit: wkmodel.instance) + } + } + else { + Table(cards, selection: $selected) { + TableColumn("Question", value: \.question) + TableColumn("Answer", value: \.answer) + TableColumn("Similarity", value: \.similarity) + TableColumn("URL", value: \.url) + } + + if searching { + ProgressView() + } + } + } + + HStack { + Spacer() + Picker("Search Engine", selection: $engine) { + Text("Google").tag("google") + Text("Bing").tag("bing") + Text("Startpage").tag("startpage") + } + Button(action: { + var original: Flashcard? + + for item in cards { + if (item.id == selected) { + original = item + break + } + } + + let url = URL(string: original!.url)! + wkmodel.urlString = url.absoluteString + showBrowser = true + wkmodel.loadUrl() + + }, label: { + Text("Open question") + }) + .disabled(selected == nil) + Toggle(isOn: $enableQuizizz) { + Image("QuizizzIcon") + .resizable() + .frame(width: 16, height: 16) + Text("Quizizz") + } + Toggle(isOn: $enableQuizlet) { + Image("QuizletIcon") + .resizable() + .frame(width: 16, height: 16) + Text("Quizlet") + } + } + .padding() + } + .onAppear { + if hasShown! { + self.question = skQuery! + searchQuestion(query: skQuery!) + } + } + } + + func searchQuestion(query: String) { + searching = true + + var sites: [String] = [] + + if enableQuizizz { + sites.append("quizizz") + } + if enableQuizlet { + sites.append("quizlet") + } + + DispatchQueue.global(qos: .userInitiated).async { + cards = Scraper.search(query: query, sites: sites.joined(separator: ","), engine: engine) + searching = false + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView(isPanel: false, question: nil) + } +} diff --git a/macOS/SearchifyX/Views/NotesView.swift b/macOS/SearchifyX/Views/NotesView.swift new file mode 100644 index 0000000..09ffe1d --- /dev/null +++ b/macOS/SearchifyX/Views/NotesView.swift @@ -0,0 +1,20 @@ +// +// NotesView.swift +// SearchifyX +// +// Created by Jose Molina on 11/17/22. +// + +import SwiftUI + +struct NotesView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +struct NotesView_Previews: PreviewProvider { + static var previews: some View { + NotesView() + } +} diff --git a/macOS/SearchifyX/Views/ScraperView.swift b/macOS/SearchifyX/Views/ScraperView.swift new file mode 100644 index 0000000..be27729 --- /dev/null +++ b/macOS/SearchifyX/Views/ScraperView.swift @@ -0,0 +1,127 @@ +import SwiftUI + +struct ScraperView: View { + @State var question: String = "" + @State var enableQuizizz: Bool = true + @State var enableQuizlet: Bool = true + @State var cards: [Flashcard] = [] + @State var selected: Flashcard.ID? + @State var searching: Bool = false + @State var engine: String = "google" + + @AppStorage("runAfterOcr") var runAfterOcr: Bool = false + @AppStorage("runAfterPaste") var runAfterPaste: Bool = false + + var hasShown: Bool? + var skQuery: String? + + var isPanel: Bool + + init(isPanel: Bool, question q: String?) { + self.isPanel = isPanel + if q != nil { + skQuery = q! + hasShown = true + } + else { + hasShown = false + } + } + + var body: some View { + VStack { + if isPanel { + ScraperToolbar(question: $question, runAfterPaste: $runAfterPaste, searchQuestion: searchQuestion(query:), runAfterOcr: $runAfterOcr, searching: $searching) + .padding(.leading) + .padding(.trailing) + } + + ZStack { + Table(cards, selection: $selected) { + TableColumn("Question", value: \.question) + TableColumn("Answer", value: \.answer) + TableColumn("Similarity", value: \.similarity) + TableColumn("URL", value: \.url) + } + + if searching { + ProgressView() + } + } + + HStack { + Spacer() + Picker("Search Engine", selection: $engine) { + Text("Google").tag("google") + Text("Bing").tag("bing") + Text("Startpage").tag("startpage") + } + Button(action: { + var original: Flashcard? + + for item in cards { + if (item.id == selected) { + original = item + break + } + } + + let url = URL(string: original!.url)! + Variables.wkModel.urlString = url.absoluteString + + }, label: { + Text("Open question") + }) + .disabled(selected == nil) + Toggle(isOn: $enableQuizizz) { + Image("QuizizzIcon") + .resizable() + .frame(width: 16, height: 16) + Text("Quizizz") + } + Toggle(isOn: $enableQuizlet) { + Image("QuizletIcon") + .resizable() + .frame(width: 16, height: 16) + Text("Quizlet") + } + } + .padding() + } + .onAppear { + if hasShown! { + self.question = skQuery! + searchQuestion(query: skQuery!) + } + } + .toolbar { + ToolbarItemGroup(placement: .principal) { + ScraperToolbar(question: $question, runAfterPaste: $runAfterPaste, searchQuestion: searchQuestion(query:), runAfterOcr: $runAfterOcr, searching: $searching) + } + } + } + + func searchQuestion(query: String) { + searching = true + + var sites: [String] = [] + + if enableQuizizz { + sites.append("quizizz") + } + if enableQuizlet { + sites.append("quizlet") + } + + DispatchQueue.global(qos: .userInitiated).async { + cards = Scraper.search(query: query, sites: sites.joined(separator: ","), engine: engine) + searching = false + } + } +} + +struct ScraperView_Previews: PreviewProvider { + static var previews: some View { + ScraperView(isPanel: false, question: nil) + } +} diff --git a/macOS/SearchifyX/Views/SettingsView.swift b/macOS/SearchifyX/Views/SettingsView.swift new file mode 100644 index 0000000..585d0da --- /dev/null +++ b/macOS/SearchifyX/Views/SettingsView.swift @@ -0,0 +1,32 @@ +import SwiftUI +import KeyboardShortcuts + +struct SettingsView: View { + @AppStorage("runAfterOcr") var runAfterOcr: Bool = false + @AppStorage("runAfterPaste") var runAfterPaste: Bool = false + @AppStorage("showOnNotificationCenter") var showOnNotificationCenter: Bool = false + + var body: some View { + VStack { + Toggle(isOn: $runAfterOcr) { + Text("Search after OCR text is pasted") + } + Toggle(isOn: $runAfterPaste) { + Text("Search after text is pasted") + } + Toggle(isOn: $showOnNotificationCenter) { + Text("Send answer as notification instead of showing window") + } + KeyboardShortcuts.Recorder("Open Hidden SearchifyX", name: .openSearchify) + KeyboardShortcuts.Recorder("OCR and search", name: .ocrAndSearch) + KeyboardShortcuts.Recorder("Paste and search", name: .pasteAndSearch) + } + .padding() + } +} + +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + SettingsView() + } +} diff --git a/macOS/SearchifyX/WebView/NSWebView.swift b/macOS/SearchifyX/WebView/NSWebView.swift new file mode 100644 index 0000000..20decdd --- /dev/null +++ b/macOS/SearchifyX/WebView/NSWebView.swift @@ -0,0 +1,21 @@ +// +// WebView.swift +// SearchifyX +// +// Created by Jose Molina on 10/20/22. +// + +import Foundation +import WebKit +import SwiftUI + +struct NSWebView: NSViewRepresentable { + let webkit: WKWebView + + func makeNSView(context: Context) -> WKWebView { + webkit + } + + func updateNSView(_ nsView: WKWebView, context: Context) { + } +} diff --git a/macOS/SearchifyX/WebView/WebView.swift b/macOS/SearchifyX/WebView/WebView.swift new file mode 100644 index 0000000..ca13cda --- /dev/null +++ b/macOS/SearchifyX/WebView/WebView.swift @@ -0,0 +1,22 @@ +// +// WebView.swift +// SearchifyX +// +// Created by Jose Molina on 10/24/22. +// + +import SwiftUI + +struct WebView: View { + @StateObject var model = WebViewModel() + + var body: some View { + + } +} + +struct WebView_Previews: PreviewProvider { + static var previews: some View { + WebView() + } +} diff --git a/macOS/SearchifyX/WebView/WebViewModel.swift b/macOS/SearchifyX/WebView/WebViewModel.swift new file mode 100644 index 0000000..db12488 --- /dev/null +++ b/macOS/SearchifyX/WebView/WebViewModel.swift @@ -0,0 +1,58 @@ +// +// WebViewModel.swift +// SearchifyX +// +// Created by Jose Molina on 11/17/22. +// + +import Foundation +import WebKit + +class WebViewModel: ObservableObject { + @Published var urlString: String = "https://google.com" + + let instance: WKWebView + var cord: Coordinator? + var bindingsInit: Bool + + init() { + instance = WKWebView(frame: .zero); + bindingsInit = false + cord = nil + } + + func loadUrl() { + createBindings() + + if !urlString.starts(with: "http") { + urlString = "https://" + urlString + } + + var req = URLRequest(url: URL(string: urlString)!) + instance.load(req) + } + + func createBindings() { + if bindingsInit == true { + return + } + cord = Coordinator(self) + instance.navigationDelegate = cord + bindingsInit = true + } + + class Coordinator: NSObject, WKNavigationDelegate { + let parent: WebViewModel + + init(_ parent: WebViewModel) { + self.parent = parent + } + + func webView(_ webView: WKWebView, + didStartProvisionalNavigation navigation: WKNavigation!) { + print(webView.url!.absoluteString) + parent.urlString = webView.url!.absoluteString + } + + } +} diff --git a/macOS/build.sh b/macOS/build.sh old mode 100755 new mode 100644 index 953807a..3457b4b --- a/macOS/build.sh +++ b/macOS/build.sh @@ -3,14 +3,12 @@ # Get package requirements pip3 install -r requirements.txt -# Create setup.py file -py2applet --make-setup ../scraper.py - # Generate app -python3 setup.py py2app +mkdir ./SearchifyX/scraper +python3 -m PyInstaller --noconfirm ../scraper.py # Delete old scraper.app -rm -r ./SearchifyX/scraper.app +rm -r ./SearchifyX/scraper # Copy newest scraper.app -cp -R ./dist/scraper.app ./SearchifyX/ \ No newline at end of file +cp -R ./dist/scraper ./SearchifyX/ diff --git a/macOS/clean.sh b/macOS/clean.sh old mode 100755 new mode 100644 index 7a8f27c..3b248ad --- a/macOS/clean.sh +++ b/macOS/clean.sh @@ -3,5 +3,5 @@ # Remove build directories rm -r ./build/ rm -r ./dist/ - -rm setup.py \ No newline at end of file +rm scraper.spec +rm ./SearchifyX/scraper diff --git a/merlin.py b/merlin.py new file mode 100644 index 0000000..db60f58 --- /dev/null +++ b/merlin.py @@ -0,0 +1,104 @@ +import requests +import re +import json +import os +from random import randint +import logging +from queue import Queue + +# logging +logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)s: %(message)s', datefmt='%H:%M:%S') + +# headers +class MerlinScraper: + headers = { + 'authority': 'openai-api-yak3s7dv3a-ue.a.run.app', + 'accept': 'text/event-stream', + 'accept-language': 'en-US,en;q=0.9', + 'cache-control': 'no-cache', + 'dnt': '1', + 'origin': 'https://www.google.com', + 'referer': 'https://www.google.com/', + 'sec-ch-ua': '"Not?A_Brand";v="8", "Chromium";v="108"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'cross-site', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', + } + + def __init__(self): + self.setAccount() + + def setAccount(self): + # make account + if os.path.exists('merlin_id.bin'): + with open('merlin_id.bin', 'r') as f: + self.userid = f.read().strip() + logging.debug(f'Account loaded from merin_id.bin: {self.userid}') + else: + self.userid = self.generateAccount() + + def generateAccount(self): # sourcery skip: raise-specific-error + logging.info('Generating Merlin account...') + url = "https://us-central1-foyer-work.cloudfunctions.net:443/createMerlinFreeUser" + acc_headers = { + **self.headers, + "content-type": "text/plain;charset=UTF-8", + "accept": "*/*", + "origin": "chrome-extension://camppjleccjaphfdbohjdohecfnoikec", + "connection": "close" + } + userid = ''.join([hex(randint(0, 16))[-1] for _ in range(60)]) + payload = {"segmentation": "OPENAI_CHATGPT_VERSION", "userid": userid} + r = requests.post(url, headers=acc_headers, json=payload) + if r.status_code != 200: + logging.critical('Error making account.') + raise Exception(r.text) + else: + logging.info(f'Account created: {userid}') + with open('merlin_id.bin', 'w') as f: + f.write(userid) + return userid + + def prompt(self, prompt, queue: Queue=None): + assert self.userid is not None + r = requests.get( + "https://openai-api-yak3s7dv3a-ue.a.run.app/", + headers=self.headers, + params={ + 'q': prompt.strip(), + 'userid': self.userid, + 'segmentation': 'OPENAI_CHATGPT_VERSION' + }, + stream=True + ) + all_text = '' + for chunk in r.iter_content(chunk_size=1024): + resp = re.search('{.*}', chunk.decode()) + if not resp: + continue + try: + data = json.loads(resp[0]) + except json.decoder.JSONDecodeError: + logging.critical(f'Error decoding JSON:\n{resp[0]}') + continue + if 'choices' not in data: + continue + if 'text' not in data['choices'][0]: + continue + text = data['choices'][0]['text'] + if not all_text: + text = text.lstrip() + queue and queue.put(text) + print(text, end='') + all_text += text + queue and queue.put(None) + print('') + return all_text + + +if __name__ == "__main__": + chatgpt = MerlinScraper() + chatgpt.prompt('Hello, how are you?') \ No newline at end of file diff --git a/scraper.py b/scraper.py index ef2fe4a..3cf0629 100644 --- a/scraper.py +++ b/scraper.py @@ -15,7 +15,7 @@ import orjson import regex from bs4 import BeautifulSoup, MarkupResemblesLocatorWarning -from jellyfish import jaro_distance as similar +from jellyfish import jaro_similarity as similar # set logger logger = logging.getLogger(__name__) diff --git a/windoweffect/LICENSE b/windoweffect/LICENSE deleted file mode 100644 index c446912..0000000 --- a/windoweffect/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Zhengzhi Huang - -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. \ No newline at end of file diff --git a/windoweffect/__init__.py b/windoweffect/__init__.py deleted file mode 100644 index a3fb4ed..0000000 --- a/windoweffect/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .window_effect import WindowEffect -from .c_structures import * \ No newline at end of file diff --git a/windoweffect/c_structures.py b/windoweffect/c_structures.py deleted file mode 100644 index 1553383..0000000 --- a/windoweffect/c_structures.py +++ /dev/null @@ -1,135 +0,0 @@ -# coding:utf-8 - -from ctypes import POINTER, Structure, c_int -from ctypes.wintypes import DWORD, HWND, ULONG, POINT, RECT, UINT -from enum import Enum - - -class WINDOWCOMPOSITIONATTRIB(Enum): - WCA_UNDEFINED = 0 - WCA_NCRENDERING_ENABLED = 1 - WCA_NCRENDERING_POLICY = 2 - WCA_TRANSITIONS_FORCEDISABLED = 3 - WCA_ALLOW_NCPAINT = 4 - WCA_CAPTION_BUTTON_BOUNDS = 5 - WCA_NONCLIENT_RTL_LAYOUT = 6 - WCA_FORCE_ICONIC_REPRESENTATION = 7 - WCA_EXTENDED_FRAME_BOUNDS = 8 - WCA_HAS_ICONIC_BITMAP = 9 - WCA_THEME_ATTRIBUTES = 10 - WCA_NCRENDERING_EXILED = 11 - WCA_NCADORNMENTINFO = 12 - WCA_EXCLUDED_FROM_LIVEPREVIEW = 13 - WCA_VIDEO_OVERLAY_ACTIVE = 14 - WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15 - WCA_DISALLOW_PEEK = 16 - WCA_CLOAK = 17 - WCA_CLOAKED = 18 - WCA_ACCENT_POLICY = 19 - WCA_FREEZE_REPRESENTATION = 20 - WCA_EVER_UNCLOAKED = 21 - WCA_VISUAL_OWNER = 22 - WCA_LAST = 23 - - -class ACCENT_STATE(Enum): - """ Client area status enumeration class """ - ACCENT_DISABLED = 0 - ACCENT_ENABLE_GRADIENT = 1 - ACCENT_ENABLE_TRANSPARENTGRADIENT = 2 - ACCENT_ENABLE_BLURBEHIND = 3 # Aero effect - ACCENT_ENABLE_ACRYLICBLURBEHIND = 4 # Acrylic effect - ACCENT_ENABLE_HOSTBACKDROP = 5 # Mica effect - ACCENT_INVALID_STATE = 6 - - -class ACCENT_POLICY(Structure): - """ Specific attributes of client area """ - - _fields_ = [ - ("AccentState", DWORD), - ("AccentFlags", DWORD), - ("GradientColor", DWORD), - ("AnimationId", DWORD), - ] - - -class WINDOWCOMPOSITIONATTRIBDATA(Structure): - _fields_ = [ - ("Attribute", DWORD), - # Pointer() receives any ctypes type and returns a pointer type - ("Data", POINTER(ACCENT_POLICY)), - ("SizeOfData", ULONG), - ] - - -class DWMNCRENDERINGPOLICY(Enum): - DWMNCRP_USEWINDOWSTYLE = 0 - DWMNCRP_DISABLED = 1 - DWMNCRP_ENABLED = 2 - DWMNCRP_LAS = 3 - - -class DWMWINDOWATTRIBUTE(Enum): - DWMWA_NCRENDERING_ENABLED = 1 - DWMWA_NCRENDERING_POLICY = 2 - DWMWA_TRANSITIONS_FORCEDISABLED = 3 - DWMWA_ALLOW_NCPAINT = 4 - DWMWA_CAPTION_BUTTON_BOUNDS = 5 - DWMWA_NONCLIENT_RTL_LAYOUT = 6 - DWMWA_FORCE_ICONIC_REPRESENTATION = 7 - DWMWA_FLIP3D_POLICY = 8 - DWMWA_EXTENDED_FRAME_BOUNDS = 9 - DWMWA_HAS_ICONIC_BITMAP = 10 - DWMWA_DISALLOW_PEEK = 11 - DWMWA_EXCLUDED_FROM_PEEK = 12 - DWMWA_CLOAK = 13 - DWMWA_CLOAKED = 14 - DWMWA_FREEZE_REPRESENTATION = 15 - DWMWA_PASSIVE_UPDATE_MODE = 16 - DWMWA_USE_HOSTBACKDROPBRUSH = 17 - DWMWA_USE_IMMERSIVE_DARK_MODE = 18 - DWMWA_WINDOW_CORNER_PREFERENCE = 19 - DWMWA_BORDER_COLOR = 20 - DWMWA_CAPTION_COLOR = 21 - DWMWA_TEXT_COLOR = 22 - DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 23 - DWMWA_LAST = 24 - - -class MARGINS(Structure): - _fields_ = [ - ("cxLeftWidth", c_int), - ("cxRightWidth", c_int), - ("cyTopHeight", c_int), - ("cyBottomHeight", c_int), - ] - - -class MINMAXINFO(Structure): - _fields_ = [ - ("ptReserved", POINT), - ("ptMaxSize", POINT), - ("ptMaxPosition", POINT), - ("ptMinTrackSize", POINT), - ("ptMaxTrackSize", POINT), - ] - - -class PWINDOWPOS(Structure): - _fields_ = [ - ('hWnd', HWND), - ('hwndInsertAfter', HWND), - ('x', c_int), - ('y', c_int), - ('cx', c_int), - ('cy', c_int), - ('flags', UINT) - ] - - -class NCCALCSIZE_PARAMS(Structure): - _fields_ = [ - ('rgrc', RECT*3), - ('lppos', POINTER(PWINDOWPOS)) - ] diff --git a/windoweffect/window_effect.py b/windoweffect/window_effect.py deleted file mode 100644 index e53946c..0000000 --- a/windoweffect/window_effect.py +++ /dev/null @@ -1,114 +0,0 @@ -# coding:utf-8 -import sys - -from ctypes import POINTER, c_bool, c_int, pointer, sizeof, WinDLL, byref -from ctypes.wintypes import DWORD, LONG, LPCVOID - -from win32 import win32api, win32gui -from win32.lib import win32con - -from .c_structures import ( - ACCENT_POLICY, - ACCENT_STATE, - MARGINS, - DWMNCRENDERINGPOLICY, - DWMWINDOWATTRIBUTE, - WINDOWCOMPOSITIONATTRIB, - WINDOWCOMPOSITIONATTRIBDATA, -) - - -class WindowEffect: - """ A class that calls Windows API to realize window effect """ - - def __init__(self): - # Declare the function signature of the API - self.user32 = WinDLL("user32") - self.dwmapi = WinDLL("dwmapi") - self.SetWindowCompositionAttribute = self.user32.SetWindowCompositionAttribute - self.DwmExtendFrameIntoClientArea = self.dwmapi.DwmExtendFrameIntoClientArea - self.DwmSetWindowAttribute = self.dwmapi.DwmSetWindowAttribute - self.SetWindowCompositionAttribute.restype = c_bool - self.DwmExtendFrameIntoClientArea.restype = LONG - self.DwmSetWindowAttribute.restype = LONG - self.SetWindowCompositionAttribute.argtypes = [ - c_int, - POINTER(WINDOWCOMPOSITIONATTRIBDATA), - ] - self.DwmSetWindowAttribute.argtypes = [c_int, DWORD, LPCVOID, DWORD] - self.DwmExtendFrameIntoClientArea.argtypes = [c_int, POINTER(MARGINS)] - - # Initialize structure - self.accentPolicy = ACCENT_POLICY() - self.winCompAttrData = WINDOWCOMPOSITIONATTRIBDATA() - self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value - self.winCompAttrData.SizeOfData = sizeof(self.accentPolicy) - self.winCompAttrData.Data = pointer(self.accentPolicy) - - - def addShadowEffect(self, hWnd): - """ Add DWM shadow to the window - - Parameters - ---------- - hWnd: int or `sip.voidptr` - Window handle - """ - hWnd = int(hWnd) - self.DwmSetWindowAttribute( - hWnd, - DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value, - byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_ENABLED.value)), - 4, - ) - margins = MARGINS(-1, -1, -1, -1) - self.DwmExtendFrameIntoClientArea(hWnd, byref(margins)) - - def removeShadowEffect(self, hWnd): - """ Remove DWM shadow from the window - - Parameters - ---------- - hWnd: int or `sip.voidptr` - Window handle - """ - hWnd = int(hWnd) - self.DwmSetWindowAttribute( - hWnd, - DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value, - byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_DISABLED.value)), - 4, - ) - - @staticmethod - def removeMenuShadowEffect(hWnd): - """ Remove shadow from pop-up menu - - Parameters - ---------- - hWnd: int or `sip.voidptr` - Window handle - """ - style = win32gui.GetClassLong(hWnd, win32con.GCL_STYLE) - style &= ~0x00020000 # CS_DROPSHADOW - win32api.SetClassLong(hWnd, win32con.GCL_STYLE, style) - - @staticmethod - def addWindowAnimation(hWnd): - """ Enables the maximize and minimize animation of the window - - Parameters - ---------- - hWnd : int or `sip.voidptr` - Window handle - """ - style = win32gui.GetWindowLong(hWnd, win32con.GWL_STYLE) - win32gui.SetWindowLong( - hWnd, - win32con.GWL_STYLE, - style - | win32con.WS_MAXIMIZEBOX - | win32con.WS_CAPTION - | win32con.CS_DBLCLKS - | win32con.WS_THICKFRAME, - )