11import Foundation
22import 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}
0 commit comments