// Copyright © 2024 Ryan Booker. All rights reserved.
import ArgumentParser
import Foundation
extension Kagi {
struct Search: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "search",
abstract: "Search for something (Beta, requires Kagi Business (Team) plan).",
usage: "kg search \"Do androids dream of electric sheep?\""
)
@Argument(help: "Something to search for") var query: String
@Option(help: "The maximum number of search results") var limit: Int?
mutating func run() async throws {
do {
let response = try await process(query, limit: limit)
print(response)
} catch {
throw ValidationError(error.localizedDescription)
}
}
}
}
// MARK: - Processing
extension Kagi.Search {
func process(_ query: String, limit: Int?) async throws -> Kagi.Response<[Either<SearchResult, RelatedResults>]> {
var queryItems: [URLQueryItem] = [.init(name: "q", value: query)]
if let limit {
queryItems.append(.init(name: "limit", value: "\(limit)"))
}
var request = try Kagi.Endpoint.search.authorizedRequest
request.url?.append(queryItems: queryItems)
let (data, response) = try await URLSession.shared.data(for: request)
do {
return try JSONDecoder().decode(Kagi.Response.self, from: data)
} catch {
#if DEBUG
print(response)
print(error)
print("-----")
#endif
throw error
}
}
}
// MARK: - Response
extension Kagi.Search {
struct SearchResult: CustomStringConvertible, Decodable {
struct Thumbnail: Decodable {
var url: URL
var height: Int
var width: Int
}
var rank: Int
var url: URL
var title: String
var snippet: String?
var published: Date?
var thumbnail: Thumbnail?
var description: String {
[
"""
\(rank): \(title)
\(url)
-----
""",
snippet.map {
"""
\($0)
-----
"""
},
published.map {
"""
\($0.formatted(date: .long, time: .shortened))
-----
"""
}
]
.compactMap { $0 }
.joined(separator: "\n")
}
}
struct RelatedResults: CustomStringConvertible, Decodable {
var list: [String]
var description: String {
"""
Possibly related searches:
\(list.map { "- \($0)" })
"""
}
}
}
extension Array where Element == Either<Kagi.Search.SearchResult, Kagi.Search.RelatedResults> {
var description: String {
"""
Results:
\(map(\.description).joined(separator: "\n\n"))
"""
}
}