Merge pull request #9 from hamzenis/Sora-JSCore
Some checks failed
Build and Release IPA / Build IPA (push) Has been cancelled

This commit is contained in:
cranci 2025-01-10 19:01:54 +01:00 committed by GitHub
commit c643e02b8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 135 additions and 9 deletions

View file

@ -2,10 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>

View file

@ -20,6 +20,46 @@ class JSController: ObservableObject {
print("JavaScript log: \(message)")
}
context.setObject(logFunction, forKeyedSubscript: "log" as NSString)
// Because fetch isnt available in JSContext, it simulates the fetch api for Javascript.
// Performs network calls with URLSession
let fetchNativeFunction: @convention(block) (String, JSValue, JSValue) -> Void = { urlString, resolve, reject in
guard let url = URL(string: urlString) else {
print("Invalid URL")
reject.call(withArguments: ["Invalid URL"])
return
}
let task = URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error {
print("Network error in fetchNativeFunction: \(error.localizedDescription)")
reject.call(withArguments: [error.localizedDescription])
return
}
guard let data = data else {
print("No data in response")
reject.call(withArguments: ["No data"])
return
}
if let text = String(data: data, encoding: .utf8) {
resolve.call(withArguments: [text])
} else {
print("Unable to decode data to text")
reject.call(withArguments: ["Unable to decode data"])
}
}
task.resume()
}
context.setObject(fetchNativeFunction, forKeyedSubscript: "fetchNative" as NSString)
// Define fetch for JavaScript
let fetchDefinition = """
function fetch(url) {
return new Promise(function(resolve, reject) {
fetchNative(url, resolve, reject);
});
}
"""
context.evaluateScript(fetchDefinition)
}
func loadScript(_ script: String) {
@ -154,4 +194,81 @@ class JSController: ObservableObject {
}
}.resume()
}
/// Use Javascript to fetch search results
func fetchJsSearchResults(keyword: String, module: ScrapingModule, completion: @escaping ([SearchItem]) -> Void) {
if let exception = context.exception {
print("JavaScript exception: \(exception)")
completion([])
return
}
guard let searchResultsFunction = context.objectForKeyedSubscript("searchResults") else {
print("No JavaScript function searchResults found")
completion([])
return
}
// Call the JavaScript function, passing in the parameter
let promiseValue = searchResultsFunction.call(withArguments: [keyword])
guard let promise = promiseValue else {
print("searchResults did not return a Promise")
completion([])
return
}
// Handles successful promise resolution.
let thenBlock: @convention(block) (JSValue) -> Void = { result in
if let jsonString = result.toString(),
let data = jsonString.data(using: .utf8) {
do {
if let array = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] {
let resultItems = array.map { item -> SearchItem in
let title = item["title"] as? String ?? ""
let imageUrl = item["image"] as? String ?? "https://s4.anilist.co/file/anilistcdn/character/large/default.jpg"
let href = item["href"] as? String ?? ""
return SearchItem(title: title, imageUrl: imageUrl, href: href)
}
DispatchQueue.main.async {
completion(resultItems)
}
} else {
print("Failed to parse JSON")
DispatchQueue.main.async {
completion([])
}
}
} catch {
print("JSON parsing error: \(error)")
DispatchQueue.main.async {
completion([])
}
}
} else {
print("Result is not a string")
DispatchQueue.main.async {
completion([])
}
}
}
// Handles promise rejection.
let catchBlock: @convention(block) (JSValue) -> Void = { error in
print("Promise rejected: \(String(describing: error.toString()))")
DispatchQueue.main.async {
completion([])
}
}
// Wrap the Swift blocks into JSValue functions
let thenFunction = JSValue(object: thenBlock, in: context)
let catchFunction = JSValue(object: catchBlock, in: context)
// Attach the 'then' and 'catch' callbacks to the Promise
promise.invokeMethod("then", withArguments: [thenFunction])
promise.invokeMethod("catch", withArguments: [catchFunction])
}
}

View file

@ -16,6 +16,7 @@ struct ModuleMetadata: Codable, Hashable {
let baseUrl: String
let searchBaseUrl: String
let scriptUrl: String
let asyncJS: Bool?
}
struct ScrapingModule: Codable, Identifiable, Hashable {

View file

@ -154,10 +154,18 @@ struct SearchView: View {
do {
let jsContent = try moduleManager.getModuleContent(module)
jsController.loadScript(jsContent)
jsController.fetchSearchResults(keyword: searchText, module: module) { items in
searchItems = items
hasNoResults = items.isEmpty
isSearching = false
if(module.metadata.asyncJS == false || module.metadata.asyncJS == nil) {
jsController.fetchSearchResults(keyword: searchText, module: module) { items in
searchItems = items
hasNoResults = items.isEmpty
isSearching = false
}
} else {
jsController.fetchJsSearchResults(keyword: searchText, module: module) { items in
searchItems = items
hasNoResults = items.isEmpty
isSearching = false
}
}
} catch {
print("Error loading module: \(error)")