Add local source ingest and podcasts

This commit is contained in:
Krishna Kumar
2026-07-02 01:11:03 -05:00
parent 4d875e12d6
commit f8693f4be0
18 changed files with 1044 additions and 31 deletions

View File

@@ -82,16 +82,35 @@ struct BookmarkCheck: Codable, Sendable {
struct BookmarkCreate: Codable, Sendable {
let url: String
let title: String
let description: String
let tagNames: [String]
let isArchived: Bool
let unread: Bool
let shared: Bool
enum CodingKeys: String, CodingKey {
case url, title, unread, shared
case url, title, description, unread, shared
case tagNames = "tag_names"
case isArchived = "is_archived"
}
init(
url: String,
title: String,
description: String = "",
tagNames: [String],
isArchived: Bool,
unread: Bool,
shared: Bool
) {
self.url = url
self.title = title
self.description = description
self.tagNames = tagNames
self.isArchived = isArchived
self.unread = unread
self.shared = shared
}
}
struct BookmarkUpdate: Codable, Sendable {

View File

@@ -0,0 +1,95 @@
import Foundation
struct IngestPayload: Equatable, Sendable {
let url: String
let title: String?
let notes: String
}
enum IngestPayloadParser {
static func parse(item: Any?, typeIdentifier: String? = nil) -> IngestPayload? {
if let url = item as? URL {
return payload(from: url.absoluteString, notes: "")
}
if let attributed = item as? NSAttributedString {
return payload(from: attributed.string, notes: attributed.string)
}
if let string = item as? String {
return payload(from: string, notes: shouldKeepNotes(for: typeIdentifier) ? string : "")
}
if let data = item as? Data,
let string = String(data: data, encoding: .utf8) ?? String(data: data, encoding: .ascii) {
return payload(from: string, notes: shouldKeepNotes(for: typeIdentifier) ? string : "")
}
return nil
}
private static func payload(from text: String, notes: String) -> IngestPayload? {
guard let url = firstWebURL(in: text) else { return nil }
return IngestPayload(url: url, title: nil, notes: cleanedNotes(notes, excluding: url))
}
static func firstWebURL(in text: String) -> String? {
if let direct = URL(string: text.trimmingCharacters(in: .whitespacesAndNewlines)),
isBookmarkable(direct) {
return direct.absoluteString
}
let decoded = decodeHTML(text)
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let range = NSRange(decoded.startIndex..<decoded.endIndex, in: decoded)
let match = detector?.matches(in: decoded, range: range).first { result in
guard let url = result.url else { return false }
return isBookmarkable(url)
}
guard let matchRange = match?.range,
let swiftRange = Range(matchRange, in: decoded) else {
return nil
}
let candidate = String(decoded[swiftRange])
.trimmingCharacters(in: CharacterSet(charactersIn: " \n\t\r<>\"'.,;:)]】」"))
guard let url = URL(string: candidate), isBookmarkable(url) else { return nil }
return url.absoluteString
}
private static func isBookmarkable(_ url: URL) -> Bool {
guard let scheme = url.scheme?.lowercased(),
scheme == "http" || scheme == "https",
url.host?.isEmpty == false else {
return false
}
return true
}
private static func cleanedNotes(_ notes: String, excluding url: String) -> String {
let trimmed = decodeHTML(notes)
.replacingOccurrences(of: url, with: "")
.components(separatedBy: .newlines)
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
.joined(separator: "\n")
return String(trimmed.prefix(1800))
}
private static func decodeHTML(_ text: String) -> String {
text
.replacingOccurrences(of: "&amp;", with: "&")
.replacingOccurrences(of: "&lt;", with: "<")
.replacingOccurrences(of: "&gt;", with: ">")
.replacingOccurrences(of: "&quot;", with: "\"")
.replacingOccurrences(of: "&#39;", with: "'")
}
private static func shouldKeepNotes(for typeIdentifier: String?) -> Bool {
guard let typeIdentifier else { return true }
return !typeIdentifier.lowercased().contains("url")
}
}

View File

@@ -0,0 +1,69 @@
import Foundation
enum IngestedSourceKind: String, Codable, Sendable, CaseIterable {
case text
case pdf
case file
var label: String {
switch self {
case .text: "Text"
case .pdf: "PDF"
case .file: "File"
}
}
}
struct IngestedSource: Identifiable, Codable, Sendable, Hashable {
let id: UUID
var kind: IngestedSourceKind
var title: String
var bodyText: String
var originalFilename: String?
var localFilename: String?
var tags: [String]
var createdAt: Date
var modifiedAt: Date
init(
id: UUID = UUID(),
kind: IngestedSourceKind,
title: String,
bodyText: String,
originalFilename: String? = nil,
localFilename: String? = nil,
tags: [String] = [],
createdAt: Date = Date(),
modifiedAt: Date = Date()
) {
self.id = id
self.kind = kind
self.title = title
self.bodyText = bodyText
self.originalFilename = originalFilename
self.localFilename = localFilename
self.tags = tags
self.createdAt = createdAt
self.modifiedAt = modifiedAt
}
var displayTitle: String {
let trimmed = title.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmed.isEmpty { return trimmed }
return originalFilename ?? kind.label
}
var excerpt: String {
bodyText
.trimmingCharacters(in: .whitespacesAndNewlines)
.replacingOccurrences(of: "\n", with: " ")
}
var podcastURL: String {
"marks-source://\(id.uuidString)"
}
var podcastText: String {
String(bodyText.trimmingCharacters(in: .whitespacesAndNewlines).prefix(100_000))
}
}