Skip to content

Commit db9ae49

Browse files
author
Jann Schafranek
committed
Add Codability, move to AsyncNetworking
1 parent c04a2a0 commit db9ae49

19 files changed

Lines changed: 127 additions & 83 deletions

Package.resolved

Lines changed: 31 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
// swift-tools-version:5.5
1+
// swift-tools-version:6.0
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription
55

66
let package = Package(
77
name: "SwiftyOpenGraph",
88
platforms: [
9-
.macOS(.v10_15),
10-
.iOS(.v13),
11-
.watchOS(.v6),
12-
.tvOS(.v13)
9+
.macOS(.v12),
10+
.iOS(.v16),
11+
.watchOS(.v9),
12+
.tvOS(.v16)
1313
],
1414
products: [
1515
// Products define the executables and libraries a package produces, and make them visible to other packages.
@@ -19,15 +19,16 @@ let package = Package(
1919
),
2020
],
2121
dependencies: [
22-
.package(url: "https://github.com/FiveSheepCo/SchafKit.git", .branch("master")),
22+
.package(url: "https://github.com/FiveSheepCo/AsyncNetworking.git", .branch("main")),
2323
.package(url: "https://github.com/scinfu/SwiftSoup", .branch("master")),
24+
.package(url: "https://github.com/FiveSheepCo/FiveKit.git", branch: "main")
2425
],
2526
targets: [
2627
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
2728
// Targets can depend on other targets in this package, and on products in packages this package depends on.
2829
.target(
2930
name: "SwiftyOpenGraph",
30-
dependencies: ["SwiftSoup", "SchafKit"]
31+
dependencies: ["SwiftSoup", "AsyncNetworking", "FiveKit"]
3132
),
3233
.testTarget(
3334
name: "SwiftyOpenGraphTests",
Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
import Foundation
22

33
func _getDuration(from string: String?) -> Int? {
4-
string.flatMap { (durationString: String) in
5-
if let duration = Int(durationString) {
6-
return duration
7-
} else if let match = durationString.regexMatches(with: "PT(\\d+)M(\\d+)S")?.first,
8-
let minutes = match.captureGroups[0]?.toInt,
9-
let seconds = match.captureGroups[1]?.toInt {
10-
return minutes * 60 + seconds
11-
} else {
12-
return nil
13-
}
4+
guard let durationString = string, !durationString.isEmpty else {
5+
return nil
146
}
7+
8+
// If it's just an integer string, return it directly
9+
if let duration = Int(durationString) {
10+
return duration
11+
}
12+
13+
// Match patterns like "PT3M25S"
14+
let regex = #/PT(\d+)M(\d+)S/#
15+
16+
if let match = durationString.wholeMatch(of: regex),
17+
let minutes = Int(match.1),
18+
let seconds = Int(match.2) {
19+
return minutes * 60 + seconds
20+
}
21+
22+
return nil
1523
}

Sources/SwiftyOpenGraph/OpenGraph.Determiner.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Foundation
33
extension OpenGraph {
44

55
/// An enum of (`a`, `an`, `the`, "", `auto`). If `auto` is chosen, the consumer of your data should chose between `a` or `an`.
6-
public enum Determiner: String {
6+
public enum Determiner: String, Sendable, Codable {
77
case a, an, the, blank = "", auto
88
}
99
}
Lines changed: 54 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
import Foundation
22
import SwiftSoup
3-
import SchafKit
3+
import AsyncNetworking
44

5-
public struct OpenGraph {
6-
/// The title of your object as it should appear within the graph, e.g., "The Rock".
5+
public enum OpenGraphError: Error {
6+
case invalidHTML
7+
case parsingFailed(String)
8+
case missingRequiredField(String)
9+
}
10+
11+
public struct OpenGraph: Codable {
12+
/// The title of the page, taken from the standard <title> element (falling back to og:title).
713
public let title: String
8-
/// The type of your object, e.g., "video.movie". Depending on the type you specify, other properties may also be required.
9-
public let type: OpenGraphType
10-
/// An image URL which should represent your object within the graph.
11-
public let image: OpenGraphImage
14+
/// The type of your object, e.g., "video.movie". Optional if it cannot be determined.
15+
public let type: OpenGraphType?
1216
/// The canonical URL of your object that will be used as its permanent ID in the graph, e.g., "https://www.imdb.com/title/tt0117500/".
1317
public let url: String
1418

15-
/// Other images included.
16-
public let additionalImages: [OpenGraphImage]
19+
/// All images included via Open Graph.
20+
public let images: [OpenGraphImage]
1721

1822
/// Audio files to accompany this object.
1923
public let audios: [OpenGraphAudio]
2024
/// Video files that complement this object.
2125
public let videos: [OpenGraphVideo]
2226
/// A one to two sentence description of your object.
23-
public let description: String?
27+
public let itemDescription: String?
2428
/// The word that appears before this object's title in a sentence. Default is "" (blank).
2529
public let determiner : Determiner
2630
/// The locale these tags are marked up in. Of the format `language_TERRITORY`. Default is `en_US`.
@@ -39,7 +43,6 @@ public struct OpenGraph {
3943
static let titleProperty = "og:title"
4044
static let urlProperty = "og:url"
4145

42-
// TODO: Implement these
4346
// Optional properties
4447
static let audioProperty = "og:audio"
4548
static let descriptionProperty = "og:description"
@@ -57,47 +60,48 @@ public struct OpenGraph {
5760
public init?(url: String) async throws {
5861
guard
5962
let html =
60-
try await SKNetworking
63+
try await AsyncNetworking
6164
.request(
6265
url: url,
6366
options: [
6467
.headerFields(value: [.userAgent: "Googlebot"]) // Some websites require this to return the open graph values
6568
]
6669
)
67-
.stringValue else {
68-
return nil
69-
}
70+
.stringValue
71+
else {
72+
throw OpenGraphError.invalidHTML
73+
}
7074

71-
self.init(html: html)
75+
try self.init(html: html)
7276
}
7377

74-
public init?(html: String) {
78+
public init?(html: String) throws {
7579
do {
7680
let doc: Document = try SwiftSoup.parse(html)
7781

7882
// Put all meta properties into a key-value pair array
79-
let parsed = try doc.select(Constants.metaTag).map({ element in
83+
let parsed = try doc.select(Constants.metaTag).map { element in
8084
_KeyValuePair(
8185
key: try element.attr(Constants.propertyAttribute),
8286
value: try element.attr(Constants.contentAttribute)
8387
)
84-
})
88+
}
8589

8690
func getFirstValue(for key: String) -> String? {
8791
parsed.first(where: { $0.key == key })?.value
8892
}
8993

90-
// Find required single values title and url
94+
// og:title and og:url are required
9195
guard
92-
let title = getFirstValue(for: Constants.titleProperty),
93-
let url = getFirstValue(for: Constants.urlProperty) else {
94-
return nil
95-
}
96+
let ogTitle = getFirstValue(for: Constants.titleProperty),
97+
let url = getFirstValue(for: Constants.urlProperty)
98+
else { return nil }
9699

97-
// Find images
100+
// Find images / audio / video
98101
var images: [OpenGraphImage] = []
99102
var audios: [OpenGraphAudio] = []
100103
var videos: [OpenGraphVideo] = []
104+
101105
for (index, kVP) in parsed.enumerated() {
102106

103107
func getRemainingKVPs() -> [_KeyValuePair] {
@@ -114,45 +118,49 @@ public struct OpenGraph {
114118
}
115119

116120
if OpenGraphAudio.Constants.urlProperties.contains(kVP.key) {
117-
audios.append(.init(
118-
url: kVP.value,
119-
followingProperties: getRemainingKVPs()
120-
))
121+
audios.append(
122+
.init(
123+
url: kVP.value,
124+
followingProperties: getRemainingKVPs()
125+
)
126+
)
121127
}
122128

123129
if OpenGraphVideo.Constants.urlProperties.contains(kVP.key) {
124-
videos.append(.init(
125-
url: kVP.value,
126-
followingProperties: getRemainingKVPs()
127-
))
130+
videos.append(
131+
.init(
132+
url: kVP.value,
133+
followingProperties: getRemainingKVPs()
134+
)
135+
)
128136
}
129137
}
130138

131-
// Make sure the first image exists
132-
guard let firstImage = images.removeFirstIfExists() else { return nil }
133-
134-
// Decode the type from the given properties
139+
// Decode the type from the given properties (now optional)
135140
let type = OpenGraphType(kVPs: parsed)
136141

137142
// Set properties
138-
self.title = title
139-
self.image = firstImage
143+
self.title = ogTitle
140144
self.type = type
141145
self.url = url
142146

143147
self.audios = audios
144148
self.videos = videos
145-
self.description = getFirstValue(for: Constants.descriptionProperty)
146-
self.determiner = Determiner(rawValue: getFirstValue(for: Constants.determinerProperty) ?? Constants.defaultDeterminer.rawValue) ?? Constants.defaultDeterminer
149+
self.itemDescription = getFirstValue(for: Constants.descriptionProperty)
150+
self.determiner = Determiner(
151+
rawValue: getFirstValue(for: Constants.determinerProperty)
152+
?? Constants.defaultDeterminer.rawValue
153+
) ?? Constants.defaultDeterminer
147154
self.siteName = getFirstValue(for: Constants.siteNameProperty)
148155

149156
self.locale = getFirstValue(for: Constants.localeProperty) ?? Constants.defaultLocale
150-
self.alternateLocales = parsed.filter({ $0.key == Constants.alternateLocaleProperty }).map(\.value)
157+
self.alternateLocales = parsed
158+
.filter { $0.key == Constants.alternateLocaleProperty }
159+
.map(\.value)
151160

152-
self.additionalImages = images
153-
} catch let err {
154-
assertionFailure(err.localizedDescription)
155-
return nil
161+
self.images = images
162+
} catch let error {
163+
throw OpenGraphError.parsingFailed(error.localizedDescription)
156164
}
157165
}
158166
}

Sources/SwiftyOpenGraph/OpenGraphAudio.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
22

3-
public struct OpenGraphAudio {
3+
public struct OpenGraphAudio: Codable {
44
public let url: String
55
public let secureUrl: String?
66
public let mimeType: String?

Sources/SwiftyOpenGraph/OpenGraphImage.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
22

3-
public struct OpenGraphImage {
3+
public struct OpenGraphImage: Codable {
44

55
public let url: String
66
public let secureUrl: String?

Sources/SwiftyOpenGraph/OpenGraphType.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
22

3-
public enum OpenGraphType {
3+
public enum OpenGraphType: Codable {
44

55
internal enum Constants {
66
static let typeProperty = "og:type"

Sources/SwiftyOpenGraph/OpenGraphType/ArticleAttributes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22

33
extension OpenGraphType {
44

5-
public struct ArticleAttributes {
5+
public struct ArticleAttributes: Codable {
66
/// When the article was first published.
77
public let publishedTime: Date?
88
/// When the article was last changed.

Sources/SwiftyOpenGraph/OpenGraphType/BookAttributes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22

33
extension OpenGraphType {
44

5-
public struct BookAttributes {
5+
public struct BookAttributes: Codable {
66
/// Who wrote this book.
77
public let authors: [String]
88
/// The ISBN.

0 commit comments

Comments
 (0)