Compare commits
5 Commits
baa03a4281
...
54f71ce23f
Author | SHA1 | Date |
---|---|---|
a. fox | 54f71ce23f | |
a. fox | d8c47bd905 | |
a. fox | b00669a167 | |
a. fox | ecdb1bdc35 | |
a. fox | d9cbb90575 |
|
@ -6,7 +6,7 @@ import PackageDescription
|
|||
let package = Package(
|
||||
name: "Seanut",
|
||||
platforms: [
|
||||
.macOS(.v10_15),
|
||||
.macOS(.v13),
|
||||
],
|
||||
products: [
|
||||
.executable(name: "seanut", targets: ["Seanut"])
|
||||
|
|
|
@ -57,14 +57,14 @@ extension Seanut {
|
|||
let newPath = outputPath.appendingPathComponent(rootInfo.name!)
|
||||
createDirectory(String(newPath.absoluteString.suffix(newPath.absoluteString.count - 7)))
|
||||
|
||||
let childReq: Request<[Item]> = Seanut.jellyfinRequest(
|
||||
result: [Item()],
|
||||
let childReq: Request<SearchResult> = Seanut.jellyfinRequest(
|
||||
result: SearchResult(),
|
||||
config: DownloadCommand.config,
|
||||
path: "/Items",
|
||||
method: .get,
|
||||
query: [("userId", DownloadCommand.userId!), ("parentId", id)]
|
||||
)
|
||||
guard let childInfo = try? await DownloadCommand.client.send(childReq).value else {
|
||||
guard let childInfo = try? await DownloadCommand.client.send(childReq).value.items else {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -21,18 +21,21 @@ extension Seanut {
|
|||
|
||||
mutating func run() async {
|
||||
Seanut.maybeSetLogger(options)
|
||||
|
||||
let seanutCacheFolder = "~/.seanut/".expandingTildeInPath
|
||||
|
||||
let seanutCacheFolder = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".seanut/")
|
||||
if !FileManager.default.fileExists(atPath: seanutCacheFolder.absoluteString) {
|
||||
if !FileManager.default.fileExists(atPath: seanutCacheFolder) {
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: seanutCacheFolder, withIntermediateDirectories: false)
|
||||
try FileManager.default.createDirectory(
|
||||
at: URL(filePath: seanutCacheFolder),
|
||||
withIntermediateDirectories: false
|
||||
)
|
||||
} catch {
|
||||
fatalError("could not create seanut token cache folder. quitting...")
|
||||
}
|
||||
}
|
||||
|
||||
let client = APIClient(baseURL: options.domain.toURL()!)
|
||||
// Seanut.generateJellyfinConfiguration(url: options.domain.toURL()!)
|
||||
|
||||
await Seanut.getAccessToken(
|
||||
client: client,
|
||||
|
|
|
@ -17,33 +17,31 @@ extension Seanut {
|
|||
var query: String
|
||||
|
||||
static let columnWidth = 40
|
||||
|
||||
var queryItems: [(String, String)] {
|
||||
[
|
||||
("isRecursive", "true"),
|
||||
("searchTerm", query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!),
|
||||
("fields", "Path,ChildCount".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!),
|
||||
("includeItemTypes", "MusicAlbum,MusicArtist,Movie,Book,Playlist,Series,Audio".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)
|
||||
]
|
||||
}
|
||||
|
||||
mutating func run() async {
|
||||
Seanut.maybeSetLogger(options)
|
||||
let config = Seanut.generateJellyfinConfiguration(url: options.domain.toURL()!,
|
||||
token: Seanut.retrieveAccessToken(for: options.domain.toURL()!))
|
||||
let config = Seanut.generateJellyfinConfiguration(
|
||||
url: options.domain.toURL()!,
|
||||
token: Seanut.retrieveAccessToken(for: options.domain.toURL()!)
|
||||
)
|
||||
|
||||
let client = APIClient(baseURL: options.domain.toURL()!)
|
||||
let searchRequest: Request<[Item]> = Seanut.jellyfinRequest(
|
||||
result: [Item()],
|
||||
let searchRequest: Request<SearchResult> = Seanut.jellyfinRequest(
|
||||
result: SearchResult(),
|
||||
config: config,
|
||||
path: "/Items",
|
||||
method: .get,
|
||||
query: queryItems
|
||||
query: [
|
||||
("recursive", "true"),
|
||||
("fields", "Path,ChildCount"),
|
||||
("searchTerm", query),
|
||||
("includeItemTypes", "MusicAlbum,MusicArtist,Movie,Book,Playlist,Series,Audio")
|
||||
]
|
||||
)
|
||||
|
||||
let response = try? await client.send(searchRequest).value
|
||||
|
||||
if let items = response {
|
||||
if let items = response?.items {
|
||||
print("Found \(items.count) results:")
|
||||
let header = "ID".padding(toLength: SearchCommand.columnWidth, withPad: " ", startingAt: 0) +
|
||||
"Type".padding(toLength: 15, withPad: " ", startingAt: 0) +
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
// CustomTypes.swift
|
||||
|
||||
import ArgumentParser
|
||||
|
||||
enum MediaType: String, ExpressibleByArgument {
|
||||
case book, movie, season, series, playlist, album, artist
|
||||
}
|
||||
|
||||
extension MediaType: CustomStringConvertible {
|
||||
var description: String {
|
||||
return rawValue.capitalized
|
||||
}
|
||||
}
|
|
@ -2,12 +2,30 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
struct SearchResult: Codable {
|
||||
let items: [Item]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case items = "Items"
|
||||
}
|
||||
|
||||
init() { self.items = nil }
|
||||
}
|
||||
|
||||
struct Item: Codable {
|
||||
let id: String?
|
||||
let type: String?
|
||||
let name: String?
|
||||
let childCount: Int?
|
||||
let path: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id = "Id"
|
||||
case type = "Type"
|
||||
case name = "Name"
|
||||
case childCount = "ChildCount"
|
||||
case path = "Path"
|
||||
}
|
||||
|
||||
init() {
|
||||
self.id = nil
|
||||
|
|
|
@ -4,4 +4,8 @@ struct User: Codable {
|
|||
let id: String?
|
||||
|
||||
init() { id = nil }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id = "Id"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ struct UserAuthentication: Codable {
|
|||
var username: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case password = "Password"
|
||||
case password = "Pw"
|
||||
case username = "Username"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ struct Seanut: AsyncParsableCommand {
|
|||
path: String,
|
||||
method: HTTPMethod,
|
||||
body: Encodable? = nil,
|
||||
query: [(String, String)]? = nil
|
||||
query: [(String, String?)]? = nil
|
||||
) -> Request<T> {
|
||||
let id = String(path.split(separator: "/").last!)
|
||||
return Request(
|
||||
|
@ -72,12 +72,9 @@ struct Seanut: AsyncParsableCommand {
|
|||
|
||||
let domain = client.configuration.baseURL!
|
||||
let configuration = generateJellyfinConfiguration(url: domain)
|
||||
let fileName = FileManager.default
|
||||
.homeDirectoryForCurrentUser
|
||||
.appendingPathComponent(".seanut/\(domain)")
|
||||
let fileName = "~/.seanut/\(domain.host()!)".expandingTildeInPath
|
||||
|
||||
do {
|
||||
// FIXME: remove helper function "signIn" and call path directly
|
||||
let signIn: Request<AuthenticationResponse> = jellyfinRequest(
|
||||
result: AuthenticationResponse(),
|
||||
config: configuration,
|
||||
|
@ -87,8 +84,8 @@ struct Seanut: AsyncParsableCommand {
|
|||
)
|
||||
|
||||
let resp = try await client.send(signIn).value
|
||||
|
||||
try resp.accessToken!.write(to: fileName, atomically: false, encoding: .utf8)
|
||||
try resp.accessToken!.write(to: URL(filePath: fileName), atomically: false, encoding: .utf8)
|
||||
|
||||
print("Access token retrieved ☑️")
|
||||
} catch {
|
||||
print("Failed to login with provided credentials. please try again later.")
|
||||
|
|
Loading…
Reference in New Issue