← Back to index

IBDBService

Spots
CodeWhat It DoesHow It Does It
▶ IMPORTS
import Foundation import WebKitFramework importsImports Foundation, WebKit.
@MainActor final class IBDBService: NSObject { static let shared = IBDBService() private override init() { super.init() } // MARK: - State private enum Phase { case loadingForm, loadingResults } private var webView: WKWebView? private var continuation: CheckedContinuation<URL?, Never>? private var phase: Phase = .loadingForm private var showName: String = "" // MARK: - Public func findShowURL(named showName: String) async -> URL? { self.showName = showName self.phase = .loadingForm webView?.navigationDelegate = nil webView?.stopLoading() webView?.removeFromSuperview() webView = nil print("[IBDB] Starting search for: \(showName)") return await withCheckedContinuation { cont in self.continuation = cont let config = WKWebViewConfiguration() config.websiteDataStore = .nonPersistent() let wv = WKWebView(frame: CGRect(x: 0, y: 0, width: 375, height: 812), configuration: config) wv.navigationDelegate = self wv.customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) " + "AppleWebKit/605.1.15 (KHTML, like Gecko) " + "Version/17.0 Mobile/15E148 Safari/604.1" wv.isUserInteractionEnabled = false // prevent keyboard from ever appearing self.webView = wv // Must be in a window so WebKit's JS engine runs at full speed if let window = UIApplication.shared.connectedScenes .compactMap({ $0 as? UIWindowScene }) .first?.windows.first { wv.isHidden = true window.addSubview(wv) } let url = URL(string: "https://www.ibdb.com/search")! wv.load(URLRequest(url: url)) } } // MARK: - Private /// Fills the Quick Search bar and submits. IBDB redirects straight to the /// show page when there is a clear match, so the result URL arrives in /// the very next didFinish call — no JavaScript extraction required. private func submitSearch(in wv: WKWebView) { let safe = showName .replacingOccurrences(of: "\\", with: "\\\\") .replacingOccurrences(of: "'", with: "\\'") let js = """ (function() { var input = document.getElementById('KeywordTop'); if (!input) return 'ERROR: KeywordTop not found'; input.value = '\(safe)'; var btn = document.getElementById('quickSearchTopBtn'); if (!btn) return 'ERROR: quickSearchTopBtn not found'; btn.click(); return 'submitted: ' + input.value; })() """ wv.evaluateJavaScript(js) { result, error in print("[IBDB] Submit: \(result as? String ?? "nil")") } } private func finish(with url: URL?) { webView?.removeFromSuperview() webView?.navigationDelegate = nil webView = nil continuation?.resume(returning: url) continuation = nil } }Code blockSee source code for full implementation.
▶ WKNAVIGATIONDELEGATE
extension IBDBService: WKNavigationDelegate { nonisolated func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { Task { @MainActor in let url = webView.url?.absoluteString ?? "?" print("[IBDB] didFinish: \(url) (phase: \(self.phase))") switch self.phase { case .loadingForm: self.phase = .loadingResults self.submitSearch(in: webView) case .loadingResults: // IBDB redirects directly to the show page — the URL is the result let result = webView.url print("[IBDB] Result URL: \(result?.absoluteString ?? "none")") self.finish(with: result) } } } nonisolated func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { Task { @MainActor in print("[IBDB] Navigation failed: \(error.localizedDescription)") self.finish(with: nil) } } nonisolated func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { Task { @MainActor in print("[IBDB] Provisional navigation failed: \(error.localizedDescription)") self.finish(with: nil) } } }`IBDBService` extensionDefines the `IBDBService` extension. Conforms to WKNavigationDelegate.